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