Skip to content

Commit

Permalink
lints: Add var-tmpfiles
Browse files Browse the repository at this point in the history
This checks for content in `/var` that is missing systemd tmpfiles.d
entries.

Signed-off-by: Colin Walters <walters@verbum.org>
  • Loading branch information
cgwalters committed Feb 12, 2025
1 parent e3898d6 commit 2f8bb31
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ anstyle = "1.0.6"
anyhow = { workspace = true }
bootc-utils = { path = "../utils" }
bootc-blockdev = { path = "../blockdev" }
bootc-tmpfiles = { path = "../tmpfiles" }
camino = { workspace = true, features = ["serde1"] }
ostree-ext = { path = "../ostree-ext", features = ["bootc"] }
chrono = { workspace = true, features = ["serde"] }
Expand Down
70 changes: 64 additions & 6 deletions lib/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use std::collections::BTreeSet;
use std::env::consts::ARCH;
use std::fmt::Write as WriteFmt;
use std::os::unix::ffi::OsStrExt;

use anyhow::Result;
Expand Down Expand Up @@ -467,6 +468,53 @@ fn check_varlog(root: &Dir) -> LintResult {
lint_err(format!("Found non-empty logfile: {first}{others}"))
}

#[distributed_slice(LINTS)]
static LINT_VAR_TMPFILES: Lint = Lint {
name: "var-tmpfiles",
ty: LintType::Warning,
description: indoc! { r#"
Check for content in /var that does not have corresponding systemd tmpfiles.d entries.
This can cause a problem across upgrades because content in /var from the container
image will only be applied on the initial provisioning.
Instead, it's recommended to have /var effectively empty in the container image,
and use systemd tmpfiles.d to generate empty directories and compatibility symbolic links
as part of each boot.
"#},
f: check_var_tmpfiles,
root_type: Some(RootType::Running),
};
fn check_var_tmpfiles(_root: &Dir) -> LintResult {
let r = bootc_tmpfiles::find_missing_tmpfiles_current_root()?;
if r.tmpfiles.is_empty() && r.unsupported.is_empty() {
return lint_ok();
}
let mut msg = String::new();
if let Some((samples, rest)) =
bootc_utils::iterator_split_nonempty_rest_count(r.tmpfiles.iter(), 5)
{
msg.push_str("Found content in /var missing systemd tmpfiles.d entries:\n");
for elt in samples {
writeln!(msg, " {elt}")?;
}
if rest > 0 {
writeln!(msg, " ...and {} more", rest)?;
}
}
if let Some((samples, rest)) =
bootc_utils::iterator_split_nonempty_rest_count(r.unsupported.iter(), 5)
{
msg.push_str("Found non-directory/non-symlink files in /var:\n");
for elt in samples {
writeln!(msg, " {elt:?}")?;
}
if rest > 0 {
writeln!(msg, " ...and {} more", rest)?;
}
}
lint_err(msg)
}

#[distributed_slice(LINTS)]
static LINT_NONEMPTY_BOOT: Lint = Lint::new_warning(
"nonempty-boot",
Expand Down Expand Up @@ -498,8 +546,17 @@ fn check_boot(root: &Dir) -> LintResult {

#[cfg(test)]
mod tests {
use std::sync::LazyLock;

use super::*;

static ALTROOT_LINTS: LazyLock<usize> = LazyLock::new(|| {
LINTS
.iter()
.filter(|lint| lint.root_type != Some(RootType::Running))
.count()
});

fn fixture() -> Result<cap_std_ext::cap_tempfile::TempDir> {
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;
Ok(tempdir)
Expand Down Expand Up @@ -557,26 +614,27 @@ mod tests {
let mut out = Vec::new();
let root_type = RootType::Alternative;
let r = lint_inner(root, root_type, [], &mut out).unwrap();
assert_eq!(r.passed, LINTS.len());
let running_only_lints = LINTS.len().checked_sub(*ALTROOT_LINTS).unwrap();
assert_eq!(r.passed, *ALTROOT_LINTS);
assert_eq!(r.fatal, 0);
assert_eq!(r.skipped, 0);
assert_eq!(r.skipped, running_only_lints);
assert_eq!(r.warnings, 0);

let r = lint_inner(root, root_type, ["var-log"], &mut out).unwrap();
// Trigger a failure in var-log
root.create_dir_all("var/log/dnf")?;
root.write("var/log/dnf/dnf.log", b"dummy dnf log")?;
assert_eq!(r.passed, LINTS.len().checked_sub(1).unwrap());
assert_eq!(r.passed, ALTROOT_LINTS.checked_sub(1).unwrap());
assert_eq!(r.fatal, 0);
assert_eq!(r.skipped, 1);
assert_eq!(r.skipped, running_only_lints + 1);
assert_eq!(r.warnings, 0);

// But verify that not skipping it results in a warning
let mut out = Vec::new();
let r = lint_inner(root, root_type, [], &mut out).unwrap();
assert_eq!(r.passed, LINTS.len().checked_sub(1).unwrap());
assert_eq!(r.passed, ALTROOT_LINTS.checked_sub(1).unwrap());
assert_eq!(r.fatal, 0);
assert_eq!(r.skipped, 0);
assert_eq!(r.skipped, running_only_lints);
assert_eq!(r.warnings, 1);
Ok(())
}
Expand Down

0 comments on commit 2f8bb31

Please sign in to comment.