use log::trace;
use std::{collections, fs, path};
pub struct FragmentScanner {
dirs: Vec<path::PathBuf>,
ignore_dotfiles: bool,
allowed_extensions: Vec<String>,
}
impl FragmentScanner {
pub fn new(
base_dirs: Vec<String>,
shared_path: &str,
ignore_dotfiles: bool,
allowed_extensions: Vec<String>,
) -> Self {
let mut dirs = Vec::with_capacity(base_dirs.len());
for bdir in base_dirs {
let mut dpath = path::PathBuf::from(bdir);
dpath.push(shared_path.clone());
dirs.push(dpath);
}
Self { dirs, ignore_dotfiles, allowed_extensions }
}
pub fn scan(
&self,
) -> collections::BTreeMap<String, path::PathBuf> {
let mut files_map = collections::BTreeMap::new();
for dir in &self.dirs {
trace!("Scanning directory '{}'", dir.display());
let dir_iter = match fs::read_dir(dir) {
Ok(iter) => iter,
_ => continue,
};
for dir_entry in dir_iter {
let entry = match dir_entry {
Ok(f) => f,
_ => continue,
};
let fpath = entry.path();
let fname = match entry.file_name().into_string() {
Ok(n) => n,
_ => continue,
};
if self.ignore_dotfiles && fname.starts_with('.') {
continue;
};
if !self.allowed_extensions.is_empty() {
if let Some(extension) = fpath.extension() {
if let Ok(extension) = &extension.to_owned().into_string() {
if !self.allowed_extensions.contains(&extension) {
continue;
}
} else {
continue;
}
} else {
continue;
}
}
let meta = match entry.metadata() {
Ok(m) => m,
_ => continue,
};
if !meta.file_type().is_file() {
if let Ok(target) = fs::read_link(&fpath) {
if target == path::PathBuf::from("/dev/null") {
trace!("Nulled config file '{}'", fpath.display());
files_map.remove(&fname);
}
}
continue;
}
trace!("Found config file '{}' at '{}'", fname, fpath.display());
files_map.insert(fname, fpath);
}
}
files_map
}
}
#[cfg(test)]
mod tests {
use super::*;
struct FragmentNamePath {
name: String,
path: String,
}
fn assert_fragments_match(
fragments: &collections::BTreeMap<String, path::PathBuf>,
filename: &String,
filepath: &String,
) -> () {
assert_eq!(fragments.get(filename).unwrap(), &path::PathBuf::from(filepath));
}
fn assert_fragments_hit(
fragments: &collections::BTreeMap<String, path::PathBuf>,
filename: &str,
) -> () {
assert!(fragments.get(&String::from(filename)).is_some());
}
fn assert_fragments_miss(
fragments: &collections::BTreeMap<String, path::PathBuf>,
filename: &str,
) -> () {
assert!(fragments.get(&String::from(filename)).is_none());
}
#[test]
fn basic_override() {
let treedir = "tests/fixtures/tree-basic";
let dirs = vec![
format!("{}/{}", treedir, "usr/lib"),
format!("{}/{}", treedir, "run"),
format!("{}/{}", treedir, "etc"),
];
let allowed_extensions = vec![
String::from("toml"),
];
let od_cfg = FragmentScanner::new(dirs, "liboverdrop.d", false, allowed_extensions);
let expected_fragments = vec![
FragmentNamePath { name: String::from("01-config-a.toml"), path: treedir.to_owned() + "/etc/liboverdrop.d/01-config-a.toml" },
FragmentNamePath { name: String::from("02-config-b.toml"), path: treedir.to_owned() + "/run/liboverdrop.d/02-config-b.toml" },
FragmentNamePath { name: String::from("03-config-c.toml"), path: treedir.to_owned() + "/etc/liboverdrop.d/03-config-c.toml" },
FragmentNamePath { name: String::from("04-config-d.toml"), path: treedir.to_owned() + "/usr/lib/liboverdrop.d/04-config-d.toml" },
FragmentNamePath { name: String::from("05-config-e.toml"), path: treedir.to_owned() + "/etc/liboverdrop.d/05-config-e.toml" },
FragmentNamePath { name: String::from("06-config-f.toml"), path: treedir.to_owned() + "/run/liboverdrop.d/06-config-f.toml" },
FragmentNamePath { name: String::from("07-config-g.toml"), path: treedir.to_owned() + "/etc/liboverdrop.d/07-config-g.toml" },
];
let fragments = od_cfg.scan();
for frag in &expected_fragments {
assert_fragments_match(&fragments, &frag.name, &frag.path);
}
let expected_keys: Vec<String> = expected_fragments.into_iter().map(|x| x.name).collect();
let fragments_keys: Vec<String> = fragments.keys().cloned().collect();
assert_eq!(fragments_keys, expected_keys);
}
#[test]
fn basic_override_restrict_extensions() {
let treedir = "tests/fixtures/tree-basic";
let dirs = vec![
format!("{}/{}", treedir, "etc"),
];
let allowed_extensions = vec![
String::from("toml"),
];
let od_cfg = FragmentScanner::new(dirs, "liboverdrop.d", false, allowed_extensions);
let fragments = od_cfg.scan();
assert_fragments_hit(&fragments, "01-config-a.toml");
assert_fragments_miss(&fragments, "08-config-h.conf");
assert_fragments_miss(&fragments, "noextension");
}
#[test]
fn basic_override_allow_all_extensions() {
let treedir = "tests/fixtures/tree-basic";
let dirs = vec![
format!("{}/{}", treedir, "etc"),
];
let allowed_extensions = vec![];
let od_cfg = FragmentScanner::new(dirs, "liboverdrop.d", false, allowed_extensions);
let fragments = od_cfg.scan();
assert_fragments_hit(&fragments, "01-config-a.toml");
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_hit(&fragments, "noextension");
}
#[test]
fn basic_override_ignore_hidden() {
let treedir = "tests/fixtures/tree-basic";
let dirs = vec![
format!("{}/{}", treedir, "etc"),
];
let allowed_extensions = vec![];
let od_cfg = FragmentScanner::new(dirs, "liboverdrop.d", true, allowed_extensions);
let fragments = od_cfg.scan();
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_miss(&fragments, ".hidden.conf");
}
#[test]
fn basic_override_allow_hidden() {
let treedir = "tests/fixtures/tree-basic";
let dirs = vec![
format!("{}/{}", treedir, "etc"),
];
let allowed_extensions = vec![];
let od_cfg = FragmentScanner::new(dirs, "liboverdrop.d", false, allowed_extensions);
let fragments = od_cfg.scan();
assert_fragments_hit(&fragments, "config.conf");
assert_fragments_hit(&fragments, ".hidden.conf");
}
}