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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
use super::to_cstring;
use fstype::FilesystemType;
use libc::*;
use std::{
    ffi::{CString, OsStr},
    io,
    os::unix::ffi::OsStrExt,
    path::Path,
    ptr,
};
use umount::{unmount_, Unmount, UnmountDrop, UnmountFlags};

bitflags! {
    /// Flags which may be specified when mounting a file system.
    pub struct MountFlags: c_ulong {
        /// Perform a bind mount, making a file or a directory subtree visible at another
        /// point within a file system. Bind mounts may cross file system boundaries and
        /// span chroot(2) jails. The filesystemtype and data arguments are ignored. Up
        /// until Linux 2.6.26, mountflags was also ignored (the bind mount has the same
        /// mount options as the underlying mount point).
        const BIND = MS_BIND;

        /// Make directory changes on this file system synchronous.(This property can be
        /// obtained for individual directories or subtrees using chattr(1).)
        const DIRSYNC = MS_DIRSYNC;

        /// Permit mandatory locking on files in this file system. (Mandatory locking must
        /// still be enabled on a per-file basis, as described in fcntl(2).)
        const MANDLOCK = MS_MANDLOCK;

        /// Move a subtree. source specifies an existing mount point and target specifies
        /// the new location. The move is atomic: at no point is the subtree unmounted.
        /// The filesystemtype, mountflags, and data arguments are ignored.
        const MOVE = MS_MOVE;

        /// Do not update access times for (all types of) files on this file system.
        const NOATIME = MS_NOATIME;

        /// Do not allow access to devices (special files) on this file system.
        const NODEV = MS_NODEV;

        /// Do not update access times for directories on this file system. This flag provides
        /// a subset of the functionality provided by MS_NOATIME; that is, MS_NOATIME implies
        /// MS_NODIRATIME.
        const NODIRATIME = MS_NODIRATIME;

        /// Do not allow programs to be executed from this file system.
        const NOEXEC = MS_NOEXEC;

        /// Do not honor set-user-ID and set-group-ID bits when executing programs from this
        /// file system.
        const NOSUID = MS_NOSUID;

        /// Mount file system read-only.
        const RDONLY = MS_RDONLY;

        /// When a file on this file system is accessed, only update the file's last access
        /// time (atime) if the current value of atime is less than or equal to the file's
        /// last modification time (mtime) or last status change time (ctime). This option is
        /// useful for programs, such as mutt(1), that need to know when a file has been read
        /// since it was last modified. Since Linux 2.6.30, the kernel defaults to the behavior
        /// provided by this flag (unless MS_NOATIME was specified), and the MS_STRICTATIME
        /// flag is required to obtain traditional semantics. In addition, since Linux 2.6.30,
        /// the file's last access time is always updated if it is more than 1 day old.
        const RELATIME = MS_RELATIME;

        /// Remount an existing mount. This allows you to change the mountflags and data of an
        /// existing mount without having to unmount and remount the file system. target should
        /// be the same value specified in the initial mount() call; source and filesystemtype
        /// are ignored.
        ///
        /// The following mountflags can be changed: MS_RDONLY, MS_SYNCHRONOUS, MS_MANDLOCK;
        /// before kernel 2.6.16, the following could also be changed: MS_NOATIME and
        /// MS_NODIRATIME; and, additionally, before kernel 2.4.10, the following could also
        /// be changed: MS_NOSUID, MS_NODEV, MS_NOEXEC.
        const REMOUNT = MS_REMOUNT;

        /// Suppress the display of certain (printk()) warning messages in the kernel log.
        /// This flag supersedes the misnamed and obsolete MS_VERBOSE flag (available
        /// since Linux 2.4.12), which has the same meaning.
        const SILENT = MS_SILENT;

        /// Always update the last access time (atime) when files on this file system are
        /// accessed. (This was the default behavior before Linux 2.6.30.) Specifying this
        /// flag overrides the effect of setting the MS_NOATIME and MS_RELATIME flags.
        const STRICTATIME = MS_STRICTATIME;

        /// Make writes on this file system synchronous (as though the O_SYNC flag to
        /// open(2) was specified for all file opens to this file system).
        const SYNCHRONOUS = MS_SYNCHRONOUS;
    }
}

