]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/mountd/cache.c
Merge branch 'upstream'
[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 <pwd.h>
25 #include <grp.h>
26 #include <mntent.h>
27 #include "misc.h"
28 #include "nfslib.h"
29 #include "exportfs.h"
30 #include "mountd.h"
31 #include "xmalloc.h"
32 #include "fsloc.h"
33
34 #ifdef USE_BLKID
35 #include "blkid/blkid.h"
36 #endif
37
38
39 enum nfsd_fsid {
40         FSID_DEV = 0,
41         FSID_NUM,
42         FSID_MAJOR_MINOR,
43         FSID_ENCODE_DEV,
44         FSID_UUID4_INUM,
45         FSID_UUID8,
46         FSID_UUID16,
47         FSID_UUID16_INUM,
48 };
49
50 /*
51  * Support routines for text-based upcalls.
52  * Fields are separated by spaces.
53  * Fields are either mangled to quote space tab newline slosh with slosh
54  * or a hexified with a leading \x
55  * Record is terminated with newline.
56  *
57  */
58 int cache_export_ent(char *domain, struct exportent *exp, char *p);
59
60
61 char *lbuf  = NULL;
62 int lbuflen = 0;
63
64 void auth_unix_ip(FILE *f)
65 {
66         /* requests are
67          *  class IP-ADDR
68          * Ignore if class != "nfsd"
69          * Otherwise find domainname and write back:
70          *
71          *  "nfsd" IP-ADDR expiry domainname
72          */
73         char *cp;
74         char class[20];
75         char ipaddr[20];
76         char *client;
77         struct in_addr addr;
78         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
79                 return;
80
81         cp = lbuf;
82
83         if (qword_get(&cp, class, 20) <= 0 ||
84             strcmp(class, "nfsd") != 0)
85                 return;
86
87         if (qword_get(&cp, ipaddr, 20) <= 0)
88                 return;
89
90         if (inet_aton(ipaddr, &addr)==0)
91                 return;
92
93         auth_reload();
94
95         /* addr is a valid, interesting address, find the domain name... */
96         client = client_compose(addr);
97
98         
99         qword_print(f, "nfsd");
100         qword_print(f, ipaddr);
101         qword_printint(f, time(0)+30*60);
102         if (client)
103                 qword_print(f, *client?client:"DEFAULT");
104         qword_eol(f);
105
106         if (client) free(client);
107         
108 }
109
110 void auth_unix_gid(FILE *f)
111 {
112         /* Request are
113          *  uid
114          * reply is
115          *  uid expiry count list of group ids
116          */
117         int uid;
118         struct passwd *pw;
119         gid_t glist[100], *groups = glist;
120         int ngroups = 100;
121         int rv, i;
122         char *cp;
123
124         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
125                 return;
126
127         cp = lbuf;
128         if (qword_get_int(&cp, &uid) != 0)
129                 return;
130
131         pw = getpwuid(uid);
132         if (!pw)
133                 rv = -1;
134         else {
135                 rv = getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups);
136                 if (rv == -1 && ngroups >= 100) {
137                         groups = malloc(sizeof(gid_t)*ngroups);
138                         if (!groups)
139                                 rv = -1;
140                         else
141                                 rv = getgrouplist(pw->pw_name, pw->pw_gid,
142                                                   groups, &ngroups);
143                 }
144         }
145         qword_printint(f, uid);
146         qword_printint(f, time(0)+30*60);
147         if (rv >= 0) {
148                 qword_printint(f, ngroups);
149                 for (i=0; i<ngroups; i++)
150                         qword_printint(f, groups[i]);
151         }
152         qword_eol(f);
153         if (groups != glist)
154                 free(groups);
155 }
156
157 #if USE_BLKID
158 int get_uuid(char *path, char *uuid, int uuidlen, char *u)
159 {
160         /* extract hex digits from uuidstr and compose a uuid
161          * of the given length (max 16), xoring bytes to make
162          * a smaller uuid.  Then compare with uuid
163          */
164         int i = 0;
165         const char *val;
166
167         if (path) {
168                 static blkid_cache cache = NULL;
169                 struct stat stb;
170                 char *devname;
171                 blkid_tag_iterate iter;
172                 blkid_dev dev;
173                 const char *type;
174                 if (cache == NULL)
175                         blkid_get_cache(&cache, NULL);
176
177                 blkid_probe_all_new(cache);
178
179                 if (stat(path, &stb) != 0)
180                         return 0;
181                 devname = blkid_devno_to_devname(stb.st_dev);
182                 if (!devname)
183                         return 0;
184                 dev = blkid_get_dev(cache, devname, BLKID_DEV_NORMAL);
185                 free(devname);
186                 if (!dev)
187                         return 0;
188                 iter = blkid_tag_iterate_begin(dev);
189                 if (!iter)
190                         return 0;
191                 while (blkid_tag_next(iter, &type, &val) == 0)
192                         if (strcmp(type, "UUID") == 0)
193                                 break;
194                 blkid_tag_iterate_end(iter);
195                 if (!type)
196                         return 0;
197         } else {
198                 val = uuid;
199         }
200         
201         memset(u, 0, uuidlen);
202         for ( ; *val ; val++) {
203                 char c = *val;
204                 if (!isxdigit(c))
205                         continue;
206                 if (isalpha(c)) {
207                         if (isupper(c))
208                                 c = c - 'A' + 10;
209                         else
210                                 c = c - 'a' + 10;
211                 } else
212                         c = c - '0' + 0;
213                 if ((i&1) == 0)
214                         c <<= 4;
215                 u[i/2] ^= c;
216                 i++;
217                 if (i == uuidlen*2)
218                         i = 0;
219         }
220         return 1;
221 }
222 #endif
223
224 /* Iterate through /etc/mtab, finding mountpoints
225  * at or below a given path
226  */
227 static char *next_mnt(void **v, char *p)
228 {
229         FILE *f;
230         struct mntent *me;
231         int l = strlen(p);
232         if (*v == NULL) {
233                 f = setmntent("/etc/mtab", "r");
234                 *v = f;
235         } else
236                 f = *v;
237         while ((me = getmntent(f)) != NULL &&
238                (strncmp(me->mnt_dir, p, l) != 0 ||
239                 me->mnt_dir[l] != '/'))
240                 ;
241         if (me == NULL) {
242                 endmntent(f);
243                 *v = NULL;
244                 return NULL;
245         }
246         return me->mnt_dir;
247 }
248
249 void nfsd_fh(FILE *f)
250 {
251         /* request are:
252          *  domain fsidtype fsid
253          * interpret fsid, find export point and options, and write:
254          *  domain fsidtype fsid expiry path
255          */
256         char *cp;
257         char *dom;
258         int fsidtype;
259         int fsidlen;
260         unsigned int dev, major=0, minor=0;
261         unsigned int inode=0;
262         unsigned long long inode64;
263         unsigned int fsidnum=0;
264         char fsid[32];
265         struct exportent *found = NULL;
266         char *found_path = NULL;
267         nfs_export *exp;
268         int i;
269         int dev_missing = 0;
270         int uuidlen = 0;
271         char *fhuuid = NULL;
272
273         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
274                 return;
275
276         cp = lbuf;
277
278         dom = malloc(strlen(cp));
279         if (dom == NULL)
280                 return;
281         if (qword_get(&cp, dom, strlen(cp)) <= 0)
282                 goto out;
283         if (qword_get_int(&cp, &fsidtype) != 0)
284                 goto out;
285         if (fsidtype < 0 || fsidtype > 7)
286                 goto out; /* unknown type */
287         if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
288                 goto out;
289         switch(fsidtype) {
290         case FSID_DEV: /* 4 bytes: 2 major, 2 minor, 4 inode */
291                 if (fsidlen != 8)
292                         goto out;
293                 memcpy(&dev, fsid, 4);
294                 memcpy(&inode, fsid+4, 4);
295                 major = ntohl(dev)>>16;
296                 minor = ntohl(dev) & 0xFFFF;
297                 break;
298
299         case FSID_NUM: /* 4 bytes - fsid */
300                 if (fsidlen != 4)
301                         goto out;
302                 memcpy(&fsidnum, fsid, 4);
303                 break;
304
305         case FSID_MAJOR_MINOR: /* 12 bytes: 4 major, 4 minor, 4 inode 
306                  * This format is never actually used but was
307                  * an historical accident
308                  */
309                 if (fsidlen != 12)
310                         goto out;
311                 memcpy(&dev, fsid, 4); major = ntohl(dev);
312                 memcpy(&dev, fsid+4, 4); minor = ntohl(dev);
313                 memcpy(&inode, fsid+8, 4);
314                 break;
315
316         case FSID_ENCODE_DEV: /* 8 bytes: 4 byte packed device number, 4 inode */
317                 /* This is *host* endian, not net-byte-order, because
318                  * no-one outside this host has any business interpreting it
319                  */
320                 if (fsidlen != 8)
321                         goto out;
322                 memcpy(&dev, fsid, 4);
323                 memcpy(&inode, fsid+4, 4);
324                 major = (dev & 0xfff00) >> 8;
325                 minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
326                 break;
327
328         case FSID_UUID4_INUM: /* 4 byte inode number and 4 byte uuid */
329                 if (fsidlen != 8)
330                         goto out;
331                 memcpy(&inode, fsid, 4);
332                 uuidlen = 4;
333                 fhuuid = fsid+4;
334                 break;
335         case FSID_UUID8: /* 8 byte uuid */
336                 if (fsidlen != 8)
337                         goto out;
338                 uuidlen = 8;
339                 fhuuid = fsid;
340                 break;
341         case FSID_UUID16: /* 16 byte uuid */
342                 if (fsidlen != 16)
343                         goto out;
344                 uuidlen = 16;
345                 fhuuid = fsid;
346                 break;
347         case FSID_UUID16_INUM: /* 8 byte inode number and 16 byte uuid */
348                 if (fsidlen != 24)
349                         goto out;
350                 memcpy(&inode64, fsid, 8);
351                 inode = inode64;
352                 uuidlen = 16;
353                 fhuuid = fsid+8;
354                 break;
355         }
356
357         auth_reload();
358
359         /* Now determine export point for this fsid/domain */
360         for (i=0 ; i < MCL_MAXTYPES; i++) {
361                 nfs_export *next_exp;
362                 for (exp = exportlist[i]; exp; exp = next_exp) {
363                         struct stat stb;
364                         char u[16];
365                         char *path;
366
367                         if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
368                                 static nfs_export *prev = NULL;
369                                 static void *mnt = NULL;
370                                 
371                                 if (prev == exp) {
372                                         /* try a submount */
373                                         path = next_mnt(&mnt, exp->m_export.e_path);
374                                         if (!path) {
375                                                 next_exp = exp->m_next;
376                                                 prev = NULL;
377                                                 continue;
378                                         }
379                                         next_exp = exp;
380                                 } else {
381                                         prev = exp;
382                                         mnt = NULL;
383                                         path = exp->m_export.e_path;
384                                         next_exp = exp;
385                                 }
386                         } else {
387                                 path = exp->m_export.e_path;
388                                 next_exp = exp->m_next;
389                         }
390
391                         if (!client_member(dom, exp->m_client->m_hostname))
392                                 continue;
393                         if (exp->m_export.e_mountpoint &&
394                             !is_mountpoint(exp->m_export.e_mountpoint[0]?
395                                            exp->m_export.e_mountpoint:
396                                            exp->m_export.e_path))
397                                 dev_missing ++;
398                         if (stat(path, &stb) != 0)
399                                 continue;
400                         if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
401                                 continue;
402                         }
403                         switch(fsidtype){
404                         case FSID_DEV:
405                         case FSID_MAJOR_MINOR:
406                         case FSID_ENCODE_DEV:
407                                 if (stb.st_ino != inode)
408                                         continue;
409                                 if (major != major(stb.st_dev) ||
410                                     minor != minor(stb.st_dev))
411                                         continue;
412                                 break;
413                         case FSID_NUM:
414                                 if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
415                                      exp->m_export.e_fsid != fsidnum))
416                                         continue;
417                                 break;
418                         case FSID_UUID4_INUM:
419                         case FSID_UUID16_INUM:
420                                 if (stb.st_ino != inode)
421                                         continue;
422                                 goto check_uuid;
423                         case FSID_UUID8:
424                         case FSID_UUID16:
425                                 if (!is_mountpoint(path))
426                                         continue;
427                         check_uuid:
428 #if USE_BLKID
429                                 if (exp->m_export.e_uuid)
430                                         get_uuid(NULL, exp->m_export.e_uuid,
431                                                  uuidlen, u);
432                                 else if (get_uuid(path, NULL,
433                                                   uuidlen, u) == 0)
434                                         continue;
435
436                                 if (memcmp(u, fhuuid, uuidlen) != 0)
437                                         continue;
438                                 break;
439 #else
440                                 continue;
441 #endif
442                         }
443                         /* It's a match !! */
444                         if (!found) {
445                                 found = &exp->m_export;
446                                 found_path = strdup(path);
447                         } else if (strcmp(found->e_path, exp->m_export.e_path)!= 0)
448                         {
449                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
450                                      found_path, path, dom);
451                         }
452                 }
453         }
454         if (found && 
455             found->e_mountpoint &&
456             !is_mountpoint(found->e_mountpoint[0]?
457                            found->e_mountpoint:
458                            found->e_path)) {
459                 /* Cannot export this yet 
460                  * should log a warning, but need to rate limit
461                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
462                    found->e_path, found->e_mountpoint);
463                  */
464                 /* FIXME we need to make sure we re-visit this later */
465                 goto out;
466         }
467         if (!found && dev_missing) {
468                 /* The missing dev could be what we want, so just be
469                  * quite rather than returning stale yet
470                  */
471                 goto out;
472         }
473
474         if (found)
475                 if (cache_export_ent(dom, found, found_path) < 0)
476                         found = 0;
477
478         qword_print(f, dom);
479         qword_printint(f, fsidtype);
480         qword_printhex(f, fsid, fsidlen);
481         /* The fsid -> path lookup can be quite expensive as it
482          * potentially stats and reads lots of devices, and some of those
483          * might have spun-down.  The Answer is not likely to
484          * change underneath us, and an 'exportfs -f' can always
485          * remove this from the kernel, so use a really log
486          * timeout.  Maybe this should be configurable on the command
487          * line.
488          */
489         qword_printint(f, 0x7fffffff);
490         if (found)
491                 qword_print(f, found->e_path);
492         qword_eol(f);
493  out:
494         free(dom);
495         return;         
496 }
497
498 static void write_fsloc(FILE *f, struct exportent *ep, char *path)
499 {
500         struct servers *servers;
501
502         if (ep->e_fslocmethod == FSLOC_NONE)
503                 return;
504
505         servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata, path);
506         if (!servers)
507                 return;
508         qword_print(f, "fsloc");
509         qword_printint(f, servers->h_num);
510         if (servers->h_num >= 0) {
511                 int i;
512                 for (i=0; i<servers->h_num; i++) {
513                         qword_print(f, servers->h_mp[i]->h_host);
514                         qword_print(f, servers->h_mp[i]->h_path);
515                 }
516         }
517         qword_printint(f, servers->h_referral);
518         release_replicas(servers);
519 }
520
521 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
522 {
523         qword_print(f, domain);
524         qword_print(f, path);
525         qword_printint(f, time(0)+30*60);
526         if (exp) {
527                 qword_printint(f, exp->e_flags);
528                 qword_printint(f, exp->e_anonuid);
529                 qword_printint(f, exp->e_anongid);
530                 qword_printint(f, exp->e_fsid);
531                 write_fsloc(f, exp, path);
532 #if USE_BLKID
533                 if (exp->e_uuid == NULL) {
534                         char u[16];
535                         if (get_uuid(path, NULL, 16, u)) {
536                                 qword_print(f, "uuid");
537                                 qword_printhex(f, u, 16);
538                         }
539                 } else if (exp->e_uuid) {
540                         qword_print(f, "uuid");
541                         qword_printhex(f, exp->e_uuid, 16);
542                 }
543 #endif
544         }
545         return qword_eol(f);
546 }
547
548 void nfsd_export(FILE *f)
549 {
550         /* requests are:
551          *  domain path
552          * determine export options and return:
553          *  domain path expiry flags anonuid anongid fsid
554          */
555
556         char *cp;
557         int i;
558         char *dom, *path;
559         nfs_export *exp, *found = NULL;
560         int found_type = 0;
561
562
563         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
564                 return;
565
566         cp = lbuf;
567         dom = malloc(strlen(cp));
568         path = malloc(strlen(cp));
569
570         if (!dom || !path)
571                 goto out;
572
573         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
574                 goto out;
575         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
576                 goto out;
577
578         auth_reload();
579
580         /* now find flags for this export point in this domain */
581         for (i=0 ; i < MCL_MAXTYPES; i++) {
582                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
583                         if (!client_member(dom, exp->m_client->m_hostname))
584                                 continue;
585                         if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
586                                 /* if path is a mountpoint below e_path, then OK */
587                                 int l = strlen(exp->m_export.e_path);
588                                 if (strcmp(path, exp->m_export.e_path) == 0 ||
589                                     (strncmp(path, exp->m_export.e_path, l) == 0 &&
590                                      path[l] == '/' &&
591                                      is_mountpoint(path)))
592                                         /* ok */;
593                                 else
594                                         continue;
595                         } else if (strcmp(path, exp->m_export.e_path) != 0)
596                                 continue;
597                         if (!found) {
598                                 found = exp;
599                                 found_type = i;
600                                 continue;
601                         }
602                         /* If one is a CROSSMOUNT, then prefer the longest path */
603                         if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
604                              (found->m_export.e_flags & NFSEXP_CROSSMOUNT)) &&
605                             strlen(found->m_export.e_path) !=
606                             strlen(found->m_export.e_path)) {
607
608                                 if (strlen(exp->m_export.e_path) >
609                                     strlen(found->m_export.e_path)) {
610                                         found = exp;
611                                         found_type = i;
612                                 }
613                                 continue;
614
615                         } else if (found_type == i && found->m_warned == 0) {
616                                 xlog(L_WARNING, "%s exported to both %s and %s, "
617                                      "arbitrarily choosing options from first",
618                                      path, found->m_client->m_hostname, exp->m_client->m_hostname,
619                                      dom);
620                                 found->m_warned = 1;
621                         }
622                 }
623         }
624
625         if (found) {
626                 if (dump_to_cache(f, dom, path, &found->m_export) < 0) {
627                         xlog(L_WARNING,
628                              "Cannot export %s, possibly unsupported filesystem"
629                              " or fsid= required", path);
630                         dump_to_cache(f, dom, path, NULL);
631                 } else
632                         mountlist_add(dom, path);
633         } else {
634                 dump_to_cache(f, dom, path, NULL);
635         }
636  out:
637         if (dom) free(dom);
638         if (path) free(path);
639 }
640
641
642 struct {
643         char *cache_name;
644         void (*cache_handle)(FILE *f);
645         FILE *f;
646 } cachelist[] = {
647         { "auth.unix.ip", auth_unix_ip},
648         { "auth.unix.gid", auth_unix_gid},
649         { "nfsd.export", nfsd_export},
650         { "nfsd.fh", nfsd_fh},
651         { NULL, NULL }
652 };
653
654 extern int manage_gids;
655 void cache_open(void) 
656 {
657         int i;
658         for (i=0; cachelist[i].cache_name; i++ ) {
659                 char path[100];
660                 if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid)
661                         continue;
662                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
663                 cachelist[i].f = fopen(path, "r+");
664         }
665 }
666
667 void cache_set_fds(fd_set *fdset)
668 {
669         int i;
670         for (i=0; cachelist[i].cache_name; i++) {
671                 if (cachelist[i].f)
672                         FD_SET(fileno(cachelist[i].f), fdset);
673         }
674 }
675
676 int cache_process_req(fd_set *readfds) 
677 {
678         int i;
679         int cnt = 0;
680         for (i=0; cachelist[i].cache_name; i++) {
681                 if (cachelist[i].f != NULL &&
682                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
683                         cnt++;
684                         cachelist[i].cache_handle(cachelist[i].f);
685                         FD_CLR(fileno(cachelist[i].f), readfds);
686                 }
687         }
688         return cnt;
689 }
690
691
692 /*
693  * Give IP->domain and domain+path->options to kernel
694  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
695  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
696  */
697
698 int cache_export_ent(char *domain, struct exportent *exp, char *path)
699 {
700         int err;
701         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
702         if (!f)
703                 return -1;
704
705         err = dump_to_cache(f, domain, exp->e_path, exp);
706         if (err) {
707                 xlog(L_WARNING,
708                      "Cannot export %s, possibly unsupported filesystem or"
709                      " fsid= required", exp->e_path);
710         }
711         mountlist_add(domain, exp->e_path);
712
713         while (err == 0 && (exp->e_flags & NFSEXP_CROSSMOUNT) && path) {
714                 /* really an 'if', but we can break out of
715                  * a 'while' more easily */
716                 /* Look along 'path' for other filesystems
717                  * and export them with the same options
718                  */
719                 struct stat stb;
720                 int l = strlen(exp->e_path);
721                 int dev;
722
723                 if (strlen(path) <= l || path[l] != '/' ||
724                     strncmp(exp->e_path, path, l) != 0)
725                         break;
726                 if (stat(exp->e_path, &stb) != 0)
727                         break;
728                 dev = stb.st_dev;
729                 while(path[l] == '/') {
730                         char c;
731                         /* errors for submount should fail whole filesystem */
732                         int err2;
733
734                         l++;
735                         while (path[l] != '/' && path[l])
736                                 l++;
737                         c = path[l];
738                         path[l] = 0;
739                         err2 = lstat(path, &stb);
740                         path[l] = c;
741                         if (err2 < 0)
742                                 break;
743                         if (stb.st_dev == dev)
744                                 continue;
745                         dev = stb.st_dev;
746                         path[l] = 0;
747                         dump_to_cache(f, domain, path, exp);
748                         path[l] = c;
749                 }
750                 break;
751         }
752
753         fclose(f);
754         return err;
755 }
756
757 int cache_export(nfs_export *exp, char *path)
758 {
759         int err;
760         FILE *f;
761
762         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
763         if (!f)
764                 return -1;
765
766         qword_print(f, "nfsd");
767         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
768         qword_printint(f, time(0)+30*60);
769         qword_print(f, exp->m_client->m_hostname);
770         err = qword_eol(f);
771         
772         fclose(f);
773
774         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export, path)
775                 || err;
776         return err;
777 }
778
779 /* Get a filehandle.
780  * { 
781  *   echo $domain $path $length 
782  *   read filehandle <&0
783  * } <> /proc/fs/nfsd/filehandle
784  */
785 struct nfs_fh_len *
786 cache_get_filehandle(nfs_export *exp, int len, char *p)
787 {
788         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
789         char buf[200];
790         char *bp = buf;
791         int failed;
792         static struct nfs_fh_len fh;
793
794         if (!f)
795                 f = fopen("/proc/fs/nfs/filehandle", "r+");
796         if (!f)
797                 return NULL;
798
799         qword_print(f, exp->m_client->m_hostname);
800         qword_print(f, p);
801         qword_printint(f, len); 
802         failed = qword_eol(f);
803         
804         if (!failed)
805                 failed = (fgets(buf, sizeof(buf), f) == NULL);
806         fclose(f);
807         if (failed)
808                 return NULL;
809         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
810         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
811         return &fh;
812 }
813