diff options
| author | salmonik <salmonik@tuta.io> | 2026-08-01 18:25:55 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-08-01 18:25:55 +0200 |
| commit | 1ea2f68e6a9af135e6cb3274fdf28d4ddc5812d5 (patch) | |
| tree | f28246586e387293d0c8beaf6b445210aaab8c48 | |
| parent | klog/tty/pager: no more truncated lines — full lines flow, terminal wraps (diff) | |
| download | rs-dos-1ea2f68e6a9af135e6cb3274fdf28d4ddc5812d5.tar.gz rs-dos-1ea2f68e6a9af135e6cb3274fdf28d4ddc5812d5.zip | |
fix pi-lens warnings: unwraps, shell SC2046, dup code (fmtbuf/scripts/hexdump/cat/ahci/pci)
- memory.rs: self-test Layout::from_size_align no longer panics — the
test fails cleanly on an invalid layout
- fat.rs: v() no longer unwraps the active-volume slot — invariant
violation logs + halts instead of panicking mid-disk-io
- strbuf: shared FmtBuf replaces the duplicated PanicBuf/FmtBuf
(main.rs + sched.rs)
- test-disk.sh: SC2046 — conditional --fresh arg no longer relies on
word splitting
- scripts: boot.sh/boot-image.sh share lib-boot-args.sh (dup args loop)
- shell: cat/hexdump stdin pairs unified with Option<&str> marker;
hexdump format_row folded into format_partial
- ahci: read/write_sectors unified behind dma_sectors
- pci: config_read/write share config_addr
Diffstat (limited to '')
| -rw-r--r-- | rsdos/src/main.rs | 21 | ||||
| -rw-r--r-- | rsnix/src/ahci.rs | 18 | ||||
| -rw-r--r-- | rsnix/src/fat.rs | 12 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 12 | ||||
| -rw-r--r-- | rsnix/src/pci.rs | 28 | ||||
| -rw-r--r-- | rsnix/src/sched.rs | 18 | ||||
| -rw-r--r-- | rsnix/src/strbuf.rs | 28 | ||||
| -rwxr-xr-x | scripts/boot-image.sh | 25 | ||||
| -rwxr-xr-x | scripts/boot.sh | 22 | ||||
| -rw-r--r-- | scripts/lib-boot-args.sh | 38 | ||||
| -rwxr-xr-x | scripts/test-disk.sh | 6 | ||||
| -rw-r--r-- | shell/src/lib.rs | 94 |
12 files changed, 152 insertions, 170 deletions
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs index 7ca031c..700dcc7 100644 --- a/rsdos/src/main.rs +++ b/rsdos/src/main.rs | |||
| @@ -9,20 +9,9 @@ use rsnix::uefi::*; | |||
| 9 | // Panic handler | 9 | // Panic handler |
| 10 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
| 11 | 11 | ||
| 12 | /// Small fmt::Write over a byte slice (no allocation, bounded). | 12 | // --------------------------------------------------------------------------- |
| 13 | struct PanicBuf<'a> { | 13 | // Panic handler |
| 14 | b: &'a mut [u8], | 14 | // --------------------------------------------------------------------------- |
| 15 | n: usize, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl core::fmt::Write for PanicBuf<'_> { | ||
| 19 | fn write_str(&mut self, s: &str) -> core::fmt::Result { | ||
| 20 | let m = s.len().min(self.b.len() - self.n); | ||
| 21 | self.b[self.n..self.n + m].copy_from_slice(&s.as_bytes()[..m]); | ||
| 22 | self.n += m; | ||
| 23 | Ok(()) | ||
| 24 | } | ||
| 25 | } | ||
| 26 | 15 | ||
| 27 | #[panic_handler] | 16 | #[panic_handler] |
| 28 | fn panic(info: &PanicInfo) -> ! { | 17 | fn panic(info: &PanicInfo) -> ! { |
| @@ -35,12 +24,12 @@ fn panic(info: &PanicInfo) -> ! { | |||
| 35 | if rsnix::sched::is_ready() && !rsnix::sched::in_isr() && rsnix::sched::current() >= 2 { | 24 | if rsnix::sched::is_ready() && !rsnix::sched::in_isr() && rsnix::sched::current() >= 2 { |
| 36 | let mut buf = [0u8; 192]; | 25 | let mut buf = [0u8; 192]; |
| 37 | let n = { | 26 | let n = { |
| 38 | let mut w = PanicBuf { b: &mut buf, n: 0 }; | 27 | let mut w = rsnix::strbuf::FmtBuf::new(&mut buf); |
| 39 | let _ = write!(&mut w, "{}", info.message()); | 28 | let _ = write!(&mut w, "{}", info.message()); |
| 40 | if let Some(loc) = info.location() { | 29 | if let Some(loc) = info.location() { |
| 41 | let _ = write!(&mut w, " at {}:{}", loc.file(), loc.line()); | 30 | let _ = write!(&mut w, " at {}:{}", loc.file(), loc.line()); |
| 42 | } | 31 | } |
| 43 | w.n | 32 | w.len() |
| 44 | }; | 33 | }; |
| 45 | let msg = core::str::from_utf8(&buf[..n]).unwrap_or("task panic"); | 34 | let msg = core::str::from_utf8(&buf[..n]).unwrap_or("task panic"); |
| 46 | rsnix::sched::abort_current(msg); | 35 | rsnix::sched::abort_current(msg); |
diff --git a/rsnix/src/ahci.rs b/rsnix/src/ahci.rs index 72da07d..a77b341 100644 --- a/rsnix/src/ahci.rs +++ b/rsnix/src/ahci.rs | |||
| @@ -510,22 +510,24 @@ fn active() -> Option<&'static Disk> { | |||
| 510 | disks().get(unsafe { ACTIVE }).and_then(|d| d.as_ref()) | 510 | disks().get(unsafe { ACTIVE }).and_then(|d| d.as_ref()) |
| 511 | } | 511 | } |
| 512 | 512 | ||
| 513 | /// DMA `count` sectors starting at `lba` into the selected disk's buffer. | 513 | /// Bounds-checked DMA of `count` sectors starting at `lba` into/out of the |
| 514 | pub fn read_sectors(lba: u64, count: u32) -> Result<(), ()> { | 514 | /// selected disk's buffer (single code path for both directions). |
| 515 | fn dma_sectors(lba: u64, count: u32, cmd: u8, write: bool) -> Result<(), ()> { | ||
| 515 | let d = active().ok_or(())?; | 516 | let d = active().ok_or(())?; |
| 516 | if lba + count as u64 > d.sectors { | 517 | if lba + count as u64 > d.sectors { |
| 517 | return Err(()); | 518 | return Err(()); |
| 518 | } | 519 | } |
| 519 | issue(d, ATA_READ_DMA_EXT, lba, count, false) | 520 | issue(d, cmd, lba, count, write) |
| 521 | } | ||
| 522 | |||
| 523 | /// DMA `count` sectors starting at `lba` into the selected disk's buffer. | ||
| 524 | pub fn read_sectors(lba: u64, count: u32) -> Result<(), ()> { | ||
| 525 | dma_sectors(lba, count, ATA_READ_DMA_EXT, false) | ||
| 520 | } | 526 | } |
| 521 | 527 | ||
| 522 | /// DMA `count` sectors starting at `lba` out of the selected disk's buffer. | 528 | /// DMA `count` sectors starting at `lba` out of the selected disk's buffer. |
| 523 | pub fn write_sectors(lba: u64, count: u32) -> Result<(), ()> { | 529 | pub fn write_sectors(lba: u64, count: u32) -> Result<(), ()> { |
| 524 | let d = active().ok_or(())?; | 530 | dma_sectors(lba, count, ATA_WRITE_DMA_EXT, true) |
| 525 | if lba + count as u64 > d.sectors { | ||
| 526 | return Err(()); | ||
| 527 | } | ||
| 528 | issue(d, ATA_WRITE_DMA_EXT, lba, count, true) | ||
| 529 | } | 531 | } |
| 530 | 532 | ||
| 531 | /// Pointer to the selected disk's 4 KiB DMA buffer. | 533 | /// Pointer to the selected disk's 4 KiB DMA buffer. |
diff --git a/rsnix/src/fat.rs b/rsnix/src/fat.rs index bad01cd..eda81ed 100644 --- a/rsnix/src/fat.rs +++ b/rsnix/src/fat.rs | |||
| @@ -167,8 +167,18 @@ fn write_sector(lba: u64, data: &[u8; SECTOR]) -> Result<(), ()> { | |||
| 167 | 167 | ||
| 168 | // --- cluster math ---------------------------------------------------------- | 168 | // --- cluster math ---------------------------------------------------------- |
| 169 | 169 | ||
| 170 | /// Current volume's metadata — only ever reached with a selected volume | ||
| 171 | /// (the fs layer calls `select_vol` first), so a missing slot is a kernel | ||
| 172 | /// invariant violation, not a recoverable error. | ||
| 170 | fn v() -> &'static mut Volume { | 173 | fn v() -> &'static mut Volume { |
| 171 | vols().get_mut(*active_vol()).unwrap() | 174 | let i = *active_vol(); |
| 175 | match vols().get_mut(i) { | ||
| 176 | Some(v) => v, | ||
| 177 | None => { | ||
| 178 | crate::serial_println!("fat: internal error — no volume at slot {}", i); | ||
| 179 | crate::interrupts::halt(); | ||
| 180 | } | ||
| 181 | } | ||
| 172 | } | 182 | } |
| 173 | 183 | ||
| 174 | fn spc() -> usize { | 184 | fn spc() -> usize { |
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs index 7c3eda0..e03af8b 100644 --- a/rsnix/src/memory.rs +++ b/rsnix/src/memory.rs | |||
| @@ -458,7 +458,9 @@ pub fn self_test() -> bool { | |||
| 458 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); | 458 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 459 | let align = 16usize << (seed as usize % 8); | 459 | let align = 16usize << (seed as usize % 8); |
| 460 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); | 460 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 461 | let layout = Layout::from_size_align(size, align).unwrap(); | 461 | let Ok(layout) = Layout::from_size_align(size, align) else { |
| 462 | return false; // impossible: size <= 2048, align <= 2048 | ||
| 463 | }; | ||
| 462 | let p = ALLOCATOR.alloc(layout); | 464 | let p = ALLOCATOR.alloc(layout); |
| 463 | if p.is_null() { | 465 | if p.is_null() { |
| 464 | break; // heap exhausted — stop | 466 | break; // heap exhausted — stop |
| @@ -493,7 +495,9 @@ pub fn self_test() -> bool { | |||
| 493 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); | 495 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 494 | let align = 16usize << (seed as usize % 8); | 496 | let align = 16usize << (seed as usize % 8); |
| 495 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); | 497 | seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1); |
| 496 | let layout = Layout::from_size_align(size, align).unwrap(); | 498 | let Ok(layout) = Layout::from_size_align(size, align) else { |
| 499 | return false; // impossible: size <= 2048, align <= 2048 | ||
| 500 | }; | ||
| 497 | let p = ALLOCATOR.alloc(layout); | 501 | let p = ALLOCATOR.alloc(layout); |
| 498 | if p.is_null() { | 502 | if p.is_null() { |
| 499 | return false; // failed to reuse freed space | 503 | return false; // failed to reuse freed space |
| @@ -510,7 +514,9 @@ pub fn self_test() -> bool { | |||
| 510 | if *ptr == 0 { | 514 | if *ptr == 0 { |
| 511 | continue; | 515 | continue; |
| 512 | } | 516 | } |
| 513 | let layout = Layout::from_size_align(*size, *align).unwrap(); | 517 | let Ok(layout) = Layout::from_size_align(*size, *align) else { |
| 518 | return false; // impossible: sizes/aligns came from the loop above | ||
| 519 | }; | ||
| 514 | ALLOCATOR.dealloc(*ptr as *mut u8, layout); | 520 | ALLOCATOR.dealloc(*ptr as *mut u8, layout); |
| 515 | } | 521 | } |
| 516 | let stats = heap_stats(); | 522 | let stats = heap_stats(); |
diff --git a/rsnix/src/pci.rs b/rsnix/src/pci.rs index 21d7536..b9c2265 100644 --- a/rsnix/src/pci.rs +++ b/rsnix/src/pci.rs | |||
| @@ -46,25 +46,25 @@ fn inl(port: u16) -> u32 { | |||
| 46 | val | 46 | val |
| 47 | } | 47 | } |
| 48 | 48 | ||
| 49 | /// Read a dword from a device's config space (`reg` must be dword-aligned). | 49 | /// Encode the PCI config-space port address for (bus, dev, fn, reg); |
| 50 | pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 { | 50 | /// `reg` must be dword-aligned. |
| 51 | let addr = 0x8000_0000 | 51 | fn config_addr(bus: u8, device: u8, func: u8, reg: u8) -> u32 { |
| 52 | 0x8000_0000 | ||
| 52 | | ((bus as u32) << 16) | 53 | | ((bus as u32) << 16) |
| 53 | | ((device as u32) << 11) | 54 | | ((device as u32) << 11) |
| 54 | | ((func as u32) << 8) | 55 | | ((func as u32) << 8) |
| 55 | | ((reg as u32) & 0xFC); | 56 | | ((reg as u32) & 0xFC) |
| 56 | outl(CONFIG_ADDRESS, addr); | 57 | } |
| 58 | |||
| 59 | /// Read a dword from a device's config space (`reg` must be dword-aligned). | ||
| 60 | pub fn config_read(bus: u8, device: u8, func: u8, reg: u8) -> u32 { | ||
| 61 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); | ||
| 57 | inl(CONFIG_DATA) | 62 | inl(CONFIG_DATA) |
| 58 | } | 63 | } |
| 59 | 64 | ||
| 60 | /// Write a dword to a device's config space (`reg` must be dword-aligned). | 65 | /// Write a dword to a device's config space (`reg` must be dword-aligned). |
| 61 | pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { | 66 | pub fn config_write(bus: u8, device: u8, func: u8, reg: u8, val: u32) { |
| 62 | let addr = 0x8000_0000 | 67 | outl(CONFIG_ADDRESS, config_addr(bus, device, func, reg)); |
| 63 | | ((bus as u32) << 16) | ||
| 64 | | ((device as u32) << 11) | ||
| 65 | | ((func as u32) << 8) | ||
| 66 | | ((reg as u32) & 0xFC); | ||
| 67 | outl(CONFIG_ADDRESS, addr); | ||
| 68 | outl(CONFIG_DATA, val) | 68 | outl(CONFIG_DATA, val) |
| 69 | } | 69 | } |
| 70 | 70 | ||
| @@ -83,7 +83,11 @@ pub fn bar64(bus: u8, device: u8, func: u8, bar_index: u8) -> Option<u64> { | |||
| 83 | 0 | 83 | 0 |
| 84 | }; | 84 | }; |
| 85 | let base = ((hi as u64) << 32) | (lo as u64 & 0xFFFF_FFF0); | 85 | let base = ((hi as u64) << 32) | (lo as u64 & 0xFFFF_FFF0); |
| 86 | if base == 0 { None } else { Some(base) } | 86 | if base == 0 { |
| 87 | None | ||
| 88 | } else { | ||
| 89 | Some(base) | ||
| 90 | } | ||
| 87 | } | 91 | } |
| 88 | 92 | ||
| 89 | /// Enumerate every device/function present on all buses. | 93 | /// Enumerate every device/function present on all buses. |
diff --git a/rsnix/src/sched.rs b/rsnix/src/sched.rs index 4b10503..6ffa745 100644 --- a/rsnix/src/sched.rs +++ b/rsnix/src/sched.rs | |||
| @@ -372,7 +372,7 @@ pub fn page_fault(cr2: u64, frame_ptr: usize) -> usize { | |||
| 372 | let rip = unsafe { (frame_ptr as *const IntFrame).read().rip }; | 372 | let rip = unsafe { (frame_ptr as *const IntFrame).read().rip }; |
| 373 | let mut buf = [0u8; 96]; | 373 | let mut buf = [0u8; 96]; |
| 374 | let n = { | 374 | let n = { |
| 375 | let mut f = FmtBuf { b: &mut buf, n: 0 }; | 375 | let mut f = crate::strbuf::FmtBuf::new(&mut buf); |
| 376 | let _ = fmt::write( | 376 | let _ = fmt::write( |
| 377 | &mut f, | 377 | &mut f, |
| 378 | core::format_args!( | 378 | core::format_args!( |
| @@ -386,7 +386,7 @@ pub fn page_fault(cr2: u64, frame_ptr: usize) -> usize { | |||
| 386 | rip | 386 | rip |
| 387 | ), | 387 | ), |
| 388 | ); | 388 | ); |
| 389 | f.n | 389 | f.len() |
| 390 | }; | 390 | }; |
| 391 | abort_current(core::str::from_utf8(&buf[..n]).unwrap_or("task fault")) | 391 | abort_current(core::str::from_utf8(&buf[..n]).unwrap_or("task fault")) |
| 392 | } | 392 | } |
| @@ -528,17 +528,3 @@ fn idle_main() { | |||
| 528 | } | 528 | } |
| 529 | } | 529 | } |
| 530 | } | 530 | } |
| 531 | |||
| 532 | struct FmtBuf<'a> { | ||
| 533 | b: &'a mut [u8], | ||
| 534 | n: usize, | ||
| 535 | } | ||
| 536 | |||
| 537 | impl fmt::Write for FmtBuf<'_> { | ||
| 538 | fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| 539 | let m = s.len().min(self.b.len() - self.n); | ||
| 540 | self.b[self.n..self.n + m].copy_from_slice(&s.as_bytes()[..m]); | ||
| 541 | self.n += m; | ||
| 542 | Ok(()) | ||
| 543 | } | ||
| 544 | } | ||
diff --git a/rsnix/src/strbuf.rs b/rsnix/src/strbuf.rs index 262720d..0e3ea56 100644 --- a/rsnix/src/strbuf.rs +++ b/rsnix/src/strbuf.rs | |||
| @@ -69,3 +69,31 @@ impl<const N: usize> fmt::Debug for String<N> { | |||
| 69 | write!(f, "{:?}", self.as_str()) | 69 | write!(f, "{:?}", self.as_str()) |
| 70 | } | 70 | } |
| 71 | } | 71 | } |
| 72 | |||
| 73 | /// Small `fmt::Write` over a byte slice — no allocation, bounded. | ||
| 74 | /// Shared by the panic handler (task-panic message) and the scheduler | ||
| 75 | /// (page-fault abort message) so they don't each reinvent it. | ||
| 76 | pub struct FmtBuf<'a> { | ||
| 77 | b: &'a mut [u8], | ||
| 78 | n: usize, | ||
| 79 | } | ||
| 80 | |||
| 81 | impl<'a> FmtBuf<'a> { | ||
| 82 | pub fn new(b: &'a mut [u8]) -> Self { | ||
| 83 | FmtBuf { b, n: 0 } | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Bytes written so far (clamped at the buffer length). | ||
| 87 | pub fn len(&self) -> usize { | ||
| 88 | self.n | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | impl fmt::Write for FmtBuf<'_> { | ||
| 93 | fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| 94 | let m = s.len().min(self.b.len() - self.n); | ||
| 95 | self.b[self.n..self.n + m].copy_from_slice(&s.as_bytes()[..m]); | ||
| 96 | self.n += m; | ||
| 97 | Ok(()) | ||
| 98 | } | ||
| 99 | } | ||
diff --git a/scripts/boot-image.sh b/scripts/boot-image.sh index 913e5cb..e8c093c 100755 --- a/scripts/boot-image.sh +++ b/scripts/boot-image.sh | |||
| @@ -34,29 +34,8 @@ GUI="" | |||
| 34 | DEBUG="" | 34 | DEBUG="" |
| 35 | 35 | ||
| 36 | # ---- args ---- | 36 | # ---- args ---- |
| 37 | while [ $# -gt 0 ]; do | 37 | source "$SCRIPT_DIR/lib-boot-args.sh" |
| 38 | case "$1" in | 38 | parse_boot_args "$@" |
| 39 | --gui | -g) GUI="-vga std" ;; | ||
| 40 | --debug | -d) DEBUG="-s -S" ;; | ||
| 41 | --mem) | ||
| 42 | shift | ||
| 43 | MEM="$1" | ||
| 44 | ;; | ||
| 45 | --smp) | ||
| 46 | shift | ||
| 47 | SMP="$1" | ||
| 48 | ;; | ||
| 49 | --help | -h) | ||
| 50 | head -20 "$0" | grep "^#" | sed 's/^# //' | ||
| 51 | exit 0 | ||
| 52 | ;; | ||
| 53 | *) | ||
| 54 | echo "unknown: $1" | ||
| 55 | exit 1 | ||
| 56 | ;; | ||
| 57 | esac | ||
| 58 | shift | ||
| 59 | done | ||
| 60 | 39 | ||
| 61 | # ---- build image if missing ---- | 40 | # ---- build image if missing ---- |
| 62 | if [ ! -f "$IMAGE" ]; then | 41 | if [ ! -f "$IMAGE" ]; then |
diff --git a/scripts/boot.sh b/scripts/boot.sh index 99fd103..5b8e6fa 100755 --- a/scripts/boot.sh +++ b/scripts/boot.sh | |||
| @@ -45,33 +45,21 @@ GUI="" | |||
| 45 | DEBUG="" | 45 | DEBUG="" |
| 46 | 46 | ||
| 47 | # ---- args ---- | 47 | # ---- args ---- |
| 48 | while [ $# -gt 0 ]; do | 48 | source "$SCRIPT_DIR/lib-boot-args.sh" |
| 49 | # boot.sh-specific flags live here; everything else is shared. | ||
| 50 | handle_arg() { | ||
| 49 | case "$1" in | 51 | case "$1" in |
| 50 | --gui | -g) GUI="-vga std" ;; | ||
| 51 | --debug | -d) DEBUG="-s -S" ;; | ||
| 52 | --release | -r) | 52 | --release | -r) |
| 53 | PROFILE="release" | 53 | PROFILE="release" |
| 54 | CARGO_FLAGS="--release" | 54 | CARGO_FLAGS="--release" |
| 55 | ;; | 55 | ;; |
| 56 | --mem) | ||
| 57 | shift | ||
| 58 | MEM="$1" | ||
| 59 | ;; | ||
| 60 | --smp) | ||
| 61 | shift | ||
| 62 | SMP="$1" | ||
| 63 | ;; | ||
| 64 | --help | -h) | ||
| 65 | head -20 "$0" | grep "^#" | sed 's/^# //' | ||
| 66 | exit 0 | ||
| 67 | ;; | ||
| 68 | *) | 56 | *) |
| 69 | echo "unknown: $1" | 57 | echo "unknown: $1" |
| 70 | exit 1 | 58 | exit 1 |
| 71 | ;; | 59 | ;; |
| 72 | esac | 60 | esac |
| 73 | shift | 61 | } |
| 74 | done | 62 | parse_boot_args "$@" |
| 75 | 63 | ||
| 76 | OUT_DIR="target/x86_64-unknown-uefi/$PROFILE" | 64 | OUT_DIR="target/x86_64-unknown-uefi/$PROFILE" |
| 77 | KERNEL="$OUT_DIR/rsdos.efi" | 65 | KERNEL="$OUT_DIR/rsdos.efi" |
diff --git a/scripts/lib-boot-args.sh b/scripts/lib-boot-args.sh new file mode 100644 index 0000000..ec97500 --- /dev/null +++ b/scripts/lib-boot-args.sh | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | #!/usr/bin/env bash | ||
| 2 | # | ||
| 3 | # scripts/lib-boot-args.sh — shared CLI parsing for the QEMU boot scripts. | ||
| 4 | # Sourced, not executed. The caller sets default values for GUI/DEBUG/ | ||
| 5 | # MEM/SMP first, then calls `parse_boot_args "$@"`. A caller with extra | ||
| 6 | # flags defines its own `handle_arg()` to intercept them before the | ||
| 7 | # shared cases run (unknown args still fail loudly). | ||
| 8 | set -euo pipefail | ||
| 9 | |||
| 10 | parse_boot_args() { | ||
| 11 | while [ $# -gt 0 ]; do | ||
| 12 | case "$1" in | ||
| 13 | --gui | -g) GUI="-vga std" ;; | ||
| 14 | --debug | -d) DEBUG="-s -S" ;; | ||
| 15 | --mem) | ||
| 16 | shift | ||
| 17 | MEM="$1" | ||
| 18 | ;; | ||
| 19 | --smp) | ||
| 20 | shift | ||
| 21 | SMP="$1" | ||
| 22 | ;; | ||
| 23 | --help | -h) | ||
| 24 | head -20 "$0" | grep "^#" | sed 's/^# //' | ||
| 25 | exit 0 | ||
| 26 | ;; | ||
| 27 | *) handle_arg "$1" ;; | ||
| 28 | esac | ||
| 29 | shift | ||
| 30 | done | ||
| 31 | } | ||
| 32 | |||
| 33 | # Default: no extra flags. A script with more defines its own before | ||
| 34 | # calling parse_boot_args. | ||
| 35 | handle_arg() { | ||
| 36 | echo "unknown: $1" | ||
| 37 | exit 1 | ||
| 38 | } | ||
diff --git a/scripts/test-disk.sh b/scripts/test-disk.sh index 11a9b05..0268fff 100755 --- a/scripts/test-disk.sh +++ b/scripts/test-disk.sh | |||
| @@ -72,7 +72,11 @@ cargo build --package rsdos --target x86_64-unknown-uefi | |||
| 72 | echo "==> 2/6 deploy to ESP + ensure disk image" | 72 | echo "==> 2/6 deploy to ESP + ensure disk image" |
| 73 | mkdir -p "$ESP_DIR/EFI/BOOT" | 73 | mkdir -p "$ESP_DIR/EFI/BOOT" |
| 74 | cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI" | 74 | cp target/x86_64-unknown-uefi/debug/rsdos.efi "$ESP_DIR/EFI/BOOT/BOOTX64.EFI" |
| 75 | "$SCRIPT_DIR/mkdisk.sh" $([ "$FRESH" -eq 1 ] && echo --fresh) "$IMG" | 75 | if [ "$FRESH" -eq 1 ]; then |
| 76 | "$SCRIPT_DIR/mkdisk.sh" --fresh "$IMG" | ||
| 77 | else | ||
| 78 | "$SCRIPT_DIR/mkdisk.sh" "$IMG" | ||
| 79 | fi | ||
| 76 | 80 | ||
| 77 | echo "==> 3/6 boot QEMU (headless)" | 81 | echo "==> 3/6 boot QEMU (headless)" |
| 78 | kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true | 82 | kill "$(cat "$QPID" 2>/dev/null)" 2>/dev/null || true |
diff --git a/shell/src/lib.rs b/shell/src/lib.rs index 617e5a4..4640697 100644 --- a/shell/src/lib.rs +++ b/shell/src/lib.rs | |||
| @@ -515,7 +515,12 @@ impl Pipeline { | |||
| 515 | } | 515 | } |
| 516 | } | 516 | } |
| 517 | Filter::Pause => { | 517 | Filter::Pause => { |
| 518 | let StageState::Pause { shown, screen, cols } = st else { | 518 | let StageState::Pause { |
| 519 | shown, | ||
| 520 | screen, | ||
| 521 | cols, | ||
| 522 | } = st | ||
| 523 | else { | ||
| 519 | unreachable!("stage state does not match filter") | 524 | unreachable!("stage state does not match filter") |
| 520 | }; | 525 | }; |
| 521 | for l in cur.drain(..) { | 526 | for l in cur.drain(..) { |
| @@ -871,7 +876,7 @@ fn cmd_cat(p: &Parsed) { | |||
| 871 | if p.args.is_empty() { | 876 | if p.args.is_empty() { |
| 872 | // Path-less `cat` with no input keyword reads the keyboard | 877 | // Path-less `cat` with no input keyword reads the keyboard |
| 873 | // until ^D. | 878 | // until ^D. |
| 874 | cat_stdin(&mut pipe); | 879 | cat_stdin(&mut pipe, None); |
| 875 | } else { | 880 | } else { |
| 876 | for path in &p.args { | 881 | for path in &p.args { |
| 877 | if !cat_file(&mut pipe, path) { | 882 | if !cat_file(&mut pipe, path) { |
| @@ -885,7 +890,7 @@ fn cmd_cat(p: &Parsed) { | |||
| 885 | } | 890 | } |
| 886 | Input::Til(marker) => { | 891 | Input::Til(marker) => { |
| 887 | if p.args.is_empty() { | 892 | if p.args.is_empty() { |
| 888 | cat_stdin_until(&mut pipe, marker); | 893 | cat_stdin(&mut pipe, Some(marker)); |
| 889 | } else { | 894 | } else { |
| 890 | // `cat <path> TIL <marker>` — the paths are the input | 895 | // `cat <path> TIL <marker>` — the paths are the input |
| 891 | // source, truncated at the first marker line. | 896 | // source, truncated at the first marker line. |
| @@ -904,34 +909,21 @@ fn cmd_cat(p: &Parsed) { | |||
| 904 | pipe.end(); | 909 | pipe.end(); |
| 905 | } | 910 | } |
| 906 | 911 | ||
| 907 | fn cat_stdin(pipe: &mut Pipeline) -> bool { | 912 | /// Read lines from the tty until ^C/^D (or an exact marker line, when |
| 913 | /// `marker` is Some — the marker itself is not part of the input). | ||
| 914 | fn cat_stdin(pipe: &mut Pipeline, marker: Option<&str>) -> bool { | ||
| 908 | loop { | 915 | loop { |
| 909 | match tty::read_line() { | 916 | match tty::read_line() { |
| 910 | LineInput::Line(l) => { | 917 | LineInput::Line(l) => { |
| 911 | if !pipe.line(l.as_str()) { | 918 | if marker.is_some() && l.as_str() == marker.unwrap() { |
| 912 | return false; | ||
| 913 | } | ||
| 914 | } | ||
| 915 | LineInput::Interrupted => return false, // ^C aborts the stream | ||
| 916 | LineInput::Eof => return true, // ^D = EOF | ||
| 917 | } | ||
| 918 | } | ||
| 919 | } | ||
| 920 | |||
| 921 | fn cat_stdin_until(pipe: &mut Pipeline, marker: &str) -> bool { | ||
| 922 | loop { | ||
| 923 | match tty::read_line() { | ||
| 924 | LineInput::Line(l) => { | ||
| 925 | // Marker line is not part of the input. | ||
| 926 | if l.as_str() == marker { | ||
| 927 | return true; | 919 | return true; |
| 928 | } | 920 | } |
| 929 | if !pipe.line(l.as_str()) { | 921 | if !pipe.line(l.as_str()) { |
| 930 | return false; | 922 | return false; |
| 931 | } | 923 | } |
| 932 | } | 924 | } |
| 933 | LineInput::Interrupted => return false, | 925 | LineInput::Interrupted => return false, // ^C aborts the stream |
| 934 | LineInput::Eof => return true, | 926 | LineInput::Eof => return true, // ^D = EOF |
| 935 | } | 927 | } |
| 936 | } | 928 | } |
| 937 | } | 929 | } |
| @@ -1030,25 +1022,6 @@ struct HexCtx<'a> { | |||
| 1030 | alive: bool, | 1022 | alive: bool, |
| 1031 | } | 1023 | } |
| 1032 | 1024 | ||
| 1033 | fn format_row(addr: u64, row: &[u8; 16]) -> String { | ||
| 1034 | let mut out = String::new(); | ||
| 1035 | use core::fmt::Write as _; | ||
| 1036 | let _ = core::write!(out, "{:08x} ", addr); | ||
| 1037 | for &b in row { | ||
| 1038 | let _ = core::write!(out, "{:02x} ", b); | ||
| 1039 | } | ||
| 1040 | out.push_str(" |"); | ||
| 1041 | for &b in row { | ||
| 1042 | out.push(if b.is_ascii_graphic() || b == b' ' { | ||
| 1043 | b as char | ||
| 1044 | } else { | ||
| 1045 | '.' | ||
| 1046 | }); | ||
| 1047 | } | ||
| 1048 | out.push('|'); | ||
| 1049 | out | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | fn format_partial(addr: u64, row: &[u8]) -> String { | 1025 | fn format_partial(addr: u64, row: &[u8]) -> String { |
| 1053 | let mut out = String::new(); | 1026 | let mut out = String::new(); |
| 1054 | use core::fmt::Write as _; | 1027 | use core::fmt::Write as _; |
| @@ -1081,7 +1054,7 @@ impl HexCtx<'_> { | |||
| 1081 | self.row[self.row_len] = b; | 1054 | self.row[self.row_len] = b; |
| 1082 | self.row_len += 1; | 1055 | self.row_len += 1; |
| 1083 | if self.row_len == 16 { | 1056 | if self.row_len == 16 { |
| 1084 | let line = format_row(self.pos, &self.row); | 1057 | let line = format_partial(self.pos, &self.row); |
| 1085 | self.alive = self.pipe.line(&line); | 1058 | self.alive = self.pipe.line(&line); |
| 1086 | self.pos += 16; | 1059 | self.pos += 16; |
| 1087 | self.row_len = 0; | 1060 | self.row_len = 0; |
| @@ -1106,7 +1079,7 @@ fn cmd_hexdump(p: &Parsed) { | |||
| 1106 | match &p.input { | 1079 | match &p.input { |
| 1107 | Input::None => { | 1080 | Input::None => { |
| 1108 | if p.args.is_empty() { | 1081 | if p.args.is_empty() { |
| 1109 | hexdump_stdin(&mut pipe); | 1082 | hexdump_stdin(&mut pipe, None); |
| 1110 | } else { | 1083 | } else { |
| 1111 | for path in &p.args { | 1084 | for path in &p.args { |
| 1112 | if !hexdump_file(&mut pipe, path) { | 1085 | if !hexdump_file(&mut pipe, path) { |
| @@ -1120,7 +1093,7 @@ fn cmd_hexdump(p: &Parsed) { | |||
| 1120 | } | 1093 | } |
| 1121 | Input::Til(marker) => { | 1094 | Input::Til(marker) => { |
| 1122 | if p.args.is_empty() { | 1095 | if p.args.is_empty() { |
| 1123 | hexdump_stdin_until(&mut pipe, marker); | 1096 | hexdump_stdin(&mut pipe, Some(marker)); |
| 1124 | } else { | 1097 | } else { |
| 1125 | // `hexdump <path> TIL <marker>` — the paths are the input | 1098 | // `hexdump <path> TIL <marker>` — the paths are the input |
| 1126 | // source, truncated at the first marker line. | 1099 | // source, truncated at the first marker line. |
| @@ -1176,34 +1149,9 @@ fn hexdump_file(pipe: &mut Pipeline, path: &str) -> bool { | |||
| 1176 | ctx.alive | 1149 | ctx.alive |
| 1177 | } | 1150 | } |
| 1178 | 1151 | ||
| 1179 | fn hexdump_stdin(pipe: &mut Pipeline) { | 1152 | /// Hex-dump lines from the tty until ^C/^D (or an exact marker line, |
| 1180 | let mut ctx = HexCtx { | 1153 | /// when `marker` is Some — the marker itself is not dumped). |
| 1181 | pipe, | 1154 | fn hexdump_stdin(pipe: &mut Pipeline, marker: Option<&str>) { |
| 1182 | pos: 0, | ||
| 1183 | row: [0u8; 16], | ||
| 1184 | row_len: 0, | ||
| 1185 | alive: true, | ||
| 1186 | }; | ||
| 1187 | loop { | ||
| 1188 | match tty::read_line() { | ||
| 1189 | LineInput::Line(l) => { | ||
| 1190 | ctx.feed(l.as_str().as_bytes()); | ||
| 1191 | if !ctx.alive { | ||
| 1192 | break; | ||
| 1193 | } | ||
| 1194 | ctx.feed(b"\n"); | ||
| 1195 | if !ctx.alive { | ||
| 1196 | break; | ||
| 1197 | } | ||
| 1198 | } | ||
| 1199 | LineInput::Interrupted => break, | ||
| 1200 | LineInput::Eof => break, | ||
| 1201 | } | ||
| 1202 | } | ||
| 1203 | ctx.flush(); | ||
| 1204 | } | ||
| 1205 | |||
| 1206 | fn hexdump_stdin_until(pipe: &mut Pipeline, marker: &str) { | ||
| 1207 | let mut ctx = HexCtx { | 1155 | let mut ctx = HexCtx { |
| 1208 | pipe, | 1156 | pipe, |
| 1209 | pos: 0, | 1157 | pos: 0, |
| @@ -1214,7 +1162,7 @@ fn hexdump_stdin_until(pipe: &mut Pipeline, marker: &str) { | |||
| 1214 | loop { | 1162 | loop { |
| 1215 | match tty::read_line() { | 1163 | match tty::read_line() { |
| 1216 | LineInput::Line(l) => { | 1164 | LineInput::Line(l) => { |
| 1217 | if l.as_str() == marker { | 1165 | if marker.is_some() && l.as_str() == marker.unwrap() { |
| 1218 | break; | 1166 | break; |
| 1219 | } | 1167 | } |
| 1220 | ctx.feed(l.as_str().as_bytes()); | 1168 | ctx.feed(l.as_str().as_bytes()); |
