/* vi: set sw=4 ts=4: */
-/* Copyright 2005 Rob Landley <rob@landley.net>
+/*
+ * Copyright 2005 Rob Landley <rob@landley.net>
*
* Switch from rootfs to another filesystem as the root of the mount tree.
*
//config: * Because the Linux kernel uses rootfs internally as the starting
//config: and ending point for searching through the kernel's doubly linked
//config: list of active mount points. That's why.
+//config:
+// RUN_INIT config item is in klibc-utils
//applet:IF_SWITCH_ROOT(APPLET(switch_root, BB_DIR_SBIN, BB_SUID_DROP))
// APPLET_ODDNAME:name main location suid_type help
//kbuild:lib-$(CONFIG_SWITCH_ROOT) += switch_root.o
//kbuild:lib-$(CONFIG_RUN_INIT) += switch_root.o
-//usage:#define switch_root_trivial_usage
-//usage: "[-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
-//usage:#define switch_root_full_usage "\n\n"
-//usage: "Free initramfs and switch to another root fs:\n"
-//usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
-//usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
-//usage: "\n -c DEV Reopen stdio to DEV after switch"
-
-//usage:#define run_init_trivial_usage
-//usage: "[-d CAP,CAP...] [-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
-//usage:#define run_init_full_usage "\n\n"
-//usage: "Free initramfs and switch to another root fs:\n"
-//usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
-//usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
-//usage: "\n -c DEV Reopen stdio to DEV after switch"
-//usage: "\n -d CAPS Drop capabilities"
-
#include <sys/vfs.h>
#include <sys/mount.h>
#if ENABLE_RUN_INIT
# include <sys/prctl.h>
+# ifndef PR_CAPBSET_READ
+# define PR_CAPBSET_READ 23
+# endif
+# ifndef PR_CAPBSET_DROP
+# define PR_CAPBSET_DROP 24
+# endif
# include <linux/capability.h>
// #include <sys/capability.h>
// This header is in libcap, but the functions are in libc.
{
struct caps caps;
- /* Get the current capability mask */
getcaps(&caps);
-
- /* Drop the bit */
caps.data[CAP_TO_INDEX(cap_idx)].inheritable &= ~CAP_TO_MASK(cap_idx);
-
- /* And drop the capability. */
if (capset(&caps.header, caps.data) != 0)
bb_perror_msg_and_die("capset");
}
char *newroot, *console = NULL;
struct stat st;
struct statfs stfs;
+ unsigned dry_run = 0;
dev_t rootdev;
- // Parse args (-c console). '+': stop at first non-option
+ // Parse args. '+': stop at first non-option
if (ENABLE_SWITCH_ROOT && (!ENABLE_RUN_INIT || applet_name[0] == 's')) {
+//usage:#define switch_root_trivial_usage
+//usage: "[-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
+//usage:#define switch_root_full_usage "\n\n"
+//usage: "Free initramfs and switch to another root fs:\n"
+//usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
+//usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
+//usage: "\n -c DEV Reopen stdio to DEV after switch"
getopt32(argv, "^+"
"c:"
"\0" "-2" /* minimum 2 args */,
);
} else {
#if ENABLE_RUN_INIT
+//usage:#define run_init_trivial_usage
+//usage: "[-d CAP,CAP...] [-n] [-c CONSOLE_DEV] NEW_ROOT NEW_INIT [ARGS]"
+//usage:#define run_init_full_usage "\n\n"
+//usage: "Free initramfs and switch to another root fs:\n"
+//usage: "chroot to NEW_ROOT, delete all in /, move NEW_ROOT to /,\n"
+//usage: "execute NEW_INIT. PID must be 1. NEW_ROOT must be a mountpoint.\n"
+//usage: "\n -c DEV Reopen stdio to DEV after switch"
+//usage: "\n -d CAPS Drop capabilities"
+//usage: "\n -n Dry run"
char *cap_list = NULL;
- getopt32(argv, "^+"
- "c:d:"
+ dry_run = getopt32(argv, "^+"
+ "c:d:n"
"\0" "-2" /* minimum 2 args */,
&console,
&cap_list
);
+ dry_run >>= 2; // -n
if (cap_list)
drop_capabilities(cap_list);
#endif
xstat("/", &st);
rootdev = st.st_dev;
xstat(".", &st);
- if (st.st_dev == rootdev || getpid() != 1) {
+ if (st.st_dev == rootdev) {
// Show usage, it says new root must be a mountpoint
- // and we must be PID 1
+ bb_show_usage();
+ }
+ if (!dry_run && getpid() != 1) {
+ // Show usage, it says we must be PID 1
bb_show_usage();
}
// we mean it. I could make this a CONFIG option, but I would get email
// from all the people who WILL destroy their filesystems.
if (stat("/init", &st) != 0 || !S_ISREG(st.st_mode)) {
- bb_error_msg_and_die("/init is not a regular file");
+ bb_error_msg_and_die("'%s' is not a regular file", "/init");
}
statfs("/", &stfs); // this never fails
if ((unsigned)stfs.f_type != RAMFS_MAGIC
bb_error_msg_and_die("root filesystem is not ramfs/tmpfs");
}
- // Zap everything out of rootdev
- delete_contents("/", rootdev);
+ if (!dry_run) {
+ // Zap everything out of rootdev
+ delete_contents("/", rootdev);
- // Overmount / with newdir and chroot into it
- if (mount(".", "/", NULL, MS_MOVE, NULL)) {
- // For example, fails when newroot is not a mountpoint
- bb_perror_msg_and_die("error moving root");
+ // Overmount / with newdir and chroot into it
+ if (mount(".", "/", NULL, MS_MOVE, NULL)) {
+ // For example, fails when newroot is not a mountpoint
+ bb_perror_msg_and_die("error moving root");
+ }
}
xchroot(".");
// The chdir is needed to recalculate "." and ".." links
}
}
- // Exec real init
- execv(argv[0], argv);
+ if (dry_run) {
+ // Does NEW_INIT look like it can be executed?
+ //xstat(argv[0], &st);
+ //if (!S_ISREG(st.st_mode))
+ // bb_perror_msg_and_die("'%s' is not a regular file", argv[0]);
+ if (access(argv[0], X_OK) == 0)
+ return 0;
+ } else {
+ // Exec NEW_INIT
+ execv(argv[0], argv);
+ }
bb_perror_msg_and_die("can't execute '%s'", argv[0]);
}