use super::to_cstring;
use crate::{
io, libc, CString, FilesystemType, Mount, MountFlags, OsStrExt, Path, SupportedFilesystems,
Unmount, UnmountDrop, UnmountFlags,
};
use libc::mount;
use std::ptr;
#[derive(Clone, Copy, smart_default::SmartDefault)]
#[allow(clippy::module_name_repetitions)]
pub struct MountBuilder<'a> {
#[default(MountFlags::empty())]
flags: MountFlags,
fstype: Option<FilesystemType<'a>>,
#[cfg(feature = "loop")]
loopback_offset: u64,
#[cfg(feature = "loop")]
explicit_loopback: bool,
data: Option<&'a str>,
}
impl<'a> MountBuilder<'a> {
#[must_use]
pub fn data(mut self, data: &'a str) -> Self {
self.data = Some(data);
self
}
#[must_use]
pub fn fstype(mut self, fs: impl Into<FilesystemType<'a>>) -> Self {
self.fstype = Some(fs.into());
self
}
#[must_use]
pub fn flags(mut self, flags: MountFlags) -> Self {
self.flags = flags;
self
}
#[cfg(feature = "loop")]
#[must_use]
pub fn loopback_offset(mut self, offset: u64) -> Self {
self.loopback_offset = offset;
self
}
#[cfg(feature = "loop")]
#[must_use]
pub fn explicit_loopback(mut self) -> Self {
self.explicit_loopback = true;
self
}
pub fn mount(self, source: impl AsRef<Path>, target: impl AsRef<Path>) -> io::Result<Mount> {
let MountBuilder {
data,
fstype,
flags,
#[cfg(feature = "loop")]
loopback_offset,
#[cfg(feature = "loop")]
explicit_loopback,
} = self;
let supported;
let fstype = if let Some(fstype) = fstype {
fstype
} else {
supported = SupportedFilesystems::new()?;
FilesystemType::Auto(&supported)
};
let source = source.as_ref();
let mut c_source = None;
#[cfg(feature = "loop")]
let (mut flags, mut fstype, mut loopback, mut loop_path) = (flags, fstype, None, None);
if !source.as_os_str().is_empty() {
#[cfg(feature = "loop")]
let mut create_loopback = |flags: &MountFlags| -> io::Result<loopdev::LoopDevice> {
let new_loopback = loopdev::LoopControl::open()?.next_free()?;
new_loopback
.with()
.read_only(flags.contains(MountFlags::RDONLY))
.offset(loopback_offset)
.attach(source)?;
let path = new_loopback.path().expect("loopback does not have path");
c_source = Some(to_cstring(path.as_os_str().as_bytes())?);
loop_path = Some(path);
Ok(new_loopback)
};
#[cfg(feature = "loop")]
if let Some(ext) = source.extension() {
let extf = i32::from(ext == "iso") | if ext == "squashfs" { 2 } else { 0 };
if extf != 0 {
fstype = if extf == 1 {
flags |= MountFlags::RDONLY;
FilesystemType::Manual("iso9660")
} else {
flags |= MountFlags::RDONLY;
FilesystemType::Manual("squashfs")
};
loopback = Some(create_loopback(&flags)?);
}
}
#[cfg(feature = "loop")]
if loopback.is_none() && explicit_loopback {
loopback = Some(create_loopback(&flags)?);
}
if c_source.is_none() {
c_source = Some(to_cstring(source.as_os_str().as_bytes())?);
}
};
let c_target = to_cstring(target.as_ref().as_os_str().as_bytes())?;
let data = match data.map(|o| to_cstring(o.as_bytes())) {
Some(Ok(string)) => Some(string),
Some(Err(why)) => return Err(why),
None => None,
};
let mut mount_data = MountData {
c_source,
c_target,
flags,
data,
};
let mut res = match fstype {
FilesystemType::Auto(supported) => mount_data.automount(supported.dev_file_systems()),
FilesystemType::Set(set) => mount_data.automount(set.iter().copied()),
FilesystemType::Manual(fstype) => mount_data.mount(fstype),
};
match res {
Ok(ref mut _mount) => {
#[cfg(feature = "loop")]
{
_mount.loopback = loopback;
_mount.loop_path = loop_path;
}
}
Err(_) =>
{
#[cfg(feature = "loop")]
if let Some(loopback) = loopback {
let _res = loopback.detach();
}
}
}
res
}
pub fn mount_autodrop(
self,
source: impl AsRef<Path>,
target: impl AsRef<Path>,
unmount_flags: UnmountFlags,
) -> io::Result<UnmountDrop<Mount>> {
self.mount(source, target)
.map(|m| m.into_unmount_drop(unmount_flags))
}
}
struct MountData {
c_source: Option<CString>,
c_target: CString,
flags: MountFlags,
data: Option<CString>,
}
impl MountData {
fn mount(&mut self, fstype: &str) -> io::Result<Mount> {
let c_fstype = to_cstring(fstype.as_bytes())?;
match mount_(
self.c_source.as_ref(),
&self.c_target,
&c_fstype,
self.flags,
self.data.as_ref(),
) {
Ok(()) => Ok(Mount::from_target_and_fstype(
self.c_target.clone(),
fstype.to_owned(),
)),
Err(why) => Err(why),
}
}
fn automount<'a, I: Iterator<Item = &'a str> + 'a>(mut self, iter: I) -> io::Result<Mount> {
let mut res = Ok(());
for fstype in iter {
match self.mount(fstype) {
mount @ Ok(_) => return mount,
Err(why) => res = Err(why),
}
}
match res {
Ok(()) => Err(io::Error::new(
io::ErrorKind::NotFound,
"no supported file systems found",
)),
Err(why) => Err(why),
}
}
}
fn mount_(
c_source: Option<&CString>,
c_target: &CString,
c_fstype: &CString,
flags: MountFlags,
c_data: Option<&CString>,
) -> io::Result<()> {
let result = unsafe {
mount(
c_source.map_or_else(ptr::null, |s| s.as_ptr()),
c_target.as_ptr(),
c_fstype.as_ptr(),
flags.bits(),
c_data
.map_or_else(ptr::null, |s| s.as_ptr())
.cast::<libc::c_void>(),
)
};
match result {
0 => Ok(()),
_err => Err(io::Error::last_os_error()),
}
}