1use std::os::fd::OwnedFd;
6use std::sync::Arc;
7
8use anyhow::{Context as _, Result};
9use clap::ValueEnum;
10use rustix::fs::{CWD, Mode, OFlags};
11
12use composefs::fsverity::FsVerityHashValue;
13use composefs::mount::MountOptions;
14use composefs::repository::Repository;
15
16#[derive(Debug, Clone, Copy, Default, ValueEnum)]
18pub(crate) enum FuseMode {
19 #[default]
21 Auto,
22 Yes,
24 No,
26}
27
28pub(crate) enum MountMode {
29 Kernel,
30 Fuse,
31 FuseOverlay,
32}
33
34fn in_init_user_namespace() -> bool {
35 std::fs::read_to_string("/proc/self/uid_map")
36 .map(|s| s.trim() == "0 0 4294967295")
37 .unwrap_or(false)
38}
39
40fn has_cap_sys_admin() -> bool {
41 if let Ok(caps) = rustix::thread::capabilities(None) {
42 caps.effective
43 .contains(rustix::thread::CapabilitySet::SYS_ADMIN)
44 } else {
45 false
46 }
47}
48
49pub(crate) fn detect_mount_mode(fuse_mode: FuseMode, has_upper: bool) -> MountMode {
50 let use_fuse = match fuse_mode {
51 FuseMode::Yes => true,
52 FuseMode::No => false,
53 FuseMode::Auto => !(rustix::process::getuid().is_root() && in_init_user_namespace()),
54 };
55
56 if !use_fuse {
57 return MountMode::Kernel;
58 }
59
60 if (has_upper || has_cap_sys_admin()) && composefs_fuse::user_overlay_supported() {
61 MountMode::FuseOverlay
62 } else {
63 MountMode::Fuse
64 }
65}
66
67pub(crate) fn run_fuse_foreground(
68 image_fd: OwnedFd,
69 objects_fd: Arc<OwnedFd>,
70 mountpoint: &str,
71 mode: MountMode,
72 mount_options: MountOptions,
73 enable_verity: bool,
74 ready_fd: Option<OwnedFd>,
75) -> Result<()> {
76 match mode {
77 MountMode::Kernel => unreachable!(),
78 MountMode::Fuse => {
79 let options = composefs_fuse::ServeFuseOptions::default();
80 composefs_fuse::serve_fuse(mountpoint, image_fd, objects_fd, &options, ready_fd)
81 .context("FUSE server error")?;
82 }
83 MountMode::FuseOverlay => {
84 let dev_fuse = composefs_fuse::open_fuse()?;
85 let fuse_options = composefs_fuse::FuseMountOptions::default();
86 let fuse_mnt =
87 composefs_fuse::mount_fuse(&dev_fuse, &fuse_options).context("FUSE mount")?;
88
89 let mut serve_options = composefs_fuse::ServeFuseOptions::default();
90 serve_options.set_overlay_xattr(Some(composefs_fuse::OverlayXattrMode::User));
91
92 let serve_objects = Arc::clone(&objects_fd);
93 let serve_dev = dev_fuse;
94 let join_handle = std::thread::spawn(move || {
95 composefs_fuse::serve_fuse_fd(serve_dev, image_fd, serve_objects, &serve_options)
96 });
97
98 let read_write = mount_options.read_write();
99 let mut overlay_options = composefs_fuse::OverlayMountOptions::default();
100 if let Some((upper_fd, work_fd)) = mount_options.into_overlay() {
101 overlay_options.set_overlay(upper_fd, work_fd);
102 }
103 overlay_options.set_read_write(read_write);
104 overlay_options.set_enable_verity(enable_verity);
105
106 let overlay_mnt =
107 composefs_fuse::mount_fuse_overlay(fuse_mnt, &*objects_fd, &overlay_options)
108 .context("overlay mount")?;
109 composefs::mount::mount_at(overlay_mnt, CWD, mountpoint)?;
110
111 if let Some(fd) = ready_fd {
112 let _ = rustix::io::write(&fd, b"r");
113 }
114
115 join_handle
116 .join()
117 .map_err(|_| anyhow::anyhow!("FUSE server thread panicked"))?
118 .context("FUSE server error")?;
119 }
120 }
121 Ok(())
122}
123
124#[allow(unsafe_code)]
129pub(crate) fn run_fuse_mount<ObjectID: FsVerityHashValue>(
130 repo: &Arc<Repository<ObjectID>>,
131 name: &str,
132 mountpoint: &str,
133 mode: MountMode,
134 mount_options: MountOptions,
135 foreground: bool,
136) -> Result<()> {
137 if foreground {
138 let (image_fd, enable_verity) = repo.open_image(name)?;
139 let objects_fd = Arc::new(repo.objects_dir()?.try_clone()?);
140 return run_fuse_foreground(
141 image_fd,
142 objects_fd,
143 mountpoint,
144 mode,
145 mount_options,
146 enable_verity,
147 None,
148 );
149 }
150
151 use cap_std_ext::cmdext::{CapStdExtCommandExt as _, CmdFds, SystemdFdName};
152 use std::os::unix::process::CommandExt;
153
154 let (image_fd, enable_verity) = repo.open_image(name)?;
155 let (read_pipe, write_pipe) = rustix::pipe::pipe_with(rustix::pipe::PipeFlags::CLOEXEC)?;
156 let repo_fd = repo.repo_fd().try_clone_to_owned()?;
157
158 let read_write = mount_options.read_write();
159 let mut sd_fds: Vec<(Arc<OwnedFd>, SystemdFdName<'_>)> = vec![
160 (Arc::new(image_fd), SystemdFdName::new("image")),
161 (Arc::new(repo_fd), SystemdFdName::new("repo")),
162 (Arc::new(write_pipe), SystemdFdName::new("ready")),
163 ];
164
165 if let Some((upper_fd, work_fd)) = mount_options.into_overlay() {
166 sd_fds.push((Arc::new(upper_fd), SystemdFdName::new("upper")));
167 sd_fds.push((Arc::new(work_fd), SystemdFdName::new("work")));
168 }
169
170 let fds = CmdFds::new_systemd_fds(sd_fds);
171
172 let self_exe = std::env::current_exe().context("resolving own binary path")?;
173 let mut cmd = std::process::Command::new(&self_exe);
174 cmd.arg("--internal-fuse-serve");
175 cmd.arg("--mountpoint").arg(mountpoint);
176
177 match mode {
178 MountMode::Kernel => unreachable!(),
179 MountMode::Fuse => cmd.arg("--mode").arg("fuse"),
180 MountMode::FuseOverlay => cmd.arg("--mode").arg("fuse-overlay"),
181 };
182
183 if enable_verity {
184 cmd.arg("--enable-verity");
185 }
186 if read_write {
187 cmd.arg("--read-write");
188 }
189
190 cmd.take_fds(fds);
191
192 unsafe {
193 cmd.pre_exec(|| {
194 let _ = rustix::process::setsid();
195 Ok(())
196 });
197 }
198
199 cmd.stdin(std::process::Stdio::null());
200 cmd.stdout(std::process::Stdio::null());
201 cmd.stderr(std::process::Stdio::inherit());
202
203 let _child = cmd.spawn().context("spawning FUSE server process")?;
204
205 let mut buf = [0u8; 1];
206 let _ = rustix::io::read(&read_pipe, &mut buf);
207
208 Ok(())
209}
210
211#[derive(Debug, clap::Parser)]
215pub struct InternalFuseServeArgs {
216 #[arg(long)]
217 mountpoint: String,
218 #[arg(long, value_parser = ["fuse", "fuse-overlay"])]
219 mode: String,
220 #[arg(long)]
221 enable_verity: bool,
222 #[arg(long)]
223 read_write: bool,
224}
225
226#[allow(unsafe_code)]
229pub fn run_internal_fuse_serve(args: InternalFuseServeArgs) -> Result<()> {
230 use std::os::fd::{FromRawFd, IntoRawFd};
231
232 let fds = libsystemd::activation::receive_descriptors_with_names(true)
233 .map_err(|e| anyhow::anyhow!("receiving activation fds: {e}"))?;
234
235 let mut image_fd: Option<OwnedFd> = None;
236 let mut repo_fd: Option<OwnedFd> = None;
237 let mut ready_fd: Option<OwnedFd> = None;
238 let mut upper_fd: Option<OwnedFd> = None;
239 let mut work_fd: Option<OwnedFd> = None;
240
241 for (fd, name) in fds {
242 let owned = unsafe { OwnedFd::from_raw_fd(fd.into_raw_fd()) };
243 match name.as_str() {
244 "image" => image_fd = Some(owned),
245 "repo" => repo_fd = Some(owned),
246 "ready" => ready_fd = Some(owned),
247 "upper" => upper_fd = Some(owned),
248 "work" => work_fd = Some(owned),
249 other => log::warn!("unexpected activation fd name: {other}"),
250 }
251 }
252
253 let image_fd = image_fd.context("missing 'image' activation fd")?;
254 let repo_fd = repo_fd.context("missing 'repo' activation fd")?;
255 let ready_fd = ready_fd.context("missing 'ready' activation fd")?;
256
257 let objects_fd = Arc::new(
258 rustix::fs::openat(
259 &repo_fd,
260 "objects",
261 OFlags::RDONLY | OFlags::DIRECTORY | OFlags::CLOEXEC,
262 Mode::empty(),
263 )
264 .context("opening objects dir")?,
265 );
266
267 let mode = match args.mode.as_str() {
268 "fuse" => MountMode::Fuse,
269 "fuse-overlay" => MountMode::FuseOverlay,
270 _ => unreachable!(),
271 };
272
273 let mut mount_options = MountOptions::default();
274 if let (Some(upper), Some(work)) = (upper_fd, work_fd) {
275 mount_options.set_overlay(upper, work);
276 }
277 mount_options.set_read_write(args.read_write);
278
279 run_fuse_foreground(
280 image_fd,
281 objects_fd,
282 &args.mountpoint,
283 mode,
284 mount_options,
285 args.enable_verity,
286 Some(ready_fd),
287 )
288}