85e89753c760b81044a2b5b9d535ba3c7d467d6c
[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                         switch(fsidtype){
401                         case FSID_DEV:
402                         case FSID_MAJOR_MINOR:
403                         case FSID_ENCODE_DEV:
404                                 if (stb.st_ino != inode)
405                                         continue;
406                                 if (major != major(stb.st_dev) ||
407                                     minor != minor(stb.st_dev))
408                                         continue;
409                                 break;
410                         case FSID_NUM:
411                                 if (((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
412                                      exp->m_export.e_fsid != fsidnum))
413                                         continue;
414                                 break;
415                         case FSID_UUID4_INUM:
416                         case FSID_UUID16_INUM:
417                                 if (stb.st_ino != inode)
418                                         continue;
419                                 goto check_uuid;
420                         case FSID_UUID8:
421                         case FSID_UUID16:
422                                 if (!is_mountpoint(path))
423                                         continue;
424                         check_uuid:
425 #if USE_BLKID
426                                 if (exp->m_export.e_uuid)
427                                         get_uuid(NULL, exp->m_export.e_uuid,
428                                                  uuidlen, u);
429                                 else if (get_uuid(path, NULL,
430                                                   uuidlen, u) == 0)
431                                         continue;
432
433                                 if (memcmp(u, fhuuid, uuidlen) != 0)
434                                         continue;
435                                 break;
436 #else
437                                 continue;
438 #endif
439                         }
440                         /* It's a match !! */
441                         if (!found) {
442                                 found = &exp->m_export;
443                                 found_path = strdup(path);
444                         } else if (strcmp(found->e_path, exp->m_export.e_path)!= 0)
445                         {
446                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
447                                      found_path, path, dom);
448                         }
449                 }
450         }
451         if (found && 
452             found->e_mountpoint &&
453             !is_mountpoint(found->e_mountpoint[0]?
454                            found->e_mountpoint:
455                            found->e_path)) {
456                 /* Cannot export this yet 
457                  * should log a warning, but need to rate limit
458                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
459                    found->e_path, found->e_mountpoint);
460                  */
461                 /* FIXME we need to make sure we re-visit this later */
462                 goto out;
463         }
464         if (!found && dev_missing) {
465                 /* The missing dev could be what we want, so just be
466                  * quite rather than returning stale yet
467                  */
468                 goto out;
469         }
470
471         if (found)
472                 if (cache_export_ent(dom, found, found_path) < 0)
473                         found = 0;
474
475         qword_print(f, dom);
476         qword_printint(f, fsidtype);
477         qword_printhex(f, fsid, fsidlen);
478         /* The fsid -> path lookup can be quite expensive as it
479          * potentially stats and reads lots of devices, and some of those
480          * might have spun-down.  The Answer is not likely to
481          * change underneath us, and an 'exportfs -f' can always
482          * remove this from the kernel, so use a really log
483          * timeout.  Maybe this should be configurable on the command
484          * line.
485          */
486         qword_printint(f, 0x7fffffff);
487         if (found)
488                 qword_print(f, found->e_path);
489         qword_eol(f);
490  out:
491         free(dom);
492         return;         
493 }
494
495 static void write_fsloc(FILE *f, struct exportent *ep, char *path)
496 {
497         struct servers *servers;
498
499         if (ep->e_fslocmethod == FSLOC_NONE)
500                 return;
501
502         servers = replicas_lookup(ep->e_fslocmethod, ep->e_fslocdata, path);
503         if (!servers)
504                 return;
505         qword_print(f, "fsloc");
506         qword_printint(f, servers->h_num);
507         if (servers->h_num >= 0) {
508                 int i;
509                 for (i=0; i<servers->h_num; i++) {
510                         qword_print(f, servers->h_mp[i]->h_host);
511                         qword_print(f, servers->h_mp[i]->h_path);
512                 }
513         }
514         qword_printint(f, servers->h_referral);
515         release_replicas(servers);
516 }
517
518 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
519 {
520         qword_print(f, domain);
521         qword_print(f, path);
522         qword_printint(f, time(0)+30*60);
523         if (exp) {
524                 qword_printint(f, exp->e_flags);
525                 qword_printint(f, exp->e_anonuid);
526                 qword_printint(f, exp->e_anongid);
527                 qword_printint(f, exp->e_fsid);
528                 write_fsloc(f, exp, path);
529 #if USE_BLKID
530                 if (exp->e_uuid == NULL) {
531                         char u[16];
532                         if (get_uuid(path, NULL, 16, u)) {
533                                 qword_print(f, "uuid");
534                                 qword_printhex(f, u, 16);
535                         }
536                 } else if (exp->e_uuid) {
537                         qword_print(f, "uuid");
538                         qword_printhex(f, exp->e_uuid, 16);
539                 }
540 #endif
541         }
542         return qword_eol(f);
543 }
544
545 void nfsd_export(FILE *f)
546 {
547         /* requests are:
548          *  domain path
549          * determine export options and return:
550          *  domain path expiry flags anonuid anongid fsid
551          */
552
553         char *cp;
554         int i;
555         char *dom, *path;
556         nfs_export *exp, *found = NULL;
557         int found_type = 0;
558
559
560         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
561                 return;
562
563         cp = lbuf;
564         dom = malloc(strlen(cp));
565         path = malloc(strlen(cp));
566
567         if (!dom || !path)
568                 goto out;
569
570         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
571                 goto out;
572         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
573                 goto out;
574
575         auth_reload();
576
577         /* now find flags for this export point in this domain */
578         for (i=0 ; i < MCL_MAXTYPES; i++) {
579                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
580                         if (!client_member(dom, exp->m_client->m_hostname))
581                                 continue;
582                         if (exp->m_export.e_flags & NFSEXP_CROSSMOUNT) {
583                                 /* if path is a mountpoint below e_path, then OK */
584                                 int l = strlen(exp->m_export.e_path);
585                                 if (strcmp(path, exp->m_export.e_path) == 0 ||
586                                     (strncmp(path, exp->m_export.e_path, l) == 0 &&
587                                      path[l] == '/' &&
588                                      is_mountpoint(path)))
589                                         /* ok */;
590                                 else
591                                         continue;
592                         } else if (strcmp(path, exp->m_export.e_path) != 0)
593                                 continue;
594                         if (!found) {
595                                 found = exp;
596                                 found_type = i;
597                                 continue;
598                         }
599                         /* If one is a CROSSMOUNT, then prefer the longest path */
600                         if (((found->m_export.e_flags & NFSEXP_CROSSMOUNT) ||
601                              (found->m_export.e_flags & NFSEXP_CROSSMOUNT)) &&
602                             strlen(found->m_export.e_path) !=
603                             strlen(found->m_export.e_path)) {
604
605                                 if (strlen(exp->m_export.e_path) >
606                                     strlen(found->m_export.e_path)) {
607                                         found = exp;
608                                         found_type = i;
609                                 }
610                                 continue;
611
612                         } else if (found_type == i && found->m_warned == 0) {
613                                 xlog(L_WARNING, "%s exported to both %s and %s, "
614                                      "arbitrarily choosing options from first",
615                                      path, found->m_client->m_hostname, exp->m_client->m_hostname,
616                                      dom);
617                                 found->m_warned = 1;
618                         }
619                 }
620         }
621
622         if (found) {
623                 if (dump_to_cache(f, dom, path, &found->m_export) < 0)
624                         dump_to_cache(f, dom, path, NULL);
625                 else
626                         mountlist_add(dom, path);
627         } else {
628                 dump_to_cache(f, dom, path, NULL);
629         }
630  out:
631         if (dom) free(dom);
632         if (path) free(path);
633 }
634
635
636 struct {
637         char *cache_name;
638         void (*cache_handle)(FILE *f);
639         FILE *f;
640 } cachelist[] = {
641         { "auth.unix.ip", auth_unix_ip},
642         { "auth.unix.gid", auth_unix_gid},
643         { "nfsd.export", nfsd_export},
644         { "nfsd.fh", nfsd_fh},
645         { NULL, NULL }
646 };
647
648 extern int manage_gids;
649 void cache_open(void) 
650 {
651         int i;
652         for (i=0; cachelist[i].cache_name; i++ ) {
653                 char path[100];
654                 if (!manage_gids && cachelist[i].cache_handle == auth_unix_gid)
655                         continue;
656                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
657                 cachelist[i].f = fopen(path, "r+");
658         }
659 }
660
661 void cache_set_fds(fd_set *fdset)
662 {
663         int i;
664         for (i=0; cachelist[i].cache_name; i++) {
665                 if (cachelist[i].f)
666                         FD_SET(fileno(cachelist[i].f), fdset);
667         }
668 }
669
670 int cache_process_req(fd_set *readfds) 
671 {
672         int i;
673         int cnt = 0;
674         for (i=0; cachelist[i].cache_name; i++) {
675                 if (cachelist[i].f != NULL &&
676                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
677                         cnt++;
678                         cachelist[i].cache_handle(cachelist[i].f);
679                         FD_CLR(fileno(cachelist[i].f), readfds);
680                 }
681         }
682         return cnt;
683 }
684
685
686 /*
687  * Give IP->domain and domain+path->options to kernel
688  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
689  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
690  */
691
692 int cache_export_ent(char *domain, struct exportent *exp, char *path)
693 {
694         int err;
695         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
696         if (!f)
697                 return -1;
698
699         err = dump_to_cache(f, domain, exp->e_path, exp);
700         mountlist_add(domain, exp->e_path);
701
702         while ((exp->e_flags & NFSEXP_CROSSMOUNT) && path) {
703                 /* really an 'if', but we can break out of
704                  * a 'while' more easily */
705                 /* Look along 'path' for other filesystems
706                  * and export them with the same options
707                  */
708                 struct stat stb;
709                 int l = strlen(exp->e_path);
710                 int dev;
711
712                 if (strlen(path) <= l || path[l] != '/' ||
713                     strncmp(exp->e_path, path, l) != 0)
714                         break;
715                 if (stat(exp->e_path, &stb) != 0)
716                         break;
717                 dev = stb.st_dev;
718                 while(path[l] == '/') {
719                         char c;
720                         int err;
721
722                         l++;
723                         while (path[l] != '/' && path[l])
724                                 l++;
725                         c = path[l];
726                         path[l] = 0;
727                         err = lstat(path, &stb);
728                         path[l] = c;
729                         if (err < 0)
730                                 break;
731                         if (stb.st_dev == dev)
732                                 continue;
733                         dev = stb.st_dev;
734                         path[l] = 0;
735                         dump_to_cache(f, domain, path, exp);
736                         path[l] = c;
737                 }
738                 break;
739         }
740
741         fclose(f);
742         return err;
743 }
744
745 int cache_export(nfs_export *exp, char *path)
746 {
747         int err;
748         FILE *f;
749
750         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
751         if (!f)
752                 return -1;
753
754         qword_print(f, "nfsd");
755         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
756         qword_printint(f, time(0)+30*60);
757         qword_print(f, exp->m_client->m_hostname);
758         err = qword_eol(f);
759         
760         fclose(f);
761
762         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export, path)
763                 || err;
764         return err;
765 }
766
767 /* Get a filehandle.
768  * { 
769  *   echo $domain $path $length 
770  *   read filehandle <&0
771  * } <> /proc/fs/nfsd/filehandle
772  */
773 struct nfs_fh_len *
774 cache_get_filehandle(nfs_export *exp, int len, char *p)
775 {
776         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
777         char buf[200];
778         char *bp = buf;
779         int failed;
780         static struct nfs_fh_len fh;
781
782         if (!f)
783                 f = fopen("/proc/fs/nfs/filehandle", "r+");
784         if (!f)
785                 return NULL;
786
787         qword_print(f, exp->m_client->m_hostname);
788         qword_print(f, p);
789         qword_printint(f, len); 
790         failed = qword_eol(f);
791         
792         if (!failed)
793                 failed = (fgets(buf, sizeof(buf), f) == NULL);
794         fclose(f);
795         if (failed)
796                 return NULL;
797         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
798         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
799         return &fh;
800 }
801