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 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
400                    Some(Node::OffloadedFile(OffloadedFileNode { metadata, file, .. })) => {
401                        // Update the accessed time.
402                        metadata.accessed = time();
403
404                        // Truncate if needed.
405                        if truncate {
406                            file.truncate();
407                            metadata.len = 0;
408                        }
409                    }
410
411                    Some(Node::ReadOnlyFile(node)) => {
412                        // Update the accessed time.
413                        node.metadata.accessed = time();
414
415                        // Truncate if needed.
416                        if truncate || append {
417                            return Err(FsError::PermissionDenied);
418                        }
419                    }
420
421                    Some(Node::CustomFile(node)) => {
422                        // Update the accessed time.
423                        node.metadata.accessed = time();
424
425                        // Truncate if needed.
426                        let mut file = node.file.lock().unwrap();
427                        if truncate {
428                            file.set_len(0)?;
429                            node.metadata.len = 0;
430                        }
431                    }
432
433                    Some(Node::ArcFile(node)) => {
434                        // Update the accessed time.
435                        node.metadata.accessed = time();
436
437                        let mut file = node
438                            .fs
439                            .new_open_options()
440                            .read(read)
441                            .write(write)
442                            .append(append)
443                            .truncate(truncate)
444                            .create(create)
445                            .create_new(create_new)
446                            .open(node.path.as_path())?;
447
448                        // Truncate if needed.
449                        if truncate {
450                            file.set_len(0)?;
451                            node.metadata.len = 0;
452                        }
453                    }
454
455                    None => return Err(FsError::EntryNotFound),
456                    _ => return Err(FsError::NotAFile),
457                }
458
459                inode_of_file
460            }
461
462            // The file doesn't already exist; it's OK to create it if:
463            // 1. `create_new` is used with `write` or `append`,
464            // 2. `create` is used with `write` or `append`.
465            None if (create_new || create) && (create_new || write || append) => {
466                // Write lock.
467                let mut fs = self.inner.write().map_err(|_| FsError::Lock)?;
468
469                let metadata = {
470                    let time = time();
471                    Metadata {
472                        ft: FileType {
473                            file: true,
474                            ..Default::default()
475                        },
476                        accessed: time,
477                        created: time,
478                        modified: time,
479                        len: 0,
480                    }
481                };
482                let inode_of_file = fs.storage.vacant_entry().key();
483
484                // We might be in optimized mode
485                let file = match fs.backing_offload.clone() {
486                    Some(offload) => {
487                        let file = OffloadedFile::new(fs.limiter.clone(), offload);
488                        Node::OffloadedFile(OffloadedFileNode {
489                            inode: inode_of_file,
490                            name: name_of_file,
491                            file,
492                            metadata,
493                        })
494                    }
495                    _ => {
496                        let file = File::new(fs.limiter.clone());
497                        Node::File(FileNode {
498                            inode: inode_of_file,
499                            name: name_of_file,
500                            file,
501                            metadata,
502                        })
503                    }
504                };
505
506                // Creating the file in the storage.
507                let real_inode_of_file = fs.storage.insert(file);
508
509                assert_eq!(
510                    inode_of_file, real_inode_of_file,
511                    "new file inode should have been correctly calculated",
512                );
513
514                // Adding the new directory to its parent.
515                fs.add_child_to_node(inode_of_parent, inode_of_file)?;
516
517                inode_of_file
518            }
519
520            None if (create_new || create) => return Err(FsError::PermissionDenied),
521
522            None => return Err(FsError::EntryNotFound),
523        };
524
525        Ok(Box::new(FileHandle::new(
526            inode_of_file,
527            self.clone(),
528            read,
529            write || append || truncate,
530            append,
531            cursor,
532        )))
533    }
534}
535
536#[cfg(test)]
537mod test_file_opener {
538    use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
539
540    use crate::{FileSystem as FS, FsError, mem_fs::*};
541    use std::io;
542
543    macro_rules! path {
544        ($path:expr) => {
545            std::path::Path::new($path)
546        };
547    }
548
549    #[tokio::test]
550    async fn test_create_new_file() {
551        let fs = FileSystem::default();
552
553        assert!(
554            fs.new_open_options()
555                .write(true)
556                .create_new(true)
557                .open(path!("/foo.txt"))
558                .is_ok(),
559            "creating a new file",
560        );
561
562        {
563            let fs_inner = fs.inner.read().unwrap();
564
565            assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
566            assert!(
567                matches!(
568                    fs_inner.storage.get(ROOT_INODE),
569                    Some(Node::Directory(DirectoryNode {
570                        inode: ROOT_INODE,
571                        name,
572                        children,
573                        ..
574                    })) if name == "/" && children == &[1]
575                ),
576                "`/` contains `foo.txt`",
577            );
578            assert!(
579                matches!(
580                    fs_inner.storage.get(1),
581                    Some(Node::File(FileNode {
582                        inode: 1,
583                        name,
584                        ..
585                    })) if name == "foo.txt"
586                ),
587                "`foo.txt` exists and is a file",
588            );
589        }
590
591        assert!(
592            matches!(
593                fs.new_open_options()
594                    .write(true)
595                    .create_new(true)
596                    .open(path!("/foo.txt")),
597                Err(FsError::AlreadyExists)
598            ),
599            "creating a new file that already exist",
600        );
601
602        assert_eq!(
603            fs.new_open_options()
604                .write(true)
605                .create_new(true)
606                .open(path!("/foo/bar.txt"))
607                .map(|_| ()),
608            Err(FsError::EntryNotFound),
609            "creating a file in a directory that doesn't exist",
610        );
611
612        assert_eq!(fs.remove_file(path!("/foo.txt")), Ok(()), "removing a file");
613
614        assert!(
615            fs.new_open_options()
616                .write(false)
617                .create_new(true)
618                .open(path!("/foo.txt"))
619                .is_ok(),
620            "creating a file without the `write` option",
621        );
622    }
623
624    #[tokio::test]
625    async fn test_truncate_a_read_only_file() {
626        let fs = FileSystem::default();
627
628        assert!(
629            matches!(
630                fs.new_open_options()
631                    .write(false)
632                    .truncate(true)
633                    .open(path!("/foo.txt")),
634                Err(FsError::PermissionDenied),
635            ),
636            "truncating a read-only file",
637        );
638    }
639
640    #[tokio::test]
641    async fn test_truncate() {
642        let fs = FileSystem::default();
643
644        let mut file = fs
645            .new_open_options()
646            .write(true)
647            .create_new(true)
648            .open(path!("/foo.txt"))
649            .expect("failed to create a new file");
650
651        assert!(
652            matches!(file.write(b"foobar").await, Ok(6)),
653            "writing `foobar` at the end of the file",
654        );
655
656        assert!(
657            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
658            "checking the current position is 6",
659        );
660        assert!(
661            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
662            "checking the size is 6",
663        );
664
665        let mut file = fs
666            .new_open_options()
667            .write(true)
668            .truncate(true)
669            .open(path!("/foo.txt"))
670            .expect("failed to open + truncate `foo.txt`");
671
672        assert!(
673            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
674            "checking the current position is 0",
675        );
676        assert!(
677            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(0)),
678            "checking the size is 0",
679        );
680    }
681
682    #[tokio::test]
683    async fn test_append() {
684        let fs = FileSystem::default();
685
686        let mut file = fs
687            .new_open_options()
688            .write(true)
689            .create_new(true)
690            .open(path!("/foo.txt"))
691            .expect("failed to create a new file");
692
693        assert!(
694            matches!(file.write(b"foobar").await, Ok(6)),
695            "writing `foobar` at the end of the file",
696        );
697
698        assert!(
699            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(6)),
700            "checking the current position is 6",
701        );
702        assert!(
703            matches!(file.seek(io::SeekFrom::End(0)).await, Ok(6)),
704            "checking the size is 6",
705        );
706
707        let mut file = fs
708            .new_open_options()
709            .append(true)
710            .open(path!("/foo.txt"))
711            .expect("failed to open `foo.txt`");
712
713        assert!(
714            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
715            "checking the current position in append-mode is 0",
716        );
717        assert!(
718            matches!(file.seek(io::SeekFrom::Start(0)).await, Ok(0)),
719            "trying to rewind in append-mode",
720        );
721        assert!(matches!(file.write(b"baz").await, Ok(3)), "writing `baz`");
722
723        let mut file = fs
724            .new_open_options()
725            .read(true)
726            .open(path!("/foo.txt"))
727            .expect("failed to open `foo.txt");
728
729        assert!(
730            matches!(file.seek(io::SeekFrom::Current(0)).await, Ok(0)),
731            "checking the current position is read-mode is 0",
732        );
733
734        let mut string = String::new();
735        assert!(
736            matches!(file.read_to_string(&mut string).await, Ok(9)),
737            "reading the entire `foo.txt` file",
738        );
739        assert_eq!(
740            string, "foobarbaz",
741            "checking append-mode is ignoring seek operations",
742        );
743    }
744
745    #[tokio::test]
746    async fn test_opening_a_file_that_already_exists() {
747        let fs = FileSystem::default();
748
749        assert!(
750            fs.new_open_options()
751                .write(true)
752                .create_new(true)
753                .open(path!("/foo.txt"))
754                .is_ok(),
755            "creating a _new_ file",
756        );
757
758        assert!(
759            matches!(
760                fs.new_open_options()
761                    .create_new(true)
762                    .open(path!("/foo.txt")),
763                Err(FsError::AlreadyExists),
764            ),
765            "creating a _new_ file that already exists",
766        );
767
768        assert!(
769            fs.new_open_options()
770                .read(true)
771                .open(path!("/foo.txt"))
772                .is_ok(),
773            "opening a file that already exists",
774        );
775    }
776}