diff --git a/contrib/smbfs/mount_smbfs/mount_smbfs.c b/contrib/smbfs/mount_smbfs/mount_smbfs.c index 99f1cee8ed2e..5b357655b642 100644 --- a/contrib/smbfs/mount_smbfs/mount_smbfs.c +++ b/contrib/smbfs/mount_smbfs/mount_smbfs.c @@ -1,308 +1,307 @@ /* * Copyright (c) 2000-2002, Boris Popov * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Boris Popov. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $Id: mount_smbfs.c,v 1.17 2002/04/10 04:17:51 bp Exp $ * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include -#include "mntopts.h" - static char mount_point[MAXPATHLEN + 1]; static void usage(void); static struct mntopt mopts[] = { MOPT_STDOPTS, MOPT_END }; static char smbfs_vfsname[] = "smbfs"; int main(int argc, char *argv[]) { struct iovec *iov; unsigned int iovlen; struct smb_ctx sctx, *ctx = &sctx; struct stat st; #ifdef APPLE extern void dropsuid(); extern int loadsmbvfs(); #else struct xvfsconf vfc; #endif char *next, *p, *val; int opt, error, mntflags, caseopt, fd; uid_t uid; gid_t gid; mode_t dir_mode, file_mode; char errmsg[255] = { 0 }; iov = NULL; iovlen = 0; fd = 0; uid = (uid_t)-1; gid = (gid_t)-1; caseopt = 0; file_mode = 0; dir_mode = 0; #ifdef APPLE dropsuid(); #endif if (argc == 2) { if (strcmp(argv[1], "-h") == 0) { usage(); } } if (argc < 3) usage(); #ifdef APPLE error = loadsmbvfs(); #else error = getvfsbyname(smbfs_vfsname, &vfc); if (error) { if (kldload(smbfs_vfsname) < 0) err(EX_OSERR, "kldload(%s)", smbfs_vfsname); error = getvfsbyname(smbfs_vfsname, &vfc); } #endif if (error) errx(EX_OSERR, "SMB filesystem is not available"); if (smb_lib_init() != 0) exit(1); mntflags = error = 0; caseopt = SMB_CS_NONE; if (smb_ctx_init(ctx, argc, argv, SMBL_SHARE, SMBL_SHARE, SMB_ST_DISK) != 0) exit(1); if (smb_ctx_readrc(ctx) != 0) exit(1); if (smb_rc) rc_close(smb_rc); while ((opt = getopt(argc, argv, STDPARAM_OPT"c:d:f:g:l:n:o:u:w:")) != -1) { switch (opt) { case STDPARAM_ARGS: error = smb_ctx_opt(ctx, opt, optarg); if (error) exit(1); break; case 'u': { struct passwd *pwd; pwd = isdigit(optarg[0]) ? getpwuid(atoi(optarg)) : getpwnam(optarg); if (pwd == NULL) errx(EX_NOUSER, "unknown user '%s'", optarg); uid = pwd->pw_uid; break; } case 'g': { struct group *grp; grp = isdigit(optarg[0]) ? getgrgid(atoi(optarg)) : getgrnam(optarg); if (grp == NULL) errx(EX_NOUSER, "unknown group '%s'", optarg); gid = grp->gr_gid; break; } case 'd': errno = 0; dir_mode = strtol(optarg, &next, 8); if (errno || *next != 0) errx(EX_DATAERR, "invalid value for directory mode"); break; case 'f': errno = 0; file_mode = strtol(optarg, &next, 8); if (errno || *next != 0) errx(EX_DATAERR, "invalid value for file mode"); break; case '?': usage(); /*NOTREACHED*/ case 'n': { char *inp, *nsp; nsp = inp = optarg; while ((nsp = strsep(&inp, ",;:")) != NULL) { if (strcasecmp(nsp, "LONG") == 0) { build_iovec(&iov, &iovlen, "nolong", NULL, 0); } else { errx(EX_DATAERR, "unknown suboption '%s'", nsp); } } break; }; case 'o': getmntopts(optarg, mopts, &mntflags, 0); p = strchr(optarg, '='); val = NULL; if (p != NULL) { *p = '\0'; val = p + 1; } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); break; case 'c': switch (optarg[0]) { case 'l': caseopt |= SMB_CS_LOWER; break; case 'u': caseopt |= SMB_CS_UPPER; break; default: errx(EX_DATAERR, "invalid suboption '%c' for -c", optarg[0]); } break; default: usage(); } } if (optind == argc - 2) optind++; if (optind != argc - 1) usage(); realpath(argv[optind], mount_point); if (stat(mount_point, &st) == -1) err(EX_OSERR, "could not find mount point %s", mount_point); if (!S_ISDIR(st.st_mode)) { errno = ENOTDIR; err(EX_OSERR, "can't mount on %s", mount_point); } /* if (smb_getextattr(mount_point, &einfo) == 0) errx(EX_OSERR, "can't mount on %s twice", mount_point); */ if (uid == (uid_t)-1) uid = st.st_uid; if (gid == (gid_t)-1) gid = st.st_gid; if (file_mode == 0 ) file_mode = st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); if (dir_mode == 0) { dir_mode = file_mode; if (dir_mode & S_IRUSR) dir_mode |= S_IXUSR; if (dir_mode & S_IRGRP) dir_mode |= S_IXGRP; if (dir_mode & S_IROTH) dir_mode |= S_IXOTH; } /* * For now, let connection be private for this mount */ ctx->ct_ssn.ioc_opt |= SMBVOPT_PRIVATE; ctx->ct_ssn.ioc_owner = ctx->ct_sh.ioc_owner = 0; /* root */ ctx->ct_ssn.ioc_group = ctx->ct_sh.ioc_group = gid; opt = 0; if (dir_mode & S_IXGRP) opt |= SMBM_EXECGRP; if (dir_mode & S_IXOTH) opt |= SMBM_EXECOTH; ctx->ct_ssn.ioc_rights |= opt; ctx->ct_sh.ioc_rights |= opt; error = smb_ctx_resolve(ctx); if (error) exit(1); error = smb_ctx_lookup(ctx, SMBL_SHARE, SMBLK_CREATE); if (error) { exit(1); } fd = ctx->ct_fd; build_iovec(&iov, &iovlen, "fstype", strdup("smbfs"), -1); build_iovec(&iov, &iovlen, "fspath", mount_point, -1); build_iovec_argf(&iov, &iovlen, "fd", "%d", fd); build_iovec(&iov, &iovlen, "mountpoint", mount_point, -1); build_iovec_argf(&iov, &iovlen, "uid", "%d", uid); build_iovec_argf(&iov, &iovlen, "gid", "%d", gid); build_iovec_argf(&iov, &iovlen, "file_mode", "%d", file_mode); build_iovec_argf(&iov, &iovlen, "dir_mode", "%d", dir_mode); build_iovec_argf(&iov, &iovlen, "caseopt", "%d", caseopt); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof errmsg); error = nmount(iov, iovlen, mntflags); smb_ctx_done(ctx); if (error) { smb_error("mount error: %s %s", error, mount_point, errmsg); exit(1); } return 0; } static void usage(void) { fprintf(stderr, "%s\n%s\n%s\n%s\n", "usage: mount_smbfs [-E cs1:cs2] [-I host] [-L locale] [-M crights:srights]", " [-N] [-O cowner:cgroup/sowner:sgroup] [-R retrycount]", " [-T timeout] [-W workgroup] [-c case] [-d mode] [-f mode]", " [-g gid] [-n opt] [-u uid] [-U username] //user@server/share node"); exit (1); } diff --git a/lib/libutil/mntopts.c b/lib/libutil/mntopts.c index 0912b1e1a306..1d9347e3108a 100644 --- a/lib/libutil/mntopts.c +++ b/lib/libutil/mntopts.c @@ -1,301 +1,300 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include +#include #include #include #include #include #include -#include "mntopts.h" - int getmnt_silent = 0; void getmntopts(const char *options, const struct mntopt *m0, int *flagp, int *altflagp) { const struct mntopt *m; int negative, len; char *opt, *optbuf, *p; int *thisflagp; /* Copy option string, since it is about to be torn asunder... */ if ((optbuf = strdup(options)) == NULL) err(1, NULL); for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) { /* Check for "no" prefix. */ if (opt[0] == 'n' && opt[1] == 'o') { negative = 1; opt += 2; } else negative = 0; /* * for options with assignments in them (ie. quotas) * ignore the assignment as it's handled elsewhere */ p = strchr(opt, '='); if (p != NULL) *++p = '\0'; /* Scan option table. */ for (m = m0; m->m_option != NULL; ++m) { len = strlen(m->m_option); if (strncasecmp(opt, m->m_option, len) == 0) if (opt[len] == '\0' || opt[len] == '=') break; } /* Save flag, or fail if option is not recognized. */ if (m->m_option) { thisflagp = m->m_altloc ? altflagp : flagp; if (negative == m->m_inverse) *thisflagp |= m->m_flag; else *thisflagp &= ~m->m_flag; } else if (!getmnt_silent) { errx(1, "-o %s: option not supported", opt); } } free(optbuf); } void rmslashes(char *rrpin, char *rrpout) { char *rrpoutstart; *rrpout = *rrpin; for (rrpoutstart = rrpout; *rrpin != '\0'; *rrpout++ = *rrpin++) { /* skip all double slashes */ while (*rrpin == '/' && *(rrpin + 1) == '/') rrpin++; } /* remove trailing slash if necessary */ if (rrpout - rrpoutstart > 1 && *(rrpout - 1) == '/') *(rrpout - 1) = '\0'; else *rrpout = '\0'; } int checkpath(const char *path, char *resolved) { struct stat sb; if (realpath(path, resolved) == NULL || stat(resolved, &sb) != 0) return (1); if (!S_ISDIR(sb.st_mode)) { errno = ENOTDIR; return (1); } return (0); } int checkpath_allow_file(const char *path, char *resolved) { struct stat sb; if (realpath(path, resolved) == NULL || stat(resolved, &sb) != 0) return (1); if (!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode)) { errno = ENOTDIR; return (1); } return (0); } /* * Get the mount point information for name. Name may be mount point name * or device name (with or without /dev/ preprended). */ struct statfs * getmntpoint(const char *name) { struct stat devstat, mntdevstat; char device[sizeof(_PATH_DEV) - 1 + MNAMELEN]; char *ddevname; struct statfs *mntbuf, *statfsp; int i, mntsize, isdev; u_long len; if (stat(name, &devstat) != 0) return (NULL); if (S_ISCHR(devstat.st_mode) || S_ISBLK(devstat.st_mode)) isdev = 1; else isdev = 0; mntsize = getmntinfo(&mntbuf, MNT_NOWAIT); for (i = 0; i < mntsize; i++) { statfsp = &mntbuf[i]; if (isdev == 0) { if (strcmp(name, statfsp->f_mntonname)) continue; return (statfsp); } ddevname = statfsp->f_mntfromname; if (*ddevname != '/') { if ((len = strlen(_PATH_DEV) + strlen(ddevname) + 1) > sizeof(statfsp->f_mntfromname) || len > sizeof(device)) continue; strncpy(device, _PATH_DEV, len); strncat(device, ddevname, len); if (stat(device, &mntdevstat) == 0) strncpy(statfsp->f_mntfromname, device, len); } if (stat(ddevname, &mntdevstat) == 0 && mntdevstat.st_rdev == devstat.st_rdev) return (statfsp); } return (NULL); } /* * If possible reload a mounted filesystem. * When prtmsg != NULL print a warning if a reload is attempted, but fails. * Return 0 on success, 1 on failure. */ int chkdoreload(struct statfs *mntp, void (*prtmsg)(const char *, ...) __printflike(1,2)) { struct iovec *iov; int iovlen, error; char errmsg[255]; /* * If the filesystem is not mounted it does not need to be reloaded. * If it is mounted for writing, then it could not have been opened * for writing by a utility, so does not need to be reloaded. */ if (mntp == NULL || (mntp->f_flags & MNT_RDONLY) == 0) return (0); /* * We modified a mounted file system. Do a mount update on * it so we can continue using it as safely as possible. */ iov = NULL; iovlen = 0; errmsg[0] = '\0'; build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "ffs"), 4); build_iovec(&iov, &iovlen, "from", mntp->f_mntfromname, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntp->f_mntonname, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); build_iovec(&iov, &iovlen, "update", NULL, 0); build_iovec(&iov, &iovlen, "reload", NULL, 0); /* * XX: We need the following line until we clean up * nmount parsing of root mounts and NFS root mounts. */ build_iovec(&iov, &iovlen, "ro", NULL, 0); error = nmount(iov, iovlen, mntp->f_flags); free_iovec(&iov, &iovlen); if (error == 0) return (0); if (prtmsg != NULL) prtmsg("mount reload of '%s' failed: %s %s\n\n", mntp->f_mntonname, strerror(errno), errmsg); return (1); } void build_iovec(struct iovec **iov, int *iovlen, const char *name, void *val, size_t len) { int i; if (*iovlen < 0) return; i = *iovlen; *iov = realloc(*iov, sizeof **iov * (i + 2)); if (*iov == NULL) { *iovlen = -1; return; } (*iov)[i].iov_base = strdup(name); (*iov)[i].iov_len = strlen(name) + 1; i++; (*iov)[i].iov_base = val; if (len == (size_t)-1) { if (val != NULL) len = strlen(val) + 1; else len = 0; } (*iov)[i].iov_len = (int)len; *iovlen = ++i; } /* * This function is needed for compatibility with parameters * which used to use the mount_argf() command for the old mount() syscall. */ void build_iovec_argf(struct iovec **iov, int *iovlen, const char *name, const char *fmt, ...) { va_list ap; char val[255] = { 0 }; va_start(ap, fmt); vsnprintf(val, sizeof(val), fmt, ap); va_end(ap); build_iovec(iov, iovlen, name, strdup(val), (size_t)-1); } /* * Free the iovec and reset to NULL with zero length. Useful for calling * nmount in a loop. */ void free_iovec(struct iovec **iov, int *iovlen) { int i; for (i = 0; i < *iovlen; i += 2) free((*iov)[i].iov_base); free(*iov); } diff --git a/sbin/init/init.c b/sbin/init/init.c index b6908f19fd82..b345c8fa219a 100644 --- a/sbin/init/init.c +++ b/sbin/init/init.c @@ -1,2155 +1,2155 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Donn Seeley at Berkeley Software Design, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #ifdef SECURE #include #endif #ifdef LOGIN_CAP #include #endif -#include "mntopts.h" #include "pathnames.h" /* * Sleep times; used to prevent thrashing. */ #define GETTY_SPACING 5 /* N secs minimum getty spacing */ #define GETTY_SLEEP 30 /* sleep N secs after spacing problem */ #define GETTY_NSPACE 3 /* max. spacing count to bring reaction */ #define WINDOW_WAIT 3 /* wait N secs after starting window */ #define STALL_TIMEOUT 30 /* wait N secs after warning */ #define DEATH_WATCH 10 /* wait N secs for procs to die */ #define DEATH_SCRIPT 120 /* wait for 2min for /etc/rc.shutdown */ #define RESOURCE_RC "daemon" #define RESOURCE_WINDOW "default" #define RESOURCE_GETTY "default" #define SCRIPT_ARGV_SIZE 3 /* size of argv passed to execute_script, can be increased if needed */ static void handle(sig_t, ...); static void delset(sigset_t *, ...); static void stall(const char *, ...) __printflike(1, 2); static void warning(const char *, ...) __printflike(1, 2); static void emergency(const char *, ...) __printflike(1, 2); static void disaster(int); static void revoke_ttys(void); static int runshutdown(void); static char *strk(char *); static void runfinal(void); /* * We really need a recursive typedef... * The following at least guarantees that the return type of (*state_t)() * is sufficiently wide to hold a function pointer. */ typedef long (*state_func_t)(void); typedef state_func_t (*state_t)(void); static state_func_t single_user(void); static state_func_t runcom(void); static state_func_t read_ttys(void); static state_func_t multi_user(void); static state_func_t clean_ttys(void); static state_func_t catatonia(void); static state_func_t death(void); static state_func_t death_single(void); static state_func_t reroot(void); static state_func_t reroot_phase_two(void); static state_func_t run_script(const char *); static enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT; static bool Reboot = false; static int howto = RB_AUTOBOOT; static bool devfs = false; static char *init_path_argv0; static void transition(state_t); static state_t requested_transition; static state_t current_state = death_single; static void execute_script(char *argv[]); static void open_console(void); static const char *get_shell(void); static void replace_init(char *path); static void write_stderr(const char *message); typedef struct init_session { pid_t se_process; /* controlling process */ time_t se_started; /* used to avoid thrashing */ int se_flags; /* status of session */ #define SE_SHUTDOWN 0x1 /* session won't be restarted */ #define SE_PRESENT 0x2 /* session is in /etc/ttys */ #define SE_IFEXISTS 0x4 /* session defined as "onifexists" */ #define SE_IFCONSOLE 0x8 /* session defined as "onifconsole" */ int se_nspace; /* spacing count */ char *se_device; /* filename of port */ char *se_getty; /* what to run on that port */ char *se_getty_argv_space; /* pre-parsed argument array space */ char **se_getty_argv; /* pre-parsed argument array */ char *se_window; /* window system (started only once) */ char *se_window_argv_space; /* pre-parsed argument array space */ char **se_window_argv; /* pre-parsed argument array */ char *se_type; /* default terminal type */ struct init_session *se_prev; struct init_session *se_next; } session_t; static void free_session(session_t *); static session_t *new_session(session_t *, struct ttyent *); static session_t *sessions; static char **construct_argv(char *); static void start_window_system(session_t *); static void collect_child(pid_t); static pid_t start_getty(session_t *); static void transition_handler(int); static void alrm_handler(int); static void setsecuritylevel(int); static int getsecuritylevel(void); static int setupargv(session_t *, struct ttyent *); #ifdef LOGIN_CAP static void setprocresources(const char *); #endif static bool clang; static int start_session_db(void); static void add_session(session_t *); static void del_session(session_t *); static session_t *find_session(pid_t); static DB *session_db; /* * The mother of all processes. */ int main(int argc, char *argv[]) { state_t initial_transition = runcom; char kenv_value[PATH_MAX]; int c, error; struct sigaction sa; sigset_t mask; /* Dispose of random users. */ if (getuid() != 0) errx(1, "%s", strerror(EPERM)); BOOTTRACE("init(8) starting..."); /* System V users like to reexec init. */ if (getpid() != 1) { #ifdef COMPAT_SYSV_INIT /* So give them what they want */ if (argc > 1) { if (strlen(argv[1]) == 1) { char runlevel = *argv[1]; int sig; switch (runlevel) { case '0': /* halt + poweroff */ sig = SIGUSR2; break; case '1': /* single-user */ sig = SIGTERM; break; case '6': /* reboot */ sig = SIGINT; break; case 'c': /* block further logins */ sig = SIGTSTP; break; case 'q': /* rescan /etc/ttys */ sig = SIGHUP; break; case 'r': /* remount root */ sig = SIGEMT; break; default: goto invalid; } kill(1, sig); _exit(0); } else invalid: errx(1, "invalid run-level ``%s''", argv[1]); } else #endif errx(1, "already running"); } init_path_argv0 = strdup(argv[0]); if (init_path_argv0 == NULL) err(1, "strdup"); /* * Note that this does NOT open a file... * Does 'init' deserve its own facility number? */ openlog("init", LOG_CONS, LOG_AUTH); /* * Create an initial session. */ if (setsid() < 0 && (errno != EPERM || getsid(0) != 1)) warning("initial setsid() failed: %m"); /* * Establish an initial user so that programs running * single user do not freak out and die (like passwd). */ if (setlogin("root") < 0) warning("setlogin() failed: %m"); /* * This code assumes that we always get arguments through flags, * never through bits set in some random machine register. */ while ((c = getopt(argc, argv, "dsfr")) != -1) switch (c) { case 'd': devfs = true; break; case 's': initial_transition = single_user; break; case 'f': runcom_mode = FASTBOOT; break; case 'r': initial_transition = reroot_phase_two; break; default: warning("unrecognized flag '-%c'", c); break; } if (optind != argc) warning("ignoring excess arguments"); /* * We catch or block signals rather than ignore them, * so that they get reset on exec. */ handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ, 0); handle(transition_handler, SIGHUP, SIGINT, SIGEMT, SIGTERM, SIGTSTP, SIGUSR1, SIGUSR2, SIGWINCH, 0); handle(alrm_handler, SIGALRM, 0); sigfillset(&mask); delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS, SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGEMT, SIGTERM, SIGTSTP, SIGALRM, SIGUSR1, SIGUSR2, SIGWINCH, 0); sigprocmask(SIG_SETMASK, &mask, NULL); sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = SIG_IGN; sigaction(SIGTTIN, &sa, NULL); sigaction(SIGTTOU, &sa, NULL); /* * Paranoia. */ close(0); close(1); close(2); if (kenv(KENV_GET, "init_exec", kenv_value, sizeof(kenv_value)) > 0) { replace_init(kenv_value); _exit(0); /* reboot */ } if (kenv(KENV_GET, "init_script", kenv_value, sizeof(kenv_value)) > 0) { state_func_t next_transition; if ((next_transition = run_script(kenv_value)) != NULL) initial_transition = (state_t) next_transition; } if (kenv(KENV_GET, "init_chroot", kenv_value, sizeof(kenv_value)) > 0) { if (chdir(kenv_value) != 0 || chroot(".") != 0) warning("Can't chroot to %s: %m", kenv_value); } /* * Additional check if devfs needs to be mounted: * If "/" and "/dev" have the same device number, * then it hasn't been mounted yet. */ if (!devfs) { struct stat stst; dev_t root_devno; stat("/", &stst); root_devno = stst.st_dev; if (stat("/dev", &stst) != 0) warning("Can't stat /dev: %m"); else if (stst.st_dev == root_devno) devfs = true; } if (devfs) { struct iovec iov[4]; char *s; int i; char _fstype[] = "fstype"; char _devfs[] = "devfs"; char _fspath[] = "fspath"; char _path_dev[]= _PATH_DEV; iov[0].iov_base = _fstype; iov[0].iov_len = sizeof(_fstype); iov[1].iov_base = _devfs; iov[1].iov_len = sizeof(_devfs); iov[2].iov_base = _fspath; iov[2].iov_len = sizeof(_fspath); /* * Try to avoid the trailing slash in _PATH_DEV. * Be *very* defensive. */ s = strdup(_PATH_DEV); if (s != NULL) { i = strlen(s); if (i > 0 && s[i - 1] == '/') s[i - 1] = '\0'; iov[3].iov_base = s; iov[3].iov_len = strlen(s) + 1; } else { iov[3].iov_base = _path_dev; iov[3].iov_len = sizeof(_path_dev); } nmount(iov, 4, 0); if (s != NULL) free(s); } if (initial_transition != reroot_phase_two) { /* * Unmount reroot leftovers. This runs after init(8) * gets reexecuted after reroot_phase_two() is done. */ error = unmount(_PATH_REROOT, MNT_FORCE); if (error != 0 && errno != EINVAL) warning("Cannot unmount %s: %m", _PATH_REROOT); } /* * Start the state machine. */ transition(initial_transition); /* * Should never reach here. */ return 1; } /* * Associate a function with a signal handler. */ static void handle(sig_t handler, ...) { int sig; struct sigaction sa; sigset_t mask_everything; va_list ap; va_start(ap, handler); sa.sa_handler = handler; sigfillset(&mask_everything); while ((sig = va_arg(ap, int)) != 0) { sa.sa_mask = mask_everything; /* XXX SA_RESTART? */ sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0; sigaction(sig, &sa, NULL); } va_end(ap); } /* * Delete a set of signals from a mask. */ static void delset(sigset_t *maskp, ...) { int sig; va_list ap; va_start(ap, maskp); while ((sig = va_arg(ap, int)) != 0) sigdelset(maskp, sig); va_end(ap); } /* * Log a message and sleep for a while (to give someone an opportunity * to read it and to save log or hardcopy output if the problem is chronic). * NB: should send a message to the session logger to avoid blocking. */ static void stall(const char *message, ...) { va_list ap; va_start(ap, message); vsyslog(LOG_ALERT, message, ap); va_end(ap); sleep(STALL_TIMEOUT); } /* * Like stall(), but doesn't sleep. * If cpp had variadic macros, the two functions could be #defines for another. * NB: should send a message to the session logger to avoid blocking. */ static void warning(const char *message, ...) { va_list ap; va_start(ap, message); vsyslog(LOG_ALERT, message, ap); va_end(ap); } /* * Log an emergency message. * NB: should send a message to the session logger to avoid blocking. */ static void emergency(const char *message, ...) { va_list ap; va_start(ap, message); vsyslog(LOG_EMERG, message, ap); va_end(ap); } /* * Catch an unexpected signal. */ static void disaster(int sig) { emergency("fatal signal: %s", (unsigned)sig < NSIG ? sys_siglist[sig] : "unknown signal"); sleep(STALL_TIMEOUT); _exit(sig); /* reboot */ } /* * Get the security level of the kernel. */ static int getsecuritylevel(void) { #ifdef KERN_SECURELVL int name[2], curlevel; size_t len; name[0] = CTL_KERN; name[1] = KERN_SECURELVL; len = sizeof curlevel; if (sysctl(name, 2, &curlevel, &len, NULL, 0) == -1) { emergency("cannot get kernel security level: %m"); return (-1); } return (curlevel); #else return (-1); #endif } /* * Set the security level of the kernel. */ static void setsecuritylevel(int newlevel) { #ifdef KERN_SECURELVL int name[2], curlevel; curlevel = getsecuritylevel(); if (newlevel == curlevel) return; name[0] = CTL_KERN; name[1] = KERN_SECURELVL; if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) { emergency( "cannot change kernel security level from %d to %d: %m", curlevel, newlevel); return; } #ifdef SECURE warning("kernel security level changed from %d to %d", curlevel, newlevel); #endif #endif } /* * Change states in the finite state machine. * The initial state is passed as an argument. */ static void transition(state_t s) { current_state = s; for (;;) current_state = (state_t) (*current_state)(); } /* * Start a session and allocate a controlling terminal. * Only called by children of init after forking. */ static void open_console(void) { int fd; /* * Try to open /dev/console. Open the device with O_NONBLOCK to * prevent potential blocking on a carrier. */ revoke(_PATH_CONSOLE); if ((fd = open(_PATH_CONSOLE, O_RDWR | O_NONBLOCK)) != -1) { (void)fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) & ~O_NONBLOCK); if (login_tty(fd) == 0) return; close(fd); } /* No luck. Log output to file if possible. */ if ((fd = open(_PATH_DEVNULL, O_RDWR)) == -1) { stall("cannot open null device."); _exit(1); } if (fd != STDIN_FILENO) { dup2(fd, STDIN_FILENO); close(fd); } fd = open(_PATH_INITLOG, O_WRONLY | O_APPEND | O_CREAT, 0644); if (fd == -1) dup2(STDIN_FILENO, STDOUT_FILENO); else if (fd != STDOUT_FILENO) { dup2(fd, STDOUT_FILENO); close(fd); } dup2(STDOUT_FILENO, STDERR_FILENO); } static const char * get_shell(void) { static char kenv_value[PATH_MAX]; if (kenv(KENV_GET, "init_shell", kenv_value, sizeof(kenv_value)) > 0) return kenv_value; else return _PATH_BSHELL; } static void write_stderr(const char *message) { write(STDERR_FILENO, message, strlen(message)); } static int read_file(const char *path, void **bufp, size_t *bufsizep) { struct stat sb; size_t bufsize; void *buf; ssize_t nbytes; int error, fd; fd = open(path, O_RDONLY); if (fd < 0) { emergency("%s: %m", path); return (-1); } error = fstat(fd, &sb); if (error != 0) { emergency("fstat: %m"); close(fd); return (error); } bufsize = sb.st_size; buf = malloc(bufsize); if (buf == NULL) { emergency("malloc: %m"); close(fd); return (error); } nbytes = read(fd, buf, bufsize); if (nbytes != (ssize_t)bufsize) { emergency("read: %m"); close(fd); free(buf); return (error); } error = close(fd); if (error != 0) { emergency("close: %m"); free(buf); return (error); } *bufp = buf; *bufsizep = bufsize; return (0); } static int create_file(const char *path, const void *buf, size_t bufsize) { ssize_t nbytes; int error, fd; fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0700); if (fd < 0) { emergency("%s: %m", path); return (-1); } nbytes = write(fd, buf, bufsize); if (nbytes != (ssize_t)bufsize) { emergency("write: %m"); close(fd); return (-1); } error = close(fd); if (error != 0) { emergency("close: %m"); return (-1); } return (0); } static int mount_tmpfs(const char *fspath) { struct iovec *iov; char errmsg[255]; int error, iovlen; iov = NULL; iovlen = 0; memset(errmsg, 0, sizeof(errmsg)); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "tmpfs"), (size_t)-1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); error = nmount(iov, iovlen, 0); if (error != 0) { if (*errmsg != '\0') { emergency("cannot mount tmpfs on %s: %s: %m", fspath, errmsg); } else { emergency("cannot mount tmpfs on %s: %m", fspath); } return (error); } return (0); } static state_func_t reroot(void) { void *buf; size_t bufsize; int error; buf = NULL; bufsize = 0; revoke_ttys(); runshutdown(); /* * Make sure nobody can interfere with our scheme. * Ignore ESRCH, which can apparently happen when * there are no processes to kill. */ error = kill(-1, SIGKILL); if (error != 0 && errno != ESRCH) { emergency("kill(2) failed: %m"); goto out; } /* * Copy the init binary into tmpfs, so that we can unmount * the old rootfs without committing suicide. */ error = read_file(init_path_argv0, &buf, &bufsize); if (error != 0) goto out; error = mount_tmpfs(_PATH_REROOT); if (error != 0) goto out; error = create_file(_PATH_REROOT_INIT, buf, bufsize); if (error != 0) goto out; /* * Execute the temporary init. */ execl(_PATH_REROOT_INIT, _PATH_REROOT_INIT, "-r", NULL); emergency("cannot exec %s: %m", _PATH_REROOT_INIT); out: emergency("reroot failed; going to single user mode"); free(buf); return (state_func_t) single_user; } static state_func_t reroot_phase_two(void) { char init_path[PATH_MAX], *path, *path_component; size_t init_path_len; int nbytes, error; /* * Ask the kernel to mount the new rootfs. */ error = reboot(RB_REROOT); if (error != 0) { emergency("RB_REBOOT failed: %m"); goto out; } /* * Figure out where the destination init(8) binary is. Note that * the path could be different than what we've started with. Use * the value from kenv, if set, or the one from sysctl otherwise. * The latter defaults to a hardcoded value, but can be overridden * by a build time option. */ nbytes = kenv(KENV_GET, "init_path", init_path, sizeof(init_path)); if (nbytes <= 0) { init_path_len = sizeof(init_path); error = sysctlbyname("kern.init_path", init_path, &init_path_len, NULL, 0); if (error != 0) { emergency("failed to retrieve kern.init_path: %m"); goto out; } } /* * Repeat the init search logic from sys/kern/init_path.c */ path_component = init_path; while ((path = strsep(&path_component, ":")) != NULL) { /* * Execute init(8) from the new rootfs. */ execl(path, path, NULL); } emergency("cannot exec init from %s: %m", init_path); out: emergency("reroot failed; going to single user mode"); return (state_func_t) single_user; } /* * Bring the system up single user. */ static state_func_t single_user(void) { pid_t pid, wpid; int status; sigset_t mask; const char *shell; char *argv[2]; struct timeval tv, tn; #ifdef SECURE struct ttyent *typ; struct passwd *pp; static const char banner[] = "Enter root password, or ^D to go multi-user\n"; char *clear, *password; #endif #ifdef DEBUGSHELL char altshell[128]; #endif if (Reboot) { /* Instead of going single user, let's reboot the machine */ BOOTTRACE("shutting down the system"); sync(); /* Run scripts after all processes have been terminated. */ runfinal(); if (reboot(howto) == -1) { emergency("reboot(%#x) failed, %m", howto); _exit(1); /* panic and reboot */ } warning("reboot(%#x) returned", howto); _exit(0); /* panic as well */ } BOOTTRACE("going to single user mode"); shell = get_shell(); if ((pid = fork()) == 0) { /* * Start the single user session. */ open_console(); #ifdef SECURE /* * Check the root password. * We don't care if the console is 'on' by default; * it's the only tty that can be 'off' and 'secure'. */ typ = getttynam("console"); pp = getpwnam("root"); if (typ && (typ->ty_status & TTY_SECURE) == 0 && pp && *pp->pw_passwd) { write_stderr(banner); for (;;) { clear = getpass("Password:"); if (clear == NULL || *clear == '\0') _exit(0); password = crypt(clear, pp->pw_passwd); explicit_bzero(clear, _PASSWORD_LEN); if (password != NULL && strcmp(password, pp->pw_passwd) == 0) break; warning("single-user login failed\n"); } } endttyent(); endpwent(); #endif /* SECURE */ #ifdef DEBUGSHELL { char *cp = altshell; int num; #define SHREQUEST "Enter full pathname of shell or RETURN for " write_stderr(SHREQUEST); write_stderr(shell); write_stderr(": "); while ((num = read(STDIN_FILENO, cp, 1)) != -1 && num != 0 && *cp != '\n' && cp < &altshell[127]) cp++; *cp = '\0'; if (altshell[0] != '\0') shell = altshell; } #endif /* DEBUGSHELL */ /* * Unblock signals. * We catch all the interesting ones, * and those are reset to SIG_DFL on exec. */ sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); /* * Fire off a shell. * If the default one doesn't work, try the Bourne shell. */ char name[] = "-sh"; argv[0] = name; argv[1] = NULL; execv(shell, argv); emergency("can't exec %s for single user: %m", shell); execv(_PATH_BSHELL, argv); emergency("can't exec %s for single user: %m", _PATH_BSHELL); sleep(STALL_TIMEOUT); _exit(1); } if (pid == -1) { /* * We are seriously hosed. Do our best. */ emergency("can't fork single-user shell, trying again"); while (waitpid(-1, (int *) 0, WNOHANG) > 0) continue; return (state_func_t) single_user; } requested_transition = 0; do { if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) collect_child(wpid); if (wpid == -1) { if (errno == EINTR) continue; warning("wait for single-user shell failed: %m; restarting"); return (state_func_t) single_user; } if (wpid == pid && WIFSTOPPED(status)) { warning("init: shell stopped, restarting\n"); kill(pid, SIGCONT); wpid = -1; } } while (wpid != pid && !requested_transition); if (requested_transition) return (state_func_t) requested_transition; if (!WIFEXITED(status)) { if (WTERMSIG(status) == SIGKILL) { /* * reboot(8) killed shell? */ warning("single user shell terminated."); gettimeofday(&tv, NULL); tn = tv; tv.tv_sec += STALL_TIMEOUT; while (tv.tv_sec > tn.tv_sec || (tv.tv_sec == tn.tv_sec && tv.tv_usec > tn.tv_usec)) { sleep(1); gettimeofday(&tn, NULL); } _exit(0); } else { warning("single user shell terminated, restarting"); return (state_func_t) single_user; } } runcom_mode = FASTBOOT; return (state_func_t) runcom; } /* * Run the system startup script. */ static state_func_t runcom(void) { state_func_t next_transition; BOOTTRACE("/etc/rc starting..."); if ((next_transition = run_script(_PATH_RUNCOM)) != NULL) return next_transition; BOOTTRACE("/etc/rc finished"); runcom_mode = AUTOBOOT; /* the default */ return (state_func_t) read_ttys; } static void execute_script(char *argv[]) { struct sigaction sa; char* sh_argv[3 + SCRIPT_ARGV_SIZE]; const char *shell, *script; int error, sh_argv_len, i; bzero(&sa, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_handler = SIG_IGN; sigaction(SIGTSTP, &sa, NULL); sigaction(SIGHUP, &sa, NULL); open_console(); sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL); #ifdef LOGIN_CAP setprocresources(RESOURCE_RC); #endif /* * Try to directly execute the script first. If it * fails, try the old method of passing the script path * to sh(1). Don't complain if it fails because of * the missing execute bit. */ script = argv[0]; error = access(script, X_OK); if (error == 0) { execv(script, argv); warning("can't directly exec %s: %m", script); } else if (errno != EACCES) { warning("can't access %s: %m", script); } shell = get_shell(); sh_argv[0] = __DECONST(char*, shell); sh_argv_len = 1; #ifdef SECURE if (strcmp(shell, _PATH_BSHELL) == 0) { sh_argv[1] = __DECONST(char*, "-o"); sh_argv[2] = __DECONST(char*, "verify"); sh_argv_len = 3; } #endif for (i = 0; i != SCRIPT_ARGV_SIZE; ++i) sh_argv[i + sh_argv_len] = argv[i]; execv(shell, sh_argv); stall("can't exec %s for %s: %m", shell, script); } /* * Execute binary, replacing init(8) as PID 1. */ static void replace_init(char *path) { char *argv[SCRIPT_ARGV_SIZE]; argv[0] = path; argv[1] = NULL; execute_script(argv); } /* * Run a shell script. * Returns 0 on success, otherwise the next transition to enter: * - single_user if fork/execv/waitpid failed, or if the script * terminated with a signal or exit code != 0. * - death_single if a SIGTERM was delivered to init(8). */ static state_func_t run_script(const char *script) { pid_t pid, wpid; int status; char *argv[SCRIPT_ARGV_SIZE]; const char *shell; shell = get_shell(); if ((pid = fork()) == 0) { char _autoboot[] = "autoboot"; argv[0] = __DECONST(char *, script); argv[1] = runcom_mode == AUTOBOOT ? _autoboot : NULL; argv[2] = NULL; execute_script(argv); sleep(STALL_TIMEOUT); _exit(1); /* force single user mode */ } if (pid == -1) { emergency("can't fork for %s on %s: %m", shell, script); while (waitpid(-1, (int *) 0, WNOHANG) > 0) continue; sleep(STALL_TIMEOUT); return (state_func_t) single_user; } /* * Copied from single_user(). This is a bit paranoid. */ requested_transition = 0; do { if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) collect_child(wpid); if (requested_transition == death_single || requested_transition == reroot) return (state_func_t) requested_transition; if (wpid == -1) { if (errno == EINTR) continue; warning("wait for %s on %s failed: %m; going to " "single user mode", shell, script); return (state_func_t) single_user; } if (wpid == pid && WIFSTOPPED(status)) { warning("init: %s on %s stopped, restarting\n", shell, script); kill(pid, SIGCONT); wpid = -1; } } while (wpid != pid); if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM && requested_transition == catatonia) { /* /etc/rc executed /sbin/reboot; wait for the end quietly */ sigset_t s; sigfillset(&s); for (;;) sigsuspend(&s); } if (!WIFEXITED(status)) { warning("%s on %s terminated abnormally, going to single " "user mode", shell, script); return (state_func_t) single_user; } if (WEXITSTATUS(status)) return (state_func_t) single_user; return (state_func_t) 0; } /* * Open the session database. * * NB: We could pass in the size here; is it necessary? */ static int start_session_db(void) { if (session_db && (*session_db->close)(session_db)) emergency("session database close: %m"); if ((session_db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == NULL) { emergency("session database open: %m"); return (1); } return (0); } /* * Add a new login session. */ static void add_session(session_t *sp) { DBT key; DBT data; key.data = &sp->se_process; key.size = sizeof sp->se_process; data.data = &sp; data.size = sizeof sp; if ((*session_db->put)(session_db, &key, &data, 0)) emergency("insert %d: %m", sp->se_process); } /* * Delete an old login session. */ static void del_session(session_t *sp) { DBT key; key.data = &sp->se_process; key.size = sizeof sp->se_process; if ((*session_db->del)(session_db, &key, 0)) emergency("delete %d: %m", sp->se_process); } /* * Look up a login session by pid. */ static session_t * find_session(pid_t pid) { DBT key; DBT data; session_t *ret; key.data = &pid; key.size = sizeof pid; if ((*session_db->get)(session_db, &key, &data, 0) != 0) return 0; bcopy(data.data, (char *)&ret, sizeof(ret)); return ret; } /* * Construct an argument vector from a command line. */ static char ** construct_argv(char *command) { int argc = 0; char **argv = (char **) malloc(((strlen(command) + 1) / 2 + 1) * sizeof (char *)); if ((argv[argc++] = strk(command)) == NULL) { free(argv); return (NULL); } while ((argv[argc++] = strk((char *) 0)) != NULL) continue; return argv; } /* * Deallocate a session descriptor. */ static void free_session(session_t *sp) { free(sp->se_device); if (sp->se_getty) { free(sp->se_getty); free(sp->se_getty_argv_space); free(sp->se_getty_argv); } if (sp->se_window) { free(sp->se_window); free(sp->se_window_argv_space); free(sp->se_window_argv); } if (sp->se_type) free(sp->se_type); free(sp); } /* * Allocate a new session descriptor. * Mark it SE_PRESENT. */ static session_t * new_session(session_t *sprev, struct ttyent *typ) { session_t *sp; if ((typ->ty_status & TTY_ON) == 0 || typ->ty_name == 0 || typ->ty_getty == 0) return 0; sp = (session_t *) calloc(1, sizeof (session_t)); sp->se_flags |= SE_PRESENT; if ((typ->ty_status & TTY_IFEXISTS) != 0) sp->se_flags |= SE_IFEXISTS; if ((typ->ty_status & TTY_IFCONSOLE) != 0) sp->se_flags |= SE_IFCONSOLE; if (asprintf(&sp->se_device, "%s%s", _PATH_DEV, typ->ty_name) < 0) err(1, "asprintf"); if (setupargv(sp, typ) == 0) { free_session(sp); return (0); } sp->se_next = 0; if (sprev == NULL) { sessions = sp; sp->se_prev = 0; } else { sprev->se_next = sp; sp->se_prev = sprev; } return sp; } /* * Calculate getty and if useful window argv vectors. */ static int setupargv(session_t *sp, struct ttyent *typ) { if (sp->se_getty) { free(sp->se_getty); free(sp->se_getty_argv_space); free(sp->se_getty_argv); } if (asprintf(&sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name) < 0) err(1, "asprintf"); sp->se_getty_argv_space = strdup(sp->se_getty); sp->se_getty_argv = construct_argv(sp->se_getty_argv_space); if (sp->se_getty_argv == NULL) { warning("can't parse getty for port %s", sp->se_device); free(sp->se_getty); free(sp->se_getty_argv_space); sp->se_getty = sp->se_getty_argv_space = 0; return (0); } if (sp->se_window) { free(sp->se_window); free(sp->se_window_argv_space); free(sp->se_window_argv); } sp->se_window = sp->se_window_argv_space = 0; sp->se_window_argv = 0; if (typ->ty_window) { sp->se_window = strdup(typ->ty_window); sp->se_window_argv_space = strdup(sp->se_window); sp->se_window_argv = construct_argv(sp->se_window_argv_space); if (sp->se_window_argv == NULL) { warning("can't parse window for port %s", sp->se_device); free(sp->se_window_argv_space); free(sp->se_window); sp->se_window = sp->se_window_argv_space = 0; return (0); } } if (sp->se_type) free(sp->se_type); sp->se_type = typ->ty_type ? strdup(typ->ty_type) : 0; return (1); } /* * Walk the list of ttys and create sessions for each active line. */ static state_func_t read_ttys(void) { session_t *sp, *snext; struct ttyent *typ; /* * Destroy any previous session state. * There shouldn't be any, but just in case... */ for (sp = sessions; sp; sp = snext) { snext = sp->se_next; free_session(sp); } sessions = 0; if (start_session_db()) return (state_func_t) single_user; /* * Allocate a session entry for each active port. * Note that sp starts at 0. */ while ((typ = getttyent()) != NULL) if ((snext = new_session(sp, typ)) != NULL) sp = snext; endttyent(); return (state_func_t) multi_user; } /* * Start a window system running. */ static void start_window_system(session_t *sp) { pid_t pid; sigset_t mask; char term[64], *env[2]; int status; if ((pid = fork()) == -1) { emergency("can't fork for window system on port %s: %m", sp->se_device); /* hope that getty fails and we can try again */ return; } if (pid) { waitpid(-1, &status, 0); return; } /* reparent window process to the init to not make a zombie on exit */ if ((pid = fork()) == -1) { emergency("can't fork for window system on port %s: %m", sp->se_device); _exit(1); } if (pid) _exit(0); sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); if (setsid() < 0) emergency("setsid failed (window) %m"); #ifdef LOGIN_CAP setprocresources(RESOURCE_WINDOW); #endif if (sp->se_type) { /* Don't use malloc after fork */ strcpy(term, "TERM="); strlcat(term, sp->se_type, sizeof(term)); env[0] = term; env[1] = NULL; } else env[0] = NULL; execve(sp->se_window_argv[0], sp->se_window_argv, env); stall("can't exec window system '%s' for port %s: %m", sp->se_window_argv[0], sp->se_device); _exit(1); } /* * Start a login session running. */ static pid_t start_getty(session_t *sp) { pid_t pid; sigset_t mask; time_t current_time = time((time_t *) 0); int too_quick = 0; char term[64], *env[2]; if (current_time >= sp->se_started && current_time - sp->se_started < GETTY_SPACING) { if (++sp->se_nspace > GETTY_NSPACE) { sp->se_nspace = 0; too_quick = 1; } } else sp->se_nspace = 0; /* * fork(), not vfork() -- we can't afford to block. */ if ((pid = fork()) == -1) { emergency("can't fork for getty on port %s: %m", sp->se_device); return -1; } if (pid) return pid; if (too_quick) { warning("getty repeating too quickly on port %s, sleeping %d secs", sp->se_device, GETTY_SLEEP); sleep((unsigned) GETTY_SLEEP); } if (sp->se_window) { start_window_system(sp); sleep(WINDOW_WAIT); } sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); #ifdef LOGIN_CAP setprocresources(RESOURCE_GETTY); #endif if (sp->se_type) { /* Don't use malloc after fork */ strcpy(term, "TERM="); strlcat(term, sp->se_type, sizeof(term)); env[0] = term; env[1] = NULL; } else env[0] = NULL; execve(sp->se_getty_argv[0], sp->se_getty_argv, env); stall("can't exec getty '%s' for port %s: %m", sp->se_getty_argv[0], sp->se_device); _exit(1); } /* * Return 1 if the session is defined as "onifexists" * or "onifconsole" and the device node does not exist. */ static int session_has_no_tty(session_t *sp) { int fd; if ((sp->se_flags & SE_IFEXISTS) == 0 && (sp->se_flags & SE_IFCONSOLE) == 0) return (0); fd = open(sp->se_device, O_RDONLY | O_NONBLOCK, 0); if (fd < 0) { if (errno == ENOENT) return (1); return (0); } close(fd); return (0); } /* * Collect exit status for a child. * If an exiting login, start a new login running. */ static void collect_child(pid_t pid) { session_t *sp, *sprev, *snext; if (! sessions) return; if (! (sp = find_session(pid))) return; del_session(sp); sp->se_process = 0; if (sp->se_flags & SE_SHUTDOWN || session_has_no_tty(sp)) { if ((sprev = sp->se_prev) != NULL) sprev->se_next = sp->se_next; else sessions = sp->se_next; if ((snext = sp->se_next) != NULL) snext->se_prev = sp->se_prev; free_session(sp); return; } if ((pid = start_getty(sp)) == -1) { /* serious trouble */ requested_transition = clean_ttys; return; } sp->se_process = pid; sp->se_started = time((time_t *) 0); add_session(sp); } static const char * get_current_state(void) { if (current_state == single_user) return ("single-user"); if (current_state == runcom) return ("runcom"); if (current_state == read_ttys) return ("read-ttys"); if (current_state == multi_user) return ("multi-user"); if (current_state == clean_ttys) return ("clean-ttys"); if (current_state == catatonia) return ("catatonia"); if (current_state == death) return ("death"); if (current_state == death_single) return ("death-single"); return ("unknown"); } static void boottrace_transition(int sig) { const char *action; switch (sig) { case SIGUSR2: action = "halt & poweroff"; break; case SIGUSR1: action = "halt"; break; case SIGINT: action = "reboot"; break; case SIGWINCH: action = "powercycle"; break; case SIGTERM: action = Reboot ? "reboot" : "single-user"; break; default: BOOTTRACE("signal %d from %s", sig, get_current_state()); return; } /* Trace the shutdown reason. */ SHUTTRACE("%s from %s", action, get_current_state()); } /* * Catch a signal and request a state transition. */ static void transition_handler(int sig) { boottrace_transition(sig); switch (sig) { case SIGHUP: if (current_state == read_ttys || current_state == multi_user || current_state == clean_ttys || current_state == catatonia) requested_transition = clean_ttys; break; case SIGUSR2: howto = RB_POWEROFF; case SIGUSR1: howto |= RB_HALT; case SIGWINCH: case SIGINT: if (sig == SIGWINCH) howto |= RB_POWERCYCLE; Reboot = true; case SIGTERM: if (current_state == read_ttys || current_state == multi_user || current_state == clean_ttys || current_state == catatonia) requested_transition = death; else requested_transition = death_single; break; case SIGTSTP: if (current_state == runcom || current_state == read_ttys || current_state == clean_ttys || current_state == multi_user || current_state == catatonia) requested_transition = catatonia; break; case SIGEMT: requested_transition = reroot; break; default: requested_transition = 0; break; } } /* * Take the system multiuser. */ static state_func_t multi_user(void) { static bool inmultiuser = false; pid_t pid; session_t *sp; requested_transition = 0; /* * If the administrator has not set the security level to -1 * to indicate that the kernel should not run multiuser in secure * mode, and the run script has not set a higher level of security * than level 1, then put the kernel into secure mode. */ if (getsecuritylevel() == 0) setsecuritylevel(1); for (sp = sessions; sp; sp = sp->se_next) { if (sp->se_process) continue; if (session_has_no_tty(sp)) continue; if ((pid = start_getty(sp)) == -1) { /* serious trouble */ requested_transition = clean_ttys; break; } sp->se_process = pid; sp->se_started = time((time_t *) 0); add_session(sp); } if (requested_transition == 0 && !inmultiuser) { inmultiuser = true; /* This marks the change from boot-time tracing to run-time. */ RUNTRACE("multi-user start"); } while (!requested_transition) if ((pid = waitpid(-1, (int *) 0, 0)) != -1) collect_child(pid); return (state_func_t) requested_transition; } /* * This is an (n*2)+(n^2) algorithm. We hope it isn't run often... */ static state_func_t clean_ttys(void) { session_t *sp, *sprev; struct ttyent *typ; int devlen; char *old_getty, *old_window, *old_type; /* * mark all sessions for death, (!SE_PRESENT) * as we find or create new ones they'll be marked as keepers, * we'll later nuke all the ones not found in /etc/ttys */ for (sp = sessions; sp != NULL; sp = sp->se_next) sp->se_flags &= ~SE_PRESENT; devlen = sizeof(_PATH_DEV) - 1; while ((typ = getttyent()) != NULL) { for (sprev = 0, sp = sessions; sp; sprev = sp, sp = sp->se_next) if (strcmp(typ->ty_name, sp->se_device + devlen) == 0) break; if (sp) { /* we want this one to live */ sp->se_flags |= SE_PRESENT; if ((typ->ty_status & TTY_ON) == 0 || typ->ty_getty == 0) { sp->se_flags |= SE_SHUTDOWN; kill(sp->se_process, SIGHUP); continue; } sp->se_flags &= ~SE_SHUTDOWN; old_getty = sp->se_getty ? strdup(sp->se_getty) : 0; old_window = sp->se_window ? strdup(sp->se_window) : 0; old_type = sp->se_type ? strdup(sp->se_type) : 0; if (setupargv(sp, typ) == 0) { warning("can't parse getty for port %s", sp->se_device); sp->se_flags |= SE_SHUTDOWN; kill(sp->se_process, SIGHUP); } else if ( !old_getty || (!old_type && sp->se_type) || (old_type && !sp->se_type) || (!old_window && sp->se_window) || (old_window && !sp->se_window) || (strcmp(old_getty, sp->se_getty) != 0) || (old_window && strcmp(old_window, sp->se_window) != 0) || (old_type && strcmp(old_type, sp->se_type) != 0) ) { /* Don't set SE_SHUTDOWN here */ sp->se_nspace = 0; sp->se_started = 0; kill(sp->se_process, SIGHUP); } if (old_getty) free(old_getty); if (old_window) free(old_window); if (old_type) free(old_type); continue; } new_session(sprev, typ); } endttyent(); /* * sweep through and kill all deleted sessions * ones who's /etc/ttys line was deleted (SE_PRESENT unset) */ for (sp = sessions; sp != NULL; sp = sp->se_next) { if ((sp->se_flags & SE_PRESENT) == 0) { sp->se_flags |= SE_SHUTDOWN; kill(sp->se_process, SIGHUP); } } return (state_func_t) multi_user; } /* * Block further logins. */ static state_func_t catatonia(void) { session_t *sp; for (sp = sessions; sp; sp = sp->se_next) sp->se_flags |= SE_SHUTDOWN; return (state_func_t) multi_user; } /* * Note SIGALRM. */ static void alrm_handler(int sig) { (void)sig; clang = true; } /* * Bring the system down to single user. */ static state_func_t death(void) { int block, blocked; size_t len; /* Temporarily block suspend. */ len = sizeof(blocked); block = 1; if (sysctlbyname("kern.suspend_blocked", &blocked, &len, &block, sizeof(block)) == -1) blocked = 0; /* * Also revoke the TTY here. Because runshutdown() may reopen * the TTY whose getty we're killing here, there is no guarantee * runshutdown() will perform the initial open() call, causing * the terminal attributes to be misconfigured. */ revoke_ttys(); /* Try to run the rc.shutdown script within a period of time */ runshutdown(); /* Unblock suspend if we blocked it. */ if (!blocked) sysctlbyname("kern.suspend_blocked", NULL, NULL, &blocked, sizeof(blocked)); return (state_func_t) death_single; } /* * Do what is necessary to reinitialize single user mode or reboot * from an incomplete state. */ static state_func_t death_single(void) { int i; pid_t pid; static const int death_sigs[2] = { SIGTERM, SIGKILL }; revoke(_PATH_CONSOLE); BOOTTRACE("start killing user processes"); for (i = 0; i < 2; ++i) { if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH) return (state_func_t) single_user; clang = false; alarm(DEATH_WATCH); do if ((pid = waitpid(-1, (int *)0, 0)) != -1) collect_child(pid); while (!clang && errno != ECHILD); if (errno == ECHILD) return (state_func_t) single_user; } warning("some processes would not die; ps axl advised"); return (state_func_t) single_user; } static void revoke_ttys(void) { session_t *sp; for (sp = sessions; sp; sp = sp->se_next) { sp->se_flags |= SE_SHUTDOWN; kill(sp->se_process, SIGHUP); revoke(sp->se_device); } } /* * Run the system shutdown script. * * Exit codes: XXX I should document more * -2 shutdown script terminated abnormally * -1 fatal error - can't run script * 0 good. * >0 some error (exit code) */ static int runshutdown(void) { pid_t pid, wpid; int status; int shutdowntimeout; size_t len; char *argv[SCRIPT_ARGV_SIZE]; struct stat sb; BOOTTRACE("init(8): start rc.shutdown"); /* * rc.shutdown is optional, so to prevent any unnecessary * complaints from the shell we simply don't run it if the * file does not exist. If the stat() here fails for other * reasons, we'll let the shell complain. */ if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT) return 0; if ((pid = fork()) == 0) { char _reboot[] = "reboot"; char _single[] = "single"; char _path_rundown[] = _PATH_RUNDOWN; argv[0] = _path_rundown; argv[1] = Reboot ? _reboot : _single; argv[2] = NULL; execute_script(argv); _exit(1); /* force single user mode */ } if (pid == -1) { emergency("can't fork for %s: %m", _PATH_RUNDOWN); while (waitpid(-1, (int *) 0, WNOHANG) > 0) continue; sleep(STALL_TIMEOUT); return -1; } len = sizeof(shutdowntimeout); if (sysctlbyname("kern.init_shutdown_timeout", &shutdowntimeout, &len, NULL, 0) == -1 || shutdowntimeout < 2) shutdowntimeout = DEATH_SCRIPT; alarm(shutdowntimeout); clang = false; /* * Copied from single_user(). This is a bit paranoid. * Use the same ALRM handler. */ do { if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1) collect_child(wpid); if (clang) { /* we were waiting for the sub-shell */ kill(wpid, SIGTERM); warning("timeout expired for %s: %m; going to " "single user mode", _PATH_RUNDOWN); BOOTTRACE("rc.shutdown's %d sec timeout expired", shutdowntimeout); return -1; } if (wpid == -1) { if (errno == EINTR) continue; warning("wait for %s failed: %m; going to " "single user mode", _PATH_RUNDOWN); return -1; } if (wpid == pid && WIFSTOPPED(status)) { warning("init: %s stopped, restarting\n", _PATH_RUNDOWN); kill(pid, SIGCONT); wpid = -1; } } while (wpid != pid && !clang); /* Turn off the alarm */ alarm(0); if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM && requested_transition == catatonia) { /* * /etc/rc.shutdown executed /sbin/reboot; * wait for the end quietly */ sigset_t s; sigfillset(&s); for (;;) sigsuspend(&s); } if (!WIFEXITED(status)) { warning("%s terminated abnormally, going to " "single user mode", _PATH_RUNDOWN); return -2; } if ((status = WEXITSTATUS(status)) != 0) warning("%s returned status %d", _PATH_RUNDOWN, status); return status; } static char * strk(char *p) { static char *t; char *q; int c; if (p) t = p; if (!t) return 0; c = *t; while (c == ' ' || c == '\t' ) c = *++t; if (!c) { t = 0; return 0; } q = t; if (c == '\'') { c = *++t; q = t; while (c && c != '\'') c = *++t; if (!c) /* unterminated string */ q = t = 0; else *t++ = 0; } else { while (c && c != ' ' && c != '\t' ) c = *++t; *t++ = 0; if (!c) t = 0; } return q; } #ifdef LOGIN_CAP static void setprocresources(const char *cname) { login_cap_t *lc; if ((lc = login_getclassbyname(cname, NULL)) != NULL) { setusercontext(lc, (struct passwd*)NULL, 0, LOGIN_SETENV | LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETLOGINCLASS | LOGIN_SETCPUMASK); login_close(lc); } } #endif /* * Run /etc/rc.final to execute scripts after all user processes have been * terminated. */ static void runfinal(void) { struct stat sb; pid_t other_pid, pid; sigset_t mask; /* Avoid any surprises. */ alarm(0); /* rc.final is optional. */ if (stat(_PATH_RUNFINAL, &sb) == -1 && errno == ENOENT) return; if (access(_PATH_RUNFINAL, X_OK) != 0) { warning("%s exists, but not executable", _PATH_RUNFINAL); return; } pid = fork(); if (pid == 0) { /* * Reopen stdin/stdout/stderr so that scripts can write to * console. */ close(0); open(_PATH_DEVNULL, O_RDONLY); close(1); close(2); open_console(); dup2(1, 2); sigemptyset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); signal(SIGCHLD, SIG_DFL); execl(_PATH_RUNFINAL, _PATH_RUNFINAL, NULL); perror("execl(" _PATH_RUNFINAL ") failed"); exit(1); } /* Wait for rc.final script to exit */ while ((other_pid = waitpid(-1, NULL, 0)) != pid && other_pid > 0) { continue; } } diff --git a/sbin/mount/mount.c b/sbin/mount/mount.c index cf603a63e394..03ae69272e1a 100644 --- a/sbin/mount/mount.c +++ b/sbin/mount/mount.c @@ -1,975 +1,975 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1980, 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #define _WANT_MNTOPTNAMES #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include "extern.h" -#include "mntopts.h" #include "pathnames.h" #define EXIT(a) { \ xo_close_container("mount"); \ if (xo_finish() < 0) \ xo_err(EXIT_FAILURE, "stdout"); \ exit(a); \ } /* `meta' options */ #define MOUNT_META_OPTION_FSTAB "fstab" #define MOUNT_META_OPTION_CURRENT "current" static int debug, fstab_style, verbose; struct cpa { char **a; ssize_t sz; int c; }; char *catopt(char *, const char *); int hasopt(const char *, const char *); int ismounted(struct fstab *, struct statfs *, int); int isremountable(const char *); int allow_file_mount(const char *); void mangle(char *, struct cpa *); char *update_options(char *, char *, int); int mountfs(const char *, const char *, const char *, int, const char *, const char *); void remopt(char *, const char *); void prmount(struct statfs *); void putfsent(struct statfs *); void usage(void); char *flags2opts(int); /* Map from mount options to printable formats. */ static struct mntoptnames optnames[] = { MNTOPT_NAMES }; /* * List of VFS types that can be remounted without becoming mounted on top * of each other. * XXX Is this list correct? */ static const char * remountable_fs_names[] = { "ufs", "ffs", "ext2fs", 0 }; static const char userquotaeq[] = "userquota="; static const char groupquotaeq[] = "groupquota="; static char *mountprog = NULL; static int use_mountprog(const char *vfstype) { /* XXX: We need to get away from implementing external mount * programs for every filesystem, and move towards having * each filesystem properly implement the nmount() system call. */ unsigned int i; const char *fs[] = { "cd9660", "mfs", "msdosfs", "nfs", "nullfs", "smbfs", "udf", "unionfs", NULL }; if (mountprog != NULL) return (1); for (i = 0; fs[i] != NULL; ++i) { if (strcmp(vfstype, fs[i]) == 0) return (1); } return (0); } static int exec_mountprog(const char *name, const char *execname, char *const argv[]) { pid_t pid; int status; switch (pid = fork()) { case -1: /* Error. */ xo_warn("fork"); EXIT(EXIT_FAILURE); case 0: /* Child. */ /* Go find an executable. */ execvP(execname, _PATH_SYSPATH, argv); if (errno == ENOENT) { xo_warn("exec %s not found", execname); if (execname[0] != '/') { xo_warnx("in path: %s", _PATH_SYSPATH); } } EXIT(EXIT_FAILURE); default: /* Parent. */ if (waitpid(pid, &status, 0) < 0) { xo_warn("waitpid"); return (1); } if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) return (WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { xo_warnx("%s: %s", name, sys_siglist[WTERMSIG(status)]); return (1); } break; } return (0); } static int specified_ro(const char *arg) { char *optbuf, *opt; int ret = 0; optbuf = strdup(arg); if (optbuf == NULL) xo_err(1, "strdup failed"); for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) { if (strcmp(opt, "ro") == 0) { ret = 1; break; } } free(optbuf); return (ret); } static void restart_mountd(void) { pidfile_signal(_PATH_MOUNTDPID, SIGHUP, NULL); } int main(int argc, char *argv[]) { const char *mntfromname, **vfslist, *vfstype; struct fstab *fs; struct statfs *mntbuf; int all, ch, i, init_flags, late, failok, mntsize, rval, have_fstab, ro; int onlylate; char *cp, *ep, *options; all = init_flags = late = onlylate = 0; ro = 0; options = NULL; vfslist = NULL; vfstype = "ufs"; argc = xo_parse_args(argc, argv); if (argc < 0) exit(EXIT_FAILURE); xo_open_container("mount"); while ((ch = getopt(argc, argv, "adF:fLlno:prt:uvw")) != -1) switch (ch) { case 'a': all = 1; break; case 'd': debug = 1; break; case 'F': setfstab(optarg); break; case 'f': init_flags |= MNT_FORCE; break; case 'L': onlylate = 1; late = 1; break; case 'l': late = 1; break; case 'n': /* For compatibility with the Linux version of mount. */ break; case 'o': if (*optarg) { options = catopt(options, optarg); if (specified_ro(optarg)) ro = 1; } break; case 'p': fstab_style = 1; verbose = 1; break; case 'r': options = catopt(options, "ro"); ro = 1; break; case 't': if (vfslist != NULL) xo_errx(1, "only one -t option may be specified"); vfslist = makevfslist(optarg); vfstype = optarg; break; case 'u': init_flags |= MNT_UPDATE; break; case 'v': verbose = 1; break; case 'w': options = catopt(options, "noro"); break; case '?': default: usage(); /* NOTREACHED */ } argc -= optind; argv += optind; #define BADTYPE(type) \ (strcmp(type, FSTAB_RO) && \ strcmp(type, FSTAB_RW) && strcmp(type, FSTAB_RQ)) if ((init_flags & MNT_UPDATE) && (ro == 0)) options = catopt(options, "noro"); rval = EXIT_SUCCESS; switch (argc) { case 0: if ((mntsize = getmntinfo(&mntbuf, verbose ? MNT_WAIT : MNT_NOWAIT)) == 0) xo_err(1, "getmntinfo"); if (all) { while ((fs = getfsent()) != NULL) { if (BADTYPE(fs->fs_type)) continue; if (checkvfsname(fs->fs_vfstype, vfslist)) continue; if (hasopt(fs->fs_mntops, "noauto")) continue; if (!hasopt(fs->fs_mntops, "late") && onlylate) continue; if (hasopt(fs->fs_mntops, "late") && !late) continue; if (hasopt(fs->fs_mntops, "failok")) failok = 1; else failok = 0; if (!(init_flags & MNT_UPDATE) && !hasopt(fs->fs_mntops, "update") && ismounted(fs, mntbuf, mntsize)) continue; options = update_options(options, fs->fs_mntops, mntbuf->f_flags); if (mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file, init_flags, options, fs->fs_mntops) && !failok) rval = EXIT_FAILURE; } } else if (fstab_style) { xo_open_list("fstab"); for (i = 0; i < mntsize; i++) { if (checkvfsname(mntbuf[i].f_fstypename, vfslist)) continue; xo_open_instance("fstab"); putfsent(&mntbuf[i]); xo_close_instance("fstab"); } xo_close_list("fstab"); } else { xo_open_list("mounted"); for (i = 0; i < mntsize; i++) { if (checkvfsname(mntbuf[i].f_fstypename, vfslist)) continue; if (!verbose && (mntbuf[i].f_flags & MNT_IGNORE) != 0) continue; xo_open_instance("mounted"); prmount(&mntbuf[i]); xo_close_instance("mounted"); } xo_close_list("mounted"); } EXIT(rval); case 1: if (vfslist != NULL) usage(); rmslashes(*argv, *argv); if (init_flags & MNT_UPDATE) { mntfromname = NULL; have_fstab = 0; if ((mntbuf = getmntpoint(*argv)) == NULL) xo_errx(1, "not currently mounted %s", *argv); /* * Only get the mntflags from fstab if both mntpoint * and mntspec are identical. Also handle the special * case where just '/' is mounted and 'spec' is not * identical with the one from fstab ('/dev' is missing * in the spec-string at boot-time). */ if ((fs = getfsfile(mntbuf->f_mntonname)) != NULL) { if (strcmp(fs->fs_spec, mntbuf->f_mntfromname) == 0 && strcmp(fs->fs_file, mntbuf->f_mntonname) == 0) { have_fstab = 1; mntfromname = mntbuf->f_mntfromname; } else if (argv[0][0] == '/' && argv[0][1] == '\0' && strcmp(fs->fs_vfstype, mntbuf->f_fstypename) == 0) { fs = getfsfile("/"); have_fstab = 1; mntfromname = fs->fs_spec; } } if (have_fstab) { options = update_options(options, fs->fs_mntops, mntbuf->f_flags); } else { mntfromname = mntbuf->f_mntfromname; options = update_options(options, NULL, mntbuf->f_flags); } rval = mountfs(mntbuf->f_fstypename, mntfromname, mntbuf->f_mntonname, init_flags, options, 0); break; } if ((fs = getfsfile(*argv)) == NULL && (fs = getfsspec(*argv)) == NULL) xo_errx(1, "%s: unknown special file or file system", *argv); if (BADTYPE(fs->fs_type)) xo_errx(1, "%s has unknown file system type", *argv); rval = mountfs(fs->fs_vfstype, fs->fs_spec, fs->fs_file, init_flags, options, fs->fs_mntops); break; case 2: /* * If -t flag has not been specified, the path cannot be * found, spec contains either a ':' or a '@', then assume * that an NFS file system is being specified ala Sun. * Check if the hostname contains only allowed characters * to reduce false positives. IPv6 addresses containing * ':' will be correctly parsed only if the separator is '@'. * The definition of a valid hostname is taken from RFC 1034. */ if (vfslist == NULL && ((ep = strchr(argv[0], '@')) != NULL || (ep = strchr(argv[0], ':')) != NULL)) { if (*ep == '@') { cp = ep + 1; ep = cp + strlen(cp); } else cp = argv[0]; while (cp != ep) { if (!isdigit(*cp) && !isalpha(*cp) && *cp != '.' && *cp != '-' && *cp != ':') break; cp++; } if (cp == ep) vfstype = "nfs"; } rval = mountfs(vfstype, argv[0], argv[1], init_flags, options, NULL); break; default: usage(); /* NOTREACHED */ } /* * If the mount was successfully, and done by root, tell mountd the * good news. */ if (rval == EXIT_SUCCESS && getuid() == 0) restart_mountd(); EXIT(rval); } int ismounted(struct fstab *fs, struct statfs *mntbuf, int mntsize) { char realfsfile[PATH_MAX]; int i; if (fs->fs_file[0] == '/' && fs->fs_file[1] == '\0') /* the root file system can always be remounted */ return (0); /* The user may have specified a symlink in fstab, resolve the path */ if (realpath(fs->fs_file, realfsfile) == NULL) { /* Cannot resolve the path, use original one */ strlcpy(realfsfile, fs->fs_file, sizeof(realfsfile)); } /* * Consider the filesystem to be mounted if: * It has the same mountpoint as a mounted filesystem, and * It has the same type as that same mounted filesystem, and * It has the same device name as that same mounted filesystem, OR * It is a nonremountable filesystem */ for (i = mntsize - 1; i >= 0; --i) if (strcmp(realfsfile, mntbuf[i].f_mntonname) == 0 && strcmp(fs->fs_vfstype, mntbuf[i].f_fstypename) == 0 && (!isremountable(fs->fs_vfstype) || (strcmp(fs->fs_spec, mntbuf[i].f_mntfromname) == 0))) return (1); return (0); } int isremountable(const char *vfsname) { const char **cp; for (cp = remountable_fs_names; *cp; cp++) if (strcmp(*cp, vfsname) == 0) return (1); return (0); } int allow_file_mount(const char *vfsname) { if (strcmp(vfsname, "nullfs") == 0) return (1); return (0); } int hasopt(const char *mntopts, const char *option) { int negative, found; char *opt, *optbuf; if (option[0] == 'n' && option[1] == 'o') { negative = 1; option += 2; } else negative = 0; optbuf = strdup(mntopts); found = 0; for (opt = optbuf; (opt = strtok(opt, ",")) != NULL; opt = NULL) { if (opt[0] == 'n' && opt[1] == 'o') { if (!strcasecmp(opt + 2, option)) found = negative; } else if (!strcasecmp(opt, option)) found = !negative; } free(optbuf); return (found); } static void append_arg(struct cpa *sa, char *arg) { if (sa->c + 1 == sa->sz) { sa->sz = sa->sz == 0 ? 8 : sa->sz * 2; sa->a = realloc(sa->a, sizeof(*sa->a) * sa->sz); if (sa->a == NULL) xo_errx(1, "realloc failed"); } sa->a[++sa->c] = arg; } int mountfs(const char *vfstype, const char *spec, const char *name, int flags, const char *options, const char *mntopts) { struct statfs sf; int i, ret; char *optbuf, execname[PATH_MAX], mntpath[PATH_MAX]; static struct cpa mnt_argv; /* resolve the mountpoint with realpath(3) */ if (allow_file_mount(vfstype)) { if (checkpath_allow_file(name, mntpath) != 0) { xo_warn("%s", mntpath); return (1); } } else { if (checkpath(name, mntpath) != 0) { xo_warn("%s", mntpath); return (1); } } name = mntpath; if (mntopts == NULL) mntopts = ""; optbuf = catopt(strdup(mntopts), options); if (strcmp(name, "/") == 0) flags |= MNT_UPDATE; if (flags & MNT_FORCE) optbuf = catopt(optbuf, "force"); if (flags & MNT_RDONLY) optbuf = catopt(optbuf, "ro"); /* * XXX * The mount_mfs (newfs) command uses -o to select the * optimization mode. We don't pass the default "-o rw" * for that reason. */ if (flags & MNT_UPDATE) optbuf = catopt(optbuf, "update"); /* Compatibility glue. */ if (strcmp(vfstype, "msdos") == 0) vfstype = "msdosfs"; /* Construct the name of the appropriate mount command */ (void)snprintf(execname, sizeof(execname), "mount_%s", vfstype); mnt_argv.c = -1; append_arg(&mnt_argv, execname); mangle(optbuf, &mnt_argv); if (mountprog != NULL) strlcpy(execname, mountprog, sizeof(execname)); append_arg(&mnt_argv, strdup(spec)); append_arg(&mnt_argv, strdup(name)); append_arg(&mnt_argv, NULL); if (debug) { if (use_mountprog(vfstype)) xo_emit("{Lwc:exec}{:execname/%s}", execname); else xo_emit("{:execname/mount}{P: }{l:opts/-t}{P: }{l:opts/%s}", vfstype); for (i = 1; i < mnt_argv.c; i++) xo_emit("{P: }{l:opts}", mnt_argv.a[i]); xo_emit("\n"); free(optbuf); free(mountprog); mountprog = NULL; return (0); } if (use_mountprog(vfstype)) { ret = exec_mountprog(name, execname, mnt_argv.a); } else { ret = mount_fs(vfstype, mnt_argv.c, mnt_argv.a); } free(optbuf); free(mountprog); mountprog = NULL; if (verbose) { if (statfs(name, &sf) < 0) { xo_warn("statfs %s", name); return (1); } if (fstab_style) { xo_open_list("fstab"); xo_open_instance("fstab"); putfsent(&sf); xo_close_instance("fstab"); xo_close_list("fstab"); } else { xo_open_list("mounted"); xo_open_instance("mounted"); prmount(&sf); xo_close_instance("mounted"); xo_close_list("mounted"); } } return (ret); } void prmount(struct statfs *sfp) { uint64_t flags; unsigned int i; struct mntoptnames *o; struct passwd *pw; char *fsidbuf; xo_emit("{:special/%hs}{L: on }{:node/%hs}{L: (}{:fstype}", sfp->f_mntfromname, sfp->f_mntonname, sfp->f_fstypename); flags = sfp->f_flags & MNT_VISFLAGMASK; for (o = optnames; flags != 0 && o->o_opt != 0; o++) if (flags & o->o_opt) { xo_emit("{D:, }{l:opts}", o->o_name); flags &= ~o->o_opt; } /* * Inform when file system is mounted by an unprivileged user * or privileged non-root user. */ if ((flags & MNT_USER) != 0 || sfp->f_owner != 0) { xo_emit("{D:, }{L:mounted by }"); if ((pw = getpwuid(sfp->f_owner)) != NULL) xo_emit("{:mounter/%hs}", pw->pw_name); else xo_emit("{:mounter/%hs}", sfp->f_owner); } if (verbose) { if (sfp->f_syncwrites != 0 || sfp->f_asyncwrites != 0) { xo_open_container("writes"); xo_emit("{D:, }{Lwc:writes}{Lw:sync}{w:sync/%ju}{Lw:async}{:async/%ju}", (uintmax_t)sfp->f_syncwrites, (uintmax_t)sfp->f_asyncwrites); xo_close_container("writes"); } if (sfp->f_syncreads != 0 || sfp->f_asyncreads != 0) { xo_open_container("reads"); xo_emit("{D:, }{Lwc:reads}{Lw:sync}{w:sync/%ju}{Lw:async}{:async/%ju}", (uintmax_t)sfp->f_syncreads, (uintmax_t)sfp->f_asyncreads); xo_close_container("reads"); } if (sfp->f_fsid.val[0] != 0 || sfp->f_fsid.val[1] != 0) { fsidbuf = malloc(sizeof(sfp->f_fsid) * 2 + 1); if (fsidbuf == NULL) xo_errx(1, "malloc failed"); for (i = 0; i < sizeof(sfp->f_fsid); i++) sprintf(&fsidbuf[i * 2], "%02x", ((u_char *)&sfp->f_fsid)[i]); fsidbuf[i * 2] = '\0'; xo_emit("{D:, }{Lw:fsid}{:fsid}", fsidbuf); free(fsidbuf); } if (sfp->f_nvnodelistsize != 0) { xo_open_container("vnodes"); xo_emit("{D:, }{Lwc:vnodes}{Lw:count}{w:count/%ju}", (uintmax_t)sfp->f_nvnodelistsize); xo_close_container("vnodes"); } } xo_emit("{D:)}\n"); } char * catopt(char *s0, const char *s1) { char *cp; if (s1 == NULL || *s1 == '\0') return (s0); if (s0 && *s0) { if (asprintf(&cp, "%s,%s", s0, s1) == -1) xo_errx(1, "asprintf failed"); } else cp = strdup(s1); if (s0) free(s0); return (cp); } void mangle(char *options, struct cpa *a) { char *p, *s, *val; for (s = options; (p = strsep(&s, ",")) != NULL;) if (*p != '\0') { if (strcmp(p, "noauto") == 0) { /* * Do not pass noauto option to nmount(). * or external mount program. noauto is * only used to prevent mounting a filesystem * when 'mount -a' is specified, and is * not a real mount option. */ continue; } else if (strcmp(p, "late") == 0) { /* * "late" is used to prevent certain file * systems from being mounted before late * in the boot cycle; for instance, * loopback NFS mounts can't be mounted * before mountd starts. */ continue; } else if (strcmp(p, "failok") == 0) { /* * "failok" is used to prevent certain file * systems from being causing the system to * drop into single user mode in the boot * cycle, and is not a real mount option. */ continue; } else if (strncmp(p, "mountprog", 9) == 0) { /* * "mountprog" is used to force the use of * userland mount programs. */ val = strchr(p, '='); if (val != NULL) { ++val; if (*val != '\0') mountprog = strdup(val); } if (mountprog == NULL) { xo_errx(1, "Need value for -o mountprog"); } continue; } else if (strcmp(p, "userquota") == 0) { continue; } else if (strncmp(p, userquotaeq, sizeof(userquotaeq) - 1) == 0) { continue; } else if (strcmp(p, "groupquota") == 0) { continue; } else if (strncmp(p, groupquotaeq, sizeof(groupquotaeq) - 1) == 0) { continue; } else if (*p == '-') { append_arg(a, p); p = strchr(p, '='); if (p != NULL) { *p = '\0'; append_arg(a, p + 1); } } else { append_arg(a, strdup("-o")); append_arg(a, p); } } } char * update_options(char *opts, char *fstab, int curflags) { char *o, *p; char *cur; char *expopt, *newopt, *tmpopt; if (opts == NULL) return (strdup("")); /* remove meta options from list */ remopt(fstab, MOUNT_META_OPTION_FSTAB); remopt(fstab, MOUNT_META_OPTION_CURRENT); cur = flags2opts(curflags); /* * Expand all meta-options passed to us first. */ expopt = NULL; for (p = opts; (o = strsep(&p, ",")) != NULL;) { if (strcmp(MOUNT_META_OPTION_FSTAB, o) == 0) expopt = catopt(expopt, fstab); else if (strcmp(MOUNT_META_OPTION_CURRENT, o) == 0) expopt = catopt(expopt, cur); else expopt = catopt(expopt, o); } free(cur); free(opts); /* * Remove previous contradictory arguments. Given option "foo" we * remove all the "nofoo" options. Given "nofoo" we remove "nonofoo" * and "foo" - so we can deal with possible options like "notice". */ newopt = NULL; for (p = expopt; (o = strsep(&p, ",")) != NULL;) { if ((tmpopt = malloc( strlen(o) + 2 + 1 )) == NULL) xo_errx(1, "malloc failed"); strcpy(tmpopt, "no"); strcat(tmpopt, o); remopt(newopt, tmpopt); free(tmpopt); if (strncmp("no", o, 2) == 0) remopt(newopt, o+2); newopt = catopt(newopt, o); } free(expopt); return (newopt); } void remopt(char *string, const char *opt) { char *o, *p, *r; if (string == NULL || *string == '\0' || opt == NULL || *opt == '\0') return; r = string; for (p = string; (o = strsep(&p, ",")) != NULL;) { if (strcmp(opt, o) != 0) { if (*r == ',' && *o != '\0') r++; while ((*r++ = *o++) != '\0') ; *--r = ','; } } *r = '\0'; } void usage(void) { xo_error("%s\n%s\n%s\n", "usage: mount [-adflpruvw] [-F fstab] [-o options] [-t ufs | external_type]", " mount [-dfpruvw] special | node", " mount [-dfpruvw] [-o options] [-t ufs | external_type] special node"); EXIT(EXIT_FAILURE); } void putfsent(struct statfs *ent) { struct fstab *fst; const char *mntfromname; char *opts, *rw; int l; mntfromname = ent->f_mntfromname; opts = NULL; /* flags2opts() doesn't return the "rw" option. */ if ((ent->f_flags & MNT_RDONLY) != 0) rw = NULL; else rw = catopt(NULL, "rw"); opts = flags2opts(ent->f_flags); opts = catopt(rw, opts); if (strncmp(mntfromname, ":", 8) == 0 || strncmp(mntfromname, ":", 8) == 0) { mntfromname += 8; } l = strlen(mntfromname); xo_emit("{:device}{P:/%s}{P:/%s}{P:/%s}", mntfromname, l < 8 ? "\t" : "", l < 16 ? "\t" : "", l < 24 ? "\t" : " "); l = strlen(ent->f_mntonname); xo_emit("{:mntpoint}{P:/%s}{P:/%s}{P:/%s}", ent->f_mntonname, l < 8 ? "\t" : "", l < 16 ? "\t" : "", l < 24 ? "\t" : " "); xo_emit("{:fstype}{P:\t}", ent->f_fstypename); l = strlen(opts); xo_emit("{:opts}{P:/%s}", opts, l < 8 ? "\t" : " "); free(opts); if ((fst = getfsspec(mntfromname))) xo_emit("{P:\t}{n:dump/%u}{P: }{n:pass/%u}\n", fst->fs_freq, fst->fs_passno); else if ((fst = getfsfile(ent->f_mntonname))) xo_emit("{P:\t}{n:dump/%u}{P: }{n:pass/%u}\n", fst->fs_freq, fst->fs_passno); else if (strcmp(ent->f_fstypename, "ufs") == 0) { if (strcmp(ent->f_mntonname, "/") == 0) xo_emit("{P:\t}{n:dump/1}{P: }{n:pass/1}\n"); else xo_emit("{P:\t}{n:dump/2}{P: }{n:pass/2}\n"); } else xo_emit("{P:\t}{n:dump/0}{P: }{n:pass/0}\n"); } char * flags2opts(int flags) { char *res; res = NULL; if (flags & MNT_RDONLY) res = catopt(res, "ro"); if (flags & MNT_SYNCHRONOUS) res = catopt(res, "sync"); if (flags & MNT_NOEXEC) res = catopt(res, "noexec"); if (flags & MNT_NOSUID) res = catopt(res, "nosuid"); if (flags & MNT_UNION) res = catopt(res, "union"); if (flags & MNT_ASYNC) res = catopt(res, "async"); if (flags & MNT_NOATIME) res = catopt(res, "noatime"); if (flags & MNT_NOCLUSTERR) res = catopt(res, "noclusterr"); if (flags & MNT_NOCLUSTERW) res = catopt(res, "noclusterw"); if (flags & MNT_NOSYMFOLLOW) res = catopt(res, "nosymfollow"); if (flags & MNT_SUIDDIR) res = catopt(res, "suiddir"); if (flags & MNT_MULTILABEL) res = catopt(res, "multilabel"); if (flags & MNT_ACLS) res = catopt(res, "acls"); if (flags & MNT_NFS4ACLS) res = catopt(res, "nfsv4acls"); if (flags & MNT_UNTRUSTED) res = catopt(res, "untrusted"); if (flags & MNT_NOCOVER) res = catopt(res, "nocover"); if (flags & MNT_EMPTYDIR) res = catopt(res, "emptydir"); return (res); } diff --git a/sbin/mount/mount_fs.c b/sbin/mount/mount_fs.c index 6035857578ae..30c34ae32f31 100644 --- a/sbin/mount/mount_fs.c +++ b/sbin/mount/mount_fs.c @@ -1,127 +1,127 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software donated to Berkeley by * Jan-Simon Pendry. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include +#include #include #include #include #include #include "extern.h" -#include "mntopts.h" static struct mntopt mopts[] = { MOPT_STDOPTS, MOPT_END }; static void usage(void) { (void)fprintf(stderr, "usage: mount [-t fstype] [-o options] target_fs mount_point\n"); exit(1); } int mount_fs(const char *vfstype, int argc, char *argv[]) { struct iovec *iov; int iovlen; int mntflags = 0; int ch; char *dev, *dir, mntpath[MAXPATHLEN]; char fstype[32]; char errmsg[255]; char *p, *val; strlcpy(fstype, vfstype, sizeof(fstype)); memset(errmsg, 0, sizeof(errmsg)); getmnt_silent = 1; iov = NULL; iovlen = 0; optind = optreset = 1; /* Reset for parse of new argv. */ while ((ch = getopt(argc, argv, "o:")) != -1) { switch(ch) { case 'o': getmntopts(optarg, mopts, &mntflags, 0); p = strchr(optarg, '='); val = NULL; if (p != NULL) { *p = '\0'; val = p + 1; } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 2) usage(); dev = argv[0]; dir = argv[1]; if (checkpath(dir, mntpath) != 0) { warn("%s", mntpath); return (1); } (void)rmslashes(dev, dev); build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "from", dev, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); if (nmount(iov, iovlen, mntflags) == -1) { if (*errmsg != '\0') warn("%s: %s", dev, errmsg); else warn("%s", dev); return (1); } return (0); } diff --git a/sbin/mount_cd9660/mount_cd9660.c b/sbin/mount_cd9660/mount_cd9660.c index d8d64eb5a656..3eddbefb9a03 100644 --- a/sbin/mount_cd9660/mount_cd9660.c +++ b/sbin/mount_cd9660/mount_cd9660.c @@ -1,329 +1,328 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley * by Pace Willisson (pace@blitz.com). The Rock Ridge Extension * Support code is derived from software contributed to Berkeley * by Atsushi Murai (amurai@spec.co.jp). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include -#include "mntopts.h" - static struct mntopt mopts[] = { MOPT_STDOPTS, MOPT_UPDATE, MOPT_END }; static gid_t a_gid(const char *); static uid_t a_uid(const char *); static mode_t a_mask(const char *); static int get_ssector(const char *dev); static int set_charset(struct iovec **, int *iovlen, const char *); void usage(void); int main(int argc, char **argv) { struct iovec *iov; int iovlen; int ch, mntflags; char *dev, *dir, *p, *val, mntpath[MAXPATHLEN]; int verbose; int ssector; /* starting sector, 0 for 1st session */ char fstype[] = "cd9660"; iov = NULL; iovlen = 0; mntflags = verbose = 0; ssector = -1; while ((ch = getopt(argc, argv, "begG:jm:M:o:rs:U:vC:")) != -1) switch (ch) { case 'b': build_iovec(&iov, &iovlen, "brokenjoliet", NULL, (size_t)-1); break; case 'e': build_iovec(&iov, &iovlen, "extatt", NULL, (size_t)-1); break; case 'g': build_iovec(&iov, &iovlen, "gens", NULL, (size_t)-1); break; case 'G': build_iovec_argf(&iov, &iovlen, "gid", "%d", a_gid(optarg)); break; case 'm': build_iovec_argf(&iov, &iovlen, "mask", "%u", a_mask(optarg)); break; case 'M': build_iovec_argf(&iov, &iovlen, "dirmask", "%u", a_mask(optarg)); break; case 'j': build_iovec(&iov, &iovlen, "nojoliet", NULL, (size_t)-1); break; case 'o': getmntopts(optarg, mopts, &mntflags, NULL); p = strchr(optarg, '='); val = NULL; if (p != NULL) { *p = '\0'; val = p + 1; } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); break; case 'r': build_iovec(&iov, &iovlen, "norrip", NULL, (size_t)-1); break; case 's': ssector = atoi(optarg); break; case 'U': build_iovec_argf(&iov, &iovlen, "uid", "%d", a_uid(optarg)); break; case 'v': verbose++; break; case 'C': if (set_charset(&iov, &iovlen, optarg) == -1) err(EX_OSERR, "cd9660_iconv"); build_iovec(&iov, &iovlen, "kiconv", NULL, (size_t)-1); break; case '?': default: usage(); } argc -= optind; argv += optind; if (argc != 2) usage(); dev = argv[0]; dir = argv[1]; /* * Resolve the mountpoint with realpath(3) and remove unnecessary * slashes from the devicename if there are any. */ if (checkpath(dir, mntpath) != 0) err(1, "%s", mntpath); (void)rmslashes(dev, dev); if (ssector == -1) { /* * The start of the session has not been specified on * the command line. If we can successfully read the * TOC of a CD-ROM, use the last data track we find. * Otherwise, just use 0, in order to mount the very * first session. This is compatible with the * historic behaviour of mount_cd9660(8). If the user * has specified -s above, we don't get here * and leave the user's will. */ if ((ssector = get_ssector(dev)) == -1) { if (verbose) printf("could not determine starting sector, " "using very first session\n"); ssector = 0; } else if (verbose) printf("using starting sector %d\n", ssector); } mntflags |= MNT_RDONLY; build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "from", dev, (size_t)-1); build_iovec_argf(&iov, &iovlen, "ssector", "%d", ssector); if (nmount(iov, iovlen, mntflags) < 0) err(1, "%s", dev); exit(0); } void usage(void) { (void)fprintf(stderr, "usage: mount_cd9660 [-begjrv] [-C charset] [-G gid] [-m mask] [-M mask]\n" " [-o options] [-U uid] [-s startsector] special node\n"); exit(EX_USAGE); } static int get_ssector(const char *dev) { struct ioc_toc_header h; struct ioc_read_toc_entry t; struct cd_toc_entry toc_buffer[100]; int fd, ntocentries, i; if ((fd = open(dev, O_RDONLY)) == -1) return -1; if (ioctl(fd, CDIOREADTOCHEADER, &h) == -1) { close(fd); return -1; } ntocentries = h.ending_track - h.starting_track + 1; if (ntocentries > 100) { /* unreasonable, only 100 allowed */ close(fd); return -1; } t.address_format = CD_LBA_FORMAT; t.starting_track = 0; t.data_len = ntocentries * sizeof(struct cd_toc_entry); t.data = toc_buffer; if (ioctl(fd, CDIOREADTOCENTRYS, (char *) &t) == -1) { close(fd); return -1; } close(fd); for (i = ntocentries - 1; i >= 0; i--) if ((toc_buffer[i].control & 4) != 0) /* found a data track */ break; if (i < 0) return -1; return ntohl(toc_buffer[i].addr.lba); } static int set_charset(struct iovec **iov, int *iovlen, const char *localcs) { int error; char *cs_disk; /* disk charset for Joliet cs conversion */ char *cs_local; /* local charset for Joliet cs conversion */ cs_disk = NULL; cs_local = NULL; if (modfind("cd9660_iconv") < 0) if (kldload("cd9660_iconv") < 0 || modfind("cd9660_iconv") < 0) { warnx( "cannot find or load \"cd9660_iconv\" kernel module"); return (-1); } if ((cs_disk = malloc(ICONV_CSNMAXLEN)) == NULL) return (-1); if ((cs_local = malloc(ICONV_CSNMAXLEN)) == NULL) { free(cs_disk); return (-1); } strncpy(cs_disk, ENCODING_UNICODE, ICONV_CSNMAXLEN); strncpy(cs_local, kiconv_quirkcs(localcs, KICONV_VENDOR_MICSFT), ICONV_CSNMAXLEN); error = kiconv_add_xlat16_cspairs(cs_disk, cs_local); if (error) return (-1); build_iovec(iov, iovlen, "cs_disk", cs_disk, (size_t)-1); build_iovec(iov, iovlen, "cs_local", cs_local, (size_t)-1); return (0); } static gid_t a_gid(const char *s) { struct group *gr; const char *gname; gid_t gid; if ((gr = getgrnam(s)) != NULL) gid = gr->gr_gid; else { for (gname = s; *s && isdigit(*s); ++s); if (!*s) gid = atoi(gname); else errx(EX_NOUSER, "unknown group id: %s", gname); } return (gid); } static uid_t a_uid(const char *s) { struct passwd *pw; const char *uname; uid_t uid; if ((pw = getpwnam(s)) != NULL) uid = pw->pw_uid; else { for (uname = s; *s && isdigit(*s); ++s); if (!*s) uid = atoi(uname); else errx(EX_NOUSER, "unknown user id: %s", uname); } return (uid); } static mode_t a_mask(const char *s) { int done, rv; char *ep; done = 0; rv = -1; if (*s >= '0' && *s <= '7') { done = 1; rv = strtol(optarg, &ep, 8); } if (!done || rv < 0 || *ep) errx(EX_USAGE, "invalid file mode: %s", s); return (rv); } diff --git a/sbin/mount_fusefs/mount_fusefs.c b/sbin/mount_fusefs/mount_fusefs.c index 8b1797747a25..2d65494421ef 100644 --- a/sbin/mount_fusefs/mount_fusefs.c +++ b/sbin/mount_fusefs/mount_fusefs.c @@ -1,495 +1,494 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2005 Jean-Sebastien Pedron * Copyright (c) 2005 Csaba Henk * All rights reserved. * * Copyright (c) 2019 The FreeBSD Foundation * * Portions of this software were developed by BFF Storage Systems under * sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include -#include "mntopts.h" - #ifndef FUSE4BSD_VERSION #define FUSE4BSD_VERSION "0.3.9-pre1" #endif void __usage_short(void); void usage(void); void helpmsg(void); void showversion(void); static struct mntopt mopts[] = { #define ALTF_PRIVATE 0x01 { "private", 0, ALTF_PRIVATE, 1 }, { "neglect_shares", 0, 0x02, 1 }, { "push_symlinks_in", 0, 0x04, 1 }, { "allow_other", 0, 0x08, 1 }, { "default_permissions", 0, 0x10, 1 }, #define ALTF_MAXREAD 0x20 { "max_read=", 0, ALTF_MAXREAD, 1 }, #define ALTF_SUBTYPE 0x40 { "subtype=", 0, ALTF_SUBTYPE, 1 }, #define ALTF_FSNAME 0x80 { "fsname=", 0, ALTF_FSNAME, 1 }, /* * MOPT_AUTOMOUNTED, included by MOPT_STDOPTS, does not fit into * the 'flags' argument to nmount(2). We have to abuse altflags * to pass it, as string, via iovec. */ #define ALTF_AUTOMOUNTED 0x100 { "automounted", 0, ALTF_AUTOMOUNTED, 1 }, #define ALTF_INTR 0x200 { "intr", 0, ALTF_INTR, 1 }, /* Linux specific options, we silently ignore them */ { "fd=", 0, 0x00, 1 }, { "rootmode=", 0, 0x00, 1 }, { "user_id=", 0, 0x00, 1 }, { "group_id=", 0, 0x00, 1 }, { "large_read", 0, 0x00, 1 }, /* "nonempty", just the first two chars are stripped off during parsing */ { "nempty", 0, 0x00, 1 }, { "async", 0, MNT_ASYNC, 0}, { "noasync", 1, MNT_ASYNC, 0}, MOPT_STDOPTS, MOPT_END }; struct mntval { int mv_flag; void *mv_value; int mv_len; }; static struct mntval mvals[] = { { ALTF_MAXREAD, NULL, 0 }, { ALTF_SUBTYPE, NULL, 0 }, { ALTF_FSNAME, NULL, 0 }, { 0, NULL, 0 } }; #define DEFAULT_MOUNT_FLAGS ALTF_PRIVATE int main(int argc, char *argv[]) { struct iovec *iov; int mntflags, iovlen, verbose = 0; char *dev = NULL, *dir = NULL, mntpath[MAXPATHLEN]; char *devo = NULL, *diro = NULL; char ndev[128], fdstr[15]; int i, done = 0, reject_allow_other = 0, safe_level = 0; int altflags = DEFAULT_MOUNT_FLAGS; int __altflags = DEFAULT_MOUNT_FLAGS; int ch = 0; struct mntopt *mo; struct mntval *mv; static struct option longopts[] = { {"reject-allow_other", no_argument, NULL, 'A'}, {"safe", no_argument, NULL, 'S'}, {"daemon", required_argument, NULL, 'D'}, {"daemon_opts", required_argument, NULL, 'O'}, {"special", required_argument, NULL, 's'}, {"mountpath", required_argument, NULL, 'm'}, {"version", no_argument, NULL, 'V'}, {"help", no_argument, NULL, 'h'}, {0,0,0,0} }; int pid = 0; int fd = -1, fdx; char *ep; char *daemon_str = NULL, *daemon_opts = NULL; /* * We want a parsing routine which is not sensitive to * the position of args/opts; it should extract the * first two args and stop at the beginning of the rest. * (This makes it easier to call mount_fusefs from external * utils than it is with a strict "util flags args" syntax.) */ iov = NULL; iovlen = 0; mntflags = 0; /* All in all, I feel it more robust this way... */ unsetenv("POSIXLY_CORRECT"); if (getenv("MOUNT_FUSEFS_IGNORE_UNKNOWN")) getmnt_silent = 1; if (getenv("MOUNT_FUSEFS_VERBOSE")) verbose = 1; do { for (i = 0; i < 3; i++) { if (optind < argc && argv[optind][0] != '-') { if (dir) { done = 1; break; } if (dev) dir = argv[optind]; else dev = argv[optind]; optind++; } } switch(ch) { case 'A': reject_allow_other = 1; break; case 'S': safe_level = 1; break; case 'D': if (daemon_str) errx(1, "daemon specified inconsistently"); daemon_str = optarg; break; case 'O': if (daemon_opts) errx(1, "daemon opts specified inconsistently"); daemon_opts = optarg; break; case 'o': getmntopts(optarg, mopts, &mntflags, &altflags); for (mv = mvals; mv->mv_flag; ++mv) { if (! (altflags & mv->mv_flag)) continue; for (mo = mopts; mo->m_flag; ++mo) { char *p, *q; if (mo->m_flag != mv->mv_flag) continue; p = strstr(optarg, mo->m_option); if (p) { p += strlen(mo->m_option); q = p; while (*q != '\0' && *q != ',') q++; mv->mv_len = q - p + 1; mv->mv_value = malloc(mv->mv_len); if (mv->mv_value == NULL) err(1, "malloc"); memcpy(mv->mv_value, p, mv->mv_len - 1); ((char *)mv->mv_value)[mv->mv_len - 1] = '\0'; break; } } } break; case 's': if (devo) errx(1, "special specified inconsistently"); devo = optarg; break; case 'm': if (diro) errx(1, "mount path specified inconsistently"); diro = optarg; break; case 'v': verbose = 1; break; case 'h': helpmsg(); break; case 'V': showversion(); break; case '\0': break; case '?': default: usage(); } if (done) break; } while ((ch = getopt_long(argc, argv, "AvVho:SD:O:s:m:", longopts, NULL)) != -1); argc -= optind; argv += optind; if (devo) { if (dev) errx(1, "special specified inconsistently"); dev = devo; } else if (diro) errx(1, "if mountpoint is given via an option, special should also be given via an option"); if (diro) { if (dir) errx(1, "mount path specified inconsistently"); dir = diro; } if ((! dev) && argc > 0) { dev = *argv++; argc--; } if ((! dir) && argc > 0) { dir = *argv++; argc--; } if (! (dev && dir)) errx(1, "missing special and/or mountpoint"); for (mo = mopts; mo->m_flag; ++mo) { if (altflags & mo->m_flag) { int iov_done = 0; if (reject_allow_other && strcmp(mo->m_option, "allow_other") == 0) /* * reject_allow_other is stronger than a * negative of allow_other: if this is set, * allow_other is blocked, period. */ errx(1, "\"allow_other\" usage is banned by respective option"); for (mv = mvals; mv->mv_flag; ++mv) { if (mo->m_flag != mv->mv_flag) continue; if (mv->mv_value) { build_iovec(&iov, &iovlen, mo->m_option, mv->mv_value, mv->mv_len); iov_done = 1; break; } } if (! iov_done) build_iovec(&iov, &iovlen, mo->m_option, __DECONST(void *, ""), -1); } if (__altflags & mo->m_flag) { char *uscore_opt; if (asprintf(&uscore_opt, "__%s", mo->m_option) == -1) err(1, "failed to allocate memory"); build_iovec(&iov, &iovlen, uscore_opt, __DECONST(void *, ""), -1); free(uscore_opt); } } if (getenv("MOUNT_FUSEFS_SAFE")) safe_level = 1; if (safe_level > 0 && (argc > 0 || daemon_str || daemon_opts)) errx(1, "safe mode, spawning daemon not allowed"); if ((argc > 0 && (daemon_str || daemon_opts)) || (daemon_opts && ! daemon_str)) errx(1, "daemon specified inconsistently"); /* * Resolve the mountpoint with realpath(3) and remove unnecessary * slashes from the devicename if there are any. */ if (checkpath(dir, mntpath) != 0) err(1, "%s", mntpath); (void)rmslashes(dev, dev); if (strcmp(dev, "auto") == 0) dev = __DECONST(char *, "/dev/fuse"); if (strcmp(dev, "/dev/fuse") == 0) { if (! (argc > 0 || daemon_str)) { fprintf(stderr, "Please also specify the fuse daemon to run when mounting via the multiplexer!\n"); usage(); } if ((fd = open(dev, O_RDWR)) < 0) err(1, "failed to open fuse device"); } else { fdx = strtol(dev, &ep, 10); if (*ep == '\0') fd = fdx; } /* Identifying device */ if (fd >= 0) { struct stat sbuf; char *ndevbas, *lep; if (fstat(fd, &sbuf) == -1) err(1, "cannot stat device file descriptor"); strcpy(ndev, _PATH_DEV); ndevbas = ndev + strlen(_PATH_DEV); devname_r(sbuf.st_rdev, S_IFCHR, ndevbas, sizeof(ndev) - strlen(_PATH_DEV)); if (strncmp(ndevbas, "fuse", 4)) errx(1, "mounting inappropriate device"); strtol(ndevbas + 4, &lep, 10); if (*lep != '\0') errx(1, "mounting inappropriate device"); dev = ndev; } if (argc > 0 || daemon_str) { char *fds; if (fd < 0 && (fd = open(dev, O_RDWR)) < 0) err(1, "failed to open fuse device"); if (asprintf(&fds, "%d", fd) == -1) err(1, "failed to allocate memory"); setenv("FUSE_DEV_FD", fds, 1); free(fds); setenv("FUSE_NO_MOUNT", "1", 1); if (daemon_str) { char *bgdaemon; int len; if (! daemon_opts) daemon_opts = __DECONST(char *, ""); len = strlen(daemon_str) + 1 + strlen(daemon_opts) + 2 + 1; bgdaemon = calloc(1, len); if (! bgdaemon) err(1, "failed to allocate memory"); strlcpy(bgdaemon, daemon_str, len); strlcat(bgdaemon, " ", len); strlcat(bgdaemon, daemon_opts, len); strlcat(bgdaemon, " &", len); if (system(bgdaemon)) err(1, "failed to call fuse daemon"); } else { if ((pid = fork()) < 0) err(1, "failed to fork for fuse daemon"); if (pid == 0) { execvp(argv[0], argv); err(1, "failed to exec fuse daemon"); } } } /* Prepare the options vector for nmount(). build_iovec() is declared * in mntopts.h. */ sprintf(fdstr, "%d", fd); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1); build_iovec(&iov, &iovlen, "fspath", mntpath, -1); build_iovec(&iov, &iovlen, "from", dev, -1); build_iovec(&iov, &iovlen, "fd", fdstr, -1); if (verbose) fprintf(stderr, "mounting fuse daemon on device %s\n", dev); if (nmount(iov, iovlen, mntflags) < 0) err(EX_OSERR, "%s on %s", dev, mntpath); exit(0); } void __usage_short(void) { fprintf(stderr, "usage:\n%s [-A|-S|-v|-V|-h|-D daemon|-O args|-s special|-m node|-o option...] special node [daemon args...]\n\n", getprogname()); } void usage(void) { struct mntopt *mo; __usage_short(); fprintf(stderr, "known options:\n"); for (mo = mopts; mo->m_flag; ++mo) fprintf(stderr, "\t%s\n", mo->m_option); fprintf(stderr, "\n(use -h for a detailed description of these options)\n"); exit(EX_USAGE); } void helpmsg(void) { if (! getenv("MOUNT_FUSEFS_CALL_BY_LIB")) { __usage_short(); fprintf(stderr, "description of options:\n"); } /* * The main use case of this function is giving info embedded in general * FUSE lib help output. Therefore the style and the content of the output * tries to fit there as much as possible. */ fprintf(stderr, " -o allow_other allow access to other users\n" /* " -o nonempty allow mounts over non-empty file/dir\n" */ " -o default_permissions enable permission checking by kernel\n" " -o intr interruptible mount\n" " -o fsname=NAME set filesystem name\n" /* " -o large_read issue large read requests (2.4 only)\n" */ " -o subtype=NAME set filesystem type\n" " -o max_read=N set maximum size of read requests\n" " -o noprivate allow secondary mounting of the filesystem\n" " -o neglect_shares don't report EBUSY when unmount attempted\n" " in presence of secondary mounts\n" " -o push_symlinks_in prefix absolute symlinks with mountpoint\n" ); exit(EX_USAGE); } void showversion(void) { puts("mount_fusefs [fuse4bsd] version: " FUSE4BSD_VERSION); exit(EX_USAGE); } diff --git a/sbin/mount_msdosfs/mount_msdosfs.c b/sbin/mount_msdosfs/mount_msdosfs.c index 9128914bc2b0..36e1c524922d 100644 --- a/sbin/mount_msdosfs/mount_msdosfs.c +++ b/sbin/mount_msdosfs/mount_msdosfs.c @@ -1,322 +1,321 @@ /* $NetBSD: mount_msdos.c,v 1.18 1997/09/16 12:24:18 lukem Exp $ */ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1994 Christopher G. Demetriou * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Christopher G. Demetriou. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include +#include #include #include /* must be after stdio to declare fparseln */ #include #include #include #include #include -#include "mntopts.h" - static gid_t a_gid(char *); static uid_t a_uid(char *); static mode_t a_mask(char *); static void usage(void) __dead2; static int set_charset(struct iovec **iov, int *iovlen, const char *, const char *); int main(int argc, char **argv) { struct iovec *iov = NULL; int iovlen = 0; struct stat sb; int c, set_gid, set_uid, set_mask, set_dirmask; char *dev, *dir, mntpath[MAXPATHLEN], *csp; char fstype[] = "msdosfs"; char errmsg[255] = {0}; char *cs_dos = NULL; char *cs_local = NULL; mode_t mask = 0, dirmask = 0; uid_t uid = 0; gid_t gid = 0; set_gid = set_uid = set_mask = set_dirmask = 0; while ((c = getopt(argc, argv, "sl9u:g:m:M:o:L:D:W:")) != -1) { switch (c) { case 's': build_iovec(&iov, &iovlen, "shortnames", NULL, (size_t)-1); break; case 'l': build_iovec(&iov, &iovlen, "longnames", NULL, (size_t)-1); break; case '9': build_iovec_argf(&iov, &iovlen, "nowin95", "", (size_t)-1); break; case 'u': uid = a_uid(optarg); set_uid = 1; break; case 'g': gid = a_gid(optarg); set_gid = 1; break; case 'm': mask = a_mask(optarg); set_mask = 1; break; case 'M': dirmask = a_mask(optarg); set_dirmask = 1; break; case 'L': { const char *quirk = NULL; if (setlocale(LC_CTYPE, optarg) == NULL) err(EX_CONFIG, "%s", optarg); csp = strchr(optarg,'.'); if (!csp) err(EX_CONFIG, "%s", optarg); quirk = kiconv_quirkcs(csp + 1, KICONV_VENDOR_MICSFT); build_iovec_argf(&iov, &iovlen, "cs_local", quirk); cs_local = strdup(quirk); } break; case 'D': cs_dos = strdup(optarg); build_iovec_argf(&iov, &iovlen, "cs_dos", cs_dos, (size_t)-1); break; case 'o': { char *p = NULL; char *val = strdup(""); p = strchr(optarg, '='); if (p != NULL) { free(val); *p = '\0'; val = p + 1; } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); } break; case 'W': if (strcmp(optarg, "iso22dos") == 0) { cs_local = strdup("ISO8859-2"); cs_dos = strdup("CP852"); } else if (strcmp(optarg, "iso72dos") == 0) { cs_local = strdup("ISO8859-7"); cs_dos = strdup("CP737"); } else if (strcmp(optarg, "koi2dos") == 0) { cs_local = strdup("KOI8-R"); cs_dos = strdup("CP866"); } else if (strcmp(optarg, "koi8u2dos") == 0) { cs_local = strdup("KOI8-U"); cs_dos = strdup("CP866"); } else { err(EX_NOINPUT, "%s", optarg); } build_iovec(&iov, &iovlen, "cs_local", cs_local, (size_t)-1); build_iovec(&iov, &iovlen, "cs_dos", cs_dos, (size_t)-1); break; case '?': default: usage(); break; } } if (optind + 2 != argc) usage(); if (set_mask && !set_dirmask) { dirmask = mask; set_dirmask = 1; } else if (set_dirmask && !set_mask) { mask = dirmask; set_mask = 1; } dev = argv[optind]; dir = argv[optind + 1]; if (cs_local != NULL) { if (set_charset(&iov, &iovlen, cs_local, cs_dos) == -1) err(EX_OSERR, "msdosfs_iconv"); build_iovec_argf(&iov, &iovlen, "kiconv", ""); } else if (cs_dos != NULL) { build_iovec_argf(&iov, &iovlen, "cs_local", "ISO8859-1"); if (set_charset(&iov, &iovlen, "ISO8859-1", cs_dos) == -1) err(EX_OSERR, "msdosfs_iconv"); build_iovec_argf(&iov, &iovlen, "kiconv", ""); } /* * Resolve the mountpoint with realpath(3) and remove unnecessary * slashes from the devicename if there are any. */ if (checkpath(dir, mntpath) != 0) err(EX_USAGE, "%s", mntpath); (void)rmslashes(dev, dev); if (!set_gid || !set_uid || !set_mask) { if (stat(mntpath, &sb) == -1) err(EX_OSERR, "stat %s", mntpath); if (!set_uid) uid = sb.st_uid; if (!set_gid) gid = sb.st_gid; if (!set_mask) mask = dirmask = sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO); } build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "from", dev, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); build_iovec_argf(&iov, &iovlen, "uid", "%d", uid); build_iovec_argf(&iov, &iovlen, "gid", "%u", gid); build_iovec_argf(&iov, &iovlen, "mask", "%u", mask); build_iovec_argf(&iov, &iovlen, "dirmask", "%u", dirmask); if (nmount(iov, iovlen, 0) < 0) { if (errmsg[0]) err(1, "%s: %s", dev, errmsg); else err(1, "%s", dev); } exit (0); } gid_t a_gid(char *s) { struct group *gr; char *gname; gid_t gid; if ((gr = getgrnam(s)) != NULL) gid = gr->gr_gid; else { for (gname = s; *s && isdigit(*s); ++s); if (!*s) gid = atoi(gname); else errx(EX_NOUSER, "unknown group id: %s", gname); } return (gid); } uid_t a_uid(char *s) { struct passwd *pw; char *uname; uid_t uid; if ((pw = getpwnam(s)) != NULL) uid = pw->pw_uid; else { for (uname = s; *s && isdigit(*s); ++s); if (!*s) uid = atoi(uname); else errx(EX_NOUSER, "unknown user id: %s", uname); } return (uid); } mode_t a_mask(char *s) { int done, rv; char *ep; done = 0; rv = -1; if (*s >= '0' && *s <= '7') { done = 1; rv = strtol(optarg, &ep, 8); } if (!done || rv < 0 || *ep) errx(EX_USAGE, "invalid file mode: %s", s); return (rv); } void usage(void) { fprintf(stderr, "%s\n%s\n%s\n", "usage: mount_msdosfs [-9ls] [-D DOS_codepage] [-g gid] [-L locale]", " [-M mask] [-m mask] [-o options] [-u uid]", " [-W table] special node"); exit(EX_USAGE); } int set_charset(struct iovec **iov, int *iovlen, const char *cs_local, const char *cs_dos) { int error; if (modfind("msdosfs_iconv") < 0) if (kldload("msdosfs_iconv") < 0 || modfind("msdosfs_iconv") < 0) { warnx("cannot find or load \"msdosfs_iconv\" kernel module"); return (-1); } build_iovec_argf(iov, iovlen, "cs_win", ENCODING_UNICODE); error = kiconv_add_xlat16_cspairs(ENCODING_UNICODE, cs_local); if (error && errno != EEXIST) return (-1); if (cs_dos != NULL) { error = kiconv_add_xlat16_cspairs(cs_dos, cs_local); if (error && errno != EEXIST) return (-1); } else { build_iovec_argf(iov, iovlen, "cs_dos", cs_local); error = kiconv_add_xlat16_cspair(cs_local, cs_local, KICONV_FROM_UPPER | KICONV_LOWER); if (error && errno != EEXIST) return (-1); } return (0); } diff --git a/sbin/mount_nfs/mount_nfs.c b/sbin/mount_nfs/mount_nfs.c index 6ba51eeec588..55adb01fc1da 100644 --- a/sbin/mount_nfs/mount_nfs.c +++ b/sbin/mount_nfs/mount_nfs.c @@ -1,1166 +1,1166 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include -#include "mntopts.h" #include "mounttab.h" /* Table for af,sotype -> netid conversions. */ static struct nc_protos { const char *netid; int af; int sotype; } nc_protos[] = { {"udp", AF_INET, SOCK_DGRAM}, {"tcp", AF_INET, SOCK_STREAM}, {"udp6", AF_INET6, SOCK_DGRAM}, {"tcp6", AF_INET6, SOCK_STREAM}, {NULL, 0, 0} }; struct nfhret { u_long stat; long vers; long auth; long fhsize; u_char nfh[NFS3_FHSIZE]; }; #define BGRND 0x01 #define ISBGRND 0x02 #define OF_NOINET4 0x04 #define OF_NOINET6 0x08 #define BGRNDNOW 0x10 static int retrycnt = -1; static int opflags = 0; static int nfsproto = IPPROTO_TCP; static int mnttcp_ok = 1; static int noconn = 0; /* The 'portspec' is the server nfs port; NULL means look up via rpcbind. */ static const char *portspec = NULL; static struct sockaddr *addr; static int addrlen = 0; static u_char *fh = NULL; static int fhsize = 0; static int secflavor = -1; static int got_principal = 0; static in_port_t mntproto_port = 0; static enum mountmode { ANY, V2, V3, V4 } mountmode = ANY; /* Return codes for nfs_tryproto. */ enum tryret { TRYRET_SUCCESS, TRYRET_TIMEOUT, /* No response received. */ TRYRET_REMOTEERR, /* Error received from remote server. */ TRYRET_LOCALERR /* Local failure. */ }; static int sec_name_to_num(const char *sec); static const char *sec_num_to_name(int num); static int getnfsargs(char **, char **, struct iovec **iov, int *iovlen); /* void set_rpc_maxgrouplist(int); */ static struct netconfig *getnetconf_cached(const char *netid); static const char *netidbytype(int af, int sotype); static void usage(void) __dead2; static int xdr_dir(XDR *, char *); static int xdr_fh(XDR *, struct nfhret *); static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr, struct iovec **iov, int *iovlen); static enum tryret returncode(enum clnt_stat stat, struct rpc_err *rpcerr); int main(int argc, char *argv[]) { int c; struct iovec *iov; int num, iovlen; char *host, *mntname, *p, *spec, *tmp; char mntpath[MAXPATHLEN], errmsg[255]; char hostname[MAXHOSTNAMELEN + 1], gssn[MAXHOSTNAMELEN + 50]; const char *gssname, *nmount_errstr; bool softintr; softintr = false; iov = NULL; iovlen = 0; memset(errmsg, 0, sizeof(errmsg)); gssname = NULL; while ((c = getopt(argc, argv, "23a:bcdD:g:I:iLlNo:PR:r:sTt:w:x:U")) != -1) switch (c) { case '2': mountmode = V2; break; case '3': mountmode = V3; break; case 'a': printf("-a deprecated, use -o readahead=\n"); build_iovec(&iov, &iovlen, "readahead", optarg, (size_t)-1); break; case 'b': opflags |= BGRND; break; case 'c': printf("-c deprecated, use -o noconn\n"); build_iovec(&iov, &iovlen, "noconn", NULL, 0); noconn = 1; break; case 'D': printf("-D deprecated, use -o deadthresh=\n"); build_iovec(&iov, &iovlen, "deadthresh", optarg, (size_t)-1); break; case 'd': printf("-d deprecated, use -o dumbtimer"); build_iovec(&iov, &iovlen, "dumbtimer", NULL, 0); break; case 'g': printf("-g deprecated, use -o maxgroups"); num = strtol(optarg, &p, 10); if (*p || num <= 0) errx(1, "illegal -g value -- %s", optarg); //set_rpc_maxgrouplist(num); build_iovec(&iov, &iovlen, "maxgroups", optarg, (size_t)-1); break; case 'I': printf("-I deprecated, use -o readdirsize=\n"); build_iovec(&iov, &iovlen, "readdirsize", optarg, (size_t)-1); break; case 'i': printf("-i deprecated, use -o intr\n"); build_iovec(&iov, &iovlen, "intr", NULL, 0); softintr = true; break; case 'L': printf("-L deprecated, use -o nolockd\n"); build_iovec(&iov, &iovlen, "nolockd", NULL, 0); break; case 'l': printf("-l deprecated, -o rdirplus\n"); build_iovec(&iov, &iovlen, "rdirplus", NULL, 0); break; case 'N': printf("-N deprecated, do not specify -o resvport\n"); break; case 'o': { int pass_flag_to_nmount; char *opt = optarg; while (opt) { char *pval = NULL; char *pnextopt = NULL; const char *val = ""; pass_flag_to_nmount = 1; pnextopt = strchr(opt, ','); if (pnextopt != NULL) { *pnextopt = '\0'; pnextopt++; } pval = strchr(opt, '='); if (pval != NULL) { *pval = '\0'; val = pval + 1; } if (strcmp(opt, "bg") == 0) { opflags |= BGRND; pass_flag_to_nmount=0; } else if (strcmp(opt, "bgnow") == 0) { opflags |= BGRNDNOW; pass_flag_to_nmount=0; } else if (strcmp(opt, "fg") == 0) { /* same as not specifying -o bg */ pass_flag_to_nmount=0; } else if (strcmp(opt, "gssname") == 0) { pass_flag_to_nmount = 0; gssname = val; } else if (strcmp(opt, "mntudp") == 0) { mnttcp_ok = 0; nfsproto = IPPROTO_UDP; } else if (strcmp(opt, "udp") == 0) { nfsproto = IPPROTO_UDP; } else if (strcmp(opt, "tcp") == 0) { nfsproto = IPPROTO_TCP; } else if (strcmp(opt, "noinet4") == 0) { pass_flag_to_nmount=0; opflags |= OF_NOINET4; } else if (strcmp(opt, "noinet6") == 0) { pass_flag_to_nmount=0; opflags |= OF_NOINET6; } else if (strcmp(opt, "noconn") == 0) { noconn = 1; } else if (strcmp(opt, "nfsv2") == 0) { pass_flag_to_nmount=0; mountmode = V2; } else if (strcmp(opt, "nfsv3") == 0) { mountmode = V3; } else if (strcmp(opt, "nfsv4") == 0) { pass_flag_to_nmount=0; mountmode = V4; nfsproto = IPPROTO_TCP; if (portspec == NULL) portspec = "2049"; } else if (strcmp(opt, "port") == 0) { pass_flag_to_nmount=0; asprintf(&tmp, "%d", atoi(val)); if (tmp == NULL) err(1, "asprintf"); portspec = tmp; } else if (strcmp(opt, "principal") == 0) { got_principal = 1; } else if (strcmp(opt, "proto") == 0) { pass_flag_to_nmount=0; if (strcmp(val, "tcp") == 0) { nfsproto = IPPROTO_TCP; opflags |= OF_NOINET6; build_iovec(&iov, &iovlen, "tcp", NULL, 0); } else if (strcmp(val, "udp") == 0) { mnttcp_ok = 0; nfsproto = IPPROTO_UDP; opflags |= OF_NOINET6; build_iovec(&iov, &iovlen, "udp", NULL, 0); } else if (strcmp(val, "tcp6") == 0) { nfsproto = IPPROTO_TCP; opflags |= OF_NOINET4; build_iovec(&iov, &iovlen, "tcp", NULL, 0); } else if (strcmp(val, "udp6") == 0) { mnttcp_ok = 0; nfsproto = IPPROTO_UDP; opflags |= OF_NOINET4; build_iovec(&iov, &iovlen, "udp", NULL, 0); } else { errx(1, "illegal proto value -- %s", val); } } else if (strcmp(opt, "sec") == 0) { /* * Don't add this option to * the iovec yet - we will * negotiate which sec flavor * to use with the remote * mountd. */ pass_flag_to_nmount=0; secflavor = sec_name_to_num(val); if (secflavor < 0) { errx(1, "illegal sec value -- %s", val); } } else if (strcmp(opt, "retrycnt") == 0) { pass_flag_to_nmount=0; num = strtol(val, &p, 10); if (*p || num < 0) errx(1, "illegal retrycnt value -- %s", val); retrycnt = num; } else if (strcmp(opt, "maxgroups") == 0) { num = strtol(val, &p, 10); if (*p || num <= 0) errx(1, "illegal maxgroups value -- %s", val); //set_rpc_maxgrouplist(num); } else if (strcmp(opt, "vers") == 0) { num = strtol(val, &p, 10); if (*p || num <= 0) errx(1, "illegal vers value -- " "%s", val); switch (num) { case 2: mountmode = V2; break; case 3: mountmode = V3; build_iovec(&iov, &iovlen, "nfsv3", NULL, 0); break; case 4: mountmode = V4; nfsproto = IPPROTO_TCP; if (portspec == NULL) portspec = "2049"; break; default: errx(1, "illegal nfs version " "value -- %s", val); } pass_flag_to_nmount=0; } else if (strcmp(opt, "soft") == 0) { softintr = true; } else if (strcmp(opt, "intr") == 0) { softintr = true; } else if (strcmp(opt, "mountport") == 0) { num = strtol(val, &p, 10); if (*p || num <= 0 || num > IPPORT_MAX) errx(1, "illegal port num -- " "%s", val); mntproto_port = num; pass_flag_to_nmount=0; } if (pass_flag_to_nmount) { build_iovec(&iov, &iovlen, opt, __DECONST(void *, val), strlen(val) + 1); } opt = pnextopt; } } break; case 'P': /* obsolete for -o noresvport now default */ printf("-P deprecated, use -o noresvport\n"); build_iovec(&iov, &iovlen, "noresvport", NULL, 0); break; case 'R': printf("-R deprecated, use -o retrycnt=\n"); num = strtol(optarg, &p, 10); if (*p || num < 0) errx(1, "illegal -R value -- %s", optarg); retrycnt = num; break; case 'r': printf("-r deprecated, use -o rsize=\n"); build_iovec(&iov, &iovlen, "rsize", optarg, (size_t)-1); break; case 's': printf("-s deprecated, use -o soft\n"); build_iovec(&iov, &iovlen, "soft", NULL, 0); softintr = true; break; case 'T': nfsproto = IPPROTO_TCP; printf("-T deprecated, use -o tcp\n"); break; case 't': printf("-t deprecated, use -o timeout=\n"); build_iovec(&iov, &iovlen, "timeout", optarg, (size_t)-1); break; case 'w': printf("-w deprecated, use -o wsize=\n"); build_iovec(&iov, &iovlen, "wsize", optarg, (size_t)-1); break; case 'x': printf("-x deprecated, use -o retrans=\n"); build_iovec(&iov, &iovlen, "retrans", optarg, (size_t)-1); break; case 'U': printf("-U deprecated, use -o mntudp\n"); mnttcp_ok = 0; nfsproto = IPPROTO_UDP; build_iovec(&iov, &iovlen, "mntudp", NULL, 0); break; default: usage(); break; } argc -= optind; argv += optind; if ((opflags & (BGRND | BGRNDNOW)) == (BGRND | BGRNDNOW)) errx(1, "Options bg and bgnow are mutually exclusive"); if (argc != 2) { usage(); /* NOTREACHED */ } /* Warn that NFSv4 mounts only work correctly as hard mounts. */ if (mountmode == V4 && softintr) warnx("Warning, options soft and/or intr cannot be safely used" " for NFSv4. See the BUGS section of mount_nfs(8)"); spec = *argv++; mntname = *argv; if (retrycnt == -1) /* The default is to keep retrying forever. */ retrycnt = 0; if (modfind("nfscl") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfscl") < 0 || modfind("nfscl") < 0) errx(1, "nfscl is not available"); } /* * Add the fqdn to the gssname, as required. */ if (gssname != NULL) { if (strchr(gssname, '@') == NULL && gethostname(hostname, MAXHOSTNAMELEN) == 0) { snprintf(gssn, sizeof (gssn), "%s@%s", gssname, hostname); gssname = gssn; } build_iovec(&iov, &iovlen, "gssname", __DECONST(void *, gssname), strlen(gssname) + 1); } if (!getnfsargs(&spec, &host, &iov, &iovlen)) exit(1); /* resolve the mountpoint with realpath(3) */ if (checkpath(mntname, mntpath) != 0) err(1, "%s", mntpath); build_iovec_argf(&iov, &iovlen, "fstype", "nfs"); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); if (nmount(iov, iovlen, 0)) { nmount_errstr = nfsv4_geterrstr(errno); if (mountmode == V4 && nmount_errstr != NULL) errx(1, "nmount: %s, %s", mntpath, nmount_errstr); else err(1, "nmount: %s%s%s", mntpath, errmsg[0] ? ", " : "", errmsg); } else if (mountmode != V4 && !add_mtab(host, spec)) { /* Add mounted file system to PATH_MOUNTTAB */ warnx("can't update %s for %s:%s", PATH_MOUNTTAB, host, spec); } exit(0); } static int sec_name_to_num(const char *sec) { if (!strcmp(sec, "krb5")) return (RPCSEC_GSS_KRB5); if (!strcmp(sec, "krb5i")) return (RPCSEC_GSS_KRB5I); if (!strcmp(sec, "krb5p")) return (RPCSEC_GSS_KRB5P); if (!strcmp(sec, "sys")) return (AUTH_SYS); return (-1); } static const char * sec_num_to_name(int flavor) { switch (flavor) { case RPCSEC_GSS_KRB5: return ("krb5"); case RPCSEC_GSS_KRB5I: return ("krb5i"); case RPCSEC_GSS_KRB5P: return ("krb5p"); case AUTH_SYS: return ("sys"); } return (NULL); } /* * Wait for RTM_IFINFO message with interface that is IFF_UP and with * link on, or until timeout expires. Returns seconds left. */ static time_t rtm_ifinfo_sleep(time_t sec) { char buf[2048] __aligned(__alignof(struct if_msghdr)); fd_set rfds; struct timeval tv, start; ssize_t nread; int n, s; s = socket(PF_ROUTE, SOCK_RAW, 0); if (s < 0) err(EX_OSERR, "socket"); (void)gettimeofday(&start, NULL); for (tv.tv_sec = sec, tv.tv_usec = 0; tv.tv_sec > 0; (void)gettimeofday(&tv, NULL), tv.tv_sec = sec - (tv.tv_sec - start.tv_sec)) { FD_ZERO(&rfds); FD_SET(s, &rfds); n = select(s + 1, &rfds, NULL, NULL, &tv); if (n == 0) continue; if (n == -1) { if (errno == EINTR) continue; else err(EX_SOFTWARE, "select"); } nread = read(s, buf, 2048); if (nread < 0) err(EX_OSERR, "read"); if ((size_t)nread >= sizeof(struct if_msghdr)) { struct if_msghdr *ifm; ifm = (struct if_msghdr *)buf; if (ifm->ifm_version == RTM_VERSION && ifm->ifm_type == RTM_IFINFO && (ifm->ifm_flags & IFF_UP) && ifm->ifm_data.ifi_link_state != LINK_STATE_DOWN) break; } } close(s); return (tv.tv_sec); } static int getnfsargs(char **specp, char **hostpp, struct iovec **iov, int *iovlen) { struct addrinfo hints, *ai_nfs, *ai; enum tryret ret; int ecode, speclen, remoteerr, offset, have_bracket = 0; char *hostp, *delimp, *errstr, *spec; size_t len; static char nam[MNAMELEN + 1], pname[MAXHOSTNAMELEN + 5]; bool resolved; spec = *specp; if (*spec == '[' && (delimp = strchr(spec + 1, ']')) != NULL && *(delimp + 1) == ':') { hostp = spec + 1; spec = delimp + 2; have_bracket = 1; } else if ((delimp = strrchr(spec, ':')) != NULL) { hostp = spec; spec = delimp + 1; } else if ((delimp = strrchr(spec, '@')) != NULL) { warnx("path@server syntax is deprecated, use server:path"); hostp = delimp + 1; } else { warnx("no : nfs-name"); return (0); } *delimp = '\0'; /* * If there has been a trailing slash at mounttime it seems * that some mountd implementations fail to remove the mount * entries from their mountlist while unmounting. */ for (speclen = strlen(spec); speclen > 1 && spec[speclen - 1] == '/'; speclen--) spec[speclen - 1] = '\0'; if (strlen(hostp) + strlen(spec) + 1 > MNAMELEN) { warnx("%s:%s: %s", hostp, spec, strerror(ENAMETOOLONG)); return (0); } /* Make both '@' and ':' notations equal */ if (*hostp != '\0') { len = strlen(hostp); offset = 0; if (have_bracket) nam[offset++] = '['; memmove(nam + offset, hostp, len); if (have_bracket) nam[len + offset++] = ']'; nam[len + offset++] = ':'; memmove(nam + len + offset, spec, speclen); nam[len + speclen + offset] = '\0'; } /* * Handle an internet host address. */ memset(&hints, 0, sizeof hints); hints.ai_flags = AI_NUMERICHOST; if (nfsproto == IPPROTO_TCP) hints.ai_socktype = SOCK_STREAM; else if (nfsproto == IPPROTO_UDP) hints.ai_socktype = SOCK_DGRAM; resolved = (getaddrinfo(hostp, portspec, &hints, &ai_nfs) == 0); if ((opflags & (BGRNDNOW | ISBGRND)) == BGRNDNOW) { warnx("Mount %s:%s, backgrounding", hostp, spec); opflags |= ISBGRND; if (daemon(0, 0) != 0) err(1, "daemon"); } ret = TRYRET_LOCALERR; for (;;) { if (!resolved) { hints.ai_flags = AI_CANONNAME; if ((ecode = getaddrinfo(hostp, portspec, &hints, &ai_nfs)) != 0) { if (portspec == NULL) warnx("%s: %s", hostp, gai_strerror(ecode)); else warnx("%s:%s: %s", hostp, portspec, gai_strerror(ecode)); if (ecode == EAI_AGAIN && (opflags & (BGRNDNOW | BGRND))) goto retry; else exit(1); } resolved = true; /* * For a Kerberized nfs mount where the * "principal" argument has not been set, add * it here. */ if (got_principal == 0 && secflavor != AUTH_SYS && ai_nfs->ai_canonname != NULL) { snprintf(pname, sizeof (pname), "nfs@%s", ai_nfs->ai_canonname); build_iovec(iov, iovlen, "principal", pname, strlen(pname) + 1); } } /* * Try each entry returned by getaddrinfo(). Note the * occurrence of remote errors by setting `remoteerr'. */ remoteerr = 0; for (ai = ai_nfs; ai != NULL; ai = ai->ai_next) { if ((ai->ai_family == AF_INET6) && (opflags & OF_NOINET6)) continue; if ((ai->ai_family == AF_INET) && (opflags & OF_NOINET4)) continue; ret = nfs_tryproto(ai, hostp, spec, &errstr, iov, iovlen); if (ret == TRYRET_SUCCESS) break; if (ret != TRYRET_LOCALERR) remoteerr = 1; if ((opflags & ISBGRND) == 0) fprintf(stderr, "%s\n", errstr); } if (ret == TRYRET_SUCCESS) break; /* Exit if all errors were local. */ if (!remoteerr) exit(1); retry: /* * If retrycnt == 0, we are to keep retrying forever. * Otherwise decrement it, and exit if it hits zero. */ if (retrycnt != 0 && --retrycnt == 0) exit(1); if ((opflags & (BGRND | ISBGRND)) == BGRND) { warnx("Cannot immediately mount %s:%s, backgrounding", hostp, spec); opflags |= ISBGRND; if (daemon(0, 0) != 0) err(1, "daemon"); } /* * If rtm_ifinfo_sleep() returns non-zero, don't count * that as a retry attempt. */ if (rtm_ifinfo_sleep(60) && retrycnt != 0) retrycnt++; } freeaddrinfo(ai_nfs); build_iovec(iov, iovlen, "hostname", nam, (size_t)-1); *specp = spec; *hostpp = hostp; return (1); } /* * Try to set up the NFS arguments according to the address * family, protocol (and possibly port) specified in `ai'. * * Returns TRYRET_SUCCESS if successful, or: * TRYRET_TIMEOUT The server did not respond. * TRYRET_REMOTEERR The server reported an error. * TRYRET_LOCALERR Local failure. * * In all error cases, *errstr will be set to a statically-allocated string * describing the error. */ static enum tryret nfs_tryproto(struct addrinfo *ai, char *hostp, char *spec, char **errstr, struct iovec **iov, int *iovlen) { static char errbuf[256]; struct sockaddr_storage nfs_ss; struct netbuf nfs_nb; struct nfhret nfhret; struct timeval try; struct rpc_err rpcerr; CLIENT *clp; struct netconfig *nconf, *nconf_mnt; const char *netid, *netid_mnt, *secname; int doconnect, nfsvers, mntvers, sotype; enum clnt_stat clntstat; enum mountmode trymntmode; sotype = 0; trymntmode = mountmode; errbuf[0] = '\0'; *errstr = errbuf; if (nfsproto == IPPROTO_TCP) sotype = SOCK_STREAM; else if (nfsproto == IPPROTO_UDP) sotype = SOCK_DGRAM; if ((netid = netidbytype(ai->ai_family, sotype)) == NULL) { snprintf(errbuf, sizeof errbuf, "af %d sotype %d not supported", ai->ai_family, sotype); return (TRYRET_LOCALERR); } if ((nconf = getnetconf_cached(netid)) == NULL) { snprintf(errbuf, sizeof errbuf, "%s: %s", netid, nc_sperror()); return (TRYRET_LOCALERR); } /* The RPCPROG_MNT netid may be different. */ if (mnttcp_ok) { netid_mnt = netid; nconf_mnt = nconf; } else { if ((netid_mnt = netidbytype(ai->ai_family, SOCK_DGRAM)) == NULL) { snprintf(errbuf, sizeof errbuf, "af %d sotype SOCK_DGRAM not supported", ai->ai_family); return (TRYRET_LOCALERR); } if ((nconf_mnt = getnetconf_cached(netid_mnt)) == NULL) { snprintf(errbuf, sizeof errbuf, "%s: %s", netid_mnt, nc_sperror()); return (TRYRET_LOCALERR); } } tryagain: if (trymntmode == V4) { nfsvers = 4; mntvers = 3; /* Workaround for GCC. */ } else if (trymntmode == V2) { nfsvers = 2; mntvers = 1; } else { nfsvers = 3; mntvers = 3; } if (portspec != NULL) { /* `ai' contains the complete nfsd sockaddr. */ nfs_nb.buf = ai->ai_addr; nfs_nb.len = nfs_nb.maxlen = ai->ai_addrlen; } else { /* Ask the remote rpcbind. */ nfs_nb.buf = &nfs_ss; nfs_nb.len = nfs_nb.maxlen = sizeof nfs_ss; if (!rpcb_getaddr(NFS_PROGRAM, nfsvers, nconf, &nfs_nb, hostp)) { if (rpc_createerr.cf_stat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { trymntmode = V2; goto tryagain; } snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_spcreateerror("RPCPROG_NFS")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } } /* Check that the server (nfsd) responds on the port we have chosen. */ clp = clnt_tli_create(RPC_ANYFD, nconf, &nfs_nb, NFS_PROGRAM, nfsvers, 0, 0); if (clp == NULL) { snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_spcreateerror("nfsd: RPCPROG_NFS")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } if (sotype == SOCK_DGRAM && noconn == 0) { /* * Use connect(), to match what the kernel does. This * catches cases where the server responds from the * wrong source address. */ doconnect = 1; if (!clnt_control(clp, CLSET_CONNECT, (char *)&doconnect)) { clnt_destroy(clp); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: CLSET_CONNECT failed", netid, hostp, spec); return (TRYRET_LOCALERR); } } try.tv_sec = 10; try.tv_usec = 0; clntstat = clnt_call(clp, NFSPROC_NULL, (xdrproc_t)xdr_void, NULL, (xdrproc_t)xdr_void, NULL, try); if (clntstat != RPC_SUCCESS) { if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { clnt_destroy(clp); trymntmode = V2; goto tryagain; } clnt_geterr(clp, &rpcerr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid, hostp, spec, clnt_sperror(clp, "NFSPROC_NULL")); clnt_destroy(clp); return (returncode(clntstat, &rpcerr)); } clnt_destroy(clp); /* * For NFSv4, there is no mount protocol. */ if (trymntmode == V4) { /* * Store the server address in nfsargsp, making * sure to copy any locally allocated structures. */ addrlen = nfs_nb.len; addr = malloc(addrlen); if (addr == NULL) err(1, "malloc"); bcopy(nfs_nb.buf, addr, addrlen); build_iovec(iov, iovlen, "addr", addr, addrlen); secname = sec_num_to_name(secflavor); if (secname != NULL) { build_iovec(iov, iovlen, "sec", __DECONST(void *, secname), (size_t)-1); } build_iovec(iov, iovlen, "nfsv4", NULL, 0); build_iovec(iov, iovlen, "dirpath", spec, (size_t)-1); return (TRYRET_SUCCESS); } /* * malloc() and copy the address, so that it can be used for * nfsargs below. */ addrlen = nfs_nb.len; addr = malloc(addrlen); if (addr == NULL) err(1, "malloc"); bcopy(nfs_nb.buf, addr, addrlen); /* Send the MOUNTPROC_MNT RPC to get the root filehandle. */ try.tv_sec = 10; try.tv_usec = 0; if (mntproto_port != 0) { struct sockaddr *sad; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; sad = (struct sockaddr *)nfs_nb.buf; switch (sad->sa_family) { case AF_INET: sin = (struct sockaddr_in *)nfs_nb.buf; sin->sin_port = htons(mntproto_port); break; case AF_INET6: sin6 = (struct sockaddr_in6 *)nfs_nb.buf; sin6->sin6_port = htons(mntproto_port); break; default: snprintf(errbuf, sizeof(errbuf), "Mnt port bad addr family %d\n", sad->sa_family); return (TRYRET_LOCALERR); } clp = clnt_tli_create(RPC_ANYFD, nconf_mnt, &nfs_nb, MOUNTPROG, mntvers, 0, 0); } else { /* Get the Mount protocol port# via rpcbind. */ clp = clnt_tp_create(hostp, MOUNTPROG, mntvers, nconf_mnt); } if (clp == NULL) { free(addr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, clnt_spcreateerror("RPCMNT: clnt_create")); return (returncode(rpc_createerr.cf_stat, &rpc_createerr.cf_error)); } clp->cl_auth = authsys_create_default(); nfhret.auth = secflavor; nfhret.vers = mntvers; clntstat = clnt_call(clp, MOUNTPROC_MNT, (xdrproc_t)xdr_dir, spec, (xdrproc_t)xdr_fh, &nfhret, try); auth_destroy(clp->cl_auth); if (clntstat != RPC_SUCCESS) { free(addr); if (clntstat == RPC_PROGVERSMISMATCH && trymntmode == ANY) { clnt_destroy(clp); trymntmode = V2; goto tryagain; } clnt_geterr(clp, &rpcerr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, clnt_sperror(clp, "RPCPROG_MNT")); clnt_destroy(clp); return (returncode(clntstat, &rpcerr)); } clnt_destroy(clp); if (nfhret.stat != 0) { free(addr); snprintf(errbuf, sizeof errbuf, "[%s] %s:%s: %s", netid_mnt, hostp, spec, strerror(nfhret.stat)); return (TRYRET_REMOTEERR); } /* * Store the filehandle and server address in nfsargsp, making * sure to copy any locally allocated structures. */ fhsize = nfhret.fhsize; fh = malloc(fhsize); if (fh == NULL) err(1, "malloc"); bcopy(nfhret.nfh, fh, fhsize); build_iovec(iov, iovlen, "addr", addr, addrlen); build_iovec(iov, iovlen, "fh", fh, fhsize); secname = sec_num_to_name(nfhret.auth); if (secname) { build_iovec(iov, iovlen, "sec", __DECONST(void *, secname), (size_t)-1); } if (nfsvers == 3) build_iovec(iov, iovlen, "nfsv3", NULL, 0); return (TRYRET_SUCCESS); } /* * Catagorise a RPC return status and error into an `enum tryret' * return code. */ static enum tryret returncode(enum clnt_stat clntstat, struct rpc_err *rpcerr) { switch (clntstat) { case RPC_TIMEDOUT: return (TRYRET_TIMEOUT); case RPC_PMAPFAILURE: case RPC_PROGNOTREGISTERED: case RPC_PROGVERSMISMATCH: /* XXX, these can be local or remote. */ case RPC_CANTSEND: case RPC_CANTRECV: return (TRYRET_REMOTEERR); case RPC_SYSTEMERROR: switch (rpcerr->re_errno) { case ETIMEDOUT: return (TRYRET_TIMEOUT); case ENOMEM: break; default: return (TRYRET_REMOTEERR); } /* FALLTHROUGH */ default: break; } return (TRYRET_LOCALERR); } /* * Look up a netid based on an address family and socket type. * `af' is the address family, and `sotype' is SOCK_DGRAM or SOCK_STREAM. * * XXX there should be a library function for this. */ static const char * netidbytype(int af, int sotype) { struct nc_protos *p; for (p = nc_protos; p->netid != NULL; p++) { if (af != p->af || sotype != p->sotype) continue; return (p->netid); } return (NULL); } /* * Look up a netconfig entry based on a netid, and cache the result so * that we don't need to remember to call freenetconfigent(). * * Otherwise it behaves just like getnetconfigent(), so nc_*error() * work on failure. */ static struct netconfig * getnetconf_cached(const char *netid) { static struct nc_entry { struct netconfig *nconf; struct nc_entry *next; } *head; struct nc_entry *p; struct netconfig *nconf; for (p = head; p != NULL; p = p->next) if (strcmp(netid, p->nconf->nc_netid) == 0) return (p->nconf); if ((nconf = getnetconfigent(netid)) == NULL) return (NULL); if ((p = malloc(sizeof(*p))) == NULL) err(1, "malloc"); p->nconf = nconf; p->next = head; head = p; return (p->nconf); } /* * xdr routines for mount rpc's */ static int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } static int xdr_fh(XDR *xdrsp, struct nfhret *np) { int i; long auth, authcnt, authfnd = 0; if (!xdr_u_long(xdrsp, &np->stat)) return (0); if (np->stat) return (1); switch (np->vers) { case 1: np->fhsize = NFS_FHSIZE; return (xdr_opaque(xdrsp, (caddr_t)np->nfh, NFS_FHSIZE)); case 3: if (!xdr_long(xdrsp, &np->fhsize)) return (0); if (np->fhsize <= 0 || np->fhsize > NFS3_FHSIZE) return (0); if (!xdr_opaque(xdrsp, (caddr_t)np->nfh, np->fhsize)) return (0); if (!xdr_long(xdrsp, &authcnt)) return (0); for (i = 0; i < authcnt; i++) { if (!xdr_long(xdrsp, &auth)) return (0); if (np->auth == -1) { np->auth = auth; authfnd++; } else if (auth == np->auth) { authfnd++; } } /* * Some servers, such as DEC's OSF/1 return a nil authenticator * list to indicate RPCAUTH_UNIX. */ if (authcnt == 0 && np->auth == -1) np->auth = AUTH_SYS; if (!authfnd && (authcnt > 0 || np->auth != AUTH_SYS)) np->stat = EAUTH; return (1); } return (0); } static void usage(void) { (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", "usage: mount_nfs [-23bcdiLlNPsTU] [-a maxreadahead] [-D deadthresh]", " [-g maxgroups] [-I readdirsize] [-o options] [-R retrycnt]", " [-r readsize] [-t timeout] [-w writesize] [-x retrans]", " rhost:path node"); exit(1); } diff --git a/sbin/mount_nullfs/mount_nullfs.c b/sbin/mount_nullfs/mount_nullfs.c index b5782dc24e45..fc04961e6247 100644 --- a/sbin/mount_nullfs/mount_nullfs.c +++ b/sbin/mount_nullfs/mount_nullfs.c @@ -1,130 +1,129 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software donated to Berkeley by * Jan-Simon Pendry. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include +#include #include #include #include #include #include -#include "mntopts.h" - static void usage(void) __dead2; static int stat_realpath(const char *path, char *resolved, struct stat *sbp) { if (realpath(path, resolved) == NULL || stat(resolved, sbp) != 0) return (1); return (0); } int main(int argc, char *argv[]) { struct iovec *iov; char *p, *val; char mountpoint[MAXPATHLEN]; char target[MAXPATHLEN]; char errmsg[255]; int ch, iovlen; char nullfs[] = "nullfs"; struct stat target_stat; struct stat mountpoint_stat; iov = NULL; iovlen = 0; errmsg[0] = '\0'; while ((ch = getopt(argc, argv, "o:")) != -1) switch(ch) { case 'o': val = strdup(""); p = strchr(optarg, '='); if (p != NULL) { free(val); *p = '\0'; val = p + 1; } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); break; case '?': default: usage(); } argc -= optind; argv += optind; if (argc != 2) usage(); /* resolve target and mountpoint with realpath(3) */ if (stat_realpath(argv[0], target, &target_stat) != 0) err(EX_USAGE, "%s", target); if (stat_realpath(argv[1], mountpoint, &mountpoint_stat) != 0) err(EX_USAGE, "%s", mountpoint); if (!S_ISDIR(target_stat.st_mode) && !S_ISREG(target_stat.st_mode)) errx(EX_USAGE, "%s: must be either a file or directory", target); if ((target_stat.st_mode & S_IFMT) != (mountpoint_stat.st_mode & S_IFMT)) errx(EX_USAGE, "%s: must be same type as %s (file or directory)", mountpoint, target); build_iovec(&iov, &iovlen, "fstype", nullfs, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mountpoint, (size_t)-1); build_iovec(&iov, &iovlen, "target", target, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); if (nmount(iov, iovlen, 0) < 0) { if (errmsg[0] != 0) err(1, "%s: %s", mountpoint, errmsg); else err(1, "%s", mountpoint); } exit(0); } static void usage(void) { (void)fprintf(stderr, "usage: mount_nullfs [-o options] target mount-point\n"); exit(1); } diff --git a/sbin/mount_udf/mount_udf.c b/sbin/mount_udf/mount_udf.c index 2805872d41ad..0425c6a1378c 100644 --- a/sbin/mount_udf/mount_udf.c +++ b/sbin/mount_udf/mount_udf.c @@ -1,167 +1,166 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 2002 Scott Long * * This code is derived from software contributed to Berkeley * by Pace Willisson (pace@blitz.com). The Rock Ridge Extension * Support code is derived from software contributed to Berkeley * by Atsushi Murai (amurai@spec.co.jp). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * This is just a rip-off of mount_iso9660.c. It's been vastly simplified * because UDF doesn't take any options at this time. */ #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include -#include "mntopts.h" - static struct mntopt mopts[] = { MOPT_STDOPTS, MOPT_UPDATE, MOPT_END }; int set_charset(char **, char **, const char *); void usage(void); int main(int argc, char **argv) { char mntpath[MAXPATHLEN]; char fstype[] = "udf"; struct iovec *iov; char *cs_disk, *cs_local, *dev, *dir; int ch, iovlen, mntflags, udf_flags, verbose; iovlen = mntflags = udf_flags = verbose = 0; cs_disk = cs_local = NULL; iov = NULL; while ((ch = getopt(argc, argv, "o:vC:")) != -1) switch (ch) { case 'o': getmntopts(optarg, mopts, &mntflags, NULL); break; case 'v': verbose++; break; case 'C': if (set_charset(&cs_disk, &cs_local, optarg) == -1) err(EX_OSERR, "udf_iconv"); udf_flags |= UDFMNT_KICONV; break; case '?': default: usage(); } argc -= optind; argv += optind; if (argc != 2) usage(); dev = argv[0]; dir = argv[1]; /* * Resolve the mountpoint with realpath(3) and remove unnecessary * slashes from the devicename if there are any. */ if (checkpath(dir, mntpath) != 0) err(EX_USAGE, "%s", mntpath); (void)rmslashes(dev, dev); /* * UDF file systems are not writeable. */ mntflags |= MNT_RDONLY; build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", mntpath, (size_t)-1); build_iovec(&iov, &iovlen, "from", dev, (size_t)-1); build_iovec(&iov, &iovlen, "flags", &udf_flags, sizeof(udf_flags)); if (udf_flags & UDFMNT_KICONV) { build_iovec(&iov, &iovlen, "cs_disk", cs_disk, (size_t)-1); build_iovec(&iov, &iovlen, "cs_local", cs_local, (size_t)-1); } if (nmount(iov, iovlen, mntflags) < 0) err(1, "%s", dev); exit(0); } int set_charset(char **cs_disk, char **cs_local, const char *localcs) { int error; if (modfind("udf_iconv") < 0) if (kldload("udf_iconv") < 0 || modfind("udf_iconv") < 0) { warnx( "cannot find or load \"udf_iconv\" kernel module"); return (-1); } if ((*cs_disk = malloc(ICONV_CSNMAXLEN)) == NULL) return (-1); if ((*cs_local = malloc(ICONV_CSNMAXLEN)) == NULL) return (-1); strncpy(*cs_disk, ENCODING_UNICODE, ICONV_CSNMAXLEN); strncpy(*cs_local, localcs, ICONV_CSNMAXLEN); error = kiconv_add_xlat16_cspairs(*cs_disk, *cs_local); if (error) return (-1); return (0); } void usage(void) { (void)fprintf(stderr, "usage: mount_udf [-v] [-o options] [-C charset] special node\n"); exit(EX_USAGE); } diff --git a/sbin/mount_unionfs/mount_unionfs.c b/sbin/mount_unionfs/mount_unionfs.c index 28f224ae0f68..d207e5581745 100644 --- a/sbin/mount_unionfs/mount_unionfs.c +++ b/sbin/mount_unionfs/mount_unionfs.c @@ -1,182 +1,181 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. * Copyright (c) 2005, 2006 Masanori Ozawa , ONGS Inc. * Copyright (c) 2006 Daichi Goto * All rights reserved. * * This code is derived from software donated to Berkeley by * Jan-Simon Pendry. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include +#include #include #include #include #include #include #include #include -#include "mntopts.h" - static int subdir(const char *p, const char *dir) { int l; l = strlen(dir); if (l <= 1) return (1); if ((strncmp(p, dir, l) == 0) && (p[l] == '/' || p[l] == '\0')) return (1); return (0); } static void usage(void) { (void)fprintf(stderr, "usage: mount_unionfs [-o options] directory uniondir\n"); exit(EX_USAGE); } static void parse_gid(const char *s, char *buf, size_t bufsize) { struct group *gr; char *inval; if ((gr = getgrnam(s)) != NULL) snprintf(buf, bufsize, "%d", gr->gr_gid); else { strtol(s, &inval, 10); if (*inval != 0) { errx(EX_NOUSER, "unknown group id: %s", s); usage(); } else { strncpy(buf, s, bufsize); } } } static void parse_uid(const char *s, char *buf, size_t bufsize) { struct passwd *pw; char *inval; if ((pw = getpwnam(s)) != NULL) snprintf(buf, bufsize, "%d", pw->pw_uid); else { strtol(s, &inval, 10); if (*inval != 0) { errx(EX_NOUSER, "unknown user id: %s", s); usage(); } else { strncpy(buf, s, bufsize); } } } int main(int argc, char *argv[]) { struct iovec *iov; int ch, iovlen; char source [MAXPATHLEN], target[MAXPATHLEN], errmsg[255]; char uid_str[20], gid_str[20]; char fstype[] = "unionfs"; char *p, *val; iov = NULL; iovlen = 0; memset(errmsg, 0, sizeof(errmsg)); while ((ch = getopt(argc, argv, "bo:")) != -1) { switch (ch) { case 'b': printf("\n -b is deprecated. Use \"-o below\" instead\n"); build_iovec(&iov, &iovlen, "below", NULL, 0); break; case 'o': p = strchr(optarg, '='); val = NULL; if (p != NULL) { *p = '\0'; val = p + 1; if (strcmp(optarg, "gid") == 0) { parse_gid(val, gid_str, sizeof(gid_str)); val = gid_str; } else if (strcmp(optarg, "uid") == 0) { parse_uid(val, uid_str, sizeof(uid_str)); val = uid_str; } } build_iovec(&iov, &iovlen, optarg, val, (size_t)-1); break; case '?': default: usage(); /* NOTREACHED */ } } argc -= optind; argv += optind; if (argc != 2) usage(); /* resolve both target and source with realpath(3) */ if (checkpath(argv[0], target) != 0) err(EX_USAGE, "%s", target); if (checkpath(argv[1], source) != 0) err(EX_USAGE, "%s", source); if (subdir(target, source) || subdir(source, target)) errx(EX_USAGE, "%s (%s) and %s (%s) are not distinct paths", argv[0], target, argv[1], source); build_iovec(&iov, &iovlen, "fstype", fstype, (size_t)-1); build_iovec(&iov, &iovlen, "fspath", source, (size_t)-1); build_iovec(&iov, &iovlen, "from", target, (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); if (nmount(iov, iovlen, 0)) err(EX_OSERR, "%s: %s", source, errmsg); exit(0); } diff --git a/tests/sys/fs/fusefs/fallocate.cc b/tests/sys/fs/fusefs/fallocate.cc index a05760207648..4e5b047b78b7 100644 --- a/tests/sys/fs/fusefs/fallocate.cc +++ b/tests/sys/fs/fusefs/fallocate.cc @@ -1,780 +1,779 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Alan Somers * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include #include #include +#include // for build_iovec #include #include - -#include "mntopts.h" // for build_iovec } #include "mockfs.hh" #include "utils.hh" using namespace testing; /* Is buf all zero? */ static bool is_zero(const char *buf, uint64_t size) { return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1); } class Fallocate: public FuseTest { public: /* * expect VOP_DEALLOCATE to be implemented by vop_stddeallocate. */ void expect_vop_stddeallocate(uint64_t ino, uint64_t off, uint64_t length) { /* XXX read offset and size may depend on cache mode */ EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { return (in.header.opcode == FUSE_READ && in.header.nodeid == ino && in.body.read.offset <= off && in.body.read.offset + in.body.read.size >= off + length); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { assert(in.body.read.size <= sizeof(out.body.bytes)); out.header.len = sizeof(struct fuse_out_header) + in.body.read.size; memset(out.body.bytes, 'X', in.body.read.size); }))).RetiresOnSaturation(); EXPECT_CALL(*m_mock, process( ResultOf([=](auto in) { const char *buf = (const char*)in.body.bytes + sizeof(struct fuse_write_in); assert(length <= sizeof(in.body.bytes) - sizeof(struct fuse_write_in)); return (in.header.opcode == FUSE_WRITE && in.header.nodeid == ino && in.body.write.offset == off && in.body.write.size == length && is_zero(buf, length)); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, write); out.body.write.size = length; }))); } }; class Fspacectl: public Fallocate {}; class Fspacectl_7_18: public Fspacectl { public: virtual void SetUp() { m_kernel_minor_version = 18; Fspacectl::SetUp(); } }; class FspacectlCache: public Fspacectl, public WithParamInterface { public: bool m_direct_io; FspacectlCache(): m_direct_io(false) {}; virtual void SetUp() { int cache_mode = GetParam(); switch (cache_mode) { case Uncached: m_direct_io = true; break; case WritebackAsync: m_async = true; /* FALLTHROUGH */ case Writeback: m_init_flags |= FUSE_WRITEBACK_CACHE; /* FALLTHROUGH */ case Writethrough: break; default: FAIL() << "Unknown cache mode"; } FuseTest::SetUp(); if (IsSkipped()) return; } }; class PosixFallocate: public Fallocate { public: static sig_atomic_t s_sigxfsz; void SetUp() { s_sigxfsz = 0; FuseTest::SetUp(); } void TearDown() { struct sigaction sa; bzero(&sa, sizeof(sa)); sa.sa_handler = SIG_DFL; sigaction(SIGXFSZ, &sa, NULL); Fallocate::TearDown(); } }; sig_atomic_t PosixFallocate::s_sigxfsz = 0; void sigxfsz_handler(int __unused sig) { PosixFallocate::s_sigxfsz = 1; } class PosixFallocate_7_18: public PosixFallocate { public: virtual void SetUp() { m_kernel_minor_version = 18; PosixFallocate::SetUp(); } }; /* * If the server returns ENOSYS, it indicates that the server does not support * FUSE_FALLOCATE. This and future calls should fall back to vop_stddeallocate. */ TEST_F(Fspacectl, enosys) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; off_t fsize = 1 << 20; off_t off0 = 100; off_t len0 = 500; struct spacectl_range rqsr = { .r_offset = off0, .r_len = len0 }; uint64_t ino = 42; uint64_t off1 = fsize; uint64_t len1 = 1000; off_t off2 = fsize / 2; off_t len2 = 500; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, off0, len0, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, ENOSYS); expect_vop_stddeallocate(ino, off0, len0); expect_vop_stddeallocate(ino, off2, len2); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); /* Subsequent calls shouldn't query the daemon either */ rqsr.r_offset = off2; rqsr.r_len = len2; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); /* Neither should posix_fallocate query the daemon */ EXPECT_EQ(EINVAL, posix_fallocate(fd, off1, len1)); leak(fd); } /* * EOPNOTSUPP means "the file system does not support fallocate with the * supplied mode on this particular file". So we should fallback, but not * assume anything about whether the operation will fail on a different file or * with a different mode. */ TEST_F(Fspacectl, eopnotsupp) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr; uint64_t ino = 42; uint64_t fsize = 1 << 20; uint64_t off0 = 500; uint64_t len = 1000; uint64_t off1 = fsize / 2; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, off0, len, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, EOPNOTSUPP); expect_vop_stddeallocate(ino, off0, len); expect_fallocate(ino, off1, len, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, EOPNOTSUPP); expect_vop_stddeallocate(ino, off1, len); expect_fallocate(ino, fsize, len, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); /* * Though the FUSE daemon will reject the call, the kernel should fall * back to a read-modify-write approach. */ rqsr.r_offset = off0; rqsr.r_len = len; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); /* Subsequent calls should still query the daemon */ rqsr.r_offset = off1; rqsr.r_len = len; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); /* But subsequent posix_fallocate calls _should_ query the daemon */ EXPECT_EQ(0, posix_fallocate(fd, fsize, len)); leak(fd); } TEST_F(Fspacectl, erofs) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct statfs statbuf; uint64_t fsize = 2000; struct spacectl_range rqsr = { .r_offset = 0, .r_len = 1 }; struct iovec *iov = NULL; int iovlen = 0; uint64_t ino = 42; int fd; int newflags; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { /* * All of the fields except f_flags are don't care, and f_flags * is set by the VFS */ SET_OUT_HEADER_LEN(out, statfs); }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); /* Remount read-only */ ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); free_iovec(&iov, &iovlen); EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); EXPECT_EQ(EROFS, errno); leak(fd); } /* * If FUSE_GETATTR fails when determining the size of the file, fspacectl * should fail gracefully. This failure mode is easiest to trigger when * attribute caching is disabled. */ TEST_F(Fspacectl, getattr_fails) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; Sequence seq; struct spacectl_range rqsr; const uint64_t ino = 42; const uint64_t fsize = 2000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == ino); }, Eq(true)), _) ).Times(1) .InSequence(seq) .WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { SET_OUT_HEADER_LEN(out, attr); out.body.attr.attr.ino = ino; out.body.attr.attr.mode = S_IFREG | 0644; out.body.attr.attr.size = fsize; out.body.attr.attr_valid = 0; }))); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_GETATTR && in.header.nodeid == ino); }, Eq(true)), _) ).InSequence(seq) .WillOnce(ReturnErrno(EIO)); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); rqsr.r_offset = 500; rqsr.r_len = 1000; EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); EXPECT_EQ(EIO, errno); leak(fd); } TEST_F(Fspacectl, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr, rmsr; struct stat sb0, sb1; uint64_t ino = 42; uint64_t fsize = 2000; uint64_t offset = 500; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, offset, length, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); rqsr.r_offset = offset; rqsr.r_len = length; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); EXPECT_EQ(0, rmsr.r_len); EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); /* * The file's attributes should not have been invalidated, so this fstat * will not requery the daemon. */ EXPECT_EQ(0, fstat(fd, &sb1)); EXPECT_EQ(fsize, (uint64_t)sb1.st_size); /* mtime and ctime should be updated */ EXPECT_EQ(sb0.st_atime, sb1.st_atime); EXPECT_NE(sb0.st_mtime, sb1.st_mtime); EXPECT_NE(sb0.st_ctime, sb1.st_ctime); leak(fd); } /* The returned rqsr.r_off should be clipped at EoF */ TEST_F(Fspacectl, past_eof) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr, rmsr; uint64_t ino = 42; uint64_t fsize = 1000; uint64_t offset = 1500; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, offset, length, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); rqsr.r_offset = offset; rqsr.r_len = length; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); EXPECT_EQ(0, rmsr.r_len); EXPECT_EQ((off_t)fsize, rmsr.r_offset); leak(fd); } /* The returned rqsr.r_off should be clipped at EoF */ TEST_F(Fspacectl, spans_eof) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr, rmsr; uint64_t ino = 42; uint64_t fsize = 1000; uint64_t offset = 500; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, offset, length, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); rqsr.r_offset = offset; rqsr.r_len = length; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); EXPECT_EQ(0, rmsr.r_len); EXPECT_EQ((off_t)fsize, rmsr.r_offset); leak(fd); } /* * With older servers, no FUSE_FALLOCATE should be attempted. The kernel * should fall back to vop_stddeallocate. */ TEST_F(Fspacectl_7_18, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr, rmsr; char *buf; uint64_t ino = 42; uint64_t fsize = 2000; uint64_t offset = 500; uint64_t length = 1000; int fd; buf = new char[length]; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_vop_stddeallocate(ino, offset, length); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); rqsr.r_offset = offset; rqsr.r_len = length; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); EXPECT_EQ(0, rmsr.r_len); EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); leak(fd); delete[] buf; } /* * A successful fspacectl should clear the zeroed data from the kernel cache. */ TEST_P(FspacectlCache, clears_cache) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; struct spacectl_range rqsr, rmsr; uint64_t ino = 42; ssize_t bufsize = strlen(CONTENTS); uint64_t fsize = bufsize; uint8_t buf[bufsize]; char zbuf[bufsize]; uint64_t offset = 0; uint64_t length = bufsize; int fd; bzero(zbuf, bufsize); expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); /* NB: expectations are applied in LIFO order */ expect_read(ino, 0, fsize, fsize, zbuf); expect_read(ino, 0, fsize, fsize, CONTENTS); expect_fallocate(ino, offset, length, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); /* Populate the cache */ ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize)); /* Zero the file */ rqsr.r_offset = offset; rqsr.r_len = length; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); EXPECT_EQ(0, rmsr.r_len); EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); /* Read again. This should query the daemon */ ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) << strerror(errno); ASSERT_EQ(0, memcmp(buf, zbuf, fsize)); leak(fd); } INSTANTIATE_TEST_SUITE_P(FspacectlCache, FspacectlCache, Values(Uncached, Writethrough, Writeback) ); /* * If the server returns ENOSYS, it indicates that the server does not support * FUSE_FALLOCATE. This and future calls should return EINVAL. */ TEST_F(PosixFallocate, enosys) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; uint64_t off0 = 0; uint64_t len0 = 1000; off_t off1 = 100; off_t len1 = 200; uint64_t fsize = 500; struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 }; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, off0, len0, 0, ENOSYS); expect_vop_stddeallocate(ino, off1, len1); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); /* Subsequent calls shouldn't query the daemon*/ EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); /* Neither should VOP_DEALLOCATE query the daemon */ EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); leak(fd); } /* * EOPNOTSUPP means "the file system does not support fallocate with the * supplied mode on this particular file". So we should fallback, but not * assume anything about whether the operation will fail on a different file or * with a different mode. */ TEST_F(PosixFallocate, eopnotsupp) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct spacectl_range rqsr; uint64_t ino = 42; uint64_t fsize = 2000; uint64_t offset = 0; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); expect_open(ino, 0, 1); expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP); expect_fallocate(ino, offset, length, 0, EOPNOTSUPP); expect_fallocate(ino, offset, length, FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length)); /* Subsequent calls should still query the daemon*/ EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); /* And subsequent VOP_DEALLOCATE calls should also query the daemon */ rqsr.r_len = length; rqsr.r_offset = offset; EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); leak(fd); } /* EIO is not a permanent error, and may be retried */ TEST_F(PosixFallocate, eio) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; uint64_t offset = 0; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_open(ino, 0, 1); expect_fallocate(ino, offset, length, 0, EIO); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(EIO, posix_fallocate(fd, offset, length)); expect_fallocate(ino, offset, length, 0, 0); EXPECT_EQ(0, posix_fallocate(fd, offset, length)); leak(fd); } TEST_F(PosixFallocate, erofs) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct statfs statbuf; struct iovec *iov = NULL; int iovlen = 0; uint64_t ino = 42; uint64_t offset = 0; uint64_t length = 1000; int fd; int newflags; expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_open(ino, 0, 1); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { /* * All of the fields except f_flags are don't care, and f_flags * is set by the VFS */ SET_OUT_HEADER_LEN(out, statfs); }))); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); /* Remount read-only */ ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); free_iovec(&iov, &iovlen); EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length)); leak(fd); } TEST_F(PosixFallocate, ok) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct stat sb0, sb1; uint64_t ino = 42; uint64_t offset = 0; uint64_t length = 1000; int fd; EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, entry); out.body.entry.attr.mode = S_IFREG | 0644; out.body.entry.nodeid = ino; out.body.entry.entry_valid = UINT64_MAX; out.body.entry.attr_valid = UINT64_MAX; }))); expect_open(ino, 0, 1); expect_fallocate(ino, offset, length, 0, 0); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); EXPECT_EQ(0, posix_fallocate(fd, offset, length)); /* * Despite the originally cached file size of zero, stat should now * return either the new size or requery the daemon. */ EXPECT_EQ(0, stat(FULLPATH, &sb1)); EXPECT_EQ(length, (uint64_t)sb1.st_size); /* mtime and ctime should be updated */ EXPECT_EQ(sb0.st_atime, sb1.st_atime); EXPECT_NE(sb0.st_mtime, sb1.st_mtime); EXPECT_NE(sb0.st_ctime, sb1.st_ctime); leak(fd); } /* fusefs should respect RLIMIT_FSIZE */ TEST_F(PosixFallocate, rlimit_fsize) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; struct rlimit rl; uint64_t ino = 42; uint64_t offset = 0; uint64_t length = 1'000'000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_open(ino, 0, 1); rl.rlim_cur = length / 2; rl.rlim_max = 10 * length; ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length)); EXPECT_EQ(1, s_sigxfsz); leak(fd); } /* With older servers, no FUSE_FALLOCATE should be attempted */ TEST_F(PosixFallocate_7_18, einval) { const char FULLPATH[] = "mountpoint/some_file.txt"; const char RELPATH[] = "some_file.txt"; uint64_t ino = 42; uint64_t offset = 0; uint64_t length = 1000; int fd; expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); expect_open(ino, 0, 1); fd = open(FULLPATH, O_RDWR); ASSERT_LE(0, fd) << strerror(errno); EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); leak(fd); } diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc index 35ae6c207229..b1621d05703c 100644 --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -1,1055 +1,1054 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include #include #include #include #include #include +#include // for build_iovec #include #include #include #include #include - -#include "mntopts.h" // for build_iovec } #include #include #include "mockfs.hh" using namespace testing; int verbosity = 0; const char* opcode2opname(uint32_t opcode) { const char* table[] = { "Unknown (opcode 0)", "LOOKUP", "FORGET", "GETATTR", "SETATTR", "READLINK", "SYMLINK", "Unknown (opcode 7)", "MKNOD", "MKDIR", "UNLINK", "RMDIR", "RENAME", "LINK", "OPEN", "READ", "WRITE", "STATFS", "RELEASE", "Unknown (opcode 19)", "FSYNC", "SETXATTR", "GETXATTR", "LISTXATTR", "REMOVEXATTR", "FLUSH", "INIT", "OPENDIR", "READDIR", "RELEASEDIR", "FSYNCDIR", "GETLK", "SETLK", "SETLKW", "ACCESS", "CREATE", "INTERRUPT", "BMAP", "DESTROY", "IOCTL", "POLL", "NOTIFY_REPLY", "BATCH_FORGET", "FALLOCATE", "READDIRPLUS", "RENAME2", "LSEEK", "COPY_FILE_RANGE", }; if (opcode >= nitems(table)) return ("Unknown (opcode > max)"); else return (table[opcode]); } ProcessMockerT ReturnErrno(int error) { return([=](auto in, auto &out) { std::unique_ptr out0(new mockfs_buf_out); out0->header.unique = in.header.unique; out0->header.error = -error; out0->header.len = sizeof(out0->header); out.push_back(std::move(out0)); }); } /* Helper function used for returning negative cache entries for LOOKUP */ ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid) { return([=](auto in, auto &out) { /* nodeid means ENOENT and cache it */ std::unique_ptr out0(new mockfs_buf_out); out0->body.entry.nodeid = 0; out0->header.unique = in.header.unique; out0->header.error = 0; out0->body.entry.entry_valid = entry_valid->tv_sec; out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec; SET_OUT_HEADER_LEN(*out0, entry); out.push_back(std::move(out0)); }); } ProcessMockerT ReturnImmediate(std::function f) { return([=](auto& in, auto &out) { std::unique_ptr out0(new mockfs_buf_out); out0->header.unique = in.header.unique; f(in, *out0); out.push_back(std::move(out0)); }); } void sigint_handler(int __unused sig) { // Don't do anything except interrupt the daemon's read(2) call } void MockFS::debug_request(const mockfs_buf_in &in, ssize_t buflen) { printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode), in.header.nodeid); if (verbosity > 1) { printf(" uid=%5u gid=%5u pid=%5u unique=%" PRIu64 " len=%u" " buflen=%zd", in.header.uid, in.header.gid, in.header.pid, in.header.unique, in.header.len, buflen); } switch (in.header.opcode) { const char *name, *value; case FUSE_ACCESS: printf(" mask=%#x", in.body.access.mask); break; case FUSE_BMAP: printf(" block=%" PRIx64 " blocksize=%#x", in.body.bmap.block, in.body.bmap.blocksize); break; case FUSE_COPY_FILE_RANGE: printf(" off_in=%" PRIu64 " ino_out=%" PRIu64 " off_out=%" PRIu64 " size=%" PRIu64, in.body.copy_file_range.off_in, in.body.copy_file_range.nodeid_out, in.body.copy_file_range.off_out, in.body.copy_file_range.len); if (verbosity > 1) printf(" fh_in=%" PRIu64 " fh_out=%" PRIu64 " flags=%" PRIx64, in.body.copy_file_range.fh_in, in.body.copy_file_range.fh_out, in.body.copy_file_range.flags); break; case FUSE_CREATE: if (m_kernel_minor_version >= 12) name = (const char*)in.body.bytes + sizeof(fuse_create_in); else name = (const char*)in.body.bytes + sizeof(fuse_open_in); printf(" flags=%#x name=%s", in.body.open.flags, name); break; case FUSE_FALLOCATE: printf(" fh=%#" PRIx64 " offset=%" PRIu64 " length=%" PRIx64 " mode=%#x", in.body.fallocate.fh, in.body.fallocate.offset, in.body.fallocate.length, in.body.fallocate.mode); break; case FUSE_FLUSH: printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64, in.body.flush.fh, in.body.flush.lock_owner); break; case FUSE_FORGET: printf(" nlookup=%" PRIu64, in.body.forget.nlookup); break; case FUSE_FSYNC: printf(" flags=%#x", in.body.fsync.fsync_flags); break; case FUSE_FSYNCDIR: printf(" flags=%#x", in.body.fsyncdir.fsync_flags); break; case FUSE_GETLK: printf(" fh=%#" PRIx64 " type=%u pid=%u", in.body.getlk.fh, in.body.getlk.lk.type, in.body.getlk.lk.pid); if (verbosity >= 2) { printf(" range=[%" PRIi64 ":%" PRIi64 "]", in.body.getlk.lk.start, in.body.getlk.lk.end); } break; case FUSE_INTERRUPT: printf(" unique=%" PRIu64, in.body.interrupt.unique); break; case FUSE_LINK: printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid); break; case FUSE_LISTXATTR: printf(" size=%" PRIu32, in.body.listxattr.size); break; case FUSE_LOOKUP: printf(" %s", in.body.lookup); break; case FUSE_LSEEK: switch (in.body.lseek.whence) { case SEEK_HOLE: printf(" SEEK_HOLE offset=%jd", in.body.lseek.offset); break; case SEEK_DATA: printf(" SEEK_DATA offset=%jd", in.body.lseek.offset); break; default: printf(" whence=%u offset=%jd", in.body.lseek.whence, in.body.lseek.offset); break; } break; case FUSE_MKDIR: name = (const char*)in.body.bytes + sizeof(fuse_mkdir_in); printf(" name=%s mode=%#o umask=%#o", name, in.body.mkdir.mode, in.body.mkdir.umask); break; case FUSE_MKNOD: if (m_kernel_minor_version >= 12) name = (const char*)in.body.bytes + sizeof(fuse_mknod_in); else name = (const char*)in.body.bytes + FUSE_COMPAT_MKNOD_IN_SIZE; printf(" mode=%#o rdev=%x umask=%#o name=%s", in.body.mknod.mode, in.body.mknod.rdev, in.body.mknod.umask, name); break; case FUSE_OPEN: printf(" flags=%#x", in.body.open.flags); break; case FUSE_OPENDIR: printf(" flags=%#x", in.body.opendir.flags); break; case FUSE_READ: printf(" offset=%" PRIu64 " size=%u", in.body.read.offset, in.body.read.size); if (verbosity > 1) printf(" flags=%#x", in.body.read.flags); break; case FUSE_READDIR: printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u", in.body.readdir.fh, in.body.readdir.offset, in.body.readdir.size); break; case FUSE_RELEASE: printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64, in.body.release.fh, in.body.release.flags, in.body.release.lock_owner); break; case FUSE_RENAME: { const char *src = (const char*)in.body.bytes + sizeof(fuse_rename_in); const char *dst = src + strlen(src) + 1; printf(" src=%s newdir=%" PRIu64 " dst=%s", src, in.body.rename.newdir, dst); } break; case FUSE_SETATTR: if (verbosity <= 1) { printf(" valid=%#x", in.body.setattr.valid); break; } if (in.body.setattr.valid & FATTR_MODE) printf(" mode=%#o", in.body.setattr.mode); if (in.body.setattr.valid & FATTR_UID) printf(" uid=%u", in.body.setattr.uid); if (in.body.setattr.valid & FATTR_GID) printf(" gid=%u", in.body.setattr.gid); if (in.body.setattr.valid & FATTR_SIZE) printf(" size=%" PRIu64, in.body.setattr.size); if (in.body.setattr.valid & FATTR_ATIME) printf(" atime=%" PRIu64 ".%u", in.body.setattr.atime, in.body.setattr.atimensec); if (in.body.setattr.valid & FATTR_MTIME) printf(" mtime=%" PRIu64 ".%u", in.body.setattr.mtime, in.body.setattr.mtimensec); if (in.body.setattr.valid & FATTR_FH) printf(" fh=%" PRIu64 "", in.body.setattr.fh); break; case FUSE_SETLK: printf(" fh=%#" PRIx64 " owner=%" PRIu64 " type=%u pid=%u", in.body.setlk.fh, in.body.setlk.owner, in.body.setlk.lk.type, in.body.setlk.lk.pid); if (verbosity >= 2) { printf(" range=[%" PRIi64 ":%" PRIi64 "]", in.body.setlk.lk.start, in.body.setlk.lk.end); } break; case FUSE_SETXATTR: /* * In theory neither the xattr name and value need be * ASCII, but in this test suite they always are. */ name = (const char*)in.body.bytes + sizeof(fuse_setxattr_in); value = name + strlen(name) + 1; printf(" %s=%s", name, value); break; case FUSE_WRITE: printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u write_flags=%u", in.body.write.fh, in.body.write.offset, in.body.write.size, in.body.write.write_flags); if (verbosity > 1) printf(" flags=%#x", in.body.write.flags); break; default: break; } printf("\n"); } /* * Debug a FUSE response. * * This is mostly useful for asynchronous notifications, which don't correspond * to any request */ void MockFS::debug_response(const mockfs_buf_out &out) { const char *name; if (verbosity == 0) return; switch (out.header.error) { case FUSE_NOTIFY_INVAL_ENTRY: name = (const char*)out.body.bytes + sizeof(fuse_notify_inval_entry_out); printf("<- INVAL_ENTRY parent=%" PRIu64 " %s\n", out.body.inval_entry.parent, name); break; case FUSE_NOTIFY_INVAL_INODE: printf("<- INVAL_INODE ino=%" PRIu64 " off=%" PRIi64 " len=%" PRIi64 "\n", out.body.inval_inode.ino, out.body.inval_inode.off, out.body.inval_inode.len); break; case FUSE_NOTIFY_STORE: printf("<- STORE ino=%" PRIu64 " off=%" PRIu64 " size=%" PRIu32 "\n", out.body.store.nodeid, out.body.store.offset, out.body.store.size); break; default: break; } } MockFS::MockFS(int max_read, int max_readahead, bool allow_other, bool default_permissions, bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags, uint32_t kernel_minor_version, uint32_t max_write, bool async, bool noclusterr, unsigned time_gran, bool nointr, bool noatime, const char *fsname, const char *subtype) : m_daemon_id(NULL), m_kernel_minor_version(kernel_minor_version), m_kq(pm == KQ ? kqueue() : -1), m_maxread(max_read), m_maxreadahead(max_readahead), m_pid(getpid()), m_uniques(new std::unordered_set), m_pm(pm), m_time_gran(time_gran), m_child_pid(-1), m_maxwrite(MIN(max_write, max_max_write)), m_nready(-1), m_quit(false) { struct sigaction sa; struct iovec *iov = NULL; int iovlen = 0; char fdstr[15]; const bool trueval = true; /* * Kyua sets pwd to a testcase-unique tempdir; no need to use * mkdtemp */ /* * googletest doesn't allow ASSERT_ in constructors, so we must throw * instead. */ if (mkdir("mountpoint" , 0755) && errno != EEXIST) throw(std::system_error(errno, std::system_category(), "Couldn't make mountpoint directory")); switch (m_pm) { case BLOCKING: m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR); break; default: m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK); break; } if (m_fuse_fd < 0) throw(std::system_error(errno, std::system_category(), "Couldn't open /dev/fuse")); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, "mountpoint"), -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); sprintf(fdstr, "%d", m_fuse_fd); build_iovec(&iov, &iovlen, "fd", fdstr, -1); if (m_maxread > 0) { char val[10]; snprintf(val, sizeof(val), "%d", m_maxread); build_iovec(&iov, &iovlen, "max_read=", &val, -1); } if (allow_other) { build_iovec(&iov, &iovlen, "allow_other", __DECONST(void*, &trueval), sizeof(bool)); } if (default_permissions) { build_iovec(&iov, &iovlen, "default_permissions", __DECONST(void*, &trueval), sizeof(bool)); } if (push_symlinks_in) { build_iovec(&iov, &iovlen, "push_symlinks_in", __DECONST(void*, &trueval), sizeof(bool)); } if (ro) { build_iovec(&iov, &iovlen, "ro", __DECONST(void*, &trueval), sizeof(bool)); } if (async) { build_iovec(&iov, &iovlen, "async", __DECONST(void*, &trueval), sizeof(bool)); } if (noatime) { build_iovec(&iov, &iovlen, "noatime", __DECONST(void*, &trueval), sizeof(bool)); } if (noclusterr) { build_iovec(&iov, &iovlen, "noclusterr", __DECONST(void*, &trueval), sizeof(bool)); } if (nointr) { build_iovec(&iov, &iovlen, "nointr", __DECONST(void*, &trueval), sizeof(bool)); } else { build_iovec(&iov, &iovlen, "intr", __DECONST(void*, &trueval), sizeof(bool)); } if (*fsname) { build_iovec(&iov, &iovlen, "fsname=", __DECONST(void*, fsname), -1); } if (*subtype) { build_iovec(&iov, &iovlen, "subtype=", __DECONST(void*, subtype), -1); } if (nmount(iov, iovlen, 0)) throw(std::system_error(errno, std::system_category(), "Couldn't mount filesystem")); free_iovec(&iov, &iovlen); // Setup default handler ON_CALL(*this, process(_, _)) .WillByDefault(Invoke(this, &MockFS::process_default)); init(flags); bzero(&sa, sizeof(sa)); sa.sa_handler = sigint_handler; sa.sa_flags = 0; /* Don't set SA_RESTART! */ if (0 != sigaction(SIGUSR1, &sa, NULL)) throw(std::system_error(errno, std::system_category(), "Couldn't handle SIGUSR1")); if (pthread_create(&m_daemon_id, NULL, service, (void*)this)) throw(std::system_error(errno, std::system_category(), "Couldn't Couldn't start fuse thread")); } MockFS::~MockFS() { kill_daemon(); if (m_daemon_id != NULL) { pthread_join(m_daemon_id, NULL); m_daemon_id = NULL; } ::unmount("mountpoint", MNT_FORCE); rmdir("mountpoint"); if (m_kq >= 0) close(m_kq); } void MockFS::audit_request(const mockfs_buf_in &in, ssize_t buflen) { uint32_t inlen = in.header.len; size_t fih = sizeof(in.header); switch (in.header.opcode) { case FUSE_LOOKUP: case FUSE_RMDIR: case FUSE_SYMLINK: case FUSE_UNLINK: EXPECT_GT(inlen, fih) << "Missing request filename"; // No redundant information for checking buflen break; case FUSE_FORGET: EXPECT_EQ(inlen, fih + sizeof(in.body.forget)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_GETATTR: EXPECT_EQ(inlen, fih + sizeof(in.body.getattr)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_SETATTR: EXPECT_EQ(inlen, fih + sizeof(in.body.setattr)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_READLINK: EXPECT_EQ(inlen, fih) << "Unexpected request body"; EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_MKNOD: { size_t s; if (m_kernel_minor_version >= 12) s = sizeof(in.body.mknod); else s = FUSE_COMPAT_MKNOD_IN_SIZE; EXPECT_GE(inlen, fih + s) << "Missing request body"; EXPECT_GT(inlen, fih + s) << "Missing request filename"; // No redundant information for checking buflen break; } case FUSE_MKDIR: EXPECT_GE(inlen, fih + sizeof(in.body.mkdir)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.mkdir)) << "Missing request filename"; // No redundant information for checking buflen break; case FUSE_RENAME: EXPECT_GE(inlen, fih + sizeof(in.body.rename)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.rename)) << "Missing request filename"; // No redundant information for checking buflen break; case FUSE_LINK: EXPECT_GE(inlen, fih + sizeof(in.body.link)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.link)) << "Missing request filename"; // No redundant information for checking buflen break; case FUSE_OPEN: EXPECT_EQ(inlen, fih + sizeof(in.body.open)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_READ: EXPECT_EQ(inlen, fih + sizeof(in.body.read)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_WRITE: { size_t s; if (m_kernel_minor_version >= 9) s = sizeof(in.body.write); else s = FUSE_COMPAT_WRITE_IN_SIZE; // I suppose a 0-byte write should be allowed EXPECT_GE(inlen, fih + s) << "Missing request body"; EXPECT_EQ((size_t)buflen, fih + s + in.body.write.size); break; } case FUSE_DESTROY: case FUSE_STATFS: EXPECT_EQ(inlen, fih); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_RELEASE: EXPECT_EQ(inlen, fih + sizeof(in.body.release)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_FSYNC: case FUSE_FSYNCDIR: EXPECT_EQ(inlen, fih + sizeof(in.body.fsync)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_SETXATTR: EXPECT_GE(inlen, fih + sizeof(in.body.setxattr)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.setxattr)) << "Missing request attribute name"; // No redundant information for checking buflen break; case FUSE_GETXATTR: EXPECT_GE(inlen, fih + sizeof(in.body.getxattr)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.getxattr)) << "Missing request attribute name"; // No redundant information for checking buflen break; case FUSE_LISTXATTR: EXPECT_EQ(inlen, fih + sizeof(in.body.listxattr)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_REMOVEXATTR: EXPECT_GT(inlen, fih) << "Missing request attribute name"; // No redundant information for checking buflen break; case FUSE_FLUSH: EXPECT_EQ(inlen, fih + sizeof(in.body.flush)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_INIT: EXPECT_EQ(inlen, fih + sizeof(in.body.init)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_OPENDIR: EXPECT_EQ(inlen, fih + sizeof(in.body.opendir)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_READDIR: EXPECT_EQ(inlen, fih + sizeof(in.body.readdir)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_RELEASEDIR: EXPECT_EQ(inlen, fih + sizeof(in.body.releasedir)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_GETLK: EXPECT_EQ(inlen, fih + sizeof(in.body.getlk)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_SETLK: case FUSE_SETLKW: EXPECT_EQ(inlen, fih + sizeof(in.body.setlk)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_ACCESS: EXPECT_EQ(inlen, fih + sizeof(in.body.access)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_CREATE: EXPECT_GE(inlen, fih + sizeof(in.body.create)) << "Missing request body"; EXPECT_GT(inlen, fih + sizeof(in.body.create)) << "Missing request filename"; // No redundant information for checking buflen break; case FUSE_INTERRUPT: EXPECT_EQ(inlen, fih + sizeof(in.body.interrupt)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_FALLOCATE: EXPECT_EQ(inlen, fih + sizeof(in.body.fallocate)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_BMAP: EXPECT_EQ(inlen, fih + sizeof(in.body.bmap)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_LSEEK: EXPECT_EQ(inlen, fih + sizeof(in.body.lseek)); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_COPY_FILE_RANGE: EXPECT_EQ(inlen, fih + sizeof(in.body.copy_file_range)); EXPECT_EQ(0ul, in.body.copy_file_range.flags); EXPECT_EQ((size_t)buflen, inlen); break; case FUSE_NOTIFY_REPLY: case FUSE_BATCH_FORGET: case FUSE_IOCTL: case FUSE_POLL: case FUSE_READDIRPLUS: FAIL() << "Unsupported opcode?"; default: FAIL() << "Unknown opcode " << in.header.opcode; } /* Verify that the ticket's unique value is actually unique. */ if (m_uniques->find(in.header.unique) != m_uniques->end()) FAIL() << "Non-unique \"unique\" value"; m_uniques->insert(in.header.unique); } void MockFS::init(uint32_t flags) { ssize_t buflen; std::unique_ptr in(new mockfs_buf_in); std::unique_ptr out(new mockfs_buf_out); read_request(*in, buflen); if (verbosity > 0) debug_request(*in, buflen); audit_request(*in, buflen); ASSERT_EQ(FUSE_INIT, in->header.opcode); out->header.unique = in->header.unique; out->header.error = 0; out->body.init.major = FUSE_KERNEL_VERSION; out->body.init.minor = m_kernel_minor_version;; out->body.init.flags = in->body.init.flags & flags; out->body.init.max_write = m_maxwrite; out->body.init.max_readahead = m_maxreadahead; if (m_kernel_minor_version < 23) { SET_OUT_HEADER_LEN(*out, init_7_22); } else { out->body.init.time_gran = m_time_gran; SET_OUT_HEADER_LEN(*out, init); } write(m_fuse_fd, out.get(), out->header.len); } void MockFS::kill_daemon() { m_quit = true; if (m_daemon_id != NULL) pthread_kill(m_daemon_id, SIGUSR1); // Closing the /dev/fuse file descriptor first allows unmount to // succeed even if the daemon doesn't correctly respond to commands // during the unmount sequence. close(m_fuse_fd); m_fuse_fd = -1; } void MockFS::loop() { std::vector> out; std::unique_ptr in(new mockfs_buf_in); ASSERT_TRUE(in != NULL); while (!m_quit) { ssize_t buflen; bzero(in.get(), sizeof(*in)); read_request(*in, buflen); if (m_quit) break; if (verbosity > 0) debug_request(*in, buflen); audit_request(*in, buflen); if (pid_ok((pid_t)in->header.pid)) { process(*in, out); } else { /* * Reject any requests from unknown processes. Because * we actually do mount a filesystem, plenty of * unrelated system daemons may try to access it. */ if (verbosity > 1) printf("\tREJECTED (wrong pid %d)\n", in->header.pid); process_default(*in, out); } for (auto &it: out) write_response(*it); out.clear(); } } int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen) { std::unique_ptr out(new mockfs_buf_out); out->header.unique = 0; /* 0 means asynchronous notification */ out->header.error = FUSE_NOTIFY_INVAL_ENTRY; out->body.inval_entry.parent = parent; out->body.inval_entry.namelen = namelen; strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry), name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry)); out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) + namelen; debug_response(*out); write_response(*out); return 0; } int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len) { std::unique_ptr out(new mockfs_buf_out); out->header.unique = 0; /* 0 means asynchronous notification */ out->header.error = FUSE_NOTIFY_INVAL_INODE; out->body.inval_inode.ino = ino; out->body.inval_inode.off = off; out->body.inval_inode.len = len; out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode); debug_response(*out); write_response(*out); return 0; } int MockFS::notify_store(ino_t ino, off_t off, const void* data, ssize_t size) { std::unique_ptr out(new mockfs_buf_out); out->header.unique = 0; /* 0 means asynchronous notification */ out->header.error = FUSE_NOTIFY_STORE; out->body.store.nodeid = ino; out->body.store.offset = off; out->body.store.size = size; bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size); out->header.len = sizeof(out->header) + sizeof(out->body.store) + size; debug_response(*out); write_response(*out); return 0; } bool MockFS::pid_ok(pid_t pid) { if (pid == m_pid) { return (true); } else if (pid == m_child_pid) { return (true); } else { struct kinfo_proc *ki; bool ok = false; ki = kinfo_getproc(pid); if (ki == NULL) return (false); /* * Allow access by the aio daemon processes so that our tests * can use aio functions */ if (0 == strncmp("aiod", ki->ki_comm, 4)) ok = true; free(ki); return (ok); } } void MockFS::process_default(const mockfs_buf_in& in, std::vector> &out) { std::unique_ptr out0(new mockfs_buf_out); out0->header.unique = in.header.unique; out0->header.error = -EOPNOTSUPP; out0->header.len = sizeof(out0->header); out.push_back(std::move(out0)); } void MockFS::read_request(mockfs_buf_in &in, ssize_t &res) { int nready = 0; fd_set readfds; pollfd fds[1]; struct kevent changes[1]; struct kevent events[1]; struct timespec timeout_ts; struct timeval timeout_tv; const int timeout_ms = 999; int timeout_int, nfds; int fuse_fd; switch (m_pm) { case BLOCKING: break; case KQ: timeout_ts.tv_sec = 0; timeout_ts.tv_nsec = timeout_ms * 1'000'000; while (nready == 0) { EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, 0); nready = kevent(m_kq, &changes[0], 1, &events[0], 1, &timeout_ts); if (m_quit) return; } ASSERT_LE(0, nready) << strerror(errno); ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd); if (events[0].flags & EV_ERROR) FAIL() << strerror(events[0].data); else if (events[0].flags & EV_EOF) FAIL() << strerror(events[0].fflags); m_nready = events[0].data; break; case POLL: timeout_int = timeout_ms; fds[0].fd = m_fuse_fd; fds[0].events = POLLIN; while (nready == 0) { nready = poll(fds, 1, timeout_int); if (m_quit) return; } ASSERT_LE(0, nready) << strerror(errno); ASSERT_TRUE(fds[0].revents & POLLIN); break; case SELECT: fuse_fd = m_fuse_fd; if (fuse_fd < 0) break; timeout_tv.tv_sec = 0; timeout_tv.tv_usec = timeout_ms * 1'000; nfds = fuse_fd + 1; while (nready == 0) { FD_ZERO(&readfds); FD_SET(fuse_fd, &readfds); nready = select(nfds, &readfds, NULL, NULL, &timeout_tv); if (m_quit) return; } ASSERT_LE(0, nready) << strerror(errno); ASSERT_TRUE(FD_ISSET(fuse_fd, &readfds)); break; default: FAIL() << "not yet implemented"; } res = read(m_fuse_fd, &in, sizeof(in)); if (res < 0 && !m_quit) { m_quit = true; FAIL() << "read: " << strerror(errno); } ASSERT_TRUE(res >= static_cast(sizeof(in.header)) || m_quit); /* * Inconsistently, fuse_in_header.len is the size of the entire * request,including header, even though fuse_out_header.len excludes * the size of the header. */ ASSERT_TRUE(res == static_cast(in.header.len) || m_quit); } void MockFS::write_response(const mockfs_buf_out &out) { fd_set writefds; pollfd fds[1]; struct kevent changes[1]; struct kevent events[1]; int nready, nfds; ssize_t r; switch (m_pm) { case BLOCKING: break; case KQ: EV_SET(&changes[0], m_fuse_fd, EVFILT_WRITE, EV_ADD | EV_ONESHOT, 0, 0, 0); nready = kevent(m_kq, &changes[0], 1, &events[0], 1, NULL); ASSERT_LE(0, nready) << strerror(errno); ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd); if (events[0].flags & EV_ERROR) FAIL() << strerror(events[0].data); else if (events[0].flags & EV_EOF) FAIL() << strerror(events[0].fflags); m_nready = events[0].data; break; case POLL: fds[0].fd = m_fuse_fd; fds[0].events = POLLOUT; nready = poll(fds, 1, INFTIM); ASSERT_LE(0, nready) << strerror(errno); ASSERT_EQ(1, nready) << "NULL timeout expired?"; ASSERT_TRUE(fds[0].revents & POLLOUT); break; case SELECT: FD_ZERO(&writefds); FD_SET(m_fuse_fd, &writefds); nfds = m_fuse_fd + 1; nready = select(nfds, NULL, &writefds, NULL, NULL); ASSERT_LE(0, nready) << strerror(errno); ASSERT_EQ(1, nready) << "NULL timeout expired?"; ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds)); break; default: FAIL() << "not yet implemented"; } r = write(m_fuse_fd, &out, out.header.len); if (out.expected_errno) { ASSERT_EQ(-1, r); ASSERT_EQ(out.expected_errno, errno) << strerror(errno); } else { if (r <= 0 && errno == EINVAL) { printf("Failed to write response. unique=%" PRIu64 ":\n", out.header.unique); } ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno); } } void* MockFS::service(void *pthr_data) { MockFS *mock_fs = (MockFS*)pthr_data; mock_fs->loop(); return (NULL); } void MockFS::unmount() { ::unmount("mountpoint", 0); } diff --git a/tests/sys/fs/fusefs/mount.cc b/tests/sys/fs/fusefs/mount.cc index 7a8d2c1396f0..ece518b09f66 100644 --- a/tests/sys/fs/fusefs/mount.cc +++ b/tests/sys/fs/fusefs/mount.cc @@ -1,200 +1,200 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019 The FreeBSD Foundation * * This software was developed by BFF Storage Systems, LLC under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ extern "C" { #include #include #include -#include "mntopts.h" // for build_iovec +#include // for build_iovec } #include "mockfs.hh" #include "utils.hh" using namespace testing; class Mount: public FuseTest { public: void expect_statfs() { EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { SET_OUT_HEADER_LEN(out, statfs); }))); } }; class Fsname: public Mount { void SetUp() { m_fsname = "http://something"; Mount::SetUp(); } }; class Subtype: public Mount { void SetUp() { m_subtype = "myfs"; Mount::SetUp(); } }; class UpdateOk: public Mount, public WithParamInterface {}; class UpdateErr: public Mount, public WithParamInterface {}; int mntflag_from_string(const char *s) { if (0 == strcmp("MNT_RDONLY", s)) return MNT_RDONLY; else if (0 == strcmp("MNT_NOEXEC", s)) return MNT_NOEXEC; else if (0 == strcmp("MNT_NOSUID", s)) return MNT_NOSUID; else if (0 == strcmp("MNT_NOATIME", s)) return MNT_NOATIME; else if (0 == strcmp("MNT_SUIDDIR", s)) return MNT_SUIDDIR; else if (0 == strcmp("MNT_USER", s)) return MNT_USER; else return 0; } TEST_F(Fsname, fsname) { struct statfs statbuf; expect_statfs(); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); ASSERT_STREQ("http://something", statbuf.f_mntfromname); } TEST_F(Subtype, subtype) { struct statfs statbuf; expect_statfs(); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); ASSERT_STREQ("fusefs.myfs", statbuf.f_fstypename); } /* Some mount options can be changed by mount -u */ TEST_P(UpdateOk, update) { struct statfs statbuf; struct iovec *iov = NULL; int iovlen = 0; int flag; int newflags = MNT_UPDATE | MNT_SYNCHRONOUS; flag = mntflag_from_string(GetParam()); if (flag == MNT_NOSUID && 0 != geteuid()) GTEST_SKIP() << "Only root may clear MNT_NOSUID"; if (flag == MNT_SUIDDIR && 0 != geteuid()) GTEST_SKIP() << "Only root may set MNT_SUIDDIR"; EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { /* * All of the fields except f_flags are don't care, and f_flags is set by * the VFS */ SET_OUT_HEADER_LEN(out, statfs); }))); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); newflags = (statbuf.f_flags | MNT_UPDATE) ^ flag; build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); EXPECT_FALSE((newflags ^ statbuf.f_flags) & flag); } /* Some mount options cannnot be changed by mount -u */ TEST_P(UpdateErr, update) { struct statfs statbuf; struct iovec *iov = NULL; int iovlen = 0; int flag; int newflags = MNT_UPDATE | MNT_SYNCHRONOUS; flag = mntflag_from_string(GetParam()); EXPECT_CALL(*m_mock, process( ResultOf([](auto in) { return (in.header.opcode == FUSE_STATFS); }, Eq(true)), _) ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { /* * All of the fields except f_flags are don't care, and f_flags is set by * the VFS */ SET_OUT_HEADER_LEN(out, statfs); }))); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); newflags = (statbuf.f_flags | MNT_UPDATE) ^ flag; build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); /* * Don't check nmount's return value, because vfs_domount may "fix" the * options for us. The important thing is to check the final value of * statbuf.f_flags below. */ (void)nmount(iov, iovlen, newflags); ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); EXPECT_TRUE((newflags ^ statbuf.f_flags) & flag); } INSTANTIATE_TEST_SUITE_P(Mount, UpdateOk, ::testing::Values("MNT_RDONLY", "MNT_NOEXEC", "MNT_NOSUID", "MNT_NOATIME", "MNT_SUIDDIR") ); INSTANTIATE_TEST_SUITE_P(Mount, UpdateErr, ::testing::Values( "MNT_USER") ); diff --git a/usr.sbin/autofs/automount.c b/usr.sbin/autofs/automount.c index 32aa2300d094..5cd7106b1161 100644 --- a/usr.sbin/autofs/automount.c +++ b/usr.sbin/autofs/automount.c @@ -1,413 +1,413 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 The FreeBSD Foundation * * This software was developed by Edward Tomasz Napierala under sponsorship * from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include "common.h" -#include "mntopts.h" static int unmount_by_statfs(const struct statfs *sb, bool force) { char *fsid_str; int error, ret, flags; ret = asprintf(&fsid_str, "FSID:%d:%d", sb->f_fsid.val[0], sb->f_fsid.val[1]); if (ret < 0) log_err(1, "asprintf"); log_debugx("unmounting %s using %s", sb->f_mntonname, fsid_str); flags = MNT_BYFSID; if (force) flags |= MNT_FORCE; error = unmount(fsid_str, flags); free(fsid_str); if (error != 0) log_warn("cannot unmount %s", sb->f_mntonname); else rpc_umntall(); return (error); } static const struct statfs * find_statfs(const struct statfs *mntbuf, int nitems, const char *mountpoint) { int i; for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_mntonname, mountpoint) == 0) return (mntbuf + i); } return (NULL); } static void mount_autofs(const char *from, const char *fspath, const char *options, const char *prefix) { struct iovec *iov = NULL; char errmsg[255]; int error, iovlen = 0; create_directory(fspath); log_debugx("mounting %s on %s, prefix \"%s\", options \"%s\"", from, fspath, prefix, options); memset(errmsg, 0, sizeof(errmsg)); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "autofs"), (size_t)-1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); build_iovec(&iov, &iovlen, "from", __DECONST(void *, from), (size_t)-1); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); /* * Append the options and mountpoint defined in auto_master(5); * this way automountd(8) does not need to parse it. */ build_iovec(&iov, &iovlen, "master_options", __DECONST(void *, options), (size_t)-1); build_iovec(&iov, &iovlen, "master_prefix", __DECONST(void *, prefix), (size_t)-1); error = nmount(iov, iovlen, 0); if (error != 0) { if (*errmsg != '\0') { log_err(1, "cannot mount %s on %s: %s", from, fspath, errmsg); } else { log_err(1, "cannot mount %s on %s", from, fspath); } } } static void mount_if_not_already(const struct node *n, const char *map, const char *options, const char *prefix, const struct statfs *mntbuf, int nitems) { const struct statfs *sb; char *mountpoint; char *from; int ret; ret = asprintf(&from, "map %s", map); if (ret < 0) log_err(1, "asprintf"); mountpoint = node_path(n); sb = find_statfs(mntbuf, nitems, mountpoint); if (sb != NULL) { if (strcmp(sb->f_fstypename, "autofs") != 0) { log_debugx("unknown filesystem mounted " "on %s; mounting", mountpoint); /* * XXX: Compare options and 'from', * and update the mount if necessary. */ } else { log_debugx("autofs already mounted " "on %s", mountpoint); free(from); free(mountpoint); return; } } else { log_debugx("nothing mounted on %s; mounting", mountpoint); } mount_autofs(from, mountpoint, options, prefix); free(from); free(mountpoint); } static void mount_unmount(struct node *root) { struct statfs *mntbuf; struct node *n, *n2; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("unmounting stale autofs mounts"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) { log_debugx("skipping %s, filesystem type is not autofs", mntbuf[i].f_mntonname); continue; } n = node_find(root, mntbuf[i].f_mntonname); if (n != NULL) { log_debugx("leaving autofs mounted on %s", mntbuf[i].f_mntonname); continue; } log_debugx("autofs mounted on %s not found " "in new configuration; unmounting", mntbuf[i].f_mntonname); unmount_by_statfs(&(mntbuf[i]), false); } log_debugx("mounting new autofs mounts"); TAILQ_FOREACH(n, &root->n_children, n_next) { if (!node_is_direct_map(n)) { mount_if_not_already(n, n->n_map, n->n_options, n->n_key, mntbuf, nitems); continue; } TAILQ_FOREACH(n2, &n->n_children, n_next) { mount_if_not_already(n2, n->n_map, n->n_options, "/", mntbuf, nitems); } } } static void flush_autofs(const char *fspath, const fsid_t *fsid) { struct iovec *iov = NULL; char errmsg[255]; int error, iovlen = 0; log_debugx("flushing %s", fspath); memset(errmsg, 0, sizeof(errmsg)); build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "autofs"), (size_t)-1); build_iovec(&iov, &iovlen, "fspath", __DECONST(void *, fspath), (size_t)-1); build_iovec(&iov, &iovlen, "fsid", __DECONST(void *, fsid), sizeof(*fsid)); build_iovec(&iov, &iovlen, "errmsg", errmsg, sizeof(errmsg)); error = nmount(iov, iovlen, MNT_UPDATE); if (error != 0) { if (*errmsg != '\0') { log_err(1, "cannot flush %s: %s", fspath, errmsg); } else { log_err(1, "cannot flush %s", fspath); } } } static void flush_caches(void) { struct statfs *mntbuf; struct statfs statbuf; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("flushing autofs caches"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") != 0) { log_debugx("skipping %s, filesystem type is not autofs", mntbuf[i].f_mntonname); continue; } /* * A direct map mountpoint may have been mounted over, in * which case we can't MNT_UPDATE it. There's an obvious race * condition remaining here, but that has to be fixed in the * kernel. */ if (statfs(mntbuf[i].f_mntonname, &statbuf) != 0) { log_err(1, "cannot statfs %s", mntbuf[i].f_mntonname); continue; } if (strcmp(statbuf.f_fstypename, "autofs") != 0) { log_debugx("skipping %s, filesystem type is not autofs", mntbuf[i].f_mntonname); continue; } flush_autofs(mntbuf[i].f_mntonname, &statbuf.f_fsid); } } static void unmount_automounted(bool force) { struct statfs *mntbuf; int i, nitems; nitems = getmntinfo(&mntbuf, MNT_WAIT); if (nitems <= 0) log_err(1, "getmntinfo"); log_debugx("unmounting automounted filesystems"); for (i = 0; i < nitems; i++) { if (strcmp(mntbuf[i].f_fstypename, "autofs") == 0) { log_debugx("skipping %s, filesystem type is autofs", mntbuf[i].f_mntonname); continue; } if ((mntbuf[i].f_flags & MNT_AUTOMOUNTED) == 0) { log_debugx("skipping %s, not automounted", mntbuf[i].f_mntonname); continue; } unmount_by_statfs(&(mntbuf[i]), force); } } static void usage_automount(void) { fprintf(stderr, "usage: automount [-D name=value][-o opts][-Lcfuv]\n"); exit(1); } int main_automount(int argc, char **argv) { struct node *root; int ch, debug = 0, show_maps = 0; char *options = NULL; bool do_unmount = false, force_unmount = false, flush = false; /* * Note that in automount(8), the only purpose of variable * handling is to aid in debugging maps (automount -L). */ defined_init(); while ((ch = getopt(argc, argv, "D:Lfco:uv")) != -1) { switch (ch) { case 'D': defined_parse_and_add(optarg); break; case 'L': show_maps++; break; case 'c': flush = true; break; case 'f': force_unmount = true; break; case 'o': options = concat(options, ',', optarg); break; case 'u': do_unmount = true; break; case 'v': debug++; break; case '?': default: usage_automount(); } } argc -= optind; if (argc != 0) usage_automount(); if (force_unmount && !do_unmount) usage_automount(); log_init(debug); if (flush) { flush_caches(); return (0); } if (do_unmount) { unmount_automounted(force_unmount); return (0); } root = node_new_root(); parse_master(root, AUTO_MASTER_PATH); if (show_maps) { if (show_maps > 1) { node_expand_indirect_maps(root); node_expand_ampersand(root, NULL); } node_expand_defined(root); node_print(root, options); return (0); } mount_unmount(root); return (0); } diff --git a/usr.sbin/mountd/mountd.c b/usr.sbin/mountd/mountd.c index 19d76b72da10..3c4e5e742c93 100644 --- a/usr.sbin/mountd/mountd.c +++ b/usr.sbin/mountd/mountd.c @@ -1,4119 +1,4119 @@ /*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 1989, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Herb Hasler and Rick Macklem at The University of Guelph. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include "pathnames.h" -#include "mntopts.h" #ifdef DEBUG #include #endif /* * Structures for keeping the mount list and export list */ struct mountlist { char ml_host[MNTNAMLEN+1]; char ml_dirp[MNTPATHLEN+1]; SLIST_ENTRY(mountlist) next; }; struct dirlist { struct dirlist *dp_left; struct dirlist *dp_right; int dp_flag; struct hostlist *dp_hosts; /* List of hosts this dir exported to */ char *dp_dirp; }; /* dp_flag bits */ #define DP_DEFSET 0x1 #define DP_HOSTSET 0x2 /* * maproot/mapall credentials. * cr_smallgrps can be used for a group list up to SMALLNGROUPS in size. * Larger group lists are malloc'd/free'd. */ #define SMALLNGROUPS 32 struct expcred { uid_t cr_uid; int cr_ngroups; gid_t cr_smallgrps[SMALLNGROUPS]; gid_t *cr_groups; }; 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 expcred ex_defanon; uint64_t ex_defexflags; int ex_numsecflavors; int ex_secflavors[MAXSECFLAVORS]; int ex_defnumsecflavors; int ex_defsecflavors[MAXSECFLAVORS]; SLIST_ENTRY(exportlist) entries; }; /* ex_flag bits */ #define EX_LINKED 0x01 #define EX_DONE 0x02 #define EX_DEFSET 0x04 #define EX_PUBLICFH 0x08 #define EX_ADMINWARN 0x10 SLIST_HEAD(exportlisthead, exportlist); struct netmsk { struct sockaddr_storage nt_net; struct sockaddr_storage nt_mask; char *nt_name; }; union grouptypes { struct addrinfo *gt_addrinfo; struct netmsk gt_net; }; struct grouplist { int gr_type; union grouptypes gr_ptr; struct grouplist *gr_next; struct expcred gr_anon; uint64_t gr_exflags; int gr_flag; int gr_numsecflavors; int gr_secflavors[MAXSECFLAVORS]; }; /* Group types */ #define GT_NULL 0x0 #define GT_HOST 0x1 #define GT_NET 0x2 #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; struct hostlist *ht_next; }; struct fhreturn { int fhr_flag; int fhr_vers; nfsfh_t fhr_fh; int fhr_numsecflavors; int *fhr_secflavors; }; #define GETPORT_MAXTRY 20 /* Max tries to get a port # */ /* * How long to delay a reload of exports when there are RPC request(s) * to process, in usec. Must be less than 1second. */ #define RELOADDELAY 250000 /* Global defs */ static char *add_expdir(struct dirlist **, char *, int); static void add_dlist(struct dirlist **, struct dirlist *, struct grouplist *, int, struct exportlist *, struct expcred *, uint64_t); static void add_mlist(char *, char *); static int check_path_component(const char *, char **); static int check_dirpath(char *, char **); static int check_statfs(const char *, struct statfs *, char **); static int check_options(struct dirlist *); static int checkmask(struct sockaddr *sa); static int chk_host(struct dirlist *, struct sockaddr *, int *, int *, int *, int **); static char *strsep_quote(char **stringp, const char *delim); static int create_service(struct netconfig *nconf); static void complete_service(struct netconfig *nconf, char *port_str); 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 *, uint64_t, struct expcred *, char *, int, struct statfs *, int, int *); static int do_opt(char **, char **, struct exportlist *, struct grouplist *, int *, uint64_t *, struct expcred *, char *); 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 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 expcred *, struct expcred *); 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); static void get_mountlist(void); 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 expcred *, uint64_t); static void huphandler(int sig); static int makemask(struct sockaddr_storage *ssp, int bitlen); static void mntsrv(struct svc_req *, SVCXPRT *); static void nextfield(char **, char **); static void out_of_mem(void) __dead2; static void parsecred(char *, struct expcred *); static int parsesec(char *, struct exportlist *); static int put_exlist(struct dirlist *, XDR *, struct dirlist *, int *, int); static void *sa_rawaddr(struct sockaddr *sa, int *nbytes); static int sacmp(struct sockaddr *sa1, struct sockaddr *sa2, struct sockaddr *samask); static int scan_tree(struct dirlist *, struct sockaddr *); static void usage(void); static int xdr_dir(XDR *, char *); static int xdr_explist(XDR *, caddr_t); static int xdr_explist_brief(XDR *, caddr_t); static int xdr_explist_common(XDR *, caddr_t, int); static int xdr_fhs(XDR *, caddr_t); static int xdr_mlist(XDR *, caddr_t); static void terminate(int); static void cp_cred(struct expcred *, struct expcred *); static gid_t nogroup(); #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; static int force_v2 = 0; static int warn_admin = 1; static int resvport_only = 1; static int nhosts = 0; static int dir_only = 1; static int dolog = 0; static _Atomic(int) got_sighup = 0; static int xcreated = 0; static char *svcport_str = NULL; static int mallocd_svcport = 0; static int *sock_fd; static int sock_fdcnt; static int sock_fdpos; static int suspend_nfsd = 0; static int nofork = 0; static int skiplocalhost = 0; static int alldirs_fail = 0; static int opt_flags; 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; /* Temporary storage for credentials' groups. */ static long tngroups_max; static gid_t *tmp_groups = NULL; /* Bits for opt_flags above */ #define OP_MAPROOT 0x01 #define OP_MAPALL 0x02 /* 0x4 free */ #define OP_MASK 0x08 #define OP_NET 0x10 #define OP_ALLDIRS 0x40 #define OP_HAVEMASK 0x80 /* A mask was specified or inferred. */ #define OP_QUIET 0x100 #define OP_MASKLEN 0x200 #define OP_SEC 0x400 #define OP_CLASSMASK 0x800 /* mask not specified, is Class A/B/C default */ #define OP_NOTROOT 0x1000 /* Mark the the mount path is not a fs root */ #ifdef DEBUG static int debug = 1; static void SYSLOG(int, const char *, ...) __printflike(2, 3); #define syslog SYSLOG #else 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. * * It returns the string (or NULL, if *stringp is NULL), * which is a de-quoted version of the string if necessary. * * It modifies *stringp in place. */ static char * strsep_quote(char **stringp, const char *delim) { char *srcptr, *dstptr, *retval; char quot = 0; if (stringp == NULL || *stringp == NULL) return (NULL); srcptr = dstptr = retval = *stringp; while (*srcptr) { /* * We're looking for several edge cases here. * First: if we're in quote state (quot != 0), * then we ignore the delim characters, but otherwise * process as normal, unless it is the quote character. * Second: if the current character is a backslash, * we take the next character as-is, without checking * for delim, quote, or backslash. Exception: if the * next character is a NUL, that's the end of the string. * Third: if the character is a quote character, we toggle * quote state. * Otherwise: check the current character for NUL, or * being in delim, and end the string if either is true. */ if (*srcptr == '\\') { srcptr++; /* * The edge case here is if the next character * is NUL, we want to stop processing. But if * it's not NUL, then we simply want to copy it. */ if (*srcptr) { *dstptr++ = *srcptr++; } continue; } if (quot == 0 && (*srcptr == '\'' || *srcptr == '"')) { quot = *srcptr++; continue; } if (quot && *srcptr == quot) { /* End of the quoted part */ quot = 0; srcptr++; continue; } if (!quot && strchr(delim, *srcptr)) break; *dstptr++ = *srcptr++; } *stringp = (*srcptr == '\0') ? NULL : srcptr + 1; *dstptr = 0; /* Terminate the string */ return (retval); } /* * Mountd server for NFS mount protocol as described in: * NFS: Network File System Protocol Specification, RFC1094, Appendix A * The optional arguments are the exports file name * default: _PATH_EXPORTS * and "-n" to allow nonroot mount. */ int main(int argc, char **argv) { fd_set readfds; struct netconfig *nconf; char *endptr, **hosts_bak; void *nc_handle; pid_t otherpid; in_port_t svcport; int c, k, s; int maxrec = RPC_MAXDATASIZE; int attempt_cnt, port_len, port_pos, ret; char **port_list; uint64_t curtime, nexttime; struct timeval tv; struct timespec tp; sigset_t sig_mask, sighup_mask; int enable_rpcbind; enable_rpcbind = 1; /* Check that another mountd isn't already running. */ pfh = pidfile_open(_PATH_MOUNTDPID, 0600, &otherpid); if (pfh == NULL) { if (errno == EEXIST) errx(1, "mountd already running, pid: %d.", otherpid); warn("cannot open or create pidfile"); } openlog("mountd", LOG_PID, LOG_DAEMON); /* How many groups do we support? */ tngroups_max = sysconf(_SC_NGROUPS_MAX); if (tngroups_max == -1) tngroups_max = NGROUPS_MAX; /* Add space for the effective GID. */ ++tngroups_max; tmp_groups = malloc(tngroups_max); if (tmp_groups == NULL) out_of_mem(); s = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (s < 0) have_v6 = 0; else close(s); while ((c = getopt(argc, argv, "2Aadeh:lNnp:RrSs")) != -1) switch (c) { case '2': force_v2 = 1; break; case 'A': warn_admin = 0; break; case 'a': alldirs_fail = 1; break; case 'e': /* now a no-op, since this is the default */ break; case 'n': resvport_only = 0; break; case 'R': /* Do not support Mount protocol */ enable_rpcbind = 0; break; case 'r': dir_only = 0; break; case 'd': debug = debug ? 0 : 1; break; case 'l': dolog = 1; break; case 'p': endptr = NULL; svcport = (in_port_t)strtoul(optarg, &endptr, 10); if (endptr == NULL || *endptr != '\0' || svcport == 0 || svcport >= IPPORT_MAX) usage(); svcport_str = strdup(optarg); break; case 'h': ++nhosts; hosts_bak = hosts; hosts_bak = realloc(hosts, nhosts * sizeof(char *)); if (hosts_bak == NULL) { if (hosts != NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } } hosts = hosts_bak; hosts[nhosts - 1] = strdup(optarg); if (hosts[nhosts - 1] == NULL) { for (k = 0; k < (nhosts - 1); k++) free(hosts[k]); free(hosts); out_of_mem(); } break; case 'S': suspend_nfsd = 1; break; case 'N': nofork = 1; break; case 's': skiplocalhost = 1; break; default: usage(); } if (enable_rpcbind == 0) { if (svcport_str != NULL) { warnx("-p option not compatible with -R, ignored"); free(svcport_str); svcport_str = NULL; } if (nhosts > 0) { warnx("-h option not compatible with -R, ignored"); for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); hosts = NULL; nhosts = 0; } } if (nhosts == 0 && skiplocalhost != 0) warnx("-s without -h, ignored"); if (modfind("nfsd") < 0) { /* Not present in kernel, try loading it */ if (kldload("nfsd") < 0 || modfind("nfsd") < 0) errx(1, "NFS server is not available"); } argc -= optind; argv += optind; if (argc > 0) exnames = argv; else exnames = exnames_default; if (debug) warnx("getting export list"); get_exportlist(0); if (debug) warnx("getting mount list"); get_mountlist(); if (debug) warnx("here we go"); if (debug == 0 && nofork == 0) { daemon(0, 0); signal(SIGINT, SIG_IGN); signal(SIGQUIT, SIG_IGN); } signal(SIGHUP, huphandler); signal(SIGTERM, terminate); signal(SIGPIPE, SIG_IGN); pidfile_write(pfh); if (enable_rpcbind != 0) { rpcb_unset(MOUNTPROG, MOUNTVERS, NULL); rpcb_unset(MOUNTPROG, MOUNTVERS3, NULL); rpc_control(RPC_SVC_CONNMAXREC_SET, &maxrec); if (!resvport_only) { if (sysctlbyname("vfs.nfsd.nfs_privport", NULL, NULL, &resvport_only, sizeof(resvport_only)) != 0 && errno != ENOENT) { syslog(LOG_ERR, "sysctl: %m"); exit(1); } } /* * If no hosts were specified, add a wildcard entry to bind to * INADDR_ANY. Otherwise make sure 127.0.0.1 and ::1 are added * to the list. */ if (nhosts == 0) { hosts = malloc(sizeof(char *)); if (hosts == NULL) out_of_mem(); hosts[0] = "*"; nhosts = 1; } else if (skiplocalhost == 0) { hosts_bak = hosts; if (have_v6) { hosts_bak = realloc(hosts, (nhosts + 2) * sizeof(char *)); if (hosts_bak == NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } else hosts = hosts_bak; nhosts += 2; hosts[nhosts - 2] = "::1"; } else { hosts_bak = realloc(hosts, (nhosts + 1) * sizeof(char *)); if (hosts_bak == NULL) { for (k = 0; k < nhosts; k++) free(hosts[k]); free(hosts); out_of_mem(); } else { nhosts += 1; hosts = hosts_bak; } } hosts[nhosts - 1] = "127.0.0.1"; } } attempt_cnt = 1; sock_fdcnt = 0; sock_fd = NULL; port_list = NULL; port_len = 0; if (enable_rpcbind != 0) { nc_handle = setnetconfig(); while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { /* DO NOTHING */ } else { ret = create_service(nconf); if (ret == 1) /* Ignore this call */ continue; if (ret < 0) { /* * Failed to bind port, so close * off all sockets created and * try again if the port# was * dynamically assigned via * bind(2). */ clearout_service(); if (mallocd_svcport != 0 && attempt_cnt < GETPORT_MAXTRY) { free(svcport_str); svcport_str = NULL; mallocd_svcport = 0; } else { errno = EADDRINUSE; syslog(LOG_ERR, "bindresvport_sa:" " %m"); exit(1); } /* * Start over at the first * service. */ free(sock_fd); sock_fdcnt = 0; sock_fd = NULL; nc_handle = setnetconfig(); attempt_cnt++; } else if (mallocd_svcport != 0 && attempt_cnt == GETPORT_MAXTRY) { /* * For the last attempt, allow * different port #s for each * nconf by saving the * svcport_str setting it back * to NULL. */ port_list = realloc(port_list, (port_len + 1) * sizeof(char *)); if (port_list == NULL) out_of_mem(); port_list[port_len++] = svcport_str; svcport_str = NULL; mallocd_svcport = 0; } } } } /* * Successfully bound the ports, so call complete_service() to * do the rest of the setup on the service(s). */ sock_fdpos = 0; port_pos = 0; nc_handle = setnetconfig(); while ((nconf = getnetconfig(nc_handle))) { if (nconf->nc_flag & NC_VISIBLE) { if (have_v6 == 0 && strcmp(nconf->nc_protofmly, "inet6") == 0) { /* DO NOTHING */ } else if (port_list != NULL) { if (port_pos >= port_len) { syslog(LOG_ERR, "too many" " port#s"); exit(1); } complete_service(nconf, port_list[port_pos++]); } else complete_service(nconf, svcport_str); } } endnetconfig(nc_handle); free(sock_fd); if (port_list != NULL) { for (port_pos = 0; port_pos < port_len; port_pos++) free(port_list[port_pos]); free(port_list); } if (xcreated == 0) { syslog(LOG_ERR, "could not create any services"); exit(1); } } /* Expand svc_run() here so that we can call get_exportlist(). */ curtime = nexttime = 0; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); for (;;) { clock_gettime(CLOCK_MONOTONIC, &tp); curtime = tp.tv_sec; curtime = curtime * 1000000 + tp.tv_nsec / 1000; sigprocmask(SIG_BLOCK, &sighup_mask, &sig_mask); if (got_sighup && curtime >= nexttime) { got_sighup = 0; sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); get_exportlist(1); clock_gettime(CLOCK_MONOTONIC, &tp); nexttime = tp.tv_sec; nexttime = nexttime * 1000000 + tp.tv_nsec / 1000 + RELOADDELAY; } else sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); /* * If a reload is pending, poll for received request(s), * otherwise set a RELOADDELAY timeout, since a SIGHUP * could be processed between the got_sighup test and * the select() system call. */ tv.tv_sec = 0; if (got_sighup) tv.tv_usec = 0; else tv.tv_usec = RELOADDELAY; if (enable_rpcbind != 0) { readfds = svc_fdset; switch (select(svc_maxfd + 1, &readfds, NULL, NULL, &tv)) { case -1: if (errno == EINTR) { /* Allow a reload now. */ nexttime = 0; continue; } syslog(LOG_ERR, "mountd died: select: %m"); exit(1); case 0: /* Allow a reload now. */ nexttime = 0; continue; default: svc_getreqset(&readfds); } } else { /* Simply wait for a signal. */ sigsuspend(&sig_mask); } } } /* * This routine creates and binds sockets on the appropriate * addresses. It gets called one time for each transport. * It returns 0 upon success, 1 for ignore the call and -1 to indicate * bind failed with EADDRINUSE. * Any file descriptors that have been created are stored in sock_fd and * the total count of them is maintained in sock_fdcnt. */ static int create_service(struct netconfig *nconf) { struct addrinfo hints, *res = NULL; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct __rpc_sockinfo si; int aicode; int fd; int nhostsbak; int one = 1; int r; u_int32_t host_addr[4]; /* IPv4 or IPv6 */ int mallocd_res; if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) return (1); /* not my type */ /* * XXX - using RPC library internal functions. */ if (!__rpc_nconf2sockinfo(nconf, &si)) { syslog(LOG_ERR, "cannot get information for %s", nconf->nc_netid); return (1); } /* Get mountd's address on this transport */ memset(&hints, 0, sizeof hints); hints.ai_family = si.si_af; hints.ai_socktype = si.si_socktype; hints.ai_protocol = si.si_proto; /* * Bind to specific IPs if asked to */ nhostsbak = nhosts; while (nhostsbak > 0) { --nhostsbak; sock_fd = realloc(sock_fd, (sock_fdcnt + 1) * sizeof(int)); if (sock_fd == NULL) out_of_mem(); sock_fd[sock_fdcnt++] = -1; /* Set invalid for now. */ mallocd_res = 0; hints.ai_flags = AI_PASSIVE; /* * XXX - using RPC library internal functions. */ if ((fd = __rpc_nconf2fd(nconf)) < 0) { int non_fatal = 0; if (errno == EAFNOSUPPORT && nconf->nc_semantics != NC_TPI_CLTS) non_fatal = 1; syslog(non_fatal ? LOG_DEBUG : LOG_ERR, "cannot create socket for %s", nconf->nc_netid); if (non_fatal != 0) continue; exit(1); } switch (hints.ai_family) { case AF_INET: if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags |= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET6 address. */ if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } break; case AF_INET6: if (inet_pton(AF_INET6, hosts[nhostsbak], host_addr) == 1) { hints.ai_flags |= AI_NUMERICHOST; } else { /* * Skip if we have an AF_INET address. */ if (inet_pton(AF_INET, hosts[nhostsbak], host_addr) == 1) { close(fd); continue; } } /* * We're doing host-based access checks here, so don't * allow v4-in-v6 to confuse things. The kernel will * disable it by default on NFS sockets too. */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof one) < 0) { syslog(LOG_ERR, "can't disable v4-in-v6 on IPv6 socket"); exit(1); } break; default: break; } /* * If no hosts were specified, just bind to INADDR_ANY */ if (strcmp("*", hosts[nhostsbak]) == 0) { if (svcport_str == NULL) { res = malloc(sizeof(struct addrinfo)); if (res == NULL) out_of_mem(); mallocd_res = 1; res->ai_flags = hints.ai_flags; res->ai_family = hints.ai_family; res->ai_protocol = hints.ai_protocol; switch (res->ai_family) { case AF_INET: sin = malloc(sizeof(struct sockaddr_in)); if (sin == NULL) out_of_mem(); sin->sin_family = AF_INET; sin->sin_port = htons(0); sin->sin_addr.s_addr = htonl(INADDR_ANY); res->ai_addr = (struct sockaddr*) sin; res->ai_addrlen = (socklen_t) sizeof(struct sockaddr_in); break; case AF_INET6: sin6 = malloc(sizeof(struct sockaddr_in6)); if (sin6 == NULL) out_of_mem(); sin6->sin6_family = AF_INET6; sin6->sin6_port = htons(0); sin6->sin6_addr = in6addr_any; res->ai_addr = (struct sockaddr*) sin6; res->ai_addrlen = (socklen_t) sizeof(struct sockaddr_in6); break; default: syslog(LOG_ERR, "bad addr fam %d", res->ai_family); exit(1); } } else { if ((aicode = getaddrinfo(NULL, svcport_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address for %s: %s", nconf->nc_netid, gai_strerror(aicode)); close(fd); continue; } } } else { if ((aicode = getaddrinfo(hosts[nhostsbak], svcport_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address for %s: %s", nconf->nc_netid, gai_strerror(aicode)); close(fd); continue; } } /* Store the fd. */ sock_fd[sock_fdcnt - 1] = fd; /* Now, attempt the bind. */ r = bindresvport_sa(fd, res->ai_addr); if (r != 0) { if (errno == EADDRINUSE && mallocd_svcport != 0) { if (mallocd_res != 0) { free(res->ai_addr); free(res); } else freeaddrinfo(res); return (-1); } syslog(LOG_ERR, "bindresvport_sa: %m"); exit(1); } if (svcport_str == NULL) { svcport_str = malloc(NI_MAXSERV * sizeof(char)); if (svcport_str == NULL) out_of_mem(); mallocd_svcport = 1; if (getnameinfo(res->ai_addr, res->ai_addr->sa_len, NULL, NI_MAXHOST, svcport_str, NI_MAXSERV * sizeof(char), NI_NUMERICHOST | NI_NUMERICSERV)) errx(1, "Cannot get port number"); } if (mallocd_res != 0) { free(res->ai_addr); free(res); } else freeaddrinfo(res); res = NULL; } return (0); } /* * Called after all the create_service() calls have succeeded, to complete * the setup and registration. */ static void complete_service(struct netconfig *nconf, char *port_str) { struct addrinfo hints, *res = NULL; struct __rpc_sockinfo si; struct netbuf servaddr; SVCXPRT *transp = NULL; int aicode, fd, nhostsbak; int registered = 0; if ((nconf->nc_semantics != NC_TPI_CLTS) && (nconf->nc_semantics != NC_TPI_COTS) && (nconf->nc_semantics != NC_TPI_COTS_ORD)) return; /* not my type */ /* * XXX - using RPC library internal functions. */ if (!__rpc_nconf2sockinfo(nconf, &si)) { syslog(LOG_ERR, "cannot get information for %s", nconf->nc_netid); return; } nhostsbak = nhosts; while (nhostsbak > 0) { --nhostsbak; if (sock_fdpos >= sock_fdcnt) { /* Should never happen. */ syslog(LOG_ERR, "Ran out of socket fd's"); return; } fd = sock_fd[sock_fdpos++]; if (fd < 0) continue; /* * Using -1 tells listen(2) to use * kern.ipc.soacceptqueue for the backlog. */ if (nconf->nc_semantics != NC_TPI_CLTS) listen(fd, -1); if (nconf->nc_semantics == NC_TPI_CLTS ) transp = svc_dg_create(fd, 0, 0); else transp = svc_vc_create(fd, RPC_MAXDATASIZE, RPC_MAXDATASIZE); if (transp != (SVCXPRT *) NULL) { if (!svc_reg(transp, MOUNTPROG, MOUNTVERS, mntsrv, NULL)) syslog(LOG_ERR, "can't register %s MOUNTVERS service", nconf->nc_netid); if (!force_v2) { if (!svc_reg(transp, MOUNTPROG, MOUNTVERS3, mntsrv, NULL)) syslog(LOG_ERR, "can't register %s MOUNTVERS3 service", nconf->nc_netid); } } else syslog(LOG_WARNING, "can't create %s services", nconf->nc_netid); if (registered == 0) { registered = 1; memset(&hints, 0, sizeof hints); hints.ai_flags = AI_PASSIVE; hints.ai_family = si.si_af; hints.ai_socktype = si.si_socktype; hints.ai_protocol = si.si_proto; if ((aicode = getaddrinfo(NULL, port_str, &hints, &res)) != 0) { syslog(LOG_ERR, "cannot get local address: %s", gai_strerror(aicode)); exit(1); } servaddr.buf = malloc(res->ai_addrlen); memcpy(servaddr.buf, res->ai_addr, res->ai_addrlen); servaddr.len = res->ai_addrlen; rpcb_set(MOUNTPROG, MOUNTVERS, nconf, &servaddr); rpcb_set(MOUNTPROG, MOUNTVERS3, nconf, &servaddr); xcreated++; freeaddrinfo(res); } } /* end while */ } /* * Clear out sockets after a failure to bind one of them, so that the * cycle of socket creation/binding can start anew. */ static void clearout_service(void) { int i; for (i = 0; i < sock_fdcnt; i++) { if (sock_fd[i] >= 0) { shutdown(sock_fd[i], SHUT_RDWR); close(sock_fd[i]); } } } static void usage(void) { fprintf(stderr, "usage: mountd [-2] [-d] [-e] [-l] [-N] [-n] [-p ] [-r] [-S] " "[-s] [-h ] [export_file ...]\n"); exit(1); } /* * The mount rpc service */ void mntsrv(struct svc_req *rqstp, SVCXPRT *transp) { struct exportlist *ep; struct dirlist *dp; struct fhreturn fhr; struct stat stb; struct statfs fsb; char host[NI_MAXHOST], numerichost[NI_MAXHOST]; int lookup_failed = 1; struct sockaddr *saddr; u_short sport; char rpcpath[MNTPATHLEN + 1], dirpath[MAXPATHLEN]; int defset, hostset; long bad = 0; sigset_t sighup_mask; int numsecflavors, *secflavorsp; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); saddr = svc_getrpccaller(transp)->buf; switch (saddr->sa_family) { case AF_INET6: sport = ntohs(((struct sockaddr_in6 *)saddr)->sin6_port); break; case AF_INET: sport = ntohs(((struct sockaddr_in *)saddr)->sin_port); break; default: syslog(LOG_ERR, "request from unknown address family"); return; } switch (rqstp->rq_proc) { case MOUNTPROC_MNT: case MOUNTPROC_UMNT: case MOUNTPROC_UMNTALL: lookup_failed = getnameinfo(saddr, saddr->sa_len, host, sizeof host, NULL, 0, 0); } getnameinfo(saddr, saddr->sa_len, numerichost, sizeof numerichost, NULL, 0, NI_NUMERICHOST); switch (rqstp->rq_proc) { case NULLPROC: if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL)) syslog(LOG_ERR, "can't send reply"); return; case MOUNTPROC_MNT: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "mount request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) { syslog(LOG_NOTICE, "undecodable mount request from %s", numerichost); svcerr_decode(transp); return; } /* * Get the real pathname and make sure it is a directory * or a regular file if the -r option was specified * and it exists. */ if (realpath(rpcpath, dirpath) == NULL || stat(dirpath, &stb) < 0 || statfs(dirpath, &fsb) < 0) { chdir("/"); /* Just in case realpath doesn't */ syslog(LOG_NOTICE, "mount request from %s for non existent path %s", numerichost, dirpath); if (debug) warnx("stat failed on %s", dirpath); bad = ENOENT; /* We will send error reply later */ } if (!bad && !S_ISDIR(stb.st_mode) && (dir_only || !S_ISREG(stb.st_mode))) { syslog(LOG_NOTICE, "mount request from %s for non-directory path %s", numerichost, dirpath); if (debug) warnx("mounting non-directory %s", dirpath); bad = ENOTDIR; /* We will send error reply later */ } /* Check in the exports list */ sigprocmask(SIG_BLOCK, &sighup_mask, NULL); if (bad) ep = NULL; else ep = ex_search(&fsb.f_fsid, exphead); hostset = defset = 0; if (ep && (chk_host(ep->ex_defdir, saddr, &defset, &hostset, &numsecflavors, &secflavorsp) || ((dp = dirp_search(ep->ex_dirl, dirpath)) && chk_host(dp, saddr, &defset, &hostset, &numsecflavors, &secflavorsp)) || (defset && scan_tree(ep->ex_defdir, saddr) == 0 && scan_tree(ep->ex_dirl, saddr) == 0))) { if (bad) { if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } if (hostset & DP_HOSTSET) { fhr.fhr_flag = hostset; fhr.fhr_numsecflavors = numsecflavors; fhr.fhr_secflavors = secflavorsp; } else { fhr.fhr_flag = defset; fhr.fhr_numsecflavors = ep->ex_defnumsecflavors; fhr.fhr_secflavors = ep->ex_defsecflavors; } fhr.fhr_vers = rqstp->rq_vers; /* Get the file handle */ memset(&fhr.fhr_fh, 0, sizeof(nfsfh_t)); if (getfh(dirpath, (fhandle_t *)&fhr.fhr_fh) < 0) { bad = errno; syslog(LOG_ERR, "can't get fh for %s", dirpath); if (!svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_fhs, (caddr_t)&fhr)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) add_mlist(host, dirpath); else add_mlist(numerichost, dirpath); if (debug) warnx("mount successful"); if (dolog) syslog(LOG_NOTICE, "mount request succeeded from %s for %s", numerichost, dirpath); } else { if (!bad) bad = EACCES; syslog(LOG_NOTICE, "mount request denied from %s for %s", numerichost, dirpath); } if (bad && !svc_sendreply(transp, (xdrproc_t)xdr_long, (caddr_t)&bad)) syslog(LOG_ERR, "can't send reply"); sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return; case MOUNTPROC_DUMP: if (!svc_sendreply(transp, (xdrproc_t)xdr_mlist, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); else if (dolog) syslog(LOG_NOTICE, "dump request succeeded from %s", numerichost); return; case MOUNTPROC_UMNT: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "umount request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_getargs(transp, (xdrproc_t)xdr_dir, rpcpath)) { syslog(LOG_NOTICE, "undecodable umount request from %s", numerichost); svcerr_decode(transp); return; } if (realpath(rpcpath, dirpath) == NULL) { syslog(LOG_NOTICE, "umount request from %s " "for non existent path %s", numerichost, dirpath); } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) del_mlist(host, dirpath); del_mlist(numerichost, dirpath); if (dolog) syslog(LOG_NOTICE, "umount request succeeded from %s for %s", numerichost, dirpath); return; case MOUNTPROC_UMNTALL: if (sport >= IPPORT_RESERVED && resvport_only) { syslog(LOG_NOTICE, "umountall request from %s from unprivileged port", numerichost); svcerr_weakauth(transp); return; } if (!svc_sendreply(transp, (xdrproc_t)xdr_void, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (!lookup_failed) del_mlist(host, NULL); del_mlist(numerichost, NULL); if (dolog) syslog(LOG_NOTICE, "umountall request succeeded from %s", numerichost); return; case MOUNTPROC_EXPORT: if (!svc_sendreply(transp, (xdrproc_t)xdr_explist, (caddr_t)NULL)) if (!svc_sendreply(transp, (xdrproc_t)xdr_explist_brief, (caddr_t)NULL)) syslog(LOG_ERR, "can't send reply"); if (dolog) syslog(LOG_NOTICE, "export request succeeded from %s", numerichost); return; default: svcerr_noproc(transp); return; } } /* * Xdr conversion for a dirpath string */ static int xdr_dir(XDR *xdrsp, char *dirp) { return (xdr_string(xdrsp, &dirp, MNTPATHLEN)); } /* * Xdr routine to generate file handle reply */ static int xdr_fhs(XDR *xdrsp, caddr_t cp) { struct fhreturn *fhrp = (struct fhreturn *)cp; u_long ok = 0, len, auth; int i; if (!xdr_long(xdrsp, &ok)) return (0); switch (fhrp->fhr_vers) { case 1: return (xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, NFSX_V2FH)); case 3: len = NFSX_V3FH; if (!xdr_long(xdrsp, &len)) return (0); if (!xdr_opaque(xdrsp, (caddr_t)&fhrp->fhr_fh, len)) return (0); if (fhrp->fhr_numsecflavors) { if (!xdr_int(xdrsp, &fhrp->fhr_numsecflavors)) return (0); for (i = 0; i < fhrp->fhr_numsecflavors; i++) if (!xdr_int(xdrsp, &fhrp->fhr_secflavors[i])) return (0); return (1); } else { auth = AUTH_SYS; len = 1; if (!xdr_long(xdrsp, &len)) return (0); return (xdr_long(xdrsp, &auth)); } } return (0); } static int xdr_mlist(XDR *xdrsp, caddr_t cp __unused) { struct mountlist *mlp; int true = 1; int false = 0; char *strp; SLIST_FOREACH(mlp, &mlhead, next) { if (!xdr_bool(xdrsp, &true)) return (0); strp = &mlp->ml_host[0]; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (0); strp = &mlp->ml_dirp[0]; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (0); } if (!xdr_bool(xdrsp, &false)) return (0); return (1); } /* * Xdr conversion for export list */ static int xdr_explist_common(XDR *xdrsp, caddr_t cp __unused, int brief) { struct exportlist *ep; int false = 0; int putdef; sigset_t sighup_mask; int i; sigemptyset(&sighup_mask); sigaddset(&sighup_mask, SIGHUP); sigprocmask(SIG_BLOCK, &sighup_mask, NULL); 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); return (1); errout: sigprocmask(SIG_UNBLOCK, &sighup_mask, NULL); return (0); } /* * Called from xdr_explist() to traverse the tree and export the * directory paths. */ static int put_exlist(struct dirlist *dp, XDR *xdrsp, struct dirlist *adp, int *putdefp, int brief) { struct grouplist *grp; struct hostlist *hp; int true = 1; int false = 0; int gotalldir = 0; char *strp; if (dp) { if (put_exlist(dp->dp_left, xdrsp, adp, putdefp, brief)) return (1); if (!xdr_bool(xdrsp, &true)) return (1); strp = dp->dp_dirp; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (1); if (adp && !strcmp(dp->dp_dirp, adp->dp_dirp)) { gotalldir = 1; *putdefp = 1; } if (brief) { if (!xdr_bool(xdrsp, &true)) return (1); strp = "(...)"; if (!xdr_string(xdrsp, &strp, MNTPATHLEN)) return (1); } else if ((dp->dp_flag & DP_DEFSET) == 0 && (gotalldir == 0 || (adp->dp_flag & DP_DEFSET) == 0)) { hp = dp->dp_hosts; while (hp) { grp = hp->ht_grp; if (grp->gr_type == GT_HOST) { if (!xdr_bool(xdrsp, &true)) return (1); strp = grp->gr_ptr.gt_addrinfo->ai_canonname; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (1); } else if (grp->gr_type == GT_NET) { if (!xdr_bool(xdrsp, &true)) return (1); strp = grp->gr_ptr.gt_net.nt_name; if (!xdr_string(xdrsp, &strp, MNTNAMLEN)) return (1); } hp = hp->ht_next; if (gotalldir && hp == (struct hostlist *)NULL) { hp = adp->dp_hosts; gotalldir = 0; } } } if (!xdr_bool(xdrsp, &false)) return (1); if (put_exlist(dp->dp_right, xdrsp, adp, putdefp, brief)) return (1); } return (0); } static int xdr_explist(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 0); } static int xdr_explist_brief(XDR *xdrsp, caddr_t cp) { return xdr_explist_common(xdrsp, cp, 1); } static char *line; static size_t linesize; static FILE *exp_file; /* * Get the export list from one, currently open file */ static void get_exportlist_one(int passno) { struct exportlist *ep; struct grouplist *grp, *tgrp, *savgrp; struct dirlist *dirhead; struct statfs fsb; struct expcred anon; char *cp, *endcp, *dirp, *hst, *usr, *dom, savedc; char *err_msg = NULL; int len, has_host, got_nondir, dirplen, netgrp; uint64_t exflags; char unvis_dir[PATH_MAX + 1]; int unvis_len; v4root_phase = 0; dirhead = (struct dirlist *)NULL; unvis_dir[0] = '\0'; fsb.f_mntonname[0] = '\0'; while (get_line()) { if (debug) warnx("got line %s", line); cp = line; nextfield(&cp, &endcp); if (*cp == '#') goto nextline; /* * Set defaults. */ has_host = FALSE; anon.cr_uid = UID_NOBODY; anon.cr_ngroups = 1; anon.cr_groups = tmp_groups; anon.cr_groups[0] = nogroup(); exflags = MNT_EXPORTED; got_nondir = 0; opt_flags = 0; ep = (struct exportlist *)NULL; dirp = NULL; /* * Handle the V4 root dir. */ if (*cp == 'V' && *(cp + 1) == '4' && *(cp + 2) == ':') { /* * V4: just indicates that it is the v4 root point, * so skip over that and set v4root_phase. */ if (v4root_phase > 0) { syslog(LOG_ERR, "V4:duplicate line, ignored"); goto nextline; } v4root_phase = 1; cp += 3; nextfield(&cp, &endcp); } /* * Create new exports list entry */ len = endcp-cp; tgrp = grp = get_grp(); while (len > 0) { if (len > MNTNAMLEN) { getexp_err(ep, tgrp, "mountpoint too long"); goto nextline; } if (*cp == '-') { if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp, "flag before export path definition"); goto nextline; } if (debug) warnx("doing opt %s", cp); got_nondir = 1; if (do_opt(&cp, &endcp, ep, grp, &has_host, &exflags, &anon, unvis_dir)) { getexp_err(ep, tgrp, NULL); goto nextline; } } else if (*cp == '/') { savedc = *endcp; *endcp = '\0'; unvis_len = strnunvis(unvis_dir, sizeof(unvis_dir), cp); if (unvis_len <= 0) { getexp_err(ep, tgrp, "Cannot strunvis " "decode dir"); goto nextline; } if (v4root_phase > 1) { if (dirp != NULL) { getexp_err(ep, tgrp, "Multiple V4 dirs"); goto nextline; } } if (check_dirpath(unvis_dir, &err_msg) && check_statfs(unvis_dir, &fsb, &err_msg)) { if ((fsb.f_flags & MNT_AUTOMOUNTED) != 0) syslog(LOG_ERR, "Warning: exporting of " "automounted fs %s not supported", unvis_dir); if (got_nondir) { getexp_err(ep, tgrp, "dirs must be first"); goto nextline; } if (v4root_phase == 1) { if (dirp != NULL) { getexp_err(ep, tgrp, "Multiple V4 dirs"); goto nextline; } if (strlen(v4root_dirpath) == 0) { strlcpy(v4root_dirpath, unvis_dir, sizeof (v4root_dirpath)); } else if (strcmp(v4root_dirpath, unvis_dir) != 0) { syslog(LOG_ERR, "different V4 dirpath %s", unvis_dir); getexp_err(ep, tgrp, NULL); goto nextline; } dirp = unvis_dir; v4root_phase = 2; got_nondir = 1; ep = get_exp(); } else { if (ep) { if (fsidcmp(&ep->ex_fs, &fsb.f_fsid) != 0) { getexp_err(ep, tgrp, "fsid mismatch"); goto nextline; } } else { /* * See if this directory is already * in the list. */ ep = ex_search(&fsb.f_fsid, exphead); if (ep == (struct exportlist *)NULL) { ep = get_exp(); ep->ex_fs = fsb.f_fsid; ep->ex_fsdir = strdup(fsb.f_mntonname); if (ep->ex_fsdir == NULL) out_of_mem(); if (debug) warnx( "making new ep fs=0x%x,0x%x", fsb.f_fsid.val[0], fsb.f_fsid.val[1]); } else if (debug) warnx("found ep fs=0x%x,0x%x", fsb.f_fsid.val[0], fsb.f_fsid.val[1]); } if (strcmp(unvis_dir, fsb.f_mntonname) != 0) opt_flags |= OP_NOTROOT; /* * Add dirpath to export mount point. */ dirp = add_expdir(&dirhead, unvis_dir, unvis_len); dirplen = unvis_len; } } else { if (err_msg != NULL) { getexp_err(ep, tgrp, err_msg); free(err_msg); err_msg = NULL; } else { getexp_err(ep, tgrp, "symbolic link in export path or " "statfs failed"); } goto nextline; } *endcp = savedc; } else { savedc = *endcp; *endcp = '\0'; got_nondir = 1; if (ep == (struct exportlist *)NULL) { getexp_err(ep, tgrp, "host(s) before export path definition"); goto nextline; } /* * Get the host or netgroup. */ setnetgrent(cp); netgrp = getnetgrent(&hst, &usr, &dom); do { if (has_host) { grp->gr_next = get_grp(); grp = grp->gr_next; } if (netgrp) { if (hst == 0) { syslog(LOG_ERR, "null hostname in netgroup %s, skipping", cp); grp->gr_type = GT_IGNORE; } else if (get_host(hst, grp, tgrp)) { syslog(LOG_ERR, "bad host %s in netgroup %s, skipping", hst, cp); grp->gr_type = GT_IGNORE; } } else if (get_host(cp, grp, tgrp)) { syslog(LOG_ERR, "bad host %s, skipping", cp); grp->gr_type = GT_IGNORE; } has_host = TRUE; } while (netgrp && getnetgrent(&hst, &usr, &dom)); endnetgrent(); *endcp = savedc; } cp = endcp; nextfield(&cp, &endcp); len = endcp - cp; } if (opt_flags & OP_CLASSMASK) syslog(LOG_ERR, "WARNING: No mask specified for %s, " "using out-of-date default", (&grp->gr_ptr.gt_net)->nt_name); if ((opt_flags & OP_NOTROOT) != 0 && warn_admin != 0 && (ep->ex_flag & EX_ADMINWARN) == 0 && unvis_dir[0] != '\0' && fsb.f_mntonname[0] != '\0') { if (debug) warnx("exporting %s exports entire %s file " "system", unvis_dir, fsb.f_mntonname); syslog(LOG_ERR, "Warning: exporting %s exports " "entire %s file system", unvis_dir, fsb.f_mntonname); ep->ex_flag |= EX_ADMINWARN; } if (check_options(dirhead)) { getexp_err(ep, tgrp, NULL); goto nextline; } if (!has_host) { grp->gr_type = GT_DEFAULT; if (debug) warnx("adding a default entry"); /* * Don't allow a network export coincide with a list of * host(s) on the same line. */ } else if ((opt_flags & OP_NET) && tgrp->gr_next) { getexp_err(ep, tgrp, "network/host conflict"); goto nextline; /* * If an export list was specified on this line, make sure * that we have at least one valid entry, otherwise skip it. */ } else { grp = tgrp; while (grp && grp->gr_type == GT_IGNORE) grp = grp->gr_next; if (! grp) { getexp_err(ep, tgrp, "no valid entries"); goto nextline; } } if (v4root_phase == 1) { getexp_err(ep, tgrp, "V4:root, no dirp, ignored"); goto nextline; } /* * 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 { grp->gr_exflags = exflags; cp_cred(&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; } } while (grp->gr_next && (grp = grp->gr_next)); /* * For V4: don't enter in mount lists. */ if (v4root_phase > 0 && v4root_phase <= 2) { /* * 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) { grp = tgrp; tgrp = tgrp->gr_next; free_grp(grp); } goto nextline; } /* * Success. Update the data structures. */ if (has_host) { 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, &anon, exflags); free_grp(grp); } dirhead = (struct dirlist *)NULL; if ((ep->ex_flag & EX_LINKED) == 0) { insert_exports(ep, exphead); ep->ex_flag |= EX_LINKED; } nextline: v4root_phase = 0; if (dirhead) { free_dir(dirhead); dirhead = (struct dirlist *)NULL; } } } /* * Get the export list from all specified files */ static void get_exportlist(int passno) { struct export_args export; struct iovec *iov; struct statfs *mntbufp; char errmsg[255]; int error, i, nfs_maxvers, num; int iovlen; struct nfsex_args eargs; FILE *debug_file; size_t nfs_maxvers_size; 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); /* * Delete the old V4 root dir. */ bzero(&eargs, sizeof (eargs)); eargs.export.ex_flags = MNT_DELEXPORT; if (nfssvc(NFSSVC_V4ROOTEXPORT | NFSSVC_NEWSTRUCT, (caddr_t)&eargs) < 0 && errno != ENOENT) syslog(LOG_ERR, "Can't delete exports for V4:"); 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 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. */ 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; /* exphead == NULL if not yet allocated (first call). */ if (exphead != NULL) { /* * First, get rid of the old lists. */ free_exports(exphead); free_exports(oldexphead); } /* * 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 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 (strlen(v4root_dirpath) == 0) { /* Check to see if a V4: line is needed. */ nfs_maxvers_size = sizeof(nfs_maxvers); error = sysctlbyname("vfs.nfsd.server_max_nfsvers", &nfs_maxvers, &nfs_maxvers_size, NULL, 0); if (error != 0 || nfs_maxvers < NFS_VER2 || nfs_maxvers > NFS_VER4) { syslog(LOG_ERR, "sysctlbyname(vfs.nfsd." "server_max_nfsvers) failed, defaulting to NFSv3"); nfs_maxvers = NFS_VER3; } if (nfs_maxvers == NFS_VER4) syslog(LOG_ERR, "NFSv4 requires at least one V4: line"); } if (iov != NULL) { /* Free strings allocated by strdup() in getmntopts.c */ free(iov[0].iov_base); /* fstype */ free(iov[2].iov_base); /* fspath */ free(iov[4].iov_base); /* from */ free(iov[6].iov_base); /* update */ free(iov[8].iov_base); /* export */ free(iov[10].iov_base); /* errmsg */ /* free iov, allocated by realloc() */ free(iov); iovlen = 0; } /* * If there was no public fh, clear any previous one set. */ 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"); } /* * 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. */ done = 0; for (i = 0; exnames[i] != NULL; i++) { if (debug) warnx("reading exports from %s", exnames[i]); if ((exp_file = fopen(exnames[i], "r")) == NULL) { syslog(LOG_WARNING, "can't open %s", exnames[i]); continue; } get_exportlist_one(passno); fclose(exp_file); done++; } if (done == 0) { 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; /* * 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 && fsidcmp(&oep->ex_fs, &ofs.f_fsid) == 0) { 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 identical 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 two struct expcred's. Return 0 if the same and 1 otherwise. */ static int compare_cred(struct expcred *cr0, struct expcred *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 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); } } /* * Allocate an export list element */ static struct exportlist * get_exp(void) { struct exportlist *ep; ep = (struct exportlist *)calloc(1, sizeof (struct exportlist)); if (ep == (struct exportlist *)NULL) out_of_mem(); return (ep); } /* * Allocate a group list element */ static struct grouplist * get_grp(void) { struct grouplist *gp; gp = (struct grouplist *)calloc(1, sizeof (struct grouplist)); if (gp == (struct grouplist *)NULL) out_of_mem(); return (gp); } /* * Clean up upon an error in get_exportlist(). */ static void getexp_err(struct exportlist *ep, struct grouplist *grp, const char *reason) { struct grouplist *tgrp; if (!(opt_flags & OP_QUIET)) { if (reason != NULL) syslog(LOG_ERR, "bad exports list line '%s': %s", line, reason); else syslog(LOG_ERR, "bad exports list line '%s'", line); } if (ep && (ep->ex_flag & EX_LINKED) == 0) free_exp(ep); while (grp) { tgrp = grp; grp = grp->gr_next; free_grp(tgrp); } } /* * Search the export list for a matching fs. */ static struct exportlist * ex_search(fsid_t *fsid, struct exportlisthead *exhp) { struct exportlist *ep; uint32_t i; i = EXPHASH(fsid); SLIST_FOREACH(ep, &exhp[i], entries) { if (fsidcmp(&ep->ex_fs, fsid) == 0) return (ep); } return (ep); } /* * Add a directory path to the list. */ static char * add_expdir(struct dirlist **dpp, char *cp, int len) { struct dirlist *dp; dp = malloc(sizeof (struct dirlist)); if (dp == (struct dirlist *)NULL) out_of_mem(); dp->dp_left = *dpp; dp->dp_right = (struct dirlist *)NULL; dp->dp_flag = 0; dp->dp_hosts = (struct hostlist *)NULL; dp->dp_dirp = strndup(cp, len); if (dp->dp_dirp == NULL) out_of_mem(); *dpp = dp; return (dp->dp_dirp); } /* * Hang the dir list element off the dirpath binary tree as required * and update the entry for host. */ static void hang_dirp(struct dirlist *dp, struct grouplist *grp, struct exportlist *ep, int flags, struct expcred *anoncrp, uint64_t exflags) { struct hostlist *hp; struct dirlist *dp2; if (flags & OP_ALLDIRS) { if (ep->ex_defdir) free((caddr_t)dp); 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)); cp_cred(&ep->ex_defanon, anoncrp); ep->ex_defexflags = exflags; } else while (grp) { hp = get_ht(); hp->ht_grp = grp; hp->ht_next = ep->ex_defdir->dp_hosts; ep->ex_defdir->dp_hosts = hp; /* Save the security flavors list for this host set. */ grp->gr_numsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(grp->gr_secflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); grp = grp->gr_next; } } else { /* * Loop through the directories adding them to the tree. */ while (dp) { dp2 = dp->dp_left; add_dlist(&ep->ex_dirl, dp, grp, flags, ep, anoncrp, exflags); dp = dp2; } } } /* * Traverse the binary tree either updating a node that is already there * for the new directory or adding the new node. */ static void add_dlist(struct dirlist **dpp, struct dirlist *newdp, struct grouplist *grp, int flags, struct exportlist *ep, struct expcred *anoncrp, uint64_t exflags) { struct dirlist *dp; struct hostlist *hp; int cmp; dp = *dpp; if (dp) { cmp = strcmp(dp->dp_dirp, newdp->dp_dirp); if (cmp > 0) { 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, anoncrp, exflags); return; } else free((caddr_t)newdp); } else { dp = newdp; dp->dp_left = (struct dirlist *)NULL; *dpp = dp; } if (grp) { /* * Hang all of the host(s) off of the directory point. */ do { hp = get_ht(); hp->ht_grp = grp; hp->ht_next = dp->dp_hosts; dp->dp_hosts = hp; /* Save the security flavors list for this host set. */ grp->gr_numsecflavors = ep->ex_numsecflavors; if (ep->ex_numsecflavors > 0) memcpy(grp->gr_secflavors, ep->ex_secflavors, sizeof(ep->ex_secflavors)); 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)); cp_cred(&ep->ex_defanon, anoncrp); ep->ex_defexflags = exflags; } } /* * Search for a dirpath on the export point. */ static struct dirlist * dirp_search(struct dirlist *dp, char *dirp) { int cmp; if (dp) { cmp = strcmp(dp->dp_dirp, dirp); if (cmp > 0) return (dirp_search(dp->dp_left, dirp)); else if (cmp < 0) return (dirp_search(dp->dp_right, dirp)); else return (dp); } return (dp); } /* * Scan for a host match in a directory tree. */ static int chk_host(struct dirlist *dp, struct sockaddr *saddr, int *defsetp, int *hostsetp, int *numsecflavors, int **secflavorsp) { struct hostlist *hp; struct grouplist *grp; struct addrinfo *ai; if (dp) { if (dp->dp_flag & DP_DEFSET) *defsetp = dp->dp_flag; hp = dp->dp_hosts; while (hp) { grp = hp->ht_grp; switch (grp->gr_type) { case GT_HOST: ai = grp->gr_ptr.gt_addrinfo; for (; ai; ai = ai->ai_next) { if (!sacmp(ai->ai_addr, saddr, NULL)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); if (numsecflavors != NULL) { *numsecflavors = grp->gr_numsecflavors; *secflavorsp = grp->gr_secflavors; } return (1); } } break; case GT_NET: if (!sacmp(saddr, (struct sockaddr *) &grp->gr_ptr.gt_net.nt_net, (struct sockaddr *) &grp->gr_ptr.gt_net.nt_mask)) { *hostsetp = (hp->ht_flag | DP_HOSTSET); if (numsecflavors != NULL) { *numsecflavors = grp->gr_numsecflavors; *secflavorsp = grp->gr_secflavors; } return (1); } break; } hp = hp->ht_next; } } return (0); } /* * Scan tree for a host that matches the address. */ static int scan_tree(struct dirlist *dp, struct sockaddr *saddr) { int defset, hostset; if (dp) { if (scan_tree(dp->dp_left, saddr)) return (1); if (chk_host(dp, saddr, &defset, &hostset, NULL, NULL)) return (1); if (scan_tree(dp->dp_right, saddr)) return (1); } return (0); } /* * Traverse the dirlist tree and free it up. */ static void free_dir(struct dirlist *dp) { if (dp) { free_dir(dp->dp_left); free_dir(dp->dp_right); free_host(dp->dp_hosts); free(dp->dp_dirp); free(dp); } } /* * Parse a colon separated list of security flavors */ static int parsesec(char *seclist, struct exportlist *ep) { char *cp, savedc; int flavor; ep->ex_numsecflavors = 0; for (;;) { cp = strchr(seclist, ':'); if (cp) { savedc = *cp; *cp = '\0'; } if (!strcmp(seclist, "sys")) flavor = AUTH_SYS; else if (!strcmp(seclist, "krb5")) flavor = RPCSEC_GSS_KRB5; else if (!strcmp(seclist, "krb5i")) flavor = RPCSEC_GSS_KRB5I; else if (!strcmp(seclist, "krb5p")) flavor = RPCSEC_GSS_KRB5P; else { if (cp) *cp = savedc; syslog(LOG_ERR, "bad sec flavor: %s", seclist); return (1); } if (ep->ex_numsecflavors == MAXSECFLAVORS) { if (cp) *cp = savedc; syslog(LOG_ERR, "too many sec flavors: %s", seclist); return (1); } ep->ex_secflavors[ep->ex_numsecflavors] = flavor; ep->ex_numsecflavors++; if (cp) { *cp = savedc; seclist = cp + 1; } else { break; } } return (0); } /* * Parse the option string and update fields. * Option arguments may either be -