Blob Blame History Raw
diff --git a/support/misc/nfsd_path.c b/support/misc/nfsd_path.c
index 8efbfcd..65e53c1 100644
--- a/support/misc/nfsd_path.c
+++ b/support/misc/nfsd_path.c
@@ -110,7 +110,7 @@ nfsd_setup_workqueue(void)
 
 	if (!rootdir)
 		return;
-printf("rootdir %s\n", rootdir);
+
 	nfsd_wq = xthread_workqueue_alloc();
 	if (!nfsd_wq)
 		return;
diff --git a/support/nfs/conffile.c b/support/nfs/conffile.c
index 3d13610..a4ea067 100644
--- a/support/nfs/conffile.c
+++ b/support/nfs/conffile.c
@@ -52,10 +52,14 @@
 #include <libgen.h>
 #include <sys/file.h>
 #include <time.h>
+#include <dirent.h>
 
 #include "conffile.h"
 #include "xlog.h"
 
+#define CONF_FILE_EXT ".conf"
+#define CONF_FILE_EXT_LEN ((int) (sizeof(CONF_FILE_EXT) - 1))
+
 #pragma GCC visibility push(hidden)
 
 static void conf_load_defaults(void);
@@ -456,7 +460,7 @@ conf_parse_line(int trans, char *line, const char *filename, int lineno, char **
 		free(subconf);
 	} else {
 		/* XXX Perhaps should we not ignore errors?  */
-		conf_set(trans, *section, *subsection, line, val, 0, 0);
+		conf_set(trans, *section, *subsection, line, val, 1, 0);
 	}
 }
 
@@ -577,6 +581,30 @@ static void conf_free_bindings(void)
 	}
 }
 
+static int
+conf_load_files(int trans, const char *conf_file)
+{
+	char *conf_data;
+	char *section = NULL;
+	char *subsection = NULL;
+
+	conf_data = conf_readfile(conf_file);
+	if (conf_data == NULL)
+		return 1;
+
+	/* Load default configuration values.  */
+	conf_load_defaults();
+
+	/* Parse config contents into the transaction queue */
+	conf_parse(trans, conf_data, &section, &subsection, conf_file);
+	if (section) 
+		free(section);
+	if (subsection) 
+		free(subsection);
+	free(conf_data);
+
+	return 0;
+}
 /* Open the config file and map it into our address space, then parse it.  */
 static int
 conf_load_file(const char *conf_file)
@@ -609,18 +637,129 @@ conf_load_file(const char *conf_file)
 	return 0;
 }
 
