diff options
Diffstat (limited to '')
| -rw-r--r-- | shell/src/lib.rs | 342 |
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`). |
| 713 | fn accepts_input(cmd: &str) -> bool { | 713 | fn 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 | ||
| 717 | fn execute(s: &str) { | 723 | fn 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 | ||
| 812 | static mut LAST_EXIT_CODE: i32 = 0; | 828 | static mut LAST_EXIT_CODE: i32 = 0; |
| 813 | 829 | ||
| 830 | /// The pipeline of the running external command (None when idle). | ||
| 831 | static 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). |
| 815 | pub fn last_exit_code() -> i32 { | 834 | pub 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. |
| 839 | struct 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 | |||
| 851 | impl 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. | ||
| 886 | static 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. | ||
| 891 | fn 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. | ||
| 820 | fn abi_put_line(line: rsapi::Line) -> bool { | 902 | fn 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. |
| 826 | fn abi_get_line(_out: &mut rsapi::Line) -> rsapi::StreamResult { | 913 | fn 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). | ||
| 1080 | fn 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. | ||
| 1132 | fn 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. | ||
| 1145 | fn 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 | ||
| 974 | fn cmd_echo(p: &Parsed) { | 1246 | fn 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 | ||
| 981 | fn cmd_hello(p: &Parsed) { | 1254 | fn 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 | ||
| 1037 | fn cmd_info(p: &Parsed) { | 1311 | fn 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) { | |||
| 1047 | fn cmd_ls(p: &Parsed) { | 1322 | fn 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 | ||
| 1112 | fn cmd_cat(p: &Parsed) { | 1388 | fn 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 | ||
| 1319 | fn cmd_hexdump(p: &Parsed) { | 1596 | fn 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) { | |||
| 1699 | fn cmd_mmap(p: &Parsed) { | 1978 | fn 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) { | |||
| 1931 | fn cmd_date(p: &Parsed) { | 2212 | fn 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 | ||
| 2185 | fn cmd_jobs(p: &Parsed) { | 2467 | fn 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 { | |||
| 2440 | fn cmd_pci(p: &Parsed) { | 2723 | fn 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; |
