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