diff --git a/src/core/main.c b/src/core/main.c index 7094be8dba..913dc0cf7e 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1884,7 +1884,8 @@ static int do_reexecute( if (switch_root_dir) { r = switch_root(/* new_root= */ switch_root_dir, /* old_root_after= */ NULL, - /* flags= */ objective == MANAGER_SWITCH_ROOT ? SWITCH_ROOT_DESTROY_OLD_ROOT : 0); + /* flags= */ (objective == MANAGER_SWITCH_ROOT ? SWITCH_ROOT_DESTROY_OLD_ROOT : 0) | + (objective == MANAGER_SOFT_REBOOT ? SWITCH_ROOT_SKIP_RECURSIVE_RUN : 0)); if (r < 0) log_error_errno(r, "Failed to switch root, trying to continue: %m"); } diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 070d5bd908..37395e02ad 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -30,17 +30,23 @@ int switch_root(const char *new_root, const char *old_root_after, /* path below the new root, where to place the old root after the transition; may be NULL to unmount it */ SwitchRootFlags flags) { + /* Stuff mounted below /run we don't save on soft reboot, as it might have lost its relevance, i.e. + * credentials, removable media and such, we rather want that the new boot mounts this fresh. + * But on the switch from initrd we do use MS_REC, as it is expected that mounts set up in /run + * are maintained. */ + unsigned long run_mount_flags = MS_BIND|(!FLAGS_SET(flags, SWITCH_ROOT_SKIP_RECURSIVE_RUN) ? MS_REC : 0); struct { const char *path; unsigned long mount_flags; + bool skip_if_run_is_rec; /* For child mounts of /run, if it's moved recursively no need to handle */ } transfer_table[] = { - { "/dev", MS_BIND|MS_REC }, /* Recursive, because we want to save the original /dev/shm + /dev/pts and similar */ - { "/sys", MS_BIND|MS_REC }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ - { "/proc", MS_BIND|MS_REC }, /* Similar */ - { "/run", MS_BIND }, /* Stuff mounted below this we don't save, as it might have lost its relevance, i.e. credentials, removable media and such, we rather want that the new boot mounts this fresh */ - { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Credentials passed into the system should survive */ - { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND }, /* Similar */ - { "/run/host", MS_BIND|MS_REC }, /* Host supplied hierarchy should also survive */ + { "/dev", MS_BIND|MS_REC, false }, /* Recursive, because we want to save the original /dev/shm + /dev/pts and similar */ + { "/sys", MS_BIND|MS_REC, false }, /* Similar, we want to retain various API VFS, or the cgroupv1 /sys/fs/cgroup/ tree */ + { "/proc", MS_BIND|MS_REC, false }, /* Similar */ + { "/run", run_mount_flags, false }, /* Recursive except on soft reboot, see above */ + { SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, true }, /* Credentials passed into the system should survive */ + { ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, MS_BIND, true }, /* Similar */ + { "/run/host", MS_BIND|MS_REC, true }, /* Host supplied hierarchy should also survive */ }; _cleanup_close_ int old_root_fd = -EBADF, new_root_fd = -EBADF; @@ -112,6 +118,9 @@ int switch_root(const char *new_root, FOREACH_ARRAY(transfer, transfer_table, ELEMENTSOF(transfer_table)) { _cleanup_free_ char *chased = NULL; + if (transfer->skip_if_run_is_rec && !FLAGS_SET(flags, SWITCH_ROOT_SKIP_RECURSIVE_RUN)) + continue; + if (access(transfer->path, F_OK) < 0) { log_debug_errno(errno, "Path '%s' to move to target root directory, not found, ignoring: %m", transfer->path); continue; diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h index 79e47c67f4..78d62f28e0 100644 --- a/src/shared/switch-root.h +++ b/src/shared/switch-root.h @@ -4,8 +4,9 @@ #include typedef enum SwitchRootFlags { - SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */ - SWITCH_ROOT_DONT_SYNC = 1 << 1, /* don't call sync() immediately before switching root */ + SWITCH_ROOT_DESTROY_OLD_ROOT = 1 << 0, /* rm -rf old root when switching – under the condition that it is backed by non-persistent tmpfs/ramfs/… */ + SWITCH_ROOT_DONT_SYNC = 1 << 1, /* don't call sync() immediately before switching root */ + SWITCH_ROOT_SKIP_RECURSIVE_RUN = 1 << 2, /* move /run without MS_REC */ } SwitchRootFlags; int switch_root(const char *new_root, const char *old_root_after, SwitchRootFlags flags); diff --git a/test/TEST-01-BASIC/test.sh b/test/TEST-01-BASIC/test.sh index d0e714ac30..c729e95f1d 100755 --- a/test/TEST-01-BASIC/test.sh +++ b/test/TEST-01-BASIC/test.sh @@ -23,4 +23,31 @@ test_append_files() { cp -v "$TEST_UNITS_DIR"/{testsuite-01,end}.service "$TEST_UNITS_DIR/testsuite.target" "$dst" } +# Setup a one shot service in initrd that creates a dummy bind mount under /run +# to check if the mount persists though the initrd transition. The "check" part +# is in the respective testsuite-01.sh script. +# +# See: https://github.com/systemd/systemd/issues/28452 +run_qemu_hook() { + local extra="$WORKDIR/initrd.extra" + + mkdir -m 755 "$extra" + mkdir -m 755 "$extra/etc" "$extra/etc/systemd" "$extra/etc/systemd/system" "$extra/etc/systemd/system/initrd.target.wants" + + cat >"$extra/etc/systemd/system/initrd-run-mount.service" < "$extra.cpio") + + INITRD_EXTRA="$extra.cpio" +} + do_test "$@" diff --git a/test/units/testsuite-01.sh b/test/units/testsuite-01.sh index 780f37ee12..a1193ce6fb 100755 --- a/test/units/testsuite-01.sh +++ b/test/units/testsuite-01.sh @@ -19,6 +19,11 @@ if systemd-detect-virt -q --container; then test ! -e /run/systemd/container cp -afv /tmp/container /run/systemd/container else + # We should've created a mount under /run in initrd (see the other half of the test) + # that should've survived the transition from initrd to the real system + test -d /run/initrd-mount-target + mountpoint /run/initrd-mount-target + # We bring the loopback netdev up only during a full setup, so it should # not get brought back up during reexec if we disable it beforehand [[ "$(ip -o link show lo)" =~ LOOPBACK,UP ]]