blob: 0144908157fc075b0583282d51e05d478c733b15 [file] [log] [blame]
Joel Galenson981b40a2021-08-09 10:34:35 -07001use crate::{
2 Error,
3 Errno,
4 NixPath,
5 Result,
6 sys::uio::IoVec
7};
8use libc::{c_char, c_int, c_uint, c_void};
9use std::{
10 borrow::Cow,
11 ffi::{CString, CStr},
12 fmt,
13 io,
14 ptr
15};
16
17
18libc_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)]
102pub struct NmountError {
103 errno: Error,
104 errmsg: Option<String>
105}
106
107impl NmountError {
108 /// Returns the additional error string sometimes generated by `nmount(2)`.
109 pub fn errmsg(&self) -> Option<&str> {
110 self.errmsg.as_deref()
111 }
112
113 /// Returns the inner [`Error`]
114 pub fn error(&self) -> Error {
115 self.errno
116 }
117
118 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
126impl std::error::Error for NmountError {}
127
128impl fmt::Display for NmountError {
129 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
138impl From<NmountError> for io::Error {
139 fn from(err: NmountError) -> Self {
140 err.errno.into()
141 }
142}
143
144/// Result type of [`Nmount::nmount`].
145pub 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)]
186pub struct Nmount<'a>{
187 iov: Vec<IoVec<&'a [u8]>>,
188 is_owned: Vec<bool>,
189}
190
191#[cfg(target_os = "freebsd")]
192impl<'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 /// ```
220 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 /// ```
245 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 /// ```
269 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 /// ```
297 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 /// ```
325 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 pub fn new() -> Self {
351 Self::default()
352 }
353
354 /// Actually mount the file system.
355 pub fn nmount(&mut self, flags: MntFlags) -> NmountResult {
356 // nmount can return extra error information via a "errmsg" return
357 // argument.
358 const ERRMSG_NAME: &[u8] = b"errmsg\0";
359 let mut errmsg = vec![0u8; 255];
360 self.iov.push(IoVec::from_raw_parts(
361 ERRMSG_NAME.as_ptr() as *mut c_void,
362 ERRMSG_NAME.len()
363 ));
364 self.iov.push(IoVec::from_raw_parts(
365 errmsg.as_mut_ptr() as *mut c_void,
366 errmsg.len()
367 ));
368
369 let niov = self.iov.len() as c_uint;
370 let iovp = self.iov.as_mut_ptr() as *mut libc::iovec;
371 let res = unsafe {
372 libc::nmount(iovp, niov, flags.bits)
373 };
374 match Errno::result(res) {
375 Ok(_) => Ok(()),
376 Err(error) => {
377 let errmsg = match errmsg.iter().position(|&x| x == 0) {
378 None => None,
379 Some(0) => None,
380 Some(n) => {
381 let sl = &errmsg[0..n + 1];
382 Some(CStr::from_bytes_with_nul(sl).unwrap())
383 }
384 };
385 Err(NmountError::new(error.into(), errmsg))
386 }
387 }
388 }
389}
390
391#[cfg(target_os = "freebsd")]
392impl<'a> Drop for Nmount<'a> {
393 fn drop(&mut self) {
394 for (iov, is_owned) in self.iov.iter().zip(self.is_owned.iter()) {
395 if *is_owned {
396 // Free the owned string. Safe because we recorded ownership,
397 // and Nmount does not implement Clone.
398 unsafe {
399 CString::from_raw(iov.0.iov_base as *mut c_char);
400 }
401 }
402 }
403 }
404}
405
406/// Unmount the file system mounted at `mountpoint`.
407///
408/// Useful flags include
409/// * `MNT_FORCE` - Unmount even if still in use.
410/// * `MNT_BYFSID` - `mountpoint` is not a path, but a file system ID
411/// encoded as `FSID:val0:val1`, where `val0` and `val1`
412/// are the contents of the `fsid_t val[]` array in decimal.
413/// The file system that has the specified file system ID
414/// will be unmounted. See
415/// [`statfs`](crate::sys::statfs::statfs) to determine the
416/// `fsid`.
417pub fn unmount<P>(mountpoint: &P, flags: MntFlags) -> Result<()>
418 where P: ?Sized + NixPath
419{
420 let res = mountpoint.with_nix_path(|cstr| {
421 unsafe { libc::unmount(cstr.as_ptr(), flags.bits) }
422 })?;
423
424 Errno::result(res).map(drop)
425}