1use crate::types::{MountError, MountFlags};
4use std::path::{Path, PathBuf};
5
6pub trait MountInspector {
7 fn flags_for(&self, path: &Path) -> Result<MountFlags, MountError>;
13}
14
15#[derive(Debug, Copy, Clone)]
17pub struct ProcStatfsInspector;
18
19impl ProcStatfsInspector {
20 fn flags_via_statvfs(path: &Path) -> Result<MountFlags, MountError> {
21 match rustix::fs::statvfs(path) {
23 Ok(vfs) => {
24 let flags = vfs.f_flag;
25 let read_only = flags.contains(rustix::fs::StatVfsMountFlags::RDONLY);
26 let no_exec = flags.contains(rustix::fs::StatVfsMountFlags::NOEXEC);
27 Ok(MountFlags { read_only, no_exec })
28 }
29 Err(_) => Err(MountError::Unknown),
30 }
31 }
32 fn parse_proc_mounts(path: &Path) -> Result<MountFlags, MountError> {
33 let p = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
35 let content =
36 std::fs::read_to_string("/proc/self/mounts").map_err(|_| MountError::Unknown)?;
37 let mut best: Option<(PathBuf, String)> = None;
38 for line in content.lines() {
39 let parts: Vec<&str> = line.split_whitespace().collect();
40 if parts.len() < 4 {
41 continue;
42 }
43 let mnt = parts.get(1).map(PathBuf::from).ok_or(MountError::Unknown)?;
44 if p.starts_with(&mnt) {
45 let opts = parts
46 .get(3)
47 .ok_or(MountError::Unknown)?
48 .to_ascii_lowercase();
49 match &best {
50 None => best = Some((mnt, opts)),
51 Some((b, _)) => {
52 if mnt.as_os_str().len() > b.as_os_str().len() {
53 best = Some((mnt, opts));
54 }
55 }
56 }
57 }
58 }
59 if let Some((_mnt, opts)) = best {
60 let has_rw = opts.split(',').any(|o| o == "rw");
61 let noexec = opts.split(',').any(|o| o == "noexec");
62 Ok(MountFlags {
63 read_only: !has_rw,
64 no_exec: noexec,
65 })
66 } else {
67 Err(MountError::Unknown)
68 }
69 }
70}
71
72impl MountInspector for ProcStatfsInspector {
73 fn flags_for(&self, path: &Path) -> Result<MountFlags, MountError> {
74 Self::flags_via_statvfs(path).or_else(|_| Self::parse_proc_mounts(path))
76 }
77}
78
79pub fn ensure_rw_exec(inspector: &impl MountInspector, path: &Path) -> Result<(), MountError> {
85 match inspector.flags_for(path) {
86 Ok(flags) => {
87 if flags.read_only || flags.no_exec {
88 return Err(MountError::Unknown);
89 }
90 Ok(())
91 }
92 Err(e) => Err(e),
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 struct MockInspector {
101 flags: Result<MountFlags, MountError>,
102 }
103 impl MountInspector for MockInspector {
104 fn flags_for(&self, _path: &Path) -> Result<MountFlags, MountError> {
105 self.flags
106 }
107 }
108
109 #[test]
110 fn ensure_rw_exec_passes_on_rw_exec() {
111 let ins = MockInspector {
112 flags: Ok(MountFlags {
113 read_only: false,
114 no_exec: false,
115 }),
116 };
117 assert!(ensure_rw_exec(&ins, Path::new("/tmp")).is_ok());
118 }
119
120 #[test]
121 fn ensure_rw_exec_fails_on_ro_or_noexec() {
122 let ins1 = MockInspector {
123 flags: Ok(MountFlags {
124 read_only: true,
125 no_exec: false,
126 }),
127 };
128 assert!(ensure_rw_exec(&ins1, Path::new("/tmp")).is_err());
129 let ins2 = MockInspector {
130 flags: Ok(MountFlags {
131 read_only: false,
132 no_exec: true,
133 }),
134 };
135 assert!(ensure_rw_exec(&ins2, Path::new("/tmp")).is_err());
136 }
137
138 #[test]
139 fn ensure_rw_exec_fails_on_ambiguous() {
140 let ins = MockInspector {
141 flags: Err(MountError::Unknown),
142 };
143 assert!(ensure_rw_exec(&ins, Path::new("/tmp")).is_err());
144 }
145}