Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 3 Jun 2019 22:58:51 +0000 (UTC)
From:      Rick Macklem <rmacklem@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r348590 - head/usr.sbin/mountd
Message-ID:  <201906032258.x53Mwp56013050@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: rmacklem
Date: Mon Jun  3 22:58:51 2019
New Revision: 348590
URL: https://svnweb.freebsd.org/changeset/base/348590

Log:
  Modify mountd so that it incrementally updates the kernel exports upon a reload.
  
  Without this patch, mountd would delete/load all exports from the exports
  file(s) when it receives a SIGHUP. This works fine for small exports file(s),
  but can take several seconds to do when there are large numbers (10000+) of
  exported file systems. Most of this time is spent doing the system calls
  that delete/export each of these file systems. When the "-S" option
  has been specified (the default these days), the nfsd threads are suspended
  for several seconds while the reload is done.
  
  This patch changes mountd so that it only does system calls for file systems
  where the exports have been changed/added/deleted as compared to the exports
  done for the previous load/reload of the exports file(s).
  Basically, when SIGHUP is posted to mountd, it saves the exportlist structures
  from the previous load and creates a new set of structures from the current
  exports file(s). Then it compares the current with the previous and only does
  system calls for cases that have been changed/added/deleted.
  The nfsd threads do not need to be suspended until the comparison step is
  being done. This results in a suspension period of milliseconds for a server
  with 10000+ exported file systems.
  
  There is some code using a LOGDEBUG() macro that allow runtime debugging
  output via syslog(LOG_DEBUG,...) that can be enabled by creating a file
  called /var/log/mountd.debug. This code is expected to be replaced with
  code that uses dtrace by cy@ in the near future, once issues w.r.t. dtrace
  in stable/12 have been resolved.
  
  The patch should not change the usage of the exports file(s), but improves
  the performance of reloading large exports file(s) where there are only a
  small number of changes done to the file(s).
  
  Tested by:	pen@lysator.liu.se
  PR:		237860
  Reviewed by:	kib
  MFC after:	1 month
  Relnotes:	yes
  Differential Revision:	https://reviews.freebsd.org/D20487

Modified:
  head/usr.sbin/mountd/mountd.c
  head/usr.sbin/mountd/pathnames.h

