1#[cfg(feature = "std")]
11use std::cell::RefCell;
12#[cfg(feature = "std")]
13use std::collections::HashMap;
14#[cfg(feature = "std")]
15use std::rc::Rc;
16
17#[cfg(not(feature = "std"))]
18use alloc::collections::BTreeMap;
19#[cfg(not(feature = "std"))]
20use alloc::rc::Rc;
21#[cfg(not(feature = "std"))]
22use core::cell::RefCell;
23
24use crate::error::FsError;
25use crate::handle::FileHandle;
26use crate::inode::{FileContent, Inode, Metadata};
27use crate::time::{MonotonicCounter, TimeProvider};
28use crate::types::*;
29
30#[cfg(feature = "logging")]
32use crate::{debug, error, trace};
33
34#[cfg(feature = "std")]
36type Map<K, V> = HashMap<K, V>;
37#[cfg(not(feature = "std"))]
38type Map<K, V> = BTreeMap<K, V>;
39
40pub type InodeRef = Rc<RefCell<Inode>>;
42
43pub struct Fs<T: TimeProvider = MonotonicCounter> {
45 pub(crate) next_inode: InodeId,
46 pub(crate) fd_table: Map<Fd, FileHandle>,
47 pub(crate) inode_table: Map<InodeId, Rc<RefCell<Inode>>>,
48 pub(crate) root_inode: InodeId,
49 pub(crate) time_provider: T,
50}
51
52impl<T: TimeProvider> Fs<T> {
53 pub fn with_time_provider(time_provider: T) -> Self {
54 let mut fs = Self {
55 next_inode: 1,
56 fd_table: Map::new(),
57 inode_table: Map::new(),
58 root_inode: 0,
59 time_provider,
60 };
61
62 let timestamp = fs.time_provider.now();
64 let mut root_inode = Inode::new_dir(0);
65 root_inode.metadata.created = timestamp;
66 root_inode.metadata.modified = timestamp;
67
68 let root = Rc::new(RefCell::new(root_inode));
69 fs.inode_table.insert(0, root);
70 fs.root_inode = 0;
71
72 fs
73 }
74
75 fn allocate_inode(&mut self) -> InodeId {
76 let id = self.next_inode;
77 self.next_inode += 1;
78 id
79 }
80
81 fn allocate_fd(&mut self) -> Fd {
82 let mut fd = 3;
85 while self.fd_table.contains_key(&fd) {
86 fd += 1;
87 }
88 fd
89 }
90
91 fn find_inode(
92 &self,
93 parent_inode: &Rc<RefCell<Inode>>,
94 name: &str,
95 ) -> Result<Rc<RefCell<Inode>>, FsError> {
96 let parent = parent_inode.borrow();
97
98 match &parent.content {
99 FileContent::Dir(entries) => {
100 if let Some(&inode_id) = entries.get(name) {
101 if let Some(inode) = self.inode_table.get(&inode_id) {
102 Ok(inode.clone())
103 } else {
104 Err(FsError::NotFound)
105 }
106 } else {
107 Err(FsError::NotFound)
108 }
109 }
110 FileContent::File(_) => Err(FsError::NotADirectory),
111 }
112 }
113
114 fn create_inode(
115 &mut self,
116 parent_inode: &Rc<RefCell<Inode>>,
117 name: &str,
118 is_dir: bool,
119 ) -> Result<Rc<RefCell<Inode>>, FsError> {
120 let mut parent = parent_inode.borrow_mut();
121
122 match &mut parent.content {
123 FileContent::Dir(entries) => {
124 if entries.contains_key(name) {
126 return Err(FsError::AlreadyExists);
127 }
128
129 let new_inode_id = self.allocate_inode();
131 let timestamp = self.time_provider.now();
132 let mut new_inode = if is_dir {
133 Inode::new_dir(new_inode_id)
134 } else {
135 Inode::new_file(new_inode_id)
136 };
137
138 new_inode.metadata.created = timestamp;
139 new_inode.metadata.modified = timestamp;
140
141 let new_inode_rc = Rc::new(RefCell::new(new_inode));
142 entries.insert(name.into(), new_inode_id);
143 self.inode_table.insert(new_inode_id, new_inode_rc.clone());
144
145 parent.metadata.modified = timestamp;
147
148 Ok(new_inode_rc)
149 }
150 FileContent::File(_) => Err(FsError::NotADirectory),
151 }
152 }
153
154 pub fn open_path(&mut self, path: &str) -> Result<Fd, FsError> {
155 self.open_path_with_flags(path, O_RDWR | O_CREAT)
156 }
157
158 pub fn open_at(&mut self, dir_fd: Fd, path: &str, flags: u32) -> Result<Fd, FsError> {
159 debug!(
160 "open_at: dir_fd={}, path={}, flags={:#x}",
161 dir_fd, path, flags
162 );
163
164 if path.starts_with('/') {
166 error!("open_at: absolute path not allowed");
167 return Err(FsError::InvalidArgument);
168 }
169
170 #[allow(clippy::unnecessary_lazy_evaluations)]
172 let dir_handle = self.fd_table.get(&dir_fd).ok_or_else(|| {
173 error!("open_at: bad directory file descriptor {}", dir_fd);
174 FsError::BadFileDescriptor
175 })?;
176
177 let dir_inode = self
179 .inode_table
180 .get(&dir_handle.inode_id)
181 .ok_or(FsError::NotFound)?
182 .clone();
183
184 {
186 let inode = dir_inode.borrow();
187 if matches!(inode.content, FileContent::File(_)) {
188 error!("open_at: dir_fd {} is not a directory", dir_fd);
189 return Err(FsError::NotADirectory);
190 }
191 }
192
193 if path.is_empty() {
195 error!("open_at: empty path not supported yet");
196 return Err(FsError::InvalidArgument);
197 }
198
199 let comps: alloc::vec::Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
200
201 if comps.is_empty() {
202 return Err(FsError::InvalidArgument);
203 }
204
205 let mut current_inode = dir_inode;
207
208 for comp in comps.iter().take(comps.len() - 1) {
210 current_inode = self.find_inode(¤t_inode, comp)?;
211
212 let inode = current_inode.borrow();
214 if matches!(inode.content, FileContent::File(_)) {
215 return Err(FsError::NotADirectory);
216 }
217 }
218
219 let filename = comps[comps.len() - 1];
221 let file_inode = match self.find_inode(¤t_inode, filename) {
222 Ok(inode) => inode,
223 Err(FsError::NotFound) if flags & O_CREAT != 0 => {
224 self.create_inode(¤t_inode, filename, false)?
226 }
227 Err(e) => return Err(e),
228 };
229
230 {
232 let inode = file_inode.borrow();
233 if matches!(inode.content, FileContent::Dir(_)) {
234 let access_mode = flags & 0x3;
235 if access_mode == O_WRONLY || access_mode == O_RDWR {
237 return Err(FsError::IsADirectory);
238 }
239 if flags & O_TRUNC != 0 {
241 return Err(FsError::IsADirectory);
242 }
243 }
244 }
245
246 if flags & O_TRUNC != 0 {
248 let access_mode = flags & 0x3;
249 if access_mode == O_RDONLY {
250 return Err(FsError::InvalidArgument);
251 }
252 let mut inode = file_inode.borrow_mut();
253 if let FileContent::File(storage) = &mut inode.content {
254 storage.truncate(0);
255 inode.metadata.size = 0;
256 inode.metadata.modified = self.time_provider.now();
257 }
258 }
259
260 let inode_id = file_inode.borrow().id;
261 let handle = FileHandle::new(inode_id, 0, flags);
262
263 let fd = self.allocate_fd();
264 self.fd_table.insert(fd, handle);
265 debug!("open_at: allocated fd={} for inode={}", fd, inode_id);
266 Ok(fd)
267 }
268
269 pub fn open_path_with_flags(&mut self, path: &str, flags: u32) -> Result<Fd, FsError> {
270 debug!("open_path_with_flags: path={}, flags={:#x}", path, flags);
271
272 if path.is_empty() {
273 error!("open_path_with_flags: empty path");
274 return Err(FsError::InvalidArgument);
275 }
276
277 let comps: alloc::vec::Vec<&str> = path
278 .trim_start_matches('/')
279 .split('/')
280 .filter(|s| !s.is_empty())
281 .collect();
282
283 if comps.is_empty() {
285 debug!("open_path_with_flags: opening root directory");
286 let handle = FileHandle::new(self.root_inode, 0, flags);
287 let fd = self.allocate_fd();
288 self.fd_table.insert(fd, handle);
289 debug!("open_path_with_flags: allocated fd={} for root", fd);
290 return Ok(fd);
291 }
292
293 let root_inode = self
294 .inode_table
295 .get(&self.root_inode)
296 .ok_or(FsError::NotFound)?
297 .clone();
298 let mut current_inode = root_inode;
299
300 for comp in comps.iter().take(comps.len() - 1) {
302 current_inode = self.find_inode(¤t_inode, comp)?;
303
304 let inode = current_inode.borrow();
306 if matches!(inode.content, FileContent::File(_)) {
307 return Err(FsError::NotADirectory);
308 }
309 }
310
311 let filename = comps[comps.len() - 1];
313 let file_inode = match self.find_inode(¤t_inode, filename) {
314 Ok(inode) => inode,
315 Err(FsError::NotFound) if flags & O_CREAT != 0 => {
316 self.create_inode(¤t_inode, filename, false)?
318 }
319 Err(e) => return Err(e),
320 };
321
322 {
324 let inode = file_inode.borrow();
325 if matches!(inode.content, FileContent::Dir(_)) {
326 let access_mode = flags & 0x3;
327 if access_mode == O_WRONLY || access_mode == O_RDWR {
329 return Err(FsError::IsADirectory);
330 }
331 if flags & O_TRUNC != 0 {
333 return Err(FsError::IsADirectory);
334 }
335 }
336 }
337
338 if flags & O_TRUNC != 0 {
341 let access_mode = flags & 0x3;
342 if access_mode == O_RDONLY {
343 return Err(FsError::InvalidArgument);
344 }
345 let mut inode = file_inode.borrow_mut();
346 if let FileContent::File(storage) = &mut inode.content {
347 storage.truncate(0);
348 inode.metadata.size = 0;
349 inode.metadata.modified = self.time_provider.now();
350 }
351 }
352
353 let inode_id = file_inode.borrow().id;
354 let handle = FileHandle::new(inode_id, 0, flags);
355
356 let fd = self.allocate_fd();
357 self.fd_table.insert(fd, handle);
358 debug!(
359 "open_path_with_flags: allocated fd={} for inode={}",
360 fd, inode_id
361 );
362 Ok(fd)
363 }
364
365 pub fn write(&mut self, fd: Fd, buf: &[u8]) -> Result<usize, FsError> {
366 trace!("write: fd={}, len={}", fd, buf.len());
367
368 #[allow(clippy::unnecessary_lazy_evaluations)]
369 let handle = self.fd_table.get_mut(&fd).ok_or_else(|| {
370 error!("write: bad file descriptor {}", fd);
371 FsError::BadFileDescriptor
372 })?;
373
374 let access_mode = handle.flags & 0x3;
376 if access_mode == O_RDONLY {
377 return Err(FsError::PermissionDenied);
378 }
379
380 let inode = self
381 .inode_table
382 .get(&handle.inode_id)
383 .ok_or(FsError::NotFound)?;
384 let mut inode_ref = inode.borrow_mut();
385
386 match &mut inode_ref.content {
387 FileContent::File(storage) => {
388 let pos = if handle.flags & O_APPEND != 0 {
390 let file_size = storage.size();
391 handle.set_position(file_size as u64);
392 file_size
393 } else {
394 handle.get_position() as usize
395 };
396
397 let n = storage.write(pos, buf);
398 handle.set_position(handle.get_position() + n as u64);
399 inode_ref.metadata.size = storage.size() as u64;
400 inode_ref.metadata.modified = self.time_provider.now();
401 debug!("write: fd={}, wrote {} bytes at pos {}", fd, n, pos);
402 Ok(n)
403 }
404 FileContent::Dir(_) => {
405 error!("write: fd={} is a directory", fd);
406 Err(FsError::BadFileDescriptor)
407 }
408 }
409 }
410
411 pub fn append_write(&mut self, fd: Fd, buf: &[u8]) -> Result<usize, FsError> {
413 trace!("append_write: fd={}, len={}", fd, buf.len());
414
415 #[allow(clippy::unnecessary_lazy_evaluations)]
416 let handle = self.fd_table.get_mut(&fd).ok_or_else(|| {
417 error!("append_write: bad file descriptor {}", fd);
418 FsError::BadFileDescriptor
419 })?;
420
421 let access_mode = handle.flags & 0x3;
423 if access_mode == O_RDONLY {
424 return Err(FsError::PermissionDenied);
425 }
426
427 let inode = self
428 .inode_table
429 .get(&handle.inode_id)
430 .ok_or(FsError::NotFound)?;
431 let mut inode_ref = inode.borrow_mut();
432
433 match &mut inode_ref.content {
434 FileContent::File(storage) => {
435 let pos = storage.size();
437 let n = storage.write(pos, buf);
438 handle.set_position((pos + n) as u64);
439 inode_ref.metadata.size = storage.size() as u64;
440 inode_ref.metadata.modified = self.time_provider.now();
441 debug!(
442 "append_write: fd={}, appended {} bytes at pos {}",
443 fd, n, pos
444 );
445 Ok(n)
446 }
447 FileContent::Dir(_) => {
448 error!("append_write: fd={} is a directory", fd);
449 Err(FsError::BadFileDescriptor)
450 }
451 }
452 }
453
454 pub fn read(&mut self, fd: Fd, out: &mut [u8]) -> Result<usize, FsError> {
455 trace!("read: fd={}, buf_len={}", fd, out.len());
456
457 #[allow(clippy::unnecessary_lazy_evaluations)]
458 let handle = self.fd_table.get_mut(&fd).ok_or_else(|| {
459 error!("read: bad file descriptor {}", fd);
460 FsError::BadFileDescriptor
461 })?;
462
463 let access_mode = handle.flags & 0x3;
465 if access_mode == O_WRONLY {
466 return Err(FsError::PermissionDenied);
467 }
468
469 let inode = self
470 .inode_table
471 .get(&handle.inode_id)
472 .ok_or(FsError::NotFound)?;
473 let inode_ref = inode.borrow();
474
475 match &inode_ref.content {
476 FileContent::File(storage) => {
477 let pos = handle.get_position() as usize;
478 let n = storage.read(pos, out);
479 handle.set_position(handle.get_position() + n as u64);
480 debug!("read: fd={}, read {} bytes at pos {}", fd, n, pos);
481 Ok(n)
482 }
483 FileContent::Dir(_) => {
484 error!("read: fd={} is a directory", fd);
485 Err(FsError::BadFileDescriptor)
486 }
487 }
488 }
489
490 pub fn ftruncate(&mut self, fd: Fd, size: u64) -> Result<(), FsError> {
491 let handle = self.fd_table.get(&fd).ok_or(FsError::BadFileDescriptor)?;
492
493 let access_mode = handle.flags & 0x3;
495 if access_mode == O_RDONLY {
496 return Err(FsError::PermissionDenied);
497 }
498
499 let inode = self
500 .inode_table
501 .get(&handle.inode_id)
502 .ok_or(FsError::NotFound)?;
503 let mut inode_ref = inode.borrow_mut();
504
505 match &mut inode_ref.content {
506 FileContent::File(storage) => {
507 storage.truncate(size as usize);
508 inode_ref.metadata.size = size;
509 inode_ref.metadata.modified = self.time_provider.now();
510 Ok(())
511 }
512 FileContent::Dir(_) => Err(FsError::BadFileDescriptor),
513 }
514 }
515
516 pub fn close(&mut self, fd: Fd) -> Result<(), FsError> {
517 trace!("close: fd={}", fd);
518
519 #[allow(clippy::unnecessary_lazy_evaluations)]
520 self.fd_table.remove(&fd).ok_or_else(|| {
521 error!("close: bad file descriptor {}", fd);
522 FsError::BadFileDescriptor
523 })?;
524
525 debug!("close: fd={} closed successfully", fd);
526 Ok(())
527 }
528
529 pub fn stat(&self, path: &str) -> Result<Metadata, FsError> {
530 if path.is_empty() {
531 return Err(FsError::InvalidArgument);
532 }
533
534 let comps: alloc::vec::Vec<&str> = path
535 .trim_start_matches('/')
536 .split('/')
537 .filter(|s| !s.is_empty())
538 .collect();
539
540 if comps.is_empty() {
541 let root = self
543 .inode_table
544 .get(&self.root_inode)
545 .ok_or(FsError::NotFound)?;
546 return Ok(root.borrow().metadata);
547 }
548
549 let root_inode = self
550 .inode_table
551 .get(&self.root_inode)
552 .ok_or(FsError::NotFound)?
553 .clone();
554 let mut current_inode = root_inode;
555
556 for comp in comps.iter() {
558 current_inode = self.find_inode(¤t_inode, comp)?;
559 }
560
561 let metadata = current_inode.borrow().metadata;
562 Ok(metadata)
563 }
564
565 pub fn fstat(&self, fd: Fd) -> Result<Metadata, FsError> {
566 let handle = self.fd_table.get(&fd).ok_or(FsError::BadFileDescriptor)?;
567 let inode = self
568 .inode_table
569 .get(&handle.inode_id)
570 .ok_or(FsError::NotFound)?;
571 Ok(inode.borrow().metadata)
572 }
573
574 pub fn seek(&mut self, fd: Fd, offset: i64, whence: i32) -> Result<u64, FsError> {
575 trace!("seek: fd={}, offset={}, whence={}", fd, offset, whence);
576
577 const SEEK_SET: i32 = 0;
578 const SEEK_CUR: i32 = 1;
579 const SEEK_END: i32 = 2;
580
581 #[allow(clippy::unnecessary_lazy_evaluations)]
582 let handle = self.fd_table.get_mut(&fd).ok_or_else(|| {
583 error!("seek: bad file descriptor {}", fd);
584 FsError::BadFileDescriptor
585 })?;
586 let inode = self
587 .inode_table
588 .get(&handle.inode_id)
589 .ok_or(FsError::NotFound)?;
590 let inode_ref = inode.borrow();
591
592 match &inode_ref.content {
593 FileContent::File(storage) => {
594 let new_pos = match whence {
595 SEEK_SET => {
596 if offset < 0 {
597 return Err(FsError::InvalidArgument);
598 }
599 offset as u64
600 }
601 SEEK_CUR => {
602 let new_pos_signed = handle.get_position() as i64 + offset;
603 if new_pos_signed < 0 {
604 return Err(FsError::InvalidArgument);
605 }
606 new_pos_signed as u64
607 }
608 SEEK_END => {
609 let new_pos_signed = storage.size() as i64 + offset;
610 if new_pos_signed < 0 {
611 return Err(FsError::InvalidArgument);
612 }
613 new_pos_signed as u64
614 }
615 _ => {
616 error!("seek: invalid whence {}", whence);
617 return Err(FsError::InvalidArgument);
618 }
619 };
620
621 handle.set_position(new_pos);
622 debug!("seek: fd={}, new_pos={}", fd, new_pos);
623 Ok(new_pos)
624 }
625 FileContent::Dir(_) => {
626 error!("seek: fd={} is a directory", fd);
627 Err(FsError::BadFileDescriptor)
628 }
629 }
630 }
631
632 pub fn mkdir(&mut self, path: &str) -> Result<(), FsError> {
633 debug!("mkdir: path={}", path);
634
635 if path.is_empty() {
636 error!("mkdir: empty path");
637 return Err(FsError::InvalidArgument);
638 }
639
640 let comps: alloc::vec::Vec<&str> = path
641 .trim_start_matches('/')
642 .split('/')
643 .filter(|s| !s.is_empty())
644 .collect();
645
646 if comps.is_empty() {
647 return Err(FsError::InvalidArgument);
648 }
649
650 let root_inode = self
651 .inode_table
652 .get(&self.root_inode)
653 .ok_or(FsError::NotFound)?
654 .clone();
655 let mut current_inode = root_inode;
656
657 for comp in comps.iter().take(comps.len() - 1) {
659 current_inode = self.find_inode(¤t_inode, comp)?;
660
661 let inode = current_inode.borrow();
663 if matches!(inode.content, FileContent::File(_)) {
664 return Err(FsError::NotADirectory);
665 }
666 }
667
668 let dirname = comps[comps.len() - 1];
670 self.create_inode(¤t_inode, dirname, true)?;
671 debug!("mkdir: created directory {}", path);
672 Ok(())
673 }
674
675 pub fn mkdir_p(&mut self, path: &str) -> Result<(), FsError> {
676 if path.is_empty() {
677 return Err(FsError::InvalidArgument);
678 }
679
680 let comps: alloc::vec::Vec<&str> = path
681 .trim_start_matches('/')
682 .split('/')
683 .filter(|s| !s.is_empty())
684 .collect();
685
686 if comps.is_empty() {
687 return Ok(()); }
689
690 let root_inode = self
691 .inode_table
692 .get(&self.root_inode)
693 .ok_or(FsError::NotFound)?
694 .clone();
695 let mut current_inode = root_inode;
696
697 for comp in comps.iter() {
699 current_inode = match self.find_inode(¤t_inode, comp) {
700 Ok(inode) => {
701 {
703 let borrowed = inode.borrow();
704 if matches!(borrowed.content, FileContent::File(_)) {
705 return Err(FsError::NotADirectory);
706 }
707 }
708 inode
709 }
710 Err(FsError::NotFound) => {
711 self.create_inode(¤t_inode, comp, true)?
713 }
714 Err(e) => return Err(e),
715 };
716 }
717
718 Ok(())
719 }
720
721 pub fn unlink(&mut self, path: &str) -> Result<(), FsError> {
722 debug!("unlink: path={}", path);
723
724 if path.is_empty() {
725 error!("unlink: empty path");
726 return Err(FsError::InvalidArgument);
727 }
728
729 let comps: alloc::vec::Vec<&str> = path
730 .trim_start_matches('/')
731 .split('/')
732 .filter(|s| !s.is_empty())
733 .collect();
734
735 if comps.is_empty() {
736 return Err(FsError::InvalidArgument);
738 }
739
740 let root_inode = self
741 .inode_table
742 .get(&self.root_inode)
743 .ok_or(FsError::NotFound)?
744 .clone();
745 let mut current_inode = root_inode;
746
747 for comp in comps.iter().take(comps.len() - 1) {
749 current_inode = self.find_inode(¤t_inode, comp)?;
750
751 let inode = current_inode.borrow();
753 if matches!(inode.content, FileContent::File(_)) {
754 return Err(FsError::NotADirectory);
755 }
756 }
757
758 let filename = comps[comps.len() - 1];
760
761 let target_inode = self.find_inode(¤t_inode, filename)?;
763
764 {
766 let target = target_inode.borrow();
767 if matches!(target.content, FileContent::Dir(_)) {
768 return Err(FsError::IsADirectory);
769 }
770 }
771
772 let mut parent = current_inode.borrow_mut();
774 match &mut parent.content {
775 FileContent::Dir(entries) => {
776 entries.remove(filename);
777
778 let timestamp = self.time_provider.now();
780 parent.metadata.modified = timestamp;
781
782 debug!("unlink: removed {}", path);
783 Ok(())
784 }
785 FileContent::File(_) => Err(FsError::NotADirectory),
786 }
787 }
789
790 pub fn readdir(&self, path: &str) -> Result<alloc::vec::Vec<alloc::string::String>, FsError> {
791 if path.is_empty() {
792 return Err(FsError::InvalidArgument);
793 }
794
795 let comps: alloc::vec::Vec<&str> = path
796 .trim_start_matches('/')
797 .split('/')
798 .filter(|s| !s.is_empty())
799 .collect();
800
801 let root_inode = self
802 .inode_table
803 .get(&self.root_inode)
804 .ok_or(FsError::NotFound)?
805 .clone();
806 let mut current_inode = root_inode;
807
808 for comp in comps.iter() {
810 current_inode = self.find_inode(¤t_inode, comp)?;
811 }
812
813 let inode = current_inode.borrow();
815 match &inode.content {
816 FileContent::Dir(entries) => {
817 let mut result = alloc::vec::Vec::new();
818 for name in entries.keys() {
819 result.push(name.clone());
820 }
821 Ok(result)
822 }
823 FileContent::File(_) => Err(FsError::NotADirectory),
824 }
825 }
826
827 pub fn readdir_fd(
830 &self,
831 fd: Fd,
832 ) -> Result<alloc::vec::Vec<(alloc::string::String, bool)>, FsError> {
833 let handle = self.fd_table.get(&fd).ok_or(FsError::BadFileDescriptor)?;
835
836 let inode = self
838 .inode_table
839 .get(&handle.inode_id)
840 .ok_or(FsError::NotFound)?;
841
842 let inode_ref = inode.borrow();
844 match &inode_ref.content {
845 FileContent::Dir(entries) => {
846 let mut result = alloc::vec::Vec::new();
847 for (name, child_inode_id) in entries.iter() {
848 if let Some(child_inode) = self.inode_table.get(child_inode_id) {
850 let is_dir = child_inode.borrow().metadata.is_dir;
851 result.push((name.clone(), is_dir));
852 }
853 }
854 Ok(result)
855 }
856 FileContent::File(_) => Err(FsError::NotADirectory),
857 }
858 }
859
860 pub fn rmdir(&mut self, path: &str) -> Result<(), FsError> {
862 debug!("rmdir: path={}", path);
863
864 if path.is_empty() {
865 error!("rmdir: empty path");
866 return Err(FsError::InvalidArgument);
867 }
868
869 let comps: alloc::vec::Vec<&str> = path
870 .trim_start_matches('/')
871 .split('/')
872 .filter(|s| !s.is_empty())
873 .collect();
874
875 if comps.is_empty() {
876 return Err(FsError::InvalidArgument);
878 }
879
880 let root_inode = self
881 .inode_table
882 .get(&self.root_inode)
883 .ok_or(FsError::NotFound)?
884 .clone();
885 let mut current_inode = root_inode;
886
887 for comp in comps.iter().take(comps.len() - 1) {
889 current_inode = self.find_inode(¤t_inode, comp)?;
890
891 let inode = current_inode.borrow();
893 if matches!(inode.content, FileContent::File(_)) {
894 return Err(FsError::NotADirectory);
895 }
896 }
897
898 let dirname = comps[comps.len() - 1];
900
901 let target_inode = self.find_inode(¤t_inode, dirname)?;
903
904 {
906 let target = target_inode.borrow();
907 match &target.content {
908 FileContent::File(_) => {
909 return Err(FsError::NotADirectory);
910 }
911 FileContent::Dir(entries) => {
912 if !entries.is_empty() {
914 error!("rmdir: directory not empty");
915 return Err(FsError::NotEmpty);
916 }
917 }
918 }
919 }
920
921 let mut parent = current_inode.borrow_mut();
923 match &mut parent.content {
924 FileContent::Dir(entries) => {
925 entries.remove(dirname);
926
927 let timestamp = self.time_provider.now();
929 parent.metadata.modified = timestamp;
930
931 debug!("rmdir: removed {}", path);
932 Ok(())
933 }
934 FileContent::File(_) => Err(FsError::NotADirectory),
935 }
936 }
938
939 pub fn rename(&mut self, old_path: &str, new_path: &str) -> Result<(), FsError> {
941 debug!("rename: old_path={}, new_path={}", old_path, new_path);
942
943 if old_path.is_empty() || new_path.is_empty() {
944 error!("rename: empty path");
945 return Err(FsError::InvalidArgument);
946 }
947
948 let old_comps: alloc::vec::Vec<&str> = old_path
949 .trim_start_matches('/')
950 .split('/')
951 .filter(|s| !s.is_empty())
952 .collect();
953
954 let new_comps: alloc::vec::Vec<&str> = new_path
955 .trim_start_matches('/')
956 .split('/')
957 .filter(|s| !s.is_empty())
958 .collect();
959
960 if old_comps.is_empty() || new_comps.is_empty() {
961 return Err(FsError::InvalidArgument);
963 }
964
965 if old_comps == new_comps {
967 return Ok(());
968 }
969
970 let root_inode = self
972 .inode_table
973 .get(&self.root_inode)
974 .ok_or(FsError::NotFound)?
975 .clone();
976 let mut old_parent = root_inode.clone();
977
978 for comp in old_comps.iter().take(old_comps.len() - 1) {
979 old_parent = self.find_inode(&old_parent, comp)?;
980 let inode = old_parent.borrow();
981 if matches!(inode.content, FileContent::File(_)) {
982 return Err(FsError::NotADirectory);
983 }
984 }
985
986 let old_name = old_comps[old_comps.len() - 1];
987
988 let source_inode = self.find_inode(&old_parent, old_name)?;
990 let source_is_dir = {
991 let inode = source_inode.borrow();
992 inode.metadata.is_dir
993 };
994
995 let mut new_parent = root_inode;
997
998 for comp in new_comps.iter().take(new_comps.len() - 1) {
999 new_parent = self.find_inode(&new_parent, comp)?;
1000 let inode = new_parent.borrow();
1001 if matches!(inode.content, FileContent::File(_)) {
1002 return Err(FsError::NotADirectory);
1003 }
1004 }
1005
1006 let new_name = new_comps[new_comps.len() - 1];
1007
1008 if let Ok(dest_inode) = self.find_inode(&new_parent, new_name) {
1010 let dest = dest_inode.borrow();
1011 let dest_is_dir = dest.metadata.is_dir;
1012
1013 if source_is_dir && !dest_is_dir {
1014 return Err(FsError::NotADirectory);
1015 }
1016 if !source_is_dir && dest_is_dir {
1017 return Err(FsError::IsADirectory);
1018 }
1019 if dest_is_dir
1020 && matches!(&dest.content, FileContent::Dir(entries) if !entries.is_empty())
1021 {
1022 return Err(FsError::NotEmpty);
1023 }
1024 }
1025
1026 let old_parent_id = old_parent.borrow().id;
1028 let new_parent_id = new_parent.borrow().id;
1029
1030 let timestamp = self.time_provider.now();
1031
1032 if old_parent_id == new_parent_id {
1033 let mut parent = old_parent.borrow_mut();
1035 if let FileContent::Dir(entries) = &mut parent.content {
1036 if let Some(inode_id) = entries.remove(old_name) {
1037 entries.insert(alloc::string::String::from(new_name), inode_id);
1038 parent.metadata.modified = timestamp;
1039 } else {
1040 return Err(FsError::NotFound);
1041 }
1042 } else {
1043 return Err(FsError::NotADirectory);
1044 }
1045 } else {
1046 let inode_id = {
1048 let mut parent = old_parent.borrow_mut();
1049 if let FileContent::Dir(entries) = &mut parent.content {
1050 let id = entries.remove(old_name).ok_or(FsError::NotFound)?;
1051 parent.metadata.modified = timestamp;
1052 id
1053 } else {
1054 return Err(FsError::NotADirectory);
1055 }
1056 };
1057
1058 let mut parent = new_parent.borrow_mut();
1059 if let FileContent::Dir(entries) = &mut parent.content {
1060 entries.insert(alloc::string::String::from(new_name), inode_id);
1061 parent.metadata.modified = timestamp;
1062 } else {
1063 return Err(FsError::NotADirectory);
1064 }
1065 }
1066
1067 debug!("rename: {} -> {}", old_path, new_path);
1068 Ok(())
1069 }
1070}
1071
1072impl Default for Fs<MonotonicCounter> {
1073 fn default() -> Self {
1074 Self::new()
1075 }
1076}
1077
1078impl Fs<MonotonicCounter> {
1079 pub fn new() -> Self {
1080 Self::with_time_provider(MonotonicCounter::new())
1081 }
1082}