1use thiserror::Error;
11
12#[derive(Debug, Error)]
13pub enum EarlyMountError {
14 #[error("mount({target}): {reason}")]
15 Mount { target: String, reason: String },
16}
17
18#[derive(Debug, Clone)]
20pub struct EarlyMount {
21 pub source: &'static str,
22 pub target: &'static str,
23 pub fstype: &'static str,
24 pub flags: u64,
25 pub data: &'static str,
26}
27
28pub const CANONICAL_MOUNTS: &[EarlyMount] = &[
30 EarlyMount {
31 source: "proc",
32 target: "/proc",
33 fstype: "proc",
34 flags: 0,
35 data: "",
36 },
37 EarlyMount {
38 source: "sysfs",
39 target: "/sys",
40 fstype: "sysfs",
41 flags: 0,
42 data: "",
43 },
44 EarlyMount {
45 source: "devtmpfs",
46 target: "/dev",
47 fstype: "devtmpfs",
48 flags: 0,
49 data: "mode=0755",
50 },
51 EarlyMount {
52 source: "tmpfs",
53 target: "/run",
54 fstype: "tmpfs",
55 flags: 0,
56 data: "mode=0755",
57 },
58 EarlyMount {
59 source: "tmpfs",
60 target: "/tmp",
61 fstype: "tmpfs",
62 flags: 0,
63 data: "mode=1777",
64 },
65];
66
67pub fn mount_early_filesystems() -> Vec<Result<EarlyMount, EarlyMountError>> {
71 CANONICAL_MOUNTS
72 .iter()
73 .map(|m| mount_one(m).map(|()| m.clone()))
74 .collect()
75}
76
77pub fn mount_extra(
88 source: &str,
89 target: &str,
90 fstype: &str,
91 options: Option<&str>,
92) -> Result<(), EarlyMountError> {
93 mount_extra_impl(source, target, fstype, options)
94}
95
96#[cfg(target_os = "linux")]
97fn mount_extra_impl(
98 source: &str,
99 target: &str,
100 fstype: &str,
101 options: Option<&str>,
102) -> Result<(), EarlyMountError> {
103 use std::ffi::CString;
104 let src = CString::new(source).map_err(|e| err(target, e))?;
105 let tgt = CString::new(target).map_err(|e| err(target, e))?;
106 let fst = CString::new(fstype).map_err(|e| err(target, e))?;
107 let opts_raw = options.unwrap_or("");
108 let opts = CString::new(opts_raw).map_err(|e| err(target, e))?;
109 let _ = std::fs::create_dir_all(target);
113 let r = unsafe {
114 libc::mount(
115 src.as_ptr(),
116 tgt.as_ptr(),
117 fst.as_ptr(),
118 0,
119 opts.as_ptr() as *const libc::c_void,
120 )
121 };
122 if r == 0 {
123 return Ok(());
124 }
125 let e = std::io::Error::last_os_error();
126 if e.raw_os_error() == Some(libc::EBUSY) {
127 return Ok(());
128 }
129 Err(EarlyMountError::Mount {
130 target: target.into(),
131 reason: e.to_string(),
132 })
133}
134
135#[cfg(not(target_os = "linux"))]
136fn mount_extra_impl(
137 _source: &str,
138 _target: &str,
139 _fstype: &str,
140 _options: Option<&str>,
141) -> Result<(), EarlyMountError> {
142 Ok(())
143}
144
145#[cfg(target_os = "linux")]
146fn mount_one(m: &EarlyMount) -> Result<(), EarlyMountError> {
147 use std::ffi::CString;
148 let source = CString::new(m.source).map_err(|e| err(m.target, e))?;
152 let target = CString::new(m.target).map_err(|e| err(m.target, e))?;
153 let fstype = CString::new(m.fstype).map_err(|e| err(m.target, e))?;
154 let data = CString::new(m.data).map_err(|e| err(m.target, e))?;
155 let _ = std::fs::create_dir_all(m.target);
157 let r = unsafe {
158 libc::mount(
159 source.as_ptr(),
160 target.as_ptr(),
161 fstype.as_ptr(),
162 m.flags,
163 data.as_ptr() as *const libc::c_void,
164 )
165 };
166 if r == 0 {
167 return Ok(());
168 }
169 let e = std::io::Error::last_os_error();
170 if e.raw_os_error() == Some(libc::EBUSY) {
171 return Ok(());
172 }
173 Err(EarlyMountError::Mount {
174 target: m.target.into(),
175 reason: e.to_string(),
176 })
177}
178
179#[cfg(not(target_os = "linux"))]
180fn mount_one(_m: &EarlyMount) -> Result<(), EarlyMountError> {
181 Ok(())
185}
186
187#[cfg(target_os = "linux")]
188fn err<E: std::fmt::Display>(target: &str, e: E) -> EarlyMountError {
189 EarlyMountError::Mount {
190 target: target.into(),
191 reason: e.to_string(),
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn canonical_set_is_in_boot_order() {
201 let targets: Vec<_> = CANONICAL_MOUNTS.iter().map(|m| m.target).collect();
202 assert_eq!(targets, ["/proc", "/sys", "/dev", "/run", "/tmp"]);
203 }
204
205 #[test]
206 fn every_canonical_mount_has_nonempty_fstype() {
207 for m in CANONICAL_MOUNTS {
208 assert!(!m.fstype.is_empty(), "{} has empty fstype", m.target);
209 assert!(m.target.starts_with('/'), "{} not absolute", m.target);
210 }
211 }
212
213 #[test]
214 fn mount_one_is_a_no_op_on_non_linux() {
215 for m in CANONICAL_MOUNTS {
217 #[cfg(not(target_os = "linux"))]
218 assert!(mount_one(m).is_ok());
219 #[cfg(target_os = "linux")]
220 {
221 let _ = m;
224 }
225 }
226 }
227}