keos_project5/ffs/
disk_layout.rs

1//! **on-disk layout** of the file system's metadata structures.
2//!
3//! This module defines the **on-disk layout** of the file system's core
4//! metadata structures. These types represent raw disk-resident data such as
5//! the superblock, block/inode allocation bitmaps, and inode table. Each struct
6//! in this module is tightly packed and designed to match the exact binary
7//! layout used by the file system when persisting and loading from disk.
8//!
9//! This module defines [`MetaData`] trait, and support [`MetaData::load`]
10//! method, allowing them to be generically loaded from a logical block address
11//! (LBA).
12//!
13//! ## Examples of accessing metadata.
14//! ```
15//! use crate::ffs::disk_layout::{MetaData, BlockBitmap};
16//!
17//! let block_bitmap = BlockBitmap::load(ffs, lba)?; // read BlockPointsTo<BlockBitMap> from lba.
18//! { // READ
19//!     let guard = block_bitmap.read(); // reading metadata does not requires transaction.
20//!     assert!(guard.is_allocated(0));
21//! }
22//! { // WRITE
23//!     let tx = ffs.open_transaction(); // Open a new transaction. Only a single transaction can be existed.
24//!     let mut guard = block_bitmap.write(&tx); // writing metadata requires transaction.
25//!     guard.try_allocate(1);
26//!     guard.submit(); // Submit the modified change to the transaction.
27//! }
28//! ```
29use crate::ffs::{
30    FastFileSystemInner, InodeNumber, JournalIO, LogicalBlockAddress, access_control::MetaData,
31};
32use alloc::boxed::Box;
33use core::fmt::Debug;
34use keos::{KernelError, fs::Disk};
35
36/// A struct for denying implementing [`MetaData`] from outside of this module.
37#[doc(hidden)]
38pub struct Private {
39    _p: (),
40}
41
42/// On-disk representation of the superblock of the Fast File System (FFS).
43///
44/// The superblock contains essential metadata about the filesystem,
45/// including the total number of blocks and inodes, as well as the
46/// size of the journal.
47#[repr(C)]
48#[derive(Clone, Copy)]
49pub struct SuperBlock {
50    /// File system magic: "KeOSFFS\0".
51    pub magic: [u8; 8],
52    /// Total number of blocks in the filesystem.
53    pub block_count: u64,
54    /// In-used count of blocks in the filesystem.
55    pub block_count_inused: u64,
56    /// Total number of inodes in the filesystem.
57    pub inode_count: u64,
58    /// In-used count of inodes in the filesystem.
59    pub inode_count_inused: u64,
60    /// A indicator that this filesystem have journaling feature.
61    pub has_journal: u64,
62    /// Padding to align to Block size.
63    pub _pad: [u8; 4096 - core::mem::size_of::<u64>() * 5 - 8],
64}
65
66impl Default for SuperBlock {
67    fn default() -> Self {
68        Self {
69            magic: [0; 8],
70            block_count: 0,
71            block_count_inused: 0,
72            inode_count: 0,
73            inode_count_inused: 0,
74            has_journal: 0,
75            _pad: [0; 4096 - 48],
76        }
77    }
78}
79
80impl Debug for SuperBlock {
81    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82        f.debug_struct("SuperBlock")
83            .field("magic", &self.magic)
84            .field("block_count", &self.block_count)
85            .field("block_count_inused", &self.block_count_inused)
86            .field("inode_count", &self.inode_count)
87            .field("inode_count_used", &self.inode_count_inused)
88            .field("has_journal", &(self.has_journal != 0))
89            .finish()
90    }
91}
92
93impl MetaData for SuperBlock {
94    const P: Private = Private { _p: () };
95    fn load(
96        _ffs: &FastFileSystemInner,
97        _lba: LogicalBlockAddress,
98    ) -> Result<super::access_control::BlockPointsTo<Self>, KernelError> {
99        unreachable!()
100    }
101}
102const_assert!(core::mem::size_of::<SuperBlock>() == 4096);
103
104/// Represents the on-disk block allocation bitmap for the file system.
105///
106/// Each bit in the [`BlockBitmap`] corresponds to a single block on disk.
107/// A bit value of `1` indicates that the block is in use, while `0`
108/// means the block is free and available for allocation.
109#[repr(C, packed)]
110pub struct BlockBitmap {
111    bits: [u64; 4096 / 8],
112}
113
114impl Default for BlockBitmap {
115    fn default() -> Self {
116        BlockBitmap {
117            bits: [0; 4096 / 8],
118        }
119    }
120}
121
122impl BlockBitmap {
123    /// Checks whether a block at the given position is allocated.
124    ///
125    /// # Parameters
126    /// - `pos`: The index of the block to check.
127    ///
128    /// # Returns
129    /// - `true` if the block is currently marked as allocated (bit is 1).
130    /// - `false` if the block is free (bit is 0).
131    ///
132    /// This method is used to determine the allocation status of a block
133    /// in the file system's block bitmap.
134    pub fn is_allocated(&self, pos: usize) -> bool {
135        let (pos, off) = (pos / 64, pos % 64);
136        self.bits[pos] & (1 << off) != 0
137    }
138
139    /// Attempts to allocate a block at the given position.
140    ///
141    /// # Parameters
142    /// - `pos`: The index of the block to allocate.
143    ///
144    /// # Returns
145    /// - `true` if the block was previously free and is now marked as
146    ///   allocated.
147    /// - `false` if the block was already allocated (no change).
148    ///
149    /// This method is used during block allocation to claim a free block.
150    /// If the block is already allocated, it fails without modifying the
151    /// bitmap.
152    pub fn try_allocate(&mut self, pos: usize) -> bool {
153        let (pos, off) = (pos / 64, pos % 64);
154        if self.bits[pos] & (1 << off) == 0 {
155            self.bits[pos] |= 1 << off;
156            true
157        } else {
158            false
159        }
160    }
161
162    /// deallocate a block at the given position.
163    ///
164    /// # Parameters
165    /// - `pos`: The index of the block to deallocate.
166    ///
167    /// # Returns
168    /// - `true` if the block was previously free and is now marked as
169    ///   allocated.
170    /// - `false` if the block was already allocated (no change).
171    ///
172    /// This method is used during block allocation to claim a free block.
173    /// If the block is already allocated, it fails without modifying the
174    /// bitmap.
175    pub fn deallocate(&mut self, pos: usize) -> bool {
176        let (pos, off) = (pos / 64, pos % 64);
177        if self.bits[pos] & (1 << off) != 0 {
178            self.bits[pos] &= !(1 << off);
179            true
180        } else {
181            false
182        }
183    }
184}
185
186impl MetaData for BlockBitmap {
187    const P: Private = Private { _p: () };
188}
189const_assert!(core::mem::size_of::<BlockBitmap>() == 4096);
190
191/// Represents the on-disk inode allocation bitmap for the file system.
192///
193/// Each bit in the [`InodeBitmap`] corresponds to a single inode on disk.
194/// A bit value of `1` indicates that the inode is in use, while `0`
195/// means the inode is free and available for allocation.
196#[repr(C, packed)]
197pub struct InodeBitmap {
198    bits: [u64; 4096 / 8],
199}
200
201impl Default for InodeBitmap {
202    fn default() -> Self {
203        InodeBitmap {
204            bits: [0; 4096 / 8],
205        }
206    }
207}
208
209impl InodeBitmap {
210    /// Checks whether a inode at the given position is allocated.
211    ///
212    /// # Parameters
213    /// - `pos`: The index of the inode to check.
214    ///
215    /// # Returns
216    /// - `true` if the inode is currently marked as allocated (bit is 1).
217    /// - `false` if the inode is free (bit is 0).
218    ///
219    /// This method is used to determine the allocation status of a inode
220    /// in the file system's inode bitmap.
221    pub fn is_allocated(&self, pos: usize) -> bool {
222        let (pos, off) = (pos / 64, pos % 64);
223        self.bits[pos] & (1 << off) != 0
224    }
225
226    /// Attempts to allocate a inode at the given position.
227    ///
228    /// # Parameters
229    /// - `pos`: The index of the inode to allocate.
230    ///
231    /// # Returns
232    /// - `true` if the inode was previously free and is now marked as
233    ///   allocated.
234    /// - `false` if the inode was already allocated (no change).
235    ///
236    /// This method is used during inode allocation to claim a free inode.
237    /// If the inode is already allocated, it fails without modifying the
238    /// bitmap.
239    pub fn try_allocate(&mut self, pos: usize) -> bool {
240        let (pos, off) = (pos / 64, pos % 64);
241        if self.bits[pos] & (1 << off) == 0 {
242            self.bits[pos] |= 1 << off;
243            true
244        } else {
245            false
246        }
247    }
248
249    pub fn deallocate(&mut self, pos: usize) -> bool {
250        let (pos, off) = (pos / 64, pos % 64);
251        if self.bits[pos] & (1 << off) != 0 {
252            self.bits[pos] &= !(1 << off);
253            true
254        } else {
255            false
256        }
257    }
258}
259
260impl MetaData for InodeBitmap {
261    const P: Private = Private { _p: () };
262}
263const_assert!(core::mem::size_of::<InodeBitmap>() == 4096);
264
265/// Represent a single inode within a inode array.
266#[derive(Debug)]
267#[repr(C, packed)]
268pub struct Inode {
269    /// File system magic: "KeOSFFSI".
270    pub magic: [u8; 8],
271    /// The unique inode number assigned to this file or directory.
272    pub ino: Option<InodeNumber>,
273    /// The type of the file (e.g., regular file, directory, symbolic link).
274    ///
275    /// Uses a `u32` to store values corresponding to [`FileType`].
276    ///
277    /// [`FileType`]: crate::ffs::types::FileType
278    pub ftype: u32,
279    /// The total size of the file in bytes.
280    pub size: u64,
281    /// The number of links alive in the file system.
282    pub link_count: u64,
283    /// Directly mapped data blocks.
284    ///
285    /// These 12 blocks store the first portions of a file's data, allowing
286    /// for efficient access to small files without requiring indirect blocks.
287    pub dblocks: [Option<LogicalBlockAddress>; 12],
288    /// An indirect block, which contains pointers to additional data
289    /// blocks.
290    ///
291    /// This extends the file size capability beyond direct blocks by storing
292    /// an array of logical block addresses in a separate block.
293    pub iblock: Option<LogicalBlockAddress>,
294    /// A doubly indirect block, which contains pointers to indirect
295    /// blocks.
296    ///
297    /// This allows for even larger file sizes by introducing an extra level
298    /// of indirection.
299    pub diblock: Option<LogicalBlockAddress>,
300    /// A padding to align to the power of two.
301    pub _pad: [u8; 112],
302}
303
304impl Default for Inode {
305    fn default() -> Self {
306        Self {
307            magic: [0; 8],
308            ino: None,
309            ftype: 0,
310            size: 0,
311            link_count: 0,
312            dblocks: [None; 12],
313            iblock: None,
314            diblock: None,
315            _pad: [0; 112],
316        }
317    }
318}
319const_assert!(core::mem::size_of::<Inode>() == 256);
320
321/// Represents a fixed-size array of inodes loaded from a block.
322///
323/// Each [`InodeArray`] holds a collection of [`Inode`] structures that are
324/// packed into a single 4KB block. The number of inodes it contains is computed
325/// based on the size of the [`Inode`] type.
326#[derive(Default)]
327#[repr(C, packed)]
328pub struct InodeArray {
329    inodes: [Inode; 4096 / core::mem::size_of::<Inode>()],
330}
331
332impl core::ops::Deref for InodeArray {
333    type Target = [Inode; 4096 / core::mem::size_of::<Inode>()];
334    fn deref(&self) -> &Self::Target {
335        &self.inodes
336    }
337}
338
339impl core::ops::DerefMut for InodeArray {
340    fn deref_mut(&mut self) -> &mut Self::Target {
341        &mut self.inodes
342    }
343}
344
345impl MetaData for InodeArray {
346    const P: Private = Private { _p: () };
347}
348const_assert!(core::mem::size_of::<InodeArray>() == 4096);
349
350/// Represents an indirect block in the filesystem.
351///
352/// An indirect block is used to extend the number of data blocks that an inode
353/// can reference. Instead of storing data directly, it contains a list of
354/// logical block addresses (LBAs), each pointing to a separate data block on
355/// disk.
356///
357/// This structure allows files to grow beyond the direct block limit imposed by
358/// the inode structure.
359///
360/// # Usage
361/// Typically used as part of indirect, or double-indirectblock addressing
362/// schemes to support large file sizes.
363#[repr(C)]
364pub struct IndirectBlock {
365    lbas: [Option<LogicalBlockAddress>; 512],
366}
367
368impl Default for IndirectBlock {
369    fn default() -> Self {
370        Self { lbas: [None; 512] }
371    }
372}
373
374impl core::ops::Deref for IndirectBlock {
375    type Target = [Option<LogicalBlockAddress>; 512];
376    fn deref(&self) -> &Self::Target {
377        &self.lbas
378    }
379}
380
381impl core::ops::DerefMut for IndirectBlock {
382    fn deref_mut(&mut self) -> &mut Self::Target {
383        &mut self.lbas
384    }
385}
386
387impl MetaData for IndirectBlock {
388    const P: Private = Private { _p: () };
389}
390
391const_assert!(core::mem::size_of::<IndirectBlock>() == 4096);
392
393/// Represent a single directory entry within a directory block.
394///
395/// Each entry stores metadata for necessary to locate a file or subdirectory.
396#[repr(C)]
397#[derive(Clone, Copy, Debug)]
398pub struct DirectoryBlockEntry {
399    /// The inode associated with this directory entry.
400    /// - `Some(inode)`: a valid file or directory.
401    /// - `None`: an unused or deleted entry.
402    pub inode: Option<InodeNumber>,
403    /// The length of the file or directory name stored in `name`.
404    /// This indicates how many bytes in the `name` array are valid.
405    pub name_len: u8,
406    /// The name of the file or directory.
407    /// Only the first `name_len` bytes are meaningful.
408    pub name: [u8; 251],
409}
410
411impl Default for DirectoryBlockEntry {
412    fn default() -> Self {
413        Self {
414            inode: None,
415            name_len: 0,
416            name: [0; 251],
417        }
418    }
419}
420
421impl DirectoryBlockEntry {
422    /// Constructs a new directory entry from an inode number and name.
423    ///
424    /// Returns `None` if the name is too long to fit in the directory entry.
425    ///
426    /// # Arguments
427    /// - `ino`: The inode number associated with the entry.
428    /// - `name`: The name of the file or directory.
429    ///
430    /// # Returns
431    /// - `Some(Self)`: A valid directory entry.
432    /// - `None`: If the name is too long to fit.
433    pub fn from_ino_name(ino: InodeNumber, name: &str) -> Option<Self> {
434        let name_len = name.len();
435        if name_len <= 251 {
436            let mut out = DirectoryBlockEntry {
437                inode: Some(ino),
438                name_len: name_len as u8,
439                name: [0; 251],
440            };
441            out.name[..name_len].copy_from_slice(name.as_bytes());
442            Some(out)
443        } else {
444            None
445        }
446    }
447
448    /// Returns the name of the directory entry as a string slice.
449    ///
450    /// # Returns
451    /// - `Some(&str)`: If the name is valid UTF-8.
452    /// - `None`: If the name contains invalid UTF-8 bytes.
453    pub fn name(&self) -> Option<&str> {
454        self.inode
455            .and_then(|_| core::str::from_utf8(&self.name[..self.name_len as usize]).ok())
456    }
457}
458
459const_assert!(core::mem::size_of::<DirectoryBlockEntry>() == 256);
460
461/// Represents a block that contains multiple directory entries.
462///
463/// A directory is composed of one or more of these blocks, each
464/// containing a fixed-size array of directory entries.
465#[repr(C)]
466pub struct DirectoryBlock {
467    entries: [DirectoryBlockEntry; 4096 / core::mem::size_of::<DirectoryBlockEntry>()],
468}
469
470impl Default for DirectoryBlock {
471    fn default() -> Self {
472        Self {
473            entries: [DirectoryBlockEntry::default();
474                4096 / core::mem::size_of::<DirectoryBlockEntry>()],
475        }
476    }
477}
478
479impl core::ops::Deref for DirectoryBlock {
480    type Target = [DirectoryBlockEntry; 4096 / core::mem::size_of::<DirectoryBlockEntry>()];
481    fn deref(&self) -> &Self::Target {
482        &self.entries
483    }
484}
485
486impl core::ops::DerefMut for DirectoryBlock {
487    fn deref_mut(&mut self) -> &mut Self::Target {
488        &mut self.entries
489    }
490}
491
492impl MetaData for DirectoryBlock {
493    const P: Private = Private { _p: () };
494}
495
496const_assert!(core::mem::size_of::<DirectoryBlock>() == 4096);
497
498/// Represents the on-disk metadata for the journal superblock.
499#[repr(C, packed)]
500pub struct JournalSb {
501    /// Journal magic: "KeOSJOUR".
502    pub magic: [u8; 8],
503    /// Indicate journal has been commited.
504    pub commited: u64,
505    /// Transaction id.
506    pub tx_id: u64,
507    /// Padding to fill a full block (4096 bytes).
508    _pad: [u8; 4096 - 24],
509}
510
511impl Default for JournalSb {
512    fn default() -> Self {
513        Self {
514            magic: [0; 8],
515            commited: 0,
516            tx_id: 0,
517            _pad: [0; 4096 - 24],
518        }
519    }
520}
521
522impl JournalSb {
523    /// Loads the journal superblock from disk.
524    ///
525    /// # Arguments
526    /// - `disk`: The underlying disk interface.
527    /// - `lba`: The logical block address where the journal superblock is
528    ///   located.
529    ///
530    /// # Returns
531    /// - `Ok(Box<Self>)`: A parsed journal superblock.
532    /// - `Err(KernelError)`: If the block could not be read or parsed.
533    pub fn from_disk(disk: &Disk, lba: LogicalBlockAddress) -> Result<Box<Self>, KernelError> {
534        let mut b = Box::new(JournalSb::default());
535        {
536            let inner =
537                unsafe { core::slice::from_raw_parts_mut(&mut *b as *mut _ as *mut u8, 4096) };
538            for i in 0..8 {
539                disk.read(
540                    lba.into_sector() + i,
541                    inner[512 * i..512 * (i + 1)].as_mut_array().unwrap(),
542                )?;
543            }
544        }
545        Ok(b)
546    }
547
548    /// Writes the current journal superblock to disk.
549    ///
550    /// This updates the on-disk metadata for the journal ring buffer.
551    ///
552    /// # Arguments
553    /// - `io`: The I/O interface to the journal.
554    /// - `ffs`: Reference to the file system's metadata layout.
555    ///
556    /// # Returns
557    /// - `Ok(())`: If the write succeeds.
558    /// - `Err(KernelError)`: If the write fails.
559    pub fn writeback(&self, io: &JournalIO, ffs: &FastFileSystemInner) -> Result<(), KernelError> {
560        let lba = ffs.journal().start;
561        {
562            let inner = unsafe { core::slice::from_raw_parts(self as *const _ as *const u8, 4096) };
563            io.write_metadata_block(lba, inner.as_array().unwrap())?;
564        }
565        Ok(())
566    }
567}
568
569const_assert!(core::mem::size_of::<JournalSb>() == 4096);
570
571/// Represents a journal transaction header used to track modified blocks.
572///
573/// This structure is used during transaction commit to list all
574/// blocks that are affected by the transaction.
575pub struct JournalTxBegin {
576    /// Transaction id.
577    pub tx_id: u64,
578    /// Array of logical block addresses involved in the transaction.
579    /// - `Some(lba)`: a block to be committed.
580    /// - `None`: an unused slot.
581    pub lbas: [Option<LogicalBlockAddress>; 511],
582}
583
584impl JournalTxBegin {
585    /// Creates a new, empty journal `TxBegin` block.
586    pub fn new(tx_id: u64) -> Box<Self> {
587        Box::new(Self {
588            tx_id,
589            lbas: [None; 511],
590        })
591    }
592
593    /// Loads a journal `TxBegin` block from disk at the specified LBA.
594    ///
595    /// # Arguments
596    /// - `io`: Interface for reading blocks from disk.
597    /// - `lba`: Logical block address of the journal transaction block to load.
598    ///
599    /// # Returns
600    /// - `Ok(Box<Self>)`: The loaded journal transaction block.
601    /// - `Err(KernelError)`: If the block could not be read or parsed.
602    pub fn from_io(io: &JournalIO, lba: LogicalBlockAddress) -> Result<Box<Self>, KernelError> {
603        let mut b = Self::new(0);
604        {
605            let inner =
606                unsafe { core::slice::from_raw_parts_mut(&mut *b as *mut _ as *mut u8, 4096) };
607            io.read_journal(lba, inner.as_mut_array().unwrap())?;
608        }
609        Ok(b)
610    }
611
612    /// Converts this transaction into a 4096-byte block suitable for writing to
613    /// disk.
614    ///
615    /// This is typically used during transaction commit.
616    pub fn into_block(self: Box<Self>) -> Box<[u8; 4096]> {
617        unsafe { Box::from_raw(Box::into_raw(self) as *mut [u8; 4096]) }
618    }
619}
620
621const_assert!(core::mem::size_of::<JournalTxBegin>() == 4096);
622
623/// Represents a journal transaction is ended.
624///
625/// This structure is used during transaction commit to mark the end of
626/// transaction.
627pub struct JournalTxEnd {
628    /// Transaction id.
629    pub tx_id: u64,
630    _pad: [u8; 4088],
631}
632
633impl JournalTxEnd {
634    /// Creates a new, empty journal `TxEnd` block.
635    pub fn new(tx_id: u64) -> Box<Self> {
636        Box::new(Self {
637            tx_id,
638            _pad: [0; 4088],
639        })
640    }
641
642    /// Loads a journal `TxEnd` block from disk at the specified LBA.
643    ///
644    /// # Arguments
645    /// - `io`: Interface for reading blocks from disk.
646    /// - `lba`: Logical block address of the journal transaction block to load.
647    ///
648    /// # Returns
649    /// - `Ok(Box<Self>)`: The loaded journal transaction block.
650    /// - `Err(KernelError)`: If the block could not be read or parsed.
651    pub fn from_io(io: &JournalIO, lba: LogicalBlockAddress) -> Result<Box<Self>, KernelError> {
652        let mut b = Self::new(0);
653        {
654            let inner =
655                unsafe { core::slice::from_raw_parts_mut(&mut *b as *mut _ as *mut u8, 4096) };
656            io.read_journal(lba, inner.as_mut_array().unwrap())?;
657        }
658        Ok(b)
659    }
660
661    /// Converts this transaction into a 4096-byte block suitable for writing to
662    /// disk.
663    ///
664    /// This is typically used during transaction commit.
665    pub fn into_block(self: Box<Self>) -> Box<[u8; 4096]> {
666        unsafe { Box::from_raw(Box::into_raw(self) as *mut [u8; 4096]) }
667    }
668}
669
670const_assert!(core::mem::size_of::<JournalTxEnd>() == 4096);