From 126d5b2e55dca93e024f5654fed6fae37a012369 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 17 Aug 2026 19:56:24 +0200 Subject: Terminal-app design (ADR 0005) + disk-write controller - ADR 0005: persistent home terminal (full-screen, chrome-less, bottom-pinned), F1 terminal windows, per-instance vt/tty/shell, queue-based input for all terminals, and the disk controller - Glossary: add the Terminal term; update Task/App for the new model - Add a re-entrant disk lock (DiskGuard over the preemption-depth counter) and wrap all fs/fat mutating operations per-call --- rsnix/src/fat.rs | 41 +++++++++++++++++++++++++++++++++-------- rsnix/src/fs.rs | 13 +++++++++++-- 2 files changed, 44 insertions(+), 10 deletions(-) (limited to 'rsnix') diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs index b7e0da1..03b9550 100644 --- a/rsnix/src/fat.rs +++ b/rsnix/src/fat.rs @@ -62,6 +62,26 @@ fn active_vol() -> &'static mut usize { unsafe { &mut *core::ptr::addr_of_mut!(ACTIVE_VOL) } } +/// Re-entrant disk lock: serializes filesystem/disk access across tasks. +/// Multiple terminals may touch the disk concurrently, so every fat/fs +/// operation that touches the shared volume state, the FAT cache, or the +/// VFS must hold this. Re-entrant (the preemption-depth counter), so an +/// `fs` op can call a `fat` op without deadlocking. +pub(crate) struct DiskGuard; + +impl DiskGuard { + pub(crate) fn acquire() -> Self { + crate::sched::lock(); + DiskGuard + } +} + +impl Drop for DiskGuard { + fn drop(&mut self) { + crate::sched::unlock(); + } +} + pub fn volume_count() -> usize { vols().len() } @@ -89,9 +109,10 @@ pub fn crash_vol() -> Option { } for vol in 0..n { if let Some(label) = volume_label(vol) - && label.eq_ignore_ascii_case("CRASHLOG") { - return Some(vol); - } + && label.eq_ignore_ascii_case("CRASHLOG") + { + return Some(vol); + } } Some(0) } @@ -102,6 +123,7 @@ pub fn crash_vol() -> Option { /// removed, so VFS disk handles keep their indices. Returns /// (mounted, unmounted). pub fn rescan() -> (usize, usize) { + let _lock = DiskGuard::acquire(); let disks = crate::ahci::disk_count(); let mut mounted = 0usize; let mut unmounted = 0usize; @@ -256,11 +278,7 @@ fn is_eoc(c: u32) -> bool { fn next_cluster(c: u32) -> Result { let n = read_fat_entry(c); - if is_eoc(n) { - Err(()) - } else { - Ok(n) - } + if is_eoc(n) { Err(()) } else { Ok(n) } } /// Allocate a fresh cluster: mark it EOC in the FAT and zero its data. @@ -532,6 +550,7 @@ fn scan_dir(dir_cluster: u32) -> Result, ()> { /// Public listing: no `.` / `..`. pub fn list_dir(vol: usize, dir_cluster: u32) -> Result, ()> { + let _lock = DiskGuard::acquire(); select_vol(vol)?; Ok(scan_dir(dir_cluster)? .into_iter() @@ -558,6 +577,7 @@ fn find_item(parent_dir_cluster: u32, name: &str) -> Result { /// Find an entry by name (case-insensitive). pub fn find(vol: usize, parent_dir_cluster: u32, name: &str) -> Result { + let _lock = DiskGuard::acquire(); select_vol(vol)?; find_item(parent_dir_cluster, name).map(|i| Entry { name: i.name, @@ -576,6 +596,7 @@ pub fn read_file( buf: &mut [u8], pos: u64, ) -> Result { + let _lock = DiskGuard::acquire(); select_vol(vol)?; if buf.is_empty() { return Ok(0); @@ -622,6 +643,7 @@ pub fn write_file( buf: &[u8], pos: u64, ) -> Result { + let _lock = DiskGuard::acquire(); select_vol(vol)?; if buf.is_empty() { return Ok(0); @@ -685,6 +707,7 @@ pub fn write_file( } pub fn set_len(vol: usize, parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { + let _lock = DiskGuard::acquire(); select_vol(vol)?; let it = find_item(parent_dir_cluster, name)?; if it.attr & 0x10 != 0 { @@ -1009,6 +1032,7 @@ fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result< } pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { + let _lock = DiskGuard::acquire(); select_vol(vol)?; if find(vol, parent_dir_cluster, name).is_ok() { return Err(()); @@ -1048,6 +1072,7 @@ pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> } pub fn remove(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<(), ()> { + let _lock = DiskGuard::acquire(); select_vol(vol)?; let it = find_item(parent_dir_cluster, name)?; if it.attr & 0x10 != 0 { diff --git a/rsnix/src/fs.rs b/rsnix/src/fs.rs index 24d8fcd..3585c00 100644 --- a/rsnix/src/fs.rs +++ b/rsnix/src/fs.rs @@ -203,6 +203,7 @@ pub fn init() -> (usize, usize) { /// its backing info), and tombstone nodes whose volume was unmounted. /// Returns (added, removed) node ops. pub fn rescan_disks() -> (usize, usize) { + let _lock = crate::fat::DiskGuard::acquire(); let mut added = 0usize; let mut removed = 0usize; let disk_dir = match lookup(0, "disk") { @@ -428,6 +429,7 @@ pub fn read(h: usize, buf: &mut [u8], pos: u64) -> Result { } pub fn write(h: usize, buf: &[u8], pos: u64) -> Result { + let _lock = crate::fat::DiskGuard::acquire(); match &mut nodes()[h].data { NodeData::Regular(v) => { let p = pos as usize; @@ -533,7 +535,10 @@ pub fn write_crash_log(lines: &[&str]) { let mut tb = [0u8; 48]; let tn = { let mut w = crate::strbuf::FmtBuf::new(&mut tb); - let _ = core::fmt::write(&mut w, core::format_args!("crashlog: vol {} ({})\r\n", vol, label)); + let _ = core::fmt::write( + &mut w, + core::format_args!("crashlog: vol {} ({})\r\n", vol, label), + ); w.len() }; crate::serial::write_raw(core::str::from_utf8(&tb[..tn]).unwrap_or("?")); @@ -555,7 +560,9 @@ pub fn write_crash_log(lines: &[&str]) { } /// Truncate/resize a regular file. -pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h].data { +pub fn set_len(h: usize, len: u64) -> Result<(), ()> { + let _lock = crate::fat::DiskGuard::acquire(); + match &mut nodes()[h].data { NodeData::Regular(v) => { v.resize(len as usize, 0); Ok(()) @@ -567,6 +574,7 @@ pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h]. /// Create a Regular or Dir child. Fails if the name exists or is invalid. pub fn create(parent: usize, name: &str, kind: FileType) -> Result { + let _lock = crate::fat::DiskGuard::acquire(); if name.is_empty() || name.contains('/') || kind == FileType::Char { return Err(()); } @@ -608,6 +616,7 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result { /// Remove a regular file or an empty directory (tombstone). pub fn remove(h: usize) -> Result<(), ()> { + let _lock = crate::fat::DiskGuard::acquire(); // Disk nodes: delete through the FAT volume (dirs must be empty). if let NodeData::Disk(i) = &nodes()[h].data { if i.name.is_empty() { -- cgit v1.2.3