+static void 
+conf_init_dir(const char *conf_file)
+{
+	struct dirent **namelist = NULL;
+	char *dname, fname[PATH_MAX], *cname;
+	int n = 0, nfiles = 0, i, fname_len, dname_len;
+	int trans, rv, path_len;
+
+	dname = malloc(strlen(conf_file) + 3);
+	if (dname == NULL) {
+		xlog(L_WARNING, "conf_init_dir: malloc: %s", strerror(errno));
+		return;	
+	}
+	sprintf(dname, "%s.d", conf_file);
+
+	n = scandir(dname, &namelist, NULL, versionsort);
+	if (n < 0) {
+		if (errno != ENOENT) {
+			xlog(L_WARNING, "conf_init_dir: scandir %s: %s", 
+				dname, strerror(errno));
+		}
+		free(dname);
+		return;
+	} else if (n == 0) {
+		free(dname);
+		return;
+	}
+
+	trans = conf_begin();
+	dname_len = strlen(dname);
+	for (i = 0; i < n; i++ ) {
+		struct dirent *d = namelist[i];
+
+	 	switch (d->d_type) {
+			case DT_UNKNOWN:
+			case DT_REG:
+			case DT_LNK:
+				break;
+			default:
+				continue;
+		}
+		if (*d->d_name == '.')
+			continue;
+		
+		fname_len = strlen(d->d_name);
+		path_len = (fname_len + dname_len);
+		if (!fname_len || path_len > PATH_MAX) {
+			xlog(L_WARNING, "conf_init_dir: Too long file name: %s in %s", 
+				d->d_name, dname);
+			continue; 
+		}
+
+		/*
+		 * Check the naming of the file. Only process files
+		 * that end with CONF_FILE_EXT
+		 */
+		if (fname_len <= CONF_FILE_EXT_LEN) {
+			xlog(D_GENERAL, "conf_init_dir: %s: name too short", 
+				d->d_name);
+			continue;
+		}
+		cname = (d->d_name + (fname_len - CONF_FILE_EXT_LEN));
+		if (strcmp(cname, CONF_FILE_EXT) != 0) {
+			xlog(D_GENERAL, "conf_init_dir: %s: invalid file extension", 
+				d->d_name);
+			continue;
+		}
+
+		rv = snprintf(fname, PATH_MAX, "%s/%s", dname, d->d_name);
+		if (rv < path_len) {
+			xlog(L_WARNING, "conf_init_dir: file name: %s/%s too short", 
+				d->d_name, dname);
+			continue;
+		}
+
+		if (conf_load_files(trans, fname))
+			continue;
+		nfiles++;
+	}
+
+	if (nfiles) {
+		/* Apply the configuration values */
+		conf_end(trans, 1);
+	}
+	for (i = 0; i < n; i++)
+		free(namelist[i]);
+	free(namelist);
+	free(dname);
+	
+	return;
+}
+
 int
 conf_init_file(const char *conf_file)
 {
 	unsigned int i;
+	int ret;
 
 	for (i = 0; i < sizeof conf_bindings / sizeof conf_bindings[0]; i++)
 		LIST_INIT (&conf_bindings[i]);
 
 	TAILQ_INIT (&conf_trans_queue);
 
-	if (conf_file == NULL) conf_file=NFS_CONFFILE;
-	return conf_load_file(conf_file);
+	if (conf_file == NULL) 
+		conf_file=NFS_CONFFILE;
+
+	/*
+	 * First parse the give config file 
+	 * then parse the config.conf.d directory 
+	 * (if it exists)
+	 *
+	 */
+	ret = conf_load_file(conf_file);
+
+	/*
+	 * When the same variable is set in both files
+	 * the conf.d file will override the config file.
+	 * This allows automated admin systems to
+	 * have the final say.
+	 */
+	conf_init_dir(conf_file);
+
+	return ret;
 }
 
 /*
diff --git a/systemd/nfs-server.service b/systemd/nfs-server.service
index 06c1adb..b432f91 100644
--- a/systemd/nfs-server.service
+++ b/systemd/nfs-server.service
@@ -21,13 +21,13 @@ After=rpc-gssd.service gssproxy.service rpc-svcgssd.service
 [Service]
 Type=oneshot
 RemainAfterExit=yes
-ExecStartPre=/usr/sbin/exportfs -r
+ExecStartPre=-/usr/sbin/exportfs -r
 ExecStart=/usr/sbin/rpc.nfsd
 ExecStop=/usr/sbin/rpc.nfsd 0
 ExecStopPost=/usr/sbin/exportfs -au
 ExecStopPost=/usr/sbin/exportfs -f
 
-ExecReload=/usr/sbin/exportfs -r
+ExecReload=-/usr/sbin/exportfs -r
 
 [Install]
 WantedBy=multi-user.target
diff --git a/systemd/nfs-v4client.target b/systemd/nfs-v4client.target
new file mode 100644
index 0000000..3d1064e
--- /dev/null
+++ b/systemd/nfs-v4client.target
@@ -0,0 +1,12 @@
+[Unit]
+Description=NFS client services
+Before=remote-fs-pre.target
+Wants=remote-fs-pre.target
+
+# GSS services dependencies and ordering
+Wants=auth-rpcgss-module.service
+After=rpc-gssd.service rpc-svcgssd.service gssproxy.service
+
+[Install]
+WantedBy=multi-user.target
+WantedBy=remote-fs.target
diff --git a/systemd/nfs.conf.man b/systemd/nfs.conf.man
index 3f1c726..16e0ec4 100644
--- a/systemd/nfs.conf.man
+++ b/systemd/nfs.conf.man
@@ -265,7 +265,15 @@ Only
 is recognized.
 
 .SH FILES
+.TP 10n
 .I /etc/nfs.conf
+Default NFS client configuration file
+.TP 10n
+.I /etc/nfs.conf.d
+When this directory exists and files ending 
+with ".conf" exist, those files will be
+used to set configuration variables. These
+files will override variables set in /etc/nfs.conf
 .SH SEE ALSO
 .BR nfsdcltrack (8),
 .BR rpc.nfsd (8),
diff --git a/tools/mountstats/mountstats.py b/tools/mountstats/mountstats.py
index 25e92a1..23876fc 100755
--- a/tools/mountstats/mountstats.py
+++ b/tools/mountstats/mountstats.py
@@ -378,7 +378,10 @@ class DeviceData:
                 print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
         elif vers == '4':
             for op in Nfsv4ops:
-                print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
+                try:
+                    print('\t%12s: %s' % (op, " ".join(str(x) for x in self.__rpc_data[op])))
+                except KeyError:
+                    continue
         else:
             print('\tnot implemented for version %d' % vers)
         print()
diff --git a/utils/exportfs/exportfs.c b/utils/exportfs/exportfs.c
index 9d5e575..9fcae0b 100644
--- a/utils/exportfs/exportfs.c
+++ b/utils/exportfs/exportfs.c
@@ -176,10 +176,10 @@ main(int argc, char **argv)
 		xlog(L_ERROR, "-r and -u are incompatible");
 		return 1;
 	}
-printf("point 1\n");
+
 	if (!setup_state_path_names(progname, ETAB, ETABTMP, ETABLCK, &etab))
 		return 1;
-printf("point 2\n");
+
 	if (optind == argc && ! f_all) {
 		if (force_flush) {
 			cache_flush(1);
@@ -193,7 +193,6 @@ printf("point 2\n");
 			return 0;
 		}
 	}
-printf("point 3\n");
 
 	/*
 	 * Serialize things as best we can
diff --git a/utils/exportfs/exports.man b/utils/exportfs/exports.man
index 1d17184..54b3f87 100644
--- a/utils/exportfs/exports.man
+++ b/utils/exportfs/exports.man
@@ -169,13 +169,6 @@ default.  In all releases after 1.0.0,
 is the default, and
 .I async
 must be explicitly requested if needed.
-To help make system administrators aware of this change,
-.B exportfs
-will issue a warning if neither
-.I sync
-nor
-.I async
-is specified.
 .TP
 .IR no_wdelay
 This option has no effect if
diff --git a/utils/mount/configfile.c b/utils/mount/configfile.c
index 93fe500..e865998 100644
--- a/utils/mount/configfile.c
+++ b/utils/mount/configfile.c
@@ -1,5 +1,5 @@
 /*
- * configfile.c -- mount configuration file manipulation 
+ * configfile.c -- mount configuration file manipulation
  * Copyright (C) 2008 Red Hat, Inc <nfs@redhat.com>
  *
  * - Routines use to create mount options from the mount
@@ -34,10 +34,7 @@
 #include "parse_opt.h"
 #include "network.h"
 #include "conffile.h"
-
-char *mountopts_convert(char *value);
-char *is_alias(char *opt);
-char *conf_get_mntopts(char *spec, char *mount_point, char *mount_opts);
+#include "mount_config.h"
 
 #define KBYTES(x)     ((x) * (1024))
 #define MEGABYTES(x)  ((x) * (1048576))
@@ -70,17 +67,31 @@ struct mnt_alias {
 	{"background", "bg", MNT_NOARG},
 	{"foreground", "fg", MNT_NOARG},
 	{"sloppy", "sloppy", MNT_NOARG},
-	{"nfsvers", "vers", MNT_UNSET},
 };
 int mnt_alias_sz = (sizeof(mnt_alias_tab)/sizeof(mnt_alias_tab[0]));
 
+static const char *version_keys[] = {
+	"v2", "v3", "v4", "vers", "nfsvers", "minorversion", NULL
+};
+
 static int strict;
 
+static int is_version(const char *field)
+{
+	int i;
+	for (i = 0; version_keys[i] ; i++)
+		if (strcmp(version_keys[i], field) == 0)
+			return 1;
+	if (strncmp(field, "v4.", 3) == 0)
+		return 1;
+	return 0;
+}
+
 /*
- * See if the option is an alias, if so return the 
+ * See if the option is an alias, if so return the
  * real mount option along with the argument type.
  */
