diff --git a/Cargo.lock b/Cargo.lock index 4464c0a3..cc8b0284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,7 +189,7 @@ dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.0", ] [[package]] @@ -306,6 +306,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.19" @@ -328,6 +337,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -342,6 +386,37 @@ dependencies = [ "rayon", ] +[[package]] +name = "derive_builder" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_builder_macro" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +dependencies = [ + "derive_builder_core", + "syn", +] + [[package]] name = "diff" version = "0.1.13" @@ -407,6 +482,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -434,6 +515,19 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + [[package]] name = "hashbrown" version = "0.13.2" @@ -455,6 +549,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + [[package]] name = "hex" version = "0.4.3" @@ -467,6 +567,28 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indoc" version = "2.0.4" @@ -547,6 +669,16 @@ dependencies = [ "adler", ] +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.32.2" @@ -596,24 +728,30 @@ dependencies = [ "os_info", "paketkoll_core", "proc-exit", + "rayon", ] [[package]] name = "paketkoll_core" version = "0.2.0" dependencies = [ + "ahash", "anyhow", "bitflags 2.4.2", "bstr", "compact_str", + "crossbeam-queue", "dashmap", + "derive_builder", "either", "flate2", "hex", + "ignore", "indoc", "lasso", "log", "md-5", + "num_cpus", "phf", "pretty_assertions", "rayon", @@ -622,7 +760,6 @@ dependencies = [ "rust-ini", "smallvec", "strum", - "typed-builder", ] [[package]] @@ -836,6 +973,15 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -886,6 +1032,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.0" @@ -934,26 +1086,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "typed-builder" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444d8748011b93cb168770e8092458cb0f8854f931ff82fdf6ddfbd72a9c933e" -dependencies = [ - "typed-builder-macro", -] - -[[package]] -name = "typed-builder-macro" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "563b3b88238ec95680aef36bdece66896eaa7ce3c0f1b4f39d38fb2435261352" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "typenum" version = "1.17.0" @@ -984,6 +1116,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -1006,6 +1148,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/crates/paketkoll/Cargo.toml b/crates/paketkoll/Cargo.toml index ac81a4d2..3621a885 100644 --- a/crates/paketkoll/Cargo.toml +++ b/crates/paketkoll/Cargo.toml @@ -29,6 +29,7 @@ log = "0.4.21" os_info = { version = "3.7.0", default-features = false } paketkoll_core = { version = "0.2.0", path = "../paketkoll_core" } proc-exit = "2.0.1" +rayon = "1.9.0" [build-dependencies] clap = { version = "4.5.1", features = ["derive"] } diff --git a/crates/paketkoll/src/cli.rs b/crates/paketkoll/src/cli.rs index c89c18fe..1f5c8257 100644 --- a/crates/paketkoll/src/cli.rs +++ b/crates/paketkoll/src/cli.rs @@ -1,9 +1,10 @@ use std::fmt::Display; -use clap::Parser; +use clap::{Parser, Subcommand}; #[derive(Debug, Parser)] #[command(version, about, long_about = None)] +#[command(propagate_version = true)] pub(crate) struct Cli { /// Trust mtime (don't check checksum if it matches) #[arg(long)] @@ -14,8 +15,25 @@ pub(crate) struct Cli { /// Which package manager backend to use #[arg(short, long, default_value_t = Backend::Auto)] pub(crate) backend: Backend, - /// Packages to check (default: all of them) - pub(crate) packages: Vec, + /// Operation to perform + #[command(subcommand)] + pub(crate) command: Commands, +} + +#[derive(Debug, Subcommand)] +pub(crate) enum Commands { + /// Check package files + Check { + /// Packages to check (default: all of them) + packages: Vec, + }, + /// Check package files and search for unexpected files + CheckUnexpected { + /// Extra paths to ignore (for unexpected file check only). + /// Globs are supported. + #[arg(long)] + ignore: Vec, + }, } /// Determine which package manager backend to use diff --git a/crates/paketkoll/src/main.rs b/crates/paketkoll/src/main.rs index 80092f90..4ce3b671 100644 --- a/crates/paketkoll/src/main.rs +++ b/crates/paketkoll/src/main.rs @@ -2,14 +2,18 @@ mod cli; +use std::{io::{stdout, BufWriter, Write}, os::unix::ffi::OsStrExt}; + use ahash::AHashSet; use clap::Parser; use cli::{Backend, Cli}; use paketkoll_core::{ backend, - config::{self, PackageFilter}, + config::{self, CheckUnexpectedConfigurationBuilder, PackageFilter}, + types::{Issue, PackageRef}, }; use proc_exit::{Code, Exit}; +use rayon::slice::ParallelSliceMut; fn main() -> anyhow::Result { let mut builder = @@ -17,24 +21,47 @@ fn main() -> anyhow::Result { builder.init(); let cli = Cli::parse(); - let (interner, found_issues) = backend::check(&cli.try_into()?)?; + let (interner, mut found_issues) = match cli.command { + cli::Commands::Check { .. } => backend::check(&(&cli).try_into()?)?, + cli::Commands::CheckUnexpected { ref ignore } => { + backend::check_unexpected(&(&cli).try_into()?, &{ + let mut builder = CheckUnexpectedConfigurationBuilder::default(); + builder.ignored_paths(ignore.clone()); + builder.build()? + })? + } + }; - let mut found_issues: Vec<_> = found_issues.collect(); - found_issues.sort_by_key(|(pkg, issue)| { + let key_extractor = |(pkg, issue): &(Option, Issue)| { ( pkg.and_then(|e| interner.try_resolve(&e.as_interner_ref())), issue.path().to_path_buf(), ) - }); + }; + + if found_issues.len() > 1000 { + found_issues.par_sort_by_key(key_extractor); + } else { + found_issues.sort_by_key(key_extractor); + } let has_issues = !found_issues.is_empty(); - for (pkg, issue) in found_issues.into_iter() { - let pkg = pkg - .and_then(|e| interner.try_resolve(&e.as_interner_ref())) - .unwrap_or("UNKNOWN_PKG"); - for kind in issue.kinds() { - println!("{pkg}: {:?} {kind}", issue.path()); + { + let mut stdout = BufWriter::new(stdout().lock()); + for (pkg, issue) in found_issues.into_iter() { + let pkg = pkg + .and_then(|e| interner.try_resolve(&e.as_interner_ref())) + .unwrap_or("UNKNOWN_PKG"); + for kind in issue.kinds() { + stdout.write(pkg.as_bytes())?; + stdout.write(b": ")?; + // Prefer to not do any escaping. This doesn't assume unicode. + // Also it is faster. + stdout.write(issue.path().as_os_str().as_bytes())?; + writeln!(stdout, " {kind}")?; + //writeln!(stdout, "{pkg}: {:?} {kind}", issue.path())?; + } } } Ok(if has_issues { @@ -82,16 +109,23 @@ impl From for config::ConfigFiles { } } -impl TryFrom for config::CheckConfiguration { +impl TryFrom<&Cli> for config::CommonConfiguration { type Error = anyhow::Error; - fn try_from(value: Cli) -> Result { - Ok(Self::builder() - .trust_mtime(value.trust_mtime) - .config_files(value.config_files.into()) - .backend(value.backend.try_into()?) - .package_filter(convert_filter(value.packages)) - .build()) + fn try_from(value: &Cli) -> Result { + let mut builder = Self::builder(); + + builder.trust_mtime(value.trust_mtime); + builder.config_files(value.config_files.into()); + builder.backend(value.backend.try_into()?); + + match value.command { + cli::Commands::Check { ref packages } => { + builder.package_filter(convert_filter(packages.clone())); + } + cli::Commands::CheckUnexpected { ignore: _ } => {} + } + Ok(builder.build()?) } } diff --git a/crates/paketkoll_core/Cargo.toml b/crates/paketkoll_core/Cargo.toml index 8f8cab34..003790b3 100644 --- a/crates/paketkoll_core/Cargo.toml +++ b/crates/paketkoll_core/Cargo.toml @@ -37,14 +37,18 @@ __md5 = ["dep:md-5"] __sha256 = ["dep:ring"] [dependencies] +ahash = "0.8.11" anyhow = { version = "1.0.80", features = ["backtrace"] } bitflags = "2.4.2" bstr = "1.9.1" compact_str = { version = "0.7.1", features = ["smallvec"] } +crossbeam-queue = "0.3.11" dashmap = { version = "5.5.3", optional = true } +derive_builder = "0.20.0" either = "1.10.0" flate2 = { version = "1.0.28", features = ["zlib-ng"], optional = true } hex = "0.4.3" +ignore = { version = "0.4.22", features = ["simd-accel"] } lasso = { version = "0.7.2", features = [ "ahasher", "inline-more", @@ -52,6 +56,7 @@ lasso = { version = "0.7.2", features = [ ] } log = "0.4.21" md-5 = { version = "0.10.6", optional = true } +num_cpus = "1.16.0" #mtree = { version = "0.5.0", optional = true } phf = { version = "0.11.2", features = ["macros"] } rayon = "1.9.0" @@ -64,7 +69,6 @@ smallvec = { version = "1.13.1", features = [ "union", ] } strum = { version = "0.26.1", features = ["derive"] } -typed-builder = "0.18.1" [dev-dependencies] indoc = "2.0.4" diff --git a/crates/paketkoll_core/src/backend.rs b/crates/paketkoll_core/src/backend.rs index 98c14cd6..48630aad 100644 --- a/crates/paketkoll_core/src/backend.rs +++ b/crates/paketkoll_core/src/backend.rs @@ -1,9 +1,14 @@ //! The various backends implementing distro specific support +use std::path::{Path, PathBuf}; + use anyhow::Context; +use crossbeam_queue::SegQueue; +use dashmap::DashMap; +use ignore::{overrides::OverrideBuilder, WalkBuilder, WalkState}; use rayon::prelude::*; -use crate::types::{Issue, IssueKind, PackageInterner, PackageRef}; +use crate::types::{FileEntry, Issue, IssueKind, PackageInterner, PackageRef}; #[cfg(feature = "arch_linux")] pub(crate) mod arch; @@ -15,10 +20,10 @@ pub(crate) mod filesystem; /// Check file system for differences using the given configuration pub fn check( - config: &crate::config::CheckConfiguration, + config: &crate::config::CommonConfiguration, ) -> anyhow::Result<( crate::types::PackageInterner, - impl Iterator, Issue)>, + Vec<(Option, Issue)>, )> { let backend = config .backend @@ -53,7 +58,163 @@ pub fn check( ) .collect(); - Ok((interner, mismatches.into_iter())) + Ok((interner, mismatches)) +} + +/// Check file system for differences (including unexpected files) using the given configuration +pub fn check_unexpected( + common_cfg: &crate::config::CommonConfiguration, + unexpected_cfg: &crate::config::CheckUnexpectedConfiguration, +) -> anyhow::Result<( + crate::types::PackageInterner, + Vec<(Option, Issue)>, +)> { + // Collect distro files + let backend = common_cfg + .backend + .create(common_cfg) + .with_context(|| format!("Failed to create backend for {}", common_cfg.backend))?; + let interner = PackageInterner::new(); + // Get distro specific file list + let results = backend.files(&interner).with_context(|| { + format!( + "Failed to collect information from backend {}", + common_cfg.backend + ) + })?; + + log::debug!(target: "paketkoll_core::backend", "Preparing data structures"); + // We want a hashmap from path to data here. + let path_map: DashMap<&Path, &FileEntry> = DashMap::new(); + results.par_iter().for_each(|file_entry| { + path_map.insert(&file_entry.path, file_entry); + }); + + // Build glob set of ignores + let overrides = { + let mut builder = OverrideBuilder::new("/"); + // Add standard ignores + for pattern in BUILTIN_IGNORES { + builder.add(pattern).expect("Builtin ignore failed"); + } + // Add user ignores + for pattern in &unexpected_cfg.ignored_paths { + builder.add(&("!".to_string() + pattern.as_str()))?; + } + builder.build()? + }; + + log::debug!(target: "paketkoll_core::backend", "Walking file system"); + let walker = WalkBuilder::new("/") + .hidden(true) + .parents(false) + .ignore(false) + .overrides(overrides) + .git_global(false) + .git_ignore(false) + .git_exclude(false) + .follow_links(false) + .same_file_system(false) + .threads(num_cpus::get()) + .build_parallel(); + + let collected_issues = SegQueue::new(); + + walker.run(|| { + Box::new(|entry| { + match entry { + Ok(entry) => { + let path = entry.path(); + if let Some(file_entry) = path_map.get(path) { + match filesystem::check_file(&file_entry, common_cfg) { + Ok(Some(inner)) => collected_issues.push((file_entry.package, inner)), + Ok(None) => (), + Err(err) => { + let issues = + smallvec::smallvec![IssueKind::FsCheckError(Box::new(err))]; + collected_issues.push(( + file_entry.package, + Issue::new(file_entry.path.clone(), issues), + )); + } + } + } else { + // Unexpected file found + collected_issues.push(( + None, + Issue::new( + path.to_path_buf(), + smallvec::smallvec![IssueKind::Unexpected], + ), + )) + } + } + Err(ignore_err) => collected_issues.push(interpret_ignore_error(ignore_err, None)), + } + WalkState::Continue + }) + }); + + // Collect all items from queue into vec and return + let mut mismatches = Vec::new(); + while let Some(item) = collected_issues.pop() { + mismatches.push(item); + } + + // Drop on a background thread, this help a bit. + drop(path_map); + rayon::spawn(move || { + drop(results); + }); + + Ok((interner, mismatches)) +} + +/// Attempt to make sense of the errors from the "ignore" crate. +/// +/// This involves recursively mapping into some of the variants to find the actual error. +fn interpret_ignore_error( + ignore_err: ignore::Error, + context: Option, +) -> (Option, Issue) { + match ignore_err { + ignore::Error::Partial(_) | ignore::Error::WithLineNumber { .. } => { + unreachable!("We don't parse ignore files") + } + ignore::Error::InvalidDefinition | ignore::Error::UnrecognizedFileType(_) => { + unreachable!("File types not used") + } + ignore::Error::Glob { .. } => unreachable!("We don't use globs from ignores"), + ignore::Error::WithPath { path, err } => { + return interpret_ignore_error(*err, Some(path)); + } + ignore::Error::WithDepth { depth: _, err } => { + return interpret_ignore_error(*err, None); + } + ignore::Error::Loop { .. } => unreachable!("We don't follow symlinks"), + ignore::Error::Io(io_error) => match io_error.kind() { + std::io::ErrorKind::PermissionDenied => { + return ( + None, + Issue::new( + context.unwrap_or_else(|| PathBuf::from("UNKNOWN_PATH")), + smallvec::smallvec![IssueKind::PermissionDenied], + ), + ); + } + _ => { + let issues = + smallvec::smallvec![IssueKind::FsCheckError(Box::new(io_error.into()))]; + return ( + None, + Issue::new( + context.unwrap_or_else(|| PathBuf::from("UNKNOWN_PATH")), + issues, + ), + ); + } + }, + } } pub(crate) trait Name { @@ -88,3 +249,21 @@ pub(crate) trait Packages: Name { // - Get source file from package (possibly downloading the package to cache if needed) // - Does a paccache equivalent exist for Debian or do we need to implement smart cache // cleaning as a separate tool? + +const BUILTIN_IGNORES: &[&str] = &[ + "!**/lost+found", + "!/dev/", + "!/home/", + "!/media/", + "!/mnt/", + "!/proc/", + "!/root/", + "!/run/", + "!/sys/", + "!/tmp/", + "!/var/lib/pacman/local/", + "!/var/lib/dpkg/", + "!/var/lib/flatpak/", + "!/var/tmp/", + "!/var/cache/", +]; diff --git a/crates/paketkoll_core/src/backend/filesystem.rs b/crates/paketkoll_core/src/backend/filesystem.rs index 722df821..d909d302 100644 --- a/crates/paketkoll_core/src/backend/filesystem.rs +++ b/crates/paketkoll_core/src/backend/filesystem.rs @@ -8,7 +8,7 @@ use std::{ }; use crate::{ - config::{CheckConfiguration, ConfigFiles}, + config::{CommonConfiguration, ConfigFiles}, types::{Directory, FileFlags, Properties, RegularFile, RegularFileBasic, Symlink}, }; @@ -20,7 +20,7 @@ use anyhow::{Context, Result}; const MODE_MASK: u32 = 0o7777; /// Determine if a given file should be processed -fn should_process(file: &FileEntry, config: &CheckConfiguration) -> bool { +fn should_process(file: &FileEntry, config: &CommonConfiguration) -> bool { match (config.config_files, file.flags.contains(FileFlags::CONFIG)) { (ConfigFiles::Include, _) | (ConfigFiles::Only, true) | (ConfigFiles::Exclude, false) => { true @@ -30,7 +30,7 @@ fn should_process(file: &FileEntry, config: &CheckConfiguration) -> bool { } /// Check a single file entry from a package database against the file system -pub(crate) fn check_file(file: &FileEntry, config: &CheckConfiguration) -> Result> { +pub(crate) fn check_file(file: &FileEntry, config: &CommonConfiguration) -> Result> { let mut issues = IssueVec::new(); match std::fs::symlink_metadata(&file.path) { Ok(metadata) => match &file.properties { @@ -138,7 +138,7 @@ pub(crate) fn check_file(file: &FileEntry, config: &CheckConfiguration) -> Resul /// Check the contents of a regular file against the expected values fn check_contents( issues: &mut IssueVec, - config: &CheckConfiguration, + config: &CommonConfiguration, path: &PathBuf, actual_metadata: &std::fs::Metadata, expected_mtime: Option<&std::time::SystemTime>, diff --git a/crates/paketkoll_core/src/config.rs b/crates/paketkoll_core/src/config.rs index 2eeb9eac..df5eb714 100644 --- a/crates/paketkoll_core/src/config.rs +++ b/crates/paketkoll_core/src/config.rs @@ -2,8 +2,6 @@ use std::fmt::Debug; -use typed_builder::TypedBuilder; - use crate::types::{PackageInterner, PackageRef}; /// Which backend to use for the system package manager @@ -21,7 +19,7 @@ impl Backend { /// Create a backend instance pub(crate) fn create( self, - configuration: &CheckConfiguration, + configuration: &CommonConfiguration, ) -> anyhow::Result> { match self { #[cfg(feature = "arch_linux")] @@ -84,24 +82,39 @@ impl Debug for PackageFilter { } } +#[derive(Debug, derive_builder::Builder)] +#[non_exhaustive] +pub struct CheckUnexpectedConfiguration { + /// Ignored paths (globs). Only appliccable to some operations. + #[builder(default = "vec![]")] + pub ignored_paths: Vec, +} + /// Describes what we want to check. Not all backends may support all features, /// in which case an error should be returned. -#[derive(Debug, TypedBuilder)] +#[derive(Debug, derive_builder::Builder)] #[non_exhaustive] -pub struct CheckConfiguration { +pub struct CommonConfiguration { /// Distro backend to use pub backend: Backend, /// Should we trust modification time and skip timestamp if mtime matches? - #[builder(default = false)] + #[builder(default = "false")] pub trust_mtime: bool, /// Should configuration files be included - #[builder(default = ConfigFiles::Include)] + #[builder(default = "ConfigFiles::Include")] pub config_files: ConfigFiles, /// Which packages to include - #[builder(default = &PackageFilter::Everything)] + #[builder(default = "&PackageFilter::Everything")] pub package_filter: &'static PackageFilter, } +impl CommonConfiguration { + /// Get a builder for this class + pub fn builder() -> CommonConfigurationBuilder { + Default::default() + } +} + /// Describe how to check config files #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub enum ConfigFiles { diff --git a/crates/paketkoll_core/src/types.rs b/crates/paketkoll_core/src/types.rs index 7d3e8c14..afd3b4a8 100644 --- a/crates/paketkoll_core/src/types.rs +++ b/crates/paketkoll_core/src/types.rs @@ -8,7 +8,7 @@ pub(crate) use files::{ Checksum, Directory, FileEntry, FileFlags, Gid, Mode, Properties, RegularFile, RegularFileBasic, Symlink, Uid, }; -pub(crate) use issue::{Issue, IssueKind, IssueVec}; +pub use issue::{Issue, IssueKind, IssueVec}; pub(crate) use package::InstallReason; pub use package::PackageInterner; diff --git a/crates/paketkoll_core/src/types/issue.rs b/crates/paketkoll_core/src/types/issue.rs index f06b5cf2..40780a01 100644 --- a/crates/paketkoll_core/src/types/issue.rs +++ b/crates/paketkoll_core/src/types/issue.rs @@ -43,6 +43,8 @@ impl Issue { pub enum IssueKind { /// Missing entity from file system Missing, + /// Extra unexpected entity on file system + Unexpected, /// Failed to check for (or check contents of) entity due to permissions PermissionDenied, /// Type of entity was not as expected (e.g. file vs symlink) @@ -69,6 +71,7 @@ impl std::fmt::Display for IssueKind { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { IssueKind::Missing => write!(f, "missing file/directory/...")?, + IssueKind::Unexpected => write!(f, "unexpected file")?, IssueKind::PermissionDenied => write!(f, "read error (Permission denied)")?, IssueKind::TypeIncorrect => write!(f, "type mismatch")?, IssueKind::SizeIncorrect => write!(f, "size mismatch")?,