diff options
Diffstat (limited to 'sys/kern/vfs_mountroot.c')
-rw-r--r-- | sys/kern/vfs_mountroot.c | 1041 |
1 files changed, 1041 insertions, 0 deletions
diff --git a/sys/kern/vfs_mountroot.c b/sys/kern/vfs_mountroot.c new file mode 100644 index 0000000..322fc9a --- /dev/null +++ b/sys/kern/vfs_mountroot.c @@ -0,0 +1,1041 @@ +/*- + * Copyright (c) 2010 Marcel Moolenaar + * Copyright (c) 1999-2004 Poul-Henning Kamp + * Copyright (c) 1999 Michael Smith + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, 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. + * 4. 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 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 "opt_rootdevname.h" + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/cons.h> +#include <sys/fcntl.h> +#include <sys/jail.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/mdioctl.h> +#include <sys/mount.h> +#include <sys/mutex.h> +#include <sys/namei.h> +#include <sys/priv.h> +#include <sys/proc.h> +#include <sys/filedesc.h> +#include <sys/reboot.h> +#include <sys/sbuf.h> +#include <sys/stat.h> +#include <sys/syscallsubr.h> +#include <sys/sysproto.h> +#include <sys/sx.h> +#include <sys/sysctl.h> +#include <sys/sysent.h> +#include <sys/systm.h> +#include <sys/vnode.h> + +#include <geom/geom.h> + +/* + * The root filesystem is detailed in the kernel environment variable + * vfs.root.mountfrom, which is expected to be in the general format + * + * <vfsname>:[<path>][ <vfsname>:[<path>] ...] + * vfsname := the name of a VFS known to the kernel and capable + * of being mounted as root + * path := disk device name or other data used by the filesystem + * to locate its physical store + * + * If the environment variable vfs.root.mountfrom is a space separated list, + * each list element is tried in turn and the root filesystem will be mounted + * from the first one that suceeds. + * + * The environment variable vfs.root.mountfrom.options is a comma delimited + * set of string mount options. These mount options must be parseable + * by nmount() in the kernel. + */ + +static int parse_mount(char **); +static struct mntarg *parse_mountroot_options(struct mntarg *, const char *); + +/* + * The vnode of the system's root (/ in the filesystem, without chroot + * active.) + */ +struct vnode *rootvnode; + +char *rootdevnames[2] = {NULL, NULL}; + +struct root_hold_token { + const char *who; + LIST_ENTRY(root_hold_token) list; +}; + +static LIST_HEAD(, root_hold_token) root_holds = + LIST_HEAD_INITIALIZER(root_holds); + +enum action { + A_CONTINUE, + A_PANIC, + A_REBOOT, + A_RETRY +}; + +static enum action root_mount_onfail = A_CONTINUE; + +static int root_mount_mddev; +static int root_mount_complete; + +/* By default wait up to 3 seconds for devices to appear. */ +static int root_mount_timeout = 3; +TUNABLE_INT("vfs.mountroot.timeout", &root_mount_timeout); + +struct root_hold_token * +root_mount_hold(const char *identifier) +{ + struct root_hold_token *h; + + if (root_mounted()) + return (NULL); + + h = malloc(sizeof *h, M_DEVBUF, M_ZERO | M_WAITOK); + h->who = identifier; + mtx_lock(&mountlist_mtx); + LIST_INSERT_HEAD(&root_holds, h, list); + mtx_unlock(&mountlist_mtx); + return (h); +} + +void +root_mount_rel(struct root_hold_token *h) +{ + + if (h == NULL) + return; + mtx_lock(&mountlist_mtx); + LIST_REMOVE(h, list); + wakeup(&root_holds); + mtx_unlock(&mountlist_mtx); + free(h, M_DEVBUF); +} + +int +root_mounted(void) +{ + + /* No mutex is acquired here because int stores are atomic. */ + return (root_mount_complete); +} + +void +root_mount_wait(void) +{ + + /* + * Panic on an obvious deadlock - the function can't be called from + * a thread which is doing the whole SYSINIT stuff. + */ + KASSERT(curthread->td_proc->p_pid != 0, + ("root_mount_wait: cannot be called from the swapper thread")); + mtx_lock(&mountlist_mtx); + while (!root_mount_complete) { + msleep(&root_mount_complete, &mountlist_mtx, PZERO, "rootwait", + hz); + } + mtx_unlock(&mountlist_mtx); +} + +static void +set_rootvnode(void) +{ + struct proc *p; + + if (VFS_ROOT(TAILQ_FIRST(&mountlist), LK_EXCLUSIVE, &rootvnode)) + panic("Cannot find root vnode"); + + VOP_UNLOCK(rootvnode, 0); + + p = curthread->td_proc; + FILEDESC_XLOCK(p->p_fd); + + if (p->p_fd->fd_cdir != NULL) + vrele(p->p_fd->fd_cdir); + p->p_fd->fd_cdir = rootvnode; + VREF(rootvnode); + + if (p->p_fd->fd_rdir != NULL) + vrele(p->p_fd->fd_rdir); + p->p_fd->fd_rdir = rootvnode; + VREF(rootvnode); + + FILEDESC_XUNLOCK(p->p_fd); +} + +static int +vfs_mountroot_devfs(struct thread *td, struct mount **mpp) +{ + struct vfsoptlist *opts; + struct vfsconf *vfsp; + struct mount *mp; + int error; + + *mpp = NULL; + + vfsp = vfs_byname("devfs"); + KASSERT(vfsp != NULL, ("Could not find devfs by name")); + if (vfsp == NULL) + return (ENOENT); + + mp = vfs_mount_alloc(NULLVP, vfsp, "/dev", td->td_ucred); + + error = VFS_MOUNT(mp); + KASSERT(error == 0, ("VFS_MOUNT(devfs) failed %d", error)); + if (error) + return (error); + + opts = malloc(sizeof(struct vfsoptlist), M_MOUNT, M_WAITOK); + TAILQ_INIT(opts); + mp->mnt_opt = opts; + + mtx_lock(&mountlist_mtx); + TAILQ_INSERT_HEAD(&mountlist, mp, mnt_list); + mtx_unlock(&mountlist_mtx); + + *mpp = mp; + set_rootvnode(); + + error = kern_symlink(td, "/", "dev", UIO_SYSSPACE); + if (error) + printf("kern_symlink /dev -> / returns %d\n", error); + + return (error); +} + +static int +vfs_mountroot_shuffle(struct thread *td, struct mount *mpdevfs) +{ + struct nameidata nd; + struct mount *mporoot, *mpnroot; + struct vnode *vp, *vporoot, *vpdevfs; + char *fspath; + int error; + + mpnroot = TAILQ_NEXT(mpdevfs, mnt_list); + + /* Shuffle the mountlist. */ + mtx_lock(&mountlist_mtx); + mporoot = TAILQ_FIRST(&mountlist); + TAILQ_REMOVE(&mountlist, mpdevfs, mnt_list); + if (mporoot != mpdevfs) { + TAILQ_REMOVE(&mountlist, mpnroot, mnt_list); + TAILQ_INSERT_HEAD(&mountlist, mpnroot, mnt_list); + } + TAILQ_INSERT_TAIL(&mountlist, mpdevfs, mnt_list); + mtx_unlock(&mountlist_mtx); + + cache_purgevfs(mporoot); + if (mporoot != mpdevfs) + cache_purgevfs(mpdevfs); + + VFS_ROOT(mporoot, LK_EXCLUSIVE, &vporoot); + + VI_LOCK(vporoot); + vporoot->v_iflag &= ~VI_MOUNT; + VI_UNLOCK(vporoot); + vporoot->v_mountedhere = NULL; + mporoot->mnt_flag &= ~MNT_ROOTFS; + mporoot->mnt_vnodecovered = NULL; + vput(vporoot); + + /* Set up the new rootvnode, and purge the cache */ + mpnroot->mnt_vnodecovered = NULL; + set_rootvnode(); + cache_purgevfs(rootvnode->v_mount); + + if (mporoot != mpdevfs) { + /* Remount old root under /.mount or /mnt */ + fspath = "/.mount"; + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, + fspath, td); + error = namei(&nd); + if (error) { + NDFREE(&nd, NDF_ONLY_PNBUF); + fspath = "/mnt"; + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, + fspath, td); + error = namei(&nd); + } + if (!error) { + vp = nd.ni_vp; + error = (vp->v_type == VDIR) ? 0 : ENOTDIR; + if (!error) + error = vinvalbuf(vp, V_SAVE, 0, 0); + if (!error) { + cache_purge(vp); + mporoot->mnt_vnodecovered = vp; + vp->v_mountedhere = mporoot; + strlcpy(mporoot->mnt_stat.f_mntonname, + fspath, MNAMELEN); + VOP_UNLOCK(vp, 0); + } else + vput(vp); + } + NDFREE(&nd, NDF_ONLY_PNBUF); + + if (error && bootverbose) + printf("mountroot: unable to remount previous root " + "under /.mount or /mnt (error %d).\n", error); + } + + /* Remount devfs under /dev */ + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, "/dev", td); + error = namei(&nd); + if (!error) { + vp = nd.ni_vp; + error = (vp->v_type == VDIR) ? 0 : ENOTDIR; + if (!error) + error = vinvalbuf(vp, V_SAVE, 0, 0); + if (!error) { + vpdevfs = mpdevfs->mnt_vnodecovered; + if (vpdevfs != NULL) { + cache_purge(vpdevfs); + vpdevfs->v_mountedhere = NULL; + vrele(vpdevfs); + } + mpdevfs->mnt_vnodecovered = vp; + vp->v_mountedhere = mpdevfs; + VOP_UNLOCK(vp, 0); + } else + vput(vp); + } + if (error && bootverbose) + printf("mountroot: unable to remount devfs under /dev " + "(error %d).\n", error); + NDFREE(&nd, NDF_ONLY_PNBUF); + + if (mporoot == mpdevfs) { + vfs_unbusy(mpdevfs); + /* Unlink the no longer needed /dev/dev -> / symlink */ + error = kern_unlink(td, "/dev/dev", UIO_SYSSPACE); + if (error && bootverbose) + printf("mountroot: unable to unlink /dev/dev " + "(error %d)\n", error); + } + + return (0); +} + +/* + * Configuration parser. + */ + +/* Parser character classes. */ +#define CC_WHITESPACE -1 +#define CC_NONWHITESPACE -2 + +/* Parse errors. */ +#define PE_EOF -1 +#define PE_EOL -2 + +static __inline int +parse_peek(char **conf) +{ + + return (**conf); +} + +static __inline void +parse_poke(char **conf, int c) +{ + + **conf = c; +} + +static __inline void +parse_advance(char **conf) +{ + + (*conf)++; +} + +static __inline int +parse_isspace(int c) +{ + + return ((c == ' ' || c == '\t' || c == '\n') ? 1 : 0); +} + +static int +parse_skipto(char **conf, int mc) +{ + int c, match; + + while (1) { + c = parse_peek(conf); + if (c == 0) + return (PE_EOF); + switch (mc) { + case CC_WHITESPACE: + match = (c == ' ' || c == '\t' || c == '\n') ? 1 : 0; + break; + case CC_NONWHITESPACE: + if (c == '\n') + return (PE_EOL); + match = (c != ' ' && c != '\t') ? 1 : 0; + break; + default: + match = (c == mc) ? 1 : 0; + break; + } + if (match) + break; + parse_advance(conf); + } + return (0); +} + +static int +parse_token(char **conf, char **tok) +{ + char *p; + size_t len; + int error; + + *tok = NULL; + error = parse_skipto(conf, CC_NONWHITESPACE); + if (error) + return (error); + p = *conf; + error = parse_skipto(conf, CC_WHITESPACE); + len = *conf - p; + *tok = malloc(len + 1, M_TEMP, M_WAITOK | M_ZERO); + bcopy(p, *tok, len); + return (0); +} + +static void +parse_dir_ask_printenv(const char *var) +{ + char *val; + + val = getenv(var); + if (val != NULL) { + printf(" %s=%s\n", var, val); + freeenv(val); + } +} + +static int +parse_dir_ask(char **conf) +{ + char name[80]; + char *mnt; + int error; + + printf("\nLoader variables:\n"); + parse_dir_ask_printenv("vfs.root.mountfrom"); + parse_dir_ask_printenv("vfs.root.mountfrom.options"); + + printf("\nManual root filesystem specification:\n"); + printf(" <fstype>:<device> [options]\n"); + printf(" Mount <device> using filesystem <fstype>\n"); + printf(" and with the specified (optional) option list.\n"); + printf("\n"); + printf(" eg. ufs:/dev/da0s1a\n"); + printf(" zfs:tank\n"); + printf(" cd9660:/dev/acd0 ro\n"); + printf(" (which is equivalent to: "); + printf("mount -t cd9660 -o ro /dev/acd0 /)\n"); + printf("\n"); + printf(" ? List valid disk boot devices\n"); + printf(" . Yield 1 second (for background tasks)\n"); + printf(" <empty line> Abort manual input\n"); + + do { + error = EINVAL; + printf("\nmountroot> "); + cngets(name, sizeof(name), GETS_ECHO); + if (name[0] == '\0') + break; + if (name[0] == '?' && name[1] == '\0') { + printf("\nList of GEOM managed disk devices:\n "); + g_dev_print(); + continue; + } + if (name[0] == '.' && name[1] == '\0') { + pause("rmask", hz); + continue; + } + mnt = name; + error = parse_mount(&mnt); + if (error == -1) + printf("Invalid file system specification.\n"); + } while (error != 0); + + return (error); +} + +static int +parse_dir_md(char **conf) +{ + struct stat sb; + struct thread *td; + struct md_ioctl *mdio; + char *path, *tok; + int error, fd, len; + + td = curthread; + + error = parse_token(conf, &tok); + if (error) + return (error); + + len = strlen(tok); + mdio = malloc(sizeof(*mdio) + len + 1, M_TEMP, M_WAITOK | M_ZERO); + path = (void *)(mdio + 1); + bcopy(tok, path, len); + free(tok, M_TEMP); + + /* Get file status. */ + error = kern_stat(td, path, UIO_SYSSPACE, &sb); + if (error) + goto out; + + /* Open /dev/mdctl so that we can attach/detach. */ + error = kern_open(td, "/dev/" MDCTL_NAME, UIO_SYSSPACE, O_RDWR, 0); + if (error) + goto out; + + fd = td->td_retval[0]; + mdio->md_version = MDIOVERSION; + mdio->md_type = MD_VNODE; + + if (root_mount_mddev != -1) { + mdio->md_unit = root_mount_mddev; + DROP_GIANT(); + error = kern_ioctl(td, fd, MDIOCDETACH, (void *)mdio); + PICKUP_GIANT(); + /* Ignore errors. We don't care. */ + root_mount_mddev = -1; + } + + mdio->md_file = (void *)(mdio + 1); + mdio->md_options = MD_AUTOUNIT | MD_READONLY; + mdio->md_mediasize = sb.st_size; + mdio->md_unit = 0; + DROP_GIANT(); + error = kern_ioctl(td, fd, MDIOCATTACH, (void *)mdio); + PICKUP_GIANT(); + if (error) + goto out; + + if (mdio->md_unit > 9) { + printf("rootmount: too many md units\n"); + mdio->md_file = NULL; + mdio->md_options = 0; + mdio->md_mediasize = 0; + DROP_GIANT(); + error = kern_ioctl(td, fd, MDIOCDETACH, (void *)mdio); + PICKUP_GIANT(); + /* Ignore errors. We don't care. */ + error = ERANGE; + goto out; + } + + root_mount_mddev = mdio->md_unit; + printf(MD_NAME "%u attached to %s\n", root_mount_mddev, mdio->md_file); + + error = kern_close(td, fd); + + out: + free(mdio, M_TEMP); + return (error); +} + +static int +parse_dir_onfail(char **conf) +{ + char *action; + int error; + + error = parse_token(conf, &action); + if (error) + return (error); + + if (!strcmp(action, "continue")) + root_mount_onfail = A_CONTINUE; + else if (!strcmp(action, "panic")) + root_mount_onfail = A_PANIC; + else if (!strcmp(action, "reboot")) + root_mount_onfail = A_REBOOT; + else if (!strcmp(action, "retry")) + root_mount_onfail = A_RETRY; + else { + printf("rootmount: %s: unknown action\n", action); + error = EINVAL; + } + + free(action, M_TEMP); + return (0); +} + +static int +parse_dir_timeout(char **conf) +{ + char *tok, *endtok; + long secs; + int error; + + error = parse_token(conf, &tok); + if (error) + return (error); + + secs = strtol(tok, &endtok, 0); + error = (secs < 0 || *endtok != '\0') ? EINVAL : 0; + if (!error) + root_mount_timeout = secs; + free(tok, M_TEMP); + return (error); +} + +static int +parse_directive(char **conf) +{ + char *dir; + int error; + + error = parse_token(conf, &dir); + if (error) + return (error); + + if (strcmp(dir, ".ask") == 0) + error = parse_dir_ask(conf); + else if (strcmp(dir, ".md") == 0) + error = parse_dir_md(conf); + else if (strcmp(dir, ".onfail") == 0) + error = parse_dir_onfail(conf); + else if (strcmp(dir, ".timeout") == 0) + error = parse_dir_timeout(conf); + else { + printf("mountroot: invalid directive `%s'\n", dir); + /* Ignore the rest of the line. */ + (void)parse_skipto(conf, '\n'); + error = EINVAL; + } + free(dir, M_TEMP); + return (error); +} + +static int +parse_mount_dev_present(const char *dev) +{ + struct nameidata nd; + int error; + + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, dev, curthread); + error = namei(&nd); + if (!error) + vput(nd.ni_vp); + NDFREE(&nd, NDF_ONLY_PNBUF); + return (error != 0) ? 0 : 1; +} + +#define ERRMSGL 255 +static int +parse_mount(char **conf) +{ + char *errmsg; + struct mntarg *ma; + char *dev, *fs, *opts, *tok; + int delay, error, timeout; + + error = parse_token(conf, &tok); + if (error) + return (error); + fs = tok; + error = parse_skipto(&tok, ':'); + if (error) { + free(fs, M_TEMP); + return (error); + } + parse_poke(&tok, '\0'); + parse_advance(&tok); + dev = tok; + + if (root_mount_mddev != -1) { + /* Handle substitution for the md unit number. */ + tok = strstr(dev, "md#"); + if (tok != NULL) + tok[2] = '0' + root_mount_mddev; + } + + /* Parse options. */ + error = parse_token(conf, &tok); + opts = (error == 0) ? tok : NULL; + + printf("Trying to mount root from %s:%s [%s]...\n", fs, dev, + (opts != NULL) ? opts : ""); + + errmsg = malloc(ERRMSGL, M_TEMP, M_WAITOK | M_ZERO); + + if (vfs_byname(fs) == NULL) { + strlcpy(errmsg, "unknown file system", ERRMSGL); + error = ENOENT; + goto out; + } + + if (strcmp(fs, "zfs") != 0 && strstr(fs, "nfs") == NULL && + dev[0] != '\0' && !parse_mount_dev_present(dev)) { + printf("mountroot: waiting for device %s ...\n", dev); + delay = hz / 10; + timeout = root_mount_timeout * hz; + do { + pause("rmdev", delay); + timeout -= delay; + } while (timeout > 0 && !parse_mount_dev_present(dev)); + if (timeout <= 0) { + error = ENODEV; + goto out; + } + } + + ma = NULL; + ma = mount_arg(ma, "fstype", fs, -1); + ma = mount_arg(ma, "fspath", "/", -1); + ma = mount_arg(ma, "from", dev, -1); + ma = mount_arg(ma, "errmsg", errmsg, ERRMSGL); + ma = mount_arg(ma, "ro", NULL, 0); + ma = parse_mountroot_options(ma, opts); + error = kernel_mount(ma, MNT_ROOTFS); + + out: + if (error) { + printf("Mounting from %s:%s failed with error %d", + fs, dev, error); + if (errmsg[0] != '\0') + printf(": %s", errmsg); + printf(".\n"); + } + free(fs, M_TEMP); + free(errmsg, M_TEMP); + if (opts != NULL) + free(opts, M_TEMP); + /* kernel_mount can return -1 on error. */ + return ((error < 0) ? EDOOFUS : error); +} +#undef ERRMSGL + +static int +vfs_mountroot_parse(struct sbuf *sb, struct mount *mpdevfs) +{ + struct mount *mp; + char *conf; + int error; + + root_mount_mddev = -1; + +retry: + conf = sbuf_data(sb); + mp = TAILQ_NEXT(mpdevfs, mnt_list); + error = (mp == NULL) ? 0 : EDOOFUS; + root_mount_onfail = A_CONTINUE; + while (mp == NULL) { + error = parse_skipto(&conf, CC_NONWHITESPACE); + if (error == PE_EOL) { + parse_advance(&conf); + continue; + } + if (error < 0) + break; + switch (parse_peek(&conf)) { + case '#': + error = parse_skipto(&conf, '\n'); + break; + case '.': + error = parse_directive(&conf); + break; + default: + error = parse_mount(&conf); + break; + } + if (error < 0) + break; + /* Ignore any trailing garbage on the line. */ + if (parse_peek(&conf) != '\n') { + printf("mountroot: advancing to next directive...\n"); + (void)parse_skipto(&conf, '\n'); + } + mp = TAILQ_NEXT(mpdevfs, mnt_list); + } + if (mp != NULL) + return (0); + + /* + * We failed to mount (a new) root. + */ + switch (root_mount_onfail) { + case A_CONTINUE: + break; + case A_PANIC: + panic("mountroot: unable to (re-)mount root."); + /* NOTREACHED */ + case A_RETRY: + goto retry; + case A_REBOOT: + kern_reboot(RB_NOSYNC); + /* NOTREACHED */ + } + + return (error); +} + +static void +vfs_mountroot_conf0(struct sbuf *sb) +{ + char *s, *tok, *mnt, *opt; + int error; + + sbuf_printf(sb, ".onfail panic\n"); + sbuf_printf(sb, ".timeout %d\n", root_mount_timeout); + if (boothowto & RB_ASKNAME) + sbuf_printf(sb, ".ask\n"); +#ifdef ROOTDEVNAME + if (boothowto & RB_DFLTROOT) + sbuf_printf(sb, "%s\n", ROOTDEVNAME); +#endif + if (boothowto & RB_CDROM) { + sbuf_printf(sb, "cd9660:/dev/cd0 ro\n"); + sbuf_printf(sb, ".timeout 0\n"); + sbuf_printf(sb, "cd9660:/dev/acd0 ro\n"); + sbuf_printf(sb, ".timeout %d\n", root_mount_timeout); + } + s = getenv("vfs.root.mountfrom"); + if (s != NULL) { + opt = getenv("vfs.root.mountfrom.options"); + tok = s; + error = parse_token(&tok, &mnt); + while (!error) { + sbuf_printf(sb, "%s %s\n", mnt, + (opt != NULL) ? opt : ""); + free(mnt, M_TEMP); + error = parse_token(&tok, &mnt); + } + if (opt != NULL) + freeenv(opt); + freeenv(s); + } + if (rootdevnames[0] != NULL) + sbuf_printf(sb, "%s\n", rootdevnames[0]); + if (rootdevnames[1] != NULL) + sbuf_printf(sb, "%s\n", rootdevnames[1]); +#ifdef ROOTDEVNAME + if (!(boothowto & RB_DFLTROOT)) + sbuf_printf(sb, "%s\n", ROOTDEVNAME); +#endif + if (!(boothowto & RB_ASKNAME)) + sbuf_printf(sb, ".ask\n"); +} + +static int +vfs_mountroot_readconf(struct thread *td, struct sbuf *sb) +{ + static char buf[128]; + struct nameidata nd; + off_t ofs; + ssize_t resid; + int error, flags, len; + + NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, "/.mount.conf", td); + flags = FREAD; + error = vn_open(&nd, &flags, 0, NULL); + if (error) + return (error); + + NDFREE(&nd, NDF_ONLY_PNBUF); + ofs = 0; + len = sizeof(buf) - 1; + while (1) { + error = vn_rdwr(UIO_READ, nd.ni_vp, buf, len, ofs, + UIO_SYSSPACE, IO_NODELOCKED, td->td_ucred, + NOCRED, &resid, td); + if (error) + break; + if (resid == len) + break; + buf[len - resid] = 0; + sbuf_printf(sb, "%s", buf); + ofs += len - resid; + } + + VOP_UNLOCK(nd.ni_vp, 0); + vn_close(nd.ni_vp, FREAD, td->td_ucred, td); + return (error); +} + +static void +vfs_mountroot_wait(void) +{ + struct root_hold_token *h; + struct timeval lastfail; + int curfail; + + curfail = 0; + while (1) { + DROP_GIANT(); + g_waitidle(); + PICKUP_GIANT(); + mtx_lock(&mountlist_mtx); + if (LIST_EMPTY(&root_holds)) { + mtx_unlock(&mountlist_mtx); + break; + } + if (ppsratecheck(&lastfail, &curfail, 1)) { + printf("Root mount waiting for:"); + LIST_FOREACH(h, &root_holds, list) + printf(" %s", h->who); + printf("\n"); + } + msleep(&root_holds, &mountlist_mtx, PZERO | PDROP, "roothold", + hz); + } +} + +void +vfs_mountroot(void) +{ + struct mount *mp; + struct sbuf *sb; + struct thread *td; + time_t timebase; + int error; + + td = curthread; + + vfs_mountroot_wait(); + + sb = sbuf_new_auto(); + vfs_mountroot_conf0(sb); + sbuf_finish(sb); + + error = vfs_mountroot_devfs(td, &mp); + while (!error) { + error = vfs_mountroot_parse(sb, mp); + if (!error) { + error = vfs_mountroot_shuffle(td, mp); + if (!error) { + sbuf_clear(sb); + error = vfs_mountroot_readconf(td, sb); + sbuf_finish(sb); + } + } + } + + sbuf_delete(sb); + + /* + * Iterate over all currently mounted file systems and use + * the time stamp found to check and/or initialize the RTC. + * Call inittodr() only once and pass it the largest of the + * timestamps we encounter. + */ + timebase = 0; + mtx_lock(&mountlist_mtx); + mp = TAILQ_FIRST(&mountlist); + while (mp != NULL) { + if (mp->mnt_time > timebase) + timebase = mp->mnt_time; + mp = TAILQ_NEXT(mp, mnt_list); + } + mtx_unlock(&mountlist_mtx); + inittodr(timebase); + + /* Keep prison0's root in sync with the global rootvnode. */ + mtx_lock(&prison0.pr_mtx); + prison0.pr_root = rootvnode; + vref(prison0.pr_root); + mtx_unlock(&prison0.pr_mtx); + + mtx_lock(&mountlist_mtx); + atomic_store_rel_int(&root_mount_complete, 1); + wakeup(&root_mount_complete); + mtx_unlock(&mountlist_mtx); + + EVENTHANDLER_INVOKE(mountroot); +} + +static struct mntarg * +parse_mountroot_options(struct mntarg *ma, const char *options) +{ + char *p; + char *name, *name_arg; + char *val, *val_arg; + char *opts; + + if (options == NULL || options[0] == '\0') + return (ma); + + p = opts = strdup(options, M_MOUNT); + if (opts == NULL) { + return (ma); + } + + while((name = strsep(&p, ",")) != NULL) { + if (name[0] == '\0') + break; + + val = strchr(name, '='); + if (val != NULL) { + *val = '\0'; + ++val; + } + if( strcmp(name, "rw") == 0 || + strcmp(name, "noro") == 0) { + /* + * The first time we mount the root file system, + * we need to mount 'ro', so We need to ignore + * 'rw' and 'noro' mount options. + */ + continue; + } + name_arg = strdup(name, M_MOUNT); + val_arg = NULL; + if (val != NULL) + val_arg = strdup(val, M_MOUNT); + + ma = mount_arg(ma, name_arg, val_arg, + (val_arg != NULL ? -1 : 0)); + } + free(opts, M_MOUNT); + return (ma); +} |