-inline static 
+inline static
 char *mountopts_alias(char *opt, int *argtype)
 {
 	int i;
@@ -99,10 +110,10 @@ char *mountopts_alias(char *opt, int *argtype)
 }
 /*
  * Convert numeric strings that end with 'k', 'm' or 'g'
- * into numeric strings with the real value. 
+ * into numeric strings with the real value.
  * Meaning '8k' becomes '8094'.
  */
-char *mountopts_convert(char *value)
+static char *mountopts_convert(char *value)
 {
 	unsigned long long factor, num;
 	static char buf[64];
@@ -136,110 +147,16 @@ char *mountopts_convert(char *value)
 	return buf;
 }
 
-struct entry {
-	SLIST_ENTRY(entry) entries;
-	char *opt;
-};
-static SLIST_HEAD(shead, entry) head = SLIST_HEAD_INITIALIZER(head);
-static int list_size;
-
-/*
- * Add option to the link list
- */
-inline static void 
-add_entry(char *opt)
-{
-	struct entry *entry;
-
-	entry = calloc(1, sizeof(struct entry));
-	if (entry == NULL) {
-		xlog_warn("Unable calloc memory for mount configs"); 
-		return;
-	}
-	entry->opt = strdup(opt);
-	if (entry->opt == NULL) {
-		xlog_warn("Unable calloc memory for mount opts"); 
-		free(entry);
-		return;
-	}
-	SLIST_INSERT_HEAD(&head, entry, entries);
-}
-/*
- * Check the alias list to see if the given 
- * opt is a alias
- */
-char *is_alias(char *opt)
-{
-	int i;
-
-	for (i=0; i < mnt_alias_sz; i++) {
-		if (strcasecmp(opt, mnt_alias_tab[i].alias) == 0)
-			return mnt_alias_tab[i].opt; 
-	}
-	return NULL;
-}
-/*
- * See if the given entry exists if the link list,
- * if so return that entry
- */
-inline static 
-char *lookup_entry(char *opt)
-{
-	struct entry *entry;
-	char *alias = is_alias(opt);
-	char *ptr;
-
-	SLIST_FOREACH(entry, &head, entries) {
-		/*
-		 * Only check the left side or options that use '='
-		 */
-		if ((ptr = strchr(entry->opt, '=')) != 0) {
-			int len = (int) (ptr - entry->opt);
-
-			if (strncasecmp(entry->opt, opt, len) == 0)
-				return opt;
-		}
-		if (strcasecmp(entry->opt, opt) == 0)
-			return opt;
-		if (alias && strcasecmp(entry->opt, alias) == 0)
-			return opt;
-		if (alias && strcasecmp(alias, "fg") == 0) {
-			if (strcasecmp(entry->opt, "bg") == 0)
-				return opt;
-		}
-		if (alias && strcasecmp(alias, "bg") == 0) {
-			if (strcasecmp(entry->opt, "fg") == 0)
-				return opt;
-		}
-	}
-	return NULL;
-}
-/*
- * Free all entries on the link list
- */
-inline static 
-void free_all(void)
-{
-	struct entry *entry;
-
-	while (!SLIST_EMPTY(&head)) {
-		entry = SLIST_FIRST(&head);
-		SLIST_REMOVE_HEAD(&head, entries);
-		free(entry->opt);
-		free(entry);
-	}
-}
-
 struct nfs_version config_default_vers;
 unsigned long config_default_proto;
 extern sa_family_t config_default_family;
 
 /*
  * Check to see if a default value is being set.
- * If so, set the appropriate global value which will 
+ * If so, set the appropriate global value which will
  * be used as the initial value in the server negation.
  */
-static int 
+static int
 default_value(char *mopt)
 {
 	struct mount_options *options = NULL;
@@ -253,11 +170,11 @@ default_value(char *mopt)
 	if (strncasecmp(field, "proto", strlen("proto")) == 0) {
 		if ((options = po_split(field)) != NULL) {
 			if (!nfs_nfs_protocol(options, &config_default_proto)) {
-				xlog_warn("Unable to set default protocol : %s", 
+				xlog_warn("Unable to set default protocol : %s",
 					strerror(errno));
 			}
 			if (!nfs_nfs_proto_family(options, &config_default_family)) {
-				xlog_warn("Unable to set default family : %s", 
+				xlog_warn("Unable to set default family : %s",
 					strerror(errno));
 			}
 		} else {
@@ -266,14 +183,13 @@ default_value(char *mopt)
 	} else if (strncasecmp(field, "vers", strlen("vers")) == 0) {
 		if ((options = po_split(field)) != NULL) {
 			if (!nfs_nfs_version("nfs", options, &config_default_vers)) {
-				xlog_warn("Unable to set default version: %s", 
+				xlog_warn("Unable to set default version: %s",
 					strerror(errno));
-				
 			}
 		} else {
 			xlog_warn("Unable to alloc memory for default version");
 		}
-	} else 
+	} else
 		xlog_warn("Invalid default setting: '%s'", mopt);
 
 	if (options)
@@ -282,32 +198,60 @@ default_value(char *mopt)
 	return 1;
 }
 /*
- * Parse the given section of the configuration 
+ * Parse the given section of the configuration
  * file to if there are any mount options set.
  * If so, added them to link list.
  */
-static void 
-conf_parse_mntopts(char *section, char *arg, char *opts)
+static void
+conf_parse_mntopts(char *section, char *arg, struct mount_options *options)
 {
 	struct conf_list *list;
 	struct conf_list_node *node;
 	char buf[BUFSIZ], *value, *field;
 	char *nvalue, *ptr;
 	int argtype;
+	int have_version = 0;
+
+	if (po_rightmost(options, version_keys) >= 0 ||
+	    po_contains_prefix(options, "v4.", NULL, 0) == PO_FOUND)
+		have_version = 1;
 
 	list = conf_get_tag_list(section, arg);
 	TAILQ_FOREACH(node, &list->fields, link) {
-		/* check first if this is an alias for another option */
-		field = mountopts_alias(node->field, &argtype);
 		/*
-		 * Do not overwrite options if already exists 
+		 * Do not overwrite options if already exists
 		 */
-		snprintf(buf, BUFSIZ, "%s=", field);
-		if (opts && strcasestr(opts, buf) != NULL)
+		field = mountopts_alias(node->field, &argtype);
+		if (po_contains(options, field) == PO_FOUND)
+			continue;
+		/* Some options can be inverted by a "no" prefix.
+		 * Check for these.
+		 * "no" prefixes are unlikely in the config file as
+		 * "option=false" is preferred, but still possible.
+		 */
+		if (strncmp(field, "no", 2) == 0 &&
+		    po_contains(options, field+2) == PO_FOUND)
 			continue;
+		if (strlen(field) < BUFSIZ-3) {
+			strcat(strcpy(buf, "no"), field);
+			if (po_contains(options, buf) == PO_FOUND)
+				continue;
+		}
 
-		if (lookup_entry(field) != NULL)
+		/* If fg or bg already present, ignore bg or fg */
+		if (strcmp(field, "fg") == 0 &&
+		    po_contains(options, "bg") == PO_FOUND)
 			continue;
+		if (strcmp(field, "bg") == 0 &&
+		    po_contains(options, "fg") == PO_FOUND)
+			continue;
+
+		if (is_version(field)) {
+			if (have_version)
+				continue;
+			have_version = 1;
+		}
+
 		buf[0] = '\0';
 		value = conf_get_section(section, arg, node->field);
 		if (value == NULL)
@@ -333,99 +277,68 @@ conf_parse_mntopts(char *section, char *arg, char *opts)
 		}
 		if (buf[0] == '\0')
 			continue;
-		/* 
-		 * Keep a running tally of the list size adding 
-		 * one for the ',' that will be appened later
-		 */
-		list_size += strlen(buf) + 1;
-		add_entry(buf);
+		if (default_value(buf))
+			continue;
+
+		po_append(options, buf);
 	}
 	conf_free_list(list);
 }
 
 /*
- * Concatenate options from the configuration file with the 
+ * Concatenate options from the configuration file with the
  * given options by building a link list of options from the
- * different sections in the conf file. Options that exists 
- * in the either the given options or link list are not 
+ * different sections in the conf file. Options that exists
+ * in the either the given options or link list are not
  * overwritten so it matter which when each section is
- * parsed. 
+ * parsed.
  */
-char *conf_get_mntopts(char *spec, char *mount_point, 
-	char *mount_opts)
+char *conf_get_mntopts(char *spec, char *mount_point,
+			      char *mount_opts)
 {
-	struct entry *entry;
-	char *ptr, *server, *config_opts;
-	int optlen = 0;
+	struct mount_options *options;
+	char *ptr, *server;
 
 	strict = 0;
-	SLIST_INIT(&head);
-	list_size = 0;
+	options = po_split(mount_opts);
+	if (!options) {
+		xlog_warn("conf_get_mountops: Unable calloc memory for options");
+		return mount_opts;
+	}
 	/*
-	 * First see if there are any mount options relative 
+	 * First see if there are any mount options relative
 	 * to the mount point.
 	 */
-	conf_parse_mntopts(NFSMOUNT_MOUNTPOINT, mount_point, mount_opts);
+	conf_parse_mntopts(NFSMOUNT_MOUNTPOINT, mount_point, options);
 
-	/* 
+	/*
 	 * Next, see if there are any mount options relative
 	 * to the server
 	 */
 	server = strdup(spec);
 	if (server == NULL) {
-		xlog_warn("conf_get_mountops: Unable calloc memory for server"); 
-		free_all();
+		xlog_warn("conf_get_mountops: Unable calloc memory for server");
+		po_destroy(options);
 		return mount_opts;
 	}
 	if ((ptr = strchr(server, ':')) != NULL)
 		*ptr='\0';
-	conf_parse_mntopts(NFSMOUNT_SERVER, server, mount_opts);
+	conf_parse_mntopts(NFSMOUNT_SERVER, server, options);
 	free(server);
 
 	/*
-	 * Finally process all the global mount options. 
-	 */
-	conf_parse_mntopts(NFSMOUNT_GLOBAL_OPTS, NULL, mount_opts);
-
-	/*
-	 * If no mount options were found in the configuration file
-	 * just return what was passed in .
+	 * Finally process all the global mount options.
 	 */
-	if (SLIST_EMPTY(&head))
-		return mount_opts;
+	conf_parse_mntopts(NFSMOUNT_GLOBAL_OPTS, NULL, options);
 
 	/*
-	 * Found options in the configuration file. So
-	 * concatenate the configuration options with the 
-	 * options that were passed in
+	 * Strip out defaults, which have already been handled,
+	 * then join the rest and return.
 	 */
-	if (mount_opts)
-		optlen = strlen(mount_opts);
-
-	/* list_size + optlen + ',' + '\0' */
-	config_opts = calloc(1, (list_size+optlen+2));
-	if (config_opts == NULL) {
-		xlog_warn("conf_get_mountops: Unable calloc memory for config_opts"); 
-		free_all();
-		return mount_opts;
-	}
-
-	if (mount_opts) {
-		strcpy(config_opts, mount_opts);
-		strcat(config_opts, ",");
-	}
-	SLIST_FOREACH(entry, &head, entries) {
-		if (default_value(entry->opt))
-			continue;
-		strcat(config_opts, entry->opt);
-		strcat(config_opts, ",");
-	}
-	if ((ptr = strrchr(config_opts, ',')) != NULL)
-		*ptr = '\0';
+	po_remove_all(options, "default");
 
-	free_all();
-	if (mount_opts)
-		free(mount_opts);
+	po_join(options, &mount_opts);
+	po_destroy(options);
 
-	return config_opts;
+	return mount_opts;
 }
diff --git a/utils/mount/network.c b/utils/mount/network.c
index d9c0b51..e803dbb 100644
--- a/utils/mount/network.c
+++ b/utils/mount/network.c
@@ -1269,27 +1269,31 @@ int
 nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *version)
 {
 	char *version_key, *version_val = NULL, *cptr;
-	int i, found = 0;
+	int i, found = -1;
 
 	version->v_mode = V_DEFAULT;
 
 	for (i = 0; nfs_version_opttbl[i]; i++) {
 		if (po_contains_prefix(options, nfs_version_opttbl[i],
-				       &version_key) == PO_FOUND) {
-			found++;
-			break;
+				       &version_key, 0) == PO_FOUND) {
+			if (found >= 0)
+				goto ret_error_multiple;
+			if (po_contains_prefix(options, nfs_version_opttbl[i],
+					       NULL, 1) == PO_FOUND)
+				goto ret_error_multiple;
+			found = i;
 		}
 	}
 
-	if (!found && strcmp(type, "nfs4") == 0)
+	if (found < 0 && strcmp(type, "nfs4") == 0)
 		version_val = type + 3;
-	else if (!found)
+	else if (found < 0)
 		return 1;
-	else if (i <= 2 ) {
+	else if (found <= 2 ) {
 		/* v2, v3, v4 */
 		version_val = version_key + 1;
 		version->v_mode = V_SPECIFIC;
-	} else if (i > 2 ) {
+	} else if (found > 2 ) {
 		/* vers=, nfsvers= */
 		version_val = po_get(options, version_key);
 	}
@@ -1303,7 +1307,7 @@ nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *v
 	if (version->major == 4 && *cptr != '.' &&
 	    (version_val = po_get(options, "minorversion")) != NULL) {
 		version->minor = strtol(version_val, &cptr, 10);
-		i = -1;
+		found = -1;
 		if (*cptr)
 			goto ret_error;
 		version->v_mode = V_SPECIFIC;
@@ -1319,7 +1323,7 @@ nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *v
 		if (version_val != NULL) {
 			version->minor = strtol(version_val, &cptr, 10);
 			version->v_mode = V_SPECIFIC;
-		} else 
+		} else
 			version->v_mode = V_GENERAL;
 	}
 	if (*cptr != '\0')
@@ -1327,17 +1331,21 @@ nfs_nfs_version(char *type, struct mount_options *options, struct nfs_version *v
 
 	return 1;
 
+ret_error_multiple:
+	nfs_error(_("%s: multiple version options not permitted"),
+		  progname);
+	found = 10; /* avoid other errors */
 ret_error:
-	if (i < 0) {
+	if (found < 0) {
 		nfs_error(_("%s: parsing error on 'minorversion=' option"),
 			progname);
-	} else if (i <= 2 ) {
+	} else if (found <= 2 ) {
 		nfs_error(_("%s: parsing error on 'v' option"),
 			progname);
-	} else if (i == 3 ) {
+	} else if (found == 3 ) {
 		nfs_error(_("%s: parsing error on 'vers=' option"),
 			progname);
-	} else if (i == 4) {
+	} else if (found == 4) {
 		nfs_error(_("%s: parsing error on 'nfsvers=' option"),
 			progname);
 	}
diff --git a/utils/mount/nfsmount.conf.man b/utils/mount/nfsmount.conf.man
index 3aa3456..73c3e11 100644
--- a/utils/mount/nfsmount.conf.man
+++ b/utils/mount/nfsmount.conf.man
@@ -1,53 +1,84 @@
-.\"@(#)nfsmount.conf.5"
-.TH NFSMOUNT.CONF 5 "9 October 2012"
+."@(#)nfsmount.conf.5"
+.TH NFSMOUNT.CONF 5 "16 December 2020"
 .SH NAME
 nfsmount.conf - Configuration file for NFS mounts
 .SH SYNOPSIS
 Configuration file for NFS mounts that allows options
 to be set globally, per server or per mount point.
 .SH DESCRIPTION
-The configuration file is made up of multiple sections 
-followed by variables associated with that section.
-A section is defined by a string enclosed by 
+The configuration file is made up of multiple section headers
+followed by variable assignments associated with that section.
+A section header is defined by a string enclosed by
 .BR [
-and 
+and
 .BR ]
-branches.
-Variables are assignment statements that assign values 
-to particular variables using the  
-.BR = 
-operator, as in 
+brackets.
+Variable assignments are assignment statements that assign values
+to particular variables using the
+.BR =
+operator, as in
 .BR Proto=Tcp .
-The variables that can be assigned are exactly the set of NFS specific
+The variables that can be assigned are the set of NFS specific
 mount options listed in
-.BR nfs (5).
+.BR nfs (5)
+together with the filesystem-independant mount options listed in
+.BR mount (8)
+and three additions:
+.B Sloppy=True
+has the same effect as the
+.B -s
+option to
+.IR mount ,
+and
+.B Foreground=True
+and
+.B Background=True
+have the same effect as
+.B bg
+and
+.BR fg .
+.PP
+Options in the config file may be given in upper, lower, or mixed case
+and will be shifted to lower case before being passed to the filesystem.
+.PP
+Boolean mount options which do not need an equals sign must be given as
+.RI \[dq] option =True".
+Instead of preceeding such an option with
+.RB \[dq] no \[dq]
+its negation must be given as
+.RI \[dq] option =False".
 .PP
 Sections are broken up into three basic categories:
 Global options, Server options and Mount Point options.
 .HP
 .B [ NFSMount_Global_Options ]
 - This statically named section
-defines all of the global mount options that can be 
+defines all of the global mount options that can be
 applied to every NFS mount.
 .HP
-.B [ Server \(lqServer_Name\(rq ] 
-- This section defines all the mount options that should 
-be used on mounts to a particular NFS server. The 
-.I \(lqServer_Name\(rq
-strings needs to be surrounded by '\(lq' and 
-be an exact match of the server name used in the 
+.B [ Server \[dq]Server_Name\[dq] ]
+- This section defines all the mount options that should
+be used on mounts to a particular NFS server. The
+.I \[dq]Server_Name\[dq]
+strings needs to be surrounded by '\[dq]' and be an exact match
+(ignoring case) of the server name used in the
 .B mount
-command. 
+command.
 .HP
-.B [ MountPoint \(lqMount_Point\(rq ]
-- This section defines all the mount options that 
+.B [ MountPoint \[dq]Mount_Point\[dq] ]
+- This section defines all the mount options that
 should be used on a particular mount point.
-The 
-.I \(lqMount_Point\(rq
-string needs to be surrounded by '\(lq' and be an 
-exact match of the mount point used in the 
-.BR mount 
-command.
+The
+.I \[dq]Mount_Point\[dq]
+string needs to be surrounded by '\[dq]' and be an
+exact match of the mount point used in the
+.BR mount
+command.  Though path names are usually case-sensitive, the Mount_Point
+name is matched insensitive to case.
+.PP
+The sections are processed in the reverse of the order listed above, and
+any options already seen, either in a previous section or on the
+command line, will be ignored when seen again.
 .SH EXAMPLES
 .PP
 These are some example lines of how sections and variables
@@ -57,37 +88,43 @@ are defined in the configuration file.
 .br
     Proto=Tcp
 .RS
-.HP
+.PP
 The TCP/IPv4 protocol will be used on every NFS mount.
-.HP
 .RE
-[ Server \(lqnfsserver.foo.com\(rq ]
+.PP
+[ Server \[dq]nfsserver.foo.com\[dq] ]
 .br
     rsize=32k
 .br
     wsize=32k
 .br
     proto=udp6
-.HP
 .RS
+.PP
 A 32k (32768 bytes) block size will be used as the read and write
 size on all mounts to the 'nfsserver.foo.com' server.  UDP/IPv6
 is the protocol to be used.
-.HP
 .RE
-.BR 
-[ MountPoint \(lq/export/home\(rq ]
+.PP
+[ MountPoint \[dq]/export/home\[dq] ]
 .br
     Background=True
 .RS
-.HP
+.PP
 All mounts to the '/export/home' export will be performed in
 the background (i.e. done asynchronously).
-.HP
+.RE
 .SH FILES
 .TP 10n
 .I /etc/nfsmount.conf
 Default NFS mount configuration file
+.TP 10n
+.I /etc/nfsmount.conf.d
+When this directory exists and files ending
+with ".conf" exist, those files will be
+used to set configuration variables. These
+files will override variables set
+in /etc/nfsmount.conf
 .PD
 .SH SEE ALSO
 .BR nfs (5),
diff --git a/utils/mount/parse_opt.c b/utils/mount/parse_opt.c
index 7ba61c4..b6065ca 100644
--- a/utils/mount/parse_opt.c
+++ b/utils/mount/parse_opt.c
@@ -414,19 +414,25 @@ po_found_t po_contains(struct mount_options *options, char *keyword)
  * @options: pointer to mount options
  * @prefix: pointer to prefix to match against a keyword
  * @keyword: pointer to a C string containing the option keyword if found
+ * @n: number of instances to skip, so '0' returns the first.
  *
  * On success, *keyword contains the pointer of the matching option's keyword.
  */
 po_found_t po_contains_prefix(struct mount_options *options,
-								const char *prefix, char **keyword)
+			      const char *prefix, char **keyword, int n)
 {
 	struct mount_option *option;
 
 	if (options && prefix) {
 		for (option = options->head; option; option = option->next)
 			if (strncmp(option->keyword, prefix, strlen(prefix)) == 0) {
-				*keyword = option->keyword;
-				return PO_FOUND;
+				if (n > 0) {
+					n -= 1;
+				} else {
+					if (keyword)
+						*keyword = option->keyword;
+					return PO_FOUND;
+				}
 			}
 	}
 
diff --git a/utils/mount/parse_opt.h b/utils/mount/parse_opt.h
index 0745e0f..0a15376 100644
--- a/utils/mount/parse_opt.h
+++ b/utils/mount/parse_opt.h
@@ -46,7 +46,8 @@ po_return_t		po_join(struct mount_options *, char **);
 po_return_t		po_append(struct mount_options *, char *);
 po_found_t		po_contains(struct mount_options *, char *);
 po_found_t		po_contains_prefix(struct mount_options *options,
-						const char *prefix, char **keyword);
+					   const char *prefix, char **keyword,
+					   int n);
 char *			po_get(struct mount_options *, char *);
 po_found_t		po_get_numeric(struct mount_options *,
 					char *, long *);
diff --git a/utils/mountd/v4root.c b/utils/mountd/v4root.c
index dd9828e..6f640aa 100644
--- a/utils/mountd/v4root.c
+++ b/utils/mountd/v4root.c
@@ -34,9 +34,9 @@ static nfs_export pseudo_root = {
 	.m_export = {
 		.e_hostname = "*",
 		.e_path = "/",
-		.e_flags = NFSEXP_READONLY | NFSEXP_ROOTSQUASH
+		.e_flags = NFSEXP_READONLY
 				| NFSEXP_NOSUBTREECHECK | NFSEXP_FSID
-				| NFSEXP_V4ROOT,
+				| NFSEXP_V4ROOT | NFSEXP_INSECURE_PORT,
 		.e_anonuid = 65534,
 		.e_anongid = 65534,
 		.e_squids = NULL,
@@ -55,15 +55,11 @@ static nfs_export pseudo_root = {
 };
 
 static void
-set_pseudofs_security(struct exportent *pseudo, int flags)
+set_pseudofs_security(struct exportent *pseudo)
 {
 	struct flav_info *flav;
 	int i;
 
-	if (flags & NFSEXP_INSECURE_PORT)
-		pseudo->e_flags |= NFSEXP_INSECURE_PORT;
-	if ((flags & NFSEXP_ROOTSQUASH) == 0)
-		pseudo->e_flags &= ~NFSEXP_ROOTSQUASH;
 	for (flav = flav_map; flav < flav_map + flav_map_size; flav++) {
 		struct sec_entry *new;
 
@@ -73,8 +69,7 @@ set_pseudofs_security(struct exportent *pseudo, int flags)
 		i = secinfo_addflavor(flav, pseudo);
 		new = &pseudo->e_secinfo[i];
 
-		if (flags & NFSEXP_INSECURE_PORT)
-			new->flags |= NFSEXP_INSECURE_PORT;
+		new->flags |= NFSEXP_INSECURE_PORT;
 	}
 }
 
@@ -93,7 +88,7 @@ v4root_create(char *path, nfs_export *export)
 	strncpy(eep.e_path, path, sizeof(eep.e_path)-1);
 	if (strcmp(path, "/") != 0)
 		eep.e_flags &= ~NFSEXP_FSID;
-	set_pseudofs_security(&eep, curexp->e_flags);
+	set_pseudofs_security(&eep);
 	exp = export_create(&eep, 0);
 	if (exp == NULL)
 		return NULL;
@@ -141,7 +136,7 @@ pseudofs_update(char *hostname, char *path, nfs_export *source)
 		return 0;
 	}
 	/* Update an existing V4ROOT export: */
-	set_pseudofs_security(&exp->m_export, source->m_export.e_flags);
+	set_pseudofs_security(&exp->m_export);
 	return 0;
 }
 
diff --git a/utils/nfsd/nfsd.c b/utils/nfsd/nfsd.c
index a412a02..c9f0385 100644
--- a/utils/nfsd/nfsd.c
+++ b/utils/nfsd/nfsd.c
@@ -162,7 +162,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "dH:hN:V:p:P:stTituUrG:L:", longopts, NULL)) != EOF) {
+	while ((c = getopt_long(argc, argv, "dH:hN:V:p:P:stTuUrG:L:", longopts, NULL)) != EOF) {
 		switch(c) {
 		case 'd':
 			xlog_config(D_ALL, 1);