1use super::*;
2use crate::{FileType, FsError, Metadata, OpenOptionsConfig, Result, VirtualFile};
3use std::io::{self, Seek};
4use std::path::Path;
5
6#[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 {
27 create = false;
28 truncate = false;
29 }
30
31 if truncate && !write {
33 return Err(FsError::PermissionDenied);
34 }
35
36 if append {
39 write = false;
40 }
41
42 let (inode_of_parent, maybe_inode_of_file, name_of_file) = {
43 let fs = self
45 .filesystem
46 .inner
47 .try_read()
48 .map_err(|_| FsError::Lock)?;
49
50 let parent_of_path = path.parent().ok_or(FsError::BaseNotDirectory)?;
52
53 let name_of_file = path
55 .file_name()
56 .ok_or(FsError::InvalidInput)?
57 .to_os_string();
58
59 let inode_of_parent = fs.inode_of_parent(parent_of_path)?;
61
62 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 Some(_inode_of_file) if create_new => return Err(FsError::AlreadyExists),
74
75 Some(inode_of_file) => {
77 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 metadata.accessed = time();
89
90 if truncate {
92 file.truncate();
93 metadata.len = 0;
94 }
95
96 if append {
98 file.seek(io::SeekFrom::End(0))?;
99 }
100 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 None if (create_new || create) && (write || append) => {
116 let mut fs = self
118 .filesystem
119 .inner
120 .try_write()
121 .map_err(|_| FsError::Lock)?;
122
123 let file = File::new();
124
125 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 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}