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