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}