1use super::root_fs::RootFileSystem;
2use super::usage::{FileSystemStats, FileSystemUsage};
3use super::vfs::{
4 VfsError, VfsResult, VirtualDirEntry, VirtualFileSystem, VirtualStat, VirtualUtimeSpec,
5};
6use std::any::Any;
7use std::collections::VecDeque;
8use std::collections::{BTreeMap, BTreeSet};
9use std::path::{Component, Path};
10use web_time::{SystemTime, UNIX_EPOCH};
11
12const MAX_REALPATH_SYMLINKS: usize = 40;
13
14pub trait MountedFileSystem: Any {
15 fn as_any(&self) -> &dyn Any;
16 fn as_any_mut(&mut self) -> &mut dyn Any;
17 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>>;
18 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>>;
19 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
20 let entries = self.read_dir(path)?;
21 if entries.len() > max_entries {
22 return Err(VfsError::new(
23 "ENOMEM",
24 format!(
25 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
26 ),
27 ));
28 }
29 Ok(entries)
30 }
31 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>>;
32 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()>;
33 fn write_file_with_mode(
34 &mut self,
35 path: &str,
36 content: Vec<u8>,
37 mode: Option<u32>,
38 ) -> VfsResult<()> {
39 let _ = mode;
40 self.write_file(path, content)
41 }
42 fn create_file_exclusive(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
43 if self.exists(path) {
44 return Err(VfsError::new(
45 "EEXIST",
46 format!("file already exists, open '{path}'"),
47 ));
48 }
49 self.write_file(path, content)
50 }
51 fn create_file_exclusive_with_mode(
52 &mut self,
53 path: &str,
54 content: Vec<u8>,
55 mode: Option<u32>,
56 ) -> VfsResult<()> {
57 let _ = mode;
58 self.create_file_exclusive(path, content)
59 }
60 fn append_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<u64> {
61 let mut existing = self.read_file(path)?;
62 existing.extend_from_slice(&content);
63 let new_len = existing.len() as u64;
64 self.write_file(path, existing)?;
65 Ok(new_len)
66 }
67 fn create_dir(&mut self, path: &str) -> VfsResult<()>;
68 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
69 let _ = mode;
70 self.create_dir(path)
71 }
72 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()>;
73 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
74 let _ = (mode, rdev);
75 Err(VfsError::new(
76 "EOPNOTSUPP",
77 format!("special inode creation is not supported for mount path '{path}'"),
78 ))
79 }
80 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
81 let _ = mode;
82 self.mkdir(path, recursive)
83 }
84 fn exists(&self, path: &str) -> bool;
85 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat>;
86 fn remove_file(&mut self, path: &str) -> VfsResult<()>;
87 fn remove_dir(&mut self, path: &str) -> VfsResult<()>;
88 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()>;
89 fn realpath(&self, path: &str) -> VfsResult<String>;
90 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()>;
91 fn read_link(&self, path: &str) -> VfsResult<String>;
92 fn lstat(&self, path: &str) -> VfsResult<VirtualStat>;
93 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()>;
94 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()>;
95 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()>;
96 fn chown_spec(
97 &mut self,
98 path: &str,
99 uid: u32,
100 gid: u32,
101 follow_symlinks: bool,
102 ) -> VfsResult<()> {
103 if !follow_symlinks {
104 return Err(VfsError::unsupported(format!(
105 "lchown is not supported for mount path '{path}'"
106 )));
107 }
108 self.chown(path, uid, gid)
109 }
110
111 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
112 self.chown(path, uid, gid)
113 }
114 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
115 let _ = (name, follow_symlinks);
116 Err(VfsError::new(
117 "EOPNOTSUPP",
118 format!("extended attributes are not supported for mount path '{path}'"),
119 ))
120 }
121 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
122 let _ = follow_symlinks;
123 Err(VfsError::new(
124 "EOPNOTSUPP",
125 format!("extended attributes are not supported for mount path '{path}'"),
126 ))
127 }
128 fn set_xattr(
129 &mut self,
130 path: &str,
131 name: &str,
132 value: Vec<u8>,
133 flags: u32,
134 follow_symlinks: bool,
135 ) -> VfsResult<()> {
136 let _ = (name, value, flags, follow_symlinks);
137 Err(VfsError::new(
138 "EOPNOTSUPP",
139 format!("extended attributes are not supported for mount path '{path}'"),
140 ))
141 }
142 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
143 let _ = (name, follow_symlinks);
144 Err(VfsError::new(
145 "EOPNOTSUPP",
146 format!("extended attributes are not supported for mount path '{path}'"),
147 ))
148 }
149 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()>;
150 fn set_atime(&mut self, path: &str, atime_ms: u64) -> VfsResult<()> {
151 let mtime_ms = self.stat(path)?.mtime_ms;
152 self.utimes(path, atime_ms, mtime_ms)
153 }
154 fn utimes_spec(
155 &mut self,
156 path: &str,
157 atime: VirtualUtimeSpec,
158 mtime: VirtualUtimeSpec,
159 follow_symlinks: bool,
160 ) -> VfsResult<()> {
161 if !follow_symlinks {
162 return Err(VfsError::unsupported(format!(
163 "lutimes is not supported for mount path '{path}'"
164 )));
165 }
166 let existing = match (atime, mtime) {
167 (VirtualUtimeSpec::Omit, _) | (_, VirtualUtimeSpec::Omit) => Some(self.stat(path)?),
168 _ => None,
169 };
170 let now_ms = SystemTime::now()
171 .duration_since(UNIX_EPOCH)
172 .unwrap_or_default()
173 .as_millis() as u64;
174 let atime_ms = match atime {
175 VirtualUtimeSpec::Set(spec) => spec.to_truncated_millis()?,
176 VirtualUtimeSpec::Now => now_ms,
177 VirtualUtimeSpec::Omit => {
178 existing
179 .as_ref()
180 .ok_or_else(|| {
181 VfsError::new("EINVAL", "UTIME_OMIT requires existing metadata")
182 })?
183 .atime_ms
184 }
185 };
186 let mtime_ms = match mtime {
187 VirtualUtimeSpec::Set(spec) => spec.to_truncated_millis()?,
188 VirtualUtimeSpec::Now => now_ms,
189 VirtualUtimeSpec::Omit => {
190 existing
191 .as_ref()
192 .ok_or_else(|| {
193 VfsError::new("EINVAL", "UTIME_OMIT requires existing metadata")
194 })?
195 .mtime_ms
196 }
197 };
198 self.utimes(path, atime_ms, mtime_ms)
199 }
200 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()>;
201 fn sync(&mut self, _path: &str) -> VfsResult<()> {
202 Ok(())
203 }
204 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
205 let end = offset
206 .checked_add(length)
207 .ok_or_else(|| VfsError::new("EINVAL", "allocation range overflows"))?;
208 if length == 0 {
209 return Ok(());
210 }
211 let stat = self.stat(path)?;
212 if end > stat.size {
213 self.truncate(path, end)?;
214 }
215 let mut cursor = offset;
216 while cursor < end {
217 let chunk_len = (end - cursor).min(64 * 1024) as usize;
218 let mut bytes = self.pread(path, cursor, chunk_len)?;
219 bytes.resize(chunk_len, 0);
220 self.pwrite(path, bytes, cursor)?;
221 cursor += chunk_len as u64;
222 }
223 Ok(())
224 }
225 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()>;
226 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()>;
227 fn zero_range(
228 &mut self,
229 path: &str,
230 offset: u64,
231 length: u64,
232 keep_size: bool,
233 ) -> VfsResult<()> {
234 let end = offset
235 .checked_add(length)
236 .ok_or_else(|| VfsError::new("EINVAL", "zero range overflows"))?;
237 if length == 0 {
238 return Err(VfsError::new("EINVAL", "zero range length must be nonzero"));
239 }
240 let original_size = self.stat(path)?.size;
241 self.allocate(path, offset, length)?;
242 let zero_end = if keep_size {
243 end.min(original_size)
244 } else {
245 end
246 };
247 let mut cursor = offset.min(zero_end);
248 while cursor < zero_end {
249 let chunk_len = (zero_end - cursor).min(64 * 1024) as usize;
250 self.pwrite(path, vec![0; chunk_len], cursor)?;
251 cursor += chunk_len as u64;
252 }
253 if keep_size && self.stat(path)?.size != original_size {
254 self.truncate(path, original_size)?;
255 }
256 Ok(())
257 }
258 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
259 let requested_end = offset
260 .checked_add(length)
261 .ok_or_else(|| VfsError::new("EINVAL", "hole-punch range overflows"))?;
262 let size = self.stat(path)?.size;
263 let end = requested_end.min(size);
264 let mut cursor = offset.min(size);
265 while cursor < end {
266 let chunk_len = (end - cursor).min(64 * 1024) as usize;
267 self.pwrite(path, vec![0; chunk_len], cursor)?;
268 cursor += chunk_len as u64;
269 }
270 Ok(())
271 }
272 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
273 Err(VfsError::new(
274 "EOPNOTSUPP",
275 format!("extent mapping is not supported for {path}"),
276 ))
277 }
278 fn unwritten_ranges(&mut self, _path: &str) -> VfsResult<Vec<(u64, u64)>> {
279 Ok(Vec::new())
280 }
281 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>>;
282 fn pwrite(&mut self, path: &str, content: Vec<u8>, offset: u64) -> VfsResult<()> {
283 let mut existing = self.read_file(path)?;
284 let start = usize::try_from(offset).map_err(|_| {
285 VfsError::new("EINVAL", format!("pwrite offset is too large: {offset}"))
286 })?;
287 if start > existing.len() {
288 existing.resize(start, 0);
289 }
290 let end = start.checked_add(content.len()).ok_or_else(|| {
291 VfsError::new(
292 "EINVAL",
293 format!(
294 "pwrite range overflows usize: offset={offset}, length={}",
295 content.len()
296 ),
297 )
298 })?;
299 if end > existing.len() {
300 existing.resize(end, 0);
301 }
302 existing[start..end].copy_from_slice(&content);
303 self.write_file(path, existing)
304 }
305 fn shutdown(&mut self) -> VfsResult<()> {
306 Ok(())
307 }
308}
309
310pub struct MountedVirtualFileSystem<F> {
311 inner: F,
312}
313
314impl<F> MountedVirtualFileSystem<F> {
315 pub fn new(inner: F) -> Self {
316 Self { inner }
317 }
318
319 pub fn inner(&self) -> &F {
320 &self.inner
321 }
322
323 pub fn inner_mut(&mut self) -> &mut F {
324 &mut self.inner
325 }
326}
327
328impl<F> MountedFileSystem for MountedVirtualFileSystem<F>
329where
330 F: VirtualFileSystem + 'static,
331{
332 fn as_any(&self) -> &dyn Any {
333 self
334 }
335
336 fn as_any_mut(&mut self) -> &mut dyn Any {
337 self
338 }
339
340 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
341 VirtualFileSystem::read_file(&mut self.inner, path)
342 }
343
344 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
345 VirtualFileSystem::read_dir(&mut self.inner, path)
346 }
347
348 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
349 VirtualFileSystem::read_dir_limited(&mut self.inner, path, max_entries)
350 }
351
352 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
353 VirtualFileSystem::read_dir_with_types(&mut self.inner, path)
354 }
355
356 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
357 VirtualFileSystem::write_file(&mut self.inner, path, content)
358 }
359
360 fn write_file_with_mode(
361 &mut self,
362 path: &str,
363 content: Vec<u8>,
364 mode: Option<u32>,
365 ) -> VfsResult<()> {
366 VirtualFileSystem::write_file_with_mode(&mut self.inner, path, content, mode)
367 }
368
369 fn create_file_exclusive(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
370 VirtualFileSystem::create_file_exclusive(&mut self.inner, path, content)
371 }
372
373 fn create_file_exclusive_with_mode(
374 &mut self,
375 path: &str,
376 content: Vec<u8>,
377 mode: Option<u32>,
378 ) -> VfsResult<()> {
379 VirtualFileSystem::create_file_exclusive_with_mode(&mut self.inner, path, content, mode)
380 }
381
382 fn append_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<u64> {
383 VirtualFileSystem::append_file(&mut self.inner, path, content)
384 }
385
386 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
387 VirtualFileSystem::create_dir(&mut self.inner, path)
388 }
389
390 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
391 VirtualFileSystem::create_dir_with_mode(&mut self.inner, path, mode)
392 }
393
394 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
395 VirtualFileSystem::mkdir(&mut self.inner, path, recursive)
396 }
397
398 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
399 VirtualFileSystem::mknod(&mut self.inner, path, mode, rdev)
400 }
401
402 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
403 VirtualFileSystem::mkdir_with_mode(&mut self.inner, path, recursive, mode)
404 }
405
406 fn exists(&self, path: &str) -> bool {
407 VirtualFileSystem::exists(&self.inner, path)
408 }
409
410 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
411 VirtualFileSystem::stat(&mut self.inner, path)
412 }
413
414 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
415 VirtualFileSystem::remove_file(&mut self.inner, path)
416 }
417
418 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
419 VirtualFileSystem::remove_dir(&mut self.inner, path)
420 }
421
422 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
423 VirtualFileSystem::rename(&mut self.inner, old_path, new_path)
424 }
425
426 fn realpath(&self, path: &str) -> VfsResult<String> {
427 VirtualFileSystem::realpath(&self.inner, path)
428 }
429
430 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
431 VirtualFileSystem::symlink(&mut self.inner, target, link_path)
432 }
433
434 fn read_link(&self, path: &str) -> VfsResult<String> {
435 VirtualFileSystem::read_link(&self.inner, path)
436 }
437
438 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
439 VirtualFileSystem::lstat(&self.inner, path)
440 }
441
442 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
443 VirtualFileSystem::link(&mut self.inner, old_path, new_path)
444 }
445
446 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
447 VirtualFileSystem::chmod(&mut self.inner, path, mode)
448 }
449
450 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
451 VirtualFileSystem::chown(&mut self.inner, path, uid, gid)
452 }
453
454 fn chown_spec(
455 &mut self,
456 path: &str,
457 uid: u32,
458 gid: u32,
459 follow_symlinks: bool,
460 ) -> VfsResult<()> {
461 VirtualFileSystem::chown_spec(&mut self.inner, path, uid, gid, follow_symlinks)
462 }
463
464 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
465 VirtualFileSystem::lchown(&mut self.inner, path, uid, gid)
466 }
467
468 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
469 VirtualFileSystem::get_xattr(&mut self.inner, path, name, follow_symlinks)
470 }
471
472 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
473 VirtualFileSystem::list_xattrs(&mut self.inner, path, follow_symlinks)
474 }
475
476 fn set_xattr(
477 &mut self,
478 path: &str,
479 name: &str,
480 value: Vec<u8>,
481 flags: u32,
482 follow_symlinks: bool,
483 ) -> VfsResult<()> {
484 VirtualFileSystem::set_xattr(&mut self.inner, path, name, value, flags, follow_symlinks)
485 }
486
487 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
488 VirtualFileSystem::remove_xattr(&mut self.inner, path, name, follow_symlinks)
489 }
490
491 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
492 VirtualFileSystem::utimes(&mut self.inner, path, atime_ms, mtime_ms)
493 }
494
495 fn utimes_spec(
496 &mut self,
497 path: &str,
498 atime: VirtualUtimeSpec,
499 mtime: VirtualUtimeSpec,
500 follow_symlinks: bool,
501 ) -> VfsResult<()> {
502 VirtualFileSystem::utimes_spec(&mut self.inner, path, atime, mtime, follow_symlinks)
503 }
504
505 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
506 VirtualFileSystem::truncate(&mut self.inner, path, length)
507 }
508
509 fn sync(&mut self, path: &str) -> VfsResult<()> {
510 VirtualFileSystem::sync(&mut self.inner, path)
511 }
512
513 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
514 VirtualFileSystem::allocate(&mut self.inner, path, offset, length)
515 }
516
517 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
518 VirtualFileSystem::insert_range(&mut self.inner, path, offset, length)
519 }
520
521 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
522 VirtualFileSystem::collapse_range(&mut self.inner, path, offset, length)
523 }
524
525 fn zero_range(
526 &mut self,
527 path: &str,
528 offset: u64,
529 length: u64,
530 keep_size: bool,
531 ) -> VfsResult<()> {
532 VirtualFileSystem::zero_range(&mut self.inner, path, offset, length, keep_size)
533 }
534
535 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
536 VirtualFileSystem::punch_hole(&mut self.inner, path, offset, length)
537 }
538
539 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
540 VirtualFileSystem::allocated_ranges(&mut self.inner, path)
541 }
542
543 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
544 VirtualFileSystem::unwritten_ranges(&mut self.inner, path)
545 }
546
547 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
548 VirtualFileSystem::pread(&mut self.inner, path, offset, length)
549 }
550
551 fn pwrite(&mut self, path: &str, content: Vec<u8>, offset: u64) -> VfsResult<()> {
552 VirtualFileSystem::pwrite(&mut self.inner, path, content, offset)
553 }
554}
555
556impl<T> MountedFileSystem for Box<T>
557where
558 T: MountedFileSystem + ?Sized + 'static,
559{
560 fn as_any(&self) -> &dyn Any {
561 (**self).as_any()
562 }
563
564 fn as_any_mut(&mut self) -> &mut dyn Any {
565 (**self).as_any_mut()
566 }
567
568 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
569 (**self).read_file(path)
570 }
571
572 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
573 (**self).read_dir(path)
574 }
575
576 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
577 (**self).read_dir_limited(path, max_entries)
578 }
579
580 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
581 (**self).read_dir_with_types(path)
582 }
583
584 fn write_file(&mut self, path: &str, content: Vec<u8>) -> VfsResult<()> {
585 (**self).write_file(path, content)
586 }
587
588 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
589 (**self).create_dir(path)
590 }
591
592 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
593 (**self).mkdir(path, recursive)
594 }
595
596 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
597 (**self).mknod(path, mode, rdev)
598 }
599
600 fn exists(&self, path: &str) -> bool {
601 (**self).exists(path)
602 }
603
604 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
605 (**self).stat(path)
606 }
607
608 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
609 (**self).remove_file(path)
610 }
611
612 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
613 (**self).remove_dir(path)
614 }
615
616 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
617 (**self).rename(old_path, new_path)
618 }
619
620 fn realpath(&self, path: &str) -> VfsResult<String> {
621 (**self).realpath(path)
622 }
623
624 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
625 (**self).symlink(target, link_path)
626 }
627
628 fn read_link(&self, path: &str) -> VfsResult<String> {
629 (**self).read_link(path)
630 }
631
632 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
633 (**self).lstat(path)
634 }
635
636 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
637 (**self).link(old_path, new_path)
638 }
639
640 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
641 (**self).chmod(path, mode)
642 }
643
644 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
645 (**self).chown(path, uid, gid)
646 }
647
648 fn chown_spec(
649 &mut self,
650 path: &str,
651 uid: u32,
652 gid: u32,
653 follow_symlinks: bool,
654 ) -> VfsResult<()> {
655 (**self).chown_spec(path, uid, gid, follow_symlinks)
656 }
657
658 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
659 (**self).lchown(path, uid, gid)
660 }
661
662 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
663 (**self).get_xattr(path, name, follow_symlinks)
664 }
665
666 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
667 (**self).list_xattrs(path, follow_symlinks)
668 }
669
670 fn set_xattr(
671 &mut self,
672 path: &str,
673 name: &str,
674 value: Vec<u8>,
675 flags: u32,
676 follow_symlinks: bool,
677 ) -> VfsResult<()> {
678 (**self).set_xattr(path, name, value, flags, follow_symlinks)
679 }
680
681 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
682 (**self).remove_xattr(path, name, follow_symlinks)
683 }
684
685 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
686 (**self).utimes(path, atime_ms, mtime_ms)
687 }
688
689 fn set_atime(&mut self, path: &str, atime_ms: u64) -> VfsResult<()> {
690 (**self).set_atime(path, atime_ms)
691 }
692
693 fn utimes_spec(
694 &mut self,
695 path: &str,
696 atime: VirtualUtimeSpec,
697 mtime: VirtualUtimeSpec,
698 follow_symlinks: bool,
699 ) -> VfsResult<()> {
700 (**self).utimes_spec(path, atime, mtime, follow_symlinks)
701 }
702
703 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
704 (**self).truncate(path, length)
705 }
706
707 fn sync(&mut self, path: &str) -> VfsResult<()> {
708 (**self).sync(path)
709 }
710
711 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
712 (**self).allocate(path, offset, length)
713 }
714
715 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
716 (**self).insert_range(path, offset, length)
717 }
718
719 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
720 (**self).collapse_range(path, offset, length)
721 }
722
723 fn zero_range(
724 &mut self,
725 path: &str,
726 offset: u64,
727 length: u64,
728 keep_size: bool,
729 ) -> VfsResult<()> {
730 (**self).zero_range(path, offset, length, keep_size)
731 }
732
733 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
734 (**self).punch_hole(path, offset, length)
735 }
736
737 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
738 (**self).allocated_ranges(path)
739 }
740
741 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
742 (**self).unwritten_ranges(path)
743 }
744
745 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
746 (**self).pread(path, offset, length)
747 }
748
749 fn pwrite(&mut self, path: &str, content: Vec<u8>, offset: u64) -> VfsResult<()> {
750 (**self).pwrite(path, content, offset)
751 }
752
753 fn shutdown(&mut self) -> VfsResult<()> {
754 (**self).shutdown()
755 }
756}
757
758pub struct ReadOnlyFileSystem<F> {
759 inner: F,
760}
761
762impl<F> ReadOnlyFileSystem<F> {
763 pub fn new(inner: F) -> Self {
764 Self { inner }
765 }
766}
767
768impl<F> MountedFileSystem for ReadOnlyFileSystem<F>
769where
770 F: MountedFileSystem + 'static,
771{
772 fn as_any(&self) -> &dyn Any {
773 self
774 }
775
776 fn as_any_mut(&mut self) -> &mut dyn Any {
777 self
778 }
779
780 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
781 self.inner.read_file(path)
782 }
783
784 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
785 self.inner.read_dir(path)
786 }
787
788 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
789 self.inner.read_dir_limited(path, max_entries)
790 }
791
792 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
793 self.inner.read_dir_with_types(path)
794 }
795
796 fn write_file(&mut self, path: &str, _content: Vec<u8>) -> VfsResult<()> {
797 Err(VfsError::new(
798 "EROFS",
799 format!("read-only filesystem: {path}"),
800 ))
801 }
802
803 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
804 Err(VfsError::new(
805 "EROFS",
806 format!("read-only filesystem: {path}"),
807 ))
808 }
809
810 fn mkdir(&mut self, path: &str, _recursive: bool) -> VfsResult<()> {
811 Err(VfsError::new(
812 "EROFS",
813 format!("read-only filesystem: {path}"),
814 ))
815 }
816
817 fn mknod(&mut self, path: &str, _mode: u32, _rdev: u64) -> VfsResult<()> {
818 Err(VfsError::new(
819 "EROFS",
820 format!("read-only filesystem: {path}"),
821 ))
822 }
823
824 fn exists(&self, path: &str) -> bool {
825 self.inner.exists(path)
826 }
827
828 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
829 self.inner.stat(path)
830 }
831
832 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
833 Err(VfsError::new(
834 "EROFS",
835 format!("read-only filesystem: {path}"),
836 ))
837 }
838
839 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
840 Err(VfsError::new(
841 "EROFS",
842 format!("read-only filesystem: {path}"),
843 ))
844 }
845
846 fn rename(&mut self, old_path: &str, _new_path: &str) -> VfsResult<()> {
847 Err(VfsError::new(
848 "EROFS",
849 format!("read-only filesystem: {old_path}"),
850 ))
851 }
852
853 fn realpath(&self, path: &str) -> VfsResult<String> {
854 self.inner.realpath(path)
855 }
856
857 fn symlink(&mut self, _target: &str, link_path: &str) -> VfsResult<()> {
858 Err(VfsError::new(
859 "EROFS",
860 format!("read-only filesystem: {link_path}"),
861 ))
862 }
863
864 fn read_link(&self, path: &str) -> VfsResult<String> {
865 self.inner.read_link(path)
866 }
867
868 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
869 self.inner.lstat(path)
870 }
871
872 fn link(&mut self, _old_path: &str, new_path: &str) -> VfsResult<()> {
873 Err(VfsError::new(
874 "EROFS",
875 format!("read-only filesystem: {new_path}"),
876 ))
877 }
878
879 fn chmod(&mut self, path: &str, _mode: u32) -> VfsResult<()> {
880 Err(VfsError::new(
881 "EROFS",
882 format!("read-only filesystem: {path}"),
883 ))
884 }
885
886 fn chown(&mut self, path: &str, _uid: u32, _gid: u32) -> VfsResult<()> {
887 Err(VfsError::new(
888 "EROFS",
889 format!("read-only filesystem: {path}"),
890 ))
891 }
892
893 fn chown_spec(
894 &mut self,
895 path: &str,
896 _uid: u32,
897 _gid: u32,
898 _follow_symlinks: bool,
899 ) -> VfsResult<()> {
900 Err(VfsError::new(
901 "EROFS",
902 format!("read-only filesystem: {path}"),
903 ))
904 }
905
906 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
907 self.inner.get_xattr(path, name, follow_symlinks)
908 }
909
910 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
911 self.inner.list_xattrs(path, follow_symlinks)
912 }
913
914 fn set_xattr(
915 &mut self,
916 path: &str,
917 _name: &str,
918 _value: Vec<u8>,
919 _flags: u32,
920 _follow_symlinks: bool,
921 ) -> VfsResult<()> {
922 Err(VfsError::new(
923 "EROFS",
924 format!("read-only filesystem: {path}"),
925 ))
926 }
927
928 fn remove_xattr(&mut self, path: &str, _name: &str, _follow_symlinks: bool) -> VfsResult<()> {
929 Err(VfsError::new(
930 "EROFS",
931 format!("read-only filesystem: {path}"),
932 ))
933 }
934
935 fn utimes(&mut self, path: &str, _atime_ms: u64, _mtime_ms: u64) -> VfsResult<()> {
936 Err(VfsError::new(
937 "EROFS",
938 format!("read-only filesystem: {path}"),
939 ))
940 }
941
942 fn set_atime(&mut self, _path: &str, _atime_ms: u64) -> VfsResult<()> {
943 Ok(())
944 }
945
946 fn utimes_spec(
947 &mut self,
948 path: &str,
949 _atime: VirtualUtimeSpec,
950 _mtime: VirtualUtimeSpec,
951 _follow_symlinks: bool,
952 ) -> VfsResult<()> {
953 Err(VfsError::new(
954 "EROFS",
955 format!("read-only filesystem: {path}"),
956 ))
957 }
958
959 fn truncate(&mut self, path: &str, _length: u64) -> VfsResult<()> {
960 Err(VfsError::new(
961 "EROFS",
962 format!("read-only filesystem: {path}"),
963 ))
964 }
965
966 fn sync(&mut self, path: &str) -> VfsResult<()> {
967 self.inner.sync(path)
968 }
969
970 fn allocate(&mut self, path: &str, _offset: u64, _length: u64) -> VfsResult<()> {
971 Err(VfsError::new(
972 "EROFS",
973 format!("read-only filesystem: {path}"),
974 ))
975 }
976
977 fn insert_range(&mut self, path: &str, _offset: u64, _length: u64) -> VfsResult<()> {
978 Err(VfsError::new(
979 "EROFS",
980 format!("read-only filesystem: {path}"),
981 ))
982 }
983
984 fn collapse_range(&mut self, path: &str, _offset: u64, _length: u64) -> VfsResult<()> {
985 Err(VfsError::new(
986 "EROFS",
987 format!("read-only filesystem: {path}"),
988 ))
989 }
990
991 fn zero_range(
992 &mut self,
993 path: &str,
994 _offset: u64,
995 _length: u64,
996 _keep_size: bool,
997 ) -> VfsResult<()> {
998 Err(VfsError::new(
999 "EROFS",
1000 format!("read-only filesystem: {path}"),
1001 ))
1002 }
1003
1004 fn punch_hole(&mut self, path: &str, _offset: u64, _length: u64) -> VfsResult<()> {
1005 Err(VfsError::new(
1006 "EROFS",
1007 format!("read-only filesystem: {path}"),
1008 ))
1009 }
1010
1011 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
1012 self.inner.allocated_ranges(path)
1013 }
1014
1015 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
1016 self.inner.unwritten_ranges(path)
1017 }
1018
1019 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
1020 self.inner.pread(path, offset, length)
1021 }
1022
1023 fn pwrite(&mut self, path: &str, _content: Vec<u8>, _offset: u64) -> VfsResult<()> {
1024 Err(VfsError::new(
1025 "EROFS",
1026 format!("read-only filesystem: {path}"),
1027 ))
1028 }
1029
1030 fn shutdown(&mut self) -> VfsResult<()> {
1031 self.inner.shutdown()
1032 }
1033}
1034
1035#[derive(Debug, Clone, PartialEq, Eq)]
1036pub enum AccessTimePolicy {
1037 Relatime,
1038 NoAtime,
1039 StrictAtime,
1040}
1041
1042impl AccessTimePolicy {
1043 pub fn option_name(&self) -> &'static str {
1044 match self {
1045 Self::Relatime => "relatime",
1046 Self::NoAtime => "noatime",
1047 Self::StrictAtime => "strictatime",
1048 }
1049 }
1050}
1051
1052impl MountEntry {
1053 pub fn option_string(&self) -> String {
1054 let mut options = vec![
1055 if self.read_only { "ro" } else { "rw" },
1056 self.access_time.option_name(),
1057 ];
1058 if self.no_dir_atime {
1059 options.push("nodiratime");
1060 }
1061 options.join(",")
1062 }
1063}
1064
1065#[derive(Debug, Clone, PartialEq, Eq)]
1066pub struct MountEntry {
1067 pub path: String,
1068 pub plugin_id: String,
1069 pub guest_source: String,
1070 pub guest_fstype: String,
1071 pub read_only: bool,
1072 pub access_time: AccessTimePolicy,
1073 pub no_dir_atime: bool,
1074}
1075
1076#[derive(Debug, Clone, PartialEq, Eq)]
1077pub struct MountOptions {
1078 pub plugin_id: String,
1079 pub guest_source: String,
1080 pub guest_fstype: String,
1081 pub read_only: bool,
1082 pub access_time: AccessTimePolicy,
1083 pub no_dir_atime: bool,
1084 pub max_bytes: Option<u64>,
1085 pub max_inodes: Option<usize>,
1086}
1087
1088impl MountOptions {
1089 pub fn new(plugin_id: impl Into<String>) -> Self {
1090 let plugin_id = plugin_id.into();
1091 Self {
1092 guest_source: plugin_id.clone(),
1093 guest_fstype: plugin_id.clone(),
1094 plugin_id,
1095 read_only: false,
1096 access_time: AccessTimePolicy::Relatime,
1097 no_dir_atime: false,
1098 max_bytes: None,
1099 max_inodes: None,
1100 }
1101 }
1102
1103 pub fn guest_source(mut self, guest_source: impl Into<String>) -> Self {
1104 self.guest_source = guest_source.into();
1105 self
1106 }
1107
1108 pub fn guest_fstype(mut self, guest_fstype: impl Into<String>) -> Self {
1109 self.guest_fstype = guest_fstype.into();
1110 self
1111 }
1112
1113 pub fn read_only(mut self, read_only: bool) -> Self {
1114 self.read_only = read_only;
1115 self
1116 }
1117
1118 pub fn access_time(mut self, access_time: AccessTimePolicy) -> Self {
1119 self.access_time = access_time;
1120 self
1121 }
1122
1123 pub fn no_dir_atime(mut self, no_dir_atime: bool) -> Self {
1124 self.no_dir_atime = no_dir_atime;
1125 self
1126 }
1127
1128 pub fn max_bytes(mut self, max_bytes: Option<u64>) -> Self {
1129 self.max_bytes = max_bytes;
1130 self
1131 }
1132
1133 pub fn max_inodes(mut self, max_inodes: Option<usize>) -> Self {
1134 self.max_inodes = max_inodes;
1135 self
1136 }
1137}
1138
1139struct MountRegistration {
1140 path: String,
1141 plugin_id: String,
1142 guest_source: String,
1143 guest_fstype: String,
1144 read_only: bool,
1145 access_time: AccessTimePolicy,
1146 no_dir_atime: bool,
1147 max_bytes: Option<u64>,
1148 max_inodes: Option<usize>,
1149 cached_usage: Option<FileSystemUsage>,
1150 filesystem: Box<dyn MountedFileSystem>,
1151}
1152
1153pub struct MountTable {
1154 mounts: Vec<MountRegistration>,
1155 mount_indices: BTreeMap<String, usize>,
1156}
1157
1158impl MountTable {
1159 pub fn new(root_fs: impl VirtualFileSystem + 'static) -> Self {
1160 Self {
1161 mounts: vec![MountRegistration {
1162 path: String::from("/"),
1163 plugin_id: String::from("root"),
1164 guest_source: String::from("root"),
1165 guest_fstype: String::from("root"),
1166 read_only: false,
1167 access_time: AccessTimePolicy::Relatime,
1168 no_dir_atime: false,
1169 max_bytes: None,
1170 max_inodes: None,
1171 cached_usage: None,
1172 filesystem: Box::new(MountedVirtualFileSystem::new(root_fs)),
1173 }],
1174 mount_indices: BTreeMap::from([(String::from("/"), 0)]),
1175 }
1176 }
1177
1178 pub fn new_boxed_root(filesystem: Box<dyn MountedFileSystem>, options: MountOptions) -> Self {
1179 let filesystem = if options.read_only {
1180 Box::new(ReadOnlyFileSystem::new(filesystem)) as Box<dyn MountedFileSystem>
1181 } else {
1182 filesystem
1183 };
1184
1185 Self {
1186 mounts: vec![MountRegistration {
1187 path: String::from("/"),
1188 plugin_id: options.plugin_id,
1189 guest_source: options.guest_source,
1190 guest_fstype: options.guest_fstype,
1191 read_only: options.read_only,
1192 access_time: options.access_time,
1193 no_dir_atime: options.no_dir_atime,
1194 max_bytes: options.max_bytes,
1195 max_inodes: options.max_inodes,
1196 cached_usage: None,
1197 filesystem,
1198 }],
1199 mount_indices: BTreeMap::from([(String::from("/"), 0)]),
1200 }
1201 }
1202
1203 pub fn mount(
1204 &mut self,
1205 path: &str,
1206 filesystem: impl VirtualFileSystem + 'static,
1207 options: MountOptions,
1208 ) -> VfsResult<()> {
1209 self.mount_boxed(
1210 path,
1211 Box::new(MountedVirtualFileSystem::new(filesystem)),
1212 options,
1213 )
1214 }
1215
1216 pub fn mount_boxed(
1217 &mut self,
1218 path: &str,
1219 mut filesystem: Box<dyn MountedFileSystem>,
1220 options: MountOptions,
1221 ) -> VfsResult<()> {
1222 let normalized = normalize_path(path);
1223 if normalized == "/" {
1224 return Err(VfsError::new("EINVAL", "cannot mount over root"));
1225 }
1226 if self.mounts.iter().any(|mount| mount.path == normalized) {
1227 return Err(VfsError::new(
1228 "EEXIST",
1229 format!("already mounted at {normalized}"),
1230 ));
1231 }
1232
1233 let (parent_index, relative_path) = self.resolve_index(&normalized)?;
1234 let parent_mount = &mut self.mounts[parent_index];
1235 if !parent_mount.filesystem.exists(&relative_path) {
1236 if let Err(error) = parent_mount.filesystem.mkdir(&relative_path, true) {
1242 if error.code() != "EROFS" {
1243 if let Err(shutdown_error) = filesystem.shutdown() {
1244 return Err(VfsError::new(
1245 shutdown_error.code(),
1246 format!(
1247 "failed to shut down filesystem after mount failure ({error}): {}",
1248 shutdown_error.message()
1249 ),
1250 ));
1251 }
1252
1253 return Err(error);
1254 }
1255 }
1256 }
1257
1258 let filesystem = if options.read_only {
1259 Box::new(ReadOnlyFileSystem::new(filesystem)) as Box<dyn MountedFileSystem>
1260 } else {
1261 filesystem
1262 };
1263
1264 self.mounts.push(MountRegistration {
1265 path: normalized,
1266 plugin_id: options.plugin_id,
1267 guest_source: options.guest_source,
1268 guest_fstype: options.guest_fstype,
1269 read_only: options.read_only,
1270 access_time: options.access_time,
1271 no_dir_atime: options.no_dir_atime,
1272 max_bytes: options.max_bytes,
1273 max_inodes: options.max_inodes,
1274 cached_usage: None,
1275 filesystem,
1276 });
1277 self.mounts
1278 .sort_by_key(|mount| std::cmp::Reverse(mount.path.len()));
1279 self.rebuild_mount_indices();
1280 Ok(())
1281 }
1282
1283 pub fn unmount(&mut self, path: &str) -> VfsResult<()> {
1284 let normalized = normalize_path(path);
1285 if normalized == "/" {
1286 return Err(VfsError::new("EINVAL", "cannot unmount root"));
1287 }
1288
1289 let child_mount_prefix = format!("{normalized}/");
1290 if self
1291 .mounts
1292 .iter()
1293 .any(|mount| mount.path.starts_with(&child_mount_prefix))
1294 {
1295 return Err(VfsError::new(
1296 "EBUSY",
1297 format!("mount point has child mounts: {normalized}"),
1298 ));
1299 }
1300
1301 let Some(index) = self
1302 .mounts
1303 .iter()
1304 .position(|mount| mount.path == normalized)
1305 else {
1306 return Err(VfsError::new(
1307 "EINVAL",
1308 format!("not a mount point: {normalized}"),
1309 ));
1310 };
1311
1312 let mut mount = self.mounts.remove(index);
1313 self.rebuild_mount_indices();
1314 mount.filesystem.shutdown()?;
1315 Ok(())
1316 }
1317
1318 pub fn remount(&mut self, path: &str, options: &str) -> VfsResult<()> {
1319 let normalized = normalize_path(path);
1320 let mount = self
1321 .mounts
1322 .iter_mut()
1323 .find(|mount| mount.path == normalized)
1324 .ok_or_else(|| VfsError::new("EINVAL", format!("not a mount point: {normalized}")))?;
1325
1326 let mut read_only = mount.read_only;
1327 let mut access_time = mount.access_time.clone();
1328 let mut no_dir_atime = mount.no_dir_atime;
1329 let mut max_bytes = mount.max_bytes;
1330 let mut max_inodes = mount.max_inodes;
1331 for option in options
1332 .split(',')
1333 .map(str::trim)
1334 .filter(|value| !value.is_empty())
1335 {
1336 match option {
1337 "remount" => {}
1338 "ro" => read_only = true,
1339 "rw" => read_only = false,
1340 "relatime" => access_time = AccessTimePolicy::Relatime,
1341 "noatime" => access_time = AccessTimePolicy::NoAtime,
1342 "strictatime" => access_time = AccessTimePolicy::StrictAtime,
1343 "nodiratime" => no_dir_atime = true,
1344 "diratime" => no_dir_atime = false,
1345 value if value.starts_with("size=") => {
1346 max_bytes = Some(parse_mount_limit(value, "size")?);
1347 }
1348 value if value.starts_with("inodes=") => {
1349 max_inodes = Some(
1350 usize::try_from(parse_mount_limit(value, "inodes")?).map_err(|_| {
1351 VfsError::new("EINVAL", "mount inode limit exceeds usize")
1352 })?,
1353 );
1354 }
1355 unsupported => {
1356 return Err(VfsError::new(
1357 "EINVAL",
1358 format!("unsupported mount option: {unsupported}"),
1359 ));
1360 }
1361 }
1362 }
1363 let usage =
1364 measure_mounted_filesystem_usage(mount.filesystem.as_mut(), "/", &mut BTreeSet::new())?;
1365 check_usage_limits(&usage, max_bytes, max_inodes)?;
1366 mount.read_only = read_only;
1367 mount.access_time = access_time;
1368 mount.no_dir_atime = no_dir_atime;
1369 mount.max_bytes = max_bytes;
1370 mount.max_inodes = max_inodes;
1371 mount.cached_usage = Some(usage);
1372 Ok(())
1373 }
1374
1375 pub fn get_mounts(&self) -> Vec<MountEntry> {
1376 self.mounts
1377 .iter()
1378 .map(|mount| MountEntry {
1379 path: mount.path.clone(),
1380 plugin_id: mount.plugin_id.clone(),
1381 guest_source: mount.guest_source.clone(),
1382 guest_fstype: mount.guest_fstype.clone(),
1383 read_only: mount.read_only,
1384 access_time: mount.access_time.clone(),
1385 no_dir_atime: mount.no_dir_atime,
1386 })
1387 .collect()
1388 }
1389
1390 pub fn root_virtual_filesystem_mut<T: VirtualFileSystem + 'static>(
1391 &mut self,
1392 ) -> Option<&mut T> {
1393 let root = self.mounts.iter_mut().find(|mount| mount.path == "/")?;
1394 root.filesystem
1395 .as_any_mut()
1396 .downcast_mut::<MountedVirtualFileSystem<T>>()
1397 .map(MountedVirtualFileSystem::inner_mut)
1398 }
1399
1400 pub fn check_rename_copy_up_limits(
1401 &mut self,
1402 old_path: &str,
1403 new_path: &str,
1404 max_bytes: Option<u64>,
1405 max_inodes: Option<usize>,
1406 ) -> VfsResult<()> {
1407 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
1408 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
1409 if old_index != new_index {
1410 return Ok(());
1411 }
1412
1413 let filesystem = &mut self.mounts[old_index].filesystem;
1414 if let Some(root) = filesystem
1415 .as_any_mut()
1416 .downcast_mut::<MountedVirtualFileSystem<RootFileSystem>>()
1417 {
1418 root.inner_mut().check_rename_copy_up_limits(
1419 &old_relative_path,
1420 &new_relative_path,
1421 max_bytes,
1422 max_inodes,
1423 )?;
1424 }
1425
1426 Ok(())
1427 }
1428
1429 pub fn root_usage(&mut self) -> VfsResult<FileSystemUsage> {
1430 let root = self
1431 .mounts
1432 .iter_mut()
1433 .find(|mount| mount.path == "/")
1434 .ok_or_else(|| VfsError::new("ENOENT", "missing root mount"))?;
1435 measure_mounted_filesystem_usage(root.filesystem.as_mut(), "/", &mut BTreeSet::new())
1436 }
1437
1438 pub fn path_stats(
1439 &mut self,
1440 path: &str,
1441 root_max_bytes: Option<u64>,
1442 root_max_inodes: Option<usize>,
1443 ) -> VfsResult<FileSystemStats> {
1444 let (index, _) = self.resolve_content_index(path)?;
1445 let mount = &mut self.mounts[index];
1446 let usage =
1447 measure_mounted_filesystem_usage(mount.filesystem.as_mut(), "/", &mut BTreeSet::new())?;
1448 mount.cached_usage = Some(usage.clone());
1449 let max_bytes = mount
1450 .max_bytes
1451 .or_else(|| (mount.path == "/").then_some(root_max_bytes).flatten())
1452 .unwrap_or(usage.total_bytes);
1453 let used_bytes = usage.total_bytes.min(max_bytes);
1454 let max_inodes = mount
1455 .max_inodes
1456 .or_else(|| (mount.path == "/").then_some(root_max_inodes).flatten())
1457 .map(|value| value as u64)
1458 .unwrap_or(usage.inode_count as u64);
1459 let used_inodes = (usage.inode_count as u64).min(max_inodes);
1460 Ok(FileSystemStats {
1461 total_bytes: max_bytes,
1462 used_bytes,
1463 available_bytes: max_bytes.saturating_sub(used_bytes),
1464 total_inodes: max_inodes,
1465 free_inodes: max_inodes.saturating_sub(used_inodes),
1466 })
1467 }
1468
1469 fn cached_usage(&mut self, index: usize) -> VfsResult<FileSystemUsage> {
1470 if let Some(usage) = self.mounts[index].cached_usage.clone() {
1471 return Ok(usage);
1472 }
1473 let usage = measure_mounted_filesystem_usage(
1474 self.mounts[index].filesystem.as_mut(),
1475 "/",
1476 &mut BTreeSet::new(),
1477 )?;
1478 self.mounts[index].cached_usage = Some(usage.clone());
1479 Ok(usage)
1480 }
1481
1482 fn update_cached_path_usage(
1483 &mut self,
1484 index: usize,
1485 before: Option<VirtualStat>,
1486 relative_path: &str,
1487 ) {
1488 let after = self.mounts[index].filesystem.lstat(relative_path).ok();
1489 let Some(usage) = self.mounts[index].cached_usage.as_mut() else {
1490 return;
1491 };
1492 match (before, after) {
1493 (None, Some(after)) => {
1494 usage.inode_count = usage.inode_count.saturating_add(1);
1495 if !after.is_directory {
1496 usage.total_bytes = usage.total_bytes.saturating_add(after.size);
1497 }
1498 }
1499 (Some(before), None) => {
1500 if before.is_directory || before.nlink <= 1 {
1501 usage.inode_count = usage.inode_count.saturating_sub(1);
1502 if !before.is_directory {
1503 usage.total_bytes = usage.total_bytes.saturating_sub(before.size);
1504 }
1505 }
1506 }
1507 (Some(before), Some(after))
1508 if (before.dev, before.ino) == (after.dev, after.ino) && !before.is_directory =>
1509 {
1510 usage.total_bytes = usage
1511 .total_bytes
1512 .saturating_sub(before.size)
1513 .saturating_add(after.size);
1514 }
1515 _ => {}
1516 }
1517 }
1518
1519 fn check_file_growth(
1520 &mut self,
1521 index: usize,
1522 relative_path: &str,
1523 new_size: u64,
1524 exclusive: bool,
1525 ) -> VfsResult<()> {
1526 if self.mounts[index].max_bytes.is_none() && self.mounts[index].max_inodes.is_none() {
1527 return Ok(());
1528 }
1529 let usage = self.cached_usage(index)?;
1530 let mount = &mut self.mounts[index];
1531 let existing = mount.filesystem.lstat(relative_path).ok();
1532 let existing_size = existing
1533 .as_ref()
1534 .filter(|stat| !stat.is_directory)
1535 .map_or(0, |stat| stat.size);
1536 let resulting = FileSystemUsage {
1537 total_bytes: usage
1538 .total_bytes
1539 .saturating_sub(existing_size)
1540 .saturating_add(new_size),
1541 inode_count: usage
1542 .inode_count
1543 .saturating_add(usize::from(existing.is_none())),
1544 };
1545 if exclusive && existing.is_some() {
1546 return Ok(());
1547 }
1548 check_usage_limits(&resulting, mount.max_bytes, mount.max_inodes)
1549 }
1550
1551 fn check_inode_growth(&mut self, index: usize, added: usize) -> VfsResult<()> {
1552 if added == 0
1553 || (self.mounts[index].max_bytes.is_none() && self.mounts[index].max_inodes.is_none())
1554 {
1555 return Ok(());
1556 }
1557 let mut usage = self.cached_usage(index)?;
1558 let mount = &self.mounts[index];
1559 usage.inode_count = usage.inode_count.saturating_add(added);
1560 check_usage_limits(&usage, mount.max_bytes, mount.max_inodes)
1561 }
1562
1563 fn missing_directory_count(&self, index: usize, relative_path: &str) -> usize {
1564 let mut current = String::from("/");
1565 let mut missing = 0usize;
1566 for component in path_components(relative_path) {
1567 current = join_path(¤t, &component);
1568 if !self.mounts[index].filesystem.exists(¤t) {
1569 missing = missing.saturating_add(1);
1570 }
1571 }
1572 missing
1573 }
1574
1575 pub fn path_uses_root_filesystem(&self, path: &str) -> bool {
1576 self.resolve_index(path)
1577 .is_ok_and(|(index, _)| self.mounts[index].path == "/")
1578 }
1579
1580 fn resolve_index(&self, full_path: &str) -> VfsResult<(usize, String)> {
1581 let normalized = normalize_path(full_path);
1582 let mut candidate = normalized.as_str();
1583 loop {
1584 if let Some(index) = self.mount_indices.get(candidate).copied() {
1585 let relative_path = if candidate == "/" {
1586 normalized
1587 } else if candidate.len() == normalized.len() {
1588 String::from("/")
1589 } else {
1590 format!("/{}", &normalized[candidate.len() + 1..])
1594 };
1595 return Ok((index, relative_path));
1596 }
1597 if candidate == "/" {
1598 break;
1599 }
1600 candidate = candidate
1601 .rfind('/')
1602 .map(|index| if index == 0 { "/" } else { &candidate[..index] })
1603 .unwrap_or("/");
1604 }
1605
1606 Err(VfsError::new(
1607 "ENOENT",
1608 format!("no such file or directory, resolve '{full_path}'"),
1609 ))
1610 }
1611
1612 fn rebuild_mount_indices(&mut self) {
1613 self.mount_indices.clear();
1614 self.mount_indices.extend(
1615 self.mounts
1616 .iter()
1617 .enumerate()
1618 .map(|(index, mount)| (mount.path.clone(), index)),
1619 );
1620 }
1621
1622 fn resolve_writable_index(&self, full_path: &str) -> VfsResult<(usize, String)> {
1623 let (index, relative_path) = self.resolve_index(full_path)?;
1624 self.ensure_writable(index, full_path)?;
1625 Ok((index, relative_path))
1626 }
1627
1628 fn ensure_writable(&self, index: usize, full_path: &str) -> VfsResult<()> {
1629 if self.mounts[index].read_only {
1630 return Err(VfsError::new(
1631 "EROFS",
1632 format!("read-only filesystem: {full_path}"),
1633 ));
1634 }
1635 Ok(())
1636 }
1637
1638 fn atime_snapshot(
1639 &mut self,
1640 index: usize,
1641 relative_path: &str,
1642 is_directory: bool,
1643 ) -> VfsResult<Option<VirtualStat>> {
1644 let mount = &mut self.mounts[index];
1645 if mount.read_only
1646 || mount.access_time == AccessTimePolicy::NoAtime
1647 || (is_directory && mount.no_dir_atime)
1648 {
1649 return Ok(None);
1650 }
1651 mount.filesystem.stat(relative_path).map(Some)
1652 }
1653
1654 fn finish_atime_update(
1655 &mut self,
1656 index: usize,
1657 relative_path: &str,
1658 before: Option<VirtualStat>,
1659 ) -> VfsResult<()> {
1660 let Some(before) = before else {
1661 return Ok(());
1662 };
1663 let mount = &mut self.mounts[index];
1664 let now_ms = SystemTime::now()
1665 .duration_since(UNIX_EPOCH)
1666 .unwrap_or_default()
1667 .as_millis() as u64;
1668 let update = match mount.access_time {
1669 AccessTimePolicy::NoAtime => false,
1670 AccessTimePolicy::StrictAtime => true,
1671 AccessTimePolicy::Relatime => {
1672 timestamp_ns(before.atime_ms, before.atime_nsec)
1673 <= timestamp_ns(before.mtime_ms, before.mtime_nsec)
1674 || timestamp_ns(before.atime_ms, before.atime_nsec)
1675 <= timestamp_ns(before.ctime_ms, before.ctime_nsec)
1676 || now_ms.saturating_sub(before.atime_ms) >= 24 * 60 * 60 * 1_000
1677 }
1678 };
1679 if update {
1680 mount.filesystem.set_atime(relative_path, now_ms)?;
1681 }
1682 Ok(())
1683 }
1684
1685 fn resolve_link_leaf_index(&self, path: &str) -> VfsResult<(usize, String)> {
1701 let normalized = normalize_path(path);
1702 let raw = self.resolve_index(&normalized)?;
1703 if raw.1 == "/" {
1704 return Ok(raw);
1705 }
1706 let parent = parent_path(&normalized);
1707 let leaf = basename(&normalized);
1708 match self.realpath(&parent) {
1709 Ok(resolved_parent) if resolved_parent != parent => {
1710 self.resolve_index(&join_path(&resolved_parent, &leaf))
1711 }
1712 Err(_) => Ok(raw),
1713 Ok(_) => Ok(raw),
1714 }
1715 }
1716
1717 fn resolve_content_index(&self, path: &str) -> VfsResult<(usize, String)> {
1718 let normalized = normalize_path(path);
1719 let raw = self.resolve_index(&normalized)?;
1720 match self.realpath(&normalized) {
1721 Ok(resolved) if resolved != normalized => self.resolve_index(&resolved),
1722 Ok(_) | Err(_) => Ok(raw),
1723 }
1724 }
1725
1726 fn child_mount_basenames(&self, path: &str) -> Vec<String> {
1727 let normalized = normalize_path(path);
1728 let mut basenames = BTreeSet::new();
1729 for mount in &self.mounts {
1730 if mount.path == "/" || mount.path == normalized {
1731 continue;
1732 }
1733
1734 if parent_path(&mount.path) == normalized {
1735 basenames.insert(basename(&mount.path));
1736 }
1737 }
1738 basenames.into_iter().collect()
1739 }
1740
1741 fn realpath_in_mount(&self, index: usize, relative_path: &str) -> VfsResult<String> {
1742 let mount = &self.mounts[index];
1743 let resolved = mount.filesystem.realpath(relative_path)?;
1744 if mount.path == "/" {
1745 return Ok(normalize_path(&resolved));
1746 }
1747 if resolved == "/" {
1748 return Ok(mount.path.clone());
1749 }
1750 Ok(normalize_path(&format!(
1751 "{}/{}",
1752 mount.path,
1753 resolved.trim_start_matches('/')
1754 )))
1755 }
1756}
1757
1758fn measure_mounted_filesystem_usage(
1759 filesystem: &mut dyn MountedFileSystem,
1760 path: &str,
1761 visited: &mut BTreeSet<(u64, u64)>,
1762) -> VfsResult<FileSystemUsage> {
1763 let stat = filesystem.lstat(path)?;
1764 let mut usage = FileSystemUsage::default();
1765
1766 if visited.insert((stat.dev, stat.ino)) {
1767 usage.inode_count += 1;
1768 if !stat.is_directory {
1769 usage.total_bytes = usage.total_bytes.saturating_add(stat.size);
1770 }
1771 }
1772
1773 if !stat.is_directory || stat.is_symbolic_link {
1774 return Ok(usage);
1775 }
1776
1777 for entry in filesystem.read_dir_with_types(path)? {
1778 if matches!(entry.name.as_str(), "." | "..") {
1779 continue;
1780 }
1781
1782 let child_path = if path == "/" {
1783 format!("/{}", entry.name)
1784 } else {
1785 format!("{path}/{}", entry.name)
1786 };
1787 let child_usage = measure_mounted_filesystem_usage(filesystem, &child_path, visited)?;
1788 usage.total_bytes = usage.total_bytes.saturating_add(child_usage.total_bytes);
1789 usage.inode_count = usage.inode_count.saturating_add(child_usage.inode_count);
1790 }
1791
1792 Ok(usage)
1793}
1794
1795impl Drop for MountTable {
1796 fn drop(&mut self) {
1797 for mount in self.mounts.iter_mut().rev() {
1798 if let Err(error) = mount.filesystem.shutdown() {
1799 eprintln!(
1800 "failed to shut down filesystem mounted at {}: {}: {}",
1801 mount.path,
1802 error.code(),
1803 error.message()
1804 );
1805 }
1806 }
1807 }
1808}
1809
1810impl VirtualFileSystem for MountTable {
1811 fn read_file(&mut self, path: &str) -> VfsResult<Vec<u8>> {
1812 let (index, relative_path) = self.resolve_content_index(path)?;
1813 let before = self.atime_snapshot(index, &relative_path, false)?;
1814 let content = self.mounts[index].filesystem.read_file(&relative_path)?;
1815 self.finish_atime_update(index, &relative_path, before)?;
1816 Ok(content)
1817 }
1818
1819 fn read_dir(&mut self, path: &str) -> VfsResult<Vec<String>> {
1820 let normalized = normalize_path(path);
1821 let (index, relative_path) = self.resolve_content_index(&normalized)?;
1827 let before = self.atime_snapshot(index, &relative_path, true)?;
1828 let mut entries = self.mounts[index].filesystem.read_dir(&relative_path)?;
1829 self.finish_atime_update(index, &relative_path, before)?;
1830 let child_mounts = self.child_mount_basenames(&normalized);
1831 if child_mounts.is_empty() {
1832 return Ok(entries);
1833 }
1834
1835 let mut merged = BTreeSet::new();
1836 merged.extend(entries.drain(..));
1837 merged.extend(child_mounts);
1838 Ok(merged.into_iter().collect())
1839 }
1840
1841 fn read_dir_limited(&mut self, path: &str, max_entries: usize) -> VfsResult<Vec<String>> {
1842 let normalized = normalize_path(path);
1843 let (index, relative_path) = self.resolve_content_index(&normalized)?;
1844 let before = self.atime_snapshot(index, &relative_path, true)?;
1845 let mut entries = self.mounts[index]
1846 .filesystem
1847 .read_dir_limited(&relative_path, max_entries)?;
1848 self.finish_atime_update(index, &relative_path, before)?;
1849 let child_mounts = self.child_mount_basenames(&normalized);
1850 if child_mounts.is_empty() {
1851 return Ok(entries);
1852 }
1853
1854 let mut merged = BTreeSet::new();
1855 merged.extend(entries.drain(..));
1856 merged.extend(child_mounts);
1857 if merged.len() > max_entries {
1858 return Err(VfsError::new(
1859 "ENOMEM",
1860 format!(
1861 "directory listing for '{path}' exceeds configured limit of {max_entries} entries"
1862 ),
1863 ));
1864 }
1865 Ok(merged.into_iter().collect())
1866 }
1867
1868 fn read_dir_with_types(&mut self, path: &str) -> VfsResult<Vec<VirtualDirEntry>> {
1869 let normalized = normalize_path(path);
1870 let (index, relative_path) = self.resolve_content_index(&normalized)?;
1871 let before = self.atime_snapshot(index, &relative_path, true)?;
1872 let mut entries = self.mounts[index]
1873 .filesystem
1874 .read_dir_with_types(&relative_path)?;
1875 self.finish_atime_update(index, &relative_path, before)?;
1876 let child_mounts = self.child_mount_basenames(&normalized);
1877 if child_mounts.is_empty() {
1878 return Ok(entries);
1879 }
1880
1881 let existing = entries
1882 .iter()
1883 .map(|entry| entry.name.clone())
1884 .collect::<BTreeSet<_>>();
1885 for mount_name in child_mounts {
1886 if existing.contains(&mount_name) {
1887 continue;
1888 }
1889 entries.push(VirtualDirEntry {
1890 name: mount_name,
1891 is_directory: true,
1892 is_symbolic_link: false,
1893 });
1894 }
1895 Ok(entries)
1896 }
1897
1898 fn write_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
1899 let content = content.into();
1900 let (index, relative_path) = self.resolve_writable_index(path)?;
1901 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1902 self.check_file_growth(index, &relative_path, content.len() as u64, false)?;
1903 self.mounts[index]
1904 .filesystem
1905 .write_file(&relative_path, content)?;
1906 self.update_cached_path_usage(index, before, &relative_path);
1907 Ok(())
1908 }
1909
1910 fn write_file_with_mode(
1911 &mut self,
1912 path: &str,
1913 content: impl Into<Vec<u8>>,
1914 mode: Option<u32>,
1915 ) -> VfsResult<()> {
1916 let content = content.into();
1917 let (index, relative_path) = self.resolve_writable_index(path)?;
1918 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1919 self.check_file_growth(index, &relative_path, content.len() as u64, false)?;
1920 self.mounts[index]
1921 .filesystem
1922 .write_file_with_mode(&relative_path, content, mode)?;
1923 self.update_cached_path_usage(index, before, &relative_path);
1924 Ok(())
1925 }
1926
1927 fn create_file_exclusive(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<()> {
1928 let content = content.into();
1929 let (index, relative_path) = self.resolve_writable_index(path)?;
1930 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1931 self.check_file_growth(index, &relative_path, content.len() as u64, true)?;
1932 self.mounts[index]
1933 .filesystem
1934 .create_file_exclusive(&relative_path, content)?;
1935 self.update_cached_path_usage(index, before, &relative_path);
1936 Ok(())
1937 }
1938
1939 fn create_file_exclusive_with_mode(
1940 &mut self,
1941 path: &str,
1942 content: impl Into<Vec<u8>>,
1943 mode: Option<u32>,
1944 ) -> VfsResult<()> {
1945 let content = content.into();
1946 let (index, relative_path) = self.resolve_writable_index(path)?;
1947 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1948 self.check_file_growth(index, &relative_path, content.len() as u64, true)?;
1949 self.mounts[index]
1950 .filesystem
1951 .create_file_exclusive_with_mode(&relative_path, content, mode)?;
1952 self.update_cached_path_usage(index, before, &relative_path);
1953 Ok(())
1954 }
1955
1956 fn append_file(&mut self, path: &str, content: impl Into<Vec<u8>>) -> VfsResult<u64> {
1957 let content = content.into();
1958 let (index, relative_path) = self.resolve_writable_index(path)?;
1959 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1960 let current_size = before.as_ref().map_or(0, |stat| stat.size);
1961 self.check_file_growth(
1962 index,
1963 &relative_path,
1964 current_size.saturating_add(content.len() as u64),
1965 false,
1966 )?;
1967 let size = self.mounts[index]
1968 .filesystem
1969 .append_file(&relative_path, content)?;
1970 self.update_cached_path_usage(index, before, &relative_path);
1971 Ok(size)
1972 }
1973
1974 fn create_dir(&mut self, path: &str) -> VfsResult<()> {
1975 let (index, relative_path) = self.resolve_writable_index(path)?;
1976 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1977 self.check_inode_growth(
1978 index,
1979 usize::from(!self.mounts[index].filesystem.exists(&relative_path)),
1980 )?;
1981 self.mounts[index].filesystem.create_dir(&relative_path)?;
1982 self.update_cached_path_usage(index, before, &relative_path);
1983 Ok(())
1984 }
1985
1986 fn create_dir_with_mode(&mut self, path: &str, mode: Option<u32>) -> VfsResult<()> {
1987 let (index, relative_path) = self.resolve_writable_index(path)?;
1988 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
1989 self.check_inode_growth(
1990 index,
1991 usize::from(!self.mounts[index].filesystem.exists(&relative_path)),
1992 )?;
1993 self.mounts[index]
1994 .filesystem
1995 .create_dir_with_mode(&relative_path, mode)?;
1996 self.update_cached_path_usage(index, before, &relative_path);
1997 Ok(())
1998 }
1999
2000 fn mkdir(&mut self, path: &str, recursive: bool) -> VfsResult<()> {
2001 let (index, relative_path) = self.resolve_writable_index(path)?;
2002 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2003 let added = if recursive {
2004 self.missing_directory_count(index, &relative_path)
2005 } else {
2006 usize::from(before.is_none())
2007 };
2008 self.check_inode_growth(index, added)?;
2009 self.mounts[index]
2010 .filesystem
2011 .mkdir(&relative_path, recursive)?;
2012 if recursive {
2013 if let Some(usage) = self.mounts[index].cached_usage.as_mut() {
2014 usage.inode_count = usage.inode_count.saturating_add(added);
2015 }
2016 } else {
2017 self.update_cached_path_usage(index, before, &relative_path);
2018 }
2019 Ok(())
2020 }
2021
2022 fn mknod(&mut self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
2023 let (index, relative_path) = self.resolve_writable_index(path)?;
2024 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2025 self.check_inode_growth(
2026 index,
2027 usize::from(!self.mounts[index].filesystem.exists(&relative_path)),
2028 )?;
2029 self.mounts[index]
2030 .filesystem
2031 .mknod(&relative_path, mode, rdev)?;
2032 self.update_cached_path_usage(index, before, &relative_path);
2033 Ok(())
2034 }
2035
2036 fn mkdir_with_mode(&mut self, path: &str, recursive: bool, mode: Option<u32>) -> VfsResult<()> {
2037 let (index, relative_path) = self.resolve_writable_index(path)?;
2038 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2039 let added = if recursive {
2040 self.missing_directory_count(index, &relative_path)
2041 } else {
2042 usize::from(before.is_none())
2043 };
2044 self.check_inode_growth(index, added)?;
2045 self.mounts[index]
2046 .filesystem
2047 .mkdir_with_mode(&relative_path, recursive, mode)?;
2048 if recursive {
2049 if let Some(usage) = self.mounts[index].cached_usage.as_mut() {
2050 usage.inode_count = usage.inode_count.saturating_add(added);
2051 }
2052 } else {
2053 self.update_cached_path_usage(index, before, &relative_path);
2054 }
2055 Ok(())
2056 }
2057
2058 fn exists(&self, path: &str) -> bool {
2059 self.resolve_content_index(path)
2062 .map(|(index, relative_path)| self.mounts[index].filesystem.exists(&relative_path))
2063 .unwrap_or(false)
2064 }
2065
2066 fn stat(&mut self, path: &str) -> VfsResult<VirtualStat> {
2067 let (index, relative_path) = self.resolve_content_index(path)?;
2068 self.mounts[index].filesystem.stat(&relative_path)
2069 }
2070
2071 fn remove_file(&mut self, path: &str) -> VfsResult<()> {
2072 let (index, relative_path) = self.resolve_writable_index(path)?;
2073 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2074 self.mounts[index].filesystem.remove_file(&relative_path)?;
2075 self.update_cached_path_usage(index, before, &relative_path);
2076 Ok(())
2077 }
2078
2079 fn remove_dir(&mut self, path: &str) -> VfsResult<()> {
2080 let (index, relative_path) = self.resolve_writable_index(path)?;
2081 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2082 self.mounts[index].filesystem.remove_dir(&relative_path)?;
2083 self.update_cached_path_usage(index, before, &relative_path);
2084 Ok(())
2085 }
2086
2087 fn rename(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
2088 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
2089 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
2090 if old_index != new_index {
2091 return Err(VfsError::new(
2092 "EXDEV",
2093 format!("rename across mounts: {old_path} -> {new_path}"),
2094 ));
2095 }
2096 self.ensure_writable(old_index, old_path)?;
2097 self.ensure_writable(new_index, new_path)?;
2098 let source = self.mounts[old_index]
2099 .filesystem
2100 .lstat(&old_relative_path)
2101 .ok();
2102 let replaced = self.mounts[new_index]
2103 .filesystem
2104 .lstat(&new_relative_path)
2105 .ok();
2106 self.mounts[old_index]
2107 .filesystem
2108 .rename(&old_relative_path, &new_relative_path)?;
2109 if let (Some(source), Some(replaced), Some(usage)) = (
2110 source,
2111 replaced,
2112 self.mounts[old_index].cached_usage.as_mut(),
2113 ) {
2114 if (source.dev, source.ino) != (replaced.dev, replaced.ino)
2115 && (replaced.is_directory || replaced.nlink <= 1)
2116 {
2117 usage.inode_count = usage.inode_count.saturating_sub(1);
2118 if !replaced.is_directory {
2119 usage.total_bytes = usage.total_bytes.saturating_sub(replaced.size);
2120 }
2121 }
2122 }
2123 Ok(())
2124 }
2125
2126 fn realpath(&self, path: &str) -> VfsResult<String> {
2127 let normalized = normalize_path(path);
2128 let (index, relative_path) = self.resolve_index(&normalized)?;
2129 let fallback_error = match self.realpath_in_mount(index, &relative_path) {
2130 Ok(resolved) => return Ok(resolved),
2131 Err(error) if error.code() == "ELOOP" => None,
2136 Err(error) if error.code() == "ENOENT" => Some(error),
2137 Err(error) => return Err(error),
2138 };
2139
2140 let mut pending = path_components(&normalized);
2141 let mut current = String::from("/");
2142 let mut followed_symlinks = 0usize;
2143
2144 while let Some(component) = pending.pop_front() {
2145 let candidate = join_path(¤t, &component);
2146 let stat = match self.lstat(&candidate) {
2147 Ok(stat) => stat,
2148 Err(error) => return Err(fallback_error.unwrap_or(error)),
2149 };
2150
2151 if stat.is_symbolic_link {
2152 followed_symlinks += 1;
2153 if followed_symlinks > MAX_REALPATH_SYMLINKS {
2154 return Err(VfsError::new(
2155 "ELOOP",
2156 format!("too many levels of symbolic links, '{path}'"),
2157 ));
2158 }
2159
2160 let (link_index, relative_path) = self.resolve_link_leaf_index(&candidate)?;
2166 let target = self.mounts[link_index]
2167 .filesystem
2168 .read_link(&relative_path)?;
2169 let target_path = if target.starts_with('/') {
2170 let mount_path = &self.mounts[link_index].path;
2171 let guest_absolute_target =
2172 target == *mount_path || target.starts_with(&format!("{mount_path}/"));
2173 if mount_path == "/" || guest_absolute_target {
2174 normalize_path(&target)
2175 } else {
2176 normalize_path(&format!(
2177 "{}/{}",
2178 mount_path,
2179 target.trim_start_matches('/')
2180 ))
2181 }
2182 } else {
2183 normalize_path(&format!("{}/{}", parent_path(&candidate), target))
2184 };
2185 let mut resolved_target = path_components(&target_path);
2186 resolved_target.extend(pending);
2187 pending = resolved_target;
2188 current = String::from("/");
2189 continue;
2190 }
2191
2192 if !pending.is_empty() && !stat.is_directory {
2193 return Err(VfsError::new(
2194 "ENOTDIR",
2195 format!("not a directory, realpath '{candidate}'"),
2196 ));
2197 }
2198
2199 current = candidate;
2200 }
2201
2202 if followed_symlinks == 0 {
2203 if let Some(error) = fallback_error {
2204 return Err(error);
2205 }
2206 }
2207 Ok(current)
2208 }
2209
2210 fn symlink(&mut self, target: &str, link_path: &str) -> VfsResult<()> {
2211 let normalized_link_path = normalize_path(link_path);
2212 let link_parent = parent_path(&normalized_link_path);
2213 let absolute_target = if target.starts_with('/') {
2214 normalize_path(target)
2215 } else {
2216 normalize_path(&format!("{link_parent}/{target}"))
2217 };
2218
2219 let (index, relative_path) = self.resolve_index(&normalized_link_path)?;
2220 let (target_index, _) = self.resolve_index(&absolute_target)?;
2221 if index != target_index {
2222 return Err(VfsError::new(
2223 "EXDEV",
2224 format!("symlink across mounts: {link_path} -> {target}"),
2225 ));
2226 }
2227 self.ensure_writable(index, link_path)?;
2228 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2229 self.check_file_growth(index, &relative_path, target.len() as u64, true)?;
2230
2231 self.mounts[index]
2232 .filesystem
2233 .symlink(target, &relative_path)?;
2234 self.update_cached_path_usage(index, before, &relative_path);
2235 Ok(())
2236 }
2237
2238 fn read_link(&self, path: &str) -> VfsResult<String> {
2239 let (index, relative_path) = self.resolve_link_leaf_index(path)?;
2240 self.mounts[index].filesystem.read_link(&relative_path)
2241 }
2242
2243 fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
2244 let (index, relative_path) = self.resolve_link_leaf_index(path)?;
2245 self.mounts[index].filesystem.lstat(&relative_path)
2246 }
2247
2248 fn link(&mut self, old_path: &str, new_path: &str) -> VfsResult<()> {
2249 let (old_index, old_relative_path) = self.resolve_index(old_path)?;
2250 let (new_index, new_relative_path) = self.resolve_index(new_path)?;
2251 if old_index != new_index {
2252 return Err(VfsError::new(
2253 "EXDEV",
2254 format!("link across mounts: {old_path} -> {new_path}"),
2255 ));
2256 }
2257 self.ensure_writable(new_index, new_path)?;
2258
2259 self.mounts[old_index]
2260 .filesystem
2261 .link(&old_relative_path, &new_relative_path)
2262 }
2263
2264 fn chmod(&mut self, path: &str, mode: u32) -> VfsResult<()> {
2265 let (index, relative_path) = self.resolve_writable_index(path)?;
2266 self.mounts[index].filesystem.chmod(&relative_path, mode)
2267 }
2268
2269 fn chown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
2270 self.chown_spec(path, uid, gid, true)
2271 }
2272
2273 fn chown_spec(
2274 &mut self,
2275 path: &str,
2276 uid: u32,
2277 gid: u32,
2278 follow_symlinks: bool,
2279 ) -> VfsResult<()> {
2280 let (index, relative_path) = if follow_symlinks {
2281 self.resolve_index(path)?
2282 } else {
2283 self.resolve_link_leaf_index(path)?
2284 };
2285 self.ensure_writable(index, path)?;
2286 self.mounts[index]
2287 .filesystem
2288 .chown_spec(&relative_path, uid, gid, follow_symlinks)
2289 }
2290
2291 fn lchown(&mut self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
2292 let (index, relative_path) = self.resolve_link_leaf_index(path)?;
2293 self.ensure_writable(index, path)?;
2294 self.mounts[index]
2295 .filesystem
2296 .lchown(&relative_path, uid, gid)
2297 }
2298
2299 fn get_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
2300 let (index, relative_path) = if follow_symlinks {
2301 self.resolve_content_index(path)?
2302 } else {
2303 self.resolve_link_leaf_index(path)?
2304 };
2305 self.mounts[index]
2306 .filesystem
2307 .get_xattr(&relative_path, name, follow_symlinks)
2308 }
2309
2310 fn list_xattrs(&mut self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
2311 let (index, relative_path) = if follow_symlinks {
2312 self.resolve_content_index(path)?
2313 } else {
2314 self.resolve_link_leaf_index(path)?
2315 };
2316 self.mounts[index]
2317 .filesystem
2318 .list_xattrs(&relative_path, follow_symlinks)
2319 }
2320
2321 fn set_xattr(
2322 &mut self,
2323 path: &str,
2324 name: &str,
2325 value: Vec<u8>,
2326 flags: u32,
2327 follow_symlinks: bool,
2328 ) -> VfsResult<()> {
2329 let (index, relative_path) = if follow_symlinks {
2330 self.resolve_content_index(path)?
2331 } else {
2332 self.resolve_link_leaf_index(path)?
2333 };
2334 self.ensure_writable(index, path)?;
2335 self.mounts[index]
2336 .filesystem
2337 .set_xattr(&relative_path, name, value, flags, follow_symlinks)
2338 }
2339
2340 fn remove_xattr(&mut self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
2341 let (index, relative_path) = if follow_symlinks {
2342 self.resolve_content_index(path)?
2343 } else {
2344 self.resolve_link_leaf_index(path)?
2345 };
2346 self.ensure_writable(index, path)?;
2347 self.mounts[index]
2348 .filesystem
2349 .remove_xattr(&relative_path, name, follow_symlinks)
2350 }
2351
2352 fn utimes(&mut self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
2353 let (index, relative_path) = self.resolve_writable_index(path)?;
2354 self.mounts[index]
2355 .filesystem
2356 .utimes(&relative_path, atime_ms, mtime_ms)
2357 }
2358
2359 fn utimes_spec(
2360 &mut self,
2361 path: &str,
2362 atime: VirtualUtimeSpec,
2363 mtime: VirtualUtimeSpec,
2364 follow_symlinks: bool,
2365 ) -> VfsResult<()> {
2366 let (index, relative_path) = self.resolve_writable_index(path)?;
2367 self.mounts[index]
2368 .filesystem
2369 .utimes_spec(&relative_path, atime, mtime, follow_symlinks)
2370 }
2371
2372 fn truncate(&mut self, path: &str, length: u64) -> VfsResult<()> {
2373 let (index, relative_path) = self.resolve_writable_index(path)?;
2374 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2375 self.check_file_growth(index, &relative_path, length, false)?;
2376 self.mounts[index]
2377 .filesystem
2378 .truncate(&relative_path, length)?;
2379 self.update_cached_path_usage(index, before, &relative_path);
2380 Ok(())
2381 }
2382
2383 fn sync(&mut self, path: &str) -> VfsResult<()> {
2384 let (index, relative_path) = self.resolve_content_index(path)?;
2385 self.mounts[index].filesystem.sync(&relative_path)
2386 }
2387
2388 fn allocate(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2389 let (index, relative_path) = self.resolve_content_index(path)?;
2390 self.ensure_writable(index, path)?;
2391 let before = self.mounts[index].filesystem.lstat(&relative_path)?;
2392 let current_size = before.size;
2393 self.check_file_growth(
2394 index,
2395 &relative_path,
2396 current_size.max(offset.saturating_add(length)),
2397 false,
2398 )?;
2399 self.mounts[index]
2400 .filesystem
2401 .allocate(&relative_path, offset, length)?;
2402 self.update_cached_path_usage(index, Some(before), &relative_path);
2403 Ok(())
2404 }
2405
2406 fn insert_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2407 let (index, relative_path) = self.resolve_content_index(path)?;
2408 self.ensure_writable(index, path)?;
2409 let before = self.mounts[index].filesystem.lstat(&relative_path)?;
2410 let current_size = before.size;
2411 self.check_file_growth(
2412 index,
2413 &relative_path,
2414 current_size.saturating_add(length),
2415 false,
2416 )?;
2417 self.mounts[index]
2418 .filesystem
2419 .insert_range(&relative_path, offset, length)?;
2420 self.update_cached_path_usage(index, Some(before), &relative_path);
2421 Ok(())
2422 }
2423
2424 fn collapse_range(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2425 let (index, relative_path) = self.resolve_content_index(path)?;
2426 self.ensure_writable(index, path)?;
2427 let before = self.mounts[index].filesystem.lstat(&relative_path)?;
2428 self.mounts[index]
2429 .filesystem
2430 .collapse_range(&relative_path, offset, length)?;
2431 self.update_cached_path_usage(index, Some(before), &relative_path);
2432 Ok(())
2433 }
2434
2435 fn zero_range(
2436 &mut self,
2437 path: &str,
2438 offset: u64,
2439 length: u64,
2440 keep_size: bool,
2441 ) -> VfsResult<()> {
2442 let (index, relative_path) = self.resolve_content_index(path)?;
2443 self.ensure_writable(index, path)?;
2444 let before = self.mounts[index].filesystem.lstat(&relative_path)?;
2445 if !keep_size {
2446 let current_size = before.size;
2447 self.check_file_growth(
2448 index,
2449 &relative_path,
2450 current_size.max(offset.saturating_add(length)),
2451 false,
2452 )?;
2453 }
2454 self.mounts[index]
2455 .filesystem
2456 .zero_range(&relative_path, offset, length, keep_size)?;
2457 self.update_cached_path_usage(index, Some(before), &relative_path);
2458 Ok(())
2459 }
2460
2461 fn punch_hole(&mut self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
2462 let (index, relative_path) = self.resolve_content_index(path)?;
2463 self.ensure_writable(index, path)?;
2464 self.mounts[index]
2465 .filesystem
2466 .punch_hole(&relative_path, offset, length)
2467 }
2468
2469 fn allocated_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
2470 let (index, relative_path) = self.resolve_content_index(path)?;
2471 self.mounts[index]
2472 .filesystem
2473 .allocated_ranges(&relative_path)
2474 }
2475
2476 fn unwritten_ranges(&mut self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
2477 let (index, relative_path) = self.resolve_content_index(path)?;
2478 self.mounts[index]
2479 .filesystem
2480 .unwritten_ranges(&relative_path)
2481 }
2482
2483 fn pread(&mut self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
2484 let (index, relative_path) = self.resolve_content_index(path)?;
2485 let before = self.atime_snapshot(index, &relative_path, false)?;
2486 let content = self.mounts[index]
2487 .filesystem
2488 .pread(&relative_path, offset, length)?;
2489 self.finish_atime_update(index, &relative_path, before)?;
2490 Ok(content)
2491 }
2492
2493 fn pwrite(&mut self, path: &str, content: impl Into<Vec<u8>>, offset: u64) -> VfsResult<()> {
2494 let content = content.into();
2495 let (index, relative_path) = self.resolve_content_index(path)?;
2496 self.ensure_writable(index, path)?;
2497 let before = self.mounts[index].filesystem.lstat(&relative_path).ok();
2498 let current_size = before.as_ref().map_or(0, |stat| stat.size);
2499 self.check_file_growth(
2500 index,
2501 &relative_path,
2502 current_size.max(offset.saturating_add(content.len() as u64)),
2503 false,
2504 )?;
2505 self.mounts[index]
2506 .filesystem
2507 .pwrite(&relative_path, content, offset)?;
2508 self.update_cached_path_usage(index, before, &relative_path);
2509 Ok(())
2510 }
2511}
2512
2513fn parse_mount_limit(option: &str, name: &str) -> VfsResult<u64> {
2514 let value = option
2515 .strip_prefix(&format!("{name}="))
2516 .ok_or_else(|| VfsError::new("EINVAL", format!("invalid mount option: {option}")))?;
2517 let parsed = value.parse::<u64>().map_err(|_| {
2518 VfsError::new(
2519 "EINVAL",
2520 format!("mount option {name} requires an unsigned integer"),
2521 )
2522 })?;
2523 if parsed == 0 {
2524 return Err(VfsError::new(
2525 "EINVAL",
2526 format!("mount option {name} must be greater than zero"),
2527 ));
2528 }
2529 Ok(parsed)
2530}
2531
2532fn check_usage_limits(
2533 usage: &FileSystemUsage,
2534 max_bytes: Option<u64>,
2535 max_inodes: Option<usize>,
2536) -> VfsResult<()> {
2537 if max_bytes.is_some_and(|limit| usage.total_bytes > limit) {
2538 return Err(VfsError::new(
2539 "ENOSPC",
2540 format!(
2541 "filesystem byte limit exceeded: {} bytes used",
2542 usage.total_bytes
2543 ),
2544 ));
2545 }
2546 if max_inodes.is_some_and(|limit| usage.inode_count > limit) {
2547 return Err(VfsError::new(
2548 "ENOSPC",
2549 format!(
2550 "filesystem inode limit exceeded: {} inodes used",
2551 usage.inode_count
2552 ),
2553 ));
2554 }
2555 Ok(())
2556}
2557
2558fn normalize_path(path: &str) -> String {
2559 let mut segments = Vec::new();
2560 for component in Path::new(path).components() {
2561 match component {
2562 Component::RootDir => segments.clear(),
2563 Component::ParentDir => {
2564 segments.pop();
2565 }
2566 Component::CurDir => {}
2567 Component::Normal(value) => segments.push(value.to_string_lossy().into_owned()),
2568 Component::Prefix(prefix) => {
2569 segments.push(prefix.as_os_str().to_string_lossy().into_owned());
2570 }
2571 }
2572 }
2573
2574 if segments.is_empty() {
2575 String::from("/")
2576 } else {
2577 format!("/{}", segments.join("/"))
2578 }
2579}
2580
2581fn timestamp_ns(milliseconds: u64, nanoseconds: u32) -> u128 {
2582 u128::from(milliseconds) * 1_000_000 + u128::from(nanoseconds % 1_000_000)
2583}
2584
2585fn path_components(path: &str) -> VecDeque<String> {
2586 normalize_path(path)
2587 .split('/')
2588 .filter(|part| !part.is_empty())
2589 .map(String::from)
2590 .collect()
2591}
2592
2593fn join_path(parent: &str, child: &str) -> String {
2594 if parent == "/" {
2595 format!("/{child}")
2596 } else {
2597 format!("{parent}/{child}")
2598 }
2599}
2600
2601fn parent_path(path: &str) -> String {
2602 let normalized = normalize_path(path);
2603 let parent = Path::new(&normalized)
2604 .parent()
2605 .unwrap_or_else(|| Path::new("/"));
2606 let value = parent.to_string_lossy();
2607 if value.is_empty() {
2608 String::from("/")
2609 } else {
2610 value.into_owned()
2611 }
2612}
2613
2614fn basename(path: &str) -> String {
2615 let normalized = normalize_path(path);
2616 Path::new(&normalized)
2617 .file_name()
2618 .map(|name| name.to_string_lossy().into_owned())
2619 .unwrap_or_else(|| String::from("/"))
2620}