diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-17 19:56:24 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-17 19:56:24 +0200 |
| commit | 126d5b2e55dca93e024f5654fed6fae37a012369 (patch) | |
| tree | a74ad41c98c121a7bc37299b8a4d1d336e0cd2c5 /rsnix | |
| parent | wm: keep windows in place when the console scrolls beneath them (diff) | |
| download | rs-dos-126d5b2e55dca93e024f5654fed6fae37a012369.tar.gz rs-dos-126d5b2e55dca93e024f5654fed6fae37a012369.zip | |
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
Diffstat (limited to 'rsnix')
| -rw-r--r-- | rsnix/src/fat.rs | 41 | ||||
| -rw-r--r-- | rsnix/src/fs.rs | 13 |
2 files changed, 44 insertions, 10 deletions
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 { | |||
| 62 | unsafe { &mut *core::ptr::addr_of_mut!(ACTIVE_VOL) } | 62 | unsafe { &mut *core::ptr::addr_of_mut!(ACTIVE_VOL) } |
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | /// Re-entrant disk lock: serializes filesystem/disk access across tasks. | ||
| 66 | /// Multiple terminals may touch the disk concurrently, so every fat/fs | ||
| 67 | /// operation that touches the shared volume state, the FAT cache, or the | ||
| 68 | /// VFS must hold this. Re-entrant (the preemption-depth counter), so an | ||
| 69 | /// `fs` op can call a `fat` op without deadlocking. | ||
| 70 | pub(crate) struct DiskGuard; | ||
| 71 | |||
| 72 | impl DiskGuard { | ||
| 73 | pub(crate) fn acquire() -> Self { | ||
| 74 | crate::sched::lock(); | ||
| 75 | DiskGuard | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | impl Drop for DiskGuard { | ||
| 80 | fn drop(&mut self) { | ||
| 81 | crate::sched::unlock(); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 65 | pub fn volume_count() -> usize { | 85 | pub fn volume_count() -> usize { |
| 66 | vols().len() | 86 | vols().len() |
| 67 | } | 87 | } |
| @@ -89,9 +109,10 @@ pub fn crash_vol() -> Option<usize> { | |||
| 89 | } | 109 | } |
| 90 | for vol in 0..n { | 110 | for vol in 0..n { |
| 91 | if let Some(label) = volume_label(vol) | 111 | if let Some(label) = volume_label(vol) |
| 92 | && label.eq_ignore_ascii_case("CRASHLOG") { | 112 | && label.eq_ignore_ascii_case("CRASHLOG") |
| 93 | return Some(vol); | 113 | { |
| 94 | } | 114 | return Some(vol); |
| 115 | } | ||
| 95 | } | 116 | } |
| 96 | Some(0) | 117 | Some(0) |
| 97 | } | 118 | } |
| @@ -102,6 +123,7 @@ pub fn crash_vol() -> Option<usize> { | |||
| 102 | /// removed, so VFS disk handles keep their indices. Returns | 123 | /// removed, so VFS disk handles keep their indices. Returns |
| 103 | /// (mounted, unmounted). | 124 | /// (mounted, unmounted). |
| 104 | pub fn rescan() -> (usize, usize) { | 125 | pub fn rescan() -> (usize, usize) { |
| 126 | let _lock = DiskGuard::acquire(); | ||
| 105 | let disks = crate::ahci::disk_count(); | 127 | let disks = crate::ahci::disk_count(); |
| 106 | let mut mounted = 0usize; | 128 | let mut mounted = 0usize; |
| 107 | let mut unmounted = 0usize; | 129 | let mut unmounted = 0usize; |
| @@ -256,11 +278,7 @@ fn is_eoc(c: u32) -> bool { | |||
| 256 | 278 | ||
| 257 | fn next_cluster(c: u32) -> Result<u32, ()> { | 279 | fn next_cluster(c: u32) -> Result<u32, ()> { |
| 258 | let n = read_fat_entry(c); | 280 | let n = read_fat_entry(c); |
| 259 | if is_eoc(n) { | 281 | if is_eoc(n) { Err(()) } else { Ok(n) } |
| 260 | Err(()) | ||
| 261 | } else { | ||
| 262 | Ok(n) | ||
| 263 | } | ||
| 264 | } | 282 | } |
| 265 | 283 | ||
| 266 | /// Allocate a fresh cluster: mark it EOC in the FAT and zero its data. | 284 | /// 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<Vec<DirItem>, ()> { | |||
| 532 | 550 | ||
| 533 | /// Public listing: no `.` / `..`. | 551 | /// Public listing: no `.` / `..`. |
| 534 | pub fn list_dir(vol: usize, dir_cluster: u32) -> Result<Vec<Entry>, ()> { | 552 | pub fn list_dir(vol: usize, dir_cluster: u32) -> Result<Vec<Entry>, ()> { |
| 553 | let _lock = DiskGuard::acquire(); | ||
| 535 | select_vol(vol)?; | 554 | select_vol(vol)?; |
| 536 | Ok(scan_dir(dir_cluster)? | 555 | Ok(scan_dir(dir_cluster)? |
| 537 | .into_iter() | 556 | .into_iter() |
| @@ -558,6 +577,7 @@ fn find_item(parent_dir_cluster: u32, name: &str) -> Result<DirItem, ()> { | |||
| 558 | 577 | ||
| 559 | /// Find an entry by name (case-insensitive). | 578 | /// Find an entry by name (case-insensitive). |
| 560 | pub fn find(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { | 579 | pub fn find(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<Entry, ()> { |
| 580 | let _lock = DiskGuard::acquire(); | ||
| 561 | select_vol(vol)?; | 581 | select_vol(vol)?; |
| 562 | find_item(parent_dir_cluster, name).map(|i| Entry { | 582 | find_item(parent_dir_cluster, name).map(|i| Entry { |
| 563 | name: i.name, | 583 | name: i.name, |
| @@ -576,6 +596,7 @@ pub fn read_file( | |||
| 576 | buf: &mut [u8], | 596 | buf: &mut [u8], |
| 577 | pos: u64, | 597 | pos: u64, |
| 578 | ) -> Result<usize, ()> { | 598 | ) -> Result<usize, ()> { |
| 599 | let _lock = DiskGuard::acquire(); | ||
| 579 | select_vol(vol)?; | 600 | select_vol(vol)?; |
| 580 | if buf.is_empty() { | 601 | if buf.is_empty() { |
| 581 | return Ok(0); | 602 | return Ok(0); |
| @@ -622,6 +643,7 @@ pub fn write_file( | |||
| 622 | buf: &[u8], | 643 | buf: &[u8], |
| 623 | pos: u64, | 644 | pos: u64, |
| 624 | ) -> Result<usize, ()> { | 645 | ) -> Result<usize, ()> { |
| 646 | let _lock = DiskGuard::acquire(); | ||
| 625 | select_vol(vol)?; | 647 | select_vol(vol)?; |
| 626 | if buf.is_empty() { | 648 | if buf.is_empty() { |
| 627 | return Ok(0); | 649 | return Ok(0); |
| @@ -685,6 +707,7 @@ pub fn write_file( | |||
| 685 | } | 707 | } |
| 686 | 708 | ||
| 687 | pub fn set_len(vol: usize, parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { | 709 | pub fn set_len(vol: usize, parent_dir_cluster: u32, name: &str, len: u64) -> Result<(), ()> { |
| 710 | let _lock = DiskGuard::acquire(); | ||
| 688 | select_vol(vol)?; | 711 | select_vol(vol)?; |
| 689 | let it = find_item(parent_dir_cluster, name)?; | 712 | let it = find_item(parent_dir_cluster, name)?; |
| 690 | if it.attr & 0x10 != 0 { | 713 | if it.attr & 0x10 != 0 { |
| @@ -1009,6 +1032,7 @@ fn add_dir_entry(parent: u32, name: &str, is_dir: bool, cluster: u32) -> Result< | |||
| 1009 | } | 1032 | } |
| 1010 | 1033 | ||
| 1011 | pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { | 1034 | pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> Result<(), ()> { |
| 1035 | let _lock = DiskGuard::acquire(); | ||
| 1012 | select_vol(vol)?; | 1036 | select_vol(vol)?; |
| 1013 | if find(vol, parent_dir_cluster, name).is_ok() { | 1037 | if find(vol, parent_dir_cluster, name).is_ok() { |
| 1014 | return Err(()); | 1038 | return Err(()); |
| @@ -1048,6 +1072,7 @@ pub fn create(vol: usize, parent_dir_cluster: u32, name: &str, is_dir: bool) -> | |||
| 1048 | } | 1072 | } |
| 1049 | 1073 | ||
| 1050 | pub fn remove(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<(), ()> { | 1074 | pub fn remove(vol: usize, parent_dir_cluster: u32, name: &str) -> Result<(), ()> { |
| 1075 | let _lock = DiskGuard::acquire(); | ||
| 1051 | select_vol(vol)?; | 1076 | select_vol(vol)?; |
| 1052 | let it = find_item(parent_dir_cluster, name)?; | 1077 | let it = find_item(parent_dir_cluster, name)?; |
| 1053 | if it.attr & 0x10 != 0 { | 1078 | 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) { | |||
| 203 | /// its backing info), and tombstone nodes whose volume was unmounted. | 203 | /// its backing info), and tombstone nodes whose volume was unmounted. |
| 204 | /// Returns (added, removed) node ops. | 204 | /// Returns (added, removed) node ops. |
| 205 | pub fn rescan_disks() -> (usize, usize) { | 205 | pub fn rescan_disks() -> (usize, usize) { |
| 206 | let _lock = crate::fat::DiskGuard::acquire(); | ||
| 206 | let mut added = 0usize; | 207 | let mut added = 0usize; |
| 207 | let mut removed = 0usize; | 208 | let mut removed = 0usize; |
| 208 | let disk_dir = match lookup(0, "disk") { | 209 | let disk_dir = match lookup(0, "disk") { |
| @@ -428,6 +429,7 @@ pub fn read(h: usize, buf: &mut [u8], pos: u64) -> Result<usize, ()> { | |||
| 428 | } | 429 | } |
| 429 | 430 | ||
| 430 | pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> { | 431 | pub fn write(h: usize, buf: &[u8], pos: u64) -> Result<usize, ()> { |
| 432 | let _lock = crate::fat::DiskGuard::acquire(); | ||
| 431 | match &mut nodes()[h].data { | 433 | match &mut nodes()[h].data { |
| 432 | NodeData::Regular(v) => { | 434 | NodeData::Regular(v) => { |
| 433 | let p = pos as usize; | 435 | let p = pos as usize; |
| @@ -533,7 +535,10 @@ pub fn write_crash_log(lines: &[&str]) { | |||
| 533 | let mut tb = [0u8; 48]; | 535 | let mut tb = [0u8; 48]; |
| 534 | let tn = { | 536 | let tn = { |
| 535 | let mut w = crate::strbuf::FmtBuf::new(&mut tb); | 537 | let mut w = crate::strbuf::FmtBuf::new(&mut tb); |
| 536 | let _ = core::fmt::write(&mut w, core::format_args!("crashlog: vol {} ({})\r\n", vol, label)); | 538 | let _ = core::fmt::write( |
| 539 | &mut w, | ||
| 540 | core::format_args!("crashlog: vol {} ({})\r\n", vol, label), | ||
| 541 | ); | ||
| 537 | w.len() | 542 | w.len() |
| 538 | }; | 543 | }; |
| 539 | crate::serial::write_raw(core::str::from_utf8(&tb[..tn]).unwrap_or("?")); | 544 | crate::serial::write_raw(core::str::from_utf8(&tb[..tn]).unwrap_or("?")); |
| @@ -555,7 +560,9 @@ pub fn write_crash_log(lines: &[&str]) { | |||
| 555 | } | 560 | } |
| 556 | 561 | ||
| 557 | /// Truncate/resize a regular file. | 562 | /// Truncate/resize a regular file. |
| 558 | pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h].data { | 563 | pub fn set_len(h: usize, len: u64) -> Result<(), ()> { |
| 564 | let _lock = crate::fat::DiskGuard::acquire(); | ||
| 565 | match &mut nodes()[h].data { | ||
| 559 | NodeData::Regular(v) => { | 566 | NodeData::Regular(v) => { |
| 560 | v.resize(len as usize, 0); | 567 | v.resize(len as usize, 0); |
| 561 | Ok(()) | 568 | Ok(()) |
| @@ -567,6 +574,7 @@ pub fn set_len(h: usize, len: u64) -> Result<(), ()> { match &mut nodes()[h]. | |||
| 567 | 574 | ||
| 568 | /// Create a Regular or Dir child. Fails if the name exists or is invalid. | 575 | /// Create a Regular or Dir child. Fails if the name exists or is invalid. |
| 569 | pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> { | 576 | pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> { |
| 577 | let _lock = crate::fat::DiskGuard::acquire(); | ||
| 570 | if name.is_empty() || name.contains('/') || kind == FileType::Char { | 578 | if name.is_empty() || name.contains('/') || kind == FileType::Char { |
| 571 | return Err(()); | 579 | return Err(()); |
| 572 | } | 580 | } |
| @@ -608,6 +616,7 @@ pub fn create(parent: usize, name: &str, kind: FileType) -> Result<usize, ()> { | |||
| 608 | 616 | ||
| 609 | /// Remove a regular file or an empty directory (tombstone). | 617 | /// Remove a regular file or an empty directory (tombstone). |
| 610 | pub fn remove(h: usize) -> Result<(), ()> { | 618 | pub fn remove(h: usize) -> Result<(), ()> { |
| 619 | let _lock = crate::fat::DiskGuard::acquire(); | ||
| 611 | // Disk nodes: delete through the FAT volume (dirs must be empty). | 620 | // Disk nodes: delete through the FAT volume (dirs must be empty). |
| 612 | if let NodeData::Disk(i) = &nodes()[h].data { | 621 | if let NodeData::Disk(i) = &nodes()[h].data { |
| 613 | if i.name.is_empty() { | 622 | if i.name.is_empty() { |
