Skip to content

Commit 7a50319

Browse files
jmarrerocgwalters
authored andcommitted
cli: add support for soft-reboots
Co-authored-by: Colin Walters <[email protected]> Signed-off-by: Joseph Marrero Corchado <[email protected]>
1 parent 9b69dc1 commit 7a50319

File tree

6 files changed

+108
-7
lines changed

6 files changed

+108
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ bootc-mount = { path = "../mount" }
2323
bootc-tmpfiles = { path = "../tmpfiles" }
2424
bootc-sysusers = { path = "../sysusers" }
2525
camino = { workspace = true, features = ["serde1"] }
26+
cfg-if = "1.0"
2627
ostree-ext = { path = "../ostree-ext", features = ["bootc"] }
2728
chrono = { workspace = true, features = ["serde"] }
2829
clap = { workspace = true, features = ["derive","cargo"] }

crates/lib/src/cli.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use anyhow::{ensure, Context, Result};
1111
use camino::Utf8PathBuf;
1212
use cap_std_ext::cap_std;
1313
use cap_std_ext::cap_std::fs::Dir;
14+
use cfg_if::cfg_if;
1415
use clap::Parser;
1516
use clap::ValueEnum;
1617
use fn_error_context::context;
@@ -544,7 +545,7 @@ pub(crate) enum Opt {
544545
Note on Rollbacks and the `/etc` Directory:
545546
546547
When you perform a rollback (e.g., with `bootc rollback`), any
547-
changes made to files in the `/etc` directory wont carry over
548+
changes made to files in the `/etc` directory won't carry over
548549
to the rolled-back deployment. The `/etc` files will revert
549550
to their state from that previous deployment instead.
550551
@@ -723,6 +724,43 @@ pub(crate) fn require_root(is_container: bool) -> Result<()> {
723724
Ok(())
724725
}
725726

727+
/// Check if a deployment can perform a soft reboot
728+
#[cfg(feature = "ostree-2025-3")]
729+
fn can_perform_soft_reboot(deployment: Option<&crate::spec::BootEntry>) -> bool {
730+
deployment.map(|d| d.soft_reboot_capable).unwrap_or(false)
731+
}
732+
733+
/// Prepare and execute a soft reboot for the given deployment
734+
#[context("Preparing soft reboot")]
735+
#[cfg(feature = "ostree-2025-3")]
736+
fn prepare_soft_reboot(
737+
sysroot: &crate::store::Storage,
738+
deployment: &ostree::Deployment,
739+
) -> Result<()> {
740+
let cancellable = ostree::gio::Cancellable::NONE;
741+
sysroot
742+
.sysroot
743+
.deployment_set_soft_reboot(deployment, false, cancellable)
744+
.context("Failed to prepare soft-reboot")?;
745+
Ok(())
746+
}
747+
748+
/// Perform a soft reboot for a staged deployment
749+
#[context("Soft reboot staged deployment")]
750+
#[cfg(feature = "ostree-2025-3")]
751+
fn soft_reboot_staged(sysroot: &crate::store::Storage) -> Result<()> {
752+
println!("Staged deployment is soft-reboot capable, performing soft-reboot...");
753+
754+
let deployments_list = sysroot.deployments();
755+
let staged_deployment = deployments_list
756+
.iter()
757+
.find(|d| d.is_staged())
758+
.ok_or_else(|| anyhow::anyhow!("Failed to find staged deployment"))?;
759+
760+
prepare_soft_reboot(sysroot, staged_deployment)?;
761+
Ok(())
762+
}
763+
726764
/// A few process changes that need to be made for writing.
727765
/// IMPORTANT: This may end up re-executing the current process,
728766
/// so anything that happens before this should be idempotent.
@@ -843,6 +881,10 @@ async fn upgrade(opts: UpgradeOpts) -> Result<()> {
843881
println!("Staged update present, not changed.");
844882

845883
if opts.apply {
884+
#[cfg(feature = "ostree-2025-3")]
885+
if can_perform_soft_reboot(host.status.staged.as_ref()) {
886+
soft_reboot_staged(sysroot)?;
887+
}
846888
crate::reboot::reboot()?;
847889
}
848890
} else if booted_unchanged {
@@ -939,6 +981,16 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
939981
sysroot.update_mtime()?;
940982

941983
if opts.apply {
984+
#[cfg(feature = "ostree-2025-3")]
985+
{
986+
// Get updated status to check for soft-reboot capability
987+
let (_updated_deployments, updated_host) =
988+
crate::status::get_status(sysroot, Some(&booted_deployment))?;
989+
990+
if can_perform_soft_reboot(updated_host.status.staged.as_ref()) {
991+
soft_reboot_staged(sysroot)?;
992+
}
993+
}
942994
crate::reboot::reboot()?;
943995
}
944996

@@ -949,10 +1001,35 @@ async fn switch(opts: SwitchOpts) -> Result<()> {
9491001
#[context("Rollback")]
9501002
async fn rollback(opts: RollbackOpts) -> Result<()> {
9511003
let sysroot = &get_storage().await?;
952-
crate::deploy::rollback(sysroot).await?;
9531004

9541005
if opts.apply {
1006+
// Get status before rollback to check soft-reboot capability
1007+
let (_booted_deployment, _deployments, host) =
1008+
crate::status::get_status_require_booted(sysroot)?;
1009+
1010+
// Perform the rollback
1011+
crate::deploy::rollback(sysroot).await?;
1012+
1013+
cfg_if! {
1014+
if #[cfg(feature = "ostree-2025-3")] {
1015+
if can_perform_soft_reboot(host.status.rollback.as_ref()) {
1016+
println!("Rollback deployment is soft-reboot capable, performing soft-reboot...");
1017+
1018+
let deployments_list = sysroot.deployments();
1019+
let target_deployment = deployments_list
1020+
.first()
1021+
.ok_or_else(|| anyhow::anyhow!("No deployments found after rollback"))?;
1022+
1023+
prepare_soft_reboot(sysroot, target_deployment)?;
1024+
}
1025+
} else {
1026+
let _host = host;
1027+
}
1028+
}
1029+
9551030
crate::reboot::reboot()?;
1031+
} else {
1032+
crate::deploy::rollback(sysroot).await?;
9561033
}
9571034

9581035
Ok(())

crates/lib/src/spec.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ pub struct BootEntry {
176176
pub incompatible: bool,
177177
/// Whether this entry will be subject to garbage collection
178178
pub pinned: bool,
179+
/// This is true if (relative to the booted system) this is a possible target for a soft reboot
180+
#[serde(default)]
181+
pub soft_reboot_capable: bool,
179182
/// The container storage backend
180183
#[serde(default)]
181184
pub store: Option<Store>,
@@ -517,6 +520,7 @@ mod tests {
517520
image: None,
518521
cached_update: None,
519522
incompatible: false,
523+
soft_reboot_capable: false,
520524
pinned: false,
521525
store: None,
522526
ostree: None,

crates/lib/src/status.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,14 @@ fn boot_entry_from_deployment(
144144
(None, CachedImageStatus::default(), false)
145145
};
146146

147+
let soft_reboot_capable =
148+
ostree_ext::deployment_can_soft_reboot(&sysroot, deployment).unwrap_or_default();
149+
147150
let r = BootEntry {
148151
image,
149152
cached_update,
150153
incompatible,
154+
soft_reboot_capable,
151155
store,
152156
pinned: deployment.is_pinned(),
153157
ostree: Some(crate::spec::BootEntryOstree {
@@ -228,17 +232,17 @@ pub(crate) fn get_status(
228232
other,
229233
};
230234

231-
let staged = deployments
232-
.staged
235+
let booted = booted_deployment
233236
.as_ref()
234237
.map(|d| boot_entry_from_deployment(sysroot, d))
235238
.transpose()
236-
.context("Staged deployment")?;
237-
let booted = booted_deployment
239+
.context("Booted deployment")?;
240+
let staged = deployments
241+
.staged
238242
.as_ref()
239243
.map(|d| boot_entry_from_deployment(sysroot, d))
240244
.transpose()
241-
.context("Booted deployment")?;
245+
.context("Staged deployment")?;
242246
let rollback = deployments
243247
.rollback
244248
.as_ref()

crates/ostree-ext/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ pub mod fixture;
7979
#[cfg(feature = "internal-testing-api")]
8080
pub mod integrationtest;
8181

82+
#[cfg(feature = "ostree-2025-3")]
83+
/// Check if the system has the soft reboot target, which signals
84+
/// systemd support for soft reboots.
85+
pub fn systemd_has_soft_reboot() -> bool {
86+
const UNIT: &str = "/usr/lib/systemd/system/soft-reboot.target";
87+
use std::sync::OnceLock;
88+
static EXISTS: OnceLock<bool> = OnceLock::new();
89+
*EXISTS.get_or_init(|| std::path::Path::new(UNIT).exists())
90+
}
91+
8292
/// Dynamic detection wrapper for soft reboots, if the installed ostree is too old
8393
/// then we return `None`.
8494
pub fn deployment_can_soft_reboot(
@@ -87,6 +97,10 @@ pub fn deployment_can_soft_reboot(
8797
) -> Option<bool> {
8898
#[cfg(feature = "ostree-2025-3")]
8999
{
100+
// Even if ostree is new enough, it
101+
if !systemd_has_soft_reboot() {
102+
return None;
103+
}
90104
Some(sysroot.deployment_can_soft_reboot(deployment))
91105
}
92106
#[cfg(not(feature = "ostree-2025-3"))]

0 commit comments

Comments
 (0)