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 }