1#[cfg(target_os = "freebsd")] 2use crate::Error; 3use crate::{Errno, NixPath, Result}; 4use libc::c_int; 5#[cfg(target_os = "freebsd")] 6use libc::{c_char, c_uint, c_void}; 7#[cfg(target_os = "freebsd")] 8use std::{ 9 borrow::Cow, 10 ffi::{CStr, CString}, 11 fmt, io, 12 marker::PhantomData, 13}; 14 15libc_bitflags!( 16 /// Used with [`Nmount::nmount`]. 17 pub struct MntFlags: c_int { 18 /// ACL support enabled. 19 #[cfg(any(target_os = "netbsd", target_os = "freebsd"))] 20 #[cfg_attr(docsrs, doc(cfg(all())))] 21 MNT_ACLS; 22 /// All I/O to the file system should be done asynchronously. 23 MNT_ASYNC; 24 /// dir should instead be a file system ID encoded as “FSID:val0:val1”. 25 #[cfg(target_os = "freebsd")] 26 #[cfg_attr(docsrs, doc(cfg(all())))] 27 MNT_BYFSID; 28 /// Force a read-write mount even if the file system appears to be 29 /// unclean. 30 MNT_FORCE; 31 /// GEOM journal support enabled. 32 #[cfg(target_os = "freebsd")] 33 #[cfg_attr(docsrs, doc(cfg(all())))] 34 MNT_GJOURNAL; 35 /// MAC support for objects. 36 #[cfg(any(target_os = "macos", target_os = "freebsd"))] 37 #[cfg_attr(docsrs, doc(cfg(all())))] 38 MNT_MULTILABEL; 39 /// Disable read clustering. 40 #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 41 #[cfg_attr(docsrs, doc(cfg(all())))] 42 MNT_NOCLUSTERR; 43 /// Disable write clustering. 44 #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 45 #[cfg_attr(docsrs, doc(cfg(all())))] 46 MNT_NOCLUSTERW; 47 /// Enable NFS version 4 ACLs. 48 #[cfg(target_os = "freebsd")] 49 #[cfg_attr(docsrs, doc(cfg(all())))] 50 MNT_NFS4ACLS; 51 /// Do not update access times. 52 MNT_NOATIME; 53 /// Disallow program execution. 54 MNT_NOEXEC; 55 /// Do not honor setuid or setgid bits on files when executing them. 56 MNT_NOSUID; 57 /// Do not follow symlinks. 58 #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 59 #[cfg_attr(docsrs, doc(cfg(all())))] 60 MNT_NOSYMFOLLOW; 61 /// Mount read-only. 62 MNT_RDONLY; 63 /// Causes the vfs subsystem to update its data structures pertaining to 64 /// the specified already mounted file system. 65 MNT_RELOAD; 66 /// Create a snapshot of the file system. 67 /// 68 /// See [mksnap_ffs(8)](https://www.freebsd.org/cgi/man.cgi?query=mksnap_ffs) 69 #[cfg(any(target_os = "macos", target_os = "freebsd"))] 70 #[cfg_attr(docsrs, doc(cfg(all())))] 71 MNT_SNAPSHOT; 72 /// Using soft updates. 73 #[cfg(any( 74 target_os = "dragonfly", 75 target_os = "freebsd", 76 target_os = "netbsd", 77 target_os = "openbsd" 78 ))] 79 #[cfg_attr(docsrs, doc(cfg(all())))] 80 MNT_SOFTDEP; 81 /// Directories with the SUID bit set chown new files to their own 82 /// owner. 83 #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 84 #[cfg_attr(docsrs, doc(cfg(all())))] 85 MNT_SUIDDIR; 86 /// All I/O to the file system should be done synchronously. 87 MNT_SYNCHRONOUS; 88 /// Union with underlying fs. 89 #[cfg(any( 90 target_os = "macos", 91 target_os = "freebsd", 92 target_os = "netbsd" 93 ))] 94 #[cfg_attr(docsrs, doc(cfg(all())))] 95 MNT_UNION; 96 /// Indicates that the mount command is being applied to an already 97 /// mounted file system. 98 MNT_UPDATE; 99 /// Check vnode use counts. 100 #[cfg(target_os = "freebsd")] 101 #[cfg_attr(docsrs, doc(cfg(all())))] 102 MNT_NONBUSY; 103 } 104); 105 106/// The Error type of [`Nmount::nmount`]. 107/// 108/// It wraps an [`Errno`], but also may contain an additional message returned 109/// by `nmount(2)`. 110#[cfg(target_os = "freebsd")] 111#[derive(Debug)] 112pub struct NmountError { 113 errno: Error, 114 errmsg: Option<String>, 115} 116 117#[cfg(target_os = "freebsd")] 118impl NmountError { 119 /// Returns the additional error string sometimes generated by `nmount(2)`. 120 pub fn errmsg(&self) -> Option<&str> { 121 self.errmsg.as_deref() 122 } 123 124 /// Returns the inner [`Error`] 125 pub const fn error(&self) -> Error { 126 self.errno 127 } 128 129 fn new(error: Error, errmsg: Option<&CStr>) -> Self { 130 Self { 131 errno: error, 132 errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned), 133 } 134 } 135} 136 137#[cfg(target_os = "freebsd")] 138impl std::error::Error for NmountError {} 139 140#[cfg(target_os = "freebsd")] 141impl fmt::Display for NmountError { 142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 143 if let Some(errmsg) = &self.errmsg { 144 write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc()) 145 } else { 146 write!(f, "{:?}: {}", self.errno, self.errno.desc()) 147 } 148 } 149} 150 151#[cfg(target_os = "freebsd")] 152impl From<NmountError> for io::Error { 153 fn from(err: NmountError) -> Self { 154 err.errno.into() 155 } 156} 157 158/// Result type of [`Nmount::nmount`]. 159#[cfg(target_os = "freebsd")] 160pub type NmountResult = std::result::Result<(), NmountError>; 161 162/// Mount a FreeBSD file system. 163/// 164/// The `nmount(2)` system call works similarly to the `mount(8)` program; it 165/// takes its options as a series of name-value pairs. Most of the values are 166/// strings, as are all of the names. The `Nmount` structure builds up an 167/// argument list and then executes the syscall. 168/// 169/// # Examples 170/// 171/// To mount `target` onto `mountpoint` with `nullfs`: 172/// ``` 173/// # use nix::unistd::Uid; 174/// # use ::sysctl::{CtlValue, Sysctl}; 175/// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap(); 176/// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() { 177/// # return; 178/// # }; 179/// use nix::mount::{MntFlags, Nmount, unmount}; 180/// use std::ffi::CString; 181/// use tempfile::tempdir; 182/// 183/// let mountpoint = tempdir().unwrap(); 184/// let target = tempdir().unwrap(); 185/// 186/// let fstype = CString::new("fstype").unwrap(); 187/// let nullfs = CString::new("nullfs").unwrap(); 188/// Nmount::new() 189/// .str_opt(&fstype, &nullfs) 190/// .str_opt_owned("fspath", mountpoint.path().to_str().unwrap()) 191/// .str_opt_owned("target", target.path().to_str().unwrap()) 192/// .nmount(MntFlags::empty()).unwrap(); 193/// 194/// unmount(mountpoint.path(), MntFlags::empty()).unwrap(); 195/// ``` 196/// 197/// # See Also 198/// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount) 199/// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs) 200#[cfg(target_os = "freebsd")] 201#[cfg_attr(docsrs, doc(cfg(all())))] 202#[derive(Debug, Default)] 203pub struct Nmount<'a> { 204 // n.b. notgull: In reality, this is a list that contains 205 // both mutable and immutable pointers. 206 // Be careful using this. 207 iov: Vec<libc::iovec>, 208 is_owned: Vec<bool>, 209 marker: PhantomData<&'a ()>, 210} 211 212#[cfg(target_os = "freebsd")] 213#[cfg_attr(docsrs, doc(cfg(all())))] 214impl<'a> Nmount<'a> { 215 /// Helper function to push a slice onto the `iov` array. 216 fn push_slice(&mut self, val: &'a [u8], is_owned: bool) { 217 self.iov.push(libc::iovec { 218 iov_base: val.as_ptr() as *mut _, 219 iov_len: val.len(), 220 }); 221 self.is_owned.push(is_owned); 222 } 223 224 /// Helper function to push a pointer and its length onto the `iov` array. 225 fn push_pointer_and_length( 226 &mut self, 227 val: *const u8, 228 len: usize, 229 is_owned: bool, 230 ) { 231 self.iov.push(libc::iovec { 232 iov_base: val as *mut _, 233 iov_len: len, 234 }); 235 self.is_owned.push(is_owned); 236 } 237 238 /// Helper function to push a `nix` path as owned. 239 fn push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P) { 240 val.with_nix_path(|s| { 241 let len = s.to_bytes_with_nul().len(); 242 let ptr = s.to_owned().into_raw() as *const u8; 243 244 self.push_pointer_and_length(ptr, len, true); 245 }) 246 .unwrap(); 247 } 248 249 /// Add an opaque mount option. 250 /// 251 /// Some file systems take binary-valued mount options. They can be set 252 /// with this method. 253 /// 254 /// # Safety 255 /// 256 /// Unsafe because it will cause `Nmount::nmount` to dereference a raw 257 /// pointer. The user is responsible for ensuring that `val` is valid and 258 /// its lifetime outlives `self`! An easy way to do that is to give the 259 /// value a larger scope than `name` 260 /// 261 /// # Examples 262 /// ``` 263 /// use libc::c_void; 264 /// use nix::mount::Nmount; 265 /// use std::ffi::CString; 266 /// use std::mem; 267 /// 268 /// // Note that flags outlives name 269 /// let mut flags: u32 = 0xdeadbeef; 270 /// let name = CString::new("flags").unwrap(); 271 /// let p = &mut flags as *mut u32 as *mut c_void; 272 /// let len = mem::size_of_val(&flags); 273 /// let mut nmount = Nmount::new(); 274 /// unsafe { nmount.mut_ptr_opt(&name, p, len) }; 275 /// ``` 276 pub unsafe fn mut_ptr_opt( 277 &mut self, 278 name: &'a CStr, 279 val: *mut c_void, 280 len: usize, 281 ) -> &mut Self { 282 self.push_slice(name.to_bytes_with_nul(), false); 283 self.push_pointer_and_length(val.cast(), len, false); 284 self 285 } 286 287 /// Add a mount option that does not take a value. 288 /// 289 /// # Examples 290 /// ``` 291 /// use nix::mount::Nmount; 292 /// use std::ffi::CString; 293 /// 294 /// let read_only = CString::new("ro").unwrap(); 295 /// Nmount::new() 296 /// .null_opt(&read_only); 297 /// ``` 298 pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self { 299 self.push_slice(name.to_bytes_with_nul(), false); 300 self.push_slice(&[], false); 301 self 302 } 303 304 /// Add a mount option that does not take a value, but whose name must be 305 /// owned. 306 /// 307 /// 308 /// This has higher runtime cost than [`Nmount::null_opt`], but is useful 309 /// when the name's lifetime doesn't outlive the `Nmount`, or it's a 310 /// different string type than `CStr`. 311 /// 312 /// # Examples 313 /// ``` 314 /// use nix::mount::Nmount; 315 /// 316 /// let read_only = "ro"; 317 /// let mut nmount: Nmount<'static> = Nmount::new(); 318 /// nmount.null_opt_owned(read_only); 319 /// ``` 320 pub fn null_opt_owned<P: ?Sized + NixPath>( 321 &mut self, 322 name: &P, 323 ) -> &mut Self { 324 self.push_nix_path(name); 325 self.push_slice(&[], false); 326 self 327 } 328 329 /// Add a mount option as a [`CStr`]. 330 /// 331 /// # Examples 332 /// ``` 333 /// use nix::mount::Nmount; 334 /// use std::ffi::CString; 335 /// 336 /// let fstype = CString::new("fstype").unwrap(); 337 /// let nullfs = CString::new("nullfs").unwrap(); 338 /// Nmount::new() 339 /// .str_opt(&fstype, &nullfs); 340 /// ``` 341 pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self { 342 self.push_slice(name.to_bytes_with_nul(), false); 343 self.push_slice(val.to_bytes_with_nul(), false); 344 self 345 } 346 347 /// Add a mount option as an owned string. 348 /// 349 /// This has higher runtime cost than [`Nmount::str_opt`], but is useful 350 /// when the value's lifetime doesn't outlive the `Nmount`, or it's a 351 /// different string type than `CStr`. 352 /// 353 /// # Examples 354 /// ``` 355 /// use nix::mount::Nmount; 356 /// use std::path::Path; 357 /// 358 /// let mountpoint = Path::new("/mnt"); 359 /// Nmount::new() 360 /// .str_opt_owned("fspath", mountpoint.to_str().unwrap()); 361 /// ``` 362 pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self 363 where 364 P1: ?Sized + NixPath, 365 P2: ?Sized + NixPath, 366 { 367 self.push_nix_path(name); 368 self.push_nix_path(val); 369 self 370 } 371 372 /// Create a new `Nmount` struct with no options 373 pub fn new() -> Self { 374 Self::default() 375 } 376 377 /// Actually mount the file system. 378 pub fn nmount(&mut self, flags: MntFlags) -> NmountResult { 379 const ERRMSG_NAME: &[u8] = b"errmsg\0"; 380 let mut errmsg = vec![0u8; 255]; 381 382 // nmount can return extra error information via a "errmsg" return 383 // argument. 384 self.push_slice(ERRMSG_NAME, false); 385 386 // SAFETY: we are pushing a mutable iovec here, so we can't use 387 // the above method 388 self.iov.push(libc::iovec { 389 iov_base: errmsg.as_mut_ptr() as *mut c_void, 390 iov_len: errmsg.len(), 391 }); 392 393 let niov = self.iov.len() as c_uint; 394 let iovp = self.iov.as_mut_ptr() as *mut libc::iovec; 395 let res = unsafe { libc::nmount(iovp, niov, flags.bits) }; 396 match Errno::result(res) { 397 Ok(_) => Ok(()), 398 Err(error) => { 399 let errmsg = match errmsg.iter().position(|&x| x == 0) { 400 None => None, 401 Some(0) => None, 402 Some(n) => { 403 let sl = &errmsg[0..n + 1]; 404 Some(CStr::from_bytes_with_nul(sl).unwrap()) 405 } 406 }; 407 Err(NmountError::new(error, errmsg)) 408 } 409 } 410 } 411} 412 413#[cfg(target_os = "freebsd")] 414impl<'a> Drop for Nmount<'a> { 415 fn drop(&mut self) { 416 for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) { 417 if *is_owned { 418 // Free the owned string. Safe because we recorded ownership, 419 // and Nmount does not implement Clone. 420 unsafe { 421 drop(CString::from_raw(iov.iov_base as *mut c_char)); 422 } 423 } 424 } 425 } 426} 427 428/// Unmount the file system mounted at `mountpoint`. 429/// 430/// Useful flags include 431/// * `MNT_FORCE` - Unmount even if still in use. 432#[cfg_attr( 433 target_os = "freebsd", 434 doc = " 435* `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID 436 encoded as `FSID:val0:val1`, where `val0` and `val1` 437 are the contents of the `fsid_t val[]` array in decimal. 438 The file system that has the specified file system ID 439 will be unmounted. See 440 [`statfs`](crate::sys::statfs::statfs) to determine the 441 `fsid`. 442" 443)] 444pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()> 445where 446 P: ?Sized + NixPath, 447{ 448 let res = mountpoint.with_nix_path(|cstr| unsafe { 449 libc::unmount(cstr.as_ptr(), flags.bits) 450 })?; 451 452 Errno::result(res).map(drop) 453} 454