| /* |
| * 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/types.h> |
| #include <sys/stat.h> |
| #include <sys/vfs.h> |
| #include <sys/stat.h> |
| #include <sys/file.h> |
| #include <unistd.h> |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdarg.h> |
| #include <getopt.h> |
| #include <fcntl.h> |
| #include <netdb.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <limits.h> |
| #include <time.h> |
| |
| #define INT_TO_LONG_THRESHOLD_SECS (INT_MAX - (60 * 60 * 24)) |
| |
| #include "sockaddr.h" |
| #include "misc.h" |
| #include "nfslib.h" |
| #include "exportfs.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, int export_format); |
| static void error(nfs_export *exp, int err); |
| static void usage(const char *progname, int n); |
| static void validate_export(nfs_export *exp); |
| static int matchhostname(const char *hostname1, const char *hostname2); |
| static void export_d_read(const char *dname); |
| static void grab_lockfile(void); |
| static void release_lockfile(void); |
| |
| static const char *lockfile = EXP_LOCKFILE; |
| static int _lockfd = -1; |
| |
| /* |
| * If we aren't careful, changes made by exportfs can be lost |
| * when multiple exports process run at once: |
| * |
| * exportfs process 1 exportfs process 2 |
| * ------------------------------------------ |
| * reads etab version A reads etab version A |
| * adds new export B adds new export C |
| * writes A+B writes A+C |
| * |
| * The locking in support/export/xtab.c will prevent mountd from |
| * seeing a partially written version of etab, and will prevent |
| * the two writers above from writing simultaneously and |
| * corrupting etab, but to prevent problems like the above we |
| * need these additional lockfile() routines. |
| */ |
| static void |
| grab_lockfile() |
| { |
| _lockfd = open(lockfile, O_CREAT|O_RDWR, 0666); |
| if (_lockfd != -1) |
| lockf(_lockfd, F_LOCK, 0); |
| } |
| static void |
| release_lockfile() |
| { |
| if (_lockfd != -1) |
| lockf(_lockfd, F_ULOCK, 0); |
| } |
| |
| int |
| main(int argc, char **argv) |
| { |
| char *options = NULL; |
| char *progname = NULL; |
| int f_export = 1; |
| int f_all = 0; |
| int f_verbose = 0; |
| int f_export_format = 0; |
| int f_reexport = 0; |
| int f_ignore = 0; |
| int i, c; |
| int new_cache = 0; |
| int force_flush = 0; |
| |
| if ((progname = strrchr(argv[0], '/')) != NULL) |
| progname++; |
| else |
| progname = argv[0]; |
| |
| xlog_open(progname); |
| xlog_stderr(1); |
| xlog_syslog(0); |
| |
| while ((c = getopt(argc, argv, "afhio:ruvs")) != EOF) { |
| switch(c) { |
| case 'a': |
| f_all = 1; |
| break; |
| case 'f': |
| force_flush = 1; |
| break; |
| case 'h': |
| usage(progname, 0); |
| 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 's': |
| f_export_format = 1; |
| break; |
| default: |
| usage(progname, 1); |
| break; |
| } |
| } |
| |
| if (optind != argc && f_all) { |
| xlog(L_ERROR, "extra arguments are not permitted with -a or -r"); |
| return 1; |
| } |
| if (f_ignore && (f_all || ! f_export)) { |
| xlog(L_ERROR, "-i not meaningful with -a, -r or -u"); |
| return 1; |
| } |
| if (f_reexport && ! f_export) { |
| xlog(L_ERROR, "-r and -u are incompatible"); |
| return 1; |
| } |
| new_cache = check_new_cache(); |
| if (optind == argc && ! f_all) { |
| if (force_flush) { |
| if (new_cache) |
| cache_flush(1); |
| else { |
| xlog(L_ERROR, "-f is available only " |
| "with new cache controls. " |
| "Mount /proc/fs/nfsd first"); |
| return 1; |
| } |
| return 0; |
| } else { |
| xtab_export_read(); |
| dump(f_verbose, f_export_format); |
| return 0; |
| } |
| } |
| |
| /* |
| * Serialize things as best we can |
| */ |
| grab_lockfile(); |
| atexit(release_lockfile); |
| |
| if (f_export && ! f_ignore) { |
| export_read(_PATH_EXPORTS); |
| export_d_read(_PATH_EXPORTS_D); |
| } |
| 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_parsed(char *hname, char *path, char *options, int verbose) |
| { |
| struct exportent *eep; |
| nfs_export *exp = NULL; |
| struct addrinfo *ai = NULL; |
| int htype; |
| |
| if ((htype = client_gettype(hname)) == MCL_FQDN) { |
| ai = host_addrinfo(hname); |
| if (ai != NULL) { |
| exp = export_find(ai, path); |
| hname = ai->ai_canonname; |
| } |
| } else |
| exp = export_lookup(hname, path, 0); |
| |
| if (!exp) { |
| if (!(eep = mkexportent(hname, path, options)) || |
| !(exp = export_create(eep, 0))) |
| goto out; |
| } else if (!updateexportent(&exp->m_export, options)) |
| goto out; |
| |
| 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); |
| |
| out: |
| freeaddrinfo(ai); |
| } |
| |
| static int exportfs_generic(char *arg, char *options, int verbose) |
| { |
| char *path; |
| |
| if ((path = strchr(arg, ':')) != NULL) |
| *path++ = '\0'; |
| |
| if (!path || *path != '/') |
| return 1; |
| |
| exportfs_parsed(arg, path, options, verbose); |
| return 0; |
| } |
| |
| static int exportfs_ipv6(char *arg, char *options, int verbose) |
| { |
| char *path, *c, hname[NI_MAXHOST + strlen("/128")]; |
| |
| arg++; |
| c = strchr(arg, ']'); |
| if (c == NULL) |
| return 1; |
| strncpy(hname, arg, c - arg); |
| |
| /* no colon means this is a wildcarded DNS hostname */ |
| if (strchr(hname, ':') == NULL) |
| return exportfs_generic(--arg, options, verbose); |
| |
| path = strstr(c, ":/"); |
| if (path == NULL) |
| return 1; |
| *path++ = '\0'; |
| |
| /* if there's anything between the closing brace and the |
| * path separator, it's probably a prefix length */ |
| strcat(hname, ++c); |
| |
| exportfs_parsed(hname, path, options, verbose); |
| return 0; |
| } |
| |
| static void |
| exportfs(char *arg, char *options, int verbose) |
| { |
| int failed; |
| |
| if (*arg == '[') |
| failed = exportfs_ipv6(arg, options, verbose); |
| else |
| failed = exportfs_generic(arg, options, verbose); |
| if (failed) |
| xlog(L_ERROR, "Invalid export syntax: %s", arg); |
| } |
| |
| static void |
| unexportfs_parsed(char *hname, char *path, int verbose) |
| { |
| nfs_export *exp; |
| struct addrinfo *ai = NULL; |
| int htype; |
| int success = 0; |
| |
| if ((htype = client_gettype(hname)) == MCL_FQDN) { |
| ai = host_addrinfo(hname); |
| if (ai) |
| hname = ai->ai_canonname; |
| } |
| |
| 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; |
| success = 1; |
| } |
| if (!success) |
| xlog(L_ERROR, "Could not find '%s:%s' to unexport.", hname, path); |
| |
| freeaddrinfo(ai); |
| } |
| |
| static int unexportfs_generic(char *arg, int verbose) |
| { |
| char *path; |
| |
| if ((path = strchr(arg, ':')) != NULL) |
| *path++ = '\0'; |
| |
| if (!path || *path != '/') |
| return 1; |
| |
| unexportfs_parsed(arg, path, verbose); |
| return 0; |
| } |
| |
| static int unexportfs_ipv6(char *arg, int verbose) |
| { |
| char *path, *c, hname[NI_MAXHOST + strlen("/128")]; |
| |
| arg++; |
| c = strchr(arg, ']'); |
| if (c == NULL) |
| return 1; |
| strncpy(hname, arg, c - arg); |
| |
| /* no colon means this is a wildcarded DNS hostname */ |
| if (strchr(hname, ':') == NULL) |
| return unexportfs_generic(--arg, verbose); |
| |
| path = strstr(c, ":/"); |
| if (path == NULL) |
| return 1; |
| *path++ = '\0'; |
| |
| /* if there's anything between the closing brace and the |
| * path separator, it's probably a prefix length */ |
| strcat(hname, ++c); |
| |
| unexportfs_parsed(hname, path, verbose); |
| return 0; |
| } |
| |
| static void |
| unexportfs(char *arg, int verbose) |
| { |
| int failed; |
| |
| if (*arg == '[') |
| failed = unexportfs_ipv6(arg, verbose); |
| else |
| failed = unexportfs_generic(arg, verbose); |
| if (failed) |
| xlog(L_ERROR, "Invalid export syntax: %s", arg); |
| } |
| |
| static int can_test(void) |
| { |
| char buf[1024]; |
| int fd; |
| int n; |
| |
| fd = open("/proc/net/rpc/auth.unix.ip/channel", O_WRONLY); |
| if (fd < 0) |
| return 0; |
| |
| /* |
| * We introduce tolerance of 1 day to ensure that we use a |
| * LONG_MAX for the expiry timestamp before it is actually |
| * needed. To use LONG_MAX, the kernel code must have |
| * commit 2f74f972 (sunrpc: prepare NFS for 2038). |
| */ |
| if (time(NULL) > INT_TO_LONG_THRESHOLD_SECS) |
| sprintf(buf, "nfsd 0.0.0.0 %ld -test-client-\n", LONG_MAX); |
| else |
| sprintf(buf, "nfsd 0.0.0.0 %d -test-client-\n", INT_MAX); |
| |
| n = write(fd, buf, strlen(buf)); |
| 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]; |
| char *bp = buf; |
| int len = sizeof(buf); |
| int fd, n; |
| |
| n = snprintf(buf, len, "-test-client- "); |
| bp += n; |
| len -= n; |
| qword_add(&bp, &len, path); |
| if (len < 1) |
| return 0; |
| snprintf(bp, len, " 3 %d 65534 65534 0\n", 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) { |
| xlog(L_ERROR, "Failed to stat %s: %m", path); |
| return; |
| } |
| if (!S_ISDIR(stb.st_mode) && !S_ISREG(stb.st_mode)) { |
| xlog(L_ERROR, "%s is neither a directory nor a file. " |
| "Remote access will fail", 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)) { |
| xlog(L_ERROR, "%s does not support NFS export", path); |
| return; |
| } |
| } else if ( ! test_export(path, 0)) { |
| if (test_export(path, 1)) |
| xlog(L_ERROR, "%s requires fsid= for NFS export", path); |
| else |
| xlog(L_ERROR, "%s does not support NFS export", path); |
| return; |
| |
| } |
| } |
| |
| static _Bool |
| is_hostname(const char *sp) |
| { |
| if (*sp == '\0' || *sp == '@') |
| return false; |
| |
| for (; *sp != '\0'; sp++) { |
| if (*sp == '*' || *sp == '?' || *sp == '[' || *sp == '/') |
| return false; |
| if (*sp == '\\' && sp[1] != '\0') |
| sp++; |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Take care to perform an explicit reverse lookup on presentation |
| * addresses. Otherwise we don't get a real canonical name or a |
| * complete list of addresses. |
| */ |
| static struct addrinfo * |
| address_list(const char *hostname) |
| { |
| struct addrinfo *ai; |
| char *cname; |
| |
| ai = host_pton(hostname); |
| if (ai != NULL) { |
| /* @hostname was a presentation address */ |
| cname = host_canonname(ai->ai_addr); |
| freeaddrinfo(ai); |
| if (cname != NULL) |
| goto out; |
| } |
| /* @hostname was a hostname or had no reverse mapping */ |
| cname = strdup(hostname); |
| if (cname == NULL) |
| return NULL; |
| |
| out: |
| ai = host_addrinfo(cname); |
| free(cname); |
| return ai; |
| } |
| |
| static int |
| matchhostname(const char *hostname1, const char *hostname2) |
| { |
| struct addrinfo *results1 = NULL, *results2 = NULL; |
| struct addrinfo *ai1, *ai2; |
| int result = 0; |
| |
| if (strcasecmp(hostname1, hostname2) == 0) |
| return 1; |
| |
| /* |
| * Don't pass export wildcards or netgroup names to DNS |
| */ |
| if (!is_hostname(hostname1) || !is_hostname(hostname2)) |
| return 0; |
| |
| results1 = address_list(hostname1); |
| if (results1 == NULL) |
| goto out; |
| results2 = address_list(hostname2); |
| if (results2 == NULL) |
| goto out; |
| |
| if (strcasecmp(results1->ai_canonname, results2->ai_canonname) == 0) { |
| result = 1; |
| goto out; |
| } |
| |
| for (ai1 = results1; ai1 != NULL; ai1 = ai1->ai_next) |
| for (ai2 = results2; ai2 != NULL; ai2 = ai2->ai_next) |
| if (nfs_compare_sockaddr(ai1->ai_addr, ai2->ai_addr)) { |
| result = 1; |
| break; |
| } |
| |
| out: |
| freeaddrinfo(results1); |
| freeaddrinfo(results2); |
| return result; |
| } |
| |
| /* Based on mnt_table_parse_dir() in |
| util-linux-ng/shlibs/mount/src/tab_parse.c */ |
| static void |
| export_d_read(const char *dname) |
| { |
| int n = 0, i; |
| struct dirent **namelist = NULL; |
| |
| |
| n = scandir(dname, &namelist, NULL, versionsort); |
| if (n < 0) { |
| if (errno == ENOENT) |
| /* Silently return */ |
| return; |
| xlog(L_NOTICE, "scandir %s: %s", dname, strerror(errno)); |
| } else if (n == 0) |
| return; |
| |
| for (i = 0; i < n; i++) { |
| struct dirent *d = namelist[i]; |
| size_t namesz; |
| char fname[PATH_MAX + 1]; |
| int fname_len; |
| |
| |
| if (d->d_type != DT_UNKNOWN |
| && d->d_type != DT_REG |
| && d->d_type != DT_LNK) |
| continue; |
| if (*d->d_name == '.') |
| continue; |
| |
| #define _EXT_EXPORT_SIZ (sizeof(_EXT_EXPORT) - 1) |
| namesz = strlen(d->d_name); |
| if (!namesz |
| || namesz < _EXT_EXPORT_SIZ + 1 |
| || strcmp(d->d_name + (namesz - _EXT_EXPORT_SIZ), |
| _EXT_EXPORT)) |
| continue; |
| |
| fname_len = snprintf(fname, PATH_MAX +1, "%s/%s", dname, d->d_name); |
| if (fname_len > PATH_MAX) { |
| xlog(L_WARNING, "Too long file name: %s in %s", d->d_name, dname); |
| continue; |
| } |
| |
| export_read(fname); |
| } |
| |
| for (i = 0; i < n; i++) |
| free(namelist[i]); |
| free(namelist); |
| |
| 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, int export_format) |
| { |
| char buf[1024]; |
| char *bp; |
| int len; |
| 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 = (export_format) ? "*" : "<world>"; |
| else |
| hname = ep->e_hostname; |
| if (strlen(ep->e_path) > 14 && !export_format) |
| printf("%-14s\n\t\t%s", ep->e_path, hname); |
| else |
| if (export_format) { |
| bp = buf; |
| len = sizeof(buf) - 1; |
| qword_add(&bp, &len, ep->e_path); |
| *bp = '\0'; |
| printf("%s %s", buf, hname); |
| } else { |
| printf("%-14s\t%s", ep->e_path, hname); |
| } |
| |
| if (!verbose && !export_format) { |
| 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) |
| { |
| xlog(L_ERROR, "%s:%s: %s", exp->m_client->m_hostname, |
| exp->m_export.e_path, strerror(err)); |
| } |
| |
| static void |
| usage(const char *progname, int n) |
| { |
| fprintf(stderr, "usage: %s [-afhioruvs] [host:/path]\n", progname); |
| exit(n); |
| } |