Index: etc/defaults/rc.conf =================================================================== --- etc/defaults/rc.conf +++ etc/defaults/rc.conf @@ -23,6 +23,7 @@ #rc_debug="NO" # Set to YES to enable debugging output from rc.d rc_info="NO" # Enables display of informational messages at boot. +rc_parallel="NO" # Run startup scripts in parallel rc_startmsgs="YES" # Show "Starting foo:" messages at boot rcshutdown_timeout="90" # Seconds to wait before terminating rc.shutdown early_late_divider="FILESYSTEMS" # Script that separates early/late @@ -84,6 +85,7 @@ #geli_mirror_home_flags="-k /etc/geli/home.keys" root_rw_mount="YES" # Set to NO to inhibit remounting root read-write. +root_hold_delay="30" # Time to wait for root mount hold release. fsck_y_enable="NO" # Set to YES to do fsck -y if the initial preen fails. fsck_y_flags="" # Additional flags for fsck -y background_fsck="YES" # Attempt to run fsck in the background where possible. Index: etc/rc.d/mountcritlocal =================================================================== --- etc/rc.d/mountcritlocal +++ etc/rc.d/mountcritlocal @@ -15,7 +15,7 @@ mountcritlocal_start() { - local err + local err holders waited # Set up the list of network filesystem types for which mounting # should be delayed until after network initialization. @@ -35,8 +35,42 @@ mount_excludes="${mount_excludes}${fstype}," done mount_excludes=${mount_excludes%,} + + # Originally, root mount hold had to be released before mounting the root + # filesystem. This delayed the boot, so it was changed to only wait if + # the root device isn't readily available. This can result in this script + # executing before all the devices - such as graid(8) - are available. + # Thus, should the mount fail, we will wait for the root mount hold release + # and retry. mount -a -t ${mount_excludes} err=$? + if [ $? -ne 0 ]; then + echo + echo 'Mounting /etc/fstab filesystems failed,' \ + 'will retry after root mount hold release' + + waited=0 + while [ ${waited} -lt ${root_hold_delay} ]; do + holders="$(sysctl -n vfs.root_mount_hold)" + if [ -z "${holders}" ]; then + break; + fi + if [ ${waited} -eq 0 ]; then + echo -n "Waiting ${root_hold_delay}s" \ + "for the root mount holders: ${holders}" + else + echo -n . + fi + if [ ${waited} -eq ${root_hold_delay} ]; then + break 2 + fi + sleep 1 + waited=$(($waited + 1)) + done + mount -a -t ${mount_excludes} + err=$? + fi + check_startmsgs && echo '.' case ${err} in @@ -44,7 +78,7 @@ ;; *) echo 'Mounting /etc/fstab filesystems failed,' \ - ' startup aborted' + 'startup aborted' stop_boot true ;; esac Index: sys/kern/vfs_mountroot.c =================================================================== --- sys/kern/vfs_mountroot.c +++ sys/kern/vfs_mountroot.c @@ -88,6 +88,8 @@ static int parse_mount(char **); static struct mntarg *parse_mountroot_options(struct mntarg *, const char *); +static int sysctl_vfs_root_mount_hold(SYSCTL_HANDLER_ARGS); +static int vfs_mountroot_wait_if_neccessary(const char *fs, const char *dev); /* * The vnode of the system's root (/ in the filesystem, without chroot @@ -129,6 +131,35 @@ static int root_mount_timeout = 3; TUNABLE_INT("vfs.mountroot.timeout", &root_mount_timeout); +SYSCTL_PROC(_vfs, OID_AUTO, root_mount_hold, + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + NULL, 0, sysctl_vfs_root_mount_hold, "A", + "List of root mount hold tokens"); + +static int +sysctl_vfs_root_mount_hold(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sb; + struct root_hold_token *h; + int error; + + sbuf_new(&sb, NULL, 256, SBUF_AUTOEXTEND | SBUF_INCLUDENUL); + + mtx_lock(&root_holds_mtx); + LIST_FOREACH(h, &root_holds, list) { + if (h != LIST_FIRST(&root_holds)) + sbuf_putc(&sb, ' '); + sbuf_printf(&sb, "%s", h->who); + } + mtx_unlock(&root_holds_mtx); + + error = sbuf_finish(&sb); + if (error == 0) + error = SYSCTL_OUT(req, sbuf_data(&sb), sbuf_len(&sb)); + sbuf_delete(&sb); + return (error); +} + struct root_hold_token * root_mount_hold(const char *identifier) { @@ -673,7 +704,7 @@ char *errmsg; struct mntarg *ma; char *dev, *fs, *opts, *tok; - int delay, error, timeout; + int error; error = parse_token(conf, &tok); if (error) @@ -710,20 +741,9 @@ 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; - } - } + error = vfs_mountroot_wait_if_neccessary(fs, dev); + if (error != 0) + goto out; ma = NULL; ma = mount_arg(ma, "fstype", fs, -1); @@ -931,6 +951,51 @@ } } +static int +vfs_mountroot_wait_if_neccessary(const char *fs, const char *dev) +{ + int delay, timeout; + + /* + * In case of ZFS and NFS we don't have a way to wait for + * specific device. + */ + if (strcmp(fs, "zfs") == 0 || strstr(fs, "nfs") != NULL || + dev[0] == '\0') { + vfs_mountroot_wait(); + return (0); + } + + /* + * Otherwise, no point in waiting if the device is already there. + * Note that we must wait for GEOM to finish reconfiguring itself, + * eg for geom_part(4) to finish tasting. + */ + DROP_GIANT(); + g_waitidle(); + PICKUP_GIANT(); + if (parse_mount_dev_present(dev)) + return (0); + + /* + * No luck. Let's wait. This code looks weird, but it's that way + * to behave exactly as it used to work before. + */ + vfs_mountroot_wait(); + 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) + return (ENODEV); + + return (0); +} + void vfs_mountroot(void) { @@ -942,8 +1007,6 @@ td = curthread; - vfs_mountroot_wait(); - sb = sbuf_new_auto(); vfs_mountroot_conf0(sb); sbuf_finish(sb);