From: Daniel Drake Date: Tue, 14 Jul 2009 12:41:33 +0000 (+0200) Subject: switch_root: add subroot support X-Git-Url: https://err.no/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a692a8745941a192528c5e2a05de97155ba586f9;p=util-linux switch_root: add subroot support The current switch_root can only switch to a new root that is the root of a mount point. This patch adds support for "subroots", where the new root is somewhere below a mount point. It does this by adding in a few extra steps to chroot into the subroot after the enclosing partition has been moved and entered. This will be used by OLPC, who sort-of have 2 copies of Fedora stored on a single partition under different directory trees, where the initramfs decides which one to boot into [kzak@redhat.com: - port to the current u-l-ng switch_root code - don't use static buffer for "dir" in get_parent_mount()] CC: Peter Jones Signed-off-by: Daniel Drake Signed-off-by: Karel Zak --- diff --git a/sys-utils/switch_root.c b/sys-utils/switch_root.c index b947f544..b192a08e 100644 --- a/sys-utils/switch_root.c +++ b/sys-utils/switch_root.c @@ -33,6 +33,7 @@ #include #include #include +#include #ifndef MS_MOVE #define MS_MOVE 8192 @@ -108,13 +109,58 @@ done: return rc; } +/* find the enclosing mount point of a path, by examining the backing device + * of parent directories until we reach / or find a parent with a differing + * device. Result must be freed. + */ +static char *get_parent_mount(const char *path) +{ + struct stat sb; + char *dir; + char tmp[PATH_MAX]; + dev_t inner_dev; + + if (stat(path, &sb) != 0) { + warn("failed to stat %s", path); + return NULL; + } + + inner_dev = sb.st_dev; + dir = strdup(path); + + while (dir) { + char *parent; + + strncpy(tmp, dir, PATH_MAX); + tmp[PATH_MAX - 1] = '\0'; + parent = dirname(tmp); + + if (stat(parent, &sb) != 0) { + warn("failed to stat %s", parent); + return NULL; + } + if (sb.st_dev != inner_dev) + return dir; + + strncpy(dir, parent, PATH_MAX); + dir[PATH_MAX - 1] = '\0'; + + /* maybe we've reached / */ + if (*dir == '/' && !*(dir + 1)) + return dir; + } + return NULL; +} + static int switchroot(const char *newroot) { /* Don't try to unmount the old "/", there's no way to do it. */ const char *umounts[] = { "/dev", "/proc", "/sys", NULL }; int i; - int cfd; + int cfd, rc = -1; pid_t pid; + const char *chroot_path = NULL; + char *newroot_mnt; for (i = 0; umounts[i] != NULL; i++) { char newmount[PATH_MAX]; @@ -129,21 +175,43 @@ static int switchroot(const char *newroot) } } + newroot_mnt = get_parent_mount(newroot); + if (newroot_mnt && strcmp(newroot, newroot_mnt)) { + /* newroot is not a mount point, so we have to MS_MOVE the + * parent mount point and then chroot in to the "subroot" + */ + chroot_path = newroot + strlen(newroot_mnt); + newroot = newroot_mnt; + } + if (chdir(newroot)) { warn("failed to change directory to %s", newroot); - return -1; + goto done; } cfd = open("/", O_RDONLY); if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) { warn("failed to mount moving %s to /", newroot); - return -1; + goto done; } + /* move to the real root of the device */ if (chroot(".")) { warn("failed to change root"); - return -1; + goto done; + } + + /* move to the subdirectory on the root device (subroot) */ + if (chroot_path) { + if (chdir(chroot_path)) { + warn("failed to chdir to subroot %s", chroot_path); + goto done; + } + if (chroot(".")) { + warn("failed to change root to subroot %s", chroot_path); + goto done; + } } if (cfd >= 0) { @@ -155,7 +223,10 @@ static int switchroot(const char *newroot) } close(cfd); } - return 0; + rc = 0; +done: + free(newroot_mnt); + return rc; } static void usage(FILE *output)