bootc_lib/bootc_composefs/
soft_reboot.rs1use crate::{
2 bootc_composefs::{
3 service::start_finalize_stated_svc,
4 status::{build_composefs_karg, get_composefs_status},
5 },
6 cli::SoftRebootMode,
7 store::{BootedComposefs, Storage},
8};
9use anyhow::{Context, Result};
10use bootc_initramfs_setup::setup_root;
11use bootc_mount::{PID1, bind_mount_from_pidns};
12use camino::Utf8Path;
13use cap_std_ext::cap_std::ambient_authority;
14use cap_std_ext::cap_std::fs::Dir;
15use cap_std_ext::dirext::CapStdExtDirExt;
16use composefs_ctl::composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
17use fn_error_context::context;
18use linux_kernel_cmdline::utf8::Cmdline;
19use ostree_ext::systemd_has_soft_reboot;
20use rustix::mount::{UnmountFlags, unmount};
21use std::{fs::create_dir_all, os::unix::process::CommandExt, path::PathBuf, process::Command};
22
23const NEXTROOT: &str = "/run/nextroot";
24
25#[context("Resetting soft reboot state")]
26pub(crate) fn reset_soft_reboot() -> Result<()> {
27 let run = Utf8Path::new("/run");
33 bind_mount_from_pidns(PID1, &run, &run, true).context("Bind mounting /run")?;
34
35 let run_dir = Dir::open_ambient_dir("/run", ambient_authority()).context("Opening run")?;
36
37 let nextroot = run_dir
38 .open_dir_optional("nextroot")
39 .context("Opening nextroot")?;
40
41 let Some(nextroot) = nextroot else {
42 tracing::debug!("Nextroot does not exist");
43 println!("No deployment staged for soft rebooting");
44 return Ok(());
45 };
46
47 let nextroot_mounted = nextroot
48 .is_mountpoint(".")?
49 .ok_or_else(|| anyhow::anyhow!("Failed to get mount info"))?;
50
51 if !nextroot_mounted {
52 tracing::debug!("Nextroot is not a mountpoint");
53 println!("No deployment staged for soft rebooting");
54 return Ok(());
55 }
56
57 unmount(NEXTROOT, UnmountFlags::DETACH).context("Unmounting nextroot")?;
58
59 println!("Cleared soft reboot queued state");
60
61 Ok(())
62}
63
64#[context("Soft rebooting")]
67pub(crate) async fn prepare_soft_reboot_composefs(
68 storage: &Storage,
69 booted_cfs: &BootedComposefs,
70 deployment_id: Option<&str>,
71 soft_reboot_mode: SoftRebootMode,
72 reboot: bool,
73) -> Result<()> {
74 if !systemd_has_soft_reboot() {
75 anyhow::bail!("System does not support soft reboots")
76 }
77
78 let deployment_id = deployment_id.ok_or_else(|| anyhow::anyhow!("Expected deployment id"))?;
79
80 if *deployment_id == *booted_cfs.cmdline.digest {
81 anyhow::bail!("Cannot soft-reboot to currently booted deployment");
82 }
83
84 let host = get_composefs_status(storage, &booted_cfs).await?;
86
87 let all_deployments = host.all_composefs_deployments()?;
88
89 let required_deployment = all_deployments
90 .iter()
91 .find(|entry| entry.deployment.verity == *deployment_id)
92 .ok_or_else(|| anyhow::anyhow!("Deployment '{deployment_id}' not found"))?;
93
94 if !required_deployment.soft_reboot_capable {
95 match soft_reboot_mode {
96 SoftRebootMode::Required => {
97 anyhow::bail!("Cannot soft-reboot to deployment with a different kernel state")
98 }
99
100 SoftRebootMode::Auto => return Ok(()),
101 }
102 }
103
104 start_finalize_stated_svc()?;
105
106 let run = Utf8Path::new("/run");
108 bind_mount_from_pidns(PID1, &run, &run, false).context("Bind mounting /run")?;
109
110 create_dir_all(NEXTROOT).context("Creating nextroot")?;
111
112 let deployment_digest = Sha512HashValue::from_hex(deployment_id)
113 .with_context(|| format!("Parsing deployment id '{deployment_id}'"))?;
114 let cmdline = build_composefs_karg(
120 deployment_digest,
121 booted_cfs.repo.erofs_version(),
122 booted_cfs.cmdline.allow_missing_fsverity,
123 );
124
125 let args = bootc_initramfs_setup::Args {
126 cmd: vec![],
127 sysroot: PathBuf::from("/sysroot"),
128 config: Default::default(),
129 root_fs: None,
130 cmdline: Some(Cmdline::from(cmdline)),
131 target: Some(NEXTROOT.into()),
132 };
133
134 setup_root(args)?;
135
136 println!("Soft reboot setup complete");
137
138 if reboot {
139 let err = Command::new("systemctl").arg("soft-reboot").exec();
141 return Err(anyhow::Error::from(err).context("Failed to exec 'systemctl soft-reboot'"));
142 }
143
144 Ok(())
145}