virtual_fs/mem_fs/
file_opener.rs

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    /// Inserts a readonly file into the file system that uses copy-on-write
10    /// (this is required for zero-copy creation of the same file)
11    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            // The file already exists, then it can not be inserted.
24            Some(_inode_of_file) => return Err(FsError::AlreadyExists),
25
26            // The file doesn't already exist; it's OK to create it if
27            None => {
28                // Write lock.
29                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                // Creating the file in the storage.
35                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                // Adding the new directory to its parent.
63                fs.add_child_to_node(inode_of_parent, inode_of_file)?;
64
65                inode_of_file
66            }
67        };
68        Ok(())
69    }
70
71    /// Inserts a arc file into the file system that references another file
72    /// in another file system (does not copy the real data)
73    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            // The file already exists, then it can not be inserted.
92            Some(_inode_of_file) => return Err(FsError::AlreadyExists),
93
94            // The file doesn't already exist; it's OK to create it if
95            None => {
96                // Write lock.
97                let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
98
99                // Read the metadata or generate a dummy one
100                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                // Creating the file in the storage.
118                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                // Adding the new directory to its parent.
134                fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
135
136                inode_of_file
137            }
138        };
139        Ok(())
140    }
141
142    /// Inserts a arc file into the file system that references another file
143    /// in another file system (does not copy the real data)
144    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    /// Inserts a arc directory into the file system that references another file
153    /// in another file system (does not copy the real data)
154    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            // The file already exists, then it can not be inserted.
173            Some(_inode_of_file) => return Err(FsError::AlreadyExists),
174
175            // The file doesn't already exist; it's OK to create it if
176            None => {
177                // Write lock.
178                let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
179
180                // Creating the file in the storage.
181                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                // Adding the new directory to its parent.
209                fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
210
211                inode_of_file
212            }
213        };
214        Ok(())
215    }
216
217    /// Inserts a arc directory into the file system that references another file
218    /// in another file system (does not copy the real data)
219    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    /// Inserts a arc file into the file system that references another file
228    /// in another file system (does not copy the real data)
229    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                // TODO: should remove the inode again!
242                return Err(FsError::InvalidInput);
243            }
244        };
245
246        if let Some(_inode_of_file) = maybe_inode_of_file {
247            // TODO: restore previous inode?
248            return Err(FsError::AlreadyExists);
249        }
250        // Write lock.
251        let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
252
253        // Creating the file in the storage.
254        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        // Adding the new directory to its parent.
281        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        // Read lock.
291        let fs = self.inner.read().map_err(|_| FsError::Lock)?;
292
293        // Check the path has a parent.
294        let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
295
296        // Check the file name.
297        let name_of_file = path
298            .file_name()
299            .ok_or(FsError::InvalidInput)?
300            .to_os_string();
301
302        // Find the parent inode.
303        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        // Find the inode of the file if it exists.
315        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` is used, `create` and `truncate ` are ignored.
343        if create_new {
344            create = false;
345            truncate = false;
346        }
347
348        // To truncate a file, `write` must be used.
349        if truncate && !write {
350            return Err(FsError::PermissionDenied);
351        }
352
353        // `append` is semantically equivalent to `write` + `append`
354        // but let's keep them exclusive.
355        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            // The file already exists, and a _new_ one _must_ be
382            // created; it's not OK.
383            Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
384
385            // The file already exists; it's OK.
386            Some(inode_of_file) => {
387                // Write lock.
388                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                        // Update the accessed time.
398                        metadata.accessed = time();
399
400                        // Truncate if needed.
401                        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                        // Update the accessed time.
416                        metadata.accessed = time();
417
418                        // Truncate if needed.
419                        if truncate {
420                            file.truncate();
421                            metadata.len = 0;
422                        }
423
424                        lifecycle.clone()
425                    }
426
427                    Some(Node::ReadOnlyFile(node)) => {
428                        // Update the accessed time.
429                        node.metadata.accessed = time();
430
431                        // Truncate if needed.
432                        if truncate || append {
433                            return Err(FsError::PermissionDenied);
434                        }
435
436                        node.lifecycle.clone()
437                    }
438
439                    Some(Node::CustomFile(node)) => {
440                        // Update the accessed time.
441                        node.metadata.accessed = time();
442
443                        // Truncate if needed.
444                        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                        // Update the accessed time.
455                        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                        // Truncate if needed.
469                        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            // The file doesn't already exist; it's OK to create it if:
486            // 1. `create_new` is used with `write` or `append`,
487            // 2. `create` is used with `write` or `append`.
488            None if (create_new || create) && (create_new || write || append) => {
489                // Write lock.
490                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                // We might be in optimized mode
509                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                // Creating the file in the storage.
533                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                // Adding the new directory to its parent.
541                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}