Skip to content

Commit 6450c47

Browse files
evan-goodecgwalters
authored andcommitted
status: Show /usr overlay status
Resolves #474 For #1044 Signed-off-by: Evan Goode <mail@evangoo.de>
1 parent 864b7f0 commit 6450c47

4 files changed

Lines changed: 167 additions & 15 deletions

File tree

crates/lib/src/bootc_composefs/state.rs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ use fn_error_context::context;
1818

1919
use ostree_ext::container::deploy::ORIGIN_CONTAINER;
2020
use rustix::{
21-
fs::{Mode, OFlags, open},
21+
fd::AsFd,
22+
fs::{Mode, OFlags, StatVfsMountFlags, open},
2223
path::Arg,
2324
};
2425

@@ -37,6 +38,7 @@ use crate::{
3738
},
3839
parsers::bls_config::BLSConfig,
3940
spec::ImageReference,
41+
spec::{FilesystemOverlay, FilesystemOverlayAccessMode, FilesystemOverlayPersistence},
4042
utils::path_relative_to,
4143
};
4244

@@ -328,19 +330,14 @@ pub(crate) async fn write_composefs_state(
328330
}
329331

330332
pub(crate) fn composefs_usr_overlay() -> Result<()> {
331-
let usr = Dir::open_ambient_dir("/usr", ambient_authority()).context("Opening /usr")?;
332-
let is_usr_mounted = usr
333-
.is_mountpoint(".")
334-
.context("Failed to get mount details for /usr")?;
335-
336-
let is_usr_mounted =
337-
is_usr_mounted.ok_or_else(|| anyhow::anyhow!("Failed to get mountinfo"))?;
338-
339-
if is_usr_mounted {
340-
println!("A writeable overlayfs is already mounted on /usr");
333+
let status = get_composefs_usr_overlay_status()?;
334+
if status.is_some() {
335+
println!("An overlayfs is already mounted on /usr");
341336
return Ok(());
342337
}
343338

339+
let usr = Dir::open_ambient_dir("/usr", ambient_authority()).context("Opening /usr")?;
340+
344341
// Get the mode from the underlying /usr directory
345342
let usr_metadata = usr.metadata(".").context("Getting /usr metadata")?;
346343
let usr_mode = Mode::from_raw_mode(usr_metadata.permissions().mode());
@@ -352,3 +349,28 @@ pub(crate) fn composefs_usr_overlay() -> Result<()> {
352349

353350
Ok(())
354351
}
352+
353+
pub(crate) fn get_composefs_usr_overlay_status() -> Result<Option<FilesystemOverlay>> {
354+
let usr = Dir::open_ambient_dir("/usr", ambient_authority()).context("Opening /usr")?;
355+
let is_usr_mounted = usr
356+
.is_mountpoint(".")
357+
.context("Failed to get mount details for /usr")?
358+
.ok_or_else(|| anyhow::anyhow!("Failed to get mountinfo"))?;
359+
360+
if is_usr_mounted {
361+
let st =
362+
rustix::fs::fstatvfs(usr.as_fd()).context("Failed to get filesystem info for /usr")?;
363+
let permissions = if st.f_flag.contains(StatVfsMountFlags::RDONLY) {
364+
FilesystemOverlayAccessMode::ReadOnly
365+
} else {
366+
FilesystemOverlayAccessMode::ReadWrite
367+
};
368+
// For the composefs backend, assume the /usr overlay is always transient.
369+
Ok(Some(FilesystemOverlay {
370+
access_mode: permissions,
371+
persistence: FilesystemOverlayPersistence::Transient,
372+
}))
373+
} else {
374+
Ok(None)
375+
}
376+
}

crates/lib/src/bootc_composefs/status.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
boot::BootType,
1212
repo::get_imgref,
1313
selinux::are_selinux_policies_compatible,
14+
state::get_composefs_usr_overlay_status,
1415
utils::{compute_store_boot_digest_for_uki, get_uki_cmdline},
1516
},
1617
composefs_consts::{
@@ -806,6 +807,8 @@ pub(crate) async fn composefs_deployment_status_from(
806807
host.spec.boot_order = BootOrder::Rollback
807808
};
808809

810+
host.status.usr_overlay = get_composefs_usr_overlay_status().ok().flatten();
811+
809812
set_soft_reboot_capability(storage, &mut host, sorted_bls_config, cmdline)?;
810813

811814
Ok(host)

crates/lib/src/spec.rs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use anyhow::Result;
88
use ostree_ext::container::Transport;
99
use ostree_ext::oci_spec::distribution::Reference;
1010
use ostree_ext::oci_spec::image::Digest;
11-
use ostree_ext::{container::OstreeImageReference, oci_spec};
11+
use ostree_ext::{container::OstreeImageReference, oci_spec, ostree::DeploymentUnlockedState};
1212
use schemars::JsonSchema;
1313
use serde::{Deserialize, Serialize};
1414

@@ -274,6 +274,83 @@ pub enum HostType {
274274
BootcHost,
275275
}
276276

277+
/// Details of an overlay filesystem: read-only or read/write, persistent or transient.
278+
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
279+
#[serde(rename_all = "camelCase")]
280+
pub struct FilesystemOverlay {
281+
/// Whether the overlay is read-only or read/write
282+
pub access_mode: FilesystemOverlayAccessMode,
283+
/// Whether the overlay will persist across reboots
284+
pub persistence: FilesystemOverlayPersistence,
285+
}
286+
287+
/// The permissions mode of a /usr overlay
288+
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
289+
#[serde(rename_all = "camelCase")]
290+
pub enum FilesystemOverlayAccessMode {
291+
/// The overlay is mounted read-only
292+
ReadOnly,
293+
/// The overlay is mounted read/write
294+
ReadWrite,
295+
}
296+
297+
impl Display for FilesystemOverlayAccessMode {
298+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
299+
match self {
300+
FilesystemOverlayAccessMode::ReadOnly => write!(f, "read-only"),
301+
FilesystemOverlayAccessMode::ReadWrite => write!(f, "read/write"),
302+
}
303+
}
304+
}
305+
306+
/// The persistence mode of a /usr overlay
307+
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema)]
308+
#[serde(rename_all = "camelCase")]
309+
pub enum FilesystemOverlayPersistence {
310+
/// Changes are temporary and will be lost on reboot
311+
Transient,
312+
/// Changes persist across reboots
313+
Persistent,
314+
}
315+
316+
impl Display for FilesystemOverlayPersistence {
317+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
318+
match self {
319+
FilesystemOverlayPersistence::Transient => write!(f, "transient"),
320+
FilesystemOverlayPersistence::Persistent => write!(f, "persistent"),
321+
}
322+
}
323+
}
324+
325+
pub(crate) fn deployment_unlocked_state_to_usr_overlay(
326+
state: DeploymentUnlockedState,
327+
) -> Option<FilesystemOverlay> {
328+
use FilesystemOverlayAccessMode::*;
329+
use FilesystemOverlayPersistence::*;
330+
match state {
331+
DeploymentUnlockedState::None => None,
332+
DeploymentUnlockedState::Development => Some(FilesystemOverlay {
333+
access_mode: ReadWrite,
334+
persistence: Transient,
335+
}),
336+
DeploymentUnlockedState::Hotfix => Some(FilesystemOverlay {
337+
access_mode: ReadWrite,
338+
persistence: Persistent,
339+
}),
340+
DeploymentUnlockedState::Transient => Some(FilesystemOverlay {
341+
access_mode: ReadOnly,
342+
persistence: Transient,
343+
}),
344+
_ => None,
345+
}
346+
}
347+
348+
impl Display for FilesystemOverlay {
349+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350+
write!(f, "{}, {}", self.persistence, self.access_mode)
351+
}
352+
}
353+
277354
/// The status of the host system
278355
#[derive(Debug, Clone, Serialize, Default, Deserialize, PartialEq, Eq, JsonSchema)]
279356
#[serde(rename_all = "camelCase")]
@@ -295,6 +372,9 @@ pub struct HostStatus {
295372
/// The detected type of system
296373
#[serde(rename = "type")]
297374
pub ty: Option<HostType>,
375+
376+
/// The state of the overlay mounted on /usr
377+
pub usr_overlay: Option<FilesystemOverlay>,
298378
}
299379

