• 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         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(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(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                 target_os = "freebsd",
75                 target_os = "netbsd"
76         ))]
77         MNT_UNION;
78         /// Indicates that the mount command is being applied to an already
79         /// mounted file system.
80         MNT_UPDATE;
81         /// Check vnode use counts.
82         #[cfg(target_os = "freebsd")]
83         MNT_NONBUSY;
84     }
85 );
86 
87 /// The Error type of [`Nmount::nmount`].
88 ///
89 /// It wraps an [`Errno`], but also may contain an additional message returned
90 /// by `nmount(2)`.
91 #[cfg(target_os = "freebsd")]
92 #[derive(Debug)]
93 pub struct NmountError {
94     errno: Error,
95     errmsg: Option<String>,
96 }
97 
98 #[cfg(target_os = "freebsd")]
99 impl NmountError {
100     /// Returns the additional error string sometimes generated by `nmount(2)`.
errmsg(&self) -> Option<&str>101     pub fn errmsg(&self) -> Option<&str> {
102         self.errmsg.as_deref()
103     }
104 
105     /// Returns the inner [`Error`]
error(&self) -> Error106     pub const fn error(&self) -> Error {
107         self.errno
108     }
109 
new(error: Error, errmsg: Option<&CStr>) -> Self110     fn new(error: Error, errmsg: Option<&CStr>) -> Self {
111         Self {
112             errno: error,
113             errmsg: errmsg.map(CStr::to_string_lossy).map(Cow::into_owned),
114         }
115     }
116 }
117 
118 #[cfg(target_os = "freebsd")]
119 impl std::error::Error for NmountError {}
120 
121 #[cfg(target_os = "freebsd")]
122 impl fmt::Display for NmountError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result123     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
124         if let Some(errmsg) = &self.errmsg {
125             write!(f, "{:?}: {}: {}", self.errno, errmsg, self.errno.desc())
126         } else {
127             write!(f, "{:?}: {}", self.errno, self.errno.desc())
128         }
129     }
130 }
131 
132 #[cfg(target_os = "freebsd")]
133 impl From<NmountError> for io::Error {
from(err: NmountError) -> Self134     fn from(err: NmountError) -> Self {
135         err.errno.into()
136     }
137 }
138 
139 /// Result type of [`Nmount::nmount`].
140 #[cfg(target_os = "freebsd")]
141 pub type NmountResult = std::result::Result<(), NmountError>;
142 
143 /// Mount a FreeBSD file system.
144 ///
145 /// The `nmount(2)` system call works similarly to the `mount(8)` program; it
146 /// takes its options as a series of name-value pairs.  Most of the values are
147 /// strings, as are all of the names.  The `Nmount` structure builds up an
148 /// argument list and then executes the syscall.
149 ///
150 /// # Examples
151 ///
152 /// To mount `target` onto `mountpoint` with `nullfs`:
153 /// ```
154 /// # use nix::unistd::Uid;
155 /// # use ::sysctl::{CtlValue, Sysctl};
156 /// # let ctl = ::sysctl::Ctl::new("vfs.usermount").unwrap();
157 /// # if !Uid::current().is_root() && CtlValue::Int(0) == ctl.value().unwrap() {
158 /// #     return;
159 /// # };
160 /// use nix::mount::{MntFlags, Nmount, unmount};
161 /// use std::ffi::CString;
162 /// use tempfile::tempdir;
163 ///
164 /// let mountpoint = tempdir().unwrap();
165 /// let target = tempdir().unwrap();
166 ///
167 /// let fstype = CString::new("fstype").unwrap();
168 /// let nullfs = CString::new("nullfs").unwrap();
169 /// Nmount::new()
170 ///     .str_opt(&fstype, &nullfs)
171 ///     .str_opt_owned("fspath", mountpoint.path().to_str().unwrap())
172 ///     .str_opt_owned("target", target.path().to_str().unwrap())
173 ///     .nmount(MntFlags::empty()).unwrap();
174 ///
175 /// unmount(mountpoint.path(), MntFlags::empty()).unwrap();
176 /// ```
177 ///
178 /// # See Also
179 /// * [`nmount(2)`](https://www.freebsd.org/cgi/man.cgi?query=nmount)
180 /// * [`nullfs(5)`](https://www.freebsd.org/cgi/man.cgi?query=nullfs)
181 #[cfg(target_os = "freebsd")]
182 #[derive(Debug, Default)]
183 pub struct Nmount<'a> {
184     // n.b. notgull: In reality, this is a list that contains
185     //               both mutable and immutable pointers.
186     //               Be careful using this.
187     iov: Vec<libc::iovec>,
188     is_owned: Vec<bool>,
189     marker: PhantomData<&'a ()>,
190 }
191 
192 #[cfg(target_os = "freebsd")]
193 impl<'a> Nmount<'a> {
194     /// Helper function to push a slice onto the `iov` array.
push_slice(&mut self, val: &'a [u8], is_owned: bool)195     fn push_slice(&mut self, val: &'a [u8], is_owned: bool) {
196         self.iov.push(libc::iovec {
197             iov_base: val.as_ptr().cast_mut().cast(),
198             iov_len: val.len(),
199         });
200         self.is_owned.push(is_owned);
201     }
202 
203     /// 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, )204     fn push_pointer_and_length(
205         &mut self,
206         val: *const u8,
207         len: usize,
208         is_owned: bool,
209     ) {
210         self.iov.push(libc::iovec {
211             iov_base: val as *mut _,
212             iov_len: len,
213         });
214         self.is_owned.push(is_owned);
215     }
216 
217     /// Helper function to push a `nix` path as owned.
push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P)218     fn push_nix_path<P: ?Sized + NixPath>(&mut self, val: &P) {
219         val.with_nix_path(|s| {
220             let len = s.to_bytes_with_nul().len();
221             let ptr = s.to_owned().into_raw() as *const u8;
222 
223             self.push_pointer_and_length(ptr, len, true);
224         })
225         .unwrap();
226     }
227 
228     /// Add an opaque mount option.
229     ///
230     /// Some file systems take binary-valued mount options.  They can be set
231     /// with this method.
232     ///
233     /// # Safety
234     ///
235     /// Unsafe because it will cause `Nmount::nmount` to dereference a raw
236     /// pointer.  The user is responsible for ensuring that `val` is valid and
237     /// its lifetime outlives `self`!  An easy way to do that is to give the
238     /// value a larger scope than `name`
239     ///
240     /// # Examples
241     /// ```
242     /// use libc::c_void;
243     /// use nix::mount::Nmount;
244     /// use std::ffi::CString;
245     /// use std::mem;
246     ///
247     /// // Note that flags outlives name
248     /// let mut flags: u32 = 0xdeadbeef;
249     /// let name = CString::new("flags").unwrap();
250     /// let p = &mut flags as *mut u32 as *mut c_void;
251     /// let len = mem::size_of_val(&flags);
252     /// let mut nmount = Nmount::new();
253     /// unsafe { nmount.mut_ptr_opt(&name, p, len) };
254     /// ```
mut_ptr_opt( &mut self, name: &'a CStr, val: *mut c_void, len: usize, ) -> &mut Self255     pub unsafe fn mut_ptr_opt(
256         &mut self,
257         name: &'a CStr,
258         val: *mut c_void,
259         len: usize,
260     ) -> &mut Self {
261         self.push_slice(name.to_bytes_with_nul(), false);
262         self.push_pointer_and_length(val.cast(), len, false);
263         self
264     }
265 
266     /// Add a mount option that does not take a value.
267     ///
268     /// # Examples
269     /// ```
270     /// use nix::mount::Nmount;
271     /// use std::ffi::CString;
272     ///
273     /// let read_only = CString::new("ro").unwrap();
274     /// Nmount::new()
275     ///     .null_opt(&read_only);
276     /// ```
null_opt(&mut self, name: &'a CStr) -> &mut Self277     pub fn null_opt(&mut self, name: &'a CStr) -> &mut Self {
278         self.push_slice(name.to_bytes_with_nul(), false);
279         self.push_slice(&[], false);
280         self
281     }
282 
283     /// Add a mount option that does not take a value, but whose name must be
284     /// owned.
285     ///
286     ///
287     /// This has higher runtime cost than [`Nmount::null_opt`], but is useful
288     /// when the name's lifetime doesn't outlive the `Nmount`, or it's a
289     /// different string type than `CStr`.
290     ///
291     /// # Examples
292     /// ```
293     /// use nix::mount::Nmount;
294     ///
295     /// let read_only = "ro";
296     /// let mut nmount: Nmount<'static> = Nmount::new();
297     /// nmount.null_opt_owned(read_only);
298     /// ```
null_opt_owned<P: ?Sized + NixPath>( &mut self, name: &P, ) -> &mut Self299     pub fn null_opt_owned<P: ?Sized + NixPath>(
300         &mut self,
301         name: &P,
302     ) -> &mut Self {
303         self.push_nix_path(name);
304         self.push_slice(&[], false);
305         self
306     }
307 
308     /// Add a mount option as a [`CStr`].
309     ///
310     /// # Examples
311     /// ```
312     /// use nix::mount::Nmount;
313     /// use std::ffi::CString;
314     ///
315     /// let fstype = CString::new("fstype").unwrap();
316     /// let nullfs = CString::new("nullfs").unwrap();
317     /// Nmount::new()
318     ///     .str_opt(&fstype, &nullfs);
319     /// ```
str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self320     pub fn str_opt(&mut self, name: &'a CStr, val: &'a CStr) -> &mut Self {
321         self.push_slice(name.to_bytes_with_nul(), false);
322         self.push_slice(val.to_bytes_with_nul(), false);
323         self
324     }
325 
326     /// Add a mount option as an owned string.
327     ///
328     /// This has higher runtime cost than [`Nmount::str_opt`], but is useful
329     /// when the value's lifetime doesn't outlive the `Nmount`, or it's a
330     /// different string type than `CStr`.
331     ///
332     /// # Examples
333     /// ```
334     /// use nix::mount::Nmount;
335     /// use std::path::Path;
336     ///
337     /// let mountpoint = Path::new("/mnt");
338     /// Nmount::new()
339     ///     .str_opt_owned("fspath", mountpoint.to_str().unwrap());
340     /// ```
str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self where P1: ?Sized + NixPath, P2: ?Sized + NixPath,341     pub fn str_opt_owned<P1, P2>(&mut self, name: &P1, val: &P2) -> &mut Self
342     where
343         P1: ?Sized + NixPath,
344         P2: ?Sized + NixPath,
345     {
346         self.push_nix_path(name);
347         self.push_nix_path(val);
348         self
349     }
350 
351     /// Create a new `Nmount` struct with no options
new() -> Self352     pub fn new() -> Self {
353         Self::default()
354     }
355 
356     /// Actually mount the file system.
nmount(&mut self, flags: MntFlags) -> NmountResult357     pub fn nmount(&mut self, flags: MntFlags) -> NmountResult {
358         const ERRMSG_NAME: &[u8] = b"errmsg\0";
359         let mut errmsg = vec![0u8; 255];
360 
361         // nmount can return extra error information via a "errmsg" return
362         // argument.
363         self.push_slice(ERRMSG_NAME, false);
364 
365         // SAFETY: we are pushing a mutable iovec here, so we can't use
366         //         the above method
367         self.iov.push(libc::iovec {
368             iov_base: errmsg.as_mut_ptr().cast(),
369             iov_len: errmsg.len(),
370         });
371 
372         let niov = self.iov.len() as c_uint;
373         let iovp = self.iov.as_mut_ptr();
374         let res = unsafe { libc::nmount(iovp, niov, flags.bits()) };
375         match Errno::result(res) {
376             Ok(_) => Ok(()),
377             Err(error) => {
378                 let errmsg = if errmsg[0] == 0 {
379                     None
380                 } else {
381                     CStr::from_bytes_until_nul(&errmsg[..]).ok()
382                 };
383                 Err(NmountError::new(error, errmsg))
384             }
385         }
386     }
387 }
388 
389 #[cfg(target_os = "freebsd")]
390 impl<'a> Drop for Nmount<'a> {
drop(&mut self)391     fn drop(&mut self) {
392         for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) {
393             if *is_owned {
394                 // Free the owned string.  Safe because we recorded ownership,
395                 // and Nmount does not implement Clone.
396                 unsafe {
397                     drop(CString::from_raw(iov.iov_base as *mut c_char));
398                 }
399             }
400         }
401     }
402 }
403 
404 /// Unmount the file system mounted at `mountpoint`.
405 ///
406 /// Useful flags include
407 /// * `MNT_FORCE` -     Unmount even if still in use.
408 #[cfg_attr(
409     target_os = "freebsd",
410     doc = "
411 * `MNT_BYFSID` -    `mountpoint` is not a path, but a file system ID
412                     encoded as `FSID:val0:val1`, where `val0` and `val1`
413                     are the contents of the `fsid_t val[]` array in decimal.
414                     The file system that has the specified file system ID
415                     will be unmounted.  See
416                     [`statfs`](crate::sys::statfs::statfs) to determine the
417                     `fsid`.
418 "
419 )]
unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()> where P: ?Sized + NixPath,420 pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
421 where
422     P: ?Sized + NixPath,
423 {
424     let res = mountpoint.with_nix_path(|cstr| unsafe {
425         libc::unmount(cstr.as_ptr(), flags.bits())
426     })?;
427 
428     Errno::result(res).map(drop)
429 }
430