From owner-svn-src-stable@freebsd.org Fri Jul 5 22:48:33 2019 Return-Path: Delivered-To: svn-src-stable@mailman.ysv.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.ysv.freebsd.org (Postfix) with ESMTP id 4367215D6CFE; Fri, 5 Jul 2019 22:48:33 +0000 (UTC) (envelope-from rmacklem@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) server-signature RSA-PSS (4096 bits) client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "Let's Encrypt Authority X3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id D6B868DA41; Fri, 5 Jul 2019 22:48:32 +0000 (UTC) (envelope-from rmacklem@FreeBSD.org) Received: from repo.freebsd.org (repo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id 922AC18E3E; Fri, 5 Jul 2019 22:48:32 +0000 (UTC) (envelope-from rmacklem@FreeBSD.org) Received: from repo.freebsd.org ([127.0.1.37]) by repo.freebsd.org (8.15.2/8.15.2) with ESMTP id x65MmWs0083436; Fri, 5 Jul 2019 22:48:32 GMT (envelope-from rmacklem@FreeBSD.org) Received: (from rmacklem@localhost) by repo.freebsd.org (8.15.2/8.15.2/Submit) id x65MmWMN083434; Fri, 5 Jul 2019 22:48:32 GMT (envelope-from rmacklem@FreeBSD.org) Message-Id: <201907052248.x65MmWMN083434@repo.freebsd.org> X-Authentication-Warning: repo.freebsd.org: rmacklem set sender to rmacklem@FreeBSD.org using -f From: Rick Macklem Date: Fri, 5 Jul 2019 22:48:32 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org Subject: svn commit: r349772 - stable/11/usr.sbin/mountd X-SVN-Group: stable-11 X-SVN-Commit-Author: rmacklem X-SVN-Commit-Paths: stable/11/usr.sbin/mountd X-SVN-Commit-Revision: 349772 X-SVN-Commit-Repository: base MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-Rspamd-Queue-Id: D6B868DA41 X-Spamd-Bar: -- Authentication-Results: mx1.freebsd.org X-Spamd-Result: default: False [-2.92 / 15.00]; local_wl_from(0.00)[FreeBSD.org]; NEURAL_HAM_MEDIUM(-1.00)[-0.999,0]; NEURAL_HAM_SHORT(-0.92)[-0.925,0]; NEURAL_HAM_LONG(-1.00)[-1.000,0]; ASN(0.00)[asn:11403, ipnet:2610:1c1:1::/48, country:US] X-BeenThere: svn-src-stable@freebsd.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: SVN commit messages for all the -stable branches of the src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Fri, 05 Jul 2019 22:48:33 -0000 Author: rmacklem Date: Fri Jul 5 22:48:31 2019 New Revision: 349772 URL: https://svnweb.freebsd.org/changeset/base/349772 Log: MFC: r348590, r348591 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). PR: 237860 Modified: stable/11/usr.sbin/mountd/mountd.c stable/11/usr.sbin/mountd/pathnames.h Directory Properties: stable/11/ (props changed) Modified: stable/11/usr.sbin/mountd/mountd.c ============================================================================== --- stable/11/usr.sbin/mountd/mountd.c Fri Jul 5 22:36:31 2019 (r349771) +++ stable/11/usr.sbin/mountd/mountd.c Fri Jul 5 22:48:31 2019 (r349772) @@ -118,6 +118,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; @@ -127,6 +129,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); @@ -145,6 +150,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]; }; @@ -155,6 +163,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; @@ -174,7 +185,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 *); @@ -187,8 +199,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 *); @@ -197,10 +210,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); @@ -210,7 +229,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 *); @@ -235,6 +254,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 }; @@ -267,7 +287,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 */ @@ -291,6 +313,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. * @@ -463,7 +494,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(); @@ -634,7 +665,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; @@ -1424,10 +1455,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; @@ -1661,11 +1692,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; } @@ -1676,9 +1714,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 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) { @@ -1693,12 +1754,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; @@ -1720,7 +1781,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; @@ -1729,70 +1790,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 */ @@ -1807,16 +1928,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"); } /* @@ -1828,6 +1950,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); } @@ -1853,7 +1976,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; @@ -1869,7 +1992,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++; } @@ -1880,6 +2003,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 @@ -2026,7 +2387,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; @@ -2037,12 +2398,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; @@ -2062,7 +2426,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; } } @@ -2074,7 +2439,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; @@ -2084,10 +2449,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); @@ -2114,12 +2481,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; } } @@ -2485,6 +2855,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 @@ -2524,12 +2907,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; @@ -2555,14 +2978,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; @@ -2726,8 +3151,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: stable/11/usr.sbin/mountd/pathnames.h ============================================================================== --- stable/11/usr.sbin/mountd/pathnames.h Fri Jul 5 22:36:31 2019 (r349771) +++ stable/11/usr.sbin/mountd/pathnames.h Fri Jul 5 22:48:31 2019 (r349772) @@ -34,3 +34,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"