bootc_lib/bootc_composefs/
soft_reboot.rs

1use crate::{
2    bootc_composefs::{
3        service::start_finalize_stated_svc, status::composefs_deployment_status_from,
4    },
5    composefs_consts::COMPOSEFS_CMDLINE,
6    store::{BootedComposefs, Storage},
7};
8use anyhow::{Context, Result};
9use bootc_initramfs_setup::setup_root;
10use bootc_kernel_cmdline::utf8::Cmdline;
11use bootc_mount::{bind_mount_from_pidns, PID1};
12use camino::Utf8Path;
13use fn_error_context::context;
14use ostree_ext::systemd_has_soft_reboot;
15use std::{fs::create_dir_all, os::unix::process::CommandExt, path::PathBuf, process::Command};
16
17const NEXTROOT: &str = "/run/nextroot";
18
19/// Checks if the provided deployment is soft reboot capable, and soft reboots the system if
20/// argument `reboot` is true
21#[context("Soft rebooting")]
22pub(crate) async fn prepare_soft_reboot_composefs(
23    storage: &Storage,
24    booted_cfs: &BootedComposefs,
25    deployment_id: &String,
26    reboot: bool,
27) -> Result<()> {
28    if !systemd_has_soft_reboot() {
29        anyhow::bail!("System does not support soft reboots")
30    }
31
32    if *deployment_id == *booted_cfs.cmdline.digest {
33        anyhow::bail!("Cannot soft-reboot to currently booted deployment");
34    }
35
36    // We definitely need to re-query the state as some deployment might've been staged
37    let host = composefs_deployment_status_from(storage, booted_cfs.cmdline).await?;
38
39    let all_deployments = host.all_composefs_deployments()?;
40
41    let requred_deployment = all_deployments
42        .iter()
43        .find(|entry| entry.deployment.verity == *deployment_id)
44        .ok_or_else(|| anyhow::anyhow!("Deployment '{deployment_id}' not found"))?;
45
46    if !requred_deployment.soft_reboot_capable {
47        anyhow::bail!("Cannot soft-reboot to deployment with a different kernel state");
48    }
49
50    start_finalize_stated_svc()?;
51
52    // escape to global mnt namespace
53    let run = Utf8Path::new("/run");
54    bind_mount_from_pidns(PID1, &run, &run, false).context("Bind mounting /run")?;
55
56    create_dir_all(NEXTROOT).context("Creating nextroot")?;
57
58    let cmdline = Cmdline::from(format!("{COMPOSEFS_CMDLINE}={deployment_id}"));
59
60    let args = bootc_initramfs_setup::Args {
61        cmd: vec![],
62        sysroot: PathBuf::from("/sysroot"),
63        config: Default::default(),
64        root_fs: None,
65        cmdline: Some(cmdline),
66        target: Some(NEXTROOT.into()),
67    };
68
69    setup_root(args)?;
70
71    if reboot {
72        // Replacing the current process should be fine as we restart userspace anyway
73        let err = Command::new("systemctl").arg("soft-reboot").exec();
74        return Err(anyhow::Error::from(err).context("Failed to exec 'systemctl soft-reboot'"));
75    }
76
77    Ok(())
78}