kanit

Toy init system
Log | Files | Refs | README | LICENSE

mod.rs (11675B)


      1 use std::cell::RefCell;
      2 use std::collections::{HashMap, HashSet};
      3 use std::fs;
      4 use std::os::unix::fs::MetadataExt;
      5 use std::sync::OnceLock;
      6 
      7 use log::{trace, warn};
      8 use send_wrapper::SendWrapper;
      9 use sha2::{Digest, Sha256};
     10 use walkdir::WalkDir;
     11 
     12 use kanit_common::constants;
     13 use kanit_common::error::{Context, Result, StaticError};
     14 use kanit_unit::formats::{DependencyGrouping, Unit};
     15 use kanit_unit::{RcUnit, UnitName, wrap_unit};
     16 use sort::{SortableUnit, obtain_load_order};
     17 
     18 mod sort;
     19 
     20 static LOADER: OnceLock<SendWrapper<RefCell<Loader>>> = OnceLock::new();
     21 
     22 pub struct Loader {
     23     pub started: HashSet<UnitName>, // TODO; should become statuses
     24     pub enabled: HashMap<Box<str>, HashSet<UnitName>>,
     25     pub grouping: HashMap<Box<str>, Vec<Vec<RcUnit>>>,
     26     pub units: HashMap<UnitName, RcUnit>,
     27     // pub ev_lock: Rc<Mutex<()>>, // i am pro at rust
     28 }
     29 
     30 enum UnitLoad {
     31     FailedRead(String),
     32     FailedParse(String),
     33     Loaded(Box<Unit>),
     34 }
     35 
     36 #[derive(Copy, Clone, Eq, PartialEq)]
     37 enum DrillRound {
     38     All,
     39     Positional,
     40     Needs,
     41 }
     42 
     43 impl Loader {
     44     // initialize populates the loader with units and if they're enabled
     45     // it won't create the load order as `units` will add more units
     46     pub fn initialize() -> Result<()> {
     47         let mut loader = Loader {
     48             started: HashSet::new(),
     49             enabled: HashMap::new(),
     50             grouping: HashMap::new(),
     51             units: HashMap::new(),
     52             // ev_lock: Rc::new(Mutex::new(())),
     53         };
     54 
     55         loader.reload()?;
     56 
     57         let _ = LOADER.set(SendWrapper::new(RefCell::new(loader)));
     58 
     59         Ok(())
     60     }
     61 
     62     // TODO; should we separate reload
     63     pub fn reload(&mut self) -> Result<()> {
     64         // TODO; async?
     65         self.units = fs::read_dir(constants::KAN_UNIT_DIR)
     66             .context("failed to read units directory")?
     67             .filter_map(|e| e.ok())
     68             .map(|f| {
     69                 fs::read_to_string(f.path())
     70                     .map(|c| {
     71                         (
     72                             UnitName::from(f.file_name().to_string_lossy()),
     73                             c.parse::<Unit>()
     74                                 .map(|u| UnitLoad::Loaded(Box::new(u)))
     75                                 .unwrap_or_else(|e| UnitLoad::FailedParse(e.to_string())),
     76                         )
     77                     })
     78                     .unwrap_or_else(|e| {
     79                         (
     80                             UnitName::from(f.file_name().to_string_lossy()),
     81                             UnitLoad::FailedRead(e.to_string()),
     82                         )
     83                     })
     84             })
     85             .filter_map(|(name, load)| match load {
     86                 UnitLoad::FailedRead(e) => {
     87                     warn!("failed to read unit {name}: {e}");
     88                     None
     89                 }
     90                 UnitLoad::FailedParse(e) => {
     91                     warn!("failed to parse unit {name}: {e}");
     92                     None
     93                 }
     94                 UnitLoad::Loaded(u) => {
     95                     trace!("loaded unit {name}");
     96                     Some((name.clone(), wrap_unit((name, *u))))
     97                 }
     98             })
     99             .collect::<HashMap<_, _>>();
    100 
    101         // technically we don't care about the contents of the file
    102         self.enabled = fs::read_dir(constants::KAN_ENABLED_DIR)
    103             .context("failed to read enabled dir")?
    104             .filter_map(|e| {
    105                 e.ok().and_then(|f| {
    106                     f.file_type()
    107                         .map(|t| t.is_dir())
    108                         .ok()
    109                         .filter(|t| *t)
    110                         .map(|_| f)
    111                 })
    112             })
    113             .filter_map(|f| {
    114                 fs::read_dir(f.path())
    115                     .ok()
    116                     .map(|r| {
    117                         r.filter_map(|e| e.ok())
    118                             .map(|e| UnitName::from(e.file_name().to_string_lossy()))
    119                             .collect::<HashSet<_>>()
    120                     })
    121                     .map(|e| (Box::from(f.file_name().to_string_lossy()), e))
    122             })
    123             .collect::<HashMap<_, _>>();
    124 
    125         Ok(())
    126     }
    127 
    128     // rebuilding levels is such an inefficient process
    129     // that's why we cache
    130     // we attempt:
    131     // * all requirements fulfilled (needs, wants, before, after)
    132     // * no positional (needs, wants)
    133     // * only needs
    134     // give up if it fails
    135 
    136     // recursively
    137     fn get_edges(unit: &SortableUnit, round: DrillRound) -> Vec<UnitName> {
    138         let deps = unit.dependencies.clone();
    139 
    140         match round {
    141             DrillRound::Needs => deps.needs.clone(),
    142             DrillRound::Positional => deps
    143                 .needs
    144                 .iter()
    145                 .chain(deps.wants.iter())
    146                 .cloned()
    147                 .collect(),
    148             DrillRound::All => deps
    149                 .needs
    150                 .iter()
    151                 .chain(deps.wants.iter())
    152                 .chain(deps.before.iter())
    153                 .chain(deps.after.iter())
    154                 .cloned()
    155                 .collect(),
    156         }
    157     }
    158 
    159     fn drill(
    160         to_drill: Vec<UnitName>,
    161         info: &HashMap<UnitName, SortableUnit>,
    162         round: DrillRound,
    163         seen: &mut HashSet<UnitName>,
    164     ) -> Result<()> {
    165         for unit in to_drill {
    166             if seen.contains(&unit) {
    167                 continue;
    168             }
    169 
    170             seen.insert(unit.clone());
    171 
    172             let unit_info = info
    173                 .get(&unit)
    174                 .with_context(move || format!("failed to find unit `{unit}`"))?;
    175 
    176             Self::drill(Self::get_edges(unit_info, round), info, round, seen)?;
    177         }
    178 
    179         Ok(())
    180     }
    181 
    182     fn rebuild_level(
    183         &self,
    184         info: &HashMap<UnitName, SortableUnit>,
    185         enabled: &HashSet<UnitName>,
    186     ) -> Result<Vec<Vec<RcUnit>>> {
    187         let mut to_load = HashSet::new();
    188 
    189         let rounds = [DrillRound::All, DrillRound::Positional, DrillRound::Needs];
    190 
    191         for round in rounds {
    192             if Self::drill(enabled.iter().cloned().collect(), info, round, &mut to_load).is_err() {
    193                 continue;
    194             }
    195 
    196             let units = to_load
    197                 .iter()
    198                 .filter_map(|n| info.get(n))
    199                 .cloned()
    200                 .collect();
    201 
    202             match obtain_load_order(units) {
    203                 Ok(order) => {
    204                     return Ok(order
    205                         .iter()
    206                         .map(|n| {
    207                             n.iter()
    208                                 .map(|m| self.units.get(&m.name).cloned().unwrap())
    209                                 .collect::<Vec<_>>()
    210                         })
    211                         .collect::<Vec<_>>());
    212                 }
    213                 Err(e) => {
    214                     if round == DrillRound::Needs {
    215                         return Err(e);
    216                     } else {
    217                         continue;
    218                     }
    219                 }
    220             }
    221         }
    222 
    223         unreachable!()
    224     }
    225 
    226     fn hash_enable_dir() -> [u8; 32] {
    227         WalkDir::new(constants::KAN_ENABLED_DIR)
    228             .follow_links(true)
    229             .into_iter()
    230             .filter_map(|e| e.ok())
    231             .filter_map(|e| e.metadata().ok().map(|e| e.mtime()))
    232             .fold(Sha256::new(), |acc, m| acc.chain_update(m.to_le_bytes()))
    233             .finalize()
    234             .as_slice()
    235             .try_into()
    236             .unwrap() // sha256 hash is always 32 bytes (32 * 8 = 256)
    237     }
    238 
    239     fn try_cache_map(&mut self) -> Result<()> {
    240         let grouping = fs::read_to_string(constants::KAN_DEPENDENCY_MAP)
    241             .context("failed to read dependency map")?
    242             .parse::<DependencyGrouping>()
    243             .context("failed to parse grouping")?;
    244 
    245         let hash = Self::hash_enable_dir();
    246 
    247         if hash.as_slice() == grouping.hash.as_slice() {
    248             self.grouping = grouping
    249                 .groups
    250                 .into_iter()
    251                 .map(|(k, v)| -> Result<_> {
    252                     Ok((
    253                         k,
    254                         v.into_iter()
    255                             .map(|v| {
    256                                 v.into_iter()
    257                                     .map(|u| {
    258                                         self.units.get(&u).cloned().context("failed to find unit")
    259                                     })
    260                                     .collect::<Result<Vec<_>>>()
    261                             })
    262                             .collect::<Result<Vec<_>>>()?,
    263                     ))
    264                 })
    265                 .collect::<Result<HashMap<_, _>>>()?;
    266         } else {
    267             Err(StaticError("invalid hashes"))?;
    268         }
    269 
    270         Ok(())
    271     }
    272 
    273     pub fn rebuild(&mut self, force: bool) -> Result<()> {
    274         if !force && self.try_cache_map().is_ok() {
    275             return Ok(());
    276         }
    277 
    278         let info = self
    279             .units
    280             .iter()
    281             .map(|(k, v)| (k.clone(), v.into()))
    282             .collect::<HashMap<_, SortableUnit>>();
    283 
    284         for (name, level) in self.enabled.iter() {
    285             let grouping = self.rebuild_level(&info, level)?;
    286 
    287             self.grouping.insert(name.clone(), grouping);
    288         }
    289 
    290         Ok(())
    291     }
    292 
    293     pub fn save(&self) -> Result<()> {
    294         let groups = self
    295             .grouping
    296             .iter()
    297             .map(|(k, v)| {
    298                 (
    299                     k.clone(),
    300                     v.iter()
    301                         .map(|g| g.iter().map(|u| u.borrow().name()).collect())
    302                         .collect(),
    303                 )
    304             })
    305             .collect();
    306 
    307         let group = DependencyGrouping {
    308             hash: Self::hash_enable_dir(),
    309             groups,
    310         };
    311 
    312         fs::write(constants::KAN_DEPENDENCY_MAP, group.to_string())
    313             .context("failed to write dependency map")?;
    314 
    315         Ok(())
    316     }
    317 
    318     pub fn obtain() -> Result<&'static SendWrapper<RefCell<Self>>> {
    319         let loader = LOADER.get().context("loader not initialized")?;
    320 
    321         if !loader.valid() {
    322             Err(StaticError("cannot obtain loader from different thread"))?;
    323         }
    324 
    325         Ok(loader)
    326     }
    327 
    328     pub fn extend_units<T>(&mut self, iter: T)
    329     where
    330         T: IntoIterator<Item = (UnitName, RcUnit)>,
    331     {
    332         self.units.extend(iter);
    333     }
    334 
    335     pub fn extend_enabled<T>(&mut self, iter: T)
    336     where
    337         T: IntoIterator<Item = (Box<str>, HashSet<UnitName>)>,
    338     {
    339         iter.into_iter().for_each(|(k, v)| {
    340             if let Some(enabled) = self.enabled.get_mut(&k) {
    341                 enabled.extend(v);
    342             } else {
    343                 self.enabled.insert(k, v);
    344             }
    345         })
    346     }
    347 
    348     pub fn mark_started(&mut self, name: UnitName) {
    349         self.started.insert(name);
    350     }
    351 
    352     // pub fn mark_stopped(&mut self, name: &UnitName) {
    353     //     self.started.remove(name);
    354     // }
    355     //
    356     // pub fn is_started(&self, name: &UnitName) -> bool {
    357     //     self.started.contains(name)
    358     // }
    359 }
    360 
    361 pub fn init_loader() -> Result<()> {
    362     Loader::initialize()?;
    363 
    364     let mut loader = Loader::obtain()?.borrow_mut();
    365 
    366     #[cfg(feature = "units")]
    367     {
    368         loader.extend_units(kanit_units::units().map(|u| {
    369             let name = u.borrow().name();
    370             (name, u)
    371         }));
    372 
    373         loader.extend_enabled(kanit_units::default_enabled());
    374 
    375         for (unit, _) in loader.units.iter() {
    376             trace!("has unit {unit}");
    377         }
    378 
    379         for (level, group) in loader.enabled.iter() {
    380             group
    381                 .iter()
    382                 .for_each(|unit| trace!("enabling unit {level}->{unit}"));
    383         }
    384 
    385         loader.rebuild(false)?;
    386     }
    387 
    388     Ok(())
    389 }