93bad32241fae7d5d0c4f7276e75f3290da5ffdf
[nfs-utils.git] / utils / exportfs / exportfs.c
1 /*
2  * utils/exportfs/exportfs.c
3  *
4  * Export file systems to knfsd
5  *
6  * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de>
7  *
8  * Extensive changes, 1999, Neil Brown <neilb@cse.unsw.edu.au>
9  */
10
11 #ifdef HAVE_CONFIG_H
12 #include <config.h>
13 #endif
14
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/vfs.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20 #include <stdbool.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <stdarg.h>
24 #include <getopt.h>
25 #include <fcntl.h>
26 #include <netdb.h>
27 #include <errno.h>
28
29 #include "misc.h"
30 #include "nfslib.h"
31 #include "exportfs.h"
32 #include "xlog.h"
33
34 static void     export_all(int verbose);
35 static void     exportfs(char *arg, char *options, int verbose);
36 static void     unexportfs(char *arg, int verbose);
37 static void     exports_update(int verbose);
38 static void     dump(int verbose);
39 static void     error(nfs_export *exp, int err);
40 static void     usage(void);
41 static void     validate_export(nfs_export *exp);
42 static int      matchhostname(const char *hostname1, const char *hostname2);
43
44 int
45 main(int argc, char **argv)
46 {
47         char    *options = NULL;
48         int     f_export = 1;
49         int     f_all = 0;
50         int     f_verbose = 0;
51         int     f_reexport = 0;
52         int     f_ignore = 0;
53         int     i, c;
54         int     new_cache = 0;
55         int     force_flush = 0;
56
57         xlog_open("exportfs");
58
59         export_errno = 0;
60
61         while ((c = getopt(argc, argv, "aio:ruvf")) != EOF) {
62                 switch(c) {
63                 case 'a':
64                         f_all = 1;
65                         break;
66                 case 'i':
67                         f_ignore = 1;
68                         break;
69                 case 'o':
70                         options = optarg;
71                         break;
72                 case 'r':
73                         f_reexport = 1;
74                         f_all = 1;
75                         break;
76                 case 'u':
77                         f_export = 0;
78                         break;
79                 case 'v':
80                         f_verbose = 1;
81                         break;
82                 case 'f':
83                         force_flush = 1;
84                         break;
85                 default:
86                         usage();
87                         break;
88                 }
89         }
90
91         if (optind != argc && f_all) {
92                 fprintf(stderr,"exportfs: extra arguments are not permitted with -a or -r.\n");
93                 return 1;
94         }
95         if (f_ignore && (f_all || ! f_export)) {
96                 fprintf(stderr,"exportfs: -i not meaningful with -a, -r or -u.\n");
97                 return 1;
98         }
99         if (f_reexport && ! f_export) {
100                 fprintf(stderr, "exportfs: -r and -u are incompatible.\n");
101                 return 1;
102         }
103         new_cache = check_new_cache();
104         if (optind == argc && ! f_all) {
105                 if (force_flush) {
106                         if (new_cache)
107                                 cache_flush(1);
108                         else {
109                                 fprintf(stderr, "exportfs: -f: only available with new cache controls: mount /proc/fs/nfsd first\n");
110                                 exit(1);
111                         }
112                         return 0;
113                 } else {
114                         xtab_export_read();
115                         dump(f_verbose);
116                         return 0;
117                 }
118         }
119         if (f_export && ! f_ignore)
120                 export_read(_PATH_EXPORTS);
121         if (f_export) {
122                 if (f_all)
123                         export_all(f_verbose);
124                 else
125                         for (i = optind; i < argc ; i++)
126                                 exportfs(argv[i], options, f_verbose);
127         }
128         /* If we are unexporting everything, then
129          * don't care about what should be exported, as that
130          * may require DNS lookups..
131          */
132         if (! ( !f_export && f_all)) {
133                 /* note: xtab_*_read does not update entries if they already exist,
134                  * so this will not lose new options
135                  */
136                 if (!f_reexport)
137                         xtab_export_read();
138                 if (!f_export)
139                         for (i = optind ; i < argc ; i++)
140                                 unexportfs(argv[i], f_verbose);
141                 if (!new_cache)
142                         rmtab_read();
143         }
144         if (!new_cache) {
145                 xtab_mount_read();
146                 exports_update(f_verbose);
147         }
148         xtab_export_write();
149         if (new_cache)
150                 cache_flush(force_flush);
151         if (!new_cache)
152                 xtab_mount_write();
153
154         return export_errno;
155 }
156
157 static void
158 exports_update_one(nfs_export *exp, int verbose)
159 {
160                 /* check mountpoint option */
161         if (exp->m_mayexport &&
162             exp->m_export.e_mountpoint &&
163             !is_mountpoint(exp->m_export.e_mountpoint[0]?
164                            exp->m_export.e_mountpoint:
165                            exp->m_export.e_path)) {
166                 printf("%s not exported as %s not a mountpoint.\n",
167                        exp->m_export.e_path, exp->m_export.e_mountpoint);
168                 exp->m_mayexport = 0;
169         }
170         if (exp->m_mayexport && ((exp->m_exported<1) || exp->m_changed)) {
171                 if (verbose)
172                         printf("%sexporting %s:%s to kernel\n",
173                                exp->m_exported ?"re":"",
174                                exp->m_client->m_hostname,
175                                exp->m_export.e_path);
176                 if (!export_export(exp))
177                         error(exp, errno);
178         }
179         if (exp->m_exported && ! exp->m_mayexport) {
180                 if (verbose)
181                         printf("unexporting %s:%s from kernel\n",
182                                exp->m_client->m_hostname,
183                                exp->m_export.e_path);
184                 if (!export_unexport(exp))
185                         error(exp, errno);
186         }
187 }
188
189
190 /* we synchronise intention with reality.
191  * entries with m_mayexport get exported
192  * entries with m_exported but not m_mayexport get unexported
193  * looking at m_client->m_type == MCL_FQDN and m_client->m_type == MCL_GSS only
194  */
195 static void
196 exports_update(int verbose)
197 {
198         nfs_export      *exp;
199
200         for (exp = exportlist[MCL_FQDN].p_head; exp; exp=exp->m_next) {
201                 exports_update_one(exp, verbose);
202         }
203         for (exp = exportlist[MCL_GSS].p_head; exp; exp=exp->m_next) {
204                 exports_update_one(exp, verbose);
205         }
206 }
207                         
208 /*
209  * export_all finds all entries and
210  *    marks them xtabent and mayexport so that they get exported
211  */
212 static void
213 export_all(int verbose)
214 {
215         nfs_export      *exp;
216         int             i;
217
218         for (i = 0; i < MCL_MAXTYPES; i++) {
219                 for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
220                         if (verbose)
221                                 printf("exporting %s:%s\n",
222                                        exp->m_client->m_hostname, 
223                                        exp->m_export.e_path);
224                         exp->m_xtabent = 1;
225                         exp->m_mayexport = 1;
226                         exp->m_changed = 1;
227                         exp->m_warned = 0;
228                         validate_export(exp);
229                 }
230         }
231 }
232
233
234 static void
235 exportfs(char *arg, char *options, int verbose)
236 {
237         struct exportent *eep;
238         nfs_export      *exp;
239         struct addrinfo *ai = NULL;
240         char            *path;
241         char            *hname = arg;
242         int             htype;
243
244         if ((path = strchr(arg, ':')) != NULL)
245                 *path++ = '\0';
246
247         if (!path || *path != '/') {
248                 fprintf(stderr, "Invalid exporting option: %s\n", arg);
249                 return;
250         }
251
252         if ((htype = client_gettype(hname)) == MCL_FQDN) {
253                 ai = host_addrinfo(hname);
254                 if (ai != NULL) {
255                         exp = export_find(ai, path);
256                         hname = ai->ai_canonname;
257                 }
258         } else
259                 exp = export_lookup(hname, path, 0);
260
261         if (!exp) {
262                 if (!(eep = mkexportent(hname, path, options)) ||
263                     !(exp = export_create(eep, 0)))
264                         goto out;
265         } else if (!updateexportent(&exp->m_export, options))
266                 goto out;
267
268         if (verbose)
269                 printf("exporting %s:%s\n", exp->m_client->m_hostname, 
270                         exp->m_export.e_path);
271         exp->m_xtabent = 1;
272         exp->m_mayexport = 1;
273         exp->m_changed = 1;
274         exp->m_warned = 0;
275         validate_export(exp);
276
277 out:
278         freeaddrinfo(ai);
279 }
280
281 static void
282 unexportfs(char *arg, int verbose)
283 {
284         nfs_export      *exp;
285         struct addrinfo *ai = NULL;
286         char            *path;
287         char            *hname = arg;
288         int             htype;
289
290         if ((path = strchr(arg, ':')) != NULL)
291                 *path++ = '\0';
292
293         if (!path || *path != '/') {
294                 fprintf(stderr, "Invalid unexporting option: %s\n",
295                         arg);
296                 return;
297         }
298
299         if ((htype = client_gettype(hname)) == MCL_FQDN) {
300                 ai = host_addrinfo(hname);
301                 if (ai)
302                         hname = ai->ai_canonname;
303         }
304
305         for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
306                 if (path && strcmp(path, exp->m_export.e_path))
307                         continue;
308                 if (htype != exp->m_client->m_type)
309                         continue;
310                 if (htype == MCL_FQDN
311                     && !matchhostname(exp->m_export.e_hostname,
312                                           hname))
313                         continue;
314                 if (htype != MCL_FQDN
315                     && strcasecmp(exp->m_export.e_hostname, hname))
316                         continue;
317                 if (verbose) {
318 #if 0
319                         if (exp->m_exported) {
320                                 printf("unexporting %s:%s from kernel\n",
321                                        exp->m_client->m_hostname,
322                                        exp->m_export.e_path);
323                         }
324                         else
325 #endif
326                                 printf("unexporting %s:%s\n",
327                                         exp->m_client->m_hostname, 
328                                         exp->m_export.e_path);
329                 }
330 #if 0
331                 if (exp->m_exported && !export_unexport(exp))
332                         error(exp, errno);
333 #endif
334                 exp->m_xtabent = 0;
335                 exp->m_mayexport = 0;
336         }
337
338         freeaddrinfo(ai);
339 }
340
341 static int can_test(void)
342 {
343         int fd;
344         int n;
345         char *setup = "nfsd 0.0.0.0 2147483647 -test-client-\n";
346         fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY);
347         if ( fd < 0) return 0;
348         n = write(fd, setup, strlen(setup));
349         close(fd);
350         if (n < 0)
351                 return 0;
352         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
353         if ( fd < 0) return 0;
354         close(fd);
355         return 1;
356 }
357
358 static int test_export(char *path, int with_fsid)
359 {
360         char buf[1024];
361         int fd, n;
362
363         sprintf(buf, "-test-client- %s 3 %d -1 -1 0\n",
364                 path,
365                 with_fsid ? NFSEXP_FSID : 0);
366         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
367         if (fd < 0)
368                 return 0;
369         n = write(fd, buf, strlen(buf));
370         close(fd);
371         if (n < 0)
372                 return 0;
373         return 1;
374 }
375
376 static void
377 validate_export(nfs_export *exp)
378 {
379         /* Check that the given export point is potentially exportable.
380          * We just give warnings here, don't cause anything to fail.
381          * If a path doesn't exist, or is not a dir or file, give an warning
382          * otherwise trial-export to '-test-client-' and check for failure.
383          */
384         struct stat stb;
385         char *path = exp->m_export.e_path;
386         struct statfs64 stf;
387         int fs_has_fsid = 0;
388
389         if (stat(path, &stb) < 0) {
390                 fprintf(stderr, "exportfs: Warning: %s does not exist\n",
391                         path);
392                 return;
393         }
394         if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
395                 fprintf(stderr, "exportfs: Warning: %s is neither "
396                         "a directory nor a file.\n"
397                         "     remote access will fail\n", path);
398                 return;
399         }
400         if (!can_test())
401                 return;
402
403         if (!statfs64(path, &stf) &&
404             (stf.f_fsid.__val[0] || stf.f_fsid.__val[1]))
405                 fs_has_fsid = 1;
406
407         if ((exp->m_export.e_flags & NFSEXP_FSID) || exp->m_export.e_uuid ||
408             fs_has_fsid) {
409                 if ( !test_export(path, 1)) {
410                         fprintf(stderr, "exportfs: Warning: %s does not "
411                                 "support NFS export.\n",
412                                 path);
413                         return;
414                 }
415         } else if ( ! test_export(path, 0)) {
416                 if (test_export(path, 1))
417                         fprintf(stderr, "exportfs: Warning: %s requires fsid= "
418                                 "for NFS export\n", path);
419                 else
420                         fprintf(stderr, "exportfs: Warning: %s does not "
421                                 "support NFS export.\n",
422                                 path);
423                 return;
424
425         }
426 }
427
428 static _Bool
429 is_hostname(const char *sp)
430 {
431         if (*sp == '\0' || *sp == '@')
432                 return false;
433
434         for (; *sp != '\0'; sp++) {
435                 if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
436                         return false;
437                 if (*sp == '\\' && sp[1] != '\0')
438                         sp++;
439         }
440
441         return true;
442 }
443
444 static _Bool
445 compare_sockaddrs4(const struct sockaddr *sa1, const struct sockaddr *sa2)
446 {
447         const struct sockaddr_in *sin1 = (const struct sockaddr_in *)sa1;
448         const struct sockaddr_in *sin2 = (const struct sockaddr_in *)sa2;
449         return sin1->sin_addr.s_addr == sin2->sin_addr.s_addr;
450 }
451
452 static _Bool
453 compare_sockaddrs(const struct sockaddr *sa1, const struct sockaddr *sa2)
454 {
455         if (sa1->sa_family == sa2->sa_family)
456                 switch (sa1->sa_family) {
457                 case AF_INET:
458                         return compare_sockaddrs4(sa1, sa2);
459                 }
460
461         return false;
462 }
463
464 static int
465 matchhostname(const char *hostname1, const char *hostname2)
466 {
467         struct addrinfo *results1 = NULL, *results2 = NULL;
468         struct addrinfo *ai1, *ai2;
469         int result = 0;
470
471         if (strcasecmp(hostname1, hostname2) == 0)
472                 return 1;
473
474         /*
475          * Don't pass export wildcards or netgroup names to DNS
476          */
477         if (!is_hostname(hostname1) || !is_hostname(hostname2))
478                 return 0;
479
480         results1 = host_addrinfo(hostname1);
481         if (results1 == NULL)
482                 goto out;
483         results2 = host_addrinfo(hostname2);
484         if (results2 == NULL)
485                 goto out;
486
487         if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
488                 result = 1;
489                 goto out;
490         }
491
492         for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
493                 for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
494                         if (compare_sockaddrs(ai1->ai_addr, ai2->ai_addr)) {
495                                 result = 1;
496                                 break;
497                         }
498
499 out:
500         freeaddrinfo(results1);
501         freeaddrinfo(results2);
502         return result;
503 }
504
505 static char
506 dumpopt(char c, char *fmt, ...)
507 {
508         va_list ap;
509
510         va_start(ap, fmt);
511         printf("%c", c);
512         vprintf(fmt, ap);
513         va_end(ap);
514         return ',';
515 }
516
517 static void
518 dump(int verbose)
519 {
520         nfs_export      *exp;
521         struct exportent *ep;
522         int             htype;
523         char            *hname, c;
524
525         for (htype = 0; htype < MCL_MAXTYPES; htype++) {
526                 for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
527                         ep = &exp->m_export;
528                         if (!exp->m_xtabent)
529                             continue; /* neilb */
530                         if (htype == MCL_ANONYMOUS)
531                                 hname = "<world>";
532                         else
533                                 hname = ep->e_hostname;
534                         if (strlen(ep->e_path) > 14)
535                                 printf("%-14s\n\t\t%s", ep->e_path, hname);
536                         else
537                                 printf("%-14s\t%s", ep->e_path, hname);
538                         if (!verbose) {
539                                 printf("\n");
540                                 continue;
541                         }
542                         c = '(';
543                         if (ep->e_flags & NFSEXP_READONLY)
544                                 c = dumpopt(c, "ro");
545                         else
546                                 c = dumpopt(c, "rw");
547                         if (ep->e_flags & NFSEXP_ASYNC)
548                                 c = dumpopt(c, "async");
549                         if (ep->e_flags & NFSEXP_GATHERED_WRITES)
550                                 c = dumpopt(c, "wdelay");
551                         if (ep->e_flags & NFSEXP_NOHIDE)
552                                 c = dumpopt(c, "nohide");
553                         if (ep->e_flags & NFSEXP_CROSSMOUNT)
554                                 c = dumpopt(c, "crossmnt");
555                         if (ep->e_flags & NFSEXP_INSECURE_PORT)
556                                 c = dumpopt(c, "insecure");
557                         if (ep->e_flags & NFSEXP_ROOTSQUASH)
558                                 c = dumpopt(c, "root_squash");
559                         else
560                                 c = dumpopt(c, "no_root_squash");
561                         if (ep->e_flags & NFSEXP_ALLSQUASH)
562                                 c = dumpopt(c, "all_squash");
563                         if (ep->e_flags & NFSEXP_NOSUBTREECHECK)
564                                 c = dumpopt(c, "no_subtree_check");
565                         if (ep->e_flags & NFSEXP_NOAUTHNLM)
566                                 c = dumpopt(c, "insecure_locks");
567                         if (ep->e_flags & NFSEXP_NOACL)
568                                 c = dumpopt(c, "no_acl");
569                         if (ep->e_flags & NFSEXP_FSID)
570                                 c = dumpopt(c, "fsid=%d", ep->e_fsid);
571                         if (ep->e_uuid)
572                                 c = dumpopt(c, "fsid=%s", ep->e_uuid);
573                         if (ep->e_mountpoint)
574                                 c = dumpopt(c, "mountpoint%s%s", 
575                                             ep->e_mountpoint[0]?"=":"", 
576                                             ep->e_mountpoint);
577                         if (ep->e_anonuid != 65534)
578                                 c = dumpopt(c, "anonuid=%d", ep->e_anonuid);
579                         if (ep->e_anongid != 65534)
580                                 c = dumpopt(c, "anongid=%d", ep->e_anongid);
581                         switch(ep->e_fslocmethod) {
582                         case FSLOC_NONE:
583                                 break;
584                         case FSLOC_REFER:
585                                 c = dumpopt(c, "refer=%s", ep->e_fslocdata);
586                                 break;
587                         case FSLOC_REPLICA:
588                                 c = dumpopt(c, "replicas=%s", ep->e_fslocdata);
589                                 break;
590 #ifdef DEBUG
591                         case FSLOC_STUB:
592                                 c = dumpopt(c, "fsloc=stub");
593                                 break;
594 #endif
595                         }
596                         secinfo_show(stdout, ep);
597                         printf("%c\n", (c != '(')? ')' : ' ');
598                 }
599         }
600 }
601
602 static void
603 error(nfs_export *exp, int err)
604 {
605         fprintf(stderr, "%s:%s: %s\n", exp->m_client->m_hostname, 
606                 exp->m_export.e_path, strerror(err));
607 }
608
609 static void
610 usage(void)
611 {
612         fprintf(stderr, "usage: exportfs [-aruv] [host:/path]\n");
613         exit(1);
614 }