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