Skip to main content

sys_mount/
mount.rs

1// Copyright 2018-2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::umount::{unmount_, Unmount, UnmountDrop};
5use crate::{MountBuilder, PropagationType, UnmountFlags};
6use std::ptr;
7use std::{
8    ffi::{CString, OsStr},
9    io,
10    os::unix::ffi::OsStrExt,
11    path::Path,
12};
13
14/// Handle for managing a mounted file system.
15#[derive(Debug)]
16pub struct Mount {
17    pub(crate) target: CString,
18    pub(crate) fstype: String,
19    #[cfg(feature = "loop")]
20    pub(crate) loopback: Option<loopdev::LoopDevice>,
21    pub(crate) loop_path: Option<std::path::PathBuf>,
22}
23
24impl Unmount for Mount {
25    fn unmount(&self, flags: UnmountFlags) -> io::Result<()> {
26        unsafe {
27            unmount_(self.target.as_ptr(), flags)?;
28        }
29
30        #[cfg(feature = "loop")]
31        if let Some(ref loopback) = self.loopback {
32            loopback.detach()?;
33        }
34
35        Ok(())
36    }
37}
38
39impl Mount {
40    /// Creates a [`MountBuilder`] for configuring a new mount.
41    ///
42    /// ```no_run
43    /// use sys_mount::*;
44    ///
45    /// fn main() -> std::io::Result<()> {
46    ///     let _mount = Mount::builder()
47    ///         .fstype("btrfs")
48    ///         .data("subvol=@home")
49    ///         .mount("/dev/sda1", "/home")?;
50    ///     Ok(())
51    /// }
52    /// ```
53    #[inline]
54    #[must_use]
55    pub fn builder<'a>() -> MountBuilder<'a> {
56        MountBuilder::default()
57    }
58
59    /// Mounts the source device to the target path.
60    ///
61    /// Attempts to automatically detect the filesystem of the source device.
62    ///
63    /// For more flexibility, use [`Mount::builder`] instead.
64    ///
65    /// # Errors
66    ///
67    /// Errors if supported filesystems cannot be detected, or the mount fails.
68    #[inline]
69    pub fn new(source: impl AsRef<Path>, target: impl AsRef<Path>) -> io::Result<Mount> {
70        let supported = crate::SupportedFilesystems::new()?;
71        MountBuilder::default()
72            .fstype(&supported)
73            .mount(source, target)
74    }
75
76    /// If the device was associated with a loopback device, that device's path
77    /// can be retrieved here.
78    #[inline]
79    #[must_use]
80    pub fn backing_loop_device(&self) -> Option<&Path> {
81        self.loop_path.as_deref()
82    }
83
84    /// Describes the file system which this mount was mounted with.
85    ///
86    /// This is useful in the event that the mounted device was mounted automatically.
87    #[inline]
88    #[must_use]
89    pub fn get_fstype(&self) -> &str {
90        &self.fstype
91    }
92
93    /// Return the path this mount was mounted on.
94    #[inline]
95    #[must_use]
96    pub fn target_path(&self) -> &Path {
97        Path::new(OsStr::from_bytes(self.target.as_bytes()))
98    }
99
100    /// Change the propagation type of the mount.
101    #[inline]
102    #[must_use]
103    pub fn set_propagation_type(&mut self, propagation_type: PropagationType) -> io::Result<()> {
104        let result = unsafe {
105            libc::mount(
106                ptr::null(),
107                self.target.as_ptr(),
108                ptr::null(),
109                propagation_type.bits(),
110                ptr::null(),
111            )
112        };
113
114        match result {
115            0 => Ok(()),
116            _err => Err(io::Error::last_os_error()),
117        }
118    }
119
120    #[inline]
121    pub(crate) fn from_target_and_fstype(target: CString, fstype: String) -> Self {
122        Mount {
123            target,
124            fstype,
125            #[cfg(feature = "loop")]
126            loopback: None,
127            loop_path: None,
128        }
129    }
130}
131
132/// An abstraction that will ensure that temporary mounts are dropped in reverse.
133pub struct Mounts(pub Vec<UnmountDrop<Mount>>);
134
135impl Mounts {
136    /// Unmounts all mounts, with the option to do so lazily.
137    ///
138    /// # Errors
139    ///
140    /// Returns on the first error when unmounting.
141    pub fn unmount(&mut self, lazy: bool) -> io::Result<()> {
142        let flags = if lazy {
143            UnmountFlags::DETACH
144        } else {
145            UnmountFlags::empty()
146        };
147        self.0
148            .iter_mut()
149            .rev()
150            .try_for_each(|mount| mount.unmount(flags))
151    }
152}
153
154impl Drop for Mounts {
155    fn drop(&mut self) {
156        for mount in self.0.drain(..).rev() {
157            drop(mount);
158        }
159    }
160}