300380
pub(crate) struct DeploymentEntry<'a> {

crates/lib/src/status.rs

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,11 @@ pub(crate) fn get_status(
423423
None
424424
};
425425

426+
let usr_overlay = booted_deployment
427+
.as_ref()
428+
.map(|d| d.unlocked())
429+
.and_then(crate::spec::deployment_unlocked_state_to_usr_overlay);
430+
426431
let mut host = Host::new(spec);
427432
host.status = HostStatus {
428433
staged,
@@ -431,6 +436,7 @@ pub(crate) fn get_status(
431436
other_deployments,
432437
rollback_queued,
433438
ty,
439+
usr_overlay,
434440
};
435441
Ok((deployments, host))
436442
}
@@ -598,6 +604,7 @@ fn human_render_slot(
598604
slot: Option<Slot>,
599605
entry: &crate::spec::BootEntry,
600606
image: &crate::spec::ImageStatus,
607+
host_status: &crate::spec::HostStatus,
601608
verbose: bool,
602609
) -> Result<()> {
603610
let transport = &image.image.transport;
@@ -655,6 +662,9 @@ fn human_render_slot(
655662
writeln!(out, "yes")?;
656663
}
657664

665+
// Show /usr overlay status
666+
write_usr_overlay(&mut out, slot, host_status, prefix_len)?;
667+
658668
if verbose {
659669
// Show additional information in verbose mode similar to rpm-ostree
660670
if let Some(ostree) = &entry.ostree {
@@ -693,12 +703,31 @@ fn human_render_slot(
693703
Ok(())
694704
}
695705

706+
/// Helper function to render usr overlay status
707+
fn write_usr_overlay(
708+
mut out: impl Write,
709+
slot: Option<Slot>,
710+
host_status: &crate::spec::HostStatus,
711+
prefix_len: usize,
712+
) -> Result<()> {
713+
// Only booted deployments can have /usr overlay status
714+
if matches!(slot, Some(Slot::Booted)) {
715+
// Only print row if overlay is present
716+
if let Some(ref overlay) = host_status.usr_overlay {
717+
write_row_name(&mut out, "/usr overlay", prefix_len)?;
718+
writeln!(out, "{}", overlay)?;
719+
}
720+
}
721+
Ok(())
722+
}
723+
696724
/// Output a rendering of a non-container boot entry.
697725
fn human_render_slot_ostree(
698726
mut out: impl Write,
699727
slot: Option<Slot>,
700728
entry: &crate::spec::BootEntry,
701729
ostree_commit: &str,
730+
host_status: &crate::spec::HostStatus,
702731
verbose: bool,
703732
) -> Result<()> {
704733
// TODO consider rendering more ostree stuff here like rpm-ostree status does
@@ -718,6 +747,9 @@ fn human_render_slot_ostree(
718747
writeln!(out, "yes")?;
719748
}
720749

750+
// Show /usr overlay status
751+
write_usr_overlay(&mut out, slot, host_status, prefix_len)?;
752+
721753
if verbose {
722754
// Show additional information in verbose mode similar to rpm-ostree
723755
if let Some(ostree) = &entry.ostree {
@@ -771,13 +803,21 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool)
771803
}
772804

773805
if let Some(image) = &host_status.image {
774-
human_render_slot(&mut out, Some(slot_name), host_status, image, verbose)?;
806+
human_render_slot(
807+
&mut out,
808+
Some(slot_name),
809+
host_status,
810+
image,
811+
&host.status,
812+
verbose,
813+
)?;
775814
} else if let Some(ostree) = host_status.ostree.as_ref() {
776815
human_render_slot_ostree(
777816
&mut out,
778817
Some(slot_name),
779818
host_status,
780819
&ostree.checksum,
820+
&host.status,
781821
verbose,
782822
)?;
783823
} else if let Some(composefs) = &host_status.composefs {
@@ -793,9 +833,16 @@ fn human_readable_output_booted(mut out: impl Write, host: &Host, verbose: bool)
793833
writeln!(out)?;
794834

795835
if let Some(image) = &entry.image {
796-
human_render_slot(&mut out, None, entry, image, verbose)?;
836+
human_render_slot(&mut out, None, entry, image, &host.status, verbose)?;
797837
} else if let Some(ostree) = entry.ostree.as_ref() {
798-
human_render_slot_ostree(&mut out, None, entry, &ostree.checksum, verbose)?;
838+
human_render_slot_ostree(
839+
&mut out,
840+
None,
841+
entry,
842+
&ostree.checksum,
843+
&host.status,
844+
verbose,
845+
)?;
799846
}
800847
}
801848
}

0 commit comments

Comments
 (0)