diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 06:03:02 +0000 |
commit | 4897093455a2bf08f3db3a1132cc2f6f5484d77c (patch) | |
tree | 9e6373544263f003139431fb4b08f9766e1ed530 /utils/exportfs/exportfs.c | |
parent | Initial commit. (diff) | |
download | nfs-utils-upstream.tar.xz nfs-utils-upstream.zip |
Adding upstream version 1:2.6.4.upstream/1%2.6.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'utils/exportfs/exportfs.c')
-rw-r--r-- | utils/exportfs/exportfs.c | 768 |
1 files changed, 768 insertions, 0 deletions
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c new file mode 100644 index 0000000..b03a047 --- /dev/null +++ b/utils/exportfs/exportfs.c @@ -0,0 +1,768 @@ +/* + * 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 <limits.h> +#include <time.h> + +#define INT_TO_LONG_THRESHOLD_SECS (INT_MAX - (60 * 60 * 24)) + +#include "sockaddr.h" +#include "misc.h" +#include "nfsd_path.h" +#include "nfslib.h" +#include "exportfs.h" +#include "xlog.h" +#include "conffile.h" +#include "reexport.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 dump(int verbose, int export_format); +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 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(void) +{ + _lockfd = open(lockfile, O_CREAT|O_RDWR, 0666); + if (_lockfd != -1) + lockf(_lockfd, F_LOCK, 0); +} +static void +release_lockfile(void) +{ + if (_lockfd != -1) { + lockf(_lockfd, F_ULOCK, 0); + close(_lockfd); + _lockfd = -1; + } +} +inline static void +read_exportfs_conf(char **argv) +{ + char *s; + + conf_init_file(NFS_CONFFILE); + xlog_set_debug("exportfs"); + + /* NOTE: following uses "mountd" section of nfs.conf !!!! */ + s = conf_get_str("mountd", "state-directory-path"); + /* Also look in the exportd section */ + if (s == NULL) + s = conf_get_str("exportd", "state-directory-path"); + if (s && !state_setup_basedir(argv[0], s)) + exit(1); + +} +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 force_flush = 0; + + if ((progname = strrchr(argv[0], '/')) != NULL) + progname++; + else + progname = argv[0]; + + xlog_open(progname); + xlog_stderr(1); + xlog_syslog(0); + + /* Read in config setting */ + read_exportfs_conf(argv); + + nfsd_path_init(); + + while ((c = getopt(argc, argv, "ad:fhio:ruvs")) != EOF) { + switch(c) { + case 'a': + f_all = 1; + break; + case 'd': + xlog_sconfig(optarg, 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; + } + + if (!setup_state_path_names(progname, ETAB, ETABTMP, ETABLCK, &etab)) + return 1; + + if (optind == argc && ! f_all) { + if (force_flush) { + cache_flush(); + free_state_path_names(&etab); + return 0; + } else { + xtab_export_read(); + dump(f_verbose, f_export_format); + free_state_path_names(&etab); + export_freeall(); + return 0; + } + } + + /* + * Serialize things as best we can + */ + grab_lockfile(); + atexit(release_lockfile); + + if (f_export && ! f_ignore) { + if (! (export_read(_PATH_EXPORTS, 0) + + export_d_read(_PATH_EXPORTS_D, 0))) { + if (f_verbose) + xlog(L_WARNING, "No file systems exported!"); + } + } + 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); + } + xtab_export_write(); + cache_flush(); + free_state_path_names(&etab); + export_freeall(); + + return export_errno; +} + +/* + * 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: + nfs_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; + + arg++; + c = strchr(arg, ']'); + if (c == NULL) + return 1; + + /* no colon means this is a wildcarded DNS hostname */ + if (memchr(arg, ':', c - arg) == 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 */ + memmove(c, c + 1, path - c); + + exportfs_parsed(arg, 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; + } + + /* + * It's possible the specified path ends with a '/'. But + * the entry from exportlist won't has the trailing '/', + * so need to deal with it. + */ + size_t nlen = strlen(path); + while ((nlen > 1) && (path[nlen - 1] == '/')) + nlen--; + + for (exp = exportlist[htype].p_head; exp; exp = exp->m_next) { + if (strlen(exp->m_export.e_path) != nlen) + continue; + if (path && strncmp(path, exp->m_export.e_path, nlen)) + 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); + } + exp->m_xtabent = 0; + exp->m_mayexport = 0; + success = 1; + } + if (!success) + xlog(L_ERROR, "Could not find '%s:%s' to unexport.", hname, path); + + nfs_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; + + arg++; + c = strchr(arg, ']'); + if (c == NULL) + return 1; + + /* no colon means this is a wildcarded DNS hostname */ + if (memchr(arg, ':', c - arg) == 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 */ + memmove(c, c + 1, path - c); + + unexportfs_parsed(arg, 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] = { 0 }; + int fd; + int n; + size_t bufsiz = sizeof(buf); + + 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) + snprintf(buf, bufsiz-1, "nfsd 0.0.0.0 %ld -test-client-\n", LONG_MAX); + else + snprintf(buf, bufsiz-1, "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 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 = exportent_realpath(&exp->m_export); + struct statfs 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)) { + xlog(L_ERROR, "%s is not a directory. " + "Remote access will fail", path); + return; + } + if (!can_test()) + return; + + if (!statfs(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 ( !export_test(&exp->m_export, 1)) { + xlog(L_ERROR, "%s does not support NFS export", path); + return; + } + } else if ( !export_test(&exp->m_export, 0)) { + if (export_test(&exp->m_export, 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); + nfs_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: + nfs_freeaddrinfo(results1); + nfs_freeaddrinfo(results2); + return result; +} + +#ifdef HAVE_FUNC_ATTRIBUTE_FORMAT +__attribute__((format (printf, 2, 3))) +#endif +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) +{ + /* buf[] size should >= sizeof(struct exportent->e_path) */ + char buf[NFS_MAXPATHLEN+1] = { 0 }; + 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_ASYNC) + c = dumpopt(c, "async"); + else + c = dumpopt(c, "sync"); + if (ep->e_flags & NFSEXP_GATHERED_WRITES) + c = dumpopt(c, "wdelay"); + else + c = dumpopt(c, "no_wdelay"); + if (ep->e_flags & NFSEXP_NOHIDE) + c = dumpopt(c, "nohide"); + else + c = dumpopt(c, "hide"); + if (ep->e_flags & NFSEXP_CROSSMOUNT) + c = dumpopt(c, "crossmnt"); + 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_NOREADDIRPLUS) + c = dumpopt(c, "nordirplus"); + if (ep->e_flags & NFSEXP_SECURITY_LABEL) + c = dumpopt(c, "security_label"); + if (ep->e_flags & NFSEXP_NOACL) + c = dumpopt(c, "no_acl"); + if (ep->e_flags & NFSEXP_PNFS) + c = dumpopt(c, "pnfs"); + 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_reexport) { + switch (ep->e_reexport) { + case REEXP_AUTO_FSIDNUM: + c = dumpopt(c, "reexport=%s", "auto-fsidnum"); + break; + case REEXP_PREDEFINED_FSIDNUM: + c = dumpopt(c, "reexport=%s", "predefined-fsidnum"); + break; + } + } + 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); + xprtsecinfo_show(stdout, ep); + printf("%c\n", (c != '(')? ')' : ' '); + } + } +} + +static void +usage(const char *progname, int n) +{ + fprintf(stderr, "usage: %s [-adfhioruvs] [host:/path]\n", progname); + exit(n); +} |