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}