diff --git a/src/core/namespace.c b/src/core/namespace.c index 7752e48fb0..c0d0cc9715 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -2486,7 +2486,7 @@ int setup_namespace( goto finish; /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ - r = mount_move_root(root); + r = mount_pivot_root(root); if (r == -EINVAL && root_directory) { /* If we are using root_directory and we don't have privileges (ie: user manager in a user * namespace) and the root_directory is already a mount point in the parent namespace, @@ -2496,7 +2496,7 @@ int setup_namespace( r = mount_nofollow_verbose(LOG_DEBUG, root, root, NULL, MS_BIND|MS_REC, NULL); if (r < 0) goto finish; - r = mount_move_root(root); + r = mount_pivot_root(root); } if (r < 0) { log_debug_errno(r, "Failed to mount root with MS_MOVE: %m"); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 1f827e2061..681d698800 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -21,6 +21,7 @@ #include "fs-util.h" #include "glyph-util.h" #include "hashmap.h" +#include "initrd-util.h" #include "label.h" #include "libmount-util.h" #include "missing_mount.h" @@ -489,6 +490,52 @@ int mount_move_root(const char *path) { return RET_NERRNO(chdir("/")); } +int mount_pivot_root(const char *path) { + _cleanup_close_ int fd_oldroot = -EBADF, fd_newroot = -EBADF; + + assert(path); + + /* pivot_root() isn't currently supported in the initramfs. */ + if (in_initrd()) + return mount_move_root(path); + + fd_oldroot = open("/", O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd_oldroot < 0) + return log_debug_errno(errno, "Failed to open old rootfs"); + + fd_newroot = open(path, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd_newroot < 0) + return log_debug_errno(errno, "Failed to open new rootfs '%s': %m", path); + + /* Change into the new rootfs. */ + if (fchdir(fd_newroot) < 0) + return log_debug_errno(errno, "Failed to change into new rootfs '%s': %m", path); + + /* Let the kernel tuck the new root under the old one. */ + if (pivot_root(".", ".") < 0) + return log_debug_errno(errno, "Failed to pivot root to new rootfs '%s': %m", path); + + + /* At this point the new root is tucked under the old root. If we want + * to unmount it we cannot be fchdir()ed into it. So escape back to the + * old root. */ + if (fchdir(fd_oldroot) < 0) + return log_debug_errno(errno, "Failed to change back to old rootfs: %m"); + + /* Note, usually we should set mount propagation up here but we'll + * assume that the caller has already done that. */ + + /* Get rid of the old root and reveal our brand new root. */ + if (umount2(".", MNT_DETACH) < 0) + return log_debug_errno(errno, "Failed to unmount old rootfs: %m"); + + if (fchdir(fd_newroot) < 0) + return log_debug_errno(errno, "Failed to switch to new rootfs '%s': %m", path); + + return 0; +} + + int repeat_unmount(const char *path, int flags) { bool done = false; diff --git a/src/shared/mount-util.h b/src/shared/mount-util.h index 8b07611ec8..29b9ed02f7 100644 --- a/src/shared/mount-util.h +++ b/src/shared/mount-util.h @@ -55,6 +55,7 @@ static inline int bind_remount_recursive(const char *prefix, unsigned long new_f int bind_remount_one_with_mountinfo(const char *path, unsigned long new_flags, unsigned long flags_mask, FILE *proc_self_mountinfo); int mount_move_root(const char *path); +int mount_pivot_root(const char *path); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(FILE*, endmntent, NULL); #define _cleanup_endmntent_ _cleanup_(endmntentp)