kanit

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

grouping.rs (7798B)


      1 // grouping
      2 
      3 // @<mtime>
      4 // :sysboot
      5 // procfs,modules,devfs
      6 // run,clock,sysfs
      7 // hostname,hwdrivers,mdev,rootfs
      8 // swap
      9 // localmount
     10 // syslog,seed
     11 // :boot
     12 // :default
     13 // getty@tty1
     14 
     15 use std::collections::HashMap;
     16 use std::fmt;
     17 use std::fmt::Formatter;
     18 use std::str::FromStr;
     19 
     20 use base64::Engine;
     21 use base64::prelude::BASE64_STANDARD;
     22 use nom::branch::alt;
     23 use nom::bytes::complete::is_a;
     24 use nom::character::complete::{alpha1, alphanumeric1, char, newline, not_line_ending};
     25 use nom::combinator::{all_consuming, map, map_res, opt, recognize};
     26 use nom::error::Error;
     27 use nom::multi::{many0, separated_list0, separated_list1};
     28 use nom::sequence::{delimited, pair, preceded, terminated};
     29 use nom::{Finish, IResult};
     30 
     31 use crate::UnitName;
     32 
     33 type Sha256Hash = [u8; 32];
     34 
     35 #[derive(Debug, PartialEq, Eq, Clone)]
     36 pub struct DependencyGrouping {
     37     pub hash: Sha256Hash,
     38     pub groups: HashMap<Box<str>, Vec<Vec<UnitName>>>,
     39 }
     40 
     41 impl fmt::Display for DependencyGrouping {
     42     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
     43         writeln!(f, "@{}", BASE64_STANDARD.encode(self.hash))?;
     44 
     45         for (name, levels) in self.groups.iter() {
     46             writeln!(f, ":{name}")?;
     47 
     48             for level in levels.iter() {
     49                 writeln!(f, "{}", level.join(","))?;
     50             }
     51         }
     52 
     53         Ok(())
     54     }
     55 }
     56 
     57 impl FromStr for DependencyGrouping {
     58     type Err = Error<String>;
     59 
     60     fn from_str(s: &str) -> Result<Self, Self::Err> {
     61         match grouping(s).finish() {
     62             Ok((_remaining, grouping)) => Ok(grouping),
     63             Err(e) => Err(Error {
     64                 input: e.input.to_string(),
     65                 code: e.code,
     66             }),
     67         }
     68     }
     69 }
     70 
     71 fn grouping(input: &str) -> IResult<&str, DependencyGrouping> {
     72     all_consuming(map(
     73         pair(header, separated_list1(newline, group)),
     74         |(hash, groups)| DependencyGrouping {
     75             hash,
     76             groups: groups.into_iter().collect(),
     77         },
     78     ))(input)
     79 }
     80 
     81 fn header(input: &str) -> IResult<&str, Sha256Hash> {
     82     delimited(char('@'), map_res(not_line_ending, hash), newline)(input)
     83 }
     84 
     85 fn hash(input: &str) -> Result<Sha256Hash, base64::DecodeSliceError> {
     86     let mut buff: Sha256Hash = [0; 32];
     87 
     88     BASE64_STANDARD.decode_slice(input, &mut buff)?;
     89 
     90     Ok(buff)
     91 }
     92 
     93 fn group(input: &str) -> IResult<&str, (Box<str>, Vec<Vec<UnitName>>)> {
     94     pair(
     95         terminated(group_name, opt(newline)),
     96         separated_list0(newline, level),
     97     )(input)
     98 }
     99 
    100 fn group_name(input: &str) -> IResult<&str, Box<str>> {
    101     preceded(
    102         char(':'),
    103         map(
    104             recognize(pair(alpha1, many0(alt((alphanumeric1, is_a("-_")))))),
    105             Box::from,
    106         ),
    107     )(input)
    108 }
    109 
    110 fn level(input: &str) -> IResult<&str, Vec<UnitName>> {
    111     separated_list1(char(','), unit_name)(input)
    112 }
    113 
    114 fn unit_name(input: &str) -> IResult<&str, UnitName> {
    115     map(
    116         recognize(pair(alpha1, many0(alt((alphanumeric1, is_a("-_.@")))))),
    117         UnitName::from,
    118     )(input)
    119 }
    120 
    121 #[cfg(test)]
    122 mod tests {
    123     use crate::parser_test;
    124 
    125     use super::*;
    126 
    127     // TODO; we should have negative tests
    128     #[test]
    129     fn parse_unit_name() -> Result<(), Error<&'static str>> {
    130         parser_test!(
    131             unit_name,
    132             [
    133                 "foo" => UnitName::from("foo"),
    134                 "foo2" => UnitName::from("foo2"),
    135                 "foo_bar" => UnitName::from("foo_bar"),
    136                 "foo-bar" => UnitName::from("foo-bar"),
    137                 "foo@bar" => UnitName::from("foo@bar")
    138             ]
    139         );
    140 
    141         Ok(())
    142     }
    143 
    144     #[test]
    145     fn parse_level() -> Result<(), Error<&'static str>> {
    146         parser_test!(
    147             level,
    148             [
    149                 "foo,bar,baz" => vec![
    150                     UnitName::from("foo"),
    151                     UnitName::from("bar"),
    152                     UnitName::from("baz"),
    153                 ],
    154                 "foo" => vec![
    155                     UnitName::from("foo")
    156                 ]
    157             ]
    158         );
    159 
    160         Ok(())
    161     }
    162 
    163     #[test]
    164     fn parse_group_name() -> Result<(), Error<&'static str>> {
    165         parser_test!(
    166             group_name,
    167             [
    168                 ":foo" => Box::from("foo"),
    169                 ":foo2" => Box::from("foo2"),
    170                 ":foo_bar" => Box::from("foo_bar"),
    171                 ":foo-bar" => Box::from("foo-bar")
    172             ]
    173         );
    174 
    175         Ok(())
    176     }
    177 
    178     #[test]
    179     fn parse_group() -> Result<(), Error<&'static str>> {
    180         parser_test!(
    181             group,
    182             [
    183                 ":foo" => (Box::from("foo"), vec![]),
    184                 ":foo\nbar,baz" => (
    185                     Box::from("foo"),
    186                     vec![vec![UnitName::from("bar"), UnitName::from("baz")]]
    187                 )
    188             ]
    189         );
    190 
    191         Ok(())
    192     }
    193 
    194     #[test]
    195     fn parse_header() -> Result<(), Error<&'static str>> {
    196         parser_test!(
    197             header,
    198             [
    199                 "@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n" => [0; 32],
    200                 "@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\n" => [1; 32]
    201             ]
    202         );
    203 
    204         Ok(())
    205     }
    206 
    207     #[test]
    208     fn parse_grouping() -> Result<(), Error<&'static str>> {
    209         parser_test!(
    210             grouping,
    211             [
    212                 "@AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n:sysboot\nprocfs,modules\nrun" => DependencyGrouping {
    213                     hash: [0; 32],
    214                     groups: HashMap::from([
    215                         (
    216                             Box::from("sysboot"),
    217                             vec![
    218                                 vec![UnitName::from("procfs"), UnitName::from("modules")],
    219                                 vec![UnitName::from("run")]
    220                             ]
    221                         )
    222                     ])
    223                 },
    224                 "@AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE=\n:sysboot\nprocfs,modules\nrun\n:default\ngetty@tty1" => DependencyGrouping {
    225                     hash: [1; 32],
    226                     groups: HashMap::from([
    227                         (
    228                             Box::from("sysboot"),
    229                             vec![
    230                                 vec![UnitName::from("procfs"), UnitName::from("modules")],
    231                                 vec![UnitName::from("run")]
    232                             ]
    233                         ),
    234                         (
    235                             Box::from("default"),
    236                             vec![
    237                                 vec![UnitName::from("getty@tty1")]
    238                             ]
    239                         )
    240                     ])
    241                 }
    242             ]
    243         );
    244 
    245         Ok(())
    246     }
    247 
    248     #[test]
    249     fn display_grouping() {
    250         let grouping = DependencyGrouping {
    251             hash: [2; 32],
    252             groups: HashMap::from([
    253                 (
    254                     Box::from("sysboot"),
    255                     vec![
    256                         vec![UnitName::from("procfs"), UnitName::from("modules")],
    257                         vec![UnitName::from("run")],
    258                     ],
    259                 ),
    260                 (
    261                     Box::from("default"),
    262                     vec![
    263                         vec![UnitName::from("syslog")],
    264                         vec![UnitName::from("getty@tty1")],
    265                     ],
    266                 ),
    267             ]),
    268         };
    269 
    270         // hashmap ordering leads to 2 possible orderings
    271         let output = grouping.to_string();
    272 
    273         match output.as_str() {
    274             "@AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI=\n:default\nsyslog\ngetty@tty1\n:sysboot\nprocfs,modules\nrun\n"
    275             | "@AgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI=\n:sysboot\nprocfs,modules\nrun\n:default\nsyslog\ngetty@tty1\n" =>
    276                 {}
    277             s => panic!("did not expect {s}"),
    278         }
    279     }
    280 }