1use super::super::{Dir, DirEntry, Errno, FileType, Gid, Stat, Uid};
20use crate::path::{Component, Path, PathBuf};
21use crate::str::UnixStr;
22use std::cell::RefCell;
23use std::collections::HashMap;
24use std::collections::VecDeque;
25use std::fmt::Debug;
26use std::rc::Rc;
27
28const DEFAULT_DIRECTORY_MODE: Mode = Mode::USER_ALL.union(Mode::ALL_READ).union(Mode::ALL_EXEC);
29
30#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct FileSystem {
33 pub root: Rc<RefCell<Inode>>,
35}
36
37impl Default for FileSystem {
39 fn default() -> Self {
40 FileSystem {
41 root: Rc::new(RefCell::new(Inode {
42 body: FileBody::Directory {
43 files: HashMap::new(),
44 },
45 permissions: DEFAULT_DIRECTORY_MODE,
46 })),
47 }
48 }
49}
50
51impl FileSystem {
52 pub fn save<P: AsRef<Path>>(
60 &mut self,
61 path: P,
62 content: Rc<RefCell<Inode>>,
63 ) -> Result<Option<Rc<RefCell<Inode>>>, Errno> {
64 fn ensure_dir(body: &mut FileBody) -> &mut HashMap<Rc<UnixStr>, Rc<RefCell<Inode>>> {
65 match body {
66 FileBody::Directory { files } => files,
67 _ => {
68 let files = HashMap::new();
69 *body = FileBody::Directory { files };
70 match body {
71 FileBody::Directory { files } => files,
72 _ => unreachable!(),
73 }
74 }
75 }
76 }
77
78 fn main(
79 fs: &mut FileSystem,
80 path: &Path,
81 content: Rc<RefCell<Inode>>,
82 ) -> Result<Option<Rc<RefCell<Inode>>>, Errno> {
83 let mut components = path.components();
84 let file_name = match components.next_back().ok_or(Errno::ENOENT)? {
85 Component::Normal(name) => name,
86 _ => return Err(Errno::ENOENT),
87 };
88
89 let mut node = Rc::clone(&fs.root);
91 for component in components {
92 let name = match component {
93 Component::Normal(name) => name,
94 Component::RootDir => continue,
95 _ => return Err(Errno::ENOENT),
96 };
97 let mut node_ref = node.borrow_mut();
98 let children = ensure_dir(&mut node_ref.body);
99 use std::collections::hash_map::Entry::*;
100 let child = match children.entry(Rc::from(name)) {
101 Occupied(occupied) => Rc::clone(occupied.get()),
102 Vacant(vacant) => {
103 let child = Rc::new(RefCell::new(Inode {
104 body: FileBody::Directory {
105 files: HashMap::new(),
106 },
107 permissions: DEFAULT_DIRECTORY_MODE,
108 }));
109 Rc::clone(vacant.insert(child))
110 }
111 };
112 drop(node_ref);
113 node = child;
114 }
115
116 let mut parent_ref = node.borrow_mut();
117 let children = ensure_dir(&mut parent_ref.body);
118 Ok(children.insert(Rc::from(file_name), content))
119 }
120
121 main(self, path.as_ref(), content)
122 }
123
124 pub fn get<P: AsRef<Path>>(&self, path: P) -> Result<Rc<RefCell<Inode>>, Errno> {
128 fn main(fs: &FileSystem, path: &Path) -> Result<Rc<RefCell<Inode>>, Errno> {
129 let components = path.components();
130 let mut nodes = vec![Rc::clone(&fs.root)];
131 for component in components {
132 let name = match component {
133 Component::Normal(name) => name,
134 Component::RootDir | Component::CurDir => continue,
135 Component::ParentDir => {
136 if nodes.len() > 1 {
137 nodes.pop();
138 }
139 continue;
140 }
141 };
142
143 let node_ref = nodes.last().unwrap().borrow();
144 let children = match &node_ref.body {
145 FileBody::Directory { files } => files,
146 _ => return Err(Errno::ENOTDIR),
147 };
148
149 if !node_ref.permissions.contains(Mode::USER_EXEC) {
150 return Err(Errno::EACCES);
151 }
152
153 let child = Rc::clone(children.get(name).ok_or(Errno::ENOENT)?);
154 drop(node_ref);
155 nodes.push(child);
156 }
157
158 let node = nodes.pop().unwrap();
159 if path.as_unix_str().as_bytes().ends_with(b"/")
160 && !matches!(&node.borrow().body, FileBody::Directory { .. })
161 {
162 return Err(Errno::ENOTDIR);
163 }
164 Ok(node)
165 }
166
167 main(self, path.as_ref())
168 }
169}
170
171#[derive(Clone, Debug, Default, Eq, PartialEq)]
173pub struct Inode {
174 pub body: FileBody,
176 pub permissions: Mode,
178 }
180
181impl Inode {
182 pub fn new<T: Into<Vec<u8>>>(bytes: T) -> Self {
184 Inode {
185 body: FileBody::new(bytes),
186 permissions: Mode::default(),
187 }
188 }
189
190 #[must_use]
199 pub fn stat(&self) -> Stat {
200 Stat {
201 dev: 1,
202 ino: self as *const Self as u64,
203 mode: self.permissions,
204 r#type: self.body.r#type(),
205 nlink: 1,
206 uid: Uid(1),
207 gid: Gid(1),
208 size: self.body.size() as u64,
209 }
210 }
211}
212
213#[derive(Clone, Debug, Eq, PartialEq)]
215#[non_exhaustive]
216pub enum FileBody {
217 Regular {
219 content: Vec<u8>,
221 is_native_executable: bool,
223 },
224 Directory {
226 files: HashMap<Rc<UnixStr>, Rc<RefCell<Inode>>>,
231 },
234 Fifo {
236 content: VecDeque<u8>,
238 readers: usize,
240 writers: usize,
242 },
243 Symlink {
245 target: PathBuf,
247 },
248 Terminal {
252 content: Vec<u8>,
254 },
255 }
257
258impl Default for FileBody {
260 fn default() -> Self {
261 FileBody::Regular {
262 content: Vec::default(),
263 is_native_executable: bool::default(),
264 }
265 }
266}
267
268impl FileBody {
269 pub fn new<T: Into<Vec<u8>>>(bytes: T) -> Self {
271 FileBody::Regular {
272 content: bytes.into(),
273 is_native_executable: false,
274 }
275 }
276
277 #[must_use]
279 pub const fn r#type(&self) -> FileType {
280 match self {
281 Self::Regular { .. } => FileType::Regular,
282 Self::Directory { .. } => FileType::Directory,
283 Self::Fifo { .. } => FileType::Fifo,
284 Self::Symlink { .. } => FileType::Symlink,
285 Self::Terminal { .. } => FileType::CharacterDevice,
286 }
287 }
288
289 #[must_use]
291 pub fn size(&self) -> usize {
292 match self {
293 Self::Regular { content, .. } => content.len(),
294 Self::Directory { files } => files.len(),
295 Self::Fifo { content, .. } => content.len(),
296 Self::Symlink { target } => target.as_unix_str().len(),
297 Self::Terminal { .. } => 0,
298 }
299 }
300}
301
302#[deprecated = "use yash_env::system::Mode instead"]
305pub use super::super::Mode;
306
307#[derive(Clone, Debug)]
309pub struct VirtualDir<I> {
310 iter: I,
311 current: Rc<UnixStr>,
312}
313
314impl<I> VirtualDir<I> {
315 #[must_use]
317 pub fn new<J>(iter: J) -> Self
318 where
319 J: IntoIterator<IntoIter = I, Item = Rc<UnixStr>>,
320 {
321 VirtualDir {
322 iter: iter.into_iter(),
323 current: Rc::from(UnixStr::new("")),
324 }
325 }
326}
327
328impl TryFrom<&FileBody> for VirtualDir<std::vec::IntoIter<Rc<UnixStr>>> {
332 type Error = Errno;
333 fn try_from(file: &FileBody) -> Result<Self, Errno> {
334 let FileBody::Directory { files } = file else {
335 return Err(Errno::ENOTDIR);
336 };
337
338 let mut entries = Vec::with_capacity(files.len() + 2);
339 entries.push(Rc::from(UnixStr::new(".")));
340 entries.push(Rc::from(UnixStr::new("..")));
341 entries.extend(files.keys().cloned());
342
343 let entry = entries.pop().unwrap();
346 let i = entries.len() / 2;
347 entries.insert(i, entry);
348
349 Ok(Self::new(entries))
350 }
351}
352
353impl<I> Dir for VirtualDir<I>
354where
355 I: Debug,
356 I: Iterator<Item = Rc<UnixStr>>,
357{
358 fn next(&mut self) -> Result<Option<DirEntry<'_>>, Errno> {
359 match self.iter.next() {
360 Some(name) => {
361 self.current = name;
362 let name = &self.current;
363 Ok(Some(DirEntry { name }))
364 }
365 None => {
366 self.current = Rc::from(UnixStr::new(""));
367 Ok(None)
368 }
369 }
370 }
371}
372
373#[cfg(test)]
376mod tests {
377 use super::*;
378 use assert_matches::assert_matches;
379
380 #[test]
381 fn file_system_get_root() {
382 let fs = FileSystem::default();
383 let result = fs.get("/");
384 assert_eq!(result, Ok(fs.root));
385 }
386
387 #[test]
388 fn file_system_save_and_get_file() {
389 let mut fs = FileSystem::default();
390 let file_1 = Rc::new(RefCell::new(Inode::new([12, 34, 56])));
391 let old = fs.save("/foo/bar", Rc::clone(&file_1));
392 assert_eq!(old, Ok(None));
393
394 let file_2 = Rc::new(RefCell::new(Inode::new([98, 76, 54])));
395 let old = fs.save("/foo/bar", Rc::clone(&file_2));
396 assert_eq!(old, Ok(Some(file_1)));
397
398 let result = fs.get("/foo/bar");
399 assert_eq!(result, Ok(file_2));
400 }
401
402 #[test]
403 fn file_system_save_and_get_directory() {
404 let mut fs = FileSystem::default();
405 let file = Rc::new(RefCell::new(Inode::new([12, 34, 56])));
406 let old = fs.save("/foo/bar", Rc::clone(&file));
407 assert_eq!(old, Ok(None));
408
409 let dir = fs.get("/foo").unwrap();
410 let dir = dir.borrow();
411 assert_eq!(dir.permissions, Mode::from_bits_retain(0o755));
412 assert_matches!(&dir.body, FileBody::Directory { files } => {
413 let mut i = files.iter();
414 let (name, content) = i.next().unwrap();
415 assert_eq!(name.as_bytes(), b"bar");
416 assert_eq!(content, &file);
417 assert_eq!(i.next(), None);
418 });
419 }
420
421 #[test]
422 fn file_system_save_invalid_name() {
423 let mut fs = FileSystem::default();
424 let old = fs.save("", Rc::default());
425 assert_eq!(old, Err(Errno::ENOENT));
426 }
427
428 #[test]
429 fn file_system_get_parents() {
430 let mut fs = FileSystem::default();
431 let file = Rc::new(RefCell::new(Inode::new([123])));
432 _ = fs.save("/dir/dir1/file", Rc::clone(&file));
433 _ = fs.save("/dir/dir2/dir3/file", Rc::default());
434 assert_eq!(fs.get("/dir/dir2/dir3/../../dir1/file").unwrap(), file);
435 assert_eq!(fs.get("/../dir/dir1/file").unwrap(), file);
436 }
437
438 #[test]
439 fn file_system_get_non_existent_file() {
440 let fs = FileSystem::default();
441 let result = fs.get("/no_such_file");
442 assert_eq!(result, Err(Errno::ENOENT));
443 let result = fs.get("/no_such_directory/foo");
444 assert_eq!(result, Err(Errno::ENOENT));
445 }
446
447 #[test]
448 fn file_system_get_not_directory() {
449 let mut fs = FileSystem::default();
450 let _ = fs.save("/file", Rc::default());
451 let result = fs.get("/file/");
452 assert_eq!(result, Err(Errno::ENOTDIR));
453 let result = fs.get("/file/foo");
454 assert_eq!(result, Err(Errno::ENOTDIR));
455 }
456
457 #[test]
458 fn file_system_get_no_search_permission() {
459 let mut fs = FileSystem::default();
460 let _ = fs.save("/dir/file", Rc::default());
461 {
462 let dir = fs.get("/dir").unwrap();
463 dir.borrow_mut().permissions = Mode::from_bits_retain(0o666);
464 }
465 let result = fs.get("/dir/file");
466 assert_eq!(result, Err(Errno::EACCES));
467 }
468
469 #[test]
470 fn empty_virtual_dir() {
471 let mut dir = VirtualDir::new(std::iter::empty());
472 assert_matches!(dir.next(), Ok(None));
473 }
474
475 #[test]
476 fn non_empty_virtual_dir() {
477 let iter = ["foo", "bar"]
478 .into_iter()
479 .map(|s| Rc::from(UnixStr::new(s)));
480 let mut dir = VirtualDir::new(iter);
481 assert_matches!(dir.next(), Ok(Some(entry)) => {
482 assert_eq!(entry.name, "foo");
483 });
484 assert_matches!(dir.next(), Ok(Some(entry)) => {
485 assert_eq!(entry.name, "bar");
486 });
487 assert_matches!(dir.next(), Ok(None));
488 }
489
490 #[test]
491 fn virtual_dir_try_from_file_body_directory() {
492 let files = ["one", "2", "three"]
493 .into_iter()
494 .map(|name| (Rc::from(UnixStr::new(name)), Rc::default()))
495 .collect();
496 let file = FileBody::Directory { files };
497 let mut dir = VirtualDir::try_from(&file).unwrap();
498
499 let mut files = Vec::new();
500 while let Some(entry) = dir.next().unwrap() {
501 files.push(entry.name.to_str().unwrap().to_string());
502 }
503 files.sort_unstable();
504 let files: Vec<&str> = files.iter().map(String::as_str).collect();
505 assert_eq!(files, [".", "..", "2", "one", "three"]);
506 }
507
508 #[test]
509 fn virtual_dir_try_from_file_body_non_directory() {
510 let file = FileBody::Regular {
511 content: Default::default(),
512 is_native_executable: false,
513 };
514 let result = VirtualDir::try_from(&file);
515 assert_eq!(result.unwrap_err(), Errno::ENOTDIR);
516 }
517}