/// Handle for managing a mounted file system.
#[derive(Debug)]
pub struct Mount {
    pub(crate) target: CString,
    pub(crate) fstype: String,
    #[cfg(feature = "loop")]
    loopback:          Option<loopdev::LoopDevice>,
    #[cfg(feature = "loop")]
    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 {
    /// Mounts a file system at `source` to a `target` path in the system.
    ///
    /// ```rust,no_run
    /// extern crate sys_mount;
    ///
    /// use sys_mount::{
    ///     Mount,
    ///     MountFlags,
    ///     SupportedFilesystems
    /// };
    ///
    /// fn main() {
    ///     // Fetch a list of supported file systems.
    ///     // When mounting, a file system will be selected from this.
    ///     let supported = SupportedFilesystems::new().unwrap();
    ///
    ///     // Attempt to mount the src device to the dest directory.
    ///     let mount_result = Mount::new(
    ///         "/imaginary/block/device",
    ///         "/tmp/location",
    ///         &supported,
    ///         MountFlags::empty(),
    ///         None
    ///     );
    /// }
    /// ```
    /// # Notes
    ///
    /// The provided `source` device and `target` destinations must exist within the file system.
    ///
    /// If the `loop` feature is enabled, and the `source` is a file with an extension, a loopback
    /// device will be created, and the file will be associated with the loopback device. If the
    /// extension is `iso` or `squashfs`, the filesystem type will be set accordingly, and the
    /// `MountFlags` will also be modified to ensure that the `MountFlags::RDONLY` flag is set
    /// before mounting.
    ///
    /// The `fstype` parameter accepts either a `&str` or `&SupportedFilesystem` as input. If the
    /// input is a `&str`, then a particular file system will be used to mount the `source` with.
    /// If the input is a `&SupportedFilesystems`, then the file system will be selected
    /// automatically from the list.
    ///
    /// The automatic variant of `fstype` works by attempting to mount the `source` with all
    /// supported device-based file systems until it succeeds, or fails after trying all
    /// possible options.
    #[cfg_attr(not(feature = "loop"), allow(unused_mut))]
    pub fn new<'a, S, T, F>(
        source: S,
        target: T,
        fstype: F,
        mut flags: MountFlags,
        data: Option<&str>,
    ) -> io::Result<Self>
    where
        S: AsRef<Path>,
        T: AsRef<Path>,
        F: Into<FilesystemType<'a>>,
    {
        let mut fstype = fstype.into();
        let source = source.as_ref();
        let mut c_source = None;

        #[cfg(feature = "loop")]
        let mut loopback = None;
        #[cfg(feature = "loop")]
        let mut loop_path = None;

        if !source.as_os_str().is_empty() {
            // Create a loopback device if an iso or squashfs is being mounted.
            #[cfg(feature = "loop")]
            if let Some(ext) = source.extension() {
                let extf = if ext == "iso" { 1 } else { 0 } | if ext == "squashfs" { 2 } else { 0 };

                if extf != 0 {
                    fstype = if extf == 1 {
                        flags |= MountFlags::RDONLY;
                        FilesystemType::Manual("iso9660")
                    } else {
                        flags |= MountFlags::RDONLY;
                        FilesystemType::Manual("squashfs")
                    };
                }

                let new_loopback = loopdev::LoopControl::open()?.next_free()?;
                new_loopback.attach_file(source)?;
                let path = new_loopback.path().expect("loopback does not have path");
                c_source = Some(to_cstring(path.as_os_str().as_bytes())?);
                loop_path = Some(path);
                loopback = Some(new_loopback);
            }

            if c_source.is_none() {
                c_source = Some(to_cstring(source.as_os_str().as_bytes())?);
            }
        };

        let c_target = to_cstring(target.as_ref().as_os_str().as_bytes())?;
        let c_data = match data.map(|o| to_cstring(o.as_bytes())) {
            Some(Ok(string)) => Some(string),
            Some(Err(why)) => return Err(why),
            None => None,
        };

        let data = c_data.as_ref().map_or(ptr::null(), |d| d.as_ptr()) as *const c_void;
        let mut mount_data = MountData { c_source, c_target, flags, data };

        let mut res = match fstype {
            FilesystemType::Auto(supported) => mount_data.automount(supported.dev_file_systems()),
            FilesystemType::Set(set) => mount_data.automount(set.iter().cloned()),
            FilesystemType::Manual(fstype) => mount_data.mount(fstype),
        };

        #[cfg(feature = "loop")]
        match res {
            Ok(ref mut mount) => {
                mount.loopback = loopback;
                mount.loop_path = loop_path;
            }
            Err(_) => {
                if let Some(loopback) = loopback {
                    let _ = loopback.detach();
                }
            }
        }

        res
    }

    /// If the device was associated with a loopback device, that device's path
    /// can be retrieved here.
    #[cfg(feature = "loop")]
    pub fn backing_loop_device(&self) -> Option<&Path> {
        self.loop_path.as_ref().map(|p| p.as_path())
    }

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

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

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

    #[cfg(not(feature = "loop"))]
    fn from_target_and_fstype(target: CString, fstype: String) -> Self {
        Mount { target: target, fstype: fstype }
    }
}

struct MountData {
    c_source: Option<CString>,
    c_target: CString,
    flags:    MountFlags,
    data:     *const c_void,
}

impl MountData {
    fn mount(&mut self, fstype: &str) -> io::Result<Mount> {
        let c_fstype = to_cstring(fstype.as_bytes())?;
        match mount_(self.c_source.as_ref(), &self.c_target, &c_fstype, self.flags, self.data) {
            Ok(()) => Ok(Mount::from_target_and_fstype(self.c_target.clone(), fstype.to_owned())),
            Err(why) => Err(why),
        }
    }

    fn automount<'a, I: Iterator<Item = &'a str> + 'a>(mut self, iter: I) -> io::Result<Mount> {
        let mut res = Ok(());

        for fstype in iter {
            match self.mount(fstype) {
                mount @ Ok(_) => return mount,
                Err(why) => res = Err(why),
            }
        }

        match res {
            Ok(()) => {
                Err(io::Error::new(io::ErrorKind::NotFound, "no supported file systems found"))
            }
            Err(why) => Err(why),
        }
    }
}

fn mount_(
    c_source: Option<&CString>,
    c_target: &CString,
    c_fstype: &CString,
    flags: MountFlags,
    c_data: *const c_void,
) -> io::Result<()> {
    let result = unsafe {
        mount(
            c_source.map_or_else(ptr::null, |s| s.as_ptr()),
            c_target.as_ptr(),
            c_fstype.as_ptr(),
            flags.bits(),
            c_data,
        )
    };

    match result {
        0 => Ok(()),
        _err => Err(io::Error::last_os_error()),
    }
}

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

impl Mounts {
    #[cfg_attr(rustfmt, rustfmt_skip)]
    pub fn unmount(&mut self, lazy: bool) -> io::Result<()> {
        let flags = if lazy { UnmountFlags::DETACH } else { UnmountFlags::empty() };
        self.0.iter_mut().rev().map(|mount| mount.unmount(flags)).collect()
    }
}

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