sys_mount/lib.rs
1// Copyright 2018-2022 System76 <info@system76.com>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! High level abstraction over the `mount` and `umount2` system calls.
5//!
6//! If the `loop` feature is enabled (default), additionally supports creating loopback devices
7//! automatically when mounting an iso or squashfs file.
8//!
9//! # Example
10//!
11//! ```rust,no_run
12//! extern crate sys_mount;
13//!
14//! use std::process::exit;
15//! use sys_mount::{
16//! Mount,
17//! MountFlags,
18//! SupportedFilesystems,
19//! Unmount,
20//! UnmountFlags
21//! };
22//!
23//! fn main() {
24//! // Fetch a list of supported file systems.
25//! // When mounting, a file system will be selected from this.
26//! let supported = SupportedFilesystems::new().unwrap();
27//!
28//! // Attempt to mount the src device to the dest directory.
29//! let mount_result = Mount::builder()
30//! .fstype("btrfs")
31//! .data("subvol=@home")
32//! .mount("/dev/sda1", "/home");
33//!
34//! match mount_result {
35//! Ok(mount) => {
36//! // Make the mount temporary, so that it will be unmounted on drop.
37//! let mount = mount.into_unmount_drop(UnmountFlags::DETACH);
38//! // Do thing with the mounted device.
39//! }
40//! Err(why) => {
41//! eprintln!("failed to mount device: {}", why);
42//! exit(1);
43//! }
44//! }
45//! }
46
47extern crate libc;
48#[macro_use]
49extern crate bitflags;
50#[macro_use]
51extern crate thiserror;
52
53mod builder;
54mod flags;
55mod fstype;
56mod mount;
57mod supported;
58mod umount;
59
60pub use self::{builder::*, flags::*, fstype::*, mount::*, supported::*, umount::*};
61
62use libc::swapoff as c_swapoff;
63use std::{
64 ffi::CString,
65 io::{self, Error, ErrorKind},
66 os::unix::ffi::OsStrExt,
67 path::Path,
68};
69
70#[derive(Debug, Error)]
71pub enum ScopedMountError {
72 #[error("cannot get list of supported file systems")]
73 Supported(#[source] io::Error),
74 #[error("could not mount partition")]
75 Mount(#[source] io::Error),
76}
77
78/// Mount a partition temporarily for the duration of the scoped block within.
79///
80/// # Errors
81///
82/// - Fails if the supported file systems cannot be found.
83/// - Or if it fails to unmount
84pub fn scoped_mount<T, S: FnOnce() -> T>(
85 source: &Path,
86 mount_at: &Path,
87 scope: S,
88) -> Result<T, ScopedMountError> {
89 let supported = SupportedFilesystems::new().map_err(ScopedMountError::Supported)?;
90
91 Mount::builder()
92 .fstype(&supported)
93 .mount(source, mount_at)
94 .map_err(ScopedMountError::Mount)?;
95
96 let result = scope();
97
98 if let Err(why) = unmount(mount_at, UnmountFlags::empty()) {
99 tracing::warn!("{}: failed to unmount: {}", mount_at.display(), why);
100 }
101
102 Ok(result)
103}
104
105/// Unmounts a swap partition using `libc::swapoff`
106///
107/// # Errors
108///
109/// - If the destination path is not a valid C String
110/// - Or the swapoff function fails
111pub fn swapoff<P: AsRef<Path>>(dest: P) -> io::Result<()> {
112 let Ok(swap) = CString::new(dest.as_ref().as_os_str().as_bytes().to_owned()) else {
113 return Err(Error::new(
114 ErrorKind::Other,
115 format!(
116 "swap path is not a valid c string: '{}'",
117 dest.as_ref().display()
118 )
119 ))
120 };
121
122 match unsafe { c_swapoff(swap.as_ptr()) } {
123 0 => Ok(()),
124
125 _err => Err(Error::new(
126 ErrorKind::Other,
127 format!(
128 "failed to swapoff {}: {}",
129 dest.as_ref().display(),
130 Error::last_os_error()
131 ),
132 )),
133 }
134}
135
136#[inline]
137fn to_cstring(data: &[u8]) -> io::Result<CString> {
138 CString::new(data).map_err(|why| {
139 io::Error::new(
140 io::ErrorKind::InvalidData,
141 format!("failed to create `CString`: {}", why),
142 )
143 })
144}