doc: Define a standard URI syntax for NBD URIs.
[nbd.git] / nbdsrv.c
blobb4fa80009cd1d7cae45badb1223ecc9b46de1006
1 #include "config.h"
2 #include "nbd-debug.h"
4 #include <nbdsrv.h>
6 #include <assert.h>
7 #include <ctype.h>
8 #include <netdb.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <string.h>
12 #include <syslog.h>
13 #include <unistd.h>
14 #include <pthread.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <sys/socket.h>
19 #include <treefiles.h>
20 #include "backend.h"
21 #ifdef HAVE_SYS_IOCTL_H
22 #include <sys/ioctl.h>
23 #endif
24 #ifdef HAVE_SYS_MOUNT_H
25 #include <sys/mount.h>
26 #endif
28 #define LINELEN 256 /**< Size of static buffer used to read the
29 authorization file (yuck) */
30 #include <cliserv.h>
32 bool address_matches(const char* mask, const struct sockaddr* addr, GError** err) {
33 struct addrinfo *res, *aitmp, hints;
34 char *masksep;
35 char privmask[strlen(mask)+1];
36 int masklen;
37 int addrlen = addr->sa_family == AF_INET ? 4 : 16;
39 strcpy(privmask, mask);
41 memset(&hints, 0, sizeof(hints));
42 hints.ai_family = AF_UNSPEC;
43 hints.ai_flags = AI_NUMERICHOST;
45 if((masksep = strchr(privmask, '/'))) {
46 *masksep = '\0';
47 masklen = strtol(++masksep, NULL, 10);
48 } else {
49 masklen = addrlen * 8;
52 int e;
53 if((e = getaddrinfo(privmask, NULL, &hints, &res))) {
54 g_set_error(err, NBDS_ERR, NBDS_ERR_GAI, "could not parse netmask line: %s", gai_strerror(e));
55 return false;
57 aitmp = res;
58 while(res) {
59 assert(addr->sa_family == AF_INET || addr->sa_family == AF_INET6);
60 const uint8_t* byte_s;
61 uint8_t* byte_t;
62 uint8_t mask = 0;
63 int len_left = masklen;
64 if(res->ai_family != addr->sa_family) {
65 msg(LOG_DEBUG, "client address does not match %d/%d: address family mismatch (IPv4 vs IPv6?)",
66 (int)res->ai_family, (int)addr->sa_family);
67 goto next;
69 switch(addr->sa_family) {
70 case AF_INET:
71 byte_s = (const uint8_t*)(&(((struct sockaddr_in*)addr)->sin_addr));
72 byte_t = (uint8_t*)(&(((struct sockaddr_in*)(res->ai_addr))->sin_addr));
73 break;
74 case AF_INET6:
75 byte_s = (const uint8_t*)(&(((struct sockaddr_in6*)addr)->sin6_addr));
76 byte_t = (uint8_t*)(&(((struct sockaddr_in6*)(res->ai_addr))->sin6_addr));
77 break;
79 while(len_left >= 8) {
80 if(*byte_s != *byte_t) {
81 goto next;
83 byte_s++; byte_t++;
84 len_left -= 8;
86 if(len_left) {
87 mask = getmaskbyte(len_left);
88 if((*byte_s & mask) != (*byte_t & mask)) {
89 goto next;
92 freeaddrinfo(aitmp);
93 return true;
94 next:
95 res = res->ai_next;
97 freeaddrinfo(aitmp);
98 return false;
101 uint8_t getmaskbyte(int masklen) {
102 if(masklen >= 8) {
103 return 0xFF;
105 uint8_t retval = 0;
106 for(int i = 7; i + masklen > 7; i--) {
107 retval |= 1 << i;
110 return retval;
113 int authorized_client(CLIENT *opts) {
114 FILE *f ;
115 char line[LINELEN];
117 if (opts->server->authname == NULL) {
118 msg(LOG_INFO, "No authorization file, granting access.");
119 return 1;
122 if ((f=fopen(opts->server->authname,"r"))==NULL) {
123 msg(LOG_INFO, "Can't open authorization file %s (%s).",
124 opts->server->authname, strerror(errno));
125 return 1 ;
128 while (fgets(line,LINELEN,f)!=NULL) {
129 char* pos;
130 /* Drop comments */
131 if((pos = strchr(line, '#'))) {
132 *pos = '\0';
134 /* Skip whitespace */
135 pos = line;
136 while((*pos) && isspace(*pos)) {
137 pos++;
139 /* Skip content-free lines */
140 if(!(*pos)) {
141 continue;
143 if(address_matches(line, (struct sockaddr*)&opts->clientaddr, NULL)) {
144 fclose(f);
145 return 1;
148 fclose(f);
149 return 0;
153 * duplicate server
154 * @param s the old server we want to duplicate
155 * @return new duplicated server
157 SERVER* dup_serve(const SERVER *const s) {
158 SERVER *serve = NULL;
160 serve=g_new0(SERVER, 1);
161 if(serve == NULL)
162 return NULL;
164 if(s->exportname)
165 serve->exportname = g_strdup(s->exportname);
167 serve->expected_size = s->expected_size;
169 if(s->listenaddr)
170 serve->listenaddr = g_strdup(s->listenaddr);
172 if(s->authname)
173 serve->authname = g_strdup(s->authname);
175 serve->flags = s->flags;
176 serve->virtstyle = s->virtstyle;
177 serve->cidrlen = s->cidrlen;
179 if(s->prerun)
180 serve->prerun = g_strdup(s->prerun);
182 if(s->postrun)
183 serve->postrun = g_strdup(s->postrun);
185 if(s->transactionlog)
186 serve->transactionlog = g_strdup(s->transactionlog);
188 if(s->servename)
189 serve->servename = g_strdup(s->servename);
191 if(s->cowdir)
192 serve->cowdir = g_strdup(s->cowdir);
194 serve->max_connections = s->max_connections;
196 return serve;
199 uint64_t size_autodetect(int fhandle) {
200 off_t es;
201 u64 bytes __attribute__((unused));
202 struct stat stat_buf;
203 int error;
205 #ifdef HAVE_SYS_MOUNT_H
206 #ifdef HAVE_SYS_IOCTL_H
207 #ifdef BLKGETSIZE64
208 DEBUG("looking for export size with ioctl BLKGETSIZE64\n");
209 if (!ioctl(fhandle, BLKGETSIZE64, &bytes) && bytes) {
210 return bytes;
212 #endif /* BLKGETSIZE64 */
213 #endif /* HAVE_SYS_IOCTL_H */
214 #endif /* HAVE_SYS_MOUNT_H */
216 DEBUG("looking for fhandle size with fstat\n");
217 stat_buf.st_size = 0;
218 error = fstat(fhandle, &stat_buf);
219 if (!error) {
220 /* always believe stat if a regular file as it might really
221 * be zero length */
222 if (S_ISREG(stat_buf.st_mode) || (stat_buf.st_size > 0))
223 return (uint64_t)stat_buf.st_size;
224 } else {
225 DEBUG("fstat failed: %s", strerror(errno));
228 DEBUG("looking for fhandle size with lseek SEEK_END\n");
229 es = lseek(fhandle, (off_t)0, SEEK_END);
230 if (es > ((off_t)0)) {
231 return (uint64_t)es;
232 } else {
233 DEBUG("lseek failed: %d", errno==EBADF?1:(errno==ESPIPE?2:(errno==EINVAL?3:4)));
236 DEBUG("Could not find size of exported block device: %s", strerror(errno));
237 return UINT64_MAX;
240 int exptrim(struct nbd_request* req, CLIENT* client) {
241 /* Caller did range checking */
242 assert(!(client->server->flags & F_READONLY));
243 assert(req->from + req->len <= client->exportsize);
244 /* For copy-on-write, we should trim on the diff file. Not yet
245 * implemented. */
246 if(client->server->flags & F_COPYONWRITE) {
247 DEBUG("TRIM not supported yet on copy-on-write exports");
248 return 0;
250 if (client->server->flags & F_TREEFILES) {
251 /* start address of first block to be trimmed */
252 off_t min = ( ( req->from + TREEPAGESIZE - 1 ) / TREEPAGESIZE) * TREEPAGESIZE;
253 /* start address of first block NOT to be trimmed */
254 off_t max = ( ( req->from + req->len ) / TREEPAGESIZE) * TREEPAGESIZE;
255 while (min<max) {
256 delete_treefile(client->exportname,client->exportsize,min);
257 min+=TREEPAGESIZE;
259 DEBUG("Performed TRIM request on TREE structure from %llu to %llu", (unsigned long long) req->from, (unsigned long long) req->len);
260 return 0;
262 FILE_INFO cur = g_array_index(client->export, FILE_INFO, 0);
263 FILE_INFO next;
264 int i = 1;
265 do {
266 if(i<client->export->len) {
267 next = g_array_index(client->export, FILE_INFO, i);
268 } else {
269 next.fhandle = -1;
270 next.startoff = client->exportsize;
272 if(cur.startoff <= req->from && next.startoff - cur.startoff >= req->len) {
273 off_t reqoff = req->from - cur.startoff;
274 off_t curlen = next.startoff - reqoff;
275 off_t reqlen = curlen - reqoff > req->len ? req->len : curlen - reqoff;
276 punch_hole(cur.fhandle, reqoff, reqlen);
278 cur = next;
279 i++;
280 } while(i < client->export->len && cur.startoff < (req->from + req->len));
281 DEBUG("Performed TRIM request from %llu to %llu", (unsigned long long) req->from, (unsigned long long) req->len);
282 return 0;
285 pthread_mutex_t cntmutex = PTHREAD_MUTEX_INITIALIZER;
287 SERVER* serve_inc_ref(SERVER *s) {
288 pthread_mutex_lock(&cntmutex);
289 s->refcnt++;
290 pthread_mutex_unlock(&cntmutex);
291 return s;
294 SERVER* serve_dec_ref(SERVER *s) {
295 pthread_mutex_lock(&cntmutex);
296 if(--(s->refcnt) == 0) {
297 g_free(s);
298 s = NULL;
300 pthread_mutex_unlock(&cntmutex);
301 return s;