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