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