Skip to content

Commit

Permalink
internals: Add new bootc internals fsck
Browse files Browse the repository at this point in the history
Split this out of the fsverity PR.

We obviously want a `fsck` command. This starts by doing
just two checks:

- A verification of `etc/resolv.conf`; this tests
  98995f6
- Just run `ostree fsck`

But obvious things we should be adding here are:

- Verifying kargs
- Verifying LBIs

etc.

Signed-off-by: Colin Walters <[email protected]>
  • Loading branch information
cgwalters committed Mar 7, 2025
1 parent 190085d commit 3e4050d
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
# Experimental features

- [bootc image](experimental-bootc-image.md)
- [fsck](experimental-fsck.md)
- [--progress-fd](experimental-progress-fd.md)

# More information
Expand Down
9 changes: 9 additions & 0 deletions docs/src/experimental-fsck.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# bootc internals fsck

Experimental features are subject to change or removal. Please
do provide feedback on them.

## Using `bootc internals fsck`

This command expects a booted system, and performs consistency checks
in a read-only fashion.
7 changes: 7 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ pub(crate) enum InternalsOpts {
},
#[clap(subcommand)]
Fsverity(FsverityOpts),
/// Perform consistency checking.
Fsck,
/// Perform cleanup actions
Cleanup,
/// Proxy frontend for the `ostree-ext` CLI.
Expand Down Expand Up @@ -1173,6 +1175,11 @@ async fn run_from_opt(opt: Opt) -> Result<()> {
Ok(())
}
},
InternalsOpts::Fsck => {
let sysroot = &get_storage().await?;
crate::fsck::fsck(&sysroot, std::io::stdout().lock())?;
Ok(())
}
InternalsOpts::FixupEtcFstab => crate::deploy::fixup_etc_fstab(&root),
InternalsOpts::PrintJsonSchema { of } => {
let schema = match of {
Expand Down
128 changes: 128 additions & 0 deletions lib/src/fsck.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//! # Write deployments merging image with configmap
//!
//! Create a merged filesystem tree with the image and mounted configmaps.
// Unfortunately needed here to work with linkme
#![allow(unsafe_code)]

use std::process::Command;

use cap_std_ext::cap_std;
use cap_std_ext::cap_std::fs::{Dir, MetadataExt as _};
use cap_std_ext::dirext::CapStdExtDirExt;
use linkme::distributed_slice;

use crate::store::Storage;

/// A lint check has failed.
#[derive(thiserror::Error, Debug)]
struct FsckError(String);

/// The outer error is for unexpected fatal runtime problems; the
/// inner error is for the check failing in an expected way.
type FsckResult = anyhow::Result<std::result::Result<(), FsckError>>;

/// Everything is OK - we didn't encounter a runtime error, and
/// the targeted check passed.
fn fsck_ok() -> FsckResult {
Ok(Ok(()))
}

/// We successfully found a failure.
fn fsck_err(msg: impl AsRef<str>) -> FsckResult {
Ok(Err(FsckError::new(msg)))
}

impl std::fmt::Display for FsckError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}

impl FsckError {
fn new(msg: impl AsRef<str>) -> Self {
Self(msg.as_ref().to_owned())
}
}

#[derive(Debug)]
struct FsckCheck {
name: &'static str,
ordering: u16,
f: FsckFn,
}

type FsckFn = fn(&Storage) -> FsckResult;
#[distributed_slice]
pub(crate) static FSCK_CHECKS: [FsckCheck];

impl FsckCheck {
pub(crate) const fn new(name: &'static str, ordering: u16, f: FsckFn) -> Self {
FsckCheck { name, ordering, f }
}
}

#[distributed_slice(FSCK_CHECKS)]
static CHECK_RESOLVCONF: FsckCheck = FsckCheck::new("etc-resolvconf", 5, check_resolvconf);
/// See https://github.com/containers/bootc/pull/1096 and https://github.com/containers/bootc/pull/1167
/// Basically verify that if /usr/etc/resolv.conf exists, it is not a zero-sized file that was
/// probably injected by buildah and that bootc should have removed.
///
/// Note that this fsck check can fail for systems upgraded from old bootc right now, as
/// we need the *new* bootc to fix it.
///
/// But at the current time fsck is an experimental feature that we should only be running
/// in our CI.
fn check_resolvconf(storage: &Storage) -> FsckResult {
// For now we only check the booted deployment.
if storage.booted_deployment().is_none() {
return fsck_ok();
}
// Read usr/etc/resolv.conf directly.
let usr = Dir::open_ambient_dir("/usr", cap_std::ambient_authority())?;
let Some(meta) = usr.symlink_metadata_optional("etc/resolv.conf")? else {
return fsck_ok();
};
if meta.is_file() && meta.size() == 0 {
return fsck_err("Found usr/etc/resolv.conf as zero-sized file");
}
fsck_ok()
}

pub(crate) fn fsck(storage: &Storage, mut output: impl std::io::Write) -> anyhow::Result<()> {
let mut checks = FSCK_CHECKS.static_slice().iter().collect::<Vec<_>>();
checks.sort_by(|a, b| a.ordering.cmp(&b.ordering));

let mut errors = false;
for check in checks.iter() {
let name = check.name;
// SAFETY: Shouldn't fail to join a thread
match (check.f)(&storage) {
Ok(Ok(())) => {}
Ok(Err(e)) => {
errors = true;
writeln!(output, "Failed check \"{name}\": {e}")?;
}
Err(e) => {
errors = true;
writeln!(output, "Unexpected runtime error in check \"{name}\": {e}")?;
}
}
println!("ok: {name}");
}
if errors {
anyhow::bail!("Encountered errors")
}

// Run an `ostree fsck` (yes, ostree exposes enough APIs
// that we could reimplement this in Rust, but eh)
let st = Command::new("ostree")
.arg("fsck")
.stdin(std::process::Stdio::inherit())
.status()?;
if !st.success() {
anyhow::bail!("ostree fsck failed");
}

Ok(())
}
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
mod boundimage;
pub mod cli;
pub(crate) mod deploy;
pub(crate) mod fsck;
pub(crate) mod generator;
mod glyph;
mod image;
Expand Down
9 changes: 9 additions & 0 deletions tests/booted/readonly/015-test-fsck.nu
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use std assert
use tap.nu

tap begin "Run fsck"

# That's it, just ensure we've run a fsck on our basic install.
bootc internals fsck

tap ok

0 comments on commit 3e4050d

Please sign in to comment.