xref: /third_party/rust/crates/nix/src/mount/bsd.rs (revision 3da5c369)
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