--- usr.sbin/mountd/mountd.c.xxx 2019-05-16 19:56:59.041925000 -0400 +++ usr.sbin/mountd/mountd.c 2019-05-18 23:02:31.573559000 -0400 @@ -49,6 +49,7 @@ __FBSDID("$FreeBSD: head/usr.sbin/mountd #include #include +#include #include #include #include @@ -114,10 +115,13 @@ struct dirlist { struct exportlist { struct dirlist *ex_dirl; struct dirlist *ex_defdir; + struct grouplist *ex_grphead; int ex_flag; 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 +131,10 @@ struct exportlist { }; /* ex_flag bits */ #define EX_LINKED 0x1 +#define EX_DONE 0x2 +#define EX_DEFSET 0x4 + +SLIST_HEAD(exportlisthead, exportlist); struct netmsk { struct sockaddr_storage nt_net; @@ -143,6 +151,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]; }; @@ -153,6 +164,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; @@ -172,7 +186,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 *); @@ -185,17 +200,28 @@ static void complete_service(struct netc 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 *); +static struct exportlist *ex_search(fsid_t *, struct exportlisthead *); static struct exportlist *get_exp(void); 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(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); static int get_line(void); @@ -204,8 +230,9 @@ static int get_net(char *, struct netmsk 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 void usr1handler(int sig); static int makemask(struct sockaddr_storage *ssp, int bitlen); static void mntsrv(struct svc_req *, SVCXPRT *); static void nextfield(char **, char **); @@ -227,9 +254,11 @@ static int xdr_fhs(XDR *, caddr_t); static int xdr_mlist(XDR *, caddr_t); static void terminate(int); -static SLIST_HEAD(, exportlist) exphead = SLIST_HEAD_INITIALIZER(exphead); -static SLIST_HEAD(, mountlist) mlhead = SLIST_HEAD_INITIALIZER(mlhead); -static struct grouplist *grphead; +#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 }; static char **exnames; static char **hosts = NULL; @@ -260,6 +289,8 @@ static int have_v6 = 1; static int v4root_phase = 0; static char v4root_dirpath[PATH_MAX + 1]; +static struct exportlist *v4root_ep = NULL; +static struct grouplist *v4root_grp = NULL; static int has_publicfh = 0; static struct pidfh *pfh = NULL; @@ -282,6 +313,9 @@ static void SYSLOG(int, const char *, .. #else static int debug = 0; #endif +static int logdebug = 0; +#define LOGDEBUG(format, ...) \ + (logdebug ? syslog(LOG_DEBUG, format, ## __VA_ARGS__) : 0) /* * Similar to strsep(), but it allows for quoted strings @@ -368,9 +402,10 @@ main(int argc, char **argv) in_port_t svcport; int c, k, s; int maxrec = RPC_MAXDATASIZE; - int attempt_cnt, port_len, port_pos, ret; + int attempt_cnt, passno, port_len, port_pos, ret; char **port_list; + passno = 0; /* Check that another mountd isn't already running. */ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &otherpid); if (pfh == NULL) { @@ -385,7 +420,7 @@ main(int argc, char **argv) else close(s); - while ((c = getopt(argc, argv, "2deh:lnp:rS")) != -1) + while ((c = getopt(argc, argv, "2deh:Ilnp:rS")) != -1) switch (c) { case '2': force_v2 = 1; @@ -437,6 +472,9 @@ main(int argc, char **argv) case 'S': suspend_nfsd = 1; break; + case 'I': + passno = 1; + break; default: usage(); } @@ -449,7 +487,6 @@ main(int argc, char **argv) argc -= optind; argv += optind; - grphead = (struct grouplist *)NULL; if (argc > 0) exnames = argv; else @@ -457,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(); @@ -469,6 +506,7 @@ main(int argc, char **argv) signal(SIGQUIT, SIG_IGN); } signal(SIGHUP, huphandler); + signal(SIGUSR1, usr1handler); signal(SIGTERM, terminate); signal(SIGPIPE, SIG_IGN); @@ -628,7 +666,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(passno); got_sighup = 0; } readfds = svc_fdset; @@ -1087,7 +1125,7 @@ mntsrv(struct svc_req *rqstp, SVCXPRT *t if (bad) ep = NULL; else - ep = ex_search(&fsb.f_fsid); + ep = ex_search(&fsb.f_fsid, exphead); hostset = defset = 0; if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset, &numsecflavors, &secflavorsp) || @@ -1302,21 +1340,23 @@ xdr_explist_common(XDR *xdrsp, caddr_t c int false = 0; int putdef; sigset_t sighup_mask; + int i; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); sigprocmask(SIG_BLOCK, &sighup_mask, NULL); - SLIST_FOREACH(ep, &exphead, entries) { - putdef = 0; - if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir, - &putdef, brief)) - goto errout; - if (ep->ex_defdir && putdef == 0 && - put_exlist(ep->ex_defdir, xdrsp, (struct dirlist *)NULL, - &putdef, brief)) - goto errout; - } + for (i = 0; i < exphashsize; i++) + SLIST_FOREACH(ep, &exphead[i], entries) { + putdef = 0; + if (put_exlist(ep->ex_dirl, xdrsp, ep->ex_defdir, + &putdef, brief)) + goto errout; + if (ep->ex_defdir && putdef == 0 && + put_exlist(ep->ex_defdir, xdrsp, NULL, + &putdef, brief)) + goto errout; + } sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); if (!xdr_bool(xdrsp, &false)) return (0); @@ -1416,7 +1456,7 @@ 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; @@ -1540,7 +1580,7 @@ get_exportlist_one(void) * See if this directory is already * in the list. */ - ep = ex_search(&fsb.f_fsid); + ep = ex_search(&fsb.f_fsid, exphead); if (ep == (struct exportlist *)NULL) { ep = get_exp(); ep->ex_fs = fsb.f_fsid; @@ -1653,11 +1693,16 @@ 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 (passno != 1 && do_mount(ep, grp, exflags, &anon, + dirp, dirplen, &fsb, ep->ex_numsecflavors, + ep->ex_secflavors)) { getexp_err(ep, tgrp, NULL); goto nextline; } @@ -1668,15 +1713,26 @@ 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 (ep != NULL) - free_exp(ep); - while (tgrp != NULL) { - grp = tgrp; - tgrp = tgrp->gr_next; - free_grp(grp); + if (passno == 1) { + if (v4root_ep != NULL) { + getexp_err(ep, tgrp, + "multiple V4 lines"); + goto nextline; + } + v4root_ep = ep; + v4root_grp = tgrp; + } else { + if (ep != NULL) + free_exp(ep); + while (tgrp != NULL) { + grp = tgrp; + tgrp = tgrp->gr_next; + free_grp(grp); + } } goto nextline; } @@ -1685,17 +1741,17 @@ get_exportlist_one(void) * Success. Update the data structures. */ if (has_host) { - hang_dirp(dirhead, tgrp, ep, opt_flags); - grp->gr_next = grphead; - grphead = tgrp; + 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; if ((ep->ex_flag & EX_LINKED) == 0) { - SLIST_INSERT_HEAD(&exphead, ep, entries); + insert_exports(ep, exphead); ep->ex_flag |= EX_LINKED; } @@ -1712,45 +1768,49 @@ nextline: * Get the export list from all specified files */ static void -get_exportlist(void) +get_exportlist(int passno) { - struct exportlist *ep, *ep2; - struct grouplist *grp, *tgrp; struct export_args export; struct iovec *iov; - struct statfs *fsp, *mntbufp; - struct xvfsconf vfc; + struct statfs *mntbufp; char errmsg[255]; int num, i; int iovlen; - int done; struct nfsex_args eargs; - if (suspend_nfsd != 0) - (void)nfssvc(NFSSVC_SUSPENDNFSD, NULL); + 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]); + } + + /* 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)); - /* - * First, get rid of the old list - */ - SLIST_FOREACH_SAFE(ep, &exphead, entries, ep2) { - SLIST_REMOVE(&exphead, ep, exportlist, entries); - free_exp(ep); - } - - grp = grphead; - while (grp) { - tgrp = grp; - grp = grp->gr_next; - free_grp(tgrp); - } - grphead = (struct grouplist *)NULL; - + if (suspend_nfsd != 0) + (void)nfssvc(NFSSVC_SUSPENDNFSD, NULL); /* * and the old V4 root dir. */ @@ -1765,62 +1825,71 @@ get_exportlist(void) */ 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 (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)); - } - - for (i = 0; i < num; i++) { - fsp = &mntbufp[i]; - if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) { - syslog(LOG_ERR, "getvfsbyname() failed for %s", - fsp->f_fstypename); - continue; + 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"); + /* exphead == NULL if not yet allocated (first call). */ + if (exphead != NULL) { + /* + * First, get rid of the old lists. + */ + free_exports(exphead); + free_exports(oldexphead); } + free_v4rootexp(); /* - * We do not need to delete "export" flag from - * filesystems that do not have it set. - */ - if (!(fsp->f_flags & MNT_EXPORTED)) - continue; - /* - * Do not delete export for network filesystem by - * passing "export" arg to nmount(). - * It only makes sense to do this for local filesystems. - */ - if (vfc.vfc_flags & VFCF_NETWORK) - continue; - - iov[1].iov_base = fsp->f_fstypename; - iov[1].iov_len = strlen(fsp->f_fstypename) + 1; - iov[3].iov_base = fsp->f_mntonname; - iov[3].iov_len = strlen(fsp->f_mntonname) + 1; - iov[5].iov_base = fsp->f_mntfromname; - iov[5].iov_len = strlen(fsp->f_mntfromname) + 1; - errmsg[0] = '\0'; - - /* - * EXDEV is returned when path exists but is not a - * mount point. May happens if raced with unmount. - */ - if (nmount(iov, iovlen, fsp->f_flags) < 0 && - errno != ENOENT && errno != ENOTSUP && errno != EXDEV) { - syslog(LOG_ERR, - "can't delete exports for %s: %m %s", - fsp->f_mntonname, 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); + + /* Allocate hash tables, for first call. */ + if (exphead == NULL) { + /* Target an average linked list length of 20. */ + 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) { @@ -1838,6 +1907,56 @@ get_exportlist(void) } /* + * If there was no public fh, clear any previous one set. + */ + if (has_publicfh == 0) + (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"); +} + +/* + * Insert an export entry in the appropriate list. + */ +static void +insert_exports(struct exportlist *ep, struct exportlisthead *exhp) +{ + uint32_t i; + + i = EXPHASH(&ep->ex_fs); + LOGDEBUG("fs=%s hash=%i", ep->ex_fsdir, i); + SLIST_INSERT_HEAD(&exhp[i], ep, entries); +} + +/* + * Free up the exports lists passed in as arguments. + */ +static void +free_exports(struct exportlisthead *exhp) +{ + struct exportlist *ep, *ep2; + int i; + + for (i = 0; i < exphashsize; i++) { + SLIST_FOREACH_SAFE(ep, &exhp[i], entries, ep2) { + SLIST_REMOVE(&exhp[i], ep, exportlist, entries); + free_exp(ep); + } + SLIST_INIT(&exhp[i]); + } +} + +/* + * Read the exports file(s) and call get_exportlist_one() for each line. + */ +static void +read_exportfile(int passno) +{ + int done, i; + + /* * Read in the exports file and build the list, calling * nmount() as we go along to push the export rules into the kernel. */ @@ -1849,7 +1968,7 @@ get_exportlist(void) syslog(LOG_WARNING, "can't open %s", exnames[i]); continue; } - get_exportlist_one(); + get_exportlist_one(passno); fclose(exp_file); done++; } @@ -1857,15 +1976,244 @@ get_exportlist(void) syslog(LOG_ERR, "can't open any exports file"); exit(2); } +} + +/* + * 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; /* - * If there was no public fh, clear any previous one set. + * 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. */ - if (has_publicfh == 0) - (void) nfssvc(NFSSVC_NOPUBLICFH, NULL); + 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) { + 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); + } + } - /* Resume the nfsd. If they weren't suspended, this is harmless. */ - (void)nfssvc(NFSSVC_RESUMENFSD, NULL); + /* 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"); + delete_export(iov, iovlen, &ofs, + errmsg); + } + } + } + + /* Do the V4 root exports, as required. */ + grp = v4root_grp; + 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, + v4root_ep->ex_numsecflavors, v4root_ep->ex_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); +} + +/* + * Compare to struct xucred's. Return 0 if the same and 1 otherwise. + * This algorithm is O(N**2) but fortunately N is always small. + */ +static int +compare_cred(struct xucred *cr0, struct xucred *cr1) +{ + int i, j; + + if (cr0->cr_uid != cr1->cr_uid || cr0->cr_ngroups != cr1->cr_ngroups) + return (1); + for (i = 0; i < cr0->cr_ngroups; i++) { + for (j = 0; j < cr0->cr_ngroups; j++) + if (cr0->cr_groups[i] == cr1->cr_groups[j]) + break; + if (j == cr0->cr_ngroups) + return (1); + } + return (0); +} + +/* + * Compare two lists of security flavors. Return 0 if the same and 1 otherwise. + * This algorithm is O(N**2) but fortunately N is always small. + */ +static int +compare_secflavor(int *sec1, int *sec2, int nsec) +{ + int i, j; + + for (i = 0; i < nsec; i++) { + for (j = 0; j < nsec; j++) + if (sec1[i] == sec2[j]) + break; + if (j == nsec) + return (1); + } + return (0); +} + +/* + * Delete an exports entry. + */ +static void +delete_export(struct iovec *iov, int iovlen, struct statfs *fsp, char *errmsg) +{ + struct xvfsconf vfc; + + if (getvfsbyname(fsp->f_fstypename, &vfc) != 0) { + syslog(LOG_ERR, "getvfsbyname() failed for %s", + fsp->f_fstypename); + return; + } + + /* + * We do not need to delete "export" flag from + * filesystems that do not have it set. + */ + if (!(fsp->f_flags & MNT_EXPORTED)) + return; + /* + * Do not delete export for network filesystem by + * passing "export" arg to nmount(). + * It only makes sense to do this for local filesystems. + */ + if (vfc.vfc_flags & VFCF_NETWORK) + return; + + iov[1].iov_base = fsp->f_fstypename; + iov[1].iov_len = strlen(fsp->f_fstypename) + 1; + iov[3].iov_base = fsp->f_mntonname; + iov[3].iov_len = strlen(fsp->f_mntonname) + 1; + iov[5].iov_base = fsp->f_mntfromname; + iov[5].iov_len = strlen(fsp->f_mntfromname) + 1; + errmsg[0] = '\0'; + + /* + * EXDEV is returned when path exists but is not a + * mount point. May happens if raced with unmount. + */ + if (nmount(iov, iovlen, fsp->f_flags) < 0 && errno != ENOENT && + errno != ENOTSUP && errno != EXDEV) { + syslog(LOG_ERR, + "can't delete exports for %s: %m %s", + fsp->f_mntonname, errmsg); + } } /* @@ -1924,11 +2272,13 @@ getexp_err(struct exportlist *ep, struct * Search the export list for a matching fs. */ static struct exportlist * -ex_search(fsid_t *fsid) +ex_search(fsid_t *fsid, struct exportlisthead *exhp) { struct exportlist *ep; + uint32_t i; - SLIST_FOREACH(ep, &exphead, entries) { + i = EXPHASH(fsid); + SLIST_FOREACH(ep, &exhp[i], entries) { if (ep->ex_fs.val[0] == fsid->val[0] && ep->ex_fs.val[1] == fsid->val[1]) return (ep); @@ -1965,7 +2315,7 @@ add_expdir(struct dirlist **dpp, char *c */ 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; @@ -1976,12 +2326,15 @@ hang_dirp(struct dirlist *dp, struct gro 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; @@ -2001,7 +2354,8 @@ hang_dirp(struct dirlist *dp, struct gro */ 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; } } @@ -2013,7 +2367,7 @@ hang_dirp(struct dirlist *dp, struct gro */ 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; @@ -2023,10 +2377,12 @@ add_dlist(struct dirlist **dpp, struct d 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); @@ -2053,12 +2409,15 @@ add_dlist(struct dirlist **dpp, struct d 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; } } @@ -2403,6 +2762,7 @@ get_host(char *cp, struct grouplist *grp static void free_exp(struct exportlist *ep) { + struct grouplist *grp, *tgrp; if (ep->ex_defdir) { free_host(ep->ex_defdir->dp_hosts); @@ -2413,10 +2773,37 @@ free_exp(struct exportlist *ep) if (ep->ex_indexfile) free(ep->ex_indexfile); free_dir(ep->ex_dirl); + grp = ep->ex_grphead; + while (grp) { + tgrp = grp; + grp = grp->gr_next; + free_grp(tgrp); + } free((caddr_t)ep); } /* + * Free up the v4root exports. + */ +static void +free_v4rootexp(void) +{ + struct grouplist *grp, *tgrp; + + if (v4root_ep != NULL) { + free_exp(v4root_ep); + v4root_ep = NULL; + } + tgrp = v4root_grp; + v4root_grp = NULL; + while (tgrp != NULL) { + grp = tgrp; + tgrp = tgrp->gr_next; + free_grp(grp); + } +} + +/* * Free hosts. */ static void @@ -2456,12 +2843,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_mount_export=%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; @@ -2487,14 +2914,16 @@ do_mount(struct exportlist *ep, struct g 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; @@ -2824,18 +3253,27 @@ static void nextfield(char **cp, char **endcp) { char *p; + char quot = 0; p = *cp; while (*p == ' ' || *p == '\t') p++; - if (*p == '\n' || *p == '\0') - *cp = *endcp = p; - else { - *cp = p++; - while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0') - p++; - *endcp = p; - } + *cp = p; + while (*p != '\0') { + if (quot) { + if (*p == quot) + quot = 0; + } else { + if (*p == '\\' && *(p + 1) != '\0') + p++; + else if (*p == '\'' || *p == '"') + quot = *p; + else if (*p == ' ' || *p == '\t') + break; + } + p++; + }; + *endcp = p; } /* @@ -2907,8 +3345,8 @@ parsecred(char *namelist, struct xucred /* * Get the user's password table entry. */ - names = strsep_quote(&namelist, " \t\n"); - name = strsep(&names, ":"); + names = namelist; + name = strsep_quote(&names, ":"); /* Bug? name could be NULL here */ if (isdigit(*name) || *name == '-') pw = getpwuid(atoi(name)); @@ -2952,7 +3390,7 @@ parsecred(char *namelist, struct xucred } cr->cr_ngroups = 0; while (names != NULL && *names != '\0' && cr->cr_ngroups < XU_NGROUPS) { - name = strsep(&names, ":"); + name = strsep_quote(&names, ":"); if (isdigit(*name) || *name == '-') { cr->cr_groups[cr->cr_ngroups++] = atoi(name); } else { @@ -3277,6 +3715,16 @@ huphandler(int sig __unused) } static void +usr1handler(int sig __unused) +{ + + if (logdebug != 0) + logdebug = 0; + else + logdebug = 1; +} + +static void terminate(int sig __unused) { pidfile_remove(pfh);