use thiserror::Error;
#[derive(Debug, Error)]
pub enum EarlyMountError {
#[error("mount({target}): {reason}")]
Mount { target: String, reason: String },
}
#[derive(Debug, Clone)]
pub struct EarlyMount {
pub source: &'static str,
pub target: &'static str,
pub fstype: &'static str,
pub flags: u64,
pub data: &'static str,
}
pub const CANONICAL_MOUNTS: &[EarlyMount] = &[
EarlyMount {
source: "proc",
target: "/proc",
fstype: "proc",
flags: 0,
data: "",
},
EarlyMount {
source: "sysfs",
target: "/sys",
fstype: "sysfs",
flags: 0,
data: "",
},
EarlyMount {
source: "devtmpfs",
target: "/dev",
fstype: "devtmpfs",
flags: 0,
data: "mode=0755",
},
EarlyMount {
source: "tmpfs",
target: "/run",
fstype: "tmpfs",
flags: 0,
data: "mode=0755",
},
EarlyMount {
source: "tmpfs",
target: "/tmp",
fstype: "tmpfs",
flags: 0,
data: "mode=1777",
},
];
pub fn mount_early_filesystems() -> Vec<Result<EarlyMount, EarlyMountError>> {
CANONICAL_MOUNTS
.iter()
.map(|m| mount_one(m).map(|()| m.clone()))
.collect()
}
pub fn mount_extra(
source: &str,
target: &str,
fstype: &str,
options: Option<&str>,
) -> Result<(), EarlyMountError> {
mount_extra_impl(source, target, fstype, options)
}
#[cfg(target_os = "linux")]
fn mount_extra_impl(
source: &str,
target: &str,
fstype: &str,
options: Option<&str>,
) -> Result<(), EarlyMountError> {
use std::ffi::CString;
let src = CString::new(source).map_err(|e| err(target, e))?;
let tgt = CString::new(target).map_err(|e| err(target, e))?;
let fst = CString::new(fstype).map_err(|e| err(target, e))?;
let opts_raw = options.unwrap_or("");
let opts = CString::new(opts_raw).map_err(|e| err(target, e))?;
let _ = std::fs::create_dir_all(target);
let r = unsafe {
libc::mount(
src.as_ptr(),
tgt.as_ptr(),
fst.as_ptr(),
0,
opts.as_ptr() as *const libc::c_void,
)
};
if r == 0 {
return Ok(());
}
let e = std::io::Error::last_os_error();
if e.raw_os_error() == Some(libc::EBUSY) {
return Ok(());
}
Err(EarlyMountError::Mount {
target: target.into(),
reason: e.to_string(),
})
}
#[cfg(not(target_os = "linux"))]
fn mount_extra_impl(
_source: &str,
_target: &str,
_fstype: &str,
_options: Option<&str>,
) -> Result<(), EarlyMountError> {
Ok(())
}
#[cfg(target_os = "linux")]
fn mount_one(m: &EarlyMount) -> Result<(), EarlyMountError> {
use std::ffi::CString;
let source = CString::new(m.source).map_err(|e| err(m.target, e))?;
let target = CString::new(m.target).map_err(|e| err(m.target, e))?;
let fstype = CString::new(m.fstype).map_err(|e| err(m.target, e))?;
let data = CString::new(m.data).map_err(|e| err(m.target, e))?;
let _ = std::fs::create_dir_all(m.target);
let r = unsafe {
libc::mount(
source.as_ptr(),
target.as_ptr(),
fstype.as_ptr(),
m.flags,
data.as_ptr() as *const libc::c_void,
)
};
if r == 0 {
return Ok(());
}
let e = std::io::Error::last_os_error();
if e.raw_os_error() == Some(libc::EBUSY) {
return Ok(());
}
Err(EarlyMountError::Mount {
target: m.target.into(),
reason: e.to_string(),
})
}
#[cfg(not(target_os = "linux"))]
fn mount_one(_m: &EarlyMount) -> Result<(), EarlyMountError> {
Ok(())
}
#[cfg(target_os = "linux")]
fn err<E: std::fmt::Display>(target: &str, e: E) -> EarlyMountError {
EarlyMountError::Mount {
target: target.into(),
reason: e.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn canonical_set_is_in_boot_order() {
let targets: Vec<_> = CANONICAL_MOUNTS.iter().map(|m| m.target).collect();
assert_eq!(targets, ["/proc", "/sys", "/dev", "/run", "/tmp"]);
}
#[test]
fn every_canonical_mount_has_nonempty_fstype() {
for m in CANONICAL_MOUNTS {
assert!(!m.fstype.is_empty(), "{} has empty fstype", m.target);
assert!(m.target.starts_with('/'), "{} not absolute", m.target);
}
}
#[test]
fn mount_one_is_a_no_op_on_non_linux() {
for m in CANONICAL_MOUNTS {
#[cfg(not(target_os = "linux"))]
assert!(mount_one(m).is_ok());
#[cfg(target_os = "linux")]
{
let _ = m;
}
}
}
}