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                }));
55
56                assert_eq!(
57                    inode_of_file, real_inode_of_file,
58                    "new file inode should have been correctly calculated",
59                );
60
61                // Adding the new directory to its parent.
62                fs.add_child_to_node(inode_of_parent, inode_of_file)?;
63
64                inode_of_file
65            }
66        };
67        Ok(())
68    }
69
70    /// Inserts a arc file into the file system that references another file
71    /// in another file system (does not copy the real data)
72    pub fn insert_arc_file_at(
73        &self,
74        target_path: PathBuf,
75        fs: Arc<dyn crate::FileSystem + Send + Sync>,
76        source_path: PathBuf,
77    ) -> Result<()> {
78        let _ = crate::FileSystem::remove_file(self, target_path.as_path());
79        let (inode_of_parent, maybe_inode_of_file, name_of_file) =
80            self.insert_inode(target_path.as_path())?;
81
82        let inode_of_parent = match inode_of_parent {
83            InodeResolution::Found(a) => a,
84            InodeResolution::Redirect(..) => {
85                return Err(FsError::InvalidInput);
86            }
87        };
88
89        match maybe_inode_of_file {
90            // The file already exists, then it can not be inserted.
91            Some(_inode_of_file) => return Err(FsError::AlreadyExists),
92
93            // The file doesn't already exist; it's OK to create it if
94            None => {
95                // Write lock.
96                let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
97
98                // Read the metadata or generate a dummy one
99                let meta = match fs.metadata(&target_path) {
100                    Ok(meta) => meta,
101                    _ => {
102                        let time = time();
103                        Metadata {
104                            ft: FileType {
105                                file: true,
106                                ..Default::default()
107                            },
108                            accessed: time,
109                            created: time,
110                            modified: time,
111                            len: 0,
112                        }
113                    }
114                };
115
116                // Creating the file in the storage.
117                let inode_of_file = fs_lock.storage.vacant_entry().key();
118                let real_inode_of_file = fs_lock.storage.insert(Node::ArcFile(ArcFileNode {
119                    inode: inode_of_file,
120                    name: name_of_file,
121                    fs,
122                    path: source_path,
123                    metadata: meta,
124                }));
125
126                assert_eq!(
127                    inode_of_file, real_inode_of_file,
128                    "new file inode should have been correctly calculated",
129                );
130
131                // Adding the new directory to its parent.
132                fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
133
134                inode_of_file
135            }
136        };
137        Ok(())
138    }
139
140    /// Inserts a arc file into the file system that references another file
141    /// in another file system (does not copy the real data)
142    pub fn insert_arc_file(
143        &self,
144        target_path: PathBuf,
145        fs: Arc<dyn crate::FileSystem + Send + Sync>,
146    ) -> Result<()> {
147        self.insert_arc_file_at(target_path.clone(), fs, target_path)
148    }
149
150    /// Inserts a arc directory into the file system that references another file
151    /// in another file system (does not copy the real data)
152    pub fn insert_arc_directory_at(
153        &self,
154        target_path: PathBuf,
155        other: Arc<dyn crate::FileSystem + Send + Sync>,
156        source_path: PathBuf,
157    ) -> Result<()> {
158        let _ = crate::FileSystem::remove_dir(self, target_path.as_path());
159        let (inode_of_parent, maybe_inode_of_file, name_of_file) =
160            self.insert_inode(target_path.as_path())?;
161
162        let inode_of_parent = match inode_of_parent {
163            InodeResolution::Found(a) => a,
164            InodeResolution::Redirect(..) => {
165                return Err(FsError::InvalidInput);
166            }
167        };
168
169        match maybe_inode_of_file {
170            // The file already exists, then it can not be inserted.
171            Some(_inode_of_file) => return Err(FsError::AlreadyExists),
172
173            // The file doesn't already exist; it's OK to create it if
174            None => {
175                // Write lock.
176                let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
177
178                // Creating the file in the storage.
179                let inode_of_file = fs_lock.storage.vacant_entry().key();
180                let real_inode_of_file =
181                    fs_lock.storage.insert(Node::ArcDirectory(ArcDirectoryNode {
182                        inode: inode_of_file,
183                        name: name_of_file,
184                        fs: other,
185                        path: source_path,
186                        metadata: {
187                            let time = time();
188                            Metadata {
189                                ft: FileType {
190                                    file: true,
191                                    ..Default::default()
192                                },
193                                accessed: time,
194                                created: time,
195                                modified: time,
196                                len: 0,
197                            }
198                        },
199                    }));
200
201                assert_eq!(
202                    inode_of_file, real_inode_of_file,
203                    "new file inode should have been correctly calculated",
204                );
205
206                // Adding the new directory to its parent.
207                fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
208
209                inode_of_file
210            }
211        };
212        Ok(())
213    }
214
215    /// Inserts a arc directory into the file system that references another file
216    /// in another file system (does not copy the real data)
217    pub fn insert_arc_directory(
218        &self,
219        target_path: PathBuf,
220        other: Arc<dyn crate::FileSystem + Send + Sync>,
221    ) -> Result<()> {
222        self.insert_arc_directory_at(target_path.clone(), other, target_path)
223    }
224
225    /// Inserts a arc file into the file system that references another file
226    /// in another file system (does not copy the real data)
227    pub fn insert_device_file(
228        &self,
229        path: PathBuf,
230        file: Box<dyn crate::VirtualFile + Send + Sync>,
231    ) -> Result<()> {
232        let _ = crate::FileSystem::remove_file(self, path.as_path());
233        let (inode_of_parent, maybe_inode_of_file, name_of_file) =
234            self.insert_inode(path.as_path())?;
235
236        let inode_of_parent = match inode_of_parent {
237            InodeResolution::Found(a) => a,
238            InodeResolution::Redirect(..) => {
239                // TODO: should remove the inode again!
240                return Err(FsError::InvalidInput);
241            }
242        };
243
244        if let Some(_inode_of_file) = maybe_inode_of_file {
245            // TODO: restore previous inode?
246            return Err(FsError::AlreadyExists);
247        }
248        // Write lock.
249        let mut fs_lock = self.inner.write().map_err(|_| FsError::Lock)?;
250
251        // Creating the file in the storage.
252        let inode_of_file = fs_lock.storage.vacant_entry().key();
253        let real_inode_of_file = fs_lock.storage.insert(Node::CustomFile(CustomFileNode {
254            inode: inode_of_file,
255            name: name_of_file,
256            file: Mutex::new(file),
257            metadata: {
258                let time = time();
259                Metadata {
260                    ft: FileType {
261                        file: true,
262                        ..Default::default()
263                    },
264                    accessed: time,
265                    created: time,
266                    modified: time,
267                    len: 0,
268                }
269            },
270        }));
271
272        assert_eq!(
273            inode_of_file, real_inode_of_file,
274            "new file inode should have been correctly calculated",
275        );
276
277        // Adding the new directory to its parent.
278        fs_lock.add_child_to_node(inode_of_parent, inode_of_file)?;
279
280        Ok(())
281    }
282
283    fn insert_inode(
284        &self,
285        path: &Path,
286    ) -> Result<(InodeResolution, Option<InodeResolution>, OsString)> {
287        // Read lock.
288        let fs = self.inner.read().map_err(|_| FsError::Lock)?;
289
290        // Check the path has a parent.
291        let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
292
293        // Check the file name.
294        let name_of_file = path
295            .file_name()
296            .ok_or(FsError::InvalidInput)?
297            .to_os_string();
298
299        // Find the parent inode.
300        let inode_of_parent = match fs.inode_of_parent(parent_of_path)? {
301            InodeResolution::Found(a) => a,
302            InodeResolution::Redirect(fs, parent_path) => {
303                return Ok((
304                    InodeResolution::Redirect(fs, parent_path),
305                    None,
306                    name_of_file,
307                ));
308            }
309        };
310
311        // Find the inode of the file if it exists.
312        let maybe_inode_of_file = fs
313            .as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?
314            .map(|(_nth, inode)| inode);
315
316        Ok((
317            InodeResolution::Found(inode_of_parent),
318            maybe_inode_of_file,
319            name_of_file,
320        ))
321    }
322}
323
324impl crate::FileOpener for FileSystem {
325    fn open(
326        &self,
327        path: &Path,
328        conf: &OpenOptionsConfig,
329    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
330        debug!(path=%path.display(), "open");
331
332        let read = conf.read();
333        let mut write = conf.write();
334        let append = conf.append();
335        let mut truncate = conf.truncate();
336        let mut create = conf.create();
337        let create_new = conf.create_new();
338
339        // If `create_new` is used, `create` and `truncate ` are ignored.
340        if create_new {
341            create = false;
342            truncate = false;
343        }
344
345        // To truncate a file, `write` must be used.
346        if truncate && !write {
347            return Err(FsError::PermissionDenied);
348        }
349
350        // `append` is semantically equivalent to `write` + `append`
351        // but let's keep them exclusive.
352        if append {
353            write = false;
354        }
355
356        let (inode_of_parent, maybe_inode_of_file, name_of_file) = self.insert_inode(path)?;
357
358        let inode_of_parent = match inode_of_parent {
359            InodeResolution::Found(a) => a,
360            InodeResolution::Redirect(fs, mut parent_path) => {
361                parent_path.push(name_of_file);
362                return fs
363                    .new_open_options()
364                    .options(conf.clone())
365                    .open(parent_path);
366            }
367        };
368
369        let mut cursor = 0u64;
370        let inode_of_file = match maybe_inode_of_file {
371            // The file already exists, and a _new_ one _must_ be
372            // created; it's not OK.
373            Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
374
375            // The file already exists; it's OK.
376            Some(inode_of_file) => {
377                let inode_of_file = match inode_of_file {
378                    InodeResolution::Found(a) => a,
379                    InodeResolution::Redirect(fs, path) => {
380                        return fs.new_open_options().options(conf.clone()).open(path);
381                    }
382                };
383
384                // Write lock.
385                let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
386
387                let inode = fs.storage.get_mut(inode_of_file);
388                match inode {
389                    Some(Node::File(FileNode { metadata, file, .. })) => {
390                        // Update the accessed time.
391                        metadata.accessed = time();
392
393                        // Truncate if needed.
394                        if truncate {
395                            file.truncate();
396                            metadata.len = 0;
397                        }
398
399                        // Move the cursor to the end if needed.
400                        if append {
401                            cursor = file.len() as u64;
402                        }
403                    }
404
405                    Some(Node::OffloadedFile(OffloadedFileNode { metadata, file, .. })) => {
406                        // Update the accessed time.
407                        metadata.accessed = time();
408
409                        // Truncate if needed.
410                        if truncate {
411                            file.truncate();
412                            metadata.len = 0;
413                        }
414
415                        // Move the cursor to the end if needed.
416                        if append {
417                            cursor = file.len();
418                        }
419                    }
420
421                    Some(Node::ReadOnlyFile(node)) => {
422                        // Update the accessed time.
423                        node.metadata.accessed = time();
424
425                        // Truncate if needed.
426                        if truncate || append {
427                            return Err(FsError::PermissionDenied);
428                        }
429                    }
430
431                    Some(Node::CustomFile(node)) => {
432                        // Update the accessed time.
433                        node.metadata.accessed = time();
434
435                        // Truncate if needed.
436                        let mut file = node.file.lock().unwrap();
437                        if truncate {
438                            file.set_len(0)?;
439                            node.metadata.len = 0;
440                        }
441
442                        // Move the cursor to the end if needed.
443                        if append {
444                            cursor = file.size();
445                        }
446                    }
447
448                    Some(Node::ArcFile(node)) => {
449                        // Update the accessed time.
450                        node.metadata.accessed = time();
451
452                        let mut file = node
453                            .fs
454                            .new_open_options()
455                            .read(read)
456                            .write(write)
457                            .append(append)
458                            .truncate(truncate)
459                            .create(create)
460                            .create_new(create_new)
461                            .open(node.path.as_path())?;
462
463                        // Truncate if needed.
464                        if truncate {
465                            file.set_len(0)?;
466                            node.metadata.len = 0;
467                        }
468
469                        // Move the cursor to the end if needed.
470                        if append {
471                            cursor = file.size();
472                        }
473                    }
474
475                    None => return Err(FsError::EntryNotFound),
476                    _ => return Err(FsError::NotAFile),
477                }
478
479                inode_of_file
480            }
481
482            // The file doesn't already exist; it's OK to create it if:
483            // 1. `create_new` is used with `write` or `append`,
484            // 2. `create` is used with `write` or `append`.
485            None if (create_new || create) && (create_new || write || append) => {
486                // Write lock.
487                let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
488
489                let metadata = {
490                    let time = time();
491                    Metadata {
492                        ft: FileType {
493                            file: true,
494                            ..Default::default()
495                        },
496                        accessed: time,
497                        created: time,
498                        modified: time,
499                        len: 0,
500                    }
501                };
502                let inode_of_file = fs.storage.vacant_entry().key();
503
504                // We might be in optimized mode
505                let file = match fs.backing_offload.clone() {
506                    Some(offload) => {
507                        let file = OffloadedFile::new(fs.limiter.clone(), offload);
508                        Node::OffloadedFile(OffloadedFileNode {
509                            inode: inode_of_file,
510                            name: name_of_file,
511                            file,
512                            metadata,
513                        })
514                    }
515                    _ => {
516                        let file = File::new(fs.limiter.clone());
517                        Node::File(FileNode {
518                            inode: inode_of_file,
519                            name: name_of_file,
520                            file,
521                            metadata,
522                        })
523                    }
524                };
525
526                // Creating the file in the storage.
527                let real_inode_of_file = fs.storage.insert(file);
528
529                assert_eq!(
530                    inode_of_file, real_inode_of_file,
531                    "new file inode should have been correctly calculated",
532                );
533
534                // Adding the new directory to its parent.
535                fs.add_child_to_node(inode_of_parent, inode_of_file)?;
536
537                inode_of_file
538            }
539
540            None if (create_new || create) => return Err(FsError::PermissionDenied),
541
542            None => return Err(FsError::EntryNotFound),
543        };
544
545        Ok(Box::new(FileHandle::new(
546            inode_of_file,
547            self.clone(),
548            read,
549            write || append || truncate,
550            append,
551            cursor,
552        )))
553    }
554}
555
556#[cfg(test)]
557mod test_file_opener {
558    use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
559
560    use crate::{FileSystem as FS, FsError, mem_fs::*};
561    use std::io;
562
563    macro_rules! path {
564        ($path:expr_2021) => {
565            std::path::Path::new($path)
566        };
567    }
568
569    #[tokio::test]
570    async fn test_create_new_file() {
571        let fs = FileSystem::default();
572
573        assert!(
574            fs.new_open_options()
575                .write(true)
576                .create_new(true)
577                .open(path!("/foo.txt"))
578                .is_ok(),
579            "creating a new file",
580        );
581
582        {
583            let fs_inner = fs.inner.read().unwrap();
584
585            assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
586            assert!(
587                matches!(
588                    fs_inner.storage.get(ROOT_INODE),
589                    Some(Node::Directory(DirectoryNode {
590                        inode: ROOT_INODE,
591                        name,
592                        children,
593                        ..
594                    })) if name == "/" && children == &[1]
595                ),
596                "`/` contains `foo.txt`",
597            );
598            assert!(
599                matches!(
600                    fs_inner.storage.get(1),
601                    Some(Node::File(FileNode {
602                        inode: 1,
603                        name,
604                        ..
605                    })) if name == "foo.txt"
606                ),
607                "`foo.txt` exists and is a file",
608            );
609        }
610
611        assert!(
612            matches!(
613                fs.new_open_options()
614                    .write(true)
615                    .create_new(true)
616                    .open(path!("/foo.txt")),
617                Err(FsError::AlreadyExists)
618            ),
619            "creating a new file that already exist",
620        );
621
622        assert_eq!(
623            fs.new_open_options()
624                .write(true)
625                .create_new(true)
626                .open(path!("/foo/bar.txt"))
627                .map(|_| ()),
628            Err(FsError::EntryNotFound),
629            "creating a file in a directory that doesn't exist",
630        );
631
632        assert_eq!(fs.remove_file(path!("/foo.txt")), Ok(()), "removing a file");
633
634        assert!(
635            fs.new_open_options()
636                .write(false)
637                .create_new(true)
638                .open(path!("/foo.txt"))
639                .is_ok(),
640            "creating a file without the `write` option",
641        );
642    }
643
644    #[tokio::test]
645    async fn test_truncate_a_read_only_file() {
646        let fs = FileSystem::default();
647
648        assert!(
649            matches!(
650                fs.new_open_options()
651                    .write(false)
652                    .truncate(true)
653                    .open(path!("/foo.txt")),
654                Err(FsError::PermissionDenied),
655            ),
656            "truncating a read-only file",
657        );
658    }
659
660    #[tokio::test]
661    async fn test_truncate() {
662        let fs = FileSystem::default();
663
664        let mut file = fs
665            .new_open_options()
666            .write(true)
667            .create_new(true)
668            .open(path!("/foo.txt"))
669            .expect("failed to create a new file");
670
671        assert!(
672            matches!(file.write(b"foobar").await, Ok(6)),
673            "writing `foobar` at the end of the file",
674        );
675
676        assert!(
677            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
678            "checking the current position is 6",
679        );
680        assert!(
681            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
682            "checking the size is 6",
683        );
684
685        let mut file = fs
686            .new_open_options()
687            .write(true)
688            .truncate(true)
689            .open(path!("/foo.txt"))
690            .expect("failed to open + truncate `foo.txt`");
691
692        assert!(
693            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
694            "checking the current position is 0",
695        );
696        assert!(
697            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(0)),
698            "checking the size is 0",
699        );
700    }
701
702    #[tokio::test]
703    async fn test_append() {
704        let fs = FileSystem::default();
705
706        let mut file = fs
707            .new_open_options()
708            .write(true)
709            .create_new(true)
710            .open(path!("/foo.txt"))
711            .expect("failed to create a new file");
712
713        assert!(
714            matches!(file.write(b"foobar").await, Ok(6)),
715            "writing `foobar` at the end of the file",
716        );
717
718        assert!(
719            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
720            "checking the current position is 6",
721        );
722        assert!(
723            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
724            "checking the size is 6",
725        );
726
727        let mut file = fs
728            .new_open_options()
729            .append(true)
730            .open(path!("/foo.txt"))
731            .expect("failed to open `foo.txt`");
732
733        assert!(
734            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
735            "checking the current position in append-mode is 0",
736        );
737        assert!(
738            matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
739            "trying to rewind in append-mode",
740        );
741        assert!(matches!(file.write(b"baz").await, Ok(3)), "writing `baz`");
742
743        let mut file = fs
744            .new_open_options()
745            .read(true)
746            .open(path!("/foo.txt"))
747            .expect("failed to open `foo.txt");
748
749        assert!(
750            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
751            "checking the current position is read-mode is 0",
752        );
753
754        let mut string = String::new();
755        assert!(
756            matches!(file.read_to_string(&mut string).await, Ok(9)),
757            "reading the entire `foo.txt` file",
758        );
759        assert_eq!(
760            string, "foobarbaz",
761            "checking append-mode is ignoring seek operations",
762        );
763    }
764
765    #[tokio::test]
766    async fn test_opening_a_file_that_already_exists() {
767        let fs = FileSystem::default();
768
769        assert!(
770            fs.new_open_options()
771                .write(true)
772                .create_new(true)
773                .open(path!("/foo.txt"))
774                .is_ok(),
775            "creating a _new_ file",
776        );
777
778        assert!(
779            matches!(
780                fs.new_open_options()
781                    .create_new(true)
782                    .open(path!("/foo.txt")),
783                Err(FsError::AlreadyExists),
784            ),
785            "creating a _new_ file that already exists",
786        );
787
788        assert!(
789            fs.new_open_options()
790                .read(true)
791                .open(path!("/foo.txt"))
792                .is_ok(),
793            "opening a file that already exists",
794        );
795    }
796}