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