| /* |
| * utils/exportfs/exportfs.c |
| * |
| * Export file systems to knfsd |
| * |
| * Copyright (C) 1995, 1996, 1997 Olaf Kirch <okir@monad.swb.de> |
| * |
| * Extensive changes, 1999, Neil Brown <neilb@cse.unsw.edu.au> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <sys/vfs.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <netdb.h> |
| #include <errno.h> |
| #include "xmalloc.h" |
| #include "misc.h" |
| #include "nfslib.h" |
| #include "exportfs.h" |
| #include "xmalloc.h" |
| #include "xlog.h" |
| |
| static void export_all(int verbose); |
| static void exportfs(char *arg, char *options, int verbose); |
| static void unexportfs(char *arg, int verbose); |
| static void exports_update(int verbose); |
| static void dump(int verbose); |
| static void error(nfs_export *exp, int err); |
| static void usage(void); |
| static void validate_export(nfs_export *exp); |
| |
| int |
| main(int argc, char **argv) |
| { |
| char *options = NULL; |
| int f_export = 1; |
| int f_all = 0; |
| int f_verbose = 0; |
| int f_reexport = 0; |
| int f_ignore = 0; |
| int i, c; |
| int new_cache = 0; |
| int force_flush = 0; |
| |
| xlog_open("exportfs"); |
| |
| export_errno = 0; |
| |
| while ((c = getopt(argc, argv, "aio:ruvf")) != EOF) { |
| switch(c) { |
| case 'a': |
| f_all = 1; |
| break; |
| case 'i': |
| f_ignore = 1; |
| break; |
| case 'o': |
| options = optarg; |
| break; |
| case 'r': |
| f_reexport = 1; |
| f_all = 1; |
| break; |
| case 'u': |
| f_export = 0; |
| break; |
| case 'v': |
| f_verbose = 1; |
| break; |
| case 'f': |
| force_flush = 1; |
| break; |
| default: |
| usage(); |
| break; |
| } |
| } |
| |
| if (optind != argc && f_all) { |
| fprintf(stderr,"exportfs: extra arguments are not permitted with -a or -r.\n"); |
| return 1; |
| } |
| if (f_ignore && (f_all || ! f_export)) { |
| fprintf(stderr,"exportfs: -i not meaningful with -a, -r or -u.\n"); |
| return 1; |
| } |
| if (f_reexport && ! f_export) { |
| fprintf(stderr, "exportfs: -r and -u are incompatible.\n"); |
| return 1; |
| } |
| new_cache = check_new_cache(); |
| if (optind == argc && ! f_all) { |
| if (force_flush) { |
| if (new_cache) |
| cache_flush(1); |
| else { |
| fprintf(stderr, "exportfs: -f: only available with new cache controls: mount /proc/fs/nfsd first\n"); |
| exit(1); |
| } |
| return 0; |
| } else { |
| xtab_export_read(); |
| dump(f_verbose); |
| return 0; |
| } |
| } |
| if (f_export && ! f_ignore) |
| export_read(_PATH_EXPORTS); |
| if (f_export) { |
| if (f_all) |
| export_all(f_verbose); |
| else |
| for (i = optind; i < argc ; i++) |
| exportfs(argv[i], options, f_verbose); |
| } |
| /* If we are unexporting everything, then |
| * don't care about what should be exported, as that |
| * may require DNS lookups.. |
| */ |
| if (! ( !f_export && f_all)) { |
| /* note: xtab_*_read does not update entries if they already exist, |
| * so this will not lose new options |
| */ |
| if (!f_reexport) |
| xtab_export_read(); |
| if (!f_export) |
| for (i = optind ; i < argc ; i++) |
| unexportfs(argv[i], f_verbose); |
| if (!new_cache) |
| rmtab_read(); |
| } |
| if (!new_cache) { |
| xtab_mount_read(); |
| exports_update(f_verbose); |
| } |
| xtab_export_write(); |
| if (new_cache) |
| cache_flush(force_flush); |
| if (!new_cache) |
| xtab_mount_write(); |
| |
| return export_errno; |
| } |
| |
| static void |
| exports_update_one(nfs_export *exp, int verbose) |
| { |
| /* check mountpoint option */ |
| if (exp->m_mayexport && |
| exp->m_export.e_mountpoint && |
| !is_mountpoint(exp->m_export.e_mountpoint[0]? |
| exp->m_export.e_mountpoint: |
| exp->m_export.e_path)) { |
| printf("%s not exported as %s not a mountpoint.\n", |
| exp->m_export.e_path, exp->m_export.e_mountpoint); |
| exp->m_mayexport = 0; |
| } |
| if (exp->m_mayexport && ((exp->m_exported<1) || exp->m_changed)) { |
| if (verbose) |
| printf("%sexporting %s:%s to kernel\n", |
| exp->m_exported ?"re":"", |
| exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| if (!export_export(exp)) |
| error(exp, errno); |
| } |
| if (exp->m_exported && ! exp->m_mayexport) { |
| if (verbose) |
| printf("unexporting %s:%s from kernel\n", |
| exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| if (!export_unexport(exp)) |
| error(exp, errno); |
| } |
| } |
| |
| |
| /* we synchronise intention with reality. |
| * entries with m_mayexport get exported |
| * entries with m_exported but not m_mayexport get unexported |
| * looking at m_client->m_type == MCL_FQDN and m_client->m_type == MCL_GSS only |
| */ |
| static void |
| exports_update(int verbose) |
| { |
| nfs_export *exp; |
| |
| for (exp = exportlist[MCL_FQDN].p_head; exp; exp=exp->m_next) { |
| exports_update_one(exp, verbose); |
| } |
| for (exp = exportlist[MCL_GSS].p_head; exp; exp=exp->m_next) { |
| exports_update_one(exp, verbose); |
| } |
| } |
| |
| /* |
| * export_all finds all entries and |
| * marks them xtabent and mayexport so that they get exported |
| */ |
| static void |
| export_all(int verbose) |
| { |
| nfs_export *exp; |
| int i; |
| |
| for (i = 0; i < MCL_MAXTYPES; i++) { |
| for (exp = exportlist[i].p_head; exp; exp = exp->m_next) { |
| if (verbose) |
| printf("exporting %s:%s\n", |
| exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| exp->m_xtabent = 1; |
| exp->m_mayexport = 1; |
| exp->m_changed = 1; |
| exp->m_warned = 0; |
| validate_export(exp); |
| } |
| } |
| } |
| |
| |
| static void |
| exportfs(char *arg, char *options, int verbose) |
| { |
| struct exportent *eep; |
| nfs_export *exp; |
| struct hostent *hp = NULL; |
| char *path; |
| char *hname = arg; |
| int htype; |
| |
| if ((path = strchr(arg, ':')) != NULL) |
| *path++ = '\0'; |
| |
| if (!path || *path != '/') { |
| fprintf(stderr, "Invalid exporting option: %s\n", arg); |
| return; |
| } |
| |
| if ((htype = client_gettype(hname)) == MCL_FQDN && |
| (hp = gethostbyname(hname)) != NULL) { |
| struct hostent *hp2 = hostent_dup (hp); |
| hp = gethostbyaddr(hp2->h_addr, hp2->h_length, |
| hp2->h_addrtype); |
| if (hp) { |
| free(hp2); |
| hp = hostent_dup(hp); |
| } else |
| hp = hp2; |
| exp = export_find(hp, path); |
| hname = hp->h_name; |
| } else { |
| exp = export_lookup(hname, path, 0); |
| } |
| |
| if (!exp) { |
| if (!(eep = mkexportent(hname, path, options)) || |
| !(exp = export_create(eep, 0))) { |
| if (hp) free (hp); |
| return; |
| } |
| } else if (!updateexportent(&exp->m_export, options)) { |
| if (hp) free (hp); |
| return; |
| } |
| |
| if (verbose) |
| printf("exporting %s:%s\n", exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| exp->m_xtabent = 1; |
| exp->m_mayexport = 1; |
| exp->m_changed = 1; |
| exp->m_warned = 0; |
| validate_export(exp); |
| if (hp) free (hp); |
| } |
| |
| static void |
| unexportfs(char *arg, int verbose) |
| { |
| nfs_export *exp; |
| struct hostent *hp = NULL; |
| char *path; |
| char *hname = arg; |
| int htype; |
| |
| if ((path = strchr(arg, ':')) != NULL) |
| *path++ = '\0'; |
| |
| if (!path || *path != '/') { |
| fprintf(stderr, "Invalid unexporting option: %s\n", |
| arg); |
| return; |
| } |
| |
| if ((htype = client_gettype(hname)) == MCL_FQDN) { |
| if ((hp = gethostbyname(hname)) != 0) { |
| hp = hostent_dup (hp); |
| hname = (char *) hp->h_name; |
| } |
| } |
| |
| for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) { |
| if (path && strcmp(path, exp->m_export.e_path)) |
| continue; |
| if (htype != exp->m_client->m_type) |
| continue; |
| if (htype == MCL_FQDN |
| && !matchhostname(exp->m_export.e_hostname, |
| hname)) |
| continue; |
| if (htype != MCL_FQDN |
| && strcasecmp(exp->m_export.e_hostname, hname)) |
| continue; |
| if (verbose) { |
| #if 0 |
| if (exp->m_exported) { |
| printf("unexporting %s:%s from kernel\n", |
| exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| } |
| else |
| #endif |
| printf("unexporting %s:%s\n", |
| exp->m_client->m_hostname, |
| exp->m_export.e_path); |
| } |
| #if 0 |
| if (exp->m_exported && !export_unexport(exp)) |
| error(exp, errno); |
| #endif |
| exp->m_xtabent = 0; |
| exp->m_mayexport = 0; |
| } |
| |
| if (hp) free (hp); |
| } |
| |
| static int can_test(void) |
| { |
| int fd; |
| int n; |
| char *setup = "nfsd 0.0.0.0 2147483647 -test-client-\n"; |
| fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY); |
| if ( fd < 0) return 0; |
| n = write(fd, setup, strlen(setup)); |
| close(fd); |
| if (n < 0) |
| return 0; |
| fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY); |
| if ( fd < 0) return 0; |
| close(fd); |
| return 1; |
| } |
| |
| static int test_export(char *path, int with_fsid) |
| { |
| char buf[1024]; |
| int fd, n; |
| |
| sprintf(buf, "-test-client- %s 3 %d -1 -1 0\n", |
| path, |
| with_fsid ? NFSEXP_FSID : 0); |
| fd = open("/proc/net/rpc/nfsd.export/channel", O_WRONLY); |
| if (fd < 0) |
| return 0; |
| n = write(fd, buf, strlen(buf)); |
| close(fd); |
| if (n < 0) |
| return 0; |
| return 1; |
| } |
| |
| static void |
| validate_export(nfs_export *exp) |
| { |
| /* Check that the given export point is potentially exportable. |
| * We just give warnings here, don't cause anything to fail. |
| * If a path doesn't exist, or is not a dir or file, give an warning |
| * otherwise trial-export to '-test-client-' and check for failure. |
| */ |
| struct stat stb; |
| char *path = exp->m_export.e_path; |
| struct statfs64 stf; |
| int fs_has_fsid = 0; |
| |
| if (stat(path, &stb) < 0) { |
| fprintf(stderr, "exportfs: Warning: %s does not exist\n", |
| path); |
| return; |
| } |
| if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) { |
| fprintf(stderr, "exportfs: Warning: %s is neither " |
| "a directory nor a file.\n" |
| " remote access will fail\n", path); |
| return; |
| } |
| if (!can_test()) |
| return; |
| |
| if (!statfs64(path, &stf) && |
| (stf.f_fsid.__val[0] || stf.f_fsid.__val[1])) |
| fs_has_fsid = 1; |
| |
| if ((exp->m_export.e_flags & NFSEXP_FSID) || exp->m_export.e_uuid || |
| fs_has_fsid) { |
| if ( !test_export(path, 1)) { |
| fprintf(stderr, "exportfs: Warning: %s does not " |
| "support NFS export.\n", |
| path); |
| return; |
| } |
| } else if ( ! test_export(path, 0)) { |
| if (test_export(path, 1)) |
| fprintf(stderr, "exportfs: Warning: %s requires fsid= " |
| "for NFS export\n", path); |
| else |
| fprintf(stderr, "exportfs: Warning: %s does not " |
| "support NFS export.\n", |
| path); |
| return; |
| |
| } |
| } |
| |
| |
| static char |
| dumpopt(char c, char *fmt, ...) |
| { |
| va_list ap; |
| |
| va_start(ap, fmt); |
| printf("%c", c); |
| vprintf(fmt, ap); |
| va_end(ap); |
| return ','; |
| } |
| |
| static void |
| dump(int verbose) |
| { |
| nfs_export *exp; |
| struct exportent *ep; |
| int htype; |
| char *hname, c; |
| |
| for (htype = 0; htype < MCL_MAXTYPES; htype++) { |
| for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) { |
| ep = &exp->m_export; |
| if (!exp->m_xtabent) |
| continue; /* neilb */ |
| if (htype == MCL_ANONYMOUS) |
| hname = "<world>"; |
| else |
| hname = ep->e_hostname; |
| if (strlen(ep->e_path) > 14) |
| printf("%-14s\n\t\t%s", ep->e_path, hname); |
| else |
| printf("%-14s\t%s", ep->e_path, hname); |
| if (!verbose) { |
| printf("\n"); |
| continue; |
| } |
| c = '('; |
| if (ep->e_flags & NFSEXP_READONLY) |
| c = dumpopt(c, "ro"); |
| else |
| c = dumpopt(c, "rw"); |
| if (ep->e_flags & NFSEXP_ASYNC) |
| c = dumpopt(c, "async"); |
| if (ep->e_flags & NFSEXP_GATHERED_WRITES) |
| c = dumpopt(c, "wdelay"); |
| if (ep->e_flags & NFSEXP_NOHIDE) |
| c = dumpopt(c, "nohide"); |
| if (ep->e_flags & NFSEXP_CROSSMOUNT) |
| c = dumpopt(c, "crossmnt"); |
| if (ep->e_flags & NFSEXP_INSECURE_PORT) |
| c = dumpopt(c, "insecure"); |
| if (ep->e_flags & NFSEXP_ROOTSQUASH) |
| c = dumpopt(c, "root_squash"); |
| else |
| c = dumpopt(c, "no_root_squash"); |
| if (ep->e_flags & NFSEXP_ALLSQUASH) |
| c = dumpopt(c, "all_squash"); |
| if (ep->e_flags & NFSEXP_NOSUBTREECHECK) |
| c = dumpopt(c, "no_subtree_check"); |
| if (ep->e_flags & NFSEXP_NOAUTHNLM) |
| c = dumpopt(c, "insecure_locks"); |
| if (ep->e_flags & NFSEXP_NOACL) |
| c = dumpopt(c, "no_acl"); |
| if (ep->e_flags & NFSEXP_FSID) |
| c = dumpopt(c, "fsid=%d", ep->e_fsid); |
| if (ep->e_uuid) |
| c = dumpopt(c, "fsid=%s", ep->e_uuid); |
| if (ep->e_mountpoint) |
| c = dumpopt(c, "mountpoint%s%s", |
| ep->e_mountpoint[0]?"=":"", |
| ep->e_mountpoint); |
| if (ep->e_anonuid != 65534) |
| c = dumpopt(c, "anonuid=%d", ep->e_anonuid); |
| if (ep->e_anongid != 65534) |
| c = dumpopt(c, "anongid=%d", ep->e_anongid); |
| switch(ep->e_fslocmethod) { |
| case FSLOC_NONE: |
| break; |
| case FSLOC_REFER: |
| c = dumpopt(c, "refer=%s", ep->e_fslocdata); |
| break; |
| case FSLOC_REPLICA: |
| c = dumpopt(c, "replicas=%s", ep->e_fslocdata); |
| break; |
| #ifdef DEBUG |
| case FSLOC_STUB: |
| c = dumpopt(c, "fsloc=stub"); |
| break; |
| #endif |
| } |
| secinfo_show(stdout, ep); |
| printf("%c\n", (c != '(')? ')' : ' '); |
| } |
| } |
| } |
| |
| static void |
| error(nfs_export *exp, int err) |
| { |
| fprintf(stderr, "%s:%s: %s\n", exp->m_client->m_hostname, |
| exp->m_export.e_path, strerror(err)); |
| } |
| |
| static void |
| usage(void) |
| { |
| fprintf(stderr, "usage: exportfs [-aruv] [host:/path]\n"); |
| exit(1); |
| } |