keos_project5/ffs/
fs_objects.rs

1//! ## File system objects.
2//!
3//! Every on-disk object is represented by an **inode**. This inode is
4//! diverge into multiple file types like a **regular file** or a **directory**
5//! based on the [`Inode::ftype`] field. In this project, you will support only
6//! two primary types of file:
7//! - [`RegularFile`]: A **regular file** is the most common file type. It
8//!   represents a sequence of bytes, typically used to store application data,
9//!   logs, executables, or any other form of persistent information. The
10//!   operating system exposes system calls such as `read()`, and `write()` to
11//!   allow user programs to interact with regular files. Inodes for regular
12//!   files point to data blocks on disk that store the actual file content.
13//! - [`Directory`]: A **directory** is a special type of file that acts as a
14//!   container for other files. Internally, a directory consists of a list of
15//!   entries that map human-readable file names to inode numbers. This mapping
16//!   allows the kernel to resolve file paths and traverse the hierarchical file
17//!   system structure. Directory inodes point to data blocks containing
18//!   serialized directory entries rather than arbitrary user data.
19//!
20//! ## Directory Internals
21//! A directory is represented as a collection of directory entries
22//! stored within the **data blocks** of the directory inode. Each directory
23//! block contains an array of [`DirectoryBlockEntry`] structures, which provide
24//! the mapping between human-readable names and their corresponding inode
25//! numbers. All directory operations, such as adding, removing, or searching
26//! for files and subdirectories, are performed by manipulating this collection
27//! of entries. The directory **MUST** start with two entries: "." and "..",
28//! which points to itself and the parent directory respectively.
29//!
30//! ## Implementation Requirements
31//! You need to implement the followings:
32//! - [`RegularFile::read`]
33//! - [`RegularFile::write`]
34//! - [`Directory::read_dir`]
35//! - [`Directory::find`]
36//! - [`Directory::open_entry`]
37//! - [`Directory::create_entry`]
38//!
39//! At this point, you have implemented the core abstractions for managing
40//! inodes in the filesystem, supporting both regular files and directories.
41//! These abstractions provide safe and structured access to both on-disk and
42//! in-memory inode data, allowing higher-level components to interact with
43//! files without dealing directly with low-level disk structures.
44//!
45//! This module gives you insight into the internal foundations of the file
46//! abstraction in the filesystem. The next [`section`] will focus on
47//! **maintaining crash consistency in the filesystem**..
48//!
49//! [`section`]: mod@crate::ffs::journal
50#[cfg(doc)]
51use crate::ffs::inode::Inode;
52use crate::ffs::{
53    FastFileSystemInner, FileBlockNumber, InodeNumber,
54    access_control::{MetaData, TrackedInode},
55    disk_layout::{DirectoryBlock, DirectoryBlockEntry},
56    journal::RunningTransaction,
57    types::FileType,
58};
59use alloc::{
60    string::String,
61    sync::{Arc, Weak},
62    vec::Vec,
63};
64#[cfg(doc)]
65use keos::fs::traits::{Directory as _Directory, RegularFile as _RegularFile};
66use keos::{KernelError, sync::atomic::AtomicBool};
67
68/// A handle to a regular file in the filesystem.
69///
70/// This struct represents a low-level kernel handle to a regular file,
71/// associated with a specific [`TrackedInode`] and the backing
72/// [`FastFileSystemInner`] instance.
73pub struct RegularFile {
74    /// Weak reference to the [`FastFileSystemInner`].
75    ffs: Weak<FastFileSystemInner>,
76    /// The inode associated with this file.
77    inode: TrackedInode,
78}
79
80impl RegularFile {
81    /// Creates a new [`RegularFile`] from a given inode and filesystem
82    /// reference.
83    ///
84    /// Returns `None` if the provided inode does not represent a regular file.
85    ///
86    /// # Parameters
87    /// - `inode`: A tracked reference to the file’s inode.
88    /// - `ffs`: A weak reference to the filesystem context.
89    ///
90    /// # Returns
91    /// - `Some(RegularFile)` if the inode is valid and represents a regular
92    ///   file.
93    /// - `None` if the inode is invalid or not a regular file.
94    pub fn new(inode: TrackedInode, ffs: Weak<FastFileSystemInner>) -> Option<Self> {
95        if inode.read().ftype == FileType::RegularFile {
96            Some(Self { inode, ffs })
97        } else {
98            None
99        }
100    }
101}
102
103impl keos::fs::traits::RegularFile for RegularFile {
104    /// Inode number of the file.
105    fn ino(&self) -> InodeNumber {
106        self.inode.read().ino
107    }
108
109    /// Returns the size of the file in bytes.
110    fn size(&self) -> usize {
111        self.inode.read().size
112    }
113
114    /// Reads data from the file into the provided buffer.
115    ///
116    /// # Parameters
117    /// - `ofs`: The `FileBlockNumber` which to read.
118    /// - `buf`: A mutable array where the file content will be stored.
119    ///
120    /// # Returns
121    /// - `Ok(true)`: If the read success.
122    /// - `Ok(false)`: If the read failed.
123    /// - `Err(Error)`: An error occured while the read operation.
124    fn read(&self, fba: FileBlockNumber, buf: &mut [u8; 4096]) -> Result<bool, keos::KernelError> {
125        let ffs = self.ffs.upgrade().unwrap();
126        let inode = self.inode.read();
127        match inode.get(&ffs, fba)? {
128            Some(lba) => {
129                todo!();
130            }
131            None => Ok(false),
132        }
133    }
134
135    /// Writes a 4096-byte data into the specified file block.
136    ///
137    /// This method writes the contents of `buf` to the file block indicated by
138    /// `fba`. If the target block lies beyond the current end of the file,
139    /// the file may be extended up to `new_size` bytes to accommodate the
140    /// write.
141    ///
142    /// However, if the target block lies beyond the current file size **and**
143    /// `new_size` is insufficient to reach it, the write will fail.
144    ///
145    /// # Parameters
146    /// - `fba`: The `FileBlockNumber` indicating the block to write to.
147    /// - `buf`: A buffer containing exactly 4096 bytes of data to write.
148    /// - `new_size`: The desired minimum file size (in bytes) after the write.
149    ///   If this value is less than or equal to the current file size, no
150    ///   growth occurs.
151    ///
152    /// # Returns
153    /// - `Ok(())` if the write is successful.
154    /// - `Err(KernelError)` if the operation fails (e.g., out-of-bounds write,
155    ///   I/O error).
156    fn write(
157        &self,
158        fba: FileBlockNumber,
159        buf: &[u8; 4096],
160        min_size: usize,
161    ) -> Result<(), keos::KernelError> {
162        let ffs = self.ffs.upgrade().unwrap();
163        let tx = ffs.open_transaction("RegularFile::write");
164        self.inode.write_with(&tx, |mut inode| {
165            // Hint: Must conduct the following step
166            // 1: Grow.
167            // 2: Update the field `size`.
168            // 3: Write to the data block.
169            // 4: Submit change of the inode.
170            todo!();
171        })?;
172        tx.commit()?;
173
174        Ok(())
175    }
176
177    fn writeback(&self) -> Result<(), keos::KernelError> {
178        Ok(())
179    }
180}
181
182/// Represents a directory, which contains multiple directory entries.
183///
184/// This structure provides access to the directory's inode, which stores
185/// information about the directory's metadata and contents.
186pub struct Directory {
187    /// Weak reference to the file system reference.
188    pub ffs: Weak<FastFileSystemInner>,
189    /// The inode associated with this directory.
190    pub inode: TrackedInode,
191    /// Whether the directory is removed,
192    pub removed: AtomicBool,
193}
194
195impl Directory {
196    /// Creates a new `Directory` from the given inode.
197    ///
198    /// # Arguments
199    /// - `inode`: The tracked inode representing this directory.
200    /// - `ffs`: A weak reference to the owning `FastFileSystemInner`.
201    ///
202    /// # Returns
203    /// - `Some(Directory)`: if the provided inode is valid and represents a
204    ///   directory.
205    /// - `None`: if the inode is not of type `File::Directory`.
206    pub fn new(inode: TrackedInode, ffs: Weak<FastFileSystemInner>) -> Option<Self> {
207        if inode.read().ftype == FileType::Directory {
208            Some(Self {
209                inode,
210                ffs,
211                removed: AtomicBool::new(false),
212            })
213        } else {
214            None
215        }
216    }
217
218    /// Reads the contents of the directory.
219    ///
220    /// This function lists all the entries within the directory as a [`Vec`].
221    ///
222    /// A single entry is a tuple that consists of inode number and file name,
223    /// that is `(InodeNumber, String)`.
224    ///
225    /// # Returns
226    /// - `Ok(())`: If the directory was successfully read.
227    /// - `Err(Error)`: An error if the read operation fails.
228    pub fn read_dir(
229        &self,
230        ffs: &FastFileSystemInner,
231    ) -> Result<Vec<(InodeNumber, String)>, keos::KernelError> {
232        let mut output = Vec::new();
233        let inode = self.inode.read();
234        for fba in (0..inode.size.div_ceil(4096)).map(FileBlockNumber) {
235            todo!()
236        }
237        Ok(output)
238    }
239
240    /// Finds the inode number corresponding to a directory entry by name.
241    ///
242    /// # Arguments
243    /// - `ffs`: Reference to the file system’s internal structure.
244    /// - `entry`: The name of the directory entry to search for.
245    ///
246    /// # Returns
247    /// - `Ok(inode_number)`: if the entry is found in the directory.
248    /// - `Err(KernelError)`: if the entry is not found or other errors occurs.
249    pub fn find(&self, ffs: &FastFileSystemInner, entry: &str) -> Result<InodeNumber, KernelError> {
250        todo!()
251    }
252
253    /// Adds a new entry to the directory.
254    ///
255    /// # Arguments
256    /// - `ffs`: Reference to the file system’s internal structure.
257    /// - `entry`: The name of the new directory entry.
258    /// - `is_dir`: Whether the entry is a subdirectory (`true`) or a regular
259    ///   file (`false`).
260    /// - `tx`: A running transaction used to persist metadata changes.
261    ///
262    /// # Returns
263    /// - `Ok(())`: if the entry is successfully added.
264    /// - `Err(KernelError)`: if an error occurs (e.g., entry already exists or
265    ///   out of space).
266    pub fn add_entry(
267        &self,
268        ffs: &Arc<FastFileSystemInner>,
269        entry: &str,
270        ino: InodeNumber,
271        tx: &RunningTransaction,
272    ) -> Result<(), KernelError> {
273        if self.removed.load() {
274            return Err(KernelError::NoSuchEntry);
275        }
276        let en = DirectoryBlockEntry::from_ino_name(ino, entry).ok_or(KernelError::NameTooLong)?;
277        // Read path
278        {
279            let inode = self.inode.read();
280            // Find reusable entry.
281            for fba in (0..inode.size.div_ceil(4096)).map(FileBlockNumber) {
282                let lba = inode.get(ffs, fba)?;
283                let blk = DirectoryBlock::load(ffs, lba.unwrap())?;
284                let mut fit = None;
285                {
286                    let guard = blk.read();
287                    for (i, en) in guard.iter().enumerate() {
288                        if en.inode.is_none() {
289                            fit = Some(i);
290                            break;
291                        }
292                    }
293                }
294                if let Some(fit) = fit {
295                    let mut guard = blk.write(tx);
296                    guard[fit] = en;
297                    guard.submit();
298                    drop(inode);
299                    return ffs.get_inode(ino).unwrap().write_with(tx, |mut inode| {
300                        inode.link_count += 1;
301                        inode.submit();
302                        Ok(())
303                    });
304                }
305            }
306        }
307
308        self.inode.write_with(tx, |mut inode| {
309            // Grow the directory if no available space.
310            let until = FileBlockNumber(inode.size.div_ceil(0x1000));
311            inode.grow(ffs, until, tx)?;
312            inode.size += 0x1000;
313
314            // Fill the entry.
315            let lba = inode.get(ffs, until)?;
316            let blk = DirectoryBlock::load(ffs, lba.unwrap())?;
317            let mut guard = blk.write(tx);
318            guard[0] = en;
319            guard.submit();
320            inode.submit();
321            ffs.get_inode(ino).unwrap().write_with(tx, |mut inode| {
322                inode.link_count += 1;
323                inode.submit();
324                Ok(())
325            })
326        })
327    }
328
329    /// Takes an existing entry out of the directory.
330    ///
331    /// # Arguments
332    /// - `ffs`: Reference to the file system’s internal structure.
333    /// - `entry`: The name of the entry to remove.
334    /// - `tx`: A running transaction used to persist metadata changes.
335    ///
336    /// # Returns
337    /// - `Ok(())`: if the entry is successfully removed.
338    /// - `Err(KernelError)`: if the entry does not exist or an I/O error
339    ///   occurs.
340    pub fn take_entry(
341        &self,
342        ffs: &Arc<FastFileSystemInner>,
343        entry: &str,
344        tx: &RunningTransaction,
345    ) -> Result<TrackedInode, KernelError> {
346        let guard = self.inode.read();
347        for fba in (0..guard.size.div_ceil(4096)).map(FileBlockNumber) {
348            let lba = guard.get(ffs, fba)?;
349            let blk = DirectoryBlock::load(ffs, lba.unwrap())?;
350            let mut fit = None;
351            {
352                let guard = blk.read();
353                for (i, en) in guard.iter().enumerate() {
354                    if en.name() == Some(entry) {
355                        fit = Some(i);
356                        break;
357                    }
358                }
359            }
360            if let Some(fit) = fit {
361                let mut guard = blk.write(tx);
362                let ino = guard[fit].inode.take();
363                guard.submit();
364                return ffs
365                    .get_inode(ino.ok_or(KernelError::FilesystemCorrupted("DirectoryEntry"))?);
366            }
367        }
368        Err(KernelError::NoSuchEntry)
369    }
370}
371
372impl keos::fs::traits::Directory for Directory {
373    /// Inode number of the directory.
374    fn ino(&self) -> InodeNumber {
375        self.inode.read().ino
376    }
377
378    /// Returns the size of the file in bytes.
379    #[inline]
380    fn size(&self) -> usize {
381        self.inode.read().size
382    }
383
384    /// Link count of the directory.
385    fn link_count(&self) -> usize {
386        self.inode.read().link_count
387    }
388
389    /// Opens an entry by name.
390    ///
391    /// # Parameters
392    /// - `entry`: The name of the entry to open.
393    ///
394    /// # Returns
395    /// - `Ok(File)`: The enumerate of the file (e.g., regular file, directory).
396    /// - `Err(Error)`: An error if the entry cannot be found or accessed.
397    fn open_entry(&self, entry: &str) -> Result<keos::fs::File, keos::KernelError> {
398        // Get the filesystem from the weak reference.
399        let ffs = self
400            .ffs
401            .upgrade()
402            .ok_or(KernelError::FilesystemCorrupted("File system closed."))?;
403        // Find the inode corresponding to the entry from the directory.
404        let ino = self.find(&ffs, entry)?;
405        let inode = ffs.get_inode(ino)?;
406        todo!()
407    }
408
409    /// Add an entry by name.
410    ///
411    /// # Parameters
412    /// - `entry`: The name of the entry to add.
413    /// - `is_dir`: Indicate whether the entry is directory or not.
414    ///
415    /// # Returns
416    /// - `Ok(())`: If the entry was successfully added.
417    /// - `Err(Error)`: An error if the add fails.
418    fn create_entry(&self, entry: &str, is_dir: bool) -> Result<keos::fs::File, keos::KernelError> {
419        // Get the filesystem from the weak reference.
420        let ffs = self
421            .ffs
422            .upgrade()
423            .ok_or(KernelError::FilesystemCorrupted("File system closed."))?;
424        // Find whether the duplicated entry exists.
425        match self.find(&ffs, entry) {
426            Err(KernelError::NoSuchEntry) => {
427                // If not exist, add the entry to the directory.
428                let tx = ffs.open_transaction("Directory::add_entry");
429                let parent_ino = self.inode.read().ino;
430                let (ino, inode) = ffs.allocate_inode(is_dir, &tx)?;
431                todo!()
432            }
433            Ok(_) => Err(KernelError::FileExist),
434            Err(e) => Err(e),
435        }
436    }
437
438    /// Removes a directory entry by name.
439    ///
440    /// # Errors
441    /// - Returns [`KernelError::Busy`] when entry's Inode number is `1`
442    ///  as it means it's a root directory.
443    /// - Returns [`KernelError::DirectoryNotEmpty`] when the entry is a
444    ///  non-empty directory
445    /// - Returns [`KernelError::NoSuchEntry`] if specified entry does
446    ///  not exists.
447    ///
448    /// # Parameters
449    /// - `entry`: The name of the entry to remove.
450    ///
451    /// # Returns
452    /// - `Ok(())`: If the entry was successfully removed.
453    /// - `Err(Error)`: An error if the removal fails.
454    fn unlink_entry(&self, entry: &str) -> Result<(), keos::KernelError> {
455        // Get the filesystem from the weak reference.
456        let ffs = self
457            .ffs
458            .upgrade()
459            .ok_or(KernelError::FilesystemCorrupted("File system closed."))?;
460
461        let tx = ffs.open_transaction("Directory::remove_entry");
462        let inode = self.open_entry(entry)?;
463
464        if inode.ino() == InodeNumber::new(1).unwrap() {
465            return Err(KernelError::Busy);
466        }
467
468        let links_to_dec = match inode {
469            keos::fs::File::Directory(d) => {
470                if d.read_dir()?.len() != 2 {
471                    Err(KernelError::DirectoryNotEmpty)
472                } else {
473                    match d.removed()?.swap(true) {
474                        true => Err(KernelError::NoSuchEntry),
475                        false => Ok(2),
476                    }
477                }
478            }
479            _ => Ok(1),
480        }?;
481        let inode = self.take_entry(&ffs, entry, &tx)?;
482        inode.write_with(&tx, |mut ino| {
483            ino.link_count -= links_to_dec;
484            ino.submit();
485            Ok(())
486        })?;
487        tx.commit()
488    }
489
490    /// Reads the contents of the directory.
491    ///
492    /// This function lists all the entries within the directory.
493    ///
494    /// # Returns
495    /// - `Ok(())`: If the directory was successfully read.
496    /// - `Err(Error)`: An error if the read operation fails.
497    fn read_dir(&self) -> Result<Vec<(InodeNumber, String)>, keos::KernelError> {
498        // Get the filesystem from the weak reference.
499        let ffs = self
500            .ffs
501            .upgrade()
502            .ok_or(KernelError::FilesystemCorrupted("File system closed."))?;
503        self.read_dir(&ffs)
504    }
505
506    /// Returns [`AtomicBool`] which contains whether directory is removed.
507    ///
508    /// This is important because directory operations against the removed
509    /// directory will result in undesirable behavior (e.g. unreachable file).
510    ///
511    /// # Returns
512    /// - `Ok(())`: If the directory was successfully read.
513    /// - `Err(Error)`: An error if the operation fails.
514    fn removed(&self) -> Result<&AtomicBool, KernelError> {
515        Ok(&self.removed)
516    }
517}