Skip to main content

bootc_internal_mount/
tempmount.rs

1use std::os::fd::AsFd;
2use std::path::Path;
3
4use anyhow::{Context, Result};
5
6use camino::Utf8Path;
7use cap_std_ext::cap_std::{ambient_authority, fs::Dir};
8use fn_error_context::context;
9use rustix::mount::{MountFlags, MoveMountFlags, UnmountFlags, move_mount, unmount};
10
11/// RAII guard that synchronously unmounts a path on drop, flushing all writes.
12///
13/// Prefer this over `MNT_DETACH` when the mounted filesystem has received
14/// writes (e.g. FAT ESP) and you need them flushed before the guard drops.
15#[derive(Debug)]
16pub struct MountGuard(std::path::PathBuf);
17
18impl MountGuard {
19    /// Mount `dev` at `path` and return a guard that will synchronously
20    /// unmount it on drop.
21    pub fn mount(
22        dev: &str,
23        path: std::path::PathBuf,
24        fstype: &str,
25        flags: MountFlags,
26        data: Option<&std::ffi::CStr>,
27    ) -> Result<Self> {
28        rustix::mount::mount(dev, &path, fstype, flags, data)
29            .with_context(|| format!("Mounting {} at {}", dev, path.display()))?;
30        Ok(Self(path))
31    }
32}
33
34impl std::ops::Deref for MountGuard {
35    type Target = Path;
36    fn deref(&self) -> &Path {
37        &self.0
38    }
39}
40
41impl Drop for MountGuard {
42    fn drop(&mut self) {
43        if let Err(e) = unmount(&self.0, UnmountFlags::empty()) {
44            // Synchronous unmount failure may mean buffered writes were not
45            // flushed to the underlying device (e.g. FAT ESP).  Treat this as
46            // an error rather than a warning.
47            tracing::error!("Failed to unmount {}: {e:?}", self.0.display());
48        }
49    }
50}
51
52/// RAII wrapper for a temporary mount that is automatically unmounted on drop.
53#[derive(Debug)]
54pub struct TempMount {
55    /// The backing temporary directory.
56    pub dir: tempfile::TempDir,
57    /// An open handle to the mounted directory.
58    pub fd: Dir,
59}
60
61impl TempMount {
62    /// Mount device/partition on a tempdir which will be automatically unmounted on drop
63    #[context("Mounting {dev}")]
64    pub fn mount_dev(
65        dev: &str,
66        fstype: &str,
67        flags: MountFlags,
68        data: Option<&std::ffi::CStr>,
69    ) -> Result<Self> {
70        let tempdir = tempfile::TempDir::new()?;
71
72        let utf8path = Utf8Path::from_path(tempdir.path())
73            .ok_or(anyhow::anyhow!("Failed to convert path to UTF-8 Path"))?;
74
75        rustix::mount::mount(dev, utf8path.as_std_path(), fstype, flags, data)?;
76
77        let fd = Dir::open_ambient_dir(tempdir.path(), ambient_authority())
78            .with_context(|| format!("Opening {:?}", tempdir.path()));
79
80        let fd = match fd {
81            Ok(fd) => fd,
82            Err(e) => {
83                unmount(tempdir.path(), UnmountFlags::DETACH)?;
84                Err(e)?
85            }
86        };
87
88        Ok(Self { dir: tempdir, fd })
89    }
90
91    /// Mount and fd acquired with `open_tree` like syscall
92    #[context("Mounting fd")]
93    pub fn mount_fd(mnt_fd: impl AsFd) -> Result<Self> {
94        let tempdir = tempfile::TempDir::new()?;
95
96        move_mount(
97            mnt_fd.as_fd(),
98            "",
99            rustix::fs::CWD,
100            tempdir.path(),
101            MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
102        )
103        .context("move_mount")?;
104
105        let fd = Dir::open_ambient_dir(tempdir.path(), ambient_authority())
106            .with_context(|| format!("Opening {:?}", tempdir.path()));
107
108        let fd = match fd {
109            Ok(fd) => fd,
110            Err(e) => {
111                unmount(tempdir.path(), UnmountFlags::DETACH)?;
112                Err(e)?
113            }
114        };
115
116        Ok(Self { dir: tempdir, fd })
117    }
118}
119
120impl Drop for TempMount {
121    fn drop(&mut self) {
122        match unmount(self.dir.path(), UnmountFlags::DETACH) {
123            Ok(_) => {}
124            Err(e) => tracing::warn!("Failed to unmount tempdir: {e:?}"),
125        }
126    }
127}