kanit

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

mounts.rs (4316B)


      1 use std::collections::HashMap;
      2 use std::path::Path;
      3 
      4 use async_process::Command;
      5 
      6 use kanit_common::error::{Context, Result};
      7 
      8 pub async fn is_fs_available(fs: &str) -> Result<bool> {
      9     let filesystems = async_fs::read_to_string("/proc/filesystems")
     10         .await
     11         .context("failed to read filesystems")?;
     12 
     13     // prepend tab as the format is `nodev <fs>` or `   <fs>`
     14     // TODO; maybe something a bit more elegant
     15     Ok(filesystems.contains(&format!("\t{fs}")))
     16 }
     17 
     18 pub async fn is_fs_mounted<P: AsRef<Path>>(path: P) -> Result<bool> {
     19     let mounted = async_fs::read_to_string("/proc/mounts")
     20         .await
     21         .context("failed to read mounts")?;
     22 
     23     let path = path.as_ref().to_string_lossy();
     24 
     25     Ok(parse_mounts(&mounted)?.iter().any(|m| m.fs_file == path))
     26 }
     27 
     28 pub async fn try_mount_from_fstab<P: AsRef<Path>>(path: P) -> Result<bool> {
     29     try_mount_from_fstab_action(path, MountAction::Mount).await
     30 }
     31 
     32 pub async fn try_mount_from_fstab_action<P: AsRef<Path>>(
     33     path: P,
     34     action: MountAction,
     35 ) -> Result<bool> {
     36     let fstab = async_fs::read_to_string("/etc/fstab")
     37         .await
     38         .context("failed to read fstab")?;
     39 
     40     let path = path.as_ref().to_string_lossy();
     41 
     42     if let Some(entry) = parse_mounts(&fstab)?.iter().find(|m| m.fs_file == path) {
     43         Ok(entry.mount(action).await?)
     44     } else {
     45         Ok(false)
     46     }
     47 }
     48 
     49 pub struct MountEntry<'a> {
     50     pub fs_spec: &'a str,
     51     pub fs_file: &'a str,
     52     pub fs_vfstype: &'a str,
     53     pub fs_mntopts: HashMap<&'a str, Option<&'a str>>,
     54     // used by dump(8) and fsck(8)
     55     pub _fs_freq: u8,
     56     pub _fs_passno: u8,
     57 }
     58 
     59 pub enum MountAction {
     60     Mount,
     61     Remount,
     62 }
     63 
     64 impl<'a> MountEntry<'a> {
     65     pub fn parse_single_mount(line: &'a str) -> Result<Self> {
     66         let mut parts = line.split_whitespace();
     67 
     68         let fs_spec = parts.next().context("expected `fs_spec`")?;
     69 
     70         let fs_file = parts.next().context("expected `fs_file`")?;
     71 
     72         let fs_vfstype = parts.next().context("expected `fs_vfstype`")?;
     73 
     74         let fs_mntopts = parts.next().context("expected `fs_mntopts`")?;
     75 
     76         let fs_mntopts: HashMap<&str, Option<&str>> = fs_mntopts
     77             .split(',')
     78             .map(|s| {
     79                 let mut split = s.splitn(2, '=');
     80                 // unwrap: split will always have at least 1
     81                 let opt = split.next().unwrap();
     82                 let val = split.next();
     83 
     84                 (opt, val)
     85             })
     86             .collect();
     87 
     88         let fs_freq = parts
     89             .next()
     90             .context("expected `fs_freq`")?
     91             .parse::<u8>()
     92             .context("failed to parse `fs_freq`")?;
     93 
     94         let fs_passno = parts
     95             .next()
     96             .context("expected `fs_passno`")?
     97             .parse::<u8>()
     98             .context("failed to parse `fs_passno`")?;
     99 
    100         Ok(Self {
    101             fs_spec,
    102             fs_file,
    103             fs_vfstype,
    104             fs_mntopts,
    105             _fs_freq: fs_freq,
    106             _fs_passno: fs_passno,
    107         })
    108     }
    109 
    110     pub async fn mount(&self, action: MountAction) -> Result<bool> {
    111         let mut opts = self
    112             .fs_mntopts
    113             .iter()
    114             .map(|(k, v)| {
    115                 if let Some(v) = v {
    116                     format!("{k}={v}")
    117                 } else {
    118                     k.to_string()
    119                 }
    120             })
    121             .collect::<Vec<_>>()
    122             .join(",");
    123 
    124         if let MountAction::Remount = action {
    125             if opts.is_empty() {
    126                 opts.push_str("remount");
    127             } else {
    128                 opts.push_str(",remount");
    129             }
    130         }
    131 
    132         Ok(Command::new("mount")
    133             .arg("-o")
    134             .arg(opts)
    135             .arg("-t")
    136             .arg(self.fs_vfstype)
    137             .arg(self.fs_spec)
    138             .arg(self.fs_file)
    139             .spawn()
    140             .context("failed to start mount")?
    141             .status()
    142             .await
    143             .context("failed to wait on mount")?
    144             .success())
    145     }
    146 }
    147 
    148 pub fn parse_mounts(lines: &str) -> Result<Vec<MountEntry>> {
    149     lines
    150         .lines()
    151         .filter_map(|line| {
    152             if line.starts_with('#') || line.is_empty() {
    153                 return None;
    154             }
    155             Some(MountEntry::parse_single_mount(line))
    156         })
    157         .collect()
    158 }