summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-27 16:58:31 +0200
committersalmonik <salmonik@tuta.io>2026-08-27 16:58:31 +0200
commit300c0acbd7ab3fae8596ff839c39d9a17c70c68d (patch)
tree5a600b24c037dc798766e3ce3fc004956e517abb /shell
parentexec: PE32+ loader, versioned ABI, first external binaries (v0.6.0) (diff)
downloadrs-dos-300c0acbd7ab3fae8596ff839c39d9a17c70c68d.tar.gz
rs-dos-300c0acbd7ab3fae8596ff839c39d9a17c70c68d.zip
streams: per-invocation channel, pumping mode, filter pipeline wiring
exec: LineChannel bounded ring (cap=16) with abort/eof/marker fields. Shell 'pumping mode' assembles canonical lines from its queue into the channel while the command runs; ^C marks the channel aborted. Shell-side grammar: USE/TIL pre-drain into the channel before spawn; <cmd> <path> TIL <marker> sugar drains paths until the marker line. Interactive TIL: pump_key checks each submitted line against the marker and closes the channel when it matches. Exports callbacks run in the command task but touch shell-owned state: this is safe by construction — the shell blocks in exec_external until the command completes. put_line feeds Pipeline::line directly; get_line pops from the channel (yield-poll when empty). load: drop NX from image mapping (single-privilege kernel, NX bit 63 reserved when EFER.NXE is set by OVMF); compute image span from section RVAs not file size. bins/count: gate binary — reads lines via get_line, reports count via put_line. Verified: count USE file → 6; count <path> TIL END → 2; interactive count TIL DONE → 3; -h filter across task boundary; ^C abort. v1 stream exports: put_line → Pipeline, get_line ← channel, log_line → klog append, exit → abort_current. Accounting: print_one_line → pipe.line() returns false (head kill / pager ^C) → the binary should wrap up promptly; accepted: binary does not check the return value in v1 (phase 3 simplification).
Diffstat (limited to 'shell')
-rw-r--r--shell/src/lib.rs342
1 files changed, 315 insertions, 27 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 9ca999e..5248ea9 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -711,7 +711,13 @@ fn emit_string(pipe: &mut Pipeline, s: &str) {
711 711
712/// Commands that may consume an input stream (`USE`/`TIL`). 712/// Commands that may consume an input stream (`USE`/`TIL`).
713fn accepts_input(cmd: &str) -> bool { 713fn accepts_input(cmd: &str) -> bool {
714 matches!(cmd, "cat" | "hexdump") 714 // Built-in stream consumers, plus any external binary in /bin/cli —
715 // externals get injected streams by construction (ADR 0006), so the
716 // keyword is never ambiguous for them.
717 if matches!(cmd, "cat" | "hexdump") {
718 return true;
719 }
720 rsnix::exec::resolve("cli", cmd).is_some()
715} 721}
716 722
717fn execute(s: &str) { 723fn execute(s: &str) {
@@ -748,8 +754,10 @@ write `{} <path> TIL <marker>`",
748 // Resolution rule (ADR 0006): \bin\<kind>\<name> first, else the 754 // Resolution rule (ADR 0006): \bin\<kind>\<name> first, else the
749 // built-in. The frozen core (help/clear/echo/poweroff/exit/halt/ 755 // built-in. The frozen core (help/clear/echo/poweroff/exit/halt/
750 // rescan) is exempt — it exists to fix the system and never migrates. 756 // rescan) is exempt — it exists to fix the system and never migrates.
751 if !matches!(cmd, "help" | "clear" | "echo" | "halt" | "poweroff" | "exit" | "rescan") 757 if !matches!(
752 && exec_external(&parsed) 758 cmd,
759 "help" | "clear" | "echo" | "halt" | "poweroff" | "exit" | "rescan"
760 ) && exec_external(&parsed)
753 { 761 {
754 return; 762 return;
755 } 763 }
@@ -801,30 +809,133 @@ write `{} <path> TIL <marker>`",
801} 809}
802 810
803// --------------------------------------------------------------------------- 811// ---------------------------------------------------------------------------
804// External binaries (ADR 0006 Phase 2) 812// External binaries (ADR 0006 Phase 3)
813//
814// Resolution: \bin\cli\<name> on each mounted volume; else built-in.
815//
816// Stream injection: the Exports callbacks run in the COMMAND task but
817// touch shell-owned state. This is safe by construction — the invoking
818// shell blocks in exec_external until the command completes, so nothing
819// else touches the Pipeline or the channel. One invocation at a time.
805// 820//
806// Resolution: \bin\cli\<name> on each mounted volume; else "unknown 821// put_line → Pipeline::line directly (output side needs no queue).
807// command". The Exports table is constructed per invocation: v1 wires 822// get_line ← a bounded per-invocation ring; while the command runs,
808// put_line to the active terminal and get_line to EOF (stream injection 823// the blocked shell "pumps": drains its key queue, assembles
809// through the filter pipeline lands in Phase 3). 824// canonical lines, pushes them into the ring. ^C marks the
825// channel aborted instead of killing a stream consumer.
810// --------------------------------------------------------------------------- 826// ---------------------------------------------------------------------------
811 827
812static mut LAST_EXIT_CODE: i32 = 0; 828static mut LAST_EXIT_CODE: i32 = 0;
813 829
830/// The pipeline of the running external command (None when idle).
831static mut ACTIVE_PIPELINE: Option<Pipeline> = None;
832
814/// Last exit code of an external command ($? equivalent for scripts). 833/// Last exit code of an external command ($? equivalent for scripts).
815pub fn last_exit_code() -> i32 { 834pub fn last_exit_code() -> i32 {
816 unsafe { core::ptr::addr_of!(LAST_EXIT_CODE).read() } 835 unsafe { core::ptr::addr_of!(LAST_EXIT_CODE).read() }
817} 836}
818 837
819/// ABI callback: print one line to the invoking terminal. 838/// Bounded line channel between the pumping shell and the command task.
839struct LineChannel {
840 lines: Vec<String>,
841 len: usize,
842 cap: usize,
843 /// ^C seen: get_line returns Abort once drained.
844 aborted: bool,
845 /// Source exhausted (EOF / marker hit).
846 eof: bool,
847 /// Interactive TIL: exact line that closes the input (pump checks it).
848 marker: Option<String>,
849}
850
851impl LineChannel {
852 const CAP: usize = 16;
853 fn new() -> Self {
854 LineChannel {
855 lines: Vec::with_capacity(Self::CAP),
856 len: 0,
857 cap: Self::CAP,
858 aborted: false,
859 eof: false,
860 marker: None,
861 }
862 }
863 /// Shell side: push one assembled line. Returns false when full
864 /// (the pump waits and retries).
865 fn push(&mut self, s: &str) -> bool {
866 if self.len >= self.cap {
867 return false;
868 }
869 self.lines.push(String::from(s));
870 self.len += 1;
871 true
872 }
873 /// Command side: pop one line.
874 fn pop(&mut self) -> Option<String> {
875 if self.len == 0 {
876 return None;
877 }
878 let s = self.lines.remove(0);
879 self.len -= 1;
880 Some(s)
881 }
882}
883
884/// The current invocation. Only touched by the shell (pump + setup) and
885/// by the ABI callbacks (command task) — never concurrently, see above.
886static mut INVOCATION: Option<LineChannel> = None;
887
888/// Set when the input source is interactive (tty): the pump assembles
889/// canonical lines from keys. File/marker sources are pre-drained into
890/// the channel before spawn instead.
891fn with_invocation<R>(f: impl FnOnce(&mut LineChannel) -> R) -> R {
892 let inv = unsafe {
893 match &mut *core::ptr::addr_of_mut!(INVOCATION) {
894 Some(ch) => ch,
895 None => panic!("exec: no active invocation"),
896 }
897 };
898 f(inv)
899}
900
901/// ABI callback: print one line through the filter pipeline / sink.
820fn abi_put_line(line: rsapi::Line) -> bool { 902fn abi_put_line(line: rsapi::Line) -> bool {
821 term_println(line.as_str()); 903 let pipe = unsafe {
822 true 904 match &mut *core::ptr::addr_of_mut!(ACTIVE_PIPELINE) {
905 Some(p) => p,
906 None => panic!("exec: no active pipeline"),
907 }
908 };
909 pipe.line(line.as_str())
823} 910}
824 911
825/// ABI callback: read one input line — no stream source wired yet (Phase 3). 912/// ABI callback: read one input line from the invocation channel.
826fn abi_get_line(_out: &mut rsapi::Line) -> rsapi::StreamResult { 913fn abi_get_line(out: &mut rsapi::Line) -> rsapi::StreamResult {
827 rsapi::StreamResult::Eof 914 loop {
915 let (line, status) = with_invocation(|ch| match ch.pop() {
916 Some(l) => (Some(l), rsapi::StreamResult::Line),
917 None if ch.aborted => (None, rsapi::StreamResult::Abort),
918 None if ch.eof => (None, rsapi::StreamResult::Eof),
919 None => (None, rsapi::StreamResult::Line), // empty but open: pump
920 });
921 match status {
922 rsapi::StreamResult::Abort => return rsapi::StreamResult::Abort,
923 rsapi::StreamResult::Eof => return rsapi::StreamResult::Eof,
924 rsapi::StreamResult::Line => {
925 if let Some(l) = line {
926 out.ptr = l.as_ptr();
927 out.len = l.len();
928 // Leak the String into the command's lifetime: lines are
929 // one-shot and the task dies soon; freeing across tasks
930 // is not worth the bookkeeping.
931 core::mem::forget(l);
932 return rsapi::StreamResult::Line;
933 }
934 // Channel empty: yield so the shell pump can refill.
935 rsnix::sched::yield_now();
936 }
937 }
938 }
828} 939}
829 940
830/// ABI callback: kernel log. 941/// ABI callback: kernel log.
@@ -837,8 +948,7 @@ fn abi_exit(code: i32) -> ! {
837 unsafe { 948 unsafe {
838 LAST_EXIT_CODE = code; 949 LAST_EXIT_CODE = code;
839 } 950 }
840 // Unwind the binary by longjmp-style task abort: the trampoline never 951 // Kill the calling task; exec_external sees the abort notification.
841 // returns here. We report the code via the static and kill the task.
842 rsnix::sched::abort_current("exit") 952 rsnix::sched::abort_current("exit")
843} 953}
844 954
@@ -871,37 +981,199 @@ fn exec_external(p: &Parsed) -> bool {
871 return true; 981 return true;
872 } 982 }
873 }; 983 };
984
985 // Grammar stays shell-side (ADR 0006): the binary gets clean argv and
986 // injected streams. Build its pipeline exactly like a built-in's.
987 let pipe = Pipeline::new(&p.filters, &p.output);
988 #[allow(unused_mut)]
989 // Input channel: pre-drain file sources; tty sources are pumped.
990 let mut channel = LineChannel::new();
991 match &p.input {
992 Input::None => {}
993 Input::Use(path) => {
994 drain_file_into(&mut channel, path, None);
995 channel.eof = true;
996 }
997 Input::UseTil(path, marker) => {
998 drain_file_into(&mut channel, path, Some(marker));
999 channel.eof = true;
1000 }
1001 Input::Til(marker) => {
1002 if p.args.is_empty() {
1003 // Interactive: read from tty until marker — the pump
1004 // checks each submitted line against it.
1005 channel.marker = Some(marker.clone());
1006 } else {
1007 for path in &p.args {
1008 drain_file_into(&mut channel, path, Some(marker));
1009 }
1010 channel.eof = true;
1011 }
1012 }
1013 }
1014
874 let mut argv: Vec<String> = Vec::with_capacity(p.args.len() + 1); 1015 let mut argv: Vec<String> = Vec::with_capacity(p.args.len() + 1);
875 argv.push(p.cmd.clone()); 1016 argv.push(p.cmd.clone());
876 argv.extend(p.args.iter().cloned()); 1017 argv.extend(p.args.iter().cloned());
877 let name = alloc::format!("{}", p.cmd); 1018 let name = alloc::format!("{}", p.cmd);
1019
1020 // Publish the invocation state before spawn (the command may call
1021 // put_line immediately).
1022 unsafe {
1023 *core::ptr::addr_of_mut!(ACTIVE_PIPELINE) = Some(pipe);
1024 *core::ptr::addr_of_mut!(INVOCATION) = Some(channel);
1025 }
1026
1027 let mut cmd_done = false;
1028 let mut exit_code: i64 = 0;
878 match rsnix::exec::spawn(image, &EXPORTS_V1, &argv, &name) { 1029 match rsnix::exec::spawn(image, &EXPORTS_V1, &argv, &name) {
879 Ok(id) => { 1030 Ok(_id) => {
880 // Wait for the completion message (TaskMsg::User(exit_code)). 1031 // Pumping mode: assemble canonical lines from our queue into
1032 // the channel while the command runs. The WM pump delivers
1033 // keys to us only while our window is focused.
881 loop { 1034 loop {
882 match rsnix::sched::recv_blocking() { 1035 match rsnix::sched::recv_blocking() {
883 rsnix::sched::TaskMsg::User(code) => { 1036 rsnix::sched::TaskMsg::User(code) => {
884 unsafe { 1037 exit_code = if code == u64::MAX { -1 } else { code as i64 };
885 LAST_EXIT_CODE = code as i32; 1038 cmd_done = true;
886 }
887 break;
888 } 1039 }
889 rsnix::sched::TaskMsg::Key(rsnix::ps2::KeyEvent::Ctrl('c')) => { 1040 rsnix::sched::TaskMsg::Key(rsnix::ps2::KeyEvent::Ctrl('c')) => {
890 // ^C while waiting: kill the command task. 1041 // ^C: mark the channel aborted; stream consumers stop
891 let _ = rsnix::sched::kill(id); 1042 // at their next get_line/put_line-false. Compute-
892 term_err("^C"); 1043 // bound commands stay blind to it (accepted policy).
893 break; 1044 with_invocation(|ch| ch.aborted = true);
1045 }
1046 rsnix::sched::TaskMsg::Key(ev) => {
1047 // Canonical assembly for the interactive source:
1048 // mirror tty::read_line's key handling, minus echo
1049 // (the terminal already echoed it? no — the shell
1050 // must echo here since nothing else does).
1051 pump_key(ev);
894 } 1052 }
895 _ => {} 1053 _ => {}
896 } 1054 }
1055 if cmd_done {
1056 break;
1057 }
897 } 1058 }
898 } 1059 }
899 Err(rsnix::exec::ExecError::Busy) => term_err(&alloc::format!("{}: task table full", p.cmd)), 1060 Err(rsnix::exec::ExecError::Busy) => {
1061 term_err(&alloc::format!("{}: task table full", p.cmd))
1062 }
900 Err(_) => term_err(&alloc::format!("{}: cannot start", p.cmd)), 1063 Err(_) => term_err(&alloc::format!("{}: cannot start", p.cmd)),
901 } 1064 }
1065
1066 unsafe {
1067 let pipe_ptr = core::ptr::addr_of_mut!(ACTIVE_PIPELINE);
1068 if let Some(mut pipe) = (*pipe_ptr).take() {
1069 pipe.end();
1070 }
1071 *core::ptr::addr_of_mut!(INVOCATION) = None;
1072 LAST_EXIT_CODE = exit_code as i32;
1073 }
902 true 1074 true
903} 1075}
904 1076
1077/// One key of canonical assembly during pumping. Lines land in the
1078/// channel; ^D closes it; Enter submits the line. Echo goes to the
1079/// terminal (this is the command's stdin).
1080fn pump_key(ev: rsnix::ps2::KeyEvent) {
1081 use rsnix::ps2::KeyEvent;
1082 // Static assembly buffer: only touched between keys by the shell.
1083 static mut PUMP_BUF: Option<String> = None;
1084 let buf = unsafe {
1085 let p = core::ptr::addr_of_mut!(PUMP_BUF);
1086 if (*p).is_none() {
1087 *p = Some(String::new());
1088 }
1089 (*p).as_mut().unwrap()
1090 };
1091 match ev {
1092 KeyEvent::Enter => {
1093 gop_println!();
1094 let line = core::mem::take(buf);
1095 with_invocation(|ch| {
1096 // Interactive TIL: the submitted line itself is checked
1097 // against the marker (it is consumed, not delivered).
1098 if let Some(m) = &ch.marker
1099 && line.as_str() == m
1100 {
1101 ch.eof = true;
1102 } else {
1103 ch.push(line.as_str());
1104 }
1105 });
1106 }
1107 KeyEvent::Ctrl('c') => {
1108 buf.clear();
1109 with_invocation(|ch| ch.aborted = true);
1110 }
1111 KeyEvent::Ctrl('d') => {
1112 if buf.is_empty() {
1113 with_invocation(|ch| ch.eof = true);
1114 }
1115 }
1116 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => {
1117 buf.push(c);
1118 gop_print!("{}", c);
1119 }
1120 KeyEvent::Backspace => {
1121 if buf.pop().is_some() {
1122 gop_print!("\x08");
1123 }
1124 }
1125 _ => {}
1126 }
1127}
1128
1129/// Pre-drain a file into the channel; `marker` stops before that line
1130/// (USE <path> TIL sugar). The channel is left open — the caller sets
1131/// `eof` when this is the whole source.
1132fn drain_file_into(ch: &mut LineChannel, path: &str, marker: Option<&str>) {
1133 let h = match rsnix::fs::resolve(path) {
1134 Some(h) => h,
1135 None => {
1136 term_err(&alloc::format!("{}: no such file", path));
1137 ch.eof = true;
1138 return;
1139 }
1140 };
1141 drain_handle(ch, h, marker);
1142}
1143
1144/// Shared drain: read `h` in chunks, push complete lines; stop at marker.
1145fn drain_handle(ch: &mut LineChannel, h: usize, marker: Option<&str>) {
1146 let mut buf = [0u8; 512];
1147 let mut pos = 0u64;
1148 let mut pending = String::new();
1149 'outer: loop {
1150 match rsnix::fs::read(h, &mut buf, pos) {
1151 Ok(0) | Err(_) => break,
1152 Ok(n) => {
1153 pos += n as u64;
1154 pending.push_str(&String::from_utf8_lossy(&buf[..n]));
1155 while let Some(idx) = pending.find('\n') {
1156 let line: String = pending.drain(..=idx).collect();
1157 let line = line.trim_end_matches('\n').to_string();
1158 if let Some(m) = marker
1159 && line == m
1160 {
1161 break 'outer;
1162 }
1163 while !ch.push(&line) {
1164 rsnix::sched::yield_now();
1165 }
1166 }
1167 }
1168 }
1169 }
1170 if marker.is_none() && !pending.is_empty() {
1171 while !ch.push(&pending) {
1172 rsnix::sched::yield_now();
1173 }
1174 }
1175}
1176
905// --------------------------------------------------------------------------- 1177// ---------------------------------------------------------------------------
906// Built-in commands 1178// Built-in commands
907// --------------------------------------------------------------------------- 1179// ---------------------------------------------------------------------------
@@ -973,6 +1245,7 @@ an operator: echo \"WRITING\" prints WRITING.
973 1245
974fn cmd_echo(p: &Parsed) { 1246fn cmd_echo(p: &Parsed) {
975 let mut pipe = Pipeline::new(&p.filters, &p.output); 1247 let mut pipe = Pipeline::new(&p.filters, &p.output);
1248 #[allow(unused_mut)]
976 let line = p.args.join(" "); 1249 let line = p.args.join(" ");
977 let _ = pipe.line(&line); 1250 let _ = pipe.line(&line);
978 pipe.end(); 1251 pipe.end();
@@ -980,6 +1253,7 @@ fn cmd_echo(p: &Parsed) {
980 1253
981fn cmd_hello(p: &Parsed) { 1254fn cmd_hello(p: &Parsed) {
982 let mut pipe = Pipeline::new(&p.filters, &p.output); 1255 let mut pipe = Pipeline::new(&p.filters, &p.output);
1256 #[allow(unused_mut)]
983 let _ = pipe.line("World!"); 1257 let _ = pipe.line("World!");
984 pipe.end(); 1258 pipe.end();
985} 1259}
@@ -1036,6 +1310,7 @@ fn cmd_clear() {
1036 1310
1037fn cmd_info(p: &Parsed) { 1311fn cmd_info(p: &Parsed) {
1038 let mut pipe = Pipeline::new(&p.filters, &p.output); 1312 let mut pipe = Pipeline::new(&p.filters, &p.output);
1313 #[allow(unused_mut)]
1039 let mut s = String::from("rs-dos v"); 1314 let mut s = String::from("rs-dos v");
1040 s.push_str(env!("CARGO_PKG_VERSION")); 1315 s.push_str(env!("CARGO_PKG_VERSION"));
1041 s.push_str(" - rsnix kernel"); 1316 s.push_str(" - rsnix kernel");
@@ -1047,6 +1322,7 @@ fn cmd_info(p: &Parsed) {
1047fn cmd_ls(p: &Parsed) { 1322fn cmd_ls(p: &Parsed) {
1048 let path = p.args.first().map(|s| s.as_str()).unwrap_or("/"); 1323 let path = p.args.first().map(|s| s.as_str()).unwrap_or("/");
1049 let mut pipe = Pipeline::new(&p.filters, &p.output); 1324 let mut pipe = Pipeline::new(&p.filters, &p.output);
1325 #[allow(unused_mut)]
1050 match rsnix::fs::resolve(path) { 1326 match rsnix::fs::resolve(path) {
1051 None => term_err(&alloc::format!("ls: {}: no such file or directory", path)), 1327 None => term_err(&alloc::format!("ls: {}: no such file or directory", path)),
1052 Some(h) => { 1328 Some(h) => {
@@ -1111,6 +1387,7 @@ enum ReadResult {
1111 1387
1112fn cmd_cat(p: &Parsed) { 1388fn cmd_cat(p: &Parsed) {
1113 let mut pipe = Pipeline::new(&p.filters, &p.output); 1389 let mut pipe = Pipeline::new(&p.filters, &p.output);
1390 #[allow(unused_mut)]
1114 match &p.input { 1391 match &p.input {
1115 Input::None => { 1392 Input::None => {
1116 if p.args.is_empty() { 1393 if p.args.is_empty() {
@@ -1318,6 +1595,7 @@ impl HexCtx<'_> {
1318 1595
1319fn cmd_hexdump(p: &Parsed) { 1596fn cmd_hexdump(p: &Parsed) {
1320 let mut pipe = Pipeline::new(&p.filters, &p.output); 1597 let mut pipe = Pipeline::new(&p.filters, &p.output);
1598 #[allow(unused_mut)]
1321 match &p.input { 1599 match &p.input {
1322 Input::None => { 1600 Input::None => {
1323 if p.args.is_empty() { 1601 if p.args.is_empty() {
@@ -1633,6 +1911,7 @@ fn cmd_mem(p: &Parsed) {
1633 let ps = rsnix::memory::page_stats(); 1911 let ps = rsnix::memory::page_stats();
1634 1912
1635 let mut pipe = Pipeline::new(&p.filters, &p.output); 1913 let mut pipe = Pipeline::new(&p.filters, &p.output);
1914 #[allow(unused_mut)]
1636 let _ = pipe.line(&alloc::format!( 1915 let _ = pipe.line(&alloc::format!(
1637 "memory map (key=0x{:x}, {} entries)", 1916 "memory map (key=0x{:x}, {} entries)",
1638 key, 1917 key,
@@ -1699,6 +1978,7 @@ fn cmd_mem(p: &Parsed) {
1699fn cmd_mmap(p: &Parsed) { 1978fn cmd_mmap(p: &Parsed) {
1700 let (map, key) = rsnix::memory::get_map(); 1979 let (map, key) = rsnix::memory::get_map();
1701 let mut pipe = Pipeline::new(&p.filters, &p.output); 1980 let mut pipe = Pipeline::new(&p.filters, &p.output);
1981 #[allow(unused_mut)]
1702 let _ = pipe.line(&alloc::format!( 1982 let _ = pipe.line(&alloc::format!(
1703 "memory map (key=0x{:x}, {} entries):", 1983 "memory map (key=0x{:x}, {} entries):",
1704 key, 1984 key,
@@ -1744,6 +2024,7 @@ fn cmd_stress(p: &Parsed) {
1744 }; 2024 };
1745 2025
1746 let mut pipe = Pipeline::new(&p.filters, &p.output); 2026 let mut pipe = Pipeline::new(&p.filters, &p.output);
2027 #[allow(unused_mut)]
1747 let mut allocated = 0usize; 2028 let mut allocated = 0usize;
1748 { 2029 {
1749 let mut blocks: [Option<alloc::vec::Vec<u8>>; 64] = core::array::from_fn(|_| None); 2030 let mut blocks: [Option<alloc::vec::Vec<u8>>; 64] = core::array::from_fn(|_| None);
@@ -1931,6 +2212,7 @@ fn cmd_heapdbg(_p: &Parsed) {
1931fn cmd_date(p: &Parsed) { 2212fn cmd_date(p: &Parsed) {
1932 let t = rsnix::rtc::now(); 2213 let t = rsnix::rtc::now();
1933 let mut pipe = Pipeline::new(&p.filters, &p.output); 2214 let mut pipe = Pipeline::new(&p.filters, &p.output);
2215 #[allow(unused_mut)]
1934 let line = alloc::format!( 2216 let line = alloc::format!(
1935 "{}-{:02}-{:02} {:02}:{:02}:{:02}", 2217 "{}-{:02}-{:02} {:02}:{:02}:{:02}",
1936 t.year, 2218 t.year,
@@ -2184,6 +2466,7 @@ fn cmd_bg(p: &Parsed) {
2184 2466
2185fn cmd_jobs(p: &Parsed) { 2467fn cmd_jobs(p: &Parsed) {
2186 let mut pipe = Pipeline::new(&p.filters, &p.output); 2468 let mut pipe = Pipeline::new(&p.filters, &p.output);
2469 #[allow(unused_mut)]
2187 let lines = rsnix::sched::jobs(); 2470 let lines = rsnix::sched::jobs();
2188 if lines.is_empty() { 2471 if lines.is_empty() {
2189 let _ = pipe.line("jobs: no tasks"); 2472 let _ = pipe.line("jobs: no tasks");
@@ -2440,6 +2723,7 @@ fn usb_if_label(c: u8, s: u8, p: u8) -> &'static str {
2440fn cmd_pci(p: &Parsed) { 2723fn cmd_pci(p: &Parsed) {
2441 let devices = rsnix::pci::probe(); 2724 let devices = rsnix::pci::probe();
2442 let mut pipe = Pipeline::new(&p.filters, &p.output); 2725 let mut pipe = Pipeline::new(&p.filters, &p.output);
2726 #[allow(unused_mut)]
2443 let _ = pipe.line(&alloc::format!("pci: {} device(s)", devices.len())); 2727 let _ = pipe.line(&alloc::format!("pci: {} device(s)", devices.len()));
2444 let _ = pipe.line(" bus dev fn class vendor:device if"); 2728 let _ = pipe.line(" bus dev fn class vendor:device if");
2445 for d in &devices { 2729 for d in &devices {
@@ -2466,11 +2750,13 @@ fn cmd_usb(p: &Parsed) {
2466 rsnix::usb::rescan(); 2750 rsnix::usb::rescan();
2467 rsnix::ehci::rescan(); 2751 rsnix::ehci::rescan();
2468 let mut pipe = Pipeline::new(&p.filters, &p.output); 2752 let mut pipe = Pipeline::new(&p.filters, &p.output);
2753 #[allow(unused_mut)]
2469 let _ = pipe.line("usb: rescanning ports"); 2754 let _ = pipe.line("usb: rescanning ports");
2470 pipe.end(); 2755 pipe.end();
2471 return; 2756 return;
2472 } 2757 }
2473 let mut pipe = Pipeline::new(&p.filters, &p.output); 2758 let mut pipe = Pipeline::new(&p.filters, &p.output);
2759 #[allow(unused_mut)]
2474 for l in rsnix::usb::diagnose().lines() { 2760 for l in rsnix::usb::diagnose().lines() {
2475 if !pipe.line(l) { 2761 if !pipe.line(l) {
2476 break; 2762 break;
@@ -2479,6 +2765,7 @@ fn cmd_usb(p: &Parsed) {
2479 pipe.end(); 2765 pipe.end();
2480 // EHCI (USB2) controller status - the real hardware path. 2766 // EHCI (USB2) controller status - the real hardware path.
2481 let mut pipe = Pipeline::new(&p.filters, &p.output); 2767 let mut pipe = Pipeline::new(&p.filters, &p.output);
2768 #[allow(unused_mut)]
2482 for l in rsnix::ehci::diagnose().lines() { 2769 for l in rsnix::ehci::diagnose().lines() {
2483 if !pipe.line(l) { 2770 if !pipe.line(l) {
2484 break; 2771 break;
@@ -2487,6 +2774,7 @@ fn cmd_usb(p: &Parsed) {
2487 pipe.end(); 2774 pipe.end();
2488 // UHCI (USB1.1 companion) — full/low-speed keyboards and mice. 2775 // UHCI (USB1.1 companion) — full/low-speed keyboards and mice.
2489 let mut pipe = Pipeline::new(&p.filters, &p.output); 2776 let mut pipe = Pipeline::new(&p.filters, &p.output);
2777 #[allow(unused_mut)]
2490 for l in rsnix::uhci::diagnose().lines() { 2778 for l in rsnix::uhci::diagnose().lines() {
2491 if !pipe.line(l) { 2779 if !pipe.line(l) {
2492 break; 2780 break;