• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         #[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)]
112 pub struct NmountError {
113     errno: Error,
114     errmsg: Option<String>,
115 }
116 
117 #[cfg(target_os = "freebsd")]
118 impl NmountError {
119     /// Returns the additional error string sometimes generated by `nmount(2)`.
errmsg(&self) -> Option<&str>120     pub fn errmsg(&self) -> Option<&str> {
121         self.errmsg.as_deref()
122     }
123 
124     /// Returns the inner [`Error`]
error(&self) -> Error125     pub const fn error(&self) -> Error {
126         self.errno
127     }
128 
new(error: Error, errmsg: Option<&CStr>) -> Self129     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")]
138 impl std::error::Error for NmountError {}
139 
140 #[cfg(target_os = "freebsd")]
141 impl fmt::Display for NmountError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result142     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")]
152 impl From<NmountError> for io::Error {
from(err: NmountError) -> Self153     fn from(err: NmountError) -> Self {
154         err.errno.into()
155     }
156 }
157 
158 /// Result type of [`Nmount::nmount`].
159 #[cfg(target_os = "freebsd")]
160 pub 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)]
203 pub 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())))]
214 impl<'a> Nmount<'a> {
215     /// Helper function to push a slice onto the `iov` array.
push_slice(&mut self, val: &'a [u8], is_owned: bool)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.
push_pointer_and_length( &mut self, val: *const u8, len: usize, is_owned: bool, )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.
push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P)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     /// ```
mut_ptr_opt( &mut self, name: &'a CStr, val: *mut c_void, len: usize, ) -> &mut Self276     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     /// ```
null_opt(&mut self, name: &'a CStr) -> &mut Self298     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     /// ```
null_opt_owned<P: ?Sized + NixPath>( &mut self, name: &P, ) -> &mut Self320     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     /// ```
str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self341     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     /// ```
str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self where P1: ?Sized + NixPath, P2: ?Sized + NixPath,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
new() -> Self373     pub fn new() -> Self {
374         Self::default()
375     }
376 
377     /// Actually mount the file system.
nmount(&mut self, flags: MntFlags) -> NmountResult378     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")]
414 impl<'a> Drop for Nmount<'a> {
drop(&mut self)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 )]
unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()> where P: ?Sized + NixPath,444 pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
445 where
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