Skip to content

Commit ae6b39f

Browse files
committed
lints: Add var-tmpfiles
This checks for content in `/var` that is missing systemd tmpfiles.d entries. Signed-off-by: Colin Walters <[email protected]>
1 parent 3f48f54 commit ae6b39f

File tree

3 files changed

+66
-6
lines changed

3 files changed

+66
-6
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.

lib/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ anstyle = "1.0.6"
1818
anyhow = { workspace = true }
1919
bootc-utils = { path = "../utils" }
2020
bootc-blockdev = { path = "../blockdev" }
21+
bootc-tmpfiles = { path = "../tmpfiles" }
2122
camino = { workspace = true, features = ["serde1"] }
2223
ostree-ext = { path = "../ostree-ext", features = ["bootc"] }
2324
chrono = { workspace = true, features = ["serde"] }

lib/src/lints.rs

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use std::collections::BTreeSet;
99
use std::env::consts::ARCH;
10+
use std::fmt::Write as WriteFmt;
1011
use std::os::unix::ffi::OsStrExt;
1112

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

471+
#[distributed_slice(LINTS)]
472+
static LINT_VAR_TMPFILES: Lint = Lint {
473+
name: "var-tmpfiles",
474+
ty: LintType::Warning,
475+
description: indoc! { r#"
476+
Check for content in /var that does not have corresponding systemd tmpfiles.d entries.
477+
This can cause a problem across upgrades because content in /var from the container
478+
image will only be applied on the initial provisioning.
479+
480+
Instead, it's recommended to have /var effectively empty in the container image,
481+
and use systemd tmpfiles.d to generate empty directories and compatibility symbolic links
482+
as part of each boot.
483+
"#},
484+
f: check_var_tmpfiles,
485+
root_type: Some(RootType::Running),
486+
};
487+
fn check_var_tmpfiles(_root: &Dir) -> LintResult {
488+
let r = bootc_tmpfiles::find_missing_tmpfiles_current_root()?;
489+
if r.tmpfiles.is_empty() && r.unsupported.is_empty() {
490+
return lint_ok();
491+
}
492+
let mut msg = String::new();
493+
if let Some((samples, rest)) =
494+
bootc_utils::iterator_split_nonempty_rest_count(r.tmpfiles.iter(), 5)
495+
{
496+
msg.push_str("Found content in /var missing systemd tmpfiles.d entries:\n");
497+
for elt in samples {
498+
writeln!(msg, " {elt}")?;
499+
}
500+
if rest > 0 {
501+
writeln!(msg, " ...and {} more", rest)?;
502+
}
503+
}
504+
if let Some((samples, rest)) =
505+
bootc_utils::iterator_split_nonempty_rest_count(r.unsupported.iter(), 5)
506+
{
507+
msg.push_str("Found non-directory/non-symlink files in /var:\n");
508+
for elt in samples {
509+
writeln!(msg, " {elt:?}")?;
510+
}
511+
if rest > 0 {
512+
writeln!(msg, " ...and {} more", rest)?;
513+
}
514+
}
515+
lint_err(msg)
516+
}
517+
470518
#[distributed_slice(LINTS)]
471519
static LINT_NONEMPTY_BOOT: Lint = Lint::new_warning(
472520
"nonempty-boot",
@@ -498,8 +546,17 @@ fn check_boot(root: &Dir) -> LintResult {
498546

499547
#[cfg(test)]
500548
mod tests {
549+
use std::sync::LazyLock;
550+
501551
use super::*;
502552

553+
static ALTROOT_LINTS: LazyLock<usize> = LazyLock::new(|| {
554+
LINTS
555+
.iter()
556+
.filter(|lint| lint.root_type != Some(RootType::Running))
557+
.count()
558+
});
559+
503560
fn fixture() -> Result<cap_std_ext::cap_tempfile::TempDir> {
504561
let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;
505562
Ok(tempdir)
@@ -557,26 +614,27 @@ mod tests {
557614
let mut out = Vec::new();
558615
let root_type = RootType::Alternative;
559616
let r = lint_inner(root, root_type, [], &mut out).unwrap();
560-
assert_eq!(r.passed, LINTS.len());
617+
let running_only_lints = LINTS.len().checked_sub(*ALTROOT_LINTS).unwrap();
618+
assert_eq!(r.passed, *ALTROOT_LINTS);
561619
assert_eq!(r.fatal, 0);
562-
assert_eq!(r.skipped, 0);
620+
assert_eq!(r.skipped, running_only_lints);
563621
assert_eq!(r.warnings, 0);
564622

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

574632
// But verify that not skipping it results in a warning
575633
let mut out = Vec::new();
576634
let r = lint_inner(root, root_type, [], &mut out).unwrap();
577-
assert_eq!(r.passed, LINTS.len().checked_sub(1).unwrap());
635+
assert_eq!(r.passed, ALTROOT_LINTS.checked_sub(1).unwrap());
578636
assert_eq!(r.fatal, 0);
579-
assert_eq!(r.skipped, 0);
637+
assert_eq!(r.skipped, running_only_lints);
580638
assert_eq!(r.warnings, 1);
581639
Ok(())
582640
}

0 commit comments

Comments
 (0)