6947203c0f9b91ee362458602a405173f3a816f6
[nfs-utils.git] / utils / mountd / cache.c
1
2 /*
3  * Handle communication with knfsd internal cache
4  *
5  * We open /proc/net/rpc/{auth.unix.ip,nfsd.export,nfsd.fh}/channel
6  * and listen for requests (using my_svc_run)
7  * 
8  */
9
10 #ifdef HAVE_CONFIG_H
11 #include <config.h>
12 #endif
13
14 #include <sys/types.h>
15 #include <sys/select.h>
16 #include <sys/stat.h>
17 #include <time.h>
18 #include <netinet/in.h>
19 #include <arpa/inet.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <errno.h>
23 #include <ctype.h>
24 #include "misc.h"
25 #include "nfslib.h"
26 #include "exportfs.h"
27 #include "mountd.h"
28 #include "xmalloc.h"
29
30 #include "blkid/blkid.h"
31
32
33 enum nfsd_fsid {
34         FSID_DEV = 0,
35         FSID_NUM,
36         FSID_MAJOR_MINOR,
37         FSID_ENCODE_DEV,
38         FSID_UUID4_INUM,
39         FSID_UUID8,
40         FSID_UUID16,
41         FSID_UUID16_INUM,
42 };
43
44 /*
45  * Support routines for text-based upcalls.
46  * Fields are separated by spaces.
47  * Fields are either mangled to quote space tab newline slosh with slosh
48  * or a hexified with a leading \x
49  * Record is terminated with newline.
50  *
51  */
52 int cache_export_ent(char *domain, struct exportent *exp);
53
54
55 char *lbuf  = NULL;
56 int lbuflen = 0;
57
58 void auth_unix_ip(FILE *f)
59 {
60         /* requests are
61          *  class IP-ADDR
62          * Ignore if class != "nfsd"
63          * Otherwise find domainname and write back:
64          *
65          *  "nfsd" IP-ADDR expiry domainname
66          */
67         char *cp;
68         char class[20];
69         char ipaddr[20];
70         char *client;
71         struct in_addr addr;
72         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
73                 return;
74
75         cp = lbuf;
76
77         if (qword_get(&cp, class, 20) <= 0 ||
78             strcmp(class, "nfsd") != 0)
79                 return;
80
81         if (qword_get(&cp, ipaddr, 20) <= 0)
82                 return;
83
84         if (inet_aton(ipaddr, &addr)==0)
85                 return;
86
87         auth_reload();
88
89         /* addr is a valid, interesting address, find the domain name... */
90         client = client_compose(addr);
91
92         
93         qword_print(f, "nfsd");
94         qword_print(f, ipaddr);
95         qword_printint(f, time(0)+30*60);
96         if (client)
97                 qword_print(f, *client?client:"DEFAULT");
98         qword_eol(f);
99
100         if (client) free(client);
101         
102 }
103
104 int get_uuid(char *path, char *uuid, int uuidlen, char *u)
105 {
106         /* extract hex digits from uuidstr and compose a uuid
107          * of the given length (max 16), xoring bytes to make
108          * a smaller uuid.  Then compare with uuid
109          */
110         int i = 0;
111         const char *val;
112
113         if (path) {
114                 static blkid_cache cache = NULL;
115                 struct stat stb;
116                 char *devname;
117                 blkid_tag_iterate iter;
118                 blkid_dev dev;
119                 const char *type;
120                 if (cache == NULL)
121                         blkid_get_cache(&cache, NULL);
122
123                 blkid_probe_all_new(cache);
124
125                 if (stat(path, &stb) != 0)
126                         return 0;
127                 devname = blkid_devno_to_devname(stb.st_dev);
128                 if (!devname)
129                         return 0;
130                 dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
131                 free(devname);
132                 if (!dev)
133                         return 0;
134                 iter = blkid_tag_iterate_begin(dev);
135                 if (!iter)
136                         return 0;
137                 while (blkid_tag_next(iter, &type, &val) == 0)
138                         if (strcmp(type, "UUID") == 0)
139                                 break;
140                 blkid_tag_iterate_end(iter);
141                 if (!type)
142                         return 0;
143         } else {
144                 val = uuid;
145         }
146         
147         memset(u, 0, uuidlen);
148         for ( ; *val ; val++) {
149                 char c = *val;
150                 if (!isxdigit(c))
151                         continue;
152                 if (isalpha(c)) {
153                         if (isupper(c))
154                                 c = c - 'A' + 10;
155                         else
156                                 c = c - 'a' + 10;
157                 } else
158                         c = c - '0' + 0;
159                 if ((i&1) == 0)
160                         c <<= 4;
161                 u[i/2] ^= c;
162                 i++;
163                 if (i == uuidlen*2)
164                         i = 0;
165         }
166         return 1;
167 }
168         
169
170 void nfsd_fh(FILE *f)
171 {
172         /* request are:
173          *  domain fsidtype fsid
174          * interpret fsid, find export point and options, and write:
175          *  domain fsidtype fsid expiry path
176          */
177         char *cp;
178         char *dom;
179         int fsidtype;
180         int fsidlen;
181         unsigned int dev, major=0, minor=0;
182         unsigned int inode=0;
183         unsigned long long inode64;
184         unsigned int fsidnum=0;
185         char fsid[32];
186         struct exportent *found = NULL;
187         nfs_export *exp;
188         int i;
189         int dev_missing = 0;
190         int uuidlen = 0;
191         char *fhuuid = NULL;
192
193         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
194                 return;
195
196         cp = lbuf;
197
198         dom = malloc(strlen(cp));
199         if (dom == NULL)
200                 return;
201         if (qword_get(&cp, dom, strlen(cp)) <= 0)
202                 goto out;
203         if (qword_get_int(&cp, &fsidtype) != 0)
204                 goto out;
205         if (fsidtype < 0 || fsidtype > 7)
206                 goto out; /* unknown type */
207         if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
208                 goto out;
209         switch(fsidtype) {
210         case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */
211                 if (fsidlen != 8)
212                         goto out;
213                 memcpy(&dev, fsid, 4);
214                 memcpy(&inode, fsid+4, 4);
215                 major = ntohl(dev)>>16;
216                 minor = ntohl(dev) & 0xFFFF;
217                 break;
218
219         case FSID_NUM: /* 4 bytes - fsid */
220                 if (fsidlen != 4)
221                         goto out;
222                 memcpy(&fsidnum, fsid, 4);
223                 break;
224
225         case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode 
226                  * This format is never actually used but was
227                  * an historical accident
228                  */
229                 if (fsidlen != 12)
230                         goto out;
231                 memcpy(&dev, fsid, 4); major = ntohl(dev);
232                 memcpy(&dev, fsid+4, 4); minor = ntohl(dev);
233                 memcpy(&inode, fsid+8, 4);
234                 break;
235
236         case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */
237                 /* This is *host* endian, not net-byte-order, because
238                  * no-one outside this host has any business interpreting it
239                  */
240                 if (fsidlen != 8)
241                         goto out;
242                 memcpy(&dev, fsid, 4);
243                 memcpy(&inode, fsid+4, 4);
244                 major = (dev & 0xfff00) >> 8;
245                 minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
246                 break;
247
248         case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */
249                 if (fsidlen != 8)
250                         goto out;
251                 memcpy(&inode, fsid, 4);
252                 uuidlen = 4;
253                 fhuuid = fsid+4;
254                 break;
255         case FSID_UUID8: /* 8 byte uuid */
256                 if (fsidlen != 8)
257                         goto out;
258                 uuidlen = 8;
259                 fhuuid = fsid;
260                 break;
261         case FSID_UUID16: /* 16 byte uuid */
262                 if (fsidlen != 16)
263                         goto out;
264                 uuidlen = 16;
265                 fhuuid = fsid;
266                 break;
267         case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */
268                 if (fsidlen != 24)
269                         goto out;
270                 memcpy(&inode64, fsid, 8);
271                 inode = inode64;
272                 uuidlen = 16;
273                 fhuuid = fsid+8;
274                 break;
275         }
276
277         auth_reload();
278
279         /* Now determine export point for this fsid/domain */
280         for (i=0 ; i < MCL_MAXTYPES; i++) {
281                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
282                         struct stat stb;
283                         char u[16];                     
284
285                         if (!client_member(dom, exp->m_client->m_hostname))
286                                 continue;
287                         if (exp->m_export.e_mountpoint &&
288                             !is_mountpoint(exp->m_export.e_mountpoint[0]?
289                                            exp->m_export.e_mountpoint:
290                                            exp->m_export.e_path))
291                                 dev_missing ++;
292                         if (stat(exp->m_export.e_path, &stb) != 0)
293                                 continue;
294                         switch(fsidtype){
295                         case FSID_DEV:
296                         case FSID_MAJOR_MINOR:
297                         case FSID_ENCODE_DEV:
298                                 if (stb.st_ino != inode)
299                                         continue;
300                                 if (major != major(stb.st_dev) ||
301                                     minor != minor(stb.st_dev))
302                                         continue;
303                                 break;
304                         case FSID_NUM:
305                                 if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
306                                      exp->m_export.e_fsid != fsidnum))
307                                         continue;
308                                 break;
309                         case FSID_UUID4_INUM:
310                         case FSID_UUID16_INUM:
311                                 if (stb.st_ino != inode)
312                                         continue;
313                                 goto check_uuid;
314                         case FSID_UUID8:
315                         case FSID_UUID16:
316                                 if (!is_mountpoint(exp->m_export.e_path))
317                                         continue;
318                         check_uuid:
319                                 if (exp->m_export.e_uuid)
320                                         get_uuid(NULL, exp->m_export.e_uuid,
321                                                  uuidlen, u);
322                                 else if (get_uuid(exp->m_export.e_path, NULL,
323                                                   uuidlen, u) == 0)
324                                         continue;
325
326                                 if (memcmp(u, fhuuid, uuidlen) != 0)
327                                         continue;
328                                 break;
329                         }
330                         /* It's a match !! */
331                         if (!found)
332                                 found = &exp->m_export;
333                         else if (strcmp(found->e_path, exp->m_export.e_path)!= 0)
334                         {
335                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
336                                      found->e_path, exp->m_export.e_path, dom);
337                         }
338                 }
339         }
340         if (found && 
341             found->e_mountpoint &&
342             !is_mountpoint(found->e_mountpoint[0]?
343                            found->e_mountpoint:
344                            found->e_path)) {
345                 /* Cannot export this yet 
346                  * should log a warning, but need to rate limit
347                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
348                    found->e_path, found->e_mountpoint);
349                  */
350                 /* FIXME we need to make sure we re-visit this later */
351                 goto out;
352         }
353         if (!found && dev_missing) {
354                 /* The missing dev could be what we want, so just be
355                  * quite rather than returning stale yet
356                  */
357                 goto out;
358         }
359
360         if (found)
361                 cache_export_ent(dom, found);
362
363         qword_print(f, dom);
364         qword_printint(f, fsidtype);
365         qword_printhex(f, fsid, fsidlen);
366         qword_printint(f, time(0)+30*60);
367         if (found)
368                 qword_print(f, found->e_path);
369         qword_eol(f);
370  out:
371         free(dom);
372         return;         
373 }
374
375 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
376 {
377         qword_print(f, domain);
378         qword_print(f, path);
379         qword_printint(f, time(0)+30*60);
380         if (exp) {
381                 qword_printint(f, exp->e_flags);
382                 qword_printint(f, exp->e_anonuid);
383                 qword_printint(f, exp->e_anongid);
384                 qword_printint(f, exp->e_fsid);
385                 if (exp->e_uuid == NULL) {
386                         char u[16];
387                         if (get_uuid(exp->e_path, NULL, 16, u)) {
388                                 qword_print(f, "uuid");
389                                 qword_printhex(f, u, 16);
390                         }
391                 } else if (exp->e_uuid) {
392                         qword_print(f, "uuid");
393                         qword_printhex(f, exp->e_uuid, 16);
394                 }
395         }
396         return qword_eol(f);
397 }
398
399 void nfsd_export(FILE *f)
400 {
401         /* requests are:
402          *  domain path
403          * determine export options and return:
404          *  domain path expiry flags anonuid anongid fsid
405          */
406
407         char *cp;
408         int i;
409         char *dom, *path;
410         nfs_export *exp, *found = NULL;
411
412
413         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
414                 return;
415
416         cp = lbuf;
417         dom = malloc(strlen(cp));
418         path = malloc(strlen(cp));
419
420         if (!dom || !path)
421                 goto out;
422
423         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
424                 goto out;
425         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
426                 goto out;
427
428         auth_reload();
429
430         /* now find flags for this export point in this domain */
431         for (i=0 ; i < MCL_MAXTYPES; i++) {
432                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
433                         if (!client_member(dom, exp->m_client->m_hostname))
434                                 continue;
435                         if (strcmp(path, exp->m_export.e_path))
436                                 continue;
437                         if (!found)
438                                 found = exp;
439                         else {
440                                 xlog(L_WARNING, "%s exported to both %s and %s in %s",
441                                      path, exp->m_client->m_hostname, found->m_client->m_hostname,
442                                      dom);
443                         }
444                 }
445         }
446
447         if (found) {
448                 dump_to_cache(f, dom, path, &found->m_export);
449                 mountlist_add(dom, path);
450         } else {
451                 dump_to_cache(f, dom, path, NULL);
452         }
453  out:
454         if (dom) free(dom);
455         if (path) free(path);
456 }
457
458
459 struct {
460         char *cache_name;
461         void (*cache_handle)(FILE *f);
462         FILE *f;
463 } cachelist[] = {
464         { "auth.unix.ip", auth_unix_ip},
465         { "nfsd.export", nfsd_export},
466         { "nfsd.fh", nfsd_fh},
467         { NULL, NULL }
468 };
469
470 void cache_open(void) 
471 {
472         int i;
473         for (i=0; cachelist[i].cache_name; i++ ){
474                 char path[100];
475                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
476                 cachelist[i].f = fopen(path, "r+");
477         }
478 }
479
480 void cache_set_fds(fd_set *fdset)
481 {
482         int i;
483         for (i=0; cachelist[i].cache_name; i++) {
484                 if (cachelist[i].f)
485                         FD_SET(fileno(cachelist[i].f), fdset);
486         }
487 }
488
489 int cache_process_req(fd_set *readfds) 
490 {
491         int i;
492         int cnt = 0;
493         for (i=0; cachelist[i].cache_name; i++) {
494                 if (cachelist[i].f != NULL &&
495                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
496                         cnt++;
497                         cachelist[i].cache_handle(cachelist[i].f);
498                         FD_CLR(fileno(cachelist[i].f), readfds);
499                 }
500         }
501         return cnt;
502 }
503
504
505 /*
506  * Give IP->domain and domain+path->options to kernel
507  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
508  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
509  */
510
511 int cache_export_ent(char *domain, struct exportent *exp)
512 {
513         int err;
514         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
515         if (!f)
516                 return -1;
517
518         err = dump_to_cache(f, domain, exp->e_path, exp);
519         fclose(f);
520         mountlist_add(domain, exp->e_path);
521         return err;
522 }
523
524 int cache_export(nfs_export *exp)
525 {
526         int err;
527         FILE *f;
528
529         if (exp->m_export.e_maptype != CLE_MAP_IDENT) {
530                 xlog(L_ERROR, "%s: unsupported mapping; kernel supports only 'identity' (default)",
531                     exp->m_export.m_path);
532                 return;
533         }
534
535         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
536         if (!f)
537                 return -1;
538
539         qword_print(f, "nfsd");
540         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
541         qword_printint(f, time(0)+30*60);
542         qword_print(f, exp->m_client->m_hostname);
543         err = qword_eol(f);
544         
545         fclose(f);
546
547         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export)
548                 || err;
549         return err;
550 }
551
552 /* Get a filehandle.
553  * { 
554  *   echo $domain $path $length 
555  *   read filehandle <&0
556  * } <> /proc/fs/nfsd/filehandle
557  */
558 struct nfs_fh_len *
559 cache_get_filehandle(nfs_export *exp, int len, char *p)
560 {
561         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
562         char buf[200];
563         char *bp = buf;
564         int failed;
565         static struct nfs_fh_len fh;
566
567         if (!f)
568                 f = fopen("/proc/fs/nfs/filehandle", "r+");
569         if (!f)
570                 return NULL;
571
572         qword_print(f, exp->m_client->m_hostname);
573         qword_print(f, p);
574         qword_printint(f, len); 
575         failed = qword_eol(f);
576         
577         if (!failed)
578                 failed = (fgets(buf, sizeof(buf), f) == NULL);
579         fclose(f);
580         if (failed)
581                 return NULL;
582         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
583         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
584         return &fh;
585 }
586