aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/kern_shutdown.c
diff options
context:
space:
mode:
authorEdward Tomasz Napierala <trasz@FreeBSD.org>2015-09-18 17:32:22 +0000
committerEdward Tomasz Napierala <trasz@FreeBSD.org>2015-09-18 17:32:22 +0000
commit0d3d0cc3588de0467a7e499640499f8e6725d4bb (patch)
tree64974d4c066398aa4020300d6dc8d32a856315a2 /sys/kern/kern_shutdown.c
parent0510aedcd51ad288ed1b577c17f50915fba1d8af (diff)
downloadsrc-0d3d0cc3588de0467a7e499640499f8e6725d4bb.tar.gz
src-0d3d0cc3588de0467a7e499640499f8e6725d4bb.zip
Kernel part of reroot support - a way to change rootfs without reboot.
Note that the mountlist manipulations are somewhat fragile, and not very pretty. The reason for this is to avoid changing vfs_mountroot(), which is (obviously) rather mission-critical, but not very well documented, and thus hard to test properly. It might be possible to rework it to use its own simple root mount mechanism instead of vfs_mountroot(). Reviewed by: kib@ MFC after: 1 month Sponsored by: The FreeBSD Foundation Differential Revision: https://reviews.freebsd.org/D2698
Notes
Notes: svn path=/head/; revision=287964
Diffstat (limited to 'sys/kern/kern_shutdown.c')
-rw-r--r--sys/kern/kern_shutdown.c133
1 files changed, 130 insertions, 3 deletions
diff --git a/sys/kern/kern_shutdown.c b/sys/kern/kern_shutdown.c
index 3a91673bab11..29ea6931cad9 100644
--- a/sys/kern/kern_shutdown.c
+++ b/sys/kern/kern_shutdown.c
@@ -50,6 +50,7 @@ __FBSDID("$FreeBSD$");
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/eventhandler.h>
+#include <sys/filedesc.h>
#include <sys/jail.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
@@ -150,10 +151,16 @@ static struct dumperinfo dumper; /* our selected dumper */
static struct pcb dumppcb; /* Registers. */
lwpid_t dumptid; /* Thread ID. */
+static struct cdevsw reroot_cdevsw = {
+ .d_version = D_VERSION,
+ .d_name = "reroot",
+};
+
static void poweroff_wait(void *, int);
static void shutdown_halt(void *junk, int howto);
static void shutdown_panic(void *junk, int howto);
static void shutdown_reset(void *junk, int howto);
+static int kern_reroot(void);
/* register various local shutdown events */
static void
@@ -173,6 +180,26 @@ shutdown_conf(void *unused)
SYSINIT(shutdown_conf, SI_SUB_INTRINSIC, SI_ORDER_ANY, shutdown_conf, NULL);
/*
+ * The only reason this exists is to create the /dev/reroot/ directory,
+ * used by reroot code in init(8) as a mountpoint for tmpfs.
+ */
+static void
+reroot_conf(void *unused)
+{
+ int error;
+ struct cdev *cdev;
+
+ error = make_dev_p(MAKEDEV_CHECKNAME | MAKEDEV_WAITOK, &cdev,
+ &reroot_cdevsw, NULL, UID_ROOT, GID_WHEEL, 0600, "reroot/reroot");
+ if (error != 0) {
+ printf("%s: failed to create device node, error %d",
+ __func__, error);
+ }
+}
+
+SYSINIT(reroot_conf, SI_SUB_DEVFS, SI_ORDER_ANY, reroot_conf, NULL);
+
+/*
* The system call that results in a reboot.
*/
/* ARGSUSED */
@@ -188,9 +215,13 @@ sys_reboot(struct thread *td, struct reboot_args *uap)
if (error == 0)
error = priv_check(td, PRIV_REBOOT);
if (error == 0) {
- mtx_lock(&Giant);
- kern_reboot(uap->opt);
- mtx_unlock(&Giant);
+ if (uap->opt & RB_REROOT) {
+ error = kern_reroot();
+ } else {
+ mtx_lock(&Giant);
+ kern_reboot(uap->opt);
+ mtx_unlock(&Giant);
+ }
}
return (error);
}
@@ -336,6 +367,102 @@ kern_reboot(int howto)
}
/*
+ * The system call that results in changing the rootfs.
+ */
+static int
+kern_reroot(void)
+{
+ struct vnode *oldrootvnode, *vp;
+ struct mount *mp, *devmp;
+ int error;
+
+ if (curproc != initproc)
+ return (EPERM);
+
+ /*
+ * Mark the filesystem containing currently-running executable
+ * (the temporary copy of init(8)) busy.
+ */
+ vp = curproc->p_textvp;
+ error = vn_lock(vp, LK_SHARED);
+ if (error != 0)
+ return (error);
+ mp = vp->v_mount;
+ error = vfs_busy(mp, MBF_NOWAIT);
+ if (error != 0) {
+ vfs_ref(mp);
+ VOP_UNLOCK(vp, 0);
+ error = vfs_busy(mp, 0);
+ vn_lock(vp, LK_SHARED | LK_RETRY);
+ vfs_rel(mp);
+ if (error != 0) {
+ VOP_UNLOCK(vp, 0);
+ return (ENOENT);
+ }
+ if (vp->v_iflag & VI_DOOMED) {
+ VOP_UNLOCK(vp, 0);
+ vfs_unbusy(mp);
+ return (ENOENT);
+ }
+ }
+ VOP_UNLOCK(vp, 0);
+
+ /*
+ * Remove the filesystem containing currently-running executable
+ * from the mount list, to prevent it from being unmounted
+ * by vfs_unmountall(), and to avoid confusing vfs_mountroot().
+ *
+ * Also preserve /dev - forcibly unmounting it could cause driver
+ * reinitialization.
+ */
+
+ vfs_ref(rootdevmp);
+ devmp = rootdevmp;
+ rootdevmp = NULL;
+
+ mtx_lock(&mountlist_mtx);
+ TAILQ_REMOVE(&mountlist, mp, mnt_list);
+ TAILQ_REMOVE(&mountlist, devmp, mnt_list);
+ mtx_unlock(&mountlist_mtx);
+
+ oldrootvnode = rootvnode;
+
+ /*
+ * Unmount everything except for the two filesystems preserved above.
+ */
+ vfs_unmountall();
+
+ /*
+ * Add /dev back; vfs_mountroot() will move it into its new place.
+ */
+ mtx_lock(&mountlist_mtx);
+ TAILQ_INSERT_HEAD(&mountlist, devmp, mnt_list);
+ mtx_unlock(&mountlist_mtx);
+ rootdevmp = devmp;
+ vfs_rel(rootdevmp);
+
+ /*
+ * Mount the new rootfs.
+ */
+ vfs_mountroot();
+
+ /*
+ * Update all references to the old rootvnode.
+ */
+ mountcheckdirs(oldrootvnode, rootvnode);
+
+ /*
+ * Add the temporary filesystem back and unbusy it.
+ */
+ mtx_lock(&mountlist_mtx);
+ TAILQ_INSERT_TAIL(&mountlist, mp, mnt_list);
+ mtx_unlock(&mountlist_mtx);
+ vfs_unbusy(mp);
+
+ return (0);
+}
+
+/*
* If the shutdown was a clean halt, behave accordingly.
*/
static void