]> git.decadent.org.uk Git - nfs-utils.git/blob - utils/exportfs/exportfs.c
exportfs: Stop racing exportfs on clusters
[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 <sys/file.h>
20 #include <unistd.h>
21 #include <stdbool.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <stdarg.h>
25 #include <getopt.h>
26 #include <fcntl.h>
27 #include <netdb.h>
28 #include <errno.h>
29 #include <dirent.h>
30
31 #include "sockaddr.h"
32 #include "misc.h"
33 #include "nfslib.h"
34 #include "exportfs.h"
35 #include "xlog.h"
36
37 static void     export_all(int verbose);
38 static void     exportfs(char *arg, char *options, int verbose);
39 static void     unexportfs(char *arg, int verbose);
40 static void     exports_update(int verbose);
41 static void     dump(int verbose);
42 static void     error(nfs_export *exp, int err);
43 static void     usage(const char *progname);
44 static void     validate_export(nfs_export *exp);
45 static int      matchhostname(const char *hostname1, const char *hostname2);
46 static void     export_d_read(const char *dname);
47
48 static const char *lockfile = EXP_LOCKFILE;
49 static int _lockfd = -1;
50
51 /*
52  * If we aren't careful, changes made by exportfs can be lost
53  * when multiple exports process run at once:
54  *
55  *      exportfs process 1      exportfs process 2
56  *      ------------------------------------------
57  *      reads etab version A    reads etab version A
58  *      adds new export B       adds new export C
59  *      writes A+B              writes A+C
60  *
61  * The locking in support/export/xtab.c will prevent mountd from
62  * seeing a partially written version of etab, and will prevent 
63  * the two writers above from writing simultaneously and
64  * corrupting etab, but to prevent problems like the above we
65  * need these additional lockfile() routines.
66  */
67 void 
68 grab_lockfile()
69 {
70         _lockfd = open(lockfile, O_CREAT|O_RDWR, 0666);
71         if (_lockfd != -1) 
72                 lockf(_lockfd, F_LOCK, 0);
73 }
74 void 
75 release_lockfile()
76 {
77         if (_lockfd != -1)
78                 lockf(_lockfd, F_ULOCK, 0);
79 }
80
81 int
82 main(int argc, char **argv)
83 {
84         char    *options = NULL;
85         char    *progname = NULL;
86         int     f_export = 1;
87         int     f_all = 0;
88         int     f_verbose = 0;
89         int     f_reexport = 0;
90         int     f_ignore = 0;
91         int     i, c;
92         int     new_cache = 0;
93         int     force_flush = 0;
94
95         if ((progname = strrchr(argv[0], '/')) != NULL)
96                 progname++;
97         else
98                 progname = argv[0];
99
100         xlog_open(progname);
101         xlog_stderr(1);
102         xlog_syslog(0);
103
104         export_errno = 0;
105
106         while ((c = getopt(argc, argv, "aio:ruvf")) != EOF) {
107                 switch(c) {
108                 case 'a':
109                         f_all = 1;
110                         break;
111                 case 'i':
112                         f_ignore = 1;
113                         break;
114                 case 'o':
115                         options = optarg;
116                         break;
117                 case 'r':
118                         f_reexport = 1;
119                         f_all = 1;
120                         break;
121                 case 'u':
122                         f_export = 0;
123                         break;
124                 case 'v':
125                         f_verbose = 1;
126                         break;
127                 case 'f':
128                         force_flush = 1;
129                         break;
130                 default:
131                         usage(progname);
132                         break;
133                 }
134         }
135
136         if (optind != argc && f_all) {
137                 xlog(L_ERROR, "extra arguments are not permitted with -a or -r");
138                 return 1;
139         }
140         if (f_ignore && (f_all || ! f_export)) {
141                 xlog(L_ERROR, "-i not meaningful with -a, -r or -u");
142                 return 1;
143         }
144         if (f_reexport && ! f_export) {
145                 xlog(L_ERROR, "-r and -u are incompatible");
146                 return 1;
147         }
148         new_cache = check_new_cache();
149         if (optind == argc && ! f_all) {
150                 if (force_flush) {
151                         if (new_cache)
152                                 cache_flush(1);
153                         else {
154                                 xlog(L_ERROR, "-f is available only "
155                                         "with new cache controls. "
156                                         "Mount /proc/fs/nfsd first");
157                                 return 1;
158                         }
159                         return 0;
160                 } else {
161                         xtab_export_read();
162                         dump(f_verbose);
163                         return 0;
164                 }
165         }
166
167         /*
168          * Serialize things as best we can
169          */
170         grab_lockfile();
171         atexit(release_lockfile);
172
173         if (f_export && ! f_ignore) {
174                 export_read(_PATH_EXPORTS);
175                 export_d_read(_PATH_EXPORTS_D);
176         }
177         if (f_export) {
178                 if (f_all)
179                         export_all(f_verbose);
180                 else
181                         for (i = optind; i < argc ; i++)
182                                 exportfs(argv[i], options, f_verbose);
183         }
184         /* If we are unexporting everything, then
185          * don't care about what should be exported, as that
186          * may require DNS lookups..
187          */
188         if (! ( !f_export && f_all)) {
189                 /* note: xtab_*_read does not update entries if they already exist,
190                  * so this will not lose new options
191                  */
192                 if (!f_reexport)
193                         xtab_export_read();
194                 if (!f_export)
195                         for (i = optind ; i < argc ; i++)
196                                 unexportfs(argv[i], f_verbose);
197                 if (!new_cache)
198                         rmtab_read();
199         }
200         if (!new_cache) {
201                 xtab_mount_read();
202                 exports_update(f_verbose);
203         }
204         xtab_export_write();
205         if (new_cache)
206                 cache_flush(force_flush);
207         if (!new_cache)
208                 xtab_mount_write();
209
210         return export_errno;
211 }
212
213 static void
214 exports_update_one(nfs_export *exp, int verbose)
215 {
216                 /* check mountpoint option */
217         if (exp->m_mayexport &&
218             exp->m_export.e_mountpoint &&
219             !is_mountpoint(exp->m_export.e_mountpoint[0]?
220                            exp->m_export.e_mountpoint:
221                            exp->m_export.e_path)) {
222                 printf("%s not exported as %s not a mountpoint.\n",
223                        exp->m_export.e_path, exp->m_export.e_mountpoint);
224                 exp->m_mayexport = 0;
225         }
226         if (exp->m_mayexport && ((exp->m_exported<1) || exp->m_changed)) {
227                 if (verbose)
228                         printf("%sexporting %s:%s to kernel\n",
229                                exp->m_exported ?"re":"",
230                                exp->m_client->m_hostname,
231                                exp->m_export.e_path);
232                 if (!export_export(exp))
233                         error(exp, errno);
234         }
235         if (exp->m_exported && ! exp->m_mayexport) {
236                 if (verbose)
237                         printf("unexporting %s:%s from kernel\n",
238                                exp->m_client->m_hostname,
239                                exp->m_export.e_path);
240                 if (!export_unexport(exp))
241                         error(exp, errno);
242         }
243 }
244
245
246 /* we synchronise intention with reality.
247  * entries with m_mayexport get exported
248  * entries with m_exported but not m_mayexport get unexported
249  * looking at m_client->m_type == MCL_FQDN and m_client->m_type == MCL_GSS only
250  */
251 static void
252 exports_update(int verbose)
253 {
254         nfs_export      *exp;
255
256         for (exp = exportlist[MCL_FQDN].p_head; exp; exp=exp->m_next) {
257                 exports_update_one(exp, verbose);
258         }
259         for (exp = exportlist[MCL_GSS].p_head; exp; exp=exp->m_next) {
260                 exports_update_one(exp, verbose);
261         }
262 }
263                         
264 /*
265  * export_all finds all entries and
266  *    marks them xtabent and mayexport so that they get exported
267  */
268 static void
269 export_all(int verbose)
270 {
271         nfs_export      *exp;
272         int             i;
273
274         for (i = 0; i < MCL_MAXTYPES; i++) {
275                 for (exp = exportlist[i].p_head; exp; exp = exp->m_next) {
276                         if (verbose)
277                                 printf("exporting %s:%s\n",
278                                        exp->m_client->m_hostname, 
279                                        exp->m_export.e_path);
280                         exp->m_xtabent = 1;
281                         exp->m_mayexport = 1;
282                         exp->m_changed = 1;
283                         exp->m_warned = 0;
284                         validate_export(exp);
285                 }
286         }
287 }
288
289
290 static void
291 exportfs(char *arg, char *options, int verbose)
292 {
293         struct exportent *eep;
294         nfs_export      *exp = NULL;
295         struct addrinfo *ai = NULL;
296         char            *path;
297         char            *hname = arg;
298         int             htype;
299
300         if ((path = strchr(arg, ':')) != NULL)
301                 *path++ = '\0';
302
303         if (!path || *path != '/') {
304                 xlog(L_ERROR, "Invalid exporting option: %s", arg);
305                 return;
306         }
307
308         if ((htype = client_gettype(hname)) == MCL_FQDN) {
309                 ai = host_addrinfo(hname);
310                 if (ai != NULL) {
311                         exp = export_find(ai, path);
312                         hname = ai->ai_canonname;
313                 }
314         } else
315                 exp = export_lookup(hname, path, 0);
316
317         if (!exp) {
318                 if (!(eep = mkexportent(hname, path, options)) ||
319                     !(exp = export_create(eep, 0)))
320                         goto out;
321         } else if (!updateexportent(&exp->m_export, options))
322                 goto out;
323
324         if (verbose)
325                 printf("exporting %s:%s\n", exp->m_client->m_hostname, 
326                         exp->m_export.e_path);
327         exp->m_xtabent = 1;
328         exp->m_mayexport = 1;
329         exp->m_changed = 1;
330         exp->m_warned = 0;
331         validate_export(exp);
332
333 out:
334         freeaddrinfo(ai);
335 }
336
337 static void
338 unexportfs(char *arg, int verbose)
339 {
340         nfs_export      *exp;
341         struct addrinfo *ai = NULL;
342         char            *path;
343         char            *hname = arg;
344         int             htype;
345
346         if ((path = strchr(arg, ':')) != NULL)
347                 *path++ = '\0';
348
349         if (!path || *path != '/') {
350                 xlog(L_ERROR, "Invalid unexporting option: %s", arg);
351                 return;
352         }
353
354         if ((htype = client_gettype(hname)) == MCL_FQDN) {
355                 ai = host_addrinfo(hname);
356                 if (ai)
357                         hname = ai->ai_canonname;
358         }
359
360         for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
361                 if (path && strcmp(path, exp->m_export.e_path))
362                         continue;
363                 if (htype != exp->m_client->m_type)
364                         continue;
365                 if (htype == MCL_FQDN
366                     && !matchhostname(exp->m_export.e_hostname,
367                                           hname))
368                         continue;
369                 if (htype != MCL_FQDN
370                     && strcasecmp(exp->m_export.e_hostname, hname))
371                         continue;
372                 if (verbose) {
373 #if 0
374                         if (exp->m_exported) {
375                                 printf("unexporting %s:%s from kernel\n",
376                                        exp->m_client->m_hostname,
377                                        exp->m_export.e_path);
378                         }
379                         else
380 #endif
381                                 printf("unexporting %s:%s\n",
382                                         exp->m_client->m_hostname, 
383                                         exp->m_export.e_path);
384                 }
385 #if 0
386                 if (exp->m_exported && !export_unexport(exp))
387                         error(exp, errno);
388 #endif
389                 exp->m_xtabent = 0;
390                 exp->m_mayexport = 0;
391         }
392
393         freeaddrinfo(ai);
394 }
395
396 static int can_test(void)
397 {
398         int fd;
399         int n;
400         char *setup = "nfsd 0.0.0.0 2147483647 -test-client-\n";
401         fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY);
402         if ( fd < 0) return 0;
403         n = write(fd, setup, strlen(setup));
404         close(fd);
405         if (n < 0)
406                 return 0;
407         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
408         if ( fd < 0) return 0;
409         close(fd);
410         return 1;
411 }
412
413 static int test_export(char *path, int with_fsid)
414 {
415         char buf[1024];
416         int fd, n;
417
418         sprintf(buf, "-test-client- %s 3 %d -1 -1 0\n",
419                 path,
420                 with_fsid ? NFSEXP_FSID : 0);
421         fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY);
422         if (fd < 0)
423                 return 0;
424         n = write(fd, buf, strlen(buf));
425         close(fd);
426         if (n < 0)
427                 return 0;
428         return 1;
429 }
430
431 static void
432 validate_export(nfs_export *exp)
433 {
434         /* Check that the given export point is potentially exportable.
435          * We just give warnings here, don't cause anything to fail.
436          * If a path doesn't exist, or is not a dir or file, give an warning
437          * otherwise trial-export to '-test-client-' and check for failure.
438          */
439         struct stat stb;
440         char *path = exp->m_export.e_path;
441         struct statfs64 stf;
442         int fs_has_fsid = 0;
443
444         if (stat(path, &stb) < 0) {
445                 xlog(L_ERROR, "Failed to stat %s: %m", path);
446                 return;
447         }
448         if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) {
449                 xlog(L_ERROR, "%s is neither a directory nor a file. "
450                         "Remote access will fail", path);
451                 return;
452         }
453         if (!can_test())
454                 return;
455
456         if (!statfs64(path, &stf) &&
457             (stf.f_fsid.__val[0] || stf.f_fsid.__val[1]))
458                 fs_has_fsid = 1;
459
460         if ((exp->m_export.e_flags & NFSEXP_FSID) || exp->m_export.e_uuid ||
461             fs_has_fsid) {
462                 if ( !test_export(path, 1)) {
463                         xlog(L_ERROR, "%s does not support NFS export", path);
464                         return;
465                 }
466         } else if ( ! test_export(path, 0)) {
467                 if (test_export(path, 1))
468                         xlog(L_ERROR, "%s requires fsid= for NFS export", path);
469                 else
470                         xlog(L_ERROR, "%s does not support NFS export", path);
471                 return;
472
473         }
474 }
475
476 static _Bool
477 is_hostname(const char *sp)
478 {
479         if (*sp == '\0' || *sp == '@')
480                 return false;
481
482         for (; *sp != '\0'; sp++) {
483                 if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/')
484                         return false;
485                 if (*sp == '\\' && sp[1] != '\0')
486                         sp++;
487         }
488
489         return true;
490 }
491
492 /*
493  * Take care to perform an explicit reverse lookup on presentation
494  * addresses.  Otherwise we don't get a real canonical name or a
495  * complete list of addresses.
496  */
497 static struct addrinfo *
498 address_list(const char *hostname)
499 {
500         struct addrinfo *ai;
501         char *cname;
502
503         ai = host_pton(hostname);
504         if (ai != NULL) {
505                 /* @hostname was a presentation address */
506                 cname = host_canonname(ai->ai_addr);
507                 freeaddrinfo(ai);
508                 if (cname != NULL)
509                         goto out;
510         }
511         /* @hostname was a hostname or had no reverse mapping */
512         cname = strdup(hostname);
513         if (cname == NULL)
514                 return NULL;
515
516 out:
517         ai = host_addrinfo(cname);
518         free(cname);
519         return ai;
520 }
521
522 static int
523 matchhostname(const char *hostname1, const char *hostname2)
524 {
525         struct addrinfo *results1 = NULL, *results2 = NULL;
526         struct addrinfo *ai1, *ai2;
527         int result = 0;
528
529         if (strcasecmp(hostname1, hostname2) == 0)
530                 return 1;
531
532         /*
533          * Don't pass export wildcards or netgroup names to DNS
534          */
535         if (!is_hostname(hostname1) || !is_hostname(hostname2))
536                 return 0;
537
538         results1 = address_list(hostname1);
539         if (results1 == NULL)
540                 goto out;
541         results2 = address_list(hostname2);
542         if (results2 == NULL)
543                 goto out;
544
545         if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) {
546                 result = 1;
547                 goto out;
548         }
549
550         for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next)
551                 for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next)
552                         if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) {
553                                 result = 1;
554                                 break;
555                         }
556
557 out:
558         freeaddrinfo(results1);
559         freeaddrinfo(results2);
560         return result;
561 }
562
563 /* Based on mnt_table_parse_dir() in
564    util-linux-ng/shlibs/mount/src/tab_parse.c */
565 static void
566 export_d_read(const char *dname)
567 {
568         int n = 0, i;
569         struct dirent **namelist = NULL;
570
571
572         n = scandir(dname, &namelist, NULL, versionsort);
573         if (n < 0) {
574                 if (errno == ENOENT)
575                         /* Silently return */
576                         return;
577                 xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno));
578         } else if (n == 0)
579                 return;
580
581         for (i = 0; i < n; i++) {
582                 struct dirent *d = namelist[i];
583                 size_t namesz;
584                 char fname[PATH_MAX + 1];
585                 int fname_len;
586
587
588                 if (d->d_type != DT_UNKNOWN 
589                     && d->d_type != DT_REG
590                     && d->d_type != DT_LNK)
591                         continue;
592                 if (*d->d_name == '.')
593                         continue;
594
595 #define _EXT_EXPORT_SIZ   (sizeof(_EXT_EXPORT) - 1)
596                 namesz = strlen(d->d_name);
597                 if (!namesz 
598                     || namesz < _EXT_EXPORT_SIZ + 1
599                     || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ),
600                               _EXT_EXPORT))
601                         continue;
602
603                 fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name);
604                 if (fname_len > PATH_MAX) {
605                         xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname);
606                         continue;
607                 }
608
609                 export_read(fname);
610         }
611                 
612         for (i = 0; i < n; i++)
613                 free(namelist[i]);
614         free(namelist);
615
616         return;
617 }
618
619 static char
620 dumpopt(char c, char *fmt, ...)
621 {
622         va_list ap;
623
624         va_start(ap, fmt);
625         printf("%c", c);
626         vprintf(fmt, ap);
627         va_end(ap);
628         return ',';
629 }
630
631 static void
632 dump(int verbose)
633 {
634         nfs_export      *exp;
635         struct exportent *ep;
636         int             htype;
637         char            *hname, c;
638
639         for (htype = 0; htype < MCL_MAXTYPES; htype++) {
640                 for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) {
641                         ep = &exp->m_export;
642                         if (!exp->m_xtabent)
643                             continue; /* neilb */
644                         if (htype == MCL_ANONYMOUS)
645                                 hname = "<world>";
646                         else
647                                 hname = ep->e_hostname;
648                         if (strlen(ep->e_path) > 14)
649                                 printf("%-14s\n\t\t%s", ep->e_path, hname);
650                         else
651                                 printf("%-14s\t%s", ep->e_path, hname);
652                         if (!verbose) {
653                                 printf("\n");
654                                 continue;
655                         }
656                         c = '(';
657                         if (ep->e_flags & NFSEXP_READONLY)
658                                 c = dumpopt(c, "ro");
659                         else
660                                 c = dumpopt(c, "rw");
661                         if (ep->e_flags & NFSEXP_ASYNC)
662                                 c = dumpopt(c, "async");
663                         if (ep->e_flags & NFSEXP_GATHERED_WRITES)
664                                 c = dumpopt(c, "wdelay");
665                         if (ep->e_flags & NFSEXP_NOHIDE)
666                                 c = dumpopt(c, "nohide");
667                         if (ep->e_flags & NFSEXP_CROSSMOUNT)
668                                 c = dumpopt(c, "crossmnt");
669                         if (ep->e_flags & NFSEXP_INSECURE_PORT)
670                                 c = dumpopt(c, "insecure");
671                         if (ep->e_flags & NFSEXP_ROOTSQUASH)
672                                 c = dumpopt(c, "root_squash");
673                         else
674                                 c = dumpopt(c, "no_root_squash");
675                         if (ep->e_flags & NFSEXP_ALLSQUASH)
676                                 c = dumpopt(c, "all_squash");
677                         if (ep->e_flags & NFSEXP_NOSUBTREECHECK)
678                                 c = dumpopt(c, "no_subtree_check");
679                         if (ep->e_flags & NFSEXP_NOAUTHNLM)
680                                 c = dumpopt(c, "insecure_locks");
681                         if (ep->e_flags & NFSEXP_NOACL)
682                                 c = dumpopt(c, "no_acl");
683                         if (ep->e_flags & NFSEXP_FSID)
684                                 c = dumpopt(c, "fsid=%d", ep->e_fsid);
685                         if (ep->e_uuid)
686                                 c = dumpopt(c, "fsid=%s", ep->e_uuid);
687                         if (ep->e_mountpoint)
688                                 c = dumpopt(c, "mountpoint%s%s", 
689                                             ep->e_mountpoint[0]?"=":"", 
690                                             ep->e_mountpoint);
691                         if (ep->e_anonuid != 65534)
692                                 c = dumpopt(c, "anonuid=%d", ep->e_anonuid);
693                         if (ep->e_anongid != 65534)
694                                 c = dumpopt(c, "anongid=%d", ep->e_anongid);
695                         switch(ep->e_fslocmethod) {
696                         case FSLOC_NONE:
697                                 break;
698                         case FSLOC_REFER:
699                                 c = dumpopt(c, "refer=%s", ep->e_fslocdata);
700                                 break;
701                         case FSLOC_REPLICA:
702                                 c = dumpopt(c, "replicas=%s", ep->e_fslocdata);
703                                 break;
704 #ifdef DEBUG
705                         case FSLOC_STUB:
706                                 c = dumpopt(c, "fsloc=stub");
707                                 break;
708 #endif
709                         }
710                         secinfo_show(stdout, ep);
711                         printf("%c\n", (c != '(')? ')' : ' ');
712                 }
713         }
714 }
715
716 static void
717 error(nfs_export *exp, int err)
718 {
719         xlog(L_ERROR, "%s:%s: %s", exp->m_client->m_hostname,
720                 exp->m_export.e_path, strerror(err));
721 }
722
723 static void
724 usage(const char *progname)
725 {
726         fprintf(stderr, "usage: %s [-aruv] [host:/path]\n", progname);
727         exit(1);
728 }