1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// Copyright 2018-2022 System76 <info@system76.com>
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::umount::{unmount_, Unmount, UnmountDrop};
use crate::{MountBuilder, UnmountFlags};
use std::{
    ffi::{CString, OsStr},
    io,
    os::unix::ffi::OsStrExt,
    path::Path,
};

/// Handle for managing a mounted file system.
#[derive(Debug)]
pub struct Mount {
    pub(crate) target: CString,
    pub(crate) fstype: String,
    #[cfg(feature = "loop")]
    pub(crate) loopback: Option<loopdev::LoopDevice>,
    pub(crate) loop_path: Option<std::path::PathBuf>,
}

impl Unmount for Mount {
    fn unmount(&self, flags: UnmountFlags) -> io::Result<()> {
        unsafe {
            unmount_(self.target.as_ptr(), flags)?;
        }

        #[cfg(feature = "loop")]
        if let Some(ref loopback) = self.loopback {
            loopback.detach()?;
        }

        Ok(())
    }
}

impl Mount {
    /// Creates a [`MountBuilder`] for configuring a new mount.
    ///
    /// ```no_run
    /// use sys_mount::*;
    ///
    /// fn main() -> std::io::Result<()> {
    ///     let _mount = Mount::builder()
    ///         .fstype("btrfs")
    ///         .data("subvol=@home")
    ///         .mount("/dev/sda1", "/home")?;
    ///     Ok(())
    /// }
    /// ```
    #[inline]
    #[must_use]
    pub fn builder<'a>() -> MountBuilder<'a> {
        MountBuilder::default()
    }

    /// Mounts the source device to the target path.
    ///
    /// Attempts to automatically detect the filesystem of the source device.
    ///
    /// For more flexibility, use [`Mount::builder`] instead.
    ///
    /// # Errors
    ///
    /// Errors if supported filesystems cannot be detected, or the mount fails.
    #[inline]
    pub fn new(source: impl AsRef<Path>, target: impl AsRef<Path>) -> io::Result<Mount> {
        let supported = crate::SupportedFilesystems::new()?;
        MountBuilder::default()
            .fstype(&supported)
            .mount(source, target)
    }

    /// If the device was associated with a loopback device, that device's path
    /// can be retrieved here.
    #[inline]
    #[must_use]
    pub fn backing_loop_device(&self) -> Option<&Path> {
        self.loop_path.as_deref()
    }

    /// Describes the file system which this mount was mounted with.
    ///
    /// This is useful in the event that the mounted device was mounted automatically.
    #[inline]
    #[must_use]
    pub fn get_fstype(&self) -> &str {
        &self.fstype
    }

    /// Return the path this mount was mounted on.
    #[inline]
    #[must_use]
    pub fn target_path(&self) -> &Path {
        Path::new(OsStr::from_bytes(self.target.as_bytes()))
    }

    #[inline]
    pub(crate) fn from_target_and_fstype(target: CString, fstype: String) -> Self {
        Mount {
            target,
            fstype,
            #[cfg(feature = "loop")]
            loopback: None,
            loop_path: None,
        }
    }
}

/// An abstraction that will ensure that temporary mounts are dropped in reverse.
pub struct Mounts(pub Vec<UnmountDrop<Mount>>);

impl Mounts {
    /// Unmounts all mounts, with the option to do so lazily.
    ///
    /// # Errors
    ///
    /// Returns on the first error when unmounting.
    pub fn unmount(&mut self, lazy: bool) -> io::Result<()> {
        let flags = if lazy {
            UnmountFlags::DETACH
        } else {
            UnmountFlags::empty()
        };
        self.0
            .iter_mut()
            .rev()
            .try_for_each(|mount| mount.unmount(flags))
    }
}

impl Drop for Mounts {
    fn drop(&mut self) {
        for mount in self.0.drain(..).rev() {
            drop(mount);
        }
    }
}