keos_project5/ffs/
access_control.rs

1//! Metadata access and synchronization primitives for the filesystem.
2//!
3//! This module defines core abstractions for accessing and modifying
4//! filesystem metadata such as inodes and other block-based structures.
5//! These types ensure safe, concurrent access to on-disk metadata and provide
6//! mechanisms for **enforcing** transactional updates with journaling support.
7//!
8//! It forms the backbone of safe, transactional filesystem operations.
9//! See [`disk_layout`] module for its usage.
10//!
11//! [`disk_layout`]: super::disk_layout
12use crate::ffs::{
13    FastFileSystemInner, LogicalBlockAddress, RunningTransaction,
14    disk_layout::{InodeArray, Private, SuperBlock},
15    inode::Inode,
16};
17use alloc::{
18    boxed::Box,
19    sync::{Arc, Weak},
20};
21use keos::{
22    KernelError,
23    fs::{Disk, Sector},
24    sync::{RwLock, RwLockReadGuard, RwLockWriteGuard, SpinLock, SpinLockGuard},
25};
26
27/// Trait for file system metadata types that can be loaded from disk.
28///
29/// Types implementing [`MetaData`] represent metadata structures in the file
30/// system (e.g., superblock, inode bitmap, block bitmap, inode array) that are
31/// stored on disk in a fixed layout. This trait provides a standard interface
32/// for reading these structures from their on-disk representation.
33///
34/// The trait requires implementors to be `Default` and support loading from a
35/// logical block address (LBA) using the file system’s internal
36/// I/O abstraction.
37pub trait MetaData: Sized + Default {
38    #[doc(hidden)]
39    const P: Private;
40
41    /// Loads a metadata structure from disk at the specified logical block
42    /// address.
43    ///
44    /// # Parameters
45    /// - `ffs`: Reference to the internal file system state.
46    /// - `lba`: Logical block address where the metadata is stored.
47    ///
48    /// # Returns
49    /// - `Ok(metadata)`: An `Arc<SpinLock<T>>` wrapping the loaded metadata.
50    /// - `Err(KernelError)`: If the metadata could not be read.
51    fn load(
52        ffs: &FastFileSystemInner,
53        lba: LogicalBlockAddress,
54    ) -> Result<BlockPointsTo<Self>, KernelError> {
55        Ok(BlockPointsTo {
56            lba,
57            b: ffs.read_meta(lba)?,
58            _m: core::marker::PhantomData,
59        })
60    }
61}
62
63impl SuperBlock {
64    /// Loads the superblock structure from disk.
65    ///
66    /// This function reads the first 8 sectors (4096 bytes) from the disk.
67    /// It is the first step when mounting a file system, as the superblock
68    /// contains metadata such as layout information, and journals.
69    ///
70    /// ### Parameters
71    /// - `disk`: A reference to the block device implementing the [`Disk`]
72    ///   trait.
73    ///
74    /// ### Returns
75    /// - `Ok(Box<SuperBlock>)`: If the superblock is successfully read.
76    /// - `Err(KernelError)`: If any sector read fails.
77    pub fn from_disk(disk: &Disk) -> Result<BlockPointsTo<Self>, KernelError> {
78        let b = Arc::new(SpinLock::new([0; 4096]));
79        {
80            let mut guard = b.lock();
81            for i in 0..8 {
82                disk.read(
83                    Sector(i),
84                    guard[512 * i..512 * (i + 1)].as_mut_array().unwrap(),
85                )?;
86            }
87            guard.unlock();
88        }
89        Ok(BlockPointsTo {
90            lba: LogicalBlockAddress::new(1).unwrap(),
91            b,
92            _m: core::marker::PhantomData,
93        })
94    }
95}
96
97/// A wrapper around a metadata block that resides at a specific logical block
98/// address (LBA).
99///
100/// `BlockPointsTo` provides safe, synchronized access to a disk-backed
101/// 4096-byte block, and associates the block with a specific metadata type `M`
102/// implementing the [`MetaData`] trait. Internally, it uses a [`SpinLock`] to
103/// protect concurrent access and associate with its metadata type without
104/// affecting layout.
105///
106/// This abstraction allows safe and typed access to the underlying bytes as
107/// metadata structures, while supporting transactional read/write operations.
108///
109/// # Type Parameters
110/// - `M`: The type of metadata this block contains. Must implement
111///   [`MetaData`].
112#[derive(Clone)]
113pub struct BlockPointsTo<M: MetaData> {
114    /// Logical block address (LBA) where this block resides on disk.
115    lba: LogicalBlockAddress,
116
117    /// The in-memory contents of the block, protected by a spinlock for
118    /// concurrency.
119    b: Arc<SpinLock<[u8; 4096]>>,
120
121    /// Marker to associate this block with metadata type `M`.
122    _m: core::marker::PhantomData<M>,
123}
124
125impl<M: MetaData> BlockPointsTo<M> {
126    /// Acquires a read-only guard to the underlying block contents.
127    ///
128    /// # Returns
129    /// - [`BlockPointsToReadGuard`]: A read guard providing immutable access to
130    ///   the block's contents, typed as metadata `M`.
131    ///
132    /// This method locks the internal spinlock and returns a guard for safe,
133    /// read-only access to the raw bytes of the metadata block.
134    pub fn read(&self) -> BlockPointsToReadGuard<'_, M> {
135        BlockPointsToReadGuard {
136            b: Some(self.b.lock()),
137            _m: core::marker::PhantomData,
138        }
139    }
140
141    /// Acquires a write guard to the block, registering it with the given
142    /// transaction.
143    ///
144    /// # Arguments
145    /// - `tx`: The currently running transaction used to log changes for
146    ///   durability and crash recovery.
147    ///
148    /// # Returns
149    /// - [`BlockPointsToWriteGuard`]: A write guard that allows mutation of the
150    ///   block’s contents and records the modification in the transaction.
151    ///
152    /// This method is intended for use in metadata updates. The block is locked
153    /// for exclusive access, and the transaction ensures write-ahead
154    /// logging or journaling semantics for filesystem consistency.
155    pub fn write<'a, 'b, 'c>(
156        &'a self,
157        tx: &'b RunningTransaction<'c>,
158    ) -> BlockPointsToWriteGuard<'a, 'b, 'c, M> {
159        BlockPointsToWriteGuard {
160            lba: self.lba,
161            b: Some(self.b.lock()),
162            tx,
163            _m: core::marker::PhantomData,
164        }
165    }
166
167    /// Reload in-memory structure to synchronize with on-disk structure
168    pub fn reload(&self, disk: &Disk) -> Result<(), KernelError> {
169        let mut guard = self.b.lock();
170        for i in 0..8 {
171            disk.read(
172                self.lba.into_sector() + i,
173                guard[512 * i..512 * (i + 1)].as_mut_array().unwrap(),
174            )?;
175        }
176        guard.unlock();
177        Ok(())
178    }
179}
180
181/// A read-only guard that provides typed access to a metadata block.
182///
183/// `BlockPointsToReadGuard` is returned by [`BlockPointsTo::read`] and allows
184/// immutable access to the contents of a 4096-byte disk block as a value of
185/// type `M`, which implements the [`MetaData`] trait.
186///
187/// Internally, it wraps a locked reference to the block’s byte array using a
188/// [`SpinLockGuard`] and casts it to the metadata type using `unsafe` pointer
189/// casting. The guard ensures that the block cannot be modified while borrowed
190/// immutably.
191///
192/// # Use Case
193/// Use this when you want to inspect metadata structures (like an inode table,
194/// superblock, or bitmap) without modifying them.
195pub struct BlockPointsToReadGuard<'a, M: MetaData> {
196    /// Spinlock guard for the raw block data.
197    b: Option<SpinLockGuard<'a, [u8; 4096]>>,
198
199    /// Marker to associate the block with its metadata type.
200    _m: core::marker::PhantomData<M>,
201}
202
203impl<M: MetaData> core::ops::Deref for BlockPointsToReadGuard<'_, M> {
204    type Target = M;
205
206    fn deref(&self) -> &Self::Target {
207        unsafe { &*(self.b.as_ref().unwrap().as_ptr() as *const M) }
208    }
209}
210
211impl<M: MetaData> Drop for BlockPointsToReadGuard<'_, M> {
212    /// Ensures the spinlock is released when the guard goes out of scope.
213    fn drop(&mut self) {
214        self.b.take().unwrap().unlock();
215    }
216}
217
218/// A mutable guard for modifying metadata loaded from a block on disk,
219/// paired with a transaction context for journaling or rollback.
220///
221/// This guard is returned by [`BlockPointsTo::write`] and provides exclusive,
222/// mutable access to in-memory metadata of type `M`, along with a reference to
223/// the current transaction. Any modifications made through this guard must be
224/// explicitly committed via [`submit`] to ensure they are persisted and
225/// journaled properly.
226///
227/// # Use Case
228/// Use this when modifying metadata that needs to be tracked in a
229/// [`RunningTransaction`], such as updating inode entries, marking blocks
230/// as allocated, or changing filesystem state.
231///
232/// # Safety and Enforcement
233/// The implementation panics if this guard is dropped without calling
234/// [`submit`], enforcing that all metadata updates must go through the
235/// transaction system to maintain consistency.
236///
237/// [`submit`]: Self::submit
238pub struct BlockPointsToWriteGuard<'a, 'b, 'c, M: MetaData> {
239    /// Logical block address of the block being modified.
240    lba: LogicalBlockAddress,
241
242    /// Spinlock guard protecting the block contents.
243    b: Option<SpinLockGuard<'a, [u8; 4096]>>,
244
245    /// Mutable reference to the ongoing transaction.
246    tx: &'b RunningTransaction<'c>,
247
248    /// Marker to associate the block with its metadata type.
249    _m: core::marker::PhantomData<M>,
250}
251
252impl<M: MetaData> BlockPointsToWriteGuard<'_, '_, '_, M> {
253    /// Submits the modified metadata block to the [`RunningTransaction`].
254    ///
255    /// This function marks the block as dirty and ensures it will be written
256    /// to disk as part of the journal. After calling `submit`, the guard is
257    /// consumed.
258    pub fn submit(mut self) {
259        self.tx.write_meta(
260            self.lba,
261            Box::new(**self.b.as_ref().unwrap()),
262            core::any::type_name::<M>(),
263        );
264        let _ = unsafe { core::ptr::read(&self.b.as_ref().unwrap()) };
265        self.b.take().unwrap().unlock();
266        let _ = core::mem::ManuallyDrop::new(self);
267    }
268
269    /// Explictly drops the modified metadata block to the
270    /// [`RunningTransaction`].
271    ///
272    /// This function marks the block as intact and ensures it will never be
273    /// written to disk as part of the journal. After calling `forget`, the
274    /// guard is consumed.
275    pub fn forget(self) {
276        let _ = unsafe { core::ptr::read(&self.b) };
277        let _ = core::mem::ManuallyDrop::new(self);
278    }
279}
280
281impl<M: MetaData> core::ops::Deref for BlockPointsToWriteGuard<'_, '_, '_, M> {
282    type Target = M;
283    fn deref(&self) -> &Self::Target {
284        unsafe { &*(self.b.as_ref().unwrap().as_ptr() as *const M) }
285    }
286}
287
288impl<M: MetaData> core::ops::DerefMut for BlockPointsToWriteGuard<'_, '_, '_, M> {
289    fn deref_mut(&mut self) -> &mut Self::Target {
290        unsafe { &mut *(self.b.as_mut().unwrap().as_mut_ptr() as *mut M) }
291    }
292}
293impl<M: MetaData> Drop for BlockPointsToWriteGuard<'_, '_, '_, M> {
294    /// Panics if the guard is dropped without calling `submit`.
295    ///
296    /// This ensures that all metadata changes are either explicitly recorded
297    /// in a transaction or clearly rejected, helping prevent silent data loss.
298    fn drop(&mut self) {
299        panic!(
300            "You are not calling `submit()` on `BlockPointsToWriteGuard`.
301
302** All metadata modifications must be explicitly submitted to the transaction.
303** To apply changes safely, call `submit()` before the guard is dropped."
304        );
305    }
306}
307
308/// A reference-counted, thread-safe wrapper around an in-memory [`Inode`],
309/// enabling synchronized read and transactional write access.
310///
311/// This type provides read access through a shared guard and write access via
312/// a transactional context, ensuring consistency between the in-memory and
313/// on-disk representations of the inode.
314#[derive(Clone)]
315pub struct TrackedInode(Arc<RwLock<Inode>>, Weak<FastFileSystemInner>);
316
317impl Drop for TrackedInode {
318    fn drop(&mut self) {
319        if let Some(ffs) = self.1.upgrade() {
320            let ino = self.read().ino;
321            if let Some(inode) = ffs.remove_inode(ino) {
322                // There is no way to access this file.
323                let mem_layout = inode.write();
324                if mem_layout.link_count == 0 {
325                    let tx = ffs.open_transaction("File::remove");
326                    if let Some((lba, index)) = tx.ffs.get_inode_array_lba_index(mem_layout.ino)
327                        && let Ok(inode_array) = InodeArray::load(tx.ffs, lba)
328                    {
329                        let disk_layout = inode_array.write(&tx);
330                        let mut guard = TrackedInodeWriteGuard {
331                            mem_layout,
332                            disk_layout,
333                            index,
334                        };
335
336                        Inode::zeroify(&mut guard, &tx, &ffs);
337
338                        let mut sb = ffs.sb.write(&tx);
339                        sb.inode_count_inused -= 1;
340                        sb.submit();
341
342                        let ino = guard.ino;
343
344                        guard.disk_layout[index].ino = None;
345                        guard.do_submit();
346
347                        let bitmap_no = (ino.into_u32() - 1) as usize;
348                        let bitmap_lba = ffs.inode_bitmap().start + (bitmap_no / 0x8000);
349                        let bitmap =
350                            crate::ffs::disk_layout::InodeBitmap::load(&ffs, bitmap_lba).unwrap();
351                        let mut guard = bitmap.write(&tx);
352
353                        assert!(guard.deallocate(bitmap_no % 0x8000));
354                        guard.submit();
355
356                        let _ = tx.commit();
357                    }
358                }
359            }
360        }
361    }
362}
363
364impl TrackedInode {
365    /// Create a new [`TrackedInode`] reference.
366    pub fn new(inner: Arc<RwLock<Inode>>, ffs: Weak<FastFileSystemInner>) -> Self {
367        Self(inner, ffs)
368    }
369
370    /// Acquires a shared read lock on the in-memory inode.
371    ///
372    /// # Returns
373    /// A [`TrackedInodeReadGuard`] which provides read-only access to the
374    /// current state of the inode.
375    ///
376    /// # Use Case
377    /// Use this when you need to inspect an inode without modifying it.
378    #[inline]
379    pub fn read(&self) -> TrackedInodeReadGuard<'_> {
380        TrackedInodeReadGuard(self.0.read())
381    }
382
383    /// Acquires an exclusive write lock on both the in-memory inode and the
384    /// corresponding on-disk inode for transactional modification.
385    ///
386    /// You **must** submit the changes by calling the [`submit`] method.
387    ///
388    /// # Arguments
389    /// - `tx`: A reference to the current [`RunningTransaction`] used to track
390    ///   and commit filesystem changes.
391    /// - `f`: A closure that performs modifications using the provided
392    ///   [`TrackedInodeWriteGuard`], which contains both in-memory and on-disk
393    ///   representations of the inode.
394    ///
395    /// # Returns
396    /// - `Ok(R)`: If the closure `f` returns successfully.
397    /// - `Err(KernelError)`: If an error occurs while resolving the inode
398    ///   layout or during execution of `f`.
399    ///
400    /// # Use Case
401    /// Use this when updating inode state (e.g., growing a file, updating
402    /// metadata) and ensuring consistency between memory and disk through
403    /// the transaction.
404    ///
405    /// # Example
406    /// ```rust
407    /// tracked_inode.write_with(tx, |mut guard| {
408    ///     guard.mem_layout.size += 1;
409    ///     guard.disk_layout[guard.index].size = guard.mem_layout.size;
410    ///     guard.submit();
411    ///     Ok(())
412    /// })?;
413    /// ```
414    ///
415    /// [`submit`]: TrackedInodeWriteGuard::submit
416    #[inline]
417    pub fn write_with<R>(
418        &self,
419        tx: &RunningTransaction,
420        f: impl FnOnce(TrackedInodeWriteGuard) -> Result<R, KernelError>,
421    ) -> Result<R, KernelError> {
422        let mem_layout = self.0.write();
423
424        let (lba, index) = tx
425            .ffs
426            .get_inode_array_lba_index(mem_layout.ino)
427            .ok_or(KernelError::FilesystemCorrupted("Invalid Inode number."))?;
428        let inode_array = InodeArray::load(tx.ffs, lba)?;
429        let disk_layout = inode_array.write(tx);
430        f(TrackedInodeWriteGuard {
431            mem_layout,
432            disk_layout,
433            index,
434        })
435    }
436}
437
438/// A guard that provides read-only access to the in-memory [`Inode`] structure.
439///
440/// This guard is acquired via [`TrackedInode::read`] and ensures shared
441/// access to the inode, preventing concurrent writes.
442///
443/// # Use Case
444/// Use this guard when inspecting the state of an inode without making any
445/// modifications.
446///
447/// # Example
448/// ```rust
449/// let guard = tracked_inode.read();
450/// println!("inode size: {}", guard.size);
451/// ```
452pub struct TrackedInodeReadGuard<'a>(RwLockReadGuard<'a, Inode>);
453
454impl core::ops::Deref for TrackedInodeReadGuard<'_> {
455    type Target = Inode;
456    fn deref(&self) -> &Self::Target {
457        &self.0
458    }
459}
460
461/// A guard that provides mutable access to the in-memory within a transactional
462/// context.
463///
464/// This guard is constructed internally by [`TrackedInode::write_with`]
465/// and ensures that any modifications are performed consistently across both
466/// memory and disk. The changes must be explicitly committed using the
467/// associated transaction.
468///
469/// # Panics
470/// If dropped without submitting the change to the transaction, this guard will
471/// panic to ensure no silent inconsistency between in-memory and on-disk state.
472///
473/// # Example
474/// ```rust
475/// tracked_inode.write_with(tx, |mut guard| {
476///     guard.mem_layout.size += 1;
477///     guard.disk_layout[guard.index].size = guard.mem_layout.size;
478///     guard.submit();
479///     Ok(())
480/// })?;
481/// ```
482pub struct TrackedInodeWriteGuard<'a, 'b, 'c, 'd> {
483    /// Write guard protecting the in-memory inode.
484    mem_layout: RwLockWriteGuard<'a, Inode>,
485
486    /// Write guard protecting the on-disk inode array.
487    disk_layout: BlockPointsToWriteGuard<'b, 'c, 'd, InodeArray>,
488
489    /// Index to the inode.
490    index: usize,
491}
492
493impl core::ops::Deref for TrackedInodeWriteGuard<'_, '_, '_, '_> {
494    type Target = Inode;
495    fn deref(&self) -> &Self::Target {
496        &self.mem_layout
497    }
498}
499
500impl core::ops::DerefMut for TrackedInodeWriteGuard<'_, '_, '_, '_> {
501    fn deref_mut(&mut self) -> &mut Self::Target {
502        &mut self.mem_layout
503    }
504}
505
506impl TrackedInodeWriteGuard<'_, '_, '_, '_> {
507    /// Submits the modified metadata block to the [`RunningTransaction`].
508    ///
509    /// This function marks the block as dirty and ensures it will be written
510    /// to disk as part of the journal. After calling `submit`, the guard is
511    /// consumed.
512    pub fn submit(mut self) {
513        self.disk_layout[self.index] = self.mem_layout.into_disk_format();
514        self.do_submit();
515    }
516
517    fn do_submit(self) {
518        let _mem = unsafe { core::ptr::read(&self.mem_layout) };
519        let disk = unsafe { core::ptr::read(&self.disk_layout) };
520
521        disk.submit();
522        core::mem::forget(self);
523    }
524}
525
526impl Drop for TrackedInodeWriteGuard<'_, '_, '_, '_> {
527    /// Panics if the guard is dropped without calling `submit`.
528    ///
529    /// This ensures that all metadata changes are either explicitly recorded
530    /// in a transaction or clearly rejected, helping prevent silent data loss.
531    fn drop(&mut self) {
532        panic!(
533            "You are not calling `submit()` on `TrackedInodeWriteGuard`.
534
535** All inode modifications must be explicitly submitted to the transaction.
536** To apply changes safely, call `submit()` before the guard is dropped."
537        );
538    }
539}