| /* |
| * switchroot.c - switch to new root directory and start init. |
| * |
| * Copyright 2002-2009 Red Hat, Inc. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. |
| * |
| * Authors: |
| * Peter Jones <pjones@redhat.com> |
| * Jeremy Katz <katzj@redhat.com> |
| */ |
| #include <sys/mount.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/statfs.h> |
| #include <sys/param.h> |
| #include <fcntl.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <ctype.h> |
| #include <dirent.h> |
| #include <getopt.h> |
| |
| #include "c.h" |
| #include "nls.h" |
| #include "closestream.h" |
| #include "statfs_magic.h" |
| |
| #ifndef MS_MOVE |
| #define MS_MOVE 8192 |
| #endif |
| |
| #ifndef MNT_DETACH |
| #define MNT_DETACH 0x00000002 /* Just detach from the tree */ |
| #endif |
| |
| /* remove all files/directories below dirName -- don't cross mountpoints */ |
| static int recursiveRemove(int fd) |
| { |
| struct stat rb; |
| DIR *dir; |
| int rc = -1; |
| int dfd; |
| |
| if (!(dir = fdopendir(fd))) { |
| warn(_("failed to open directory")); |
| goto done; |
| } |
| |
| /* fdopendir() precludes us from continuing to use the input fd */ |
| dfd = dirfd(dir); |
| |
| if (fstat(dfd, &rb)) { |
| warn(_("stat failed")); |
| goto done; |
| } |
| |
| while(1) { |
| struct dirent *d; |
| int isdir = 0; |
| |
| errno = 0; |
| if (!(d = readdir(dir))) { |
| if (errno) { |
| warn(_("failed to read directory")); |
| goto done; |
| } |
| break; /* end of directory */ |
| } |
| |
| if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, "..")) |
| continue; |
| #ifdef _DIRENT_HAVE_D_TYPE |
| if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN) |
| #endif |
| { |
| struct stat sb; |
| |
| if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) { |
| warn(_("stat of %s failed"), d->d_name); |
| continue; |
| } |
| |
| /* skip if device is not the same */ |
| if (sb.st_dev != rb.st_dev) |
| continue; |
| |
| /* remove subdirectories */ |
| if (S_ISDIR(sb.st_mode)) { |
| int cfd; |
| |
| cfd = openat(dfd, d->d_name, O_RDONLY); |
| if (cfd >= 0) { |
| recursiveRemove(cfd); |
| close(cfd); |
| } |
| isdir = 1; |
| } |
| } |
| |
| if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0)) |
| warn(_("failed to unlink %s"), d->d_name); |
| } |
| |
| rc = 0; /* success */ |
| |
| done: |
| if (dir) |
| closedir(dir); |
| return rc; |
| } |
| |
| 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", "/run", NULL }; |
| int i; |
| int cfd; |
| pid_t pid; |
| struct stat newroot_stat, sb; |
| |
| if (stat(newroot, &newroot_stat) != 0) { |
| warn(_("stat of %s failed"), newroot); |
| return -1; |
| } |
| |
| for (i = 0; umounts[i] != NULL; i++) { |
| char newmount[PATH_MAX]; |
| |
| snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]); |
| |
| if ((stat(newmount, &sb) != 0) || (sb.st_dev != newroot_stat.st_dev)) { |
| /* mount point seems to be mounted already or stat failed */ |
| umount2(umounts[i], MNT_DETACH); |
| continue; |
| } |
| |
| if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) { |
| warn(_("failed to mount moving %s to %s"), |
| umounts[i], newmount); |
| warnx(_("forcing unmount of %s"), umounts[i]); |
| umount2(umounts[i], MNT_FORCE); |
| } |
| } |
| |
| if (chdir(newroot)) { |
| warn(_("failed to change directory to %s"), newroot); |
| return -1; |
| } |
| |
| cfd = open("/", O_RDONLY); |
| if (cfd < 0) { |
| warn(_("cannot open %s"), "/"); |
| return -1; |
| } |
| |
| if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) { |
| close(cfd); |
| warn(_("failed to mount moving %s to /"), newroot); |
| return -1; |
| } |
| |
| if (chroot(".")) { |
| close(cfd); |
| warn(_("failed to change root")); |
| return -1; |
| } |
| |
| pid = fork(); |
| if (pid <= 0) { |
| struct statfs stfs; |
| |
| if (fstatfs(cfd, &stfs) == 0 && |
| (F_TYPE_EQUAL(stfs.f_type, STATFS_RAMFS_MAGIC) || |
| F_TYPE_EQUAL(stfs.f_type, STATFS_TMPFS_MAGIC))) |
| recursiveRemove(cfd); |
| else |
| warn(_("old root filesystem is not an initramfs")); |
| if (pid == 0) |
| exit(EXIT_SUCCESS); |
| } |
| |
| close(cfd); |
| return 0; |
| } |
| |
| static void __attribute__((__noreturn__)) usage(FILE *output) |
| { |
| fputs(USAGE_HEADER, output); |
| fprintf(output, _(" %s [options] <newrootdir> <init> <args to init>\n"), |
| program_invocation_short_name); |
| |
| fputs(USAGE_SEPARATOR, output); |
| fputs(_("Switch to another filesystem as the root of the mount tree.\n"), output); |
| |
| fputs(USAGE_OPTIONS, output); |
| fputs(USAGE_HELP, output); |
| fputs(USAGE_VERSION, output); |
| fprintf(output, USAGE_MAN_TAIL("switch_root(8)")); |
| |
| exit(output == stderr ? EXIT_FAILURE : EXIT_SUCCESS); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| char *newroot, *init, **initargs; |
| int c; |
| static const struct option longopts[] = { |
| {"version", no_argument, NULL, 'V'}, |
| {"help", no_argument, NULL, 'h'}, |
| {NULL, 0, NULL, 0} |
| }; |
| |
| atexit(close_stdout); |
| |
| while ((c = getopt_long(argc, argv, "Vh", longopts, NULL)) != -1) |
| switch (c) { |
| case 'V': |
| printf(UTIL_LINUX_VERSION); |
| return EXIT_SUCCESS; |
| case 'h': |
| usage(stdout); |
| default: |
| errtryhelp(EXIT_FAILURE); |
| } |
| if (argc < 3) |
| usage(stderr); |
| |
| newroot = argv[1]; |
| init = argv[2]; |
| initargs = &argv[2]; |
| |
| if (!*newroot || !*init) |
| usage(stderr); |
| |
| if (switchroot(newroot)) |
| errx(EXIT_FAILURE, _("failed. Sorry.")); |
| |
| if (access(init, X_OK)) |
| warn(_("cannot access %s"), init); |
| |
| execv(init, initargs); |
| err(EXIT_FAILURE, _("failed to execute %s"), init); |
| } |
| |