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}