switch-root.c revision a8fbdf5424be099ba1b2b1ec261c02b8759d6b0c
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering/***
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering This file is part of systemd.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering Copyright 2012 Harald Hoyer, Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering systemd is free software; you can redistribute it and/or modify it
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering under the terms of the GNU Lesser General Public License as published by
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering (at your option) any later version.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering systemd is distributed in the hope that it will be useful, but
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering Lesser General Public License for more details.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering You should have received a copy of the GNU Lesser General Public License
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering***/
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include <errno.h>
4871690d9e32608bbd9b18505b5326c2079c9690Allin Cottrell#include <fcntl.h>
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include <limits.h>
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include <stdbool.h>
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering#include <stdio.h>
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include <sys/mount.h>
d682b3a7e7c7c2941a4d3e193f1e330dbc9fae89Lennart Poettering#include <sys/stat.h>
d025f1e4dca8fc1436aff76f9e6185fe3e728daaZbigniew Jędrzejewski-Szmek#include <unistd.h>
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include "base-filesystem.h"
3b7124a8db56ed57525b9ecfd19cfdc8c9facba0Lennart Poettering#include "fd-util.h"
40b71e89bae4e51768db4dc50ec64c1e9c96eec4Sebastian Thorarensen#include "log.h"
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering#include "missing.h"
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering#include "mkdir.h"
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering#include "path-util.h"
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering#include "rm-rf.h"
3b3154df7e2773332bb814e167187367a0ccae4aLennart Poettering#include "string-util.h"
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek#include "switch-root.h"
46b131574fdd7d77c15a0919ca9010cad7aa6ac7Lennart Poettering#include "user-util.h"
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek#include "util.h"
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmekint switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) {
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek /* Don't try to unmount/move the old "/", there's no way to do it. */
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek static const char move_mounts[] =
46b131574fdd7d77c15a0919ca9010cad7aa6ac7Lennart Poettering "/dev\0"
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek "/proc\0"
f8294e4175918117ca6c131720bcf287eadcd029Josh Triplett "/sys\0"
b92bea5d2a9481de69bb627a7b442a9f58fca43dZbigniew Jędrzejewski-Szmek "/run\0";
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering _cleanup_close_ int old_root_fd = -1;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering struct stat new_root_stat;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering bool old_root_remove;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering const char *i, *temporary_old_root;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (path_equal(new_root, "/"))
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return 0;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering temporary_old_root = strjoina(new_root, oldroot);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering mkdir_p_label(temporary_old_root, 0755);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering old_root_remove = in_initrd();
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (stat(new_root, &new_root_stat) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return log_error_errno(errno, "Failed to stat directory %s: %m", new_root);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering /* Work-around for kernel design: the kernel refuses switching
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * root if any file systems are mounted MS_SHARED. Hence
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * remount them MS_PRIVATE here as a work-around.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering *
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_warning_errno(errno, "Failed to make \"/\" private mount: %m");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering NULSTR_FOREACH(i, move_mounts) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering char new_mount[PATH_MAX];
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering struct stat sb;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering snprintf(new_mount, sizeof(new_mount), "%s%s", new_root, i);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering mkdir_p_label(new_mount, 0755);
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering if ((stat(new_mount, &sb) < 0) ||
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering sb.st_dev != new_root_stat.st_dev) {
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering /* Mount point seems to be mounted already or
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * stat failed. Unmount the old mount
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * point. */
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (umount2(i, MNT_DETACH) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_warning_errno(errno, "Failed to unmount %s: %m", i);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering continue;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (mount(i, new_mount, NULL, mountflags, NULL) < 0) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (mountflags & MS_MOVE) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (umount2(i, MNT_FORCE) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_warning_errno(errno, "Failed to unmount %s: %m", i);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering if (mountflags & MS_BIND)
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
178cc7700c23ac088cd7190d7854282075028d91Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering /* Do not fail, if base_filesystem_create() fails. Not all
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * switch roots are like base_filesystem_create() wants them
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * to look like. They might even boot, if they are RO and
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * don't have the FS layout. Just ignore the error and
3b3154df7e2773332bb814e167187367a0ccae4aLennart Poettering * switch_root() nevertheless. */
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (chdir(new_root) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return log_error_errno(errno, "Failed to change directory to %s: %m", new_root);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (old_root_remove) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (old_root_fd < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_warning_errno(errno, "Failed to open root directory: %m");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering /* We first try a pivot_root() so that we can umount the old
3b3154df7e2773332bb814e167187367a0ccae4aLennart Poettering * root dir. In many cases (i.e. where rootfs is /), that's
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * not possible however, and hence we simply overmount root */
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (pivot_root(new_root, temporary_old_root) >= 0) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering /* Immediately get rid of the old root, if detach_oldroot is set.
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering * Since we are running off it we need to do this lazily. */
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m",
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering oldroot,
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering errno == ENOENT ? "ignoring" : "leaving it around");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (chroot(".") < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return log_error_errno(errno, "Failed to change root: %m");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (chdir("/") < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return log_error_errno(errno, "Failed to change directory: %m");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (old_root_fd >= 0) {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering struct stat rb;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering if (fstat(old_root_fd, &rb) < 0)
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering log_warning_errno(errno, "Failed to stat old root directory, leaving: %m");
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering else {
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering (void) rm_rf_children(old_root_fd, 0, &rb);
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering old_root_fd = -1;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering }
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering return 0;
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering}
35e2e347d38cc2f8bd7c38a0d8a5129f5fbb0ab9Lennart Poettering