mirror of
https://github.com/ostreedev/ostree.git
synced 2026-02-05 09:44:55 +01:00
Merge pull request #3518 from champtar/remove-mount-cycle
Rework mounts to fix sysroot.mount umount
This commit is contained in:
@@ -38,7 +38,6 @@ endif
|
||||
if BUILDOPT_SYSTEMD
|
||||
systemdsystemunit_DATA = src/boot/ostree-prepare-root.service \
|
||||
src/boot/ostree-remount.service \
|
||||
src/boot/ostree-shutdown.service \
|
||||
src/boot/ostree-boot-complete.service \
|
||||
src/boot/ostree-finalize-staged.service \
|
||||
src/boot/ostree-finalize-staged-hold.service \
|
||||
@@ -70,7 +69,6 @@ EXTRA_DIST += src/boot/dracut/module-setup.sh \
|
||||
src/boot/ostree-boot-complete.service \
|
||||
src/boot/ostree-prepare-root.service \
|
||||
src/boot/ostree-remount.service \
|
||||
src/boot/ostree-shutdown.service \
|
||||
src/boot/ostree-finalize-staged.service \
|
||||
src/boot/ostree-finalize-staged-hold.service \
|
||||
src/boot/ostree-state-overlay@.service \
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# SPDX-License-Identifier: LGPL-2.0+
|
||||
|
||||
[Unit]
|
||||
Description=OSTree Shutdown
|
||||
Documentation=man:ostree(1)
|
||||
DefaultDependencies=no
|
||||
# Only enabled via generator, but for good measure
|
||||
ConditionKernelCommandLine=ostree
|
||||
# Run after core mounts
|
||||
RequiresMountsFor=/etc /sysroot
|
||||
# However, we want to only shut down after `/var` has been umounted.
|
||||
# Since this runs via ExecStop, this Before= is actually After= at shutdown
|
||||
Before=var.mount
|
||||
Conflicts=umount.target
|
||||
Before=umount.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStop=/usr/lib/ostree/ostree-remount --shutdown
|
||||
|
||||
# No [Install] section - we're only enabled via generator
|
||||
@@ -108,10 +108,6 @@ require_internal_units (const char *normal_dir, const char *early_dir, const cha
|
||||
"local-fs.target.requires/ostree-remount.service")
|
||||
< 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||
if (symlinkat (SYSTEM_DATA_UNIT_PATH "/ostree-shutdown.service", normal_dir_dfd,
|
||||
"local-fs.target.wants/ostree-shutdown.service")
|
||||
< 0)
|
||||
return glnx_throw_errno_prefix (error, "symlinkat");
|
||||
|
||||
if (!glnx_shutil_mkdir_p_at (normal_dir_dfd, "multi-user.target.wants", 0755, cancellable, error))
|
||||
return FALSE;
|
||||
|
||||
@@ -63,6 +63,22 @@ _ostree_prepare_soft_reboot (GError **error)
|
||||
if (!glnx_shutil_mkdir_p_at (AT_FDCWD, OTCORE_RUN_NEXTROOT, 0755, NULL, error))
|
||||
return FALSE;
|
||||
|
||||
/* Bind-mount /sysroot on itself.
|
||||
* The composefs mount at /run/nextboot is going to use /sysroot,
|
||||
* causing systemd to fail to umount sysroot.mount during soft-reboot.
|
||||
* Create a temporary bind-mount, and MNT_DETACH it when we are done
|
||||
*/
|
||||
if (mount (sysroot_path, sysroot_path, NULL, MS_BIND | MS_SILENT, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_BIND '%s'", sysroot_path);
|
||||
|
||||
/* Our curent working directory is in the old /sysroot,
|
||||
* ie we are under the bind mount, so run 'cd $PWD'
|
||||
* to move to the new /sysroot
|
||||
*/
|
||||
g_autofree char *cwd = g_get_current_dir ();
|
||||
if (chdir (cwd) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to '%s'", cwd);
|
||||
|
||||
// Tracks if we did successfully enable it at runtime
|
||||
bool using_composefs = false;
|
||||
if (!otcore_mount_rootfs (rootfs_config, &metadata_builder, sysroot_path, target_deployment,
|
||||
@@ -78,6 +94,14 @@ _ostree_prepare_soft_reboot (GError **error)
|
||||
if (!otcore_mount_etc (config, &metadata_builder, OTCORE_RUN_NEXTROOT, error))
|
||||
return FALSE;
|
||||
|
||||
// detach the temporary /sysroot bind-mount
|
||||
if (umount2 (sysroot_path, MNT_DETACH) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_DETACH '%s'", sysroot_path);
|
||||
|
||||
// run 'cd $PWD' again to go back to the old /sysroot
|
||||
if (chdir (cwd) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to '%s'", cwd);
|
||||
|
||||
// And set up /sysroot. Here since we hardcode composefs, we also hardcode
|
||||
// having a read-only /sysroot.
|
||||
g_variant_builder_add (&metadata_builder, "{sv}", OTCORE_RUN_BOOTED_KEY_SYSROOT_RO,
|
||||
|
||||
@@ -325,6 +325,25 @@ main (int argc, char *argv[])
|
||||
err (EXIT_FAILURE, "failed to bind mount (class:readonly) /usr");
|
||||
}
|
||||
|
||||
/* Prepare /sysroot.
|
||||
* The future / (currently at /sysroot.tmp) is an overlayfs or composefs that uses
|
||||
* the physical root (currently at /sysroot), and we want to mount the physical root
|
||||
* on top of the future / (at /sysroot.tmp/sysroot).
|
||||
* If we MS_MOVE /sysroot to /sysroot.tmp/sysroot, we end up with a mount cycle,
|
||||
* and systemd fails to unmount sysroot.mount.
|
||||
* To avoid the mount cycle, bind-mount the physical root and then detach it.
|
||||
*/
|
||||
if (mount (root_mountpoint, TMP_SYSROOT "/sysroot", NULL, MS_BIND | MS_SILENT, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_BIND '%s' to 'sysroot'", root_mountpoint);
|
||||
|
||||
if (umount2 (root_mountpoint, MNT_DETACH) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_DETACH '%s'", root_mountpoint);
|
||||
|
||||
/* Resolve deploy path again so we can use paths relative to the physical root bind-mount */
|
||||
g_autofree char *deploy_path2 = resolve_deploy_path (kernel_cmdline, TMP_SYSROOT "/sysroot");
|
||||
if (chdir (deploy_path2) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to deploy_path2");
|
||||
|
||||
/* Prepare /var.
|
||||
* When a read-only sysroot is configured, this adds a dedicated bind-mount (to itself)
|
||||
* so that the stateroot location stays writable. */
|
||||
@@ -377,20 +396,12 @@ main (int argc, char *argv[])
|
||||
errx (EXIT_FAILURE, "Writing %s: %s", OTCORE_RUN_BOOTED, error->message);
|
||||
}
|
||||
|
||||
if (chdir (TMP_SYSROOT) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to " TMP_SYSROOT);
|
||||
|
||||
/* Now we have our ready made-up up root at
|
||||
* /sysroot.tmp and the physical root at /sysroot (root_mountpoint).
|
||||
* We want to end up with our deploy root at /sysroot/ and the physical
|
||||
* root under /sysroot/sysroot as systemd will be responsible for
|
||||
* moving /sysroot to /.
|
||||
/* Now we have our ready made-up deploy root at /sysroot.tmp,
|
||||
* we just need to move it to /sysroot (root_mountpoint).
|
||||
* systemd will be responsible for moving /sysroot to /.
|
||||
*/
|
||||
if (mount (root_mountpoint, "sysroot", NULL, MS_MOVE | MS_SILENT, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_MOVE '%s' to 'sysroot'", root_mountpoint);
|
||||
|
||||
if (mount (".", root_mountpoint, NULL, MS_MOVE | MS_SILENT, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_MOVE %s to %s", ".", root_mountpoint);
|
||||
if (mount (TMP_SYSROOT, root_mountpoint, NULL, MS_MOVE | MS_SILENT, NULL) < 0)
|
||||
err (EXIT_FAILURE, "failed to MS_MOVE %s to %s", TMP_SYSROOT, root_mountpoint);
|
||||
|
||||
if (chdir (root_mountpoint) < 0)
|
||||
err (EXIT_FAILURE, "failed to chdir to %s", root_mountpoint);
|
||||
|
||||
@@ -42,12 +42,6 @@
|
||||
#include "ostree-mount-util.h"
|
||||
#include "otcore.h"
|
||||
|
||||
static gboolean opt_shutdown;
|
||||
|
||||
static GOptionEntry options[] = { { "shutdown", 'S', 0, G_OPTION_ARG_NONE, &opt_shutdown,
|
||||
"Perform shutdown unmounting", NULL },
|
||||
{ NULL } };
|
||||
|
||||
static void
|
||||
do_remount (const char *target, bool writable)
|
||||
{
|
||||
@@ -139,61 +133,10 @@ relabel_dir_for_upper (const char *upper_path, const char *real_path, gboolean i
|
||||
#endif
|
||||
}
|
||||
|
||||
// ostree-prepare-root sets things up so that /sysroot points to the "physical" (real) root in the
|
||||
// initramfs, and then with composefs `/` is an overlay+EROFS that holds references to content in
|
||||
// that physical filesystem.
|
||||
//
|
||||
// In a typical mutable system where the OS is in a mutable `/` (or `/usr), systemd explicitly
|
||||
// skips unmounting both `/` and `/usr`. It will remount them read-only though - and that's
|
||||
// the semantic we want to match here.
|
||||
static void
|
||||
do_shutdown (void)
|
||||
{
|
||||
const char *sysroot = "/sysroot";
|
||||
if (mount (sysroot, sysroot, NULL, MS_REMOUNT | MS_SILENT | MS_RDONLY, NULL) < 0)
|
||||
{
|
||||
// Hopefully at this point nothing has any write references, but if they
|
||||
// do we still want to continue.
|
||||
perror ("Remounting /sysroot read-only");
|
||||
}
|
||||
// And fully detach it from the mountns because otherwise systemd thinks
|
||||
// it can be unmounted, but it can't - it's required by `/` (and in a
|
||||
// composefs setup `/etc`) and possibly `/var`. Again, we only really
|
||||
// care that it got mounted read-only and hence outstanding data flushed.
|
||||
// A better fix in the future would be to teach systemd to honor `-.mount`
|
||||
// having a `Requires=sysroot.mount` meaning we can't unmount the latter.
|
||||
if (umount2 (sysroot, MNT_DETACH) < 0)
|
||||
err (EXIT_FAILURE, "umount(/sysroot)");
|
||||
|
||||
// And finally: /etc
|
||||
// NOTE! This one is intentionally last in that we want to try to make
|
||||
// this read-only, but if it fails, systemd-shutdown will have another
|
||||
// attempt after a process killing spree. If anything happens to be
|
||||
// holding a writable fd at this point, conceptually it would have
|
||||
// created race conditions vs ostree-finalize-staged.service, and so
|
||||
// having this service fail will be a signal that those things need
|
||||
// to be fixed.
|
||||
do_remount ("/etc", false);
|
||||
// Don't add anything else after this.
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
g_autoptr (GError) error = NULL;
|
||||
g_autoptr (GOptionContext) context = g_option_context_new ("");
|
||||
g_option_context_add_main_entries (context, options, NULL);
|
||||
if (!g_option_context_parse (context, &argc, &argv, &error))
|
||||
errx (EXIT_FAILURE, "Error parsing options: %s", error->message);
|
||||
|
||||
// Handle the shutdown option
|
||||
if (opt_shutdown)
|
||||
{
|
||||
do_shutdown ();
|
||||
return 0;
|
||||
}
|
||||
// Otherwise fall through to the default startup
|
||||
|
||||
g_autoptr (GVariant) ostree_run_metadata_v = NULL;
|
||||
{
|
||||
glnx_autofd int fd = open (OTCORE_RUN_BOOTED, O_RDONLY | O_CLOEXEC);
|
||||
|
||||
Reference in New Issue
Block a user