|
7 | 7 |
|
8 | 8 | use std::collections::BTreeSet;
|
9 | 9 | use std::env::consts::ARCH;
|
| 10 | +use std::fmt::Write as WriteFmt; |
10 | 11 | use std::os::unix::ffi::OsStrExt;
|
11 | 12 |
|
12 | 13 | use anyhow::Result;
|
@@ -467,6 +468,53 @@ fn check_varlog(root: &Dir) -> LintResult {
|
467 | 468 | lint_err(format!("Found non-empty logfile: {first}{others}"))
|
468 | 469 | }
|
469 | 470 |
|
| 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 | + |
470 | 518 | #[distributed_slice(LINTS)]
|
471 | 519 | static LINT_NONEMPTY_BOOT: Lint = Lint::new_warning(
|
472 | 520 | "nonempty-boot",
|
@@ -498,8 +546,17 @@ fn check_boot(root: &Dir) -> LintResult {
|
498 | 546 |
|
499 | 547 | #[cfg(test)]
|
500 | 548 | mod tests {
|
| 549 | + use std::sync::LazyLock; |
| 550 | + |
501 | 551 | use super::*;
|
502 | 552 |
|
| 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 | + |
503 | 560 | fn fixture() -> Result<cap_std_ext::cap_tempfile::TempDir> {
|
504 | 561 | let tempdir = cap_std_ext::cap_tempfile::tempdir(cap_std::ambient_authority())?;
|
505 | 562 | Ok(tempdir)
|
@@ -557,26 +614,27 @@ mod tests {
|
557 | 614 | let mut out = Vec::new();
|
558 | 615 | let root_type = RootType::Alternative;
|
559 | 616 | 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); |
561 | 619 | assert_eq!(r.fatal, 0);
|
562 |
| - assert_eq!(r.skipped, 0); |
| 620 | + assert_eq!(r.skipped, running_only_lints); |
563 | 621 | assert_eq!(r.warnings, 0);
|
564 | 622 |
|
565 | 623 | let r = lint_inner(root, root_type, ["var-log"], &mut out).unwrap();
|
566 | 624 | // Trigger a failure in var-log
|
567 | 625 | root.create_dir_all("var/log/dnf")?;
|
568 | 626 | 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()); |
570 | 628 | assert_eq!(r.fatal, 0);
|
571 |
| - assert_eq!(r.skipped, 1); |
| 629 | + assert_eq!(r.skipped, running_only_lints + 1); |
572 | 630 | assert_eq!(r.warnings, 0);
|
573 | 631 |
|
574 | 632 | // But verify that not skipping it results in a warning
|
575 | 633 | let mut out = Vec::new();
|
576 | 634 | 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()); |
578 | 636 | assert_eq!(r.fatal, 0);
|
579 |
| - assert_eq!(r.skipped, 0); |
| 637 | + assert_eq!(r.skipped, running_only_lints); |
580 | 638 | assert_eq!(r.warnings, 1);
|
581 | 639 | Ok(())
|
582 | 640 | }
|
|
0 commit comments