Modified: head/usr.sbin/mountd/mountd.c
==============================================================================
--- head/usr.sbin/mountd/mountd.c	Mon Jun  3 21:40:42 2019	(r348589)
+++ head/usr.sbin/mountd/mountd.c	Mon Jun  3 22:58:51 2019	(r348590)
@@ -120,6 +120,8 @@ struct exportlist {
 	fsid_t		ex_fs;
 	char		*ex_fsdir;
 	char		*ex_indexfile;
+	struct xucred	ex_defanon;
+	int		ex_defexflags;
 	int		ex_numsecflavors;
 	int		ex_secflavors[MAXSECFLAVORS];
 	int		ex_defnumsecflavors;
@@ -129,6 +131,9 @@ struct exportlist {
 };
 /* ex_flag bits */
 #define	EX_LINKED	0x1
+#define	EX_DONE		0x2
+#define	EX_DEFSET	0x4
+#define	EX_PUBLICFH	0x8
 
 SLIST_HEAD(exportlisthead, exportlist);
 
@@ -147,6 +152,9 @@ struct grouplist {
 	int gr_type;
 	union grouptypes gr_ptr;
 	struct grouplist *gr_next;
+	struct xucred gr_anon;
+	int gr_exflags;
+	int gr_flag;
 	int gr_numsecflavors;
 	int gr_secflavors[MAXSECFLAVORS];
 };
@@ -157,6 +165,9 @@ struct grouplist {
 #define	GT_DEFAULT	0x3
 #define GT_IGNORE	0x5
 
+/* Group flags */
+#define	GR_FND		0x1
+
 struct hostlist {
 	int		 ht_flag;	/* Uses DP_xx bits */
 	struct grouplist *ht_grp;
@@ -176,7 +187,8 @@ struct fhreturn {
 /* Global defs */
 static char	*add_expdir(struct dirlist **, char *, int);
 static void	add_dlist(struct dirlist **, struct dirlist *,
-		    struct grouplist *, int, struct exportlist *);
+		    struct grouplist *, int, struct exportlist *,
+		    struct xucred *, int);
 static void	add_mlist(char *, char *);
 static int	check_dirpath(char *);
 static int	check_options(struct dirlist *);
@@ -189,8 +201,9 @@ static void	complete_service(struct netconfig *nconf, 
 static void	clearout_service(void);
 static void	del_mlist(char *hostp, char *dirp);
 static struct dirlist	*dirp_search(struct dirlist *, char *);
+static int	do_export_mount(struct exportlist *, struct statfs *);
 static int	do_mount(struct exportlist *, struct grouplist *, int,
-		    struct xucred *, char *, int, struct statfs *);
+		    struct xucred *, char *, int, struct statfs *, int, int *);
 static int	do_opt(char **, char **, struct exportlist *,
 		    struct grouplist *, int *, int *, struct xucred *);
 static struct exportlist	*ex_search(fsid_t *, struct exportlisthead *);
@@ -199,10 +212,16 @@ static void	free_dir(struct dirlist *);
 static void	free_exp(struct exportlist *);
 static void	free_grp(struct grouplist *);
 static void	free_host(struct hostlist *);
-static void	get_exportlist(void);
+static void	free_v4rootexp(void);
+static void	get_exportlist_one(int);
+static void	get_exportlist(int);
 static void	insert_exports(struct exportlist *, struct exportlisthead *);
 static void	free_exports(struct exportlisthead *);
-static void	read_exportfile(void);
+static void	read_exportfile(int);
+static int	compare_nmount_exportlist(struct iovec *, int, char *);
+static int	compare_export(struct exportlist *, struct exportlist *);
+static int	compare_cred(struct xucred *, struct xucred *);
+static int	compare_secflavor(int *, int *, int);
 static void	delete_export(struct iovec *, int, struct statfs *, char *);
 static int	get_host(char *, struct grouplist *, struct grouplist *);
 static struct hostlist *get_ht(void);
@@ -212,7 +231,7 @@ static int	get_net(char *, struct netmsk *, int);
 static void	getexp_err(struct exportlist *, struct grouplist *, const char *);
 static struct grouplist	*get_grp(void);
 static void	hang_dirp(struct dirlist *, struct grouplist *,
-				struct exportlist *, int);
+		    struct exportlist *, int, struct xucred *, int);
 static void	huphandler(int sig);
 static int	makemask(struct sockaddr_storage *ssp, int bitlen);
 static void	mntsrv(struct svc_req *, SVCXPRT *);
@@ -237,6 +256,7 @@ static void	terminate(int);
 
 #define	EXPHASH(f)	(fnv_32_buf((f), sizeof(fsid_t), 0) % exphashsize)
 static struct exportlisthead *exphead = NULL;
+static struct exportlisthead *oldexphead = NULL;
 static int exphashsize = 0;
 static SLIST_HEAD(, mountlist) mlhead = SLIST_HEAD_INITIALIZER(&mlhead);
 static char *exnames_default[2] = { _PATH_EXPORTS, NULL };
@@ -269,7 +289,9 @@ static int have_v6 = 1;
 
 static int v4root_phase = 0;
 static char v4root_dirpath[PATH_MAX + 1];
+static struct exportlist *v4root_ep = NULL;
 static int has_publicfh = 0;
+static int has_set_publicfh = 0;
 
 static struct pidfh *pfh = NULL;
 /* Bits for opt_flags above */
@@ -293,6 +315,15 @@ static int debug = 0;
 #endif
 
 /*
+ * The LOGDEBUG() syslog() calls are always compiled into the daemon.
+ * To enable them, create a file at _PATH_MOUNTDDEBUG. This file can be empty.
+ * To disable the logging, just delete the file at _PATH_MOUNTDDEBUG.
+ */
+static int logdebug = 0;
+#define	LOGDEBUG(format, ...)						\
+    (logdebug ? syslog(LOG_DEBUG, format, ## __VA_ARGS__) : 0)
+
+/*
  * Similar to strsep(), but it allows for quoted strings
  * and escaped characters.
  *
@@ -465,7 +496,7 @@ main(int argc, char **argv)
 	openlog("mountd", LOG_PID, LOG_DAEMON);
 	if (debug)
 		warnx("getting export list");
-	get_exportlist();
+	get_exportlist(0);
 	if (debug)
 		warnx("getting mount list");
 	get_mountlist();
@@ -636,7 +667,7 @@ main(int argc, char **argv)
 	/* Expand svc_run() here so that we can call get_exportlist(). */
 	for (;;) {
 		if (got_sighup) {
-			get_exportlist();
+			get_exportlist(1);
 			got_sighup = 0;
 		}
 		readfds = svc_fdset;
@@ -1426,10 +1457,10 @@ static FILE *exp_file;
  * Get the export list from one, currently open file
  */
 static void
-get_exportlist_one(void)
+get_exportlist_one(int passno)
 {
 	struct exportlist *ep;
-	struct grouplist *grp, *tgrp;
+	struct grouplist *grp, *tgrp, *savgrp;
 	struct dirlist *dirhead;
 	struct statfs fsb;
 	struct xucred anon;
@@ -1663,11 +1694,18 @@ get_exportlist_one(void)
 		 * Loop through hosts, pushing the exports into the kernel.
 		 * After loop, tgrp points to the start of the list and
 		 * grp points to the last entry in the list.
+		 * Do not do the do_mount() for passno == 1, since the
+		 * second pass will do it, as required.
 		 */
 		grp = tgrp;
 		do {
-			if (do_mount(ep, grp, exflags, &anon, dirp, dirplen,
-			    &fsb)) {
+			grp->gr_exflags = exflags;
+			grp->gr_anon = anon;
+			if (v4root_phase == 2 && passno == 0)
+				LOGDEBUG("do_mount v4root");
+			if (passno == 0 && do_mount(ep, grp, exflags, &anon,
+			    dirp, dirplen, &fsb, ep->ex_numsecflavors,
+			    ep->ex_secflavors)) {
 				getexp_err(ep, tgrp, NULL);
 				goto nextline;
 			}
@@ -1678,9 +1716,32 @@ get_exportlist_one(void)
 		 */
 		if (v4root_phase > 0 && v4root_phase <= 2) {
 			/*
-			 * Since these structures aren't used by mountd,
+			 * These structures are used for the "-I" reload,
+			 * so save them for that case.  Otherwise, just
 			 * free them up now.
 			 */
+			if (passno == 1 && ep != NULL) {
+				savgrp = tgrp;
+				while (tgrp != NULL) {
+					/*
+					 * Save the security flavors and exflags
+					 * for this host set in the groups.
+					 */
+					tgrp->gr_numsecflavors =
+					    ep->ex_numsecflavors;
+					if (ep->ex_numsecflavors > 0)
+						memcpy(tgrp->gr_secflavors,
+						    ep->ex_secflavors,
+						    sizeof(ep->ex_secflavors));
+					tgrp = tgrp->gr_next;
+				}
+				if (v4root_ep == NULL) {
+					v4root_ep = ep;
+					ep = NULL;	/* Don't free below. */
+				}
+				grp->gr_next = v4root_ep->ex_grphead;
+				v4root_ep->ex_grphead = savgrp;
+			}
 			if (ep != NULL)
 				free_exp(ep);
 			while (tgrp != NULL) {
@@ -1695,12 +1756,12 @@ get_exportlist_one(void)
 		 * Success. Update the data structures.
 		 */
 		if (has_host) {
-			hang_dirp(dirhead, tgrp, ep, opt_flags);
+			hang_dirp(dirhead, tgrp, ep, opt_flags, &anon, exflags);
 			grp->gr_next = ep->ex_grphead;
 			ep->ex_grphead = tgrp;
 		} else {
 			hang_dirp(dirhead, (struct grouplist *)NULL, ep,
-				opt_flags);
+				opt_flags, &anon, exflags);
 			free_grp(grp);
 		}
 		dirhead = (struct dirlist *)NULL;
@@ -1722,7 +1783,7 @@ nextline:
  * Get the export list from all specified files
  */
 static void
-get_exportlist(void)
+get_exportlist(int passno)
 {
 	struct export_args export;
 	struct iovec *iov;
@@ -1731,70 +1792,130 @@ get_exportlist(void)
 	int num, i;
 	int iovlen;
 	struct nfsex_args eargs;
+	FILE *debug_file;
 
-	if (suspend_nfsd != 0)
-		(void)nfssvc(NFSSVC_SUSPENDNFSD, NULL);
+	if ((debug_file = fopen(_PATH_MOUNTDDEBUG, "r")) != NULL) {
+		fclose(debug_file);
+		logdebug = 1;
+	} else
+		logdebug = 0;
+	LOGDEBUG("passno=%d", passno);
 	v4root_dirpath[0] = '\0';
+	free_v4rootexp();
+	if (passno == 1) {
+		/*
+		 * Save the current lists as old ones, so that the new lists
+		 * can be compared with the old ones in the 2nd pass.
+		 */
+		for (i = 0; i < exphashsize; i++) {
+			SLIST_FIRST(&oldexphead[i]) = SLIST_FIRST(&exphead[i]);
+			SLIST_INIT(&exphead[i]);
+		}
+
+		/* Note that the public fh has not yet been set. */
+		has_set_publicfh = 0;
+
+		/* Read the export file(s) and process them */
+		read_exportfile(passno);
+	} else {
+		/*
+		 * Just make the old lists empty.
+		 * exphashsize == 0 for the first call, before oldexphead
+		 * has been initialized-->loop won't be executed.
+		 */
+		for (i = 0; i < exphashsize; i++)
+			SLIST_INIT(&oldexphead[i]);
+	}
+
 	bzero(&export, sizeof(export));
 	export.ex_flags = MNT_DELEXPORT;
 	iov = NULL;
 	iovlen = 0;
 	bzero(errmsg, sizeof(errmsg));
 
+	if (suspend_nfsd != 0)
+		(void)nfssvc(NFSSVC_SUSPENDNFSD, NULL);
 	/*
-	 * First, get rid of the old list
+	 * Delete the old V4 root dir.
 	 */
-	if (exphead != NULL)
-		free_exports(exphead);
-
-	/*
-	 * and the old V4 root dir.
-	 */
 	bzero(&eargs, sizeof (eargs));
 	eargs.export.ex_flags = MNT_DELEXPORT;
 	if (nfssvc(NFSSVC_V4ROOTEXPORT, (caddr_t)&eargs) < 0 &&
 	    errno != ENOENT)
 		syslog(LOG_ERR, "Can't delete exports for V4:");
 
-	/*
-	 * and clear flag that notes if a public fh has been exported.
-	 */
-	has_publicfh = 0;
+	build_iovec(&iov, &iovlen, "fstype", NULL, 0);
+	build_iovec(&iov, &iovlen, "fspath", NULL, 0);
+	build_iovec(&iov, &iovlen, "from", NULL, 0);
+	build_iovec(&iov, &iovlen, "update", NULL, 0);
+	build_iovec(&iov, &iovlen, "export", &export,
+	    sizeof(export));
+	build_iovec(&iov, &iovlen, "errmsg", errmsg,
+	    sizeof(errmsg));
 
 	/*
-	 * And delete exports that are in the kernel for all local
-	 * filesystems.
-	 * XXX: Should know how to handle all local exportable filesystems.
+	 * For passno == 1, compare the old and new lists updating the kernel
+	 * exports for any cases that have changed.
+	 * This call is doing the second pass through the lists.
+	 * If it fails, fall back on the bulk reload.
 	 */
-	num = getmntinfo(&mntbufp, MNT_NOWAIT);
+	if (passno == 1 && compare_nmount_exportlist(iov, iovlen, errmsg) ==
+	    0) {
+		LOGDEBUG("compareok");
+		/* Free up the old lists. */
+		free_exports(oldexphead);
+	} else {
+		LOGDEBUG("doing passno=0");
+		/*
+		 * Clear flag that notes if a public fh has been exported.
+		 * It is set by do_mount() if MNT_EXPUBLIC is set for the entry.
+		 */
+		has_publicfh = 0;
 
-	/* Allocate hash tables, for first call. */
-	if (exphead == NULL) {
-		/* Target an average linked list length of 10. */
-		exphashsize = num / 10;
-		if (exphashsize < 1)
-			exphashsize = 1;
-		else if (exphashsize > 100000)
-			exphashsize = 100000;
-		exphead = malloc(exphashsize * sizeof(*exphead));
-		if (exphead == NULL)
-			errx(1, "Can't malloc hash table");
+		/* exphead == NULL if not yet allocated (first call). */
+		if (exphead != NULL) {
+			/*
+			 * First, get rid of the old lists.
+			 */
+			free_exports(exphead);
+			free_exports(oldexphead);
+		}
 
-		for (i = 0; i < exphashsize; i++)
-			SLIST_INIT(&exphead[i]);
-	}
-	if (num > 0) {
-		build_iovec(&iov, &iovlen, "fstype", NULL, 0);
-		build_iovec(&iov, &iovlen, "fspath", NULL, 0);
-		build_iovec(&iov, &iovlen, "from", NULL, 0);
-		build_iovec(&iov, &iovlen, "update", NULL, 0);
-		build_iovec(&iov, &iovlen, "export", &export, sizeof(export));
-		build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg));
-	}
+		/*
+		 * And delete exports that are in the kernel for all local
+		 * filesystems.
+		 * XXX: Should know how to handle all local exportable
+		 * filesystems.
+		 */
+		num = getmntinfo(&mntbufp, MNT_NOWAIT);
 
-	for (i = 0; i < num; i++)
-		delete_export(iov, iovlen, &mntbufp[i], errmsg);
+		/* Allocate hash tables, for first call. */
+		if (exphead == NULL) {
+			/* Target an average linked list length of 10. */
+			exphashsize = num / 10;
+			if (exphashsize < 1)
+				exphashsize = 1;
+			else if (exphashsize > 100000)
+				exphashsize = 100000;
+			exphead = malloc(exphashsize * sizeof(*exphead));
+			oldexphead = malloc(exphashsize * sizeof(*oldexphead));
+			if (exphead == NULL || oldexphead == NULL)
+				errx(1, "Can't malloc hash tables");
 
+			for (i = 0; i < exphashsize; i++) {
+				SLIST_INIT(&exphead[i]);
+				SLIST_INIT(&oldexphead[i]);
+			}
+		}
+	
+		for (i = 0; i < num; i++)
+			delete_export(iov, iovlen, &mntbufp[i], errmsg);
+
+
+		/* Read the export file(s) and process them */
+		read_exportfile(0);
+	}
+
 	if (iov != NULL) {
 		/* Free strings allocated by strdup() in getmntopts.c */
 		free(iov[0].iov_base); /* fstype */
@@ -1809,16 +1930,17 @@ get_exportlist(void)
 		iovlen = 0;
 	}
 
-	read_exportfile();
-
 	/*
 	 * If there was no public fh, clear any previous one set.
 	 */
-	if (has_publicfh == 0)
+	if (has_publicfh == 0) {
+		LOGDEBUG("clear public fh");
 		(void) nfssvc(NFSSVC_NOPUBLICFH, NULL);
+	}
 
 	/* Resume the nfsd. If they weren't suspended, this is harmless. */
 	(void)nfssvc(NFSSVC_RESUMENFSD, NULL);
+	LOGDEBUG("eo get_exportlist");
 }
 
 /*
@@ -1830,6 +1952,7 @@ insert_exports(struct exportlist *ep, struct exportlis
 	uint32_t i;
 
 	i = EXPHASH(&ep->ex_fs);
+	LOGDEBUG("fs=%s hash=%i", ep->ex_fsdir, i);
 	SLIST_INSERT_HEAD(&exhp[i], ep, entries);
 }
 
@@ -1855,7 +1978,7 @@ free_exports(struct exportlisthead *exhp)
  * Read the exports file(s) and call get_exportlist_one() for each line.
  */
 static void
-read_exportfile(void)
+read_exportfile(int passno)
 {
 	int done, i;
 
@@ -1871,7 +1994,7 @@ read_exportfile(void)
 			syslog(LOG_WARNING, "can't open %s", exnames[i]);
 			continue;
 		}
-		get_exportlist_one();
+		get_exportlist_one(passno);
 		fclose(exp_file);
 		done++;
 	}
@@ -1882,6 +2005,244 @@ read_exportfile(void)
 }
 
 /*
+ * Compare the export lists against the old ones and do nmount() operations
+ * for any cases that have changed.  This avoids doing nmount() for entries
+ * that have not changed.
+ * Return 0 upon success, 1 otherwise.
+ */
+static int
+compare_nmount_exportlist(struct iovec *iov, int iovlen, char *errmsg)
+{
+	struct exportlist *ep, *oep;
+	struct grouplist *grp;
+	struct statfs fs, ofs;
+	int i, ret;
+
+	/*
+	 * Loop through the current list and look for an entry in the old
+	 * list.
+	 * If found, check to see if it the same.
+	 *        If it is not the same, delete and re-export.
+	 *        Then mark it done on the old list.
+	 * else (not found)
+	 *        export it.
+	 * Any entries left in the old list after processing must have their
+	 * exports deleted.
+	 */
+	for (i = 0; i < exphashsize; i++)
+		SLIST_FOREACH(ep, &exphead[i], entries) {
+			LOGDEBUG("foreach ep=%s", ep->ex_fsdir);
+			oep = ex_search(&ep->ex_fs, oldexphead);
+			if (oep != NULL) {
+				/*
+				 * Check the mount paths are the same.
+				 * If not, return 1 so that the reload of the
+				 * exports will be done in bulk, the
+				 * passno == 0 way.
+				 */
+				LOGDEBUG("found old exp");
+				if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0)
+					return (1);
+				LOGDEBUG("same fsdir");
+				/*
+				 * Test to see if the entry is the same.
+				 * If not the same delete exports and
+				 * re-export.
+				 */
+				if (compare_export(ep, oep) != 0) {
+					/*
+					 * Clear has_publicfh if if was set
+					 * in the old exports, but only if it
+					 * has not been set during processing of
+					 * the exports for this pass, as
+					 * indicated by has_set_publicfh.
+					 */
+					if (has_set_publicfh == 0 &&
+					    (oep->ex_flag & EX_PUBLICFH) != 0)
+						has_publicfh = 0;
+
+					/* Delete and re-export. */
+					if (statfs(ep->ex_fsdir, &fs) < 0)
+						return (1);
+					delete_export(iov, iovlen, &fs, errmsg);
+					ret = do_export_mount(ep, &fs);
+					if (ret != 0)
+						return (ret);
+				}
+				oep->ex_flag |= EX_DONE;
+				LOGDEBUG("exdone");
+			} else {
+				LOGDEBUG("not found so export");
+				/* Not found, so do export. */
+				if (statfs(ep->ex_fsdir, &fs) < 0)
+					return (1);
+				ret = do_export_mount(ep, &fs);
+				if (ret != 0)
+					return (ret);
+			}
+		}
+
+	/* Delete exports not done. */
+	for (i = 0; i < exphashsize; i++)
+		SLIST_FOREACH(oep, &oldexphead[i], entries) {
+			if ((oep->ex_flag & EX_DONE) == 0) {
+				LOGDEBUG("not done delete=%s", oep->ex_fsdir);
+				if (statfs(oep->ex_fsdir, &ofs) >= 0 &&
+				    oep->ex_fs.val[0] == ofs.f_fsid.val[0] &&
+				    oep->ex_fs.val[1] == ofs.f_fsid.val[1]) {
+					LOGDEBUG("do delete");
+					/*
+					 * Clear has_publicfh if if was set
+					 * in the old exports, but only if it
+					 * has not been set during processing of
+					 * the exports for this pass, as
+					 * indicated by has_set_publicfh.
+					 */
+					if (has_set_publicfh == 0 &&
+					    (oep->ex_flag & EX_PUBLICFH) != 0)
+						has_publicfh = 0;
+
+					delete_export(iov, iovlen, &ofs,
+					    errmsg);
+				}
+			}
+		}
+
+	/* Do the V4 root exports, as required. */
+	grp = NULL;
+	if (v4root_ep != NULL)
+		grp = v4root_ep->ex_grphead;
+	v4root_phase = 2;
+	while (v4root_ep != NULL && grp != NULL) {
+		LOGDEBUG("v4root expath=%s", v4root_dirpath);
+		ret = do_mount(v4root_ep, grp, grp->gr_exflags, &grp->gr_anon,
+		    v4root_dirpath, strlen(v4root_dirpath), &fs,
+		    grp->gr_numsecflavors, grp->gr_secflavors);
+		if (ret != 0) {
+			v4root_phase = 0;
+			return (ret);
+		}
+		grp = grp->gr_next;
+	}
+	v4root_phase = 0;
+	free_v4rootexp();
+	return (0);
+}
+
+/*
+ * Compare old and current exportlist entries for the fsid and return 0
+ * if they are the same, 1 otherwise.
+ */
+static int
+compare_export(struct exportlist *ep, struct exportlist *oep)
+{
+	struct grouplist *grp, *ogrp;
+
+	if (strcmp(ep->ex_fsdir, oep->ex_fsdir) != 0)
+		return (1);
+	if ((ep->ex_flag & EX_DEFSET) != (oep->ex_flag & EX_DEFSET))
+		return (1);
+	if ((ep->ex_defdir != NULL && oep->ex_defdir == NULL) ||
+	    (ep->ex_defdir == NULL && oep->ex_defdir != NULL))
+		return (1);
+	if (ep->ex_defdir != NULL && (ep->ex_defdir->dp_flag & DP_DEFSET) !=
+	    (oep->ex_defdir->dp_flag & DP_DEFSET))
+		return (1);
+	if ((ep->ex_flag & EX_DEFSET) != 0 && (ep->ex_defnumsecflavors !=
+	    oep->ex_defnumsecflavors || ep->ex_defexflags !=
+	    oep->ex_defexflags || compare_cred(&ep->ex_defanon,
+	    &oep->ex_defanon) != 0 || compare_secflavor(ep->ex_defsecflavors,
+	    oep->ex_defsecflavors, ep->ex_defnumsecflavors) != 0))
+		return (1);
+
+	/* Now, check all the groups. */
+	for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next)
+		ogrp->gr_flag = 0;
+	for (grp = ep->ex_grphead; grp != NULL; grp = grp->gr_next) {
+		for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp =
+		    ogrp->gr_next)
+			if ((ogrp->gr_flag & GR_FND) == 0 &&
+			    grp->gr_numsecflavors == ogrp->gr_numsecflavors &&
+			    grp->gr_exflags == ogrp->gr_exflags &&
+			    compare_cred(&grp->gr_anon, &ogrp->gr_anon) == 0 &&
+			    compare_secflavor(grp->gr_secflavors,
+			    ogrp->gr_secflavors, grp->gr_numsecflavors) == 0)
+				break;
+		if (ogrp != NULL)
+			ogrp->gr_flag |= GR_FND;
+		else
+			return (1);
+	}
+	for (ogrp = oep->ex_grphead; ogrp != NULL; ogrp = ogrp->gr_next)
+		if ((ogrp->gr_flag & GR_FND) == 0)
+			return (1);
+	return (0);
+}
+
+/*
+ * This algorithm compares two arrays of "n" items. It returns 0 if they are
+ * the "same" and 1 otherwise.  Although suboptimal, it is always safe to
+ * return 1, which makes compare_nmount_export() reload the exports entry.
+ * "same" refers to having the same set of values in the two arrays.
+ * The arrays are in no particular order and duplicates (multiple entries
+ * in an array with the same value) is allowed.
+ * The algorithm is inefficient, but the common case of indentical arrays is
+ * handled first and "n" is normally fairly small.
+ * Since the two functions need the same algorithm but for arrays of
+ * different types (gid_t vs int), this is done as a macro.
+ */
+#define	COMPARE_ARRAYS(a1, a2, n)					\
+	do {								\
+		int fnd, fndarray[(n)], i, j;				\
+		/* Handle common case of identical arrays. */		\
+		for (i = 0; i < (n); i++)				\
+			if ((a1)[i] != (a2)[i])				\
+				break;					\
+		if (i == (n))						\
+			return (0);					\
+		for (i = 0; i < (n); i++)				\
+			fndarray[i] = 0;				\
+		for (i = 0; i < (n); i++) {				\
+			fnd = 0;					\
+			for (j = 0; j < (n); j++) {			\
+				if ((a1)[i] == (a2)[j]) {		\
+					fndarray[j] = 1;		\
+					fnd = 1;			\
+				}					\
+			}						\
+			if (fnd == 0)					\
+				return (1);				\
+		}							\
+		for (i = 0; i < (n); i++)				\
+			if (fndarray[i] == 0)				\
+				return (1);				\
+		return (0);						\
+	} while (0)
+
+/*
+ * Compare to struct xucred's.  Return 0 if the same and 1 otherwise.
+ */
+static int
+compare_cred(struct xucred *cr0, struct xucred *cr1)
+{
+
+	if (cr0->cr_uid != cr1->cr_uid || cr0->cr_ngroups != cr1->cr_ngroups)
+		return (1);
+
+	COMPARE_ARRAYS(cr0->cr_groups, cr1->cr_groups, cr0->cr_ngroups);
+}
+
+/*
+ * Compare two lists of security flavors.  Return 0 if the same and 1 otherwise.
+ */
+static int
+compare_secflavor(int *sec1, int *sec2, int nsec)
+{
+
+	COMPARE_ARRAYS(sec1, sec2, nsec);
+}
+
+/*
  * Delete an exports entry.
  */
 static void
@@ -2028,7 +2389,7 @@ add_expdir(struct dirlist **dpp, char *cp, int len)
  */
 static void
 hang_dirp(struct dirlist *dp, struct grouplist *grp, struct exportlist *ep,
-	int flags)
+	int flags, struct xucred *anoncrp, int exflags)
 {
 	struct hostlist *hp;
 	struct dirlist *dp2;
@@ -2039,12 +2400,15 @@ hang_dirp(struct dirlist *dp, struct grouplist *grp, s
 		else
 			ep->ex_defdir = dp;
 		if (grp == (struct grouplist *)NULL) {
+			ep->ex_flag |= EX_DEFSET;
 			ep->ex_defdir->dp_flag |= DP_DEFSET;
 			/* Save the default security flavors list. */
 			ep->ex_defnumsecflavors = ep->ex_numsecflavors;
 			if (ep->ex_numsecflavors > 0)
 				memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
 				    sizeof(ep->ex_secflavors));
+			ep->ex_defanon = *anoncrp;
+			ep->ex_defexflags = exflags;
 		} else while (grp) {
 			hp = get_ht();
 			hp->ht_grp = grp;
@@ -2064,7 +2428,8 @@ hang_dirp(struct dirlist *dp, struct grouplist *grp, s
 		 */
 		while (dp) {
 			dp2 = dp->dp_left;
-			add_dlist(&ep->ex_dirl, dp, grp, flags, ep);
+			add_dlist(&ep->ex_dirl, dp, grp, flags, ep, anoncrp,
+			    exflags);
 			dp = dp2;
 		}
 	}
@@ -2076,7 +2441,7 @@ hang_dirp(struct dirlist *dp, struct grouplist *grp, s
  */
 static void
 add_dlist(struct dirlist **dpp, struct dirlist *newdp, struct grouplist *grp,
-	int flags, struct exportlist *ep)
+	int flags, struct exportlist *ep, struct xucred *anoncrp, int exflags)
 {
 	struct dirlist *dp;
 	struct hostlist *hp;
@@ -2086,10 +2451,12 @@ add_dlist(struct dirlist **dpp, struct dirlist *newdp,
 	if (dp) {
 		cmp = strcmp(dp->dp_dirp, newdp->dp_dirp);
 		if (cmp > 0) {
-			add_dlist(&dp->dp_left, newdp, grp, flags, ep);
+			add_dlist(&dp->dp_left, newdp, grp, flags, ep, anoncrp,
+			    exflags);
 			return;
 		} else if (cmp < 0) {
-			add_dlist(&dp->dp_right, newdp, grp, flags, ep);
+			add_dlist(&dp->dp_right, newdp, grp, flags, ep, anoncrp,
+			    exflags);
 			return;
 		} else
 			free((caddr_t)newdp);
@@ -2116,12 +2483,15 @@ add_dlist(struct dirlist **dpp, struct dirlist *newdp,
 			grp = grp->gr_next;
 		} while (grp);
 	} else {
+		ep->ex_flag |= EX_DEFSET;
 		dp->dp_flag |= DP_DEFSET;
 		/* Save the default security flavors list. */
 		ep->ex_defnumsecflavors = ep->ex_numsecflavors;
 		if (ep->ex_numsecflavors > 0)
 			memcpy(ep->ex_defsecflavors, ep->ex_secflavors,
 			    sizeof(ep->ex_secflavors));
+		ep->ex_defanon = *anoncrp;
+		ep->ex_defexflags = exflags;
 	}
 }
 
@@ -2487,6 +2857,19 @@ free_exp(struct exportlist *ep)
 }
 
 /*
+ * Free up the v4root exports.
+ */
+static void
+free_v4rootexp(void)
+{
+
+	if (v4root_ep != NULL) {
+		free_exp(v4root_ep);
+		v4root_ep = NULL;
+	}
+}
+
+/*
  * Free hosts.
  */
 static void
@@ -2526,12 +2909,52 @@ out_of_mem(void)
 }
 
 /*
+ * Call do_mount() from the struct exportlist, for each case needed.
+ */
+static int
+do_export_mount(struct exportlist *ep, struct statfs *fsp)
+{
+	struct grouplist *grp, defgrp;
+	int ret;
+	size_t dirlen;
+
+	LOGDEBUG("do_export_mount=%s", ep->ex_fsdir);
+	dirlen = strlen(ep->ex_fsdir);
+	if ((ep->ex_flag & EX_DEFSET) != 0) {
+		defgrp.gr_type = GT_DEFAULT;
+		defgrp.gr_next = NULL;
+		/* We have an entry for all other hosts/nets. */
+		LOGDEBUG("ex_defexflags=0x%x", ep->ex_defexflags);
+		ret = do_mount(ep, &defgrp, ep->ex_defexflags, &ep->ex_defanon,
+		    ep->ex_fsdir, dirlen, fsp, ep->ex_defnumsecflavors,
+		    ep->ex_defsecflavors);
+		if (ret != 0)
+			return (ret);
+	}
+
+	/* Do a mount for each group. */
+	grp = ep->ex_grphead;
+	while (grp != NULL) {
+		LOGDEBUG("do mount gr_type=0x%x gr_exflags=0x%x",
+		    grp->gr_type, grp->gr_exflags);
+		ret = do_mount(ep, grp, grp->gr_exflags, &grp->gr_anon,
+		    ep->ex_fsdir, dirlen, fsp, grp->gr_numsecflavors,
+		    grp->gr_secflavors);
+		if (ret != 0)
+			return (ret);
+		grp = grp->gr_next;
+	}
+	return (0);
+}
+
+/*
  * Do the nmount() syscall with the update flag to push the export info into
  * the kernel.
  */
 static int
 do_mount(struct exportlist *ep, struct grouplist *grp, int exflags,
-    struct xucred *anoncrp, char *dirp, int dirplen, struct statfs *fsb)
+    struct xucred *anoncrp, char *dirp, int dirplen, struct statfs *fsb,
+    int numsecflavors, int *secflavors)
 {
 	struct statfs fsb1;
 	struct addrinfo *ai;
@@ -2557,14 +2980,16 @@ do_mount(struct exportlist *ep, struct grouplist *grp,
 	bzero(errmsg, sizeof(errmsg));
 	eap->ex_flags = exflags;
 	eap->ex_anon = *anoncrp;
+	LOGDEBUG("do_mount exflags=0x%x", exflags);
 	eap->ex_indexfile = ep->ex_indexfile;
 	if (grp->gr_type == GT_HOST)
 		ai = grp->gr_ptr.gt_addrinfo;
 	else
 		ai = NULL;
-	eap->ex_numsecflavors = ep->ex_numsecflavors;
+	eap->ex_numsecflavors = numsecflavors;
+	LOGDEBUG("do_mount numsec=%d", numsecflavors);
 	for (i = 0; i < eap->ex_numsecflavors; i++)
-		eap->ex_secflavors[i] = ep->ex_secflavors[i];
+		eap->ex_secflavors[i] = secflavors[i];
 	if (eap->ex_numsecflavors == 0) {
 		eap->ex_numsecflavors = 1;
 		eap->ex_secflavors[0] = AUTH_SYS;
@@ -2728,8 +3153,11 @@ do_mount(struct exportlist *ep, struct grouplist *grp,
 			else if (nfssvc(NFSSVC_PUBLICFH, (caddr_t)&fh) < 0)
 				syslog(LOG_ERR,
 				    "Can't set public fh for %s", public_name);
-			else
+			else {
 				has_publicfh = 1;
+				has_set_publicfh = 1;
+				ep->ex_flag |= EX_PUBLICFH;
+			}
 		}
 skip:
 		if (ai != NULL)

Modified: head/usr.sbin/mountd/pathnames.h
==============================================================================
--- head/usr.sbin/mountd/pathnames.h	Mon Jun  3 21:40:42 2019	(r348589)
+++ head/usr.sbin/mountd/pathnames.h	Mon Jun  3 22:58:51 2019	(r348590)
@@ -36,3 +36,4 @@
 #define	_PATH_EXPORTS		"/etc/exports"
 #define	_PATH_RMOUNTLIST	"/var/db/mountdtab"
 #define _PATH_MOUNTDPID		"/var/run/mountd.pid"
+#define	_PATH_MOUNTDDEBUG	"/var/log/mountd.debug"



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201906032258.x53Mwp56013050>