1use super::super::{Dir, DirEntry, Errno, FileType, Gid, Uid};
20use super::FileBody;
21use crate::path::{Component, Path};
22use crate::str::UnixStr;
23use std::cell::RefCell;
24use std::collections::HashMap;
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#[deprecated = "use yash_env::system::Mode instead"]
216pub use super::super::Mode;
217
218#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
224#[non_exhaustive]
225pub struct Stat {
226 pub dev: u64,
228 pub ino: u64,
230 pub mode: Mode,
235 pub r#type: FileType,
237 pub nlink: u64,
239 pub uid: Uid,
241 pub gid: Gid,
243 pub size: u64,
245 }
247
248impl Stat {
249 #[inline]
254 #[must_use]
255 pub const fn identity(&self) -> (u64, u64) {
256 (self.dev, self.ino)
257 }
258}
259
260impl super::super::Stat for Stat {
261 #[inline(always)]
262 fn dev(&self) -> u64 {
263 self.dev
264 }
265 #[inline(always)]
266 fn ino(&self) -> u64 {
267 self.ino
268 }
269 #[inline(always)]
270 fn mode(&self) -> Mode {
271 self.mode
272 }
273 #[inline(always)]
274 fn r#type(&self) -> FileType {
275 self.r#type
276 }
277 #[inline(always)]
278 fn nlink(&self) -> u64 {
279 self.nlink
280 }
281 #[inline(always)]
282 fn uid(&self) -> Uid {
283 self.uid
284 }
285 #[inline(always)]
286 fn gid(&self) -> Gid {
287 self.gid
288 }
289 #[inline(always)]
290 fn size(&self) -> u64 {
291 self.size
292 }
293}
294
295#[derive(Clone, Debug)]
297pub struct VirtualDir<I> {
298 iter: I,
299 current: Rc<UnixStr>,
300}
301
302impl<I> VirtualDir<I> {
303 #[must_use]
305 pub fn new<J>(iter: J) -> Self
306 where
307 J: IntoIterator<IntoIter = I, Item = Rc<UnixStr>>,
308 {
309 VirtualDir {
310 iter: iter.into_iter(),
311 current: Rc::from(UnixStr::new("")),
312 }
313 }
314}
315
316impl TryFrom<&FileBody> for VirtualDir<std::vec::IntoIter<Rc<UnixStr>>> {
320 type Error = Errno;
321 fn try_from(file: &FileBody) -> Result<Self, Errno> {
322 let FileBody::Directory { files } = file else {
323 return Err(Errno::ENOTDIR);
324 };
325
326 let mut entries = Vec::with_capacity(files.len() + 2);
327 entries.push(Rc::from(UnixStr::new(".")));
328 entries.push(Rc::from(UnixStr::new("..")));
329 entries.extend(files.keys().cloned());
330
331 let entry = entries.pop().unwrap();
334 let i = entries.len() / 2;
335 entries.insert(i, entry);
336
337 Ok(Self::new(entries))
338 }
339}
340
341impl<I> Dir for VirtualDir<I>
342where
343 I: Debug,
344 I: Iterator<Item = Rc<UnixStr>>,
345{
346 fn next(&mut self) -> Result<Option<DirEntry<'_>>, Errno> {
347 match self.iter.next() {
348 Some(name) => {
349 self.current = name;
350 let name = &self.current;
351 Ok(Some(DirEntry { name }))
352 }
353 None => {
354 self.current = Rc::from(UnixStr::new(""));
355 Ok(None)
356 }
357 }
358 }
359}
360
361#[cfg(test)]
364mod tests {
365 use super::*;
366 use assert_matches::assert_matches;
367
368 #[test]
369 fn file_system_get_root() {
370 let fs = FileSystem::default();
371 let result = fs.get("/");
372 assert_eq!(result, Ok(fs.root));
373 }
374
375 #[test]
376 fn file_system_save_and_get_file() {
377 let mut fs = FileSystem::default();
378 let file_1 = Rc::new(RefCell::new(Inode::new([12, 34, 56])));
379 let old = fs.save("/foo/bar", Rc::clone(&file_1));
380 assert_eq!(old, Ok(None));
381
382 let file_2 = Rc::new(RefCell::new(Inode::new([98, 76, 54])));
383 let old = fs.save("/foo/bar", Rc::clone(&file_2));
384 assert_eq!(old, Ok(Some(file_1)));
385
386 let result = fs.get("/foo/bar");
387 assert_eq!(result, Ok(file_2));
388 }
389
390 #[test]
391 fn file_system_save_and_get_directory() {
392 let mut fs = FileSystem::default();
393 let file = Rc::new(RefCell::new(Inode::new([12, 34, 56])));
394 let old = fs.save("/foo/bar", Rc::clone(&file));
395 assert_eq!(old, Ok(None));
396
397 let dir = fs.get("/foo").unwrap();
398 let dir = dir.borrow();
399 assert_eq!(dir.permissions, Mode::from_bits_retain(0o755));
400 assert_matches!(&dir.body, FileBody::Directory { files } => {
401 let mut i = files.iter();
402 let (name, content) = i.next().unwrap();
403 assert_eq!(name.as_bytes(), b"bar");
404 assert_eq!(content, &file);
405 assert_eq!(i.next(), None);
406 });
407 }
408
409 #[test]
410 fn file_system_save_invalid_name() {
411 let mut fs = FileSystem::default();
412 let old = fs.save("", Rc::default());
413 assert_eq!(old, Err(Errno::ENOENT));
414 }
415
416 #[test]
417 fn file_system_get_parents() {
418 let mut fs = FileSystem::default();
419 let file = Rc::new(RefCell::new(Inode::new([123])));
420 _ = fs.save("/dir/dir1/file", Rc::clone(&file));
421 _ = fs.save("/dir/dir2/dir3/file", Rc::default());
422 assert_eq!(fs.get("/dir/dir2/dir3/../../dir1/file").unwrap(), file);
423 assert_eq!(fs.get("/../dir/dir1/file").unwrap(), file);
424 }
425
426 #[test]
427 fn file_system_get_non_existent_file() {
428 let fs = FileSystem::default();
429 let result = fs.get("/no_such_file");
430 assert_eq!(result, Err(Errno::ENOENT));
431 let result = fs.get("/no_such_directory/foo");
432 assert_eq!(result, Err(Errno::ENOENT));
433 }
434
435 #[test]
436 fn file_system_get_not_directory() {
437 let mut fs = FileSystem::default();
438 let _ = fs.save("/file", Rc::default());
439 let result = fs.get("/file/");
440 assert_eq!(result, Err(Errno::ENOTDIR));
441 let result = fs.get("/file/foo");
442 assert_eq!(result, Err(Errno::ENOTDIR));
443 }
444
445 #[test]
446 fn file_system_get_no_search_permission() {
447 let mut fs = FileSystem::default();
448 let _ = fs.save("/dir/file", Rc::default());
449 {
450 let dir = fs.get("/dir").unwrap();
451 dir.borrow_mut().permissions = Mode::from_bits_retain(0o666);
452 }
453 let result = fs.get("/dir/file");
454 assert_eq!(result, Err(Errno::EACCES));
455 }
456
457 #[test]
458 fn empty_virtual_dir() {
459 let mut dir = VirtualDir::new(std::iter::empty());
460 assert_matches!(dir.next(), Ok(None));
461 }
462
463 #[test]
464 fn non_empty_virtual_dir() {
465 let iter = ["foo", "bar"]
466 .into_iter()
467 .map(|s| Rc::from(UnixStr::new(s)));
468 let mut dir = VirtualDir::new(iter);
469 assert_matches!(dir.next(), Ok(Some(entry)) => {
470 assert_eq!(entry.name, "foo");
471 });
472 assert_matches!(dir.next(), Ok(Some(entry)) => {
473 assert_eq!(entry.name, "bar");
474 });
475 assert_matches!(dir.next(), Ok(None));
476 }
477
478 #[test]
479 fn virtual_dir_try_from_file_body_directory() {
480 let files = ["one", "2", "three"]
481 .into_iter()
482 .map(|name| (Rc::from(UnixStr::new(name)), Rc::default()))
483 .collect();
484 let file = FileBody::Directory { files };
485 let mut dir = VirtualDir::try_from(&file).unwrap();
486
487 let mut files = Vec::new();
488 while let Some(entry) = dir.next().unwrap() {
489 files.push(entry.name.to_str().unwrap().to_string());
490 }
491 files.sort_unstable();
492 let files: Vec<&str> = files.iter().map(String::as_str).collect();
493 assert_eq!(files, [".", "..", "2", "one", "three"]);
494 }
495
496 #[test]
497 fn virtual_dir_try_from_file_body_non_directory() {
498 let file = FileBody::Regular {
499 content: Default::default(),
500 is_native_executable: false,
501 };
502 let result = VirtualDir::try_from(&file);
503 assert_eq!(result.unwrap_err(), Errno::ENOTDIR);
504 }
505}