Skip to content

Commit

Permalink
repair: New functionality to detect (future: fix) inodes
Browse files Browse the repository at this point in the history
Initial code to detect the situation resulting from
ostreedev/ostree@de6fddc
  • Loading branch information
cgwalters committed Jul 19, 2023
1 parent c2a292c commit d832217
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 1 deletion.
21 changes: 21 additions & 0 deletions lib/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,19 @@ pub(crate) enum TarOpts {
Export(ExportOpts),
}

/// Check for consistenty of deployments and container image merge commits
/// and attempt auto-repair. Not yet officially stable API.
#[derive(Debug, Parser)]
pub(crate) struct ProvisionalRepairOpts {
/// Path to the system root
#[clap(long)]
sysroot: Utf8PathBuf,

/// Do not perform any mutation
#[clap(long)]
dry_run: bool,
}

/// Options for container import/export.
#[derive(Debug, Subcommand)]
pub(crate) enum ContainerOpts {
Expand Down Expand Up @@ -410,6 +423,8 @@ pub(crate) enum Opt {
#[clap(hide(true))]
#[cfg(feature = "docgen")]
Man(ManOpts),
#[clap(hide = true)]
ProvisionalRepair(ProvisionalRepairOpts),
}

#[allow(clippy::from_over_into)]
Expand Down Expand Up @@ -978,5 +993,11 @@ where
Opt::InternalOnlyForTesting(ref opts) => testing(opts).await,
#[cfg(feature = "docgen")]
Opt::Man(manopts) => crate::docgen::generate_manpages(&manopts.directory),
Opt::ProvisionalRepair(ref opts) => {
let sysroot = &ostree::Sysroot::new(Some(&gio::File::for_path(&opts.sysroot)));
sysroot.load(gio::Cancellable::NONE)?;
let sysroot = &SysrootLock::new_from_sysroot(sysroot).await?;
crate::repair::repair(&sysroot, opts.dry_run)
}
}
}
2 changes: 1 addition & 1 deletion lib/src/container/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const IMAGE_PREFIX: &str = "ostree/container/image";
pub const BASE_IMAGE_PREFIX: &str = "ostree/container/baseimage";

/// The key injected into the merge commit for the manifest digest.
const META_MANIFEST_DIGEST: &str = "ostree.manifest-digest";
pub(crate) const META_MANIFEST_DIGEST: &str = "ostree.manifest-digest";
/// The key injected into the merge commit with the manifest serialized as JSON.
const META_MANIFEST: &str = "ostree.manifest";
/// The key injected into the merge commit with the image configuration serialized as JSON.
Expand Down
1 change: 1 addition & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub mod ima;
pub mod keyfileext;
pub(crate) mod logging;
pub mod refescape;
pub(crate) mod repair;
pub mod sysroot;
pub mod tar;
pub mod tokio_util;
Expand Down
138 changes: 138 additions & 0 deletions lib/src/repair.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//! System repair functionality

use std::collections::{BTreeMap, BTreeSet};

use anyhow::{anyhow, Context, Result};
use cap_std::fs::Dir;
use cap_tempfile::cap_std;
use fn_error_context::context;
use ostree::{gio, glib};
use std::os::unix::fs::MetadataExt;

use crate::sysroot::SysrootLock;

// Find the inode numbers for objects
fn gather_inodes(
prefix: &str,
dir: &Dir,
little_inodes: &mut BTreeMap<u32, String>,
big_inodes: &mut BTreeMap<u64, String>,
) -> Result<()> {
for child in dir.entries()? {
let child = child?;
let metadata = child.metadata()?;
if !(metadata.is_file() || metadata.is_symlink()) {
continue;
}
let name = child.file_name();
let name = name
.to_str()
.ok_or_else(|| anyhow::anyhow!("Invalid {name:?}"))?;
let object_rest = name
.split_once('.')
.ok_or_else(|| anyhow!("Invalid object {name}"))?
.0;
let checksum = format!("{prefix}{object_rest}");
let inode = metadata.ino();
if let Some(little) = u32::try_from(inode).ok() {
little_inodes.insert(little, checksum);
} else {
big_inodes.insert(inode, checksum);
}
}
Ok(())
}

#[context("Analyzing commit for derivation")]
fn commit_is_derived(commit: &glib::Variant) -> Result<bool> {
let commit_meta = &glib::VariantDict::new(Some(&commit.child_value(0)));
if commit_meta
.lookup::<String>(crate::container::store::META_MANIFEST_DIGEST)?
.is_some()
{
return Ok(true);
}
if commit_meta
.lookup::<bool>("rpmostree.clientlayer")?
.is_some()
{
return Ok(true);
}
Ok(false)
}

#[context("Repairing inodes")]
pub(crate) fn repair(sysroot: &SysrootLock, dry_run: bool) -> Result<()> {
let repo = &sysroot.repo();
let repo_dir = repo.dfd_as_dir()?;
let objects = repo_dir.open_dir("objects")?;

println!("Attempting analysis of ostree state for files that may be incorrectly linked");
println!("For more information, see https://github.com/ostreedev/ostree/pull/2874/commits/de6fddc6adee09a93901243dc7074090828a1912");
println!();
println!("Gathering inodes...");
let mut little_inodes = BTreeMap::new();
let mut big_inodes = BTreeMap::new();

for child in objects.entries()? {
let child = child?;
if !child.file_type()?.is_dir() {
continue;
}
let name = child.file_name();
if name.len() != 2 {
continue;
}
let name = name
.to_str()
.ok_or_else(|| anyhow::anyhow!("Invalid {name:?}"))?;
let objdir = child.open_dir()?;
gather_inodes(name, &objdir, &mut little_inodes, &mut big_inodes)
.with_context(|| format!("Processing {name:?}"))?;
}

let mut colliding_inodes = BTreeMap::new();
for (big_inum, big_inum_checksum) in big_inodes {
let truncated = big_inum as u32;
if let Some(small_inum_object) = little_inodes.get(&truncated) {
println!(
r#"collision:
inode (>32 bit): {big_inum}
object: {big_inum_checksum}
inode (truncated): {truncated}
object: {small_inum_object}
"#
);
colliding_inodes.insert(big_inum, big_inum_checksum);
}
}

if !colliding_inodes.is_empty() {
let l = colliding_inodes.len();
eprintln!("warning: Found {l} potentially colliding objects");
} else {
println!("No colliding objects found.");
return Ok(());
}

println!("Analyzing deployments...");

let mut potentially_corrupted_commits = BTreeSet::new();
for (_refname, digest) in repo.list_refs(None, gio::Cancellable::NONE)? {
let commit = repo.load_commit(&digest)?.0;
if commit_is_derived(&commit)? {
eprintln!("Found potentially corrupted derived commit: {commit}");
potentially_corrupted_commits.insert(digest);
}
}

if potentially_corrupted_commits.is_empty() {
println!("No derived commits found.");
}

if !dry_run {
anyhow::bail!("Repair mode not implemented yet");
}

Ok(())
}

0 comments on commit d832217

Please sign in to comment.