summaryrefslogtreecommitdiffstats
path: root/utils/exportfs/exportfs.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 06:03:02 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 06:03:02 +0000
commit4897093455a2bf08f3db3a1132cc2f6f5484d77c (patch)
tree9e6373544263f003139431fb4b08f9766e1ed530 /utils/exportfs/exportfs.c
parentInitial commit. (diff)
downloadnfs-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.c768
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);
+}