| #[cfg(target_os = "freebsd")] |
| use crate::Error; |
| use crate::{Errno, NixPath, Result}; |
| use libc::c_int; |
| #[cfg(target_os = "freebsd")] |
| use libc::{c_char, c_uint, c_void}; |
| #[cfg(target_os = "freebsd")] |
| use std::{ |
| borrow::Cow, |
| ffi::{CStr, CString}, |
| fmt, io, |
| marker::PhantomData, |
| }; |
| |
| libc_bitflags!( |
| /// Used with [`Nmount::nmount`]. |
| pub struct MntFlags: c_int { |
| /// ACL support enabled. |
| #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] |
| MNT_ACLS; |
| /// All I/O to the file system should be done asynchronously. |
| MNT_ASYNC; |
| /// dir should instead be a file system ID encoded as “FSID:val0:val1”. |
| #[cfg(target_os = "freebsd")] |
| MNT_BYFSID; |
| /// Force a read-write mount even if the file system appears to be |
| /// unclean. |
| MNT_FORCE; |
| /// GEOM journal support enabled. |
| #[cfg(target_os = "freebsd")] |
| MNT_GJOURNAL; |
| /// MAC support for objects. |
| #[cfg(any(apple_targets, target_os = "freebsd"))] |
| MNT_MULTILABEL; |
| /// Disable read clustering. |
| #[cfg(freebsdlike)] |
| MNT_NOCLUSTERR; |
| /// Disable write clustering. |
| #[cfg(freebsdlike)] |
| MNT_NOCLUSTERW; |
| /// Enable NFS version 4 ACLs. |
| #[cfg(target_os = "freebsd")] |
| MNT_NFS4ACLS; |
| /// Do not update access times. |
| MNT_NOATIME; |
| /// Disallow program execution. |
| MNT_NOEXEC; |
| /// Do not honor setuid or setgid bits on files when executing them. |
| MNT_NOSUID; |
| /// Do not follow symlinks. |
| #[cfg(freebsdlike)] |
| MNT_NOSYMFOLLOW; |
| /// Mount read-only. |
| MNT_RDONLY; |
| /// Causes the vfs subsystem to update its data structures pertaining to |
| /// the specified already mounted file system. |
| MNT_RELOAD; |
| /// Create a snapshot of the file system. |
| /// |
| /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) |
| #[cfg(any(apple_targets, target_os = "freebsd"))] |
| MNT_SNAPSHOT; |
| /// Using soft updates. |
| #[cfg(any(freebsdlike, netbsdlike))] |
| MNT_SOFTDEP; |
| /// Directories with the SUID bit set chown new files to their own |
| /// owner. |
| #[cfg(freebsdlike)] |
| MNT_SUIDDIR; |
| /// All I/O to the file system should be done synchronously. |
| MNT_SYNCHRONOUS; |
| /// Union with underlying fs. |
| #[cfg(any( |
| apple_targets, |
| target_os = "freebsd", |
| target_os = "netbsd" |
| ))] |
| MNT_UNION; |
| /// Indicates that the mount command is being applied to an already |
| /// mounted file system. |
| MNT_UPDATE; |
| /// Check vnode use counts. |
| #[cfg(target_os = "freebsd")] |
| MNT_NONBUSY; |
| } |
| ); |
| |
| /// The Error type of [`Nmount::nmount`]. |
| /// |
| /// It wraps an [`Errno`], but also may contain an additional message returned |
| /// by `nmount(2)`. |
| #[cfg(target_os = "freebsd")] |
| #[derive(Debug)] |
| pub struct NmountError { |
| errno: Error, |
| errmsg: Option<String>, |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| impl NmountError { |
| /// Returns the additional error string sometimes generated by `nmount(2)`. |
| pub fn errmsg(&self) -> Option<&str> { |
| self.errmsg.as_deref() |
| } |
| |
| /// Returns the inner [`Error`] |
| pub const fn error(&self) -> Error { |
| self.errno |
| } |
| |
| fn new(error: Error, errmsg: Option<&CStr>) -> Self { |
| Self { |
| errno: error, |
| errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned), |
| } |
| } |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| impl std::error::Error for NmountError {} |
| |
| #[cfg(target_os = "freebsd")] |
| impl fmt::Display for NmountError { |
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
| if let Some(errmsg) = &self.errmsg { |
| write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc()) |
| } else { |
| write!(f, "{:?}: {}", self.errno, self.errno.desc()) |
| } |
| } |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| impl From<NmountError> for io::Error { |
| fn from(err: NmountError) -> Self { |
| err.errno.into() |
| } |
| } |
| |
| /// Result type of [`Nmount::nmount`]. |
| #[cfg(target_os = "freebsd")] |
| pub type NmountResult = std::result::Result<(), NmountError>; |
| |
| /// Mount a FreeBSD file system. |
| /// |
| /// The `nmount(2)` system call works similarly to the `mount(8)` program; it |
| /// takes its options as a series of name-value pairs. Most of the values are |
| /// strings, as are all of the names. The `Nmount` structure builds up an |
| /// argument list and then executes the syscall. |
| /// |
| /// # Examples |
| /// |
| /// To mount `target` onto `mountpoint` with `nullfs`: |
| /// ``` |
| /// # use nix::unistd::Uid; |
| /// # use ::sysctl::{CtlValue, Sysctl}; |
| /// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); |
| /// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() { |
| /// # return; |
| /// # }; |
| /// use nix::mount::{MntFlags, Nmount, unmount}; |
| /// use std::ffi::CString; |
| /// use tempfile::tempdir; |
| /// |
| /// let mountpoint = tempdir().unwrap(); |
| /// let target = tempdir().unwrap(); |
| /// |
| /// let fstype = CString::new("fstype").unwrap(); |
| /// let nullfs = CString::new("nullfs").unwrap(); |
| /// Nmount::new() |
| /// .str_opt(&fstype, &nullfs) |
| /// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) |
| /// .str_opt_owned("target", target.path().to_str().unwrap()) |
| /// .nmount(MntFlags::empty()).unwrap(); |
| /// |
| /// unmount(mountpoint.path(), MntFlags::empty()).unwrap(); |
| /// ``` |
| /// |
| /// # See Also |
| /// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount) |
| /// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs) |
| #[cfg(target_os = "freebsd")] |
| #[derive(Debug, Default)] |
| pub struct Nmount<'a> { |
| // n.b. notgull: In reality, this is a list that contains |
| // both mutable and immutable pointers. |
| // Be careful using this. |
| iov: Vec<libc::iovec>, |
| is_owned: Vec<bool>, |
| marker: PhantomData<&'a ()>, |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| impl<'a> Nmount<'a> { |
| /// Helper function to push a slice onto the `iov` array. |
| fn push_slice(&mut self, val: &'a [u8], is_owned: bool) { |
| self.iov.push(libc::iovec { |
| iov_base: val.as_ptr().cast_mut().cast(), |
| iov_len: val.len(), |
| }); |
| self.is_owned.push(is_owned); |
| } |
| |
| /// Helper function to push a pointer and its length onto the `iov` array. |
| fn push_pointer_and_length( |
| &mut self, |
| val: *const u8, |
| len: usize, |
| is_owned: bool, |
| ) { |
| self.iov.push(libc::iovec { |
| iov_base: val as *mut _, |
| iov_len: len, |
| }); |
| self.is_owned.push(is_owned); |
| } |
| |
| /// Helper function to push a `nix` path as owned. |
| fn push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P) { |
| val.with_nix_path(|s| { |
| let len = s.to_bytes_with_nul().len(); |
| let ptr = s.to_owned().into_raw() as *const u8; |
| |
| self.push_pointer_and_length(ptr, len, true); |
| }) |
| .unwrap(); |
| } |
| |
| /// Add an opaque mount option. |
| /// |
| /// Some file systems take binary-valued mount options. They can be set |
| /// with this method. |
| /// |
| /// # Safety |
| /// |
| /// Unsafe because it will cause `Nmount::nmount` to dereference a raw |
| /// pointer. The user is responsible for ensuring that `val` is valid and |
| /// its lifetime outlives `self`! An easy way to do that is to give the |
| /// value a larger scope than `name` |
| /// |
| /// # Examples |
| /// ``` |
| /// use libc::c_void; |
| /// use nix::mount::Nmount; |
| /// use std::ffi::CString; |
| /// use std::mem; |
| /// |
| /// // Note that flags outlives name |
| /// let mut flags: u32 = 0xdeadbeef; |
| /// let name = CString::new("flags").unwrap(); |
| /// let p = &mut flags as *mut u32 as *mut c_void; |
| /// let len = mem::size_of_val(&flags); |
| /// let mut nmount = Nmount::new(); |
| /// unsafe { nmount.mut_ptr_opt(&name, p, len) }; |
| /// ``` |
| pub unsafe fn mut_ptr_opt( |
| &mut self, |
| name: &'a CStr, |
| val: *mut c_void, |
| len: usize, |
| ) -> &mut Self { |
| self.push_slice(name.to_bytes_with_nul(), false); |
| self.push_pointer_and_length(val.cast(), len, false); |
| self |
| } |
| |
| /// Add a mount option that does not take a value. |
| /// |
| /// # Examples |
| /// ``` |
| /// use nix::mount::Nmount; |
| /// use std::ffi::CString; |
| /// |
| /// let read_only = CString::new("ro").unwrap(); |
| /// Nmount::new() |
| /// .null_opt(&read_only); |
| /// ``` |
| pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self { |
| self.push_slice(name.to_bytes_with_nul(), false); |
| self.push_slice(&[], false); |
| self |
| } |
| |
| /// Add a mount option that does not take a value, but whose name must be |
| /// owned. |
| /// |
| /// |
| /// This has higher runtime cost than [`Nmount::null_opt`], but is useful |
| /// when the name's lifetime doesn't outlive the `Nmount`, or it's a |
| /// different string type than `CStr`. |
| /// |
| /// # Examples |
| /// ``` |
| /// use nix::mount::Nmount; |
| /// |
| /// let read_only = "ro"; |
| /// let mut nmount: Nmount<'static> = Nmount::new(); |
| /// nmount.null_opt_owned(read_only); |
| /// ``` |
| pub fn null_opt_owned<P: ?Sized + NixPath>( |
| &mut self, |
| name: &P, |
| ) -> &mut Self { |
| self.push_nix_path(name); |
| self.push_slice(&[], false); |
| self |
| } |
| |
| /// Add a mount option as a [`CStr`]. |
| /// |
| /// # Examples |
| /// ``` |
| /// use nix::mount::Nmount; |
| /// use std::ffi::CString; |
| /// |
| /// let fstype = CString::new("fstype").unwrap(); |
| /// let nullfs = CString::new("nullfs").unwrap(); |
| /// Nmount::new() |
| /// .str_opt(&fstype, &nullfs); |
| /// ``` |
| pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self { |
| self.push_slice(name.to_bytes_with_nul(), false); |
| self.push_slice(val.to_bytes_with_nul(), false); |
| self |
| } |
| |
| /// Add a mount option as an owned string. |
| /// |
| /// This has higher runtime cost than [`Nmount::str_opt`], but is useful |
| /// when the value's lifetime doesn't outlive the `Nmount`, or it's a |
| /// different string type than `CStr`. |
| /// |
| /// # Examples |
| /// ``` |
| /// use nix::mount::Nmount; |
| /// use std::path::Path; |
| /// |
| /// let mountpoint = Path::new("/mnt"); |
| /// Nmount::new() |
| /// .str_opt_owned("fspath", mountpoint.to_str().unwrap()); |
| /// ``` |
| pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self |
| where |
| P1: ?Sized + NixPath, |
| P2: ?Sized + NixPath, |
| { |
| self.push_nix_path(name); |
| self.push_nix_path(val); |
| self |
| } |
| |
| /// Create a new `Nmount` struct with no options |
| pub fn new() -> Self { |
| Self::default() |
| } |
| |
| /// Actually mount the file system. |
| pub fn nmount(&mut self, flags: MntFlags) -> NmountResult { |
| const ERRMSG_NAME: &[u8] = b"errmsg\0"; |
| let mut errmsg = vec![0u8; 255]; |
| |
| // nmount can return extra error information via a "errmsg" return |
| // argument. |
| self.push_slice(ERRMSG_NAME, false); |
| |
| // SAFETY: we are pushing a mutable iovec here, so we can't use |
| // the above method |
| self.iov.push(libc::iovec { |
| iov_base: errmsg.as_mut_ptr().cast(), |
| iov_len: errmsg.len(), |
| }); |
| |
| let niov = self.iov.len() as c_uint; |
| let iovp = self.iov.as_mut_ptr(); |
| let res = unsafe { libc::nmount(iovp, niov, flags.bits()) }; |
| match Errno::result(res) { |
| Ok(_) => Ok(()), |
| Err(error) => { |
| let errmsg = if errmsg[0] == 0 { |
| None |
| } else { |
| CStr::from_bytes_until_nul(&errmsg[..]).ok() |
| }; |
| Err(NmountError::new(error, errmsg)) |
| } |
| } |
| } |
| } |
| |
| #[cfg(target_os = "freebsd")] |
| impl<'a> Drop for Nmount<'a> { |
| fn drop(&mut self) { |
| for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) { |
| if *is_owned { |
| // Free the owned string. Safe because we recorded ownership, |
| // and Nmount does not implement Clone. |
| unsafe { |
| drop(CString::from_raw(iov.iov_base as *mut c_char)); |
| } |
| } |
| } |
| } |
| } |
| |
| /// Unmount the file system mounted at `mountpoint`. |
| /// |
| /// Useful flags include |
| /// * `MNT_FORCE` - Unmount even if still in use. |
| #[cfg_attr( |
| target_os = "freebsd", |
| doc = " |
| * `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID |
| encoded as `FSID:val0:val1`, where `val0` and `val1` |
| are the contents of the `fsid_t val[]` array in decimal. |
| The file system that has the specified file system ID |
| will be unmounted. See |
| [`statfs`](crate::sys::statfs::statfs) to determine the |
| `fsid`. |
| " |
| )] |
| pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()> |
| where |
| P: ?Sized + NixPath, |
| { |
| let res = mountpoint.with_nix_path(|cstr| unsafe { |
| libc::unmount(cstr.as_ptr(), flags.bits()) |
| })?; |
| |
| Errno::result(res).map(drop) |
| } |