composefs/
mount.rs

1//! Modern Linux mount API support for composefs.
2//!
3//! This module provides functionality to mount composefs images using the
4//! new mount API (fsopen/fsmount) with overlay filesystem support and
5//! fs-verity verification.
6
7use std::{
8    io::Result,
9    os::fd::{AsFd, BorrowedFd, OwnedFd},
10};
11
12use rustix::{
13    mount::{
14        fsconfig_create, fsconfig_set_flag, fsconfig_set_string, fsmount, fsopen, move_mount,
15        FsMountFlags, FsOpenFlags, MountAttrFlags, MoveMountFlags,
16    },
17    path,
18};
19
20use crate::{
21    mountcompat::{make_erofs_mountable, overlayfs_set_lower_and_data_fds, prepare_mount},
22    util::proc_self_fd,
23};
24
25/// A handle to a filesystem context created via the modern mount API.
26///
27/// This represents an open filesystem context (created by `fsopen()`) that can be
28/// configured and then mounted. The handle automatically reads and prints any
29/// error messages from the kernel when dropped.
30#[derive(Debug)]
31pub struct FsHandle {
32    /// The file descriptor for the filesystem context.
33    pub fd: OwnedFd,
34}
35
36impl FsHandle {
37    /// Opens a new filesystem context for the specified filesystem type.
38    ///
39    /// # Arguments
40    ///
41    /// * `name` - The name of the filesystem type (e.g., "erofs", "overlay")
42    ///
43    /// # Returns
44    ///
45    /// Returns a new `FsHandle` that can be configured and mounted.
46    pub fn open(name: &str) -> Result<FsHandle> {
47        Ok(FsHandle {
48            fd: fsopen(name, FsOpenFlags::FSOPEN_CLOEXEC)?,
49        })
50    }
51}
52
53impl AsFd for FsHandle {
54    fn as_fd(&self) -> BorrowedFd<'_> {
55        self.fd.as_fd()
56    }
57}
58
59impl Drop for FsHandle {
60    fn drop(&mut self) {
61        let mut buffer = [0u8; 1024];
62        loop {
63            match rustix::io::read(&self.fd, &mut buffer) {
64                Err(_) => return, // ENODATA, among others?
65                Ok(0) => return,
66                Ok(size) => eprintln!("{}", String::from_utf8(buffer[0..size].to_vec()).unwrap()),
67            }
68        }
69    }
70}
71
72/// Moves a mounted filesystem to a target location.
73///
74/// # Arguments
75///
76/// * `fs_fd` - File descriptor for the mounted filesystem (from `fsmount()`)
77/// * `dirfd` - Directory file descriptor for the target mount point
78/// * `path` - Path relative to `dirfd` where the filesystem should be mounted
79///
80/// # Returns
81///
82/// Returns `Ok(())` on success, or an error if the mount operation fails.
83pub fn mount_at(
84    fs_fd: impl AsFd,
85    dirfd: impl AsFd,
86    path: impl path::Arg,
87) -> rustix::io::Result<()> {
88    move_mount(
89        fs_fd.as_fd(),
90        "",
91        dirfd.as_fd(),
92        path,
93        MoveMountFlags::MOVE_MOUNT_F_EMPTY_PATH,
94    )
95}
96
97/// Mounts an erofs image file.
98///
99/// Creates a read-only erofs mount from the provided image file descriptor.
100/// On older kernels, this may involve creating a loopback device.
101///
102/// # Arguments
103///
104/// * `image` - File descriptor for the erofs image file
105///
106/// # Returns
107///
108/// Returns a file descriptor for the mounted filesystem, which can be used with
109/// `mount_at()` or other mount operations.
110pub fn erofs_mount(image: OwnedFd) -> Result<OwnedFd> {
111    let image = make_erofs_mountable(image)?;
112    let erofs = FsHandle::open("erofs")?;
113    fsconfig_set_flag(erofs.as_fd(), "ro")?;
114    fsconfig_set_string(erofs.as_fd(), "source", proc_self_fd(&image))?;
115    fsconfig_create(erofs.as_fd())?;
116    Ok(fsmount(
117        erofs.as_fd(),
118        FsMountFlags::FSMOUNT_CLOEXEC,
119        MountAttrFlags::empty(),
120    )?)
121}
122
123/// Creates a composefs mount using overlayfs with an erofs image and base directory.
124///
125/// This mounts a composefs image by creating an overlayfs that layers the erofs image
126/// (as the lower layer) over a base directory (as the data layer). The overlayfs is
127/// configured with metacopy and redirect_dir enabled for composefs functionality.
128///
129/// # Arguments
130///
131/// * `image` - File descriptor for the composefs erofs image
132/// * `name` - Name for the mount source (appears as "composefs:{name}")
133/// * `basedir` - File descriptor for the base directory containing the actual file data
134/// * `enable_verity` - Whether to require fs-verity verification for all files
135///
136/// # Returns
137///
138/// Returns a file descriptor for the mounted composefs filesystem, which can be used
139/// with `mount_at()` to attach it to a mount point.
140pub fn composefs_fsmount(
141    image: OwnedFd,
142    name: &str,
143    basedir: impl AsFd,
144    enable_verity: bool,
145) -> Result<OwnedFd> {
146    let erofs_mnt = prepare_mount(erofs_mount(image)?)?;
147
148    let overlayfs = FsHandle::open("overlay")?;
149    fsconfig_set_string(overlayfs.as_fd(), "source", format!("composefs:{name}"))?;
150    fsconfig_set_string(overlayfs.as_fd(), "metacopy", "on")?;
151    fsconfig_set_string(overlayfs.as_fd(), "redirect_dir", "on")?;
152    if enable_verity {
153        fsconfig_set_string(overlayfs.as_fd(), "verity", "require")?;
154    }
155    overlayfs_set_lower_and_data_fds(&overlayfs, &erofs_mnt, Some(&basedir))?;
156    fsconfig_create(overlayfs.as_fd())?;
157
158    Ok(fsmount(
159        overlayfs.as_fd(),
160        FsMountFlags::FSMOUNT_CLOEXEC,
161        MountAttrFlags::empty(),
162    )?)
163}