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 }