37e71600268eb402c8e79bc4b4f238d32bf1e3a8
[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 /*
31  * Support routines for text-based upcalls.
32  * Fields are separated by spaces.
33  * Fields are either mangled to quote space tab newline slosh with slosh
34  * or a hexified with a leading \x
35  * Record is terminated with newline.
36  *
37  */
38 int cache_export_ent(char *domain, struct exportent *exp);
39
40
41 char *lbuf  = NULL;
42 int lbuflen = 0;
43
44 void auth_unix_ip(FILE *f)
45 {
46         /* requests are
47          *  class IP-ADDR
48          * Ignore if class != "nfsd"
49          * Otherwise find domainname and write back:
50          *
51          *  "nfsd" IP-ADDR expiry domainname
52          */
53         char *cp;
54         char class[20];
55         char ipaddr[20];
56         char *client;
57         struct in_addr addr;
58         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
59                 return;
60
61         cp = lbuf;
62
63         if (qword_get(&cp, class, 20) <= 0 ||
64             strcmp(class, "nfsd") != 0)
65                 return;
66
67         if (qword_get(&cp, ipaddr, 20) <= 0)
68                 return;
69
70         if (inet_aton(ipaddr, &addr)==0)
71                 return;
72
73         auth_reload();
74
75         /* addr is a valid, interesting address, find the domain name... */
76         client = client_compose(addr);
77
78         
79         qword_print(f, "nfsd");
80         qword_print(f, ipaddr);
81         qword_printint(f, time(0)+30*60);
82         if (client)
83                 qword_print(f, *client?client:"DEFAULT");
84         qword_eol(f);
85
86         if (client) free(client);
87         
88 }
89
90 void nfsd_fh(FILE *f)
91 {
92         /* request are:
93          *  domain fsidtype fsid
94          * interpret fsid, find export point and options, and write:
95          *  domain fsidtype fsid expiry path
96          */
97         char *cp;
98         char *dom;
99         int fsidtype;
100         int fsidlen;
101         unsigned int dev, major=0, minor=0;
102         unsigned int inode=0;
103         unsigned int fsidnum=0;
104         char fsid[32];
105         struct exportent *found = NULL;
106         nfs_export *exp;
107         int i;
108         int dev_missing = 0;
109
110         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
111                 return;
112
113         cp = lbuf;
114
115         dom = malloc(strlen(cp));
116         if (dom == NULL)
117                 return;
118         if (qword_get(&cp, dom, strlen(cp)) <= 0)
119                 goto out;
120         if (qword_get_int(&cp, &fsidtype) != 0)
121                 goto out;
122         if (fsidtype < 0 || fsidtype > 3)
123                 goto out; /* unknown type */
124         if ((fsidlen = qword_get(&cp, fsid, 32)) <= 0)
125                 goto out;
126         switch(fsidtype) {
127         case 0: /* 4 bytes: 2 major, 2 minor, 4 inode */
128                 if (fsidlen != 8)
129                         goto out;
130                 memcpy(&dev, fsid, 4);
131                 memcpy(&inode, fsid+4, 4);
132                 major = ntohl(dev)>>16;
133                 minor = ntohl(dev) & 0xFFFF;
134                 break;
135
136         case 1: /* 4 bytes - fsid */
137                 if (fsidlen != 4)
138                         goto out;
139                 memcpy(&fsidnum, fsid, 4);
140                 break;
141
142         case 2: /* 12 bytes: 4 major, 4 minor, 4 inode 
143                  * This format is never actually used but was
144                  * an historical accident
145                  */
146                 if (fsidlen != 12)
147                         goto out;
148                 memcpy(&dev, fsid, 4); major = ntohl(dev);
149                 memcpy(&dev, fsid+4, 4); minor = ntohl(dev);
150                 memcpy(&inode, fsid+8, 4);
151                 break;
152
153         case 3: /* 8 bytes: 4 byte packed device number, 4 inode */
154                 /* This is *host* endian, not net-byte-order, because
155                  * no-one outside this host has any business interpreting it
156                  */
157                 if (fsidlen != 8)
158                         goto out;
159                 memcpy(&dev, fsid, 4);
160                 memcpy(&inode, fsid+4, 4);
161                 major = (dev & 0xfff00) >> 8;
162                 minor = (dev & 0xff) | ((dev >> 12) & 0xfff00);
163                 break;
164
165         }
166
167         auth_reload();
168
169         /* Now determine export point for this fsid/domain */
170         for (i=0 ; i < MCL_MAXTYPES; i++) {
171                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
172                         struct stat stb;
173
174                         if (!client_member(dom, exp->m_client->m_hostname))
175                                 continue;
176                         if (exp->m_export.e_mountpoint &&
177                             !is_mountpoint(exp->m_export.e_mountpoint[0]?
178                                            exp->m_export.e_mountpoint:
179                                            exp->m_export.e_path))
180                                 dev_missing ++;
181                         if (stat(exp->m_export.e_path, &stb) != 0)
182                                 continue;
183                         if (fsidtype == 1 &&
184                             ((exp->m_export.e_flags & NFSEXP_FSID) == 0 ||
185                              exp->m_export.e_fsid != fsidnum))
186                                 continue;
187                         if (fsidtype != 1) {
188                                 if (stb.st_ino != inode)
189                                         continue;
190                                 if (major != major(stb.st_dev) ||
191                                     minor != minor(stb.st_dev))
192                                         continue;
193                         }
194                         /* It's a match !! */
195                         if (!found)
196                                 found = &exp->m_export;
197                         else if (strcmp(found->e_path, exp->m_export.e_path)!= 0)
198                         {
199                                 xlog(L_WARNING, "%s and %s have same filehandle for %s, using first",
200                                      found->e_path, exp->m_export.e_path, dom);
201                         }
202                 }
203         }
204         if (found && 
205             found->e_mountpoint &&
206             !is_mountpoint(found->e_mountpoint[0]?
207                            found->e_mountpoint:
208                            found->e_path)) {
209                 /* Cannot export this yet 
210                  * should log a warning, but need to rate limit
211                    xlog(L_WARNING, "%s not exported as %d not a mountpoint",
212                    found->e_path, found->e_mountpoint);
213                  */
214                 /* FIXME we need to make sure we re-visit this later */
215                 goto out;
216         }
217         if (!found && dev_missing) {
218                 /* The missing dev could be what we want, so just be
219                  * quite rather than returning stale yet
220                  */
221                 goto out;
222         }
223
224         if (found)
225                 cache_export_ent(dom, found);
226
227         qword_print(f, dom);
228         qword_printint(f, fsidtype);
229         qword_printhex(f, fsid, fsidlen);
230         qword_printint(f, time(0)+30*60);
231         if (found)
232                 qword_print(f, found->e_path);
233         qword_eol(f);
234  out:
235         free(dom);
236         return;         
237 }
238
239 static int dump_to_cache(FILE *f, char *domain, char *path, struct exportent *exp)
240 {
241         qword_print(f, domain);
242         qword_print(f, path);
243         qword_printint(f, time(0)+30*60);
244         if (exp) {
245                 qword_printint(f, exp->e_flags);
246                 qword_printint(f, exp->e_anonuid);
247                 qword_printint(f, exp->e_anongid);
248                 qword_printint(f, exp->e_fsid);
249         }
250         return qword_eol(f);
251 }
252
253 void nfsd_export(FILE *f)
254 {
255         /* requests are:
256          *  domain path
257          * determine export options and return:
258          *  domain path expiry flags anonuid anongid fsid
259          */
260
261         char *cp;
262         int i;
263         char *dom, *path;
264         nfs_export *exp, *found = NULL;
265
266
267         if (readline(fileno(f), &lbuf, &lbuflen) != 1)
268                 return;
269
270         cp = lbuf;
271         dom = malloc(strlen(cp));
272         path = malloc(strlen(cp));
273
274         if (!dom || !path)
275                 goto out;
276
277         if (qword_get(&cp, dom, strlen(lbuf)) <= 0)
278                 goto out;
279         if (qword_get(&cp, path, strlen(lbuf)) <= 0)
280                 goto out;
281
282         auth_reload();
283
284         /* now find flags for this export point in this domain */
285         for (i=0 ; i < MCL_MAXTYPES; i++) {
286                 for (exp = exportlist[i]; exp; exp = exp->m_next) {
287                         if (!client_member(dom, exp->m_client->m_hostname))
288                                 continue;
289                         if (strcmp(path, exp->m_export.e_path))
290                                 continue;
291                         if (!found)
292                                 found = exp;
293                         else {
294                                 xlog(L_WARNING, "%s exported to both %s and %s in %s",
295                                      path, exp->m_client->m_hostname, found->m_client->m_hostname,
296                                      dom);
297                         }
298                 }
299         }
300
301         if (found) {
302                 dump_to_cache(f, dom, path, &found->m_export);
303                 mountlist_add(dom, path);
304         } else {
305                 dump_to_cache(f, dom, path, NULL);
306         }
307  out:
308         if (dom) free(dom);
309         if (path) free(path);
310 }
311
312
313 struct {
314         char *cache_name;
315         void (*cache_handle)(FILE *f);
316         FILE *f;
317 } cachelist[] = {
318         { "auth.unix.ip", auth_unix_ip},
319         { "nfsd.export", nfsd_export},
320         { "nfsd.fh", nfsd_fh},
321         { NULL, NULL }
322 };
323
324 void cache_open(void) 
325 {
326         int i;
327         for (i=0; cachelist[i].cache_name; i++ ){
328                 char path[100];
329                 sprintf(path, "/proc/net/rpc/%s/channel", cachelist[i].cache_name);
330                 cachelist[i].f = fopen(path, "r+");
331         }
332 }
333
334 void cache_set_fds(fd_set *fdset)
335 {
336         int i;
337         for (i=0; cachelist[i].cache_name; i++) {
338                 if (cachelist[i].f)
339                         FD_SET(fileno(cachelist[i].f), fdset);
340         }
341 }
342
343 int cache_process_req(fd_set *readfds) 
344 {
345         int i;
346         int cnt = 0;
347         for (i=0; cachelist[i].cache_name; i++) {
348                 if (cachelist[i].f != NULL &&
349                     FD_ISSET(fileno(cachelist[i].f), readfds)) {
350                         cnt++;
351                         cachelist[i].cache_handle(cachelist[i].f);
352                         FD_CLR(fileno(cachelist[i].f), readfds);
353                 }
354         }
355         return cnt;
356 }
357
358
359 /*
360  * Give IP->domain and domain+path->options to kernel
361  * % echo nfsd $IP  $[now+30*60] $domain > /proc/net/rpc/auth.unix.ip/channel
362  * % echo $domain $path $[now+30*60] $options $anonuid $anongid $fsid > /proc/net/rpc/nfsd.export/channel
363  */
364
365 int cache_export_ent(char *domain, struct exportent *exp)
366 {
367         int err;
368         FILE *f = fopen("/proc/net/rpc/nfsd.export/channel", "w");
369         if (!f)
370                 return -1;
371
372         err = dump_to_cache(f, domain, exp->e_path, exp);
373         fclose(f);
374         mountlist_add(domain, exp->e_path);
375         return err;
376 }
377
378 int cache_export(nfs_export *exp)
379 {
380         int err;
381         FILE *f;
382
383         if (exp->m_export.e_maptype != CLE_MAP_IDENT) {
384                 xlog(L_ERROR, "%s: unsupported mapping; kernel supports only 'identity' (default)",
385                     exp->m_export.m_path);
386                 return;
387         }
388
389         f = fopen("/proc/net/rpc/auth.unix.ip/channel", "w");
390         if (!f)
391                 return -1;
392
393         qword_print(f, "nfsd");
394         qword_print(f, inet_ntoa(exp->m_client->m_addrlist[0]));
395         qword_printint(f, time(0)+30*60);
396         qword_print(f, exp->m_client->m_hostname);
397         err = qword_eol(f);
398         
399         fclose(f);
400
401         err = cache_export_ent(exp->m_client->m_hostname, &exp->m_export)
402                 || err;
403         return err;
404 }
405
406 /* Get a filehandle.
407  * { 
408  *   echo $domain $path $length 
409  *   read filehandle <&0
410  * } <> /proc/fs/nfsd/filehandle
411  */
412 struct nfs_fh_len *
413 cache_get_filehandle(nfs_export *exp, int len, char *p)
414 {
415         FILE *f = fopen("/proc/fs/nfsd/filehandle", "r+");
416         char buf[200];
417         char *bp = buf;
418         int failed;
419         static struct nfs_fh_len fh;
420
421         if (!f)
422                 f = fopen("/proc/fs/nfs/filehandle", "r+");
423         if (!f)
424                 return NULL;
425
426         qword_print(f, exp->m_client->m_hostname);
427         qword_print(f, p);
428         qword_printint(f, len); 
429         failed = qword_eol(f);
430         
431         if (!failed)
432                 failed = (fgets(buf, sizeof(buf), f) == NULL);
433         fclose(f);
434         if (failed)
435                 return NULL;
436         memset(fh.fh_handle, 0, sizeof(fh.fh_handle));
437         fh.fh_size = qword_get(&bp, (char *)fh.fh_handle, NFS3_FHSIZE);
438         return &fh;
439 }
440