wasmer_vfs/mem_fs/
file_opener.rs

1use super::*;
2use crate::{FileType, FsError, Metadata, OpenOptionsConfig, Result, VirtualFile};
3use std::io::{self, Seek};
4use std::path::Path;
5
6/// The type that is responsible to open a file.
7#[derive(Debug, Clone)]
8pub struct FileOpener {
9    pub(super) filesystem: FileSystem,
10}
11
12impl crate::FileOpener for FileOpener {
13    fn open(
14        &mut self,
15        path: &Path,
16        conf: &OpenOptionsConfig,
17    ) -> Result<Box<dyn VirtualFile + Send + Sync + 'static>> {
18        let read = conf.read();
19        let mut write = conf.write();
20        let append = conf.append();
21        let mut truncate = conf.truncate();
22        let mut create = conf.create();
23        let create_new = conf.create_new();
24
25        // If `create_new` is used, `create` and `truncate ` are ignored.
26        if create_new {
27            create = false;
28            truncate = false;
29        }
30
31        // To truncate a file, `write` must be used.
32        if truncate && !write {
33            return Err(FsError::PermissionDenied);
34        }
35
36        // `append` is semantically equivalent to `write` + `append`
37        // but let's keep them exclusive.
38        if append {
39            write = false;
40        }
41
42        let (inode_of_parent, maybe_inode_of_file, name_of_file) = {
43            // Read lock.
44            let fs = self
45                .filesystem
46                .inner
47                .try_read()
48                .map_err(|_| FsError::Lock)?;
49
50            // Check the path has a parent.
51            let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
52
53            // Check the file name.
54            let name_of_file = path
55                .file_name()
56                .ok_or(FsError::InvalidInput)?
57                .to_os_string();
58
59            // Find the parent inode.
60            let inode_of_parent = fs.inode_of_parent(parent_of_path)?;
61
62            // Find the inode of the file if it exists.
63            let maybe_inode_of_file = fs
64                .as_parent_get_position_and_inode_of_file(inode_of_parent, &name_of_file)?
65                .map(|(_nth, inode)| inode);
66
67            (inode_of_parent, maybe_inode_of_file, name_of_file)
68        };
69
70        let inode_of_file = match maybe_inode_of_file {
71            // The file already exists, and a _new_ one _must_ be
72            // created; it's not OK.
73            Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
74
75            // The file already exists; it's OK.
76            Some(inode_of_file) => {
77                // Write lock.
78                let mut fs = self
79                    .filesystem
80                    .inner
81                    .try_write()
82                    .map_err(|_| FsError::Lock)?;
83
84                let inode = fs.storage.get_mut(inode_of_file);
85                match inode {
86                    Some(Node::File { metadata, file, .. }) => {
87                        // Update the accessed time.
88                        metadata.accessed = time();
89
90                        // Truncate if needed.
91                        if truncate {
92                            file.truncate();
93                            metadata.len = 0;
94                        }
95
96                        // Move the cursor to the end if needed.
97                        if append {
98                            file.seek(io::SeekFrom::End(0))?;
99                        }
100                        // Otherwise, move the cursor to the start.
101                        else {
102                            file.seek(io::SeekFrom::Start(0))?;
103                        }
104                    }
105
106                    _ => return Err(FsError::NotAFile),
107                }
108
109                inode_of_file
110            }
111
112            // The file doesn't already exist; it's OK to create it if:
113            // 1. `create_new` is used with `write` or `append`,
114            // 2. `create` is used with `write` or `append`.
115            None if (create_new || create) && (write || append) => {
116                // Write lock.
117                let mut fs = self
118                    .filesystem
119                    .inner
120                    .try_write()
121                    .map_err(|_| FsError::Lock)?;
122
123                let file = File::new();
124
125                // Creating the file in the storage.
126                let inode_of_file = fs.storage.vacant_entry().key();
127                let real_inode_of_file = fs.storage.insert(Node::File {
128                    inode: inode_of_file,
129                    name: name_of_file,
130                    file,
131                    metadata: {
132                        let time = time();
133
134                        Metadata {
135                            ft: FileType {
136                                file: true,
137                                ..Default::default()
138                            },
139                            accessed: time,
140                            created: time,
141                            modified: time,
142                            len: 0,
143                        }
144                    },
145                });
146
147                assert_eq!(
148                    inode_of_file, real_inode_of_file,
149                    "new file inode should have been correctly calculated",
150                );
151
152                // Adding the new directory to its parent.
153                fs.add_child_to_node(inode_of_parent, inode_of_file)?;
154
155                inode_of_file
156            }
157
158            None => return Err(FsError::PermissionDenied),
159        };
160
161        Ok(Box::new(FileHandle::new(
162            inode_of_file,
163            self.filesystem.clone(),
164            read,
165            write || append || truncate,
166            append,
167        )))
168    }
169}
170
171#[cfg(test)]
172mod test_file_opener {
173    use crate::{mem_fs::*, FileSystem as FS, FsError};
174    use std::io;
175
176    macro_rules! path {
177        ($path:expr) => {
178            std::path::Path::new($path)
179        };
180    }
181
182    #[test]
183    fn test_create_new_file() {
184        let fs = FileSystem::default();
185
186        assert!(
187            matches!(
188                fs.new_open_options()
189                    .write(true)
190                    .create_new(true)
191                    .open(path!("/foo.txt")),
192                Ok(_),
193            ),
194            "creating a new file",
195        );
196
197        {
198            let fs_inner = fs.inner.read().unwrap();
199
200            assert_eq!(fs_inner.storage.len(), 2, "storage has the new file");
201            assert!(
202                matches!(
203                    fs_inner.storage.get(ROOT_INODE),
204                    Some(Node::Directory {
205                        inode: ROOT_INODE,
206                        name,
207                        children,
208                        ..
209                    }) if name == "/" && children == &[1]
210                ),
211                "`/` contains `foo.txt`",
212            );
213            assert!(
214                matches!(
215                    fs_inner.storage.get(1),
216                    Some(Node::File {
217                        inode: 1,
218                        name,
219                        ..
220                    }) if name == "foo.txt"
221                ),
222                "`foo.txt` exists and is a file",
223            );
224        }
225
226        assert!(
227            matches!(
228                fs.new_open_options()
229                    .write(true)
230                    .create_new(true)
231                    .open(path!("/foo.txt")),
232                Err(FsError::AlreadyExists)
233            ),
234            "creating a new file that already exist",
235        );
236
237        assert!(
238            matches!(
239                fs.new_open_options()
240                    .write(true)
241                    .create_new(true)
242                    .open(path!("/foo/bar.txt")),
243                Err(FsError::NotAFile),
244            ),
245            "creating a file in a directory that doesn't exist",
246        );
247
248        assert_eq!(fs.remove_file(path!("/foo.txt")), Ok(()), "removing a file");
249
250        assert!(
251            matches!(
252                fs.new_open_options()
253                    .write(false)
254                    .create_new(true)
255                    .open(path!("/foo.txt")),
256                Err(FsError::PermissionDenied),
257            ),
258            "creating a file without the `write` option",
259        );
260    }
261
262    #[test]
263    fn test_truncate_a_read_only_file() {
264        let fs = FileSystem::default();
265
266        assert!(
267            matches!(
268                fs.new_open_options()
269                    .write(false)
270                    .truncate(true)
271                    .open(path!("/foo.txt")),
272                Err(FsError::PermissionDenied),
273            ),
274            "truncating a read-only file",
275        );
276    }
277
278    #[test]
279    fn test_truncate() {
280        let fs = FileSystem::default();
281
282        let mut file = fs
283            .new_open_options()
284            .write(true)
285            .create_new(true)
286            .open(path!("/foo.txt"))
287            .expect("failed to create a new file");
288
289        assert!(
290            matches!(file.write(b"foobar"), Ok(6)),
291            "writing `foobar` at the end of the file",
292        );
293
294        assert!(
295            matches!(file.seek(io::SeekFrom::Current(0)), Ok(6)),
296            "checking the current position is 6",
297        );
298        assert!(
299            matches!(file.seek(io::SeekFrom::End(0)), Ok(6)),
300            "checking the size is 6",
301        );
302
303        let mut file = fs
304            .new_open_options()
305            .write(true)
306            .truncate(true)
307            .open(path!("/foo.txt"))
308            .expect("failed to open + truncate `foo.txt`");
309
310        assert!(
311            matches!(file.seek(io::SeekFrom::Current(0)), Ok(0)),
312            "checking the current position is 0",
313        );
314        assert!(
315            matches!(file.seek(io::SeekFrom::End(0)), Ok(0)),
316            "checking the size is 0",
317        );
318    }
319
320    #[test]
321    fn test_append() {
322        let fs = FileSystem::default();
323
324        let mut file = fs
325            .new_open_options()
326            .write(true)
327            .create_new(true)
328            .open(path!("/foo.txt"))
329            .expect("failed to create a new file");
330
331        assert!(
332            matches!(file.write(b"foobar"), Ok(6)),
333            "writing `foobar` at the end of the file",
334        );
335
336        assert!(
337            matches!(file.seek(io::SeekFrom::Current(0)), Ok(6)),
338            "checking the current position is 6",
339        );
340        assert!(
341            matches!(file.seek(io::SeekFrom::End(0)), Ok(6)),
342            "checking the size is 6",
343        );
344
345        let mut file = fs
346            .new_open_options()
347            .append(true)
348            .open(path!("/foo.txt"))
349            .expect("failed to open `foo.txt`");
350
351        assert!(
352            matches!(file.seek(io::SeekFrom::Current(0)), Ok(0)),
353            "checking the current position in append-mode is 0",
354        );
355        assert!(
356            matches!(file.seek(io::SeekFrom::Start(0)), Ok(0)),
357            "trying to rewind in append-mode",
358        );
359        assert!(matches!(file.write(b"baz"), Ok(3)), "writing `baz`");
360
361        let mut file = fs
362            .new_open_options()
363            .read(true)
364            .open(path!("/foo.txt"))
365            .expect("failed to open `foo.txt");
366
367        assert!(
368            matches!(file.seek(io::SeekFrom::Current(0)), Ok(0)),
369            "checking the current position is read-mode is 0",
370        );
371
372        let mut string = String::new();
373        assert!(
374            matches!(file.read_to_string(&mut string), Ok(9)),
375            "reading the entire `foo.txt` file",
376        );
377        assert_eq!(
378            string, "foobarbaz",
379            "checking append-mode is ignoring seek operations",
380        );
381    }
382
383    #[test]
384    fn test_opening_a_file_that_already_exists() {
385        let fs = FileSystem::default();
386
387        assert!(
388            matches!(
389                fs.new_open_options()
390                    .write(true)
391                    .create_new(true)
392                    .open(path!("/foo.txt")),
393                Ok(_),
394            ),
395            "creating a _new_ file",
396        );
397
398        assert!(
399            matches!(
400                fs.new_open_options()
401                    .create_new(true)
402                    .open(path!("/foo.txt")),
403                Err(FsError::AlreadyExists),
404            ),
405            "creating a _new_ file that already exists",
406        );
407
408        assert!(
409            matches!(
410                fs.new_open_options().read(true).open(path!("/foo.txt")),
411                Ok(_),
412            ),
413            "opening a file that already exists",
414        );
415    }
416}