1use super::filesystem::InodeResolution;
2use super::*;
3use crate::{FileType, FsError, Metadata, OpenOptionsConfig, Result, VirtualFile};
4use shared_buffer::OwnedBuffer;
5use std::path::Path;
6use tracing::*;
7
8impl FileSystem {
9 pub fn insert_ro_file(&self, path: &Path, contents: OwnedBuffer) -> Result<()> {
12 let _ = crate::FileSystem::remove_file(self, path);
13 let (inode_of_parent, maybe_inode_of_file, name_of_file) = self.insert_inode(path)?;
14
15 let inode_of_parent = match inode_of_parent {
16 InodeResolution::Found(a) => a,
17 InodeResolution::Redirect(..) => {
18 return Err(FsError::InvalidInput);
19 }
20 };
21
22 match maybe_inode_of_file {
23 Some(_inode_of_file) => return Err(FsError::AlreadyExists),
25
26 None => {
28 let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
30
31 let file = ReadOnlyFile::new(contents);
32 let file_len = file.len() as u64;
33
34 let inode_of_file = fs.storage.vacant_entry().key();
36 let real_inode_of_file = fs.storage.insert(Node::ReadOnlyFile(ReadOnlyFileNode {
37 inode: inode_of_file,
38 name: name_of_file,
39 file,
40 metadata: {
41 let time = time();
42
43 Metadata {
44 ft: FileType {
45 file: true,
46 ..Default::default()
47 },
48 accessed: time,
49 created: time,
50 modified: time,
51 len: file_len,
52 }
53 },
54 lifecycle: Arc::default(),
55 }));
56
57 assert_eq!(
58 inode_of_file, real_inode_of_file,
59 "new file inode should have been correctly calculated",
60 );
61
62 fs.add_child_to_node(inode_of_parent, inode_of_file)?;
64
65 inode_of_file
66 }
67 };
68 Ok(())
69 }
70
71 pub fn insert_arc_file_at(
74 &self,
75 target_path: PathBuf,
76 fs: Arc<dyn crate::FileSystem + Send + Sync>,
77 source_path: PathBuf,
78 ) -> Result<()> {
79 let _ = crate::FileSystem::remove_file(self, target_path.as_path());
80 let (inode_of_parent, maybe_inode_of_file, name_of_file) =
81 self.insert_inode(target_path.as_path())?;
82
83 let inode_of_parent = match inode_of_parent {
84 InodeResolution::Found(a) => a,
85 InodeResolution::Redirect(..) => {
86 return Err(FsError::InvalidInput);
87 }
88 };
89
90 match maybe_inode_of_file {
91 Some(_inode_of_file) => return Err(FsError::AlreadyExists),
93
94 None => {
96 let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
98
99 let meta = match fs.metadata(&target_path) {
101 Ok(meta) => meta,
102 _ => {
103 let time = time();
104 Metadata {
105 ft: FileType {
106 file: true,
107 ..Default::default()
108 },
109 accessed: time,
110 created: time,
111 modified: time,
112 len: 0,
113 }
114 }
115 };
116
117 let inode_of_file = fs_lock.storage.vacant_entry().key();
119 let real_inode_of_file = fs_lock.storage.insert(Node::ArcFile(ArcFileNode {
120 inode: inode_of_file,
121 name: name_of_file,
122 fs,
123 path: source_path,
124 metadata: meta,
125 lifecycle: Arc::default(),
126 }));
127
128 assert_eq!(
129 inode_of_file, real_inode_of_file,
130 "new file inode should have been correctly calculated",
131 );
132
133 fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
135
136 inode_of_file
137 }
138 };
139 Ok(())
140 }
141
142 pub fn insert_arc_file(
145 &self,
146 target_path: PathBuf,
147 fs: Arc<dyn crate::FileSystem + Send + Sync>,
148 ) -> Result<()> {
149 self.insert_arc_file_at(target_path.clone(), fs, target_path)
150 }
151
152 pub fn insert_arc_directory_at(
155 &self,
156 target_path: PathBuf,
157 other: Arc<dyn crate::FileSystem + Send + Sync>,
158 source_path: PathBuf,
159 ) -> Result<()> {
160 let _ = crate::FileSystem::remove_dir(self, target_path.as_path());
161 let (inode_of_parent, maybe_inode_of_file, name_of_file) =
162 self.insert_inode(target_path.as_path())?;
163
164 let inode_of_parent = match inode_of_parent {
165 InodeResolution::Found(a) => a,
166 InodeResolution::Redirect(..) => {
167 return Err(FsError::InvalidInput);
168 }
169 };
170
171 match maybe_inode_of_file {
172 Some(_inode_of_file) => return Err(FsError::AlreadyExists),
174
175 None => {
177 let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
179
180 let inode_of_file = fs_lock.storage.vacant_entry().key();
182 let real_inode_of_file =
183 fs_lock.storage.insert(Node::ArcDirectory(ArcDirectoryNode {
184 inode: inode_of_file,
185 name: name_of_file,
186 fs: other,
187 path: source_path,
188 metadata: {
189 let time = time();
190 Metadata {
191 ft: FileType {
192 file: true,
193 ..Default::default()
194 },
195 accessed: time,
196 created: time,
197 modified: time,
198 len: 0,
199 }
200 },
201 }));
202
203 assert_eq!(
204 inode_of_file, real_inode_of_file,
205 "new file inode should have been correctly calculated",
206 );
207
208 fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
210
211 inode_of_file
212 }
213 };
214 Ok(())
215 }
216
217 pub fn insert_arc_directory(
220 &self,
221 target_path: PathBuf,
222 other: Arc<dyn crate::FileSystem + Send + Sync>,
223 ) -> Result<()> {
224 self.insert_arc_directory_at(target_path.clone(), other, target_path)
225 }
226
227 pub fn insert_device_file(
230 &self,
231 path: PathBuf,
232 file: Box<dyn crate::VirtualFile + Send + Sync>,
233 ) -> Result<()> {
234 let _ = crate::FileSystem::remove_file(self, path.as_path());
235 let (inode_of_parent, maybe_inode_of_file, name_of_file) =
236 self.insert_inode(path.as_path())?;
237
238 let inode_of_parent = match inode_of_parent {
239 InodeResolution::Found(a) => a,
240 InodeResolution::Redirect(..) => {
241 return Err(FsError::InvalidInput);
243 }
244 };
245
246 if let Some(_inode_of_file) = maybe_inode_of_file {
247 return Err(FsError::AlreadyExists);
249 }
250 let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
252
253 let inode_of_file = fs_lock.storage.vacant_entry().key();
255 let real_inode_of_file = fs_lock.storage.insert(Node::CustomFile(CustomFileNode {
256 inode: inode_of_file,
257 name: name_of_file,
258 file: Mutex::new(file),
259 metadata: {
260 let time = time();
261 Metadata {
262 ft: FileType {
263 file: true,
264 ..Default::default()
265 },
266 accessed: time,
267 created: time,
268 modified: time,
269 len: 0,
270 }
271 },
272 lifecycle: Arc::default(),
273 }));
274
275 assert_eq!(
276 inode_of_file, real_inode_of_file,
277 "new file inode should have been correctly calculated",
278 );
279
280 fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
282
283 Ok(())
284 }
285
286 fn insert_inode(
287 &self,
288 path: &Path,
289 ) -> Result<(InodeResolution, Option<InodeResolution>, OsString)> {
290 let fs = self.inner.read().map_err(|_| FsError::Lock)?;
292
293 let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
295
296 let name_of_file = path
298 .file_name()
299 .ok_or(FsError::InvalidInput)?
300 .to_os_string();
301
302 let inode_of_parent = match fs.inode_of_parent(parent_of_path)? {
304 InodeResolution::Found(a) => a,
305 InodeResolution::Redirect(fs, parent_path) => {
306 return Ok((
307 InodeResolution::Redirect(fs, parent_path),
308 None,
309 name_of_file,
310 ));
311 }
312 };
313
314 let maybe_inode_of_file = fs
316 .as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?
317 .map(|(_nth, inode)| inode);
318
319 Ok((
320 InodeResolution::Found(inode_of_parent),
321 maybe_inode_of_file,
322 name_of_file,
323 ))
324 }
325}
326
327impl crate::FileOpener for FileSystem {
328 fn open(
329 &self,
330 path: &Path,
331 conf: &OpenOptionsConfig,
332 ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
333 debug!(path=%path.display(), "open");
334
335 let read = conf.read();
336 let mut write = conf.write();
337 let append = conf.append();
338 let mut truncate = conf.truncate();
339 let mut create = conf.create();
340 let create_new = conf.create_new();
341
342 if create_new {
344 create = false;
345 truncate = false;
346 }
347
348 if truncate && !write {
350 return Err(FsError::PermissionDenied);
351 }
352
353 if append {
356 write = false;
357 }
358
359 let (inode_of_parent, maybe_inode_of_file, name_of_file) = self.insert_inode(path)?;
360
361 let inode_of_parent = match inode_of_parent {
362 InodeResolution::Found(a) => a,
363 InodeResolution::Redirect(fs, mut parent_path) => {
364 parent_path.push(name_of_file);
365 return fs
366 .new_open_options()
367 .options(conf.clone())
368 .open(parent_path);
369 }
370 };
371 let maybe_inode_of_file = match maybe_inode_of_file {
372 Some(InodeResolution::Found(inode)) => Some(inode),
373 Some(InodeResolution::Redirect(fs, path)) => {
374 return fs.new_open_options().options(conf.clone()).open(path);
375 }
376 None => None,
377 };
378
379 let cursor = 0u64;
380 let (inode_of_file, handle_lifecycle) = match maybe_inode_of_file {
381 Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
384
385 Some(inode_of_file) => {
387 let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
389
390 let handle_lifecycle = match fs.storage.get_mut(inode_of_file) {
391 Some(Node::File(FileNode {
392 metadata,
393 file,
394 lifecycle,
395 ..
396 })) => {
397 metadata.accessed = time();
399
400 if truncate {
402 file.truncate();
403 metadata.len = 0;
404 }
405
406 lifecycle.clone()
407 }
408
409 Some(Node::OffloadedFile(OffloadedFileNode {
410 metadata,
411 file,
412 lifecycle,
413 ..
414 })) => {
415 metadata.accessed = time();
417
418 if truncate {
420 file.truncate();
421 metadata.len = 0;
422 }
423
424 lifecycle.clone()
425 }
426
427 Some(Node::ReadOnlyFile(node)) => {
428 node.metadata.accessed = time();
430
431 if truncate || append {
433 return Err(FsError::PermissionDenied);
434 }
435
436 node.lifecycle.clone()
437 }
438
439 Some(Node::CustomFile(node)) => {
440 node.metadata.accessed = time();
442
443 let mut file = node.file.lock().unwrap();
445 if truncate {
446 file.set_len(0)?;
447 node.metadata.len = 0;
448 }
449
450 node.lifecycle.clone()
451 }
452
453 Some(Node::ArcFile(node)) => {
454 node.metadata.accessed = time();
456
457 let mut file = node
458 .fs
459 .new_open_options()
460 .read(read)
461 .write(write)
462 .append(append)
463 .truncate(truncate)
464 .create(create)
465 .create_new(create_new)
466 .open(node.path.as_path())?;
467
468 if truncate {
470 file.set_len(0)?;
471 node.metadata.len = 0;
472 }
473
474 node.lifecycle.clone()
475 }
476
477 None => return Err(FsError::EntryNotFound),
478 _ => return Err(FsError::NotAFile),
479 };
480
481 handle_lifecycle.opened();
482 (inode_of_file, handle_lifecycle)
483 }
484
485 None if (create_new || create) && (create_new || write || append) => {
489 let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
491
492 let handle_lifecycle: Arc<FileLifecycle> = Arc::default();
493 let metadata = {
494 let time = time();
495 Metadata {
496 ft: FileType {
497 file: true,
498 ..Default::default()
499 },
500 accessed: time,
501 created: time,
502 modified: time,
503 len: 0,
504 }
505 };
506 let inode_of_file = fs.storage.vacant_entry().key();
507
508 let file = match fs.backing_offload.clone() {
510 Some(offload) => {
511 let file = OffloadedFile::new(fs.limiter.clone(), offload);
512 Node::OffloadedFile(OffloadedFileNode {
513 inode: inode_of_file,
514 name: name_of_file,
515 file,
516 metadata,
517 lifecycle: handle_lifecycle.clone(),
518 })
519 }
520 _ => {
521 let file = File::new(fs.limiter.clone());
522 Node::File(FileNode {
523 inode: inode_of_file,
524 name: name_of_file,
525 file,
526 metadata,
527 lifecycle: handle_lifecycle.clone(),
528 })
529 }
530 };
531
532 let real_inode_of_file = fs.storage.insert(file);
534
535 assert_eq!(
536 inode_of_file, real_inode_of_file,
537 "new file inode should have been correctly calculated",
538 );
539
540 fs.add_child_to_node(inode_of_parent, inode_of_file)?;
542
543 handle_lifecycle.opened();
544 (inode_of_file, handle_lifecycle)
545 }
546
547 None if (create_new || create) => return Err(FsError::PermissionDenied),
548
549 None => return Err(FsError::EntryNotFound),
550 };
551
552 #[cfg(test)]
553 test_file_opener::run_open_before_handle_hook();
554
555 Ok(Box::new(FileHandle::new_opened(
556 inode_of_file,
557 self.clone(),
558 handle_lifecycle,
559 read,
560 write || append || truncate,
561 append,
562 cursor,
563 )))
564 }
565}
566
567#[cfg(test)]
568mod test_file_opener {
569 use std::cell::RefCell;
570 use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
571
572 use crate::{FileSystem as FS, FsError, mem_fs::*};
573 use std::io;
574 use std::sync::Arc;
575
576 macro_rules! path {
577 ($path:expr) => {
578 std::path::Path::new($path)
579 };
580 }
581
582 type OpenBeforeHandleHook = Box<dyn FnOnce() + 'static>;
583
584 #[cfg(test)]
585 thread_local! {
586 static OPEN_BEFORE_HANDLE_HOOK: RefCell<Option<OpenBeforeHandleHook>> = RefCell::new(None);
587 }
588
589 struct OpenBeforeHandleHookGuard;
590
591 impl OpenBeforeHandleHookGuard {
592 fn install(hook: OpenBeforeHandleHook) -> Self {
593 OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
594 let previous = slot.borrow_mut().replace(hook);
595 assert!(
596 previous.is_none(),
597 "open-before-handle test hook should not already be installed"
598 );
599 });
600 Self
601 }
602 }
603
604 impl Drop for OpenBeforeHandleHookGuard {
605 fn drop(&mut self) {
606 OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
607 slot.borrow_mut().take();
608 });
609 }
610 }
611
612 #[cfg(test)]
613 pub(super) fn run_open_before_handle_hook() {
614 OPEN_BEFORE_HANDLE_HOOK.with(|slot| {
615 if let Some(hook) = slot.borrow_mut().take() {
616 hook();
617 }
618 });
619 }
620
621 #[tokio::test]
622 async fn test_create_new_file() {
623 let fs = FileSystem::default();
624
625 assert!(
626 fs.new_open_options()
627 .write(true)
628 .create_new(true)
629 .open(path!("/foo.txt"))
630 .is_ok(),
631 "creating a new file",
632 );
633
634 {
635 let fs_inner = fs.inner.read().unwrap();
636
637 assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
638 assert!(
639 matches!(
640 fs_inner.storage.get(ROOT_INODE),
641 Some(Node::Directory(DirectoryNode {
642 inode: ROOT_INODE,
643 name,
644 children,
645 ..
646 })) if name == "/" && children == &[1]
647 ),
648 "`/` contains `foo.txt`",
649 );
650 assert!(
651 matches!(
652 fs_inner.storage.get(1),
653 Some(Node::File(FileNode {
654 inode: 1,
655 name,
656 ..
657 })) if name == "foo.txt"
658 ),
659 "`foo.txt` exists and is a file",
660 );
661 }
662
663 assert!(
664 matches!(
665 fs.new_open_options()
666 .write(true)
667 .create_new(true)
668 .open(path!("/foo.txt")),
669 Err(FsError::AlreadyExists)
670 ),
671 "creating a new file that already exist",
672 );
673
674 assert_eq!(
675 fs.new_open_options()
676 .write(true)
677 .create_new(true)
678 .open(path!("/foo/bar.txt"))
679 .map(|_| ()),
680 Err(FsError::EntryNotFound),
681 "creating a file in a directory that doesn't exist",
682 );
683
684 assert_eq!(fs.remove_file(path!("/foo.txt")), Ok(()), "removing a file");
685
686 assert!(
687 fs.new_open_options()
688 .write(false)
689 .create_new(true)
690 .open(path!("/foo.txt"))
691 .is_ok(),
692 "creating a file without the `write` option",
693 );
694 }
695
696 #[tokio::test]
697 async fn test_truncate_a_read_only_file() {
698 let fs = FileSystem::default();
699
700 assert!(
701 matches!(
702 fs.new_open_options()
703 .write(false)
704 .truncate(true)
705 .open(path!("/foo.txt")),
706 Err(FsError::PermissionDenied),
707 ),
708 "truncating a read-only file",
709 );
710 }
711
712 #[tokio::test]
713 async fn test_truncate() {
714 let fs = FileSystem::default();
715
716 let mut file = fs
717 .new_open_options()
718 .write(true)
719 .create_new(true)
720 .open(path!("/foo.txt"))
721 .expect("failed to create a new file");
722
723 assert!(
724 matches!(file.write(b"foobar").await, Ok(6)),
725 "writing `foobar` at the end of the file",
726 );
727
728 assert!(
729 matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
730 "checking the current position is 6",
731 );
732 assert!(
733 matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
734 "checking the size is 6",
735 );
736
737 let mut file = fs
738 .new_open_options()
739 .write(true)
740 .truncate(true)
741 .open(path!("/foo.txt"))
742 .expect("failed to open + truncate `foo.txt`");
743
744 assert!(
745 matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
746 "checking the current position is 0",
747 );
748 assert!(
749 matches!(file.seek(io::SeekFrom::End(0)).await, Ok(0)),
750 "checking the size is 0",
751 );
752 }
753
754 #[tokio::test]
755 async fn test_append() {
756 let fs = FileSystem::default();
757
758 let mut file = fs
759 .new_open_options()
760 .write(true)
761 .create_new(true)
762 .open(path!("/foo.txt"))
763 .expect("failed to create a new file");
764
765 assert!(
766 matches!(file.write(b"foobar").await, Ok(6)),
767 "writing `foobar` at the end of the file",
768 );
769
770 assert!(
771 matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
772 "checking the current position is 6",
773 );
774 assert!(
775 matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
776 "checking the size is 6",
777 );
778
779 let mut file = fs
780 .new_open_options()
781 .append(true)
782 .open(path!("/foo.txt"))
783 .expect("failed to open `foo.txt`");
784
785 assert!(
786 matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
787 "checking the current position in append-mode is 0",
788 );
789 assert!(
790 matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
791 "trying to rewind in append-mode",
792 );
793 assert!(matches!(file.write(b"baz").await, Ok(3)), "writing `baz`");
794
795 let mut file = fs
796 .new_open_options()
797 .read(true)
798 .open(path!("/foo.txt"))
799 .expect("failed to open `foo.txt");
800
801 assert!(
802 matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
803 "checking the current position is read-mode is 0",
804 );
805
806 let mut string = String::new();
807 assert!(
808 matches!(file.read_to_string(&mut string).await, Ok(9)),
809 "reading the entire `foo.txt` file",
810 );
811 assert_eq!(
812 string, "foobarbaz",
813 "checking append-mode is ignoring seek operations",
814 );
815 }
816
817 #[tokio::test]
818 async fn test_opening_a_file_that_already_exists() {
819 let fs = FileSystem::default();
820
821 assert!(
822 fs.new_open_options()
823 .write(true)
824 .create_new(true)
825 .open(path!("/foo.txt"))
826 .is_ok(),
827 "creating a _new_ file",
828 );
829
830 assert!(
831 matches!(
832 fs.new_open_options()
833 .create_new(true)
834 .open(path!("/foo.txt")),
835 Err(FsError::AlreadyExists),
836 ),
837 "creating a _new_ file that already exists",
838 );
839
840 assert!(
841 fs.new_open_options()
842 .read(true)
843 .open(path!("/foo.txt"))
844 .is_ok(),
845 "opening a file that already exists",
846 );
847 }
848
849 #[tokio::test]
850 async fn test_opening_existing_file_in_arc_directory_redirects() {
851 let fs = FileSystem::default();
852 let backing = FileSystem::default();
853 let backing_arc: Arc<dyn crate::FileSystem + Send + Sync> = Arc::new(backing.clone());
854
855 backing
856 .new_open_options()
857 .write(true)
858 .create_new(true)
859 .open(path!("/foo.txt"))
860 .expect("create file in backing fs");
861
862 fs.insert_arc_directory_at(
863 path!("/mnt").to_path_buf(),
864 backing_arc,
865 path!("/").to_path_buf(),
866 )
867 .expect("mount arc directory");
868
869 let mut file = fs
870 .new_open_options()
871 .read(true)
872 .open(path!("/mnt/foo.txt"))
873 .expect("open should redirect to the backing fs");
874
875 let mut contents = String::new();
876 file.read_to_string(&mut contents)
877 .await
878 .expect("read redirected file");
879 assert_eq!(contents, "");
880
881 assert!(
882 matches!(
883 fs.new_open_options()
884 .create_new(true)
885 .open(path!("/mnt/foo.txt")),
886 Err(FsError::AlreadyExists),
887 ),
888 "create_new on a redirected existing file follows the backing fs result",
889 );
890 }
891
892 #[tokio::test(flavor = "current_thread")]
893 async fn test_open_keeps_unlinked_inode_alive_before_returning_handle() {
894 let fs = FileSystem::default();
895
896 fs.new_open_options()
897 .write(true)
898 .create_new(true)
899 .open(path!("/foo.txt"))
900 .expect("create /foo.txt");
901
902 let _hook = OpenBeforeHandleHookGuard::install(Box::new({
903 let fs = fs.clone();
904 move || {
905 assert_eq!(
906 fs.remove_file(path!("/foo.txt")),
907 Ok(()),
908 "unlink during the open-return window",
909 );
910 }
911 }));
912
913 let mut file = fs
914 .new_open_options()
915 .read(true)
916 .write(true)
917 .open(path!("/foo.txt"))
918 .expect(
919 "open should succeed even if the path is unlinked before the handle is returned",
920 );
921
922 assert_eq!(
923 fs.metadata(path!("/foo.txt")),
924 Err(FsError::EntryNotFound),
925 "the path is gone immediately after unlink",
926 );
927
928 file.write_all(b"hello")
929 .await
930 .expect("write after the in-flight unlink should still work");
931 file.seek(io::SeekFrom::Start(0))
932 .await
933 .expect("rewind after in-flight unlink");
934
935 let mut contents = Vec::new();
936 file.read_to_end(&mut contents)
937 .await
938 .expect("read after the in-flight unlink should still work");
939 assert_eq!(contents, b"hello");
940 }
941}