state.rs (6825B)
1 use std::path::{Path, PathBuf}; 2 3 use tokio::runtime::Runtime; 4 5 use crate::{ 6 config::{Config, Filter, InventoryFormat}, 7 filetree::{self, Filetree, node::Node}, 8 inventory::{self, Inventory}, 9 path_processing, 10 }; 11 12 pub struct State { 13 pub config_dir: PathBuf, 14 pub config: Config, 15 pub runtime: Runtime, 16 pub inventory: Inventory, 17 /// Paths are absolute and processed 18 pub filters: Vec<Filter>, 19 /// Absolute processed 20 pub root: PathBuf, 21 pub blacklist_tree: Filetree<()>, 22 pub whitelist_tree: Filetree<()>, 23 pub inventory_tree: Filetree<Option<inventory::Check>>, 24 } 25 26 impl State { 27 pub fn new(config_dir: &Path, config: Config) -> Result<Self, Error> { 28 let runtime = tokio::runtime::Builder::new_current_thread() 29 .enable_all() 30 .build() 31 .map_err(Error::Runtime)?; 32 33 let inventory_path = match config.inventory_format { 34 InventoryFormat::Yaml => config_dir.join("inventory.yaml"), 35 InventoryFormat::Json => config_dir.join("inventory.json"), 36 }; 37 38 tracing::trace!("Reading inventory from {inventory_path:?}"); 39 40 let inventory = runtime 41 .block_on(tokio::fs::read(inventory_path)) 42 .map_err(Error::ReadInventoryFile)?; 43 44 tracing::trace!("Parsing inventory file contents"); 45 46 let inventory: Inventory = 47 match config.inventory_format { 48 InventoryFormat::Yaml => serde_yaml::from_slice(inventory.as_slice()) 49 .map_err(Error::ReadInventoryYaml)?, 50 InventoryFormat::Json => serde_json::from_slice(inventory.as_slice()) 51 .map_err(Error::ReadInventoryJson)?, 52 }; 53 54 tracing::trace!("Processing root path"); 55 56 let root = path_processing::process_path(&config.root).map_err(Error::ProcessRootPath)?; 57 58 let mut filters = Vec::new(); 59 60 tracing::debug!("Preprocessing filters"); 61 62 for filter in &config.filters { 63 tracing::trace!("Preprocessing filter {filter:?}"); 64 let mut path = config.root.clone(); 65 path.push(&filter.path); 66 filters.push(Filter { 67 path: match path_processing::process_path(&path) { 68 Ok(path) => path, 69 Err(error) if error.is_file_not_found() => { 70 tracing::warn!("Filter path was not found: {filter:?}"); 71 continue; 72 } 73 Err(error) => Err(Error::ProcessPath(error))?, 74 }, 75 filter_type: filter.filter_type.clone(), 76 }); 77 } 78 79 tracing::debug!("Building trees from filters"); 80 81 let mut blacklist_tree = Filetree::new(Node::new("/".into(), ())); 82 let mut whitelist_tree = Filetree::new(Node::new("/".into(), ())); 83 84 for filter in &filters { 85 let path = &filter.path; 86 match filter.filter_type { 87 crate::config::FilterType::Whitelist => { 88 tracing::debug!("Adding {:?} to whitelist", path); 89 add_to_tree(&mut whitelist_tree, &path)? 90 } 91 crate::config::FilterType::Blacklist => { 92 if filters 93 .iter() 94 .filter(|filter| filter.is_whitelist()) 95 .find(|filter| &filter.path == path) 96 .is_some() 97 { 98 tracing::debug!("Skipping {:?} blacklist", path); 99 } else { 100 tracing::debug!("Adding {:?} to blacklist", path); 101 add_to_tree(&mut blacklist_tree, &path)? 102 } 103 } 104 } 105 } 106 107 tracing::debug!("Building inventory tree"); 108 109 let mut inventory_tree = Filetree::new(Node::new("/".into(), None)); 110 for item in &inventory.items { 111 let path = match path_processing::process_path(&item.path) { 112 Ok(path) => path, 113 Err(error) if error.is_file_not_found() => { 114 tracing::warn!("Inventory item was not found: {item:?}"); 115 continue; 116 } 117 Err(error) => Err(Error::ProcessPath(error))?, 118 }; 119 let mut components = path.components(); 120 let mut path_buf = PathBuf::new(); 121 loop { 122 let component = components.next(); 123 match component { 124 Some(component) => { 125 path_buf.push(component); 126 if inventory_tree.node_by_path(&path_buf).is_none() { 127 inventory_tree.insert(Node::new(path_buf.clone(), None))?; 128 } 129 } 130 None => { 131 inventory_tree 132 .node_by_path(&path) 133 .expect("Should have added inventory item") 134 .borrow_mut() 135 .meta_mut() 136 .replace(item.check.clone()); 137 break; 138 } 139 } 140 } 141 } 142 143 Ok(Self { 144 config_dir: config_dir.into(), 145 config, 146 runtime, 147 inventory, 148 filters, 149 root, 150 blacklist_tree, 151 whitelist_tree, 152 inventory_tree, 153 }) 154 } 155 } 156 157 fn add_to_tree(tree: &mut Filetree<()>, path: &Path) -> Result<(), Error> { 158 let mut components = path.components(); 159 let mut path_buf = PathBuf::new(); 160 loop { 161 let component = components.next(); 162 match component { 163 Some(component) => { 164 path_buf.push(component); 165 if tree.node_by_path(&path_buf).is_none() { 166 tree.insert(Node::new(path_buf.clone(), ()))?; 167 } 168 } 169 None => { 170 assert!(tree.node_by_path(path).is_some()); 171 break; 172 } 173 } 174 } 175 Ok(()) 176 } 177 178 #[derive(thiserror::Error, Debug)] 179 pub enum Error { 180 #[error("Error building runtime: {0}")] 181 Runtime(tokio::io::Error), 182 #[error("Error reading inventory file: {0}")] 183 ReadInventoryFile(std::io::Error), 184 #[error("Error processing path: {0}")] 185 ProcessPath(path_processing::Error), 186 #[error("Error processing root path: {0}")] 187 ProcessRootPath(path_processing::Error), 188 #[error("Error parsing yaml inventory: {0}")] 189 ReadInventoryYaml(serde_yaml::Error), 190 #[error("Error parsing json inventory: {0}")] 191 ReadInventoryJson(serde_json::Error), 192 #[error("Error building filetree: {0}")] 193 Filetree(#[from] filetree::Error), 194 }