jannie

jannie (mirror)
Log | Files | Refs | README | LICENSE

lib.rs (8966B)


      1 use std::{
      2     cell::{BorrowError, BorrowMutError},
      3     collections::VecDeque,
      4     path::{Path, StripPrefixError},
      5     string::FromUtf8Error,
      6 };
      7 
      8 use clap::Parser;
      9 
     10 use crate::{
     11     args::Args,
     12     config::Config,
     13     filetree::{Filetree, node::Node},
     14     state::State,
     15 };
     16 
     17 pub mod args;
     18 pub mod config;
     19 pub mod filetree;
     20 pub mod inventory;
     21 pub mod logging;
     22 pub mod path_processing;
     23 pub mod state;
     24 
     25 /// `main`, but returns [`Result`].
     26 pub fn result_main() -> Result<(), Error> {
     27     let args = Args::parse();
     28     let dir = path_processing::process_path(&args.dir).map_err(Error::ProcessDirPath)?;
     29 
     30     let config_path = dir.join("config.yaml");
     31     let config = Config::new(&config_path)?;
     32 
     33     logging::setup_logging(&config.logging)?;
     34 
     35     tracing::debug!("Starting with args: {args:#?}");
     36     tracing::debug!("Read config: {config:#?}");
     37 
     38     let state = State::new(&dir, config)?;
     39 
     40     tracing::debug!("Read inventory: {:#?}", state.inventory);
     41 
     42     state.runtime.block_on(read(&state))?;
     43 
     44     Ok(())
     45 }
     46 
     47 /// This meta is used for display tree.
     48 #[derive(Clone, Debug)]
     49 struct Meta {
     50     inventory: Option<Option<String>>,
     51     blacklist: Option<bool>,
     52 }
     53 
     54 /// Scan workspace and display its tree.
     55 async fn read(state: &State) -> Result<(), Error> {
     56     let root = Node::new(
     57         "/".into(),
     58         Meta {
     59             blacklist: None,
     60             inventory: None,
     61         },
     62     );
     63     let mut filetree = Filetree::new(root);
     64 
     65     let mut processing_buffer = Vec::new();
     66 
     67     // Add all whitelist to buffer
     68     for filter in state.filters.iter().filter(|filter| filter.is_whitelist()) {
     69         processing_buffer.push(filter.path.clone());
     70     }
     71 
     72     // This makes it so every prefix goes first.
     73     processing_buffer.sort();
     74 
     75     // Has some specific ordering so the tree is built in the correct order.
     76     let mut processing_buffer = VecDeque::from(processing_buffer);
     77 
     78     while let Some(item) = processing_buffer.pop_front() {
     79         tracing::debug!("Adding {:?} to tree", item);
     80 
     81         let allowed_to_scan = allowed_to_scan(state, &item)?;
     82         let inventory_message = (|| -> Result<Option<Option<String>>, Error> {
     83             let Some(node) = state.inventory_tree.node_by_path(&item) else {
     84                 return Ok(None);
     85             };
     86             let node = node.try_borrow()?;
     87             if !node.is_leaf() {
     88                 return Ok(None);
     89             }
     90             let Some(check) = node.meta().clone() else {
     91                 return Err(Error::NoInventoryCheckInfo);
     92             };
     93             Ok(Some(match check {
     94                 inventory::Check::None => None,
     95                 inventory::Check::Shell(cmd) => Some(String::from_utf8(
     96                     std::process::Command::new("sh")
     97                         .arg("-c")
     98                         .arg(cmd)
     99                         .output()
    100                         .map_err(Error::Command)?
    101                         .stdout,
    102                 )?),
    103             }))
    104         })()?;
    105 
    106         if let Some(node) = filetree.node_by_path(&item) {
    107             node.try_borrow_mut()?.meta_mut().blacklist = Some(!allowed_to_scan);
    108         } else {
    109             // The earliest prefix that already added
    110             let mut base = item.clone();
    111             while !filetree.check_path(&base) {
    112                 base.pop();
    113             }
    114 
    115             // What is not yet added
    116             let mut suffix = item
    117                 .strip_prefix(&base)
    118                 .map_err(Error::BasePrefix)?
    119                 .to_owned();
    120             suffix.pop();
    121 
    122             for component in suffix.components() {
    123                 base.push(component);
    124                 filetree.insert(Node::new(
    125                     base.clone(),
    126                     Meta {
    127                         blacklist: None,
    128                         inventory: None,
    129                     },
    130                 ))?;
    131             }
    132             filetree.insert(Node::new(
    133                 item.clone(),
    134                 Meta {
    135                     blacklist: Some(!allowed_to_scan),
    136                     inventory: inventory_message,
    137                 },
    138             ))?;
    139         }
    140 
    141         if allowed_to_scan && should_scan(state, &item)? {
    142             tracing::debug!("Scanning {:?}", item.file_name());
    143             let mut iter = tokio::fs::read_dir(&item).await.map_err(Error::ReadFs)?;
    144 
    145             let mut dir_buffer = Vec::new();
    146 
    147             while let Some(result) = iter.next_entry().await.map_err(Error::ReadFs)? {
    148                 let path = result.path();
    149                 dir_buffer.push(path)
    150             }
    151 
    152             dir_buffer.sort();
    153 
    154             while let Some(path) = dir_buffer.pop() {
    155                 processing_buffer.push_front(path);
    156             }
    157         }
    158     }
    159 
    160     print(&filetree)?;
    161 
    162     Ok(())
    163 }
    164 
    165 /// Does not check if allowed.
    166 /// Please check if allowed before doing this.
    167 fn should_scan(state: &State, path: &Path) -> Result<bool, Error> {
    168     // is allowed
    169     // and is a prefix of a blacklist that is not overridden or is a prefix of an inventory item
    170     let inventory = if let Some(node) = state.inventory_tree.node_by_path(path) {
    171         !node.try_borrow()?.is_leaf()
    172     } else {
    173         false
    174     };
    175 
    176     Ok(state.blacklist_tree.check_path(path) || inventory)
    177 }
    178 
    179 /// If a directory is allowed to be scanned.
    180 /// Whitelist has more priority than blacklist on the same level of specificity.
    181 fn allowed_to_scan(state: &State, path: &Path) -> Result<bool, Error> {
    182     // If there is no not-overridden blacklist
    183 
    184     // If no such blacklist filter found that blacklists the path such that
    185     // for this blacklist filter no such whitelist found that whitelists the
    186     // path back aka for which the blacklist is the prefix
    187     Ok(!state
    188         .filters
    189         .iter()
    190         .filter(|filter| filter.is_blacklist())
    191         .any(|bl_filter| {
    192             path.starts_with(&bl_filter.path)
    193                 && !state
    194                     .filters
    195                     .iter()
    196                     .filter(|filter| filter.is_whitelist())
    197                     .any(|wl_filter| {
    198                         path.starts_with(&wl_filter.path)
    199                             && wl_filter.path.starts_with(&bl_filter.path)
    200                     })
    201         }))
    202 }
    203 
    204 fn print(tree: &Filetree<Meta>) -> Result<(), Error> {
    205     let mut print_buffer = VecDeque::new();
    206     println!(
    207         "{}",
    208         tree.root()
    209             .try_borrow()?
    210             .path()
    211             .to_str()
    212             .unwrap_or("UNKNOWN")
    213             .to_owned()
    214     );
    215     print_buffer.extend(
    216         tree.root()
    217             .try_borrow()?
    218             .children()
    219             .map(|child_id| (1, child_id)),
    220     );
    221     while let Some((offset, node_id)) = print_buffer.pop_front() {
    222         let node = tree.node(node_id).expect("Shold have the node");
    223         let name = node
    224             .try_borrow()?
    225             .path()
    226             .file_name()
    227             .map(|name| name.to_str())
    228             .flatten()
    229             .unwrap_or("UNKNOWN")
    230             .to_owned();
    231         let offset_text = "  ".repeat(offset);
    232         if let Some(msg) = node.try_borrow()?.meta().inventory.as_ref() {
    233             let msg = msg.as_deref().unwrap_or("\x1b[31mno info\x1b[0m");
    234             println!(
    235                 "{}\x1b[2m|\x1b[0m \x1b[32m{}\x1b[0m \x1b[3m{}\x1b[0m",
    236                 offset_text, name, msg,
    237             );
    238         } else if let Some(true) = node.try_borrow()?.meta().blacklist {
    239             println!("{}\x1b[2m|\x1b[0m \x1b[2;9m{}\x1b[0m", offset_text, name);
    240         } else if node.try_borrow()?.is_leaf() {
    241             println!("{}\x1b[2m|\x1b[0m \x1b[31m{}\x1b[0m", offset_text, name);
    242         } else {
    243             println!("{}\x1b[2m|\x1b[0m {}", offset_text, name);
    244         }
    245         for child in node
    246             .try_borrow()?
    247             .children()
    248             .map(|child_id| (offset + 1, child_id))
    249             .collect::<Vec<_>>()
    250             .into_iter()
    251             .rev()
    252         {
    253             print_buffer.push_front(child);
    254         }
    255     }
    256     Ok(())
    257 }
    258 
    259 #[derive(thiserror::Error, Debug)]
    260 pub enum Error {
    261     #[error(transparent)]
    262     Config(#[from] config::Error),
    263     #[error(transparent)]
    264     State(#[from] state::Error),
    265     #[error("Error processing dir path: {0}")]
    266     ProcessDirPath(path_processing::Error),
    267     #[error("Error setting up logging: {0}")]
    268     Logging(#[from] logging::Error),
    269     #[error("Error building filetree: {0}")]
    270     Filetree(#[from] filetree::Error),
    271     #[error("Borrow error: {0}")]
    272     Borrow(#[from] BorrowError),
    273     #[error("Borrow mut error: {0}")]
    274     BorrowMut(#[from] BorrowMutError),
    275     #[error("Error stripping base from path: {0}")]
    276     BasePrefix(StripPrefixError),
    277     #[error("No inventory check info on tree node")]
    278     NoInventoryCheckInfo,
    279     #[error("Error executing command: {0}")]
    280     Command(std::io::Error),
    281     #[error("String returned by command is not utf8: {0}")]
    282     StringUtf8(#[from] FromUtf8Error),
    283     #[error("Error reading filesystem: {0}")]
    284     ReadFs(tokio::io::Error),
    285 }