summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--shell/src/lib.rs133
1 files changed, 108 insertions, 25 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index bc43faf..cf46cb0 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -52,7 +52,7 @@ pub fn run() -> ! {
52 // Read one line from keyboard (tty canonical mode) 52 // Read one line from keyboard (tty canonical mode)
53 match tty::read_line() { 53 match tty::read_line() {
54 LineInput::Interrupted => continue, // ^C — fresh prompt 54 LineInput::Interrupted => continue, // ^C — fresh prompt
55 LineInput::Eof => continue, // ^D on an empty line — fresh prompt (no poweroff) 55 LineInput::Eof => continue, // ^D on an empty line — fresh prompt (no poweroff)
56 LineInput::Line(line) => { 56 LineInput::Line(line) => {
57 let trimmed = line.trim(); 57 let trimmed = line.trim();
58 if trimmed.is_empty() { 58 if trimmed.is_empty() {
@@ -132,7 +132,10 @@ fn tokenize(s: &str) -> Result<Vec<Token>, &'static str> {
132} 132}
133 133
134fn is_keyword(s: &str) -> bool { 134fn is_keyword(s: &str) -> bool {
135 matches!(s, "WRITING" | "WRITE" | "TO" | "USE" | "USING" | "TIL" | "UNTIL") 135 matches!(
136 s,
137 "WRITING" | "WRITE" | "TO" | "USE" | "USING" | "TIL" | "UNTIL"
138 )
136} 139}
137 140
138/// How the command's input is fed. Only `cat`/`hexdump`, and only when 141/// How the command's input is fed. Only `cat`/`hexdump`, and only when
@@ -189,7 +192,7 @@ fn parse(s: &str) -> Result<Parsed, String> {
189 192
190 let mut i = 1; 193 let mut i = 1;
191 let mut args: Vec<String> = Vec::new(); 194 let mut args: Vec<String> = Vec::new();
192 195
193 let mut filters: Vec<Filter> = Vec::new(); 196 let mut filters: Vec<Filter> = Vec::new();
194 let mut output: Option<Sink> = None; 197 let mut output: Option<Sink> = None;
195 let mut trailing = false; // a filter flag or output keyword has been seen 198 let mut trailing = false; // a filter flag or output keyword has been seen
@@ -204,6 +207,9 @@ fn parse(s: &str) -> Result<Parsed, String> {
204 if !t.literal { 207 if !t.literal {
205 match text { 208 match text {
206 "USE" | "USING" => { 209 "USE" | "USING" => {
210 if output.is_some() {
211 return Err("USE must come before the output keyword".into());
212 }
207 if trailing { 213 if trailing {
208 return Err("USE must come before the filters".into()); 214 return Err("USE must come before the filters".into());
209 } 215 }
@@ -220,6 +226,9 @@ fn parse(s: &str) -> Result<Parsed, String> {
220 continue; 226 continue;
221 } 227 }
222 "TIL" | "UNTIL" => { 228 "TIL" | "UNTIL" => {
229 if output.is_some() {
230 return Err("TIL must come before the output keyword".into());
231 }
223 if trailing { 232 if trailing {
224 return Err("TIL must come before the filters".into()); 233 return Err("TIL must come before the filters".into());
225 } 234 }
@@ -255,6 +264,9 @@ fn parse(s: &str) -> Result<Parsed, String> {
255 continue; 264 continue;
256 } 265 }
257 "-h" | "-t" => { 266 "-h" | "-t" => {
267 if output.is_some() {
268 return Err("filters must come before the output keyword".into());
269 }
258 trailing = true; 270 trailing = true;
259 i += 1; 271 i += 1;
260 let n = toks.get(i).ok_or("flag needs a count")?; 272 let n = toks.get(i).ok_or("flag needs a count")?;
@@ -271,6 +283,9 @@ fn parse(s: &str) -> Result<Parsed, String> {
271 continue; 283 continue;
272 } 284 }
273 "-g" => { 285 "-g" => {
286 if output.is_some() {
287 return Err("filters must come before the output keyword".into());
288 }
274 trailing = true; 289 trailing = true;
275 i += 1; 290 i += 1;
276 let pat = toks.get(i).ok_or("-g needs a pattern")?; 291 let pat = toks.get(i).ok_or("-g needs a pattern")?;
@@ -279,6 +294,9 @@ fn parse(s: &str) -> Result<Parsed, String> {
279 continue; 294 continue;
280 } 295 }
281 "-p" => { 296 "-p" => {
297 if output.is_some() {
298 return Err("filters must come before the output keyword".into());
299 }
282 trailing = true; 300 trailing = true;
283 filters.push(Filter::Pause); 301 filters.push(Filter::Pause);
284 i += 1; 302 i += 1;
@@ -324,7 +342,10 @@ enum StageState {
324 /// Rolling window of the last N lines. 342 /// Rolling window of the last N lines.
325 Tail(VecDeque<String>), 343 Tail(VecDeque<String>),
326 Grep, 344 Grep,
327 Pause { shown: usize, screen: usize }, 345 Pause {
346 shown: usize,
347 screen: usize,
348 },
328} 349}
329 350
330/// Where the filtered line stream ends up. 351/// Where the filtered line stream ends up.
@@ -473,10 +494,9 @@ impl Pipeline {
473 unreachable!("stage state does not match filter") 494 unreachable!("stage state does not match filter")
474 }; 495 };
475 for l in cur.drain(..) { 496 for l in cur.drain(..) {
476 if *shown > 0 && *shown % *screen == 0 497 if *shown > 0 && *shown % *screen == 0 && !pager_wait() {
477 && !pager_wait() { 498 return false;
478 return false; 499 }
479 }
480 *shown += 1; 500 *shown += 1;
481 out.push(l); 501 out.push(l);
482 } 502 }
@@ -529,7 +549,10 @@ impl Pipeline {
529 if let OutSink::File { h: None, .. } = &self.sink { 549 if let OutSink::File { h: None, .. } = &self.sink {
530 self.open_sink(); 550 self.open_sink();
531 } 551 }
532 if let OutSink::File { h: Some(h), pos, .. } = &mut self.sink { 552 if let OutSink::File {
553 h: Some(h), pos, ..
554 } = &mut self.sink
555 {
533 let mut bytes = l.as_bytes().to_vec(); 556 let mut bytes = l.as_bytes().to_vec();
534 bytes.push(b'\n'); 557 bytes.push(b'\n');
535 if let Ok(n) = rsnix::fs::write(*h, &bytes, *pos) { 558 if let Ok(n) = rsnix::fs::write(*h, &bytes, *pos) {
@@ -544,7 +567,9 @@ impl Pipeline {
544 /// the file's current size. 567 /// the file's current size.
545 fn open_sink(&mut self) { 568 fn open_sink(&mut self) {
546 let (path, overwrite) = match &self.sink { 569 let (path, overwrite) = match &self.sink {
547 OutSink::File { path, overwrite, .. } => (path.clone(), *overwrite), 570 OutSink::File {
571 path, overwrite, ..
572 } => (path.clone(), *overwrite),
548 _ => return, 573 _ => return,
549 }; 574 };
550 match rsnix::fs::ensure_file(&path) { 575 match rsnix::fs::ensure_file(&path) {
@@ -616,9 +641,16 @@ fn execute(s: &str) {
616 term_err(&alloc::format!("{}: does not accept input", cmd)); 641 term_err(&alloc::format!("{}: does not accept input", cmd));
617 return; 642 return;
618 } 643 }
619 if !parsed.args.is_empty() { 644 // `cat <path> TIL <marker>` is sugar for `cat USE <path> TIL
645 // <marker>`: the paths are the input source, truncated at the
646 // marker. An explicit source keyword (USE / USE..TIL) alongside
647 // path arguments is genuinely two input sources.
648 if !parsed.args.is_empty() && !matches!(parsed.input, Input::Til(_)) {
620 term_err(&alloc::format!( 649 term_err(&alloc::format!(
621 "{}: input keyword with path arguments is ambiguous", 650 "{}: input source keyword with path arguments is ambiguous — \
651the paths are the input; drop the keyword, or to truncate them at a marker \
652write `{} <path> TIL <marker>`",
653 cmd,
622 cmd 654 cmd
623 )); 655 ));
624 return; 656 return;
@@ -680,8 +712,10 @@ fn cmd_help(_p: &Parsed) {
680redirection keywords (uppercase, after args): 712redirection keywords (uppercase, after args):
681 USE/USING <src> read command input from a file (cat/hexdump only) 713 USE/USING <src> read command input from a file (cat/hexdump only)
682 TIL/UNTIL <marker> read input until an exact marker line (heredoc) 714 TIL/UNTIL <marker> read input until an exact marker line (heredoc)
715 cat <path> TIL <marker> reads the path to the marker
683 WRITING/WRITE <dst> write output to a file (overwrite) 716 WRITING/WRITE <dst> write output to a file (overwrite)
684 TO <dst> append output to a file 717 TO <dst> append output to a file
718 order: USE/TIL first, then the -flags, then WRITING/TO last
685 a file sink replaces the screen; > >> < << are gone (> is plain text) 719 a file sink replaces the screen; > >> < << are gone (> is plain text)
686 720
687post-filter flags (trailing, applied left to right): 721post-filter flags (trailing, applied left to right):
@@ -758,6 +792,14 @@ fn cmd_ls(p: &Parsed) {
758// cat — file and stream consumers 792// cat — file and stream consumers
759// --------------------------------------------------------------------------- 793// ---------------------------------------------------------------------------
760 794
795/// How a file read ended: read to EOF, stopped at the TIL marker, or
796/// aborted (^C / head kill) — the caller should stop processing paths.
797enum ReadResult {
798 Done,
799 Marker,
800 Aborted,
801}
802
761fn cmd_cat(p: &Parsed) { 803fn cmd_cat(p: &Parsed) {
762 let mut pipe = Pipeline::new(&p.filters, &p.output); 804 let mut pipe = Pipeline::new(&p.filters, &p.output);
763 match &p.input { 805 match &p.input {
@@ -778,7 +820,18 @@ fn cmd_cat(p: &Parsed) {
778 cat_file(&mut pipe, path); 820 cat_file(&mut pipe, path);
779 } 821 }
780 Input::Til(marker) => { 822 Input::Til(marker) => {
781 cat_stdin_until(&mut pipe, marker); 823 if p.args.is_empty() {
824 cat_stdin_until(&mut pipe, marker);
825 } else {
826 // `cat <path> TIL <marker>` — the paths are the input
827 // source, truncated at the first marker line.
828 for path in &p.args {
829 match cat_file_until(&mut pipe, path, marker) {
830 ReadResult::Done => {}
831 ReadResult::Marker | ReadResult::Aborted => break,
832 }
833 }
834 }
782 } 835 }
783 Input::UseTil(path, marker) => { 836 Input::UseTil(path, marker) => {
784 cat_file_until(&mut pipe, path, marker); 837 cat_file_until(&mut pipe, path, marker);
@@ -796,7 +849,7 @@ fn cat_stdin(pipe: &mut Pipeline) -> bool {
796 } 849 }
797 } 850 }
798 LineInput::Interrupted => return false, // ^C aborts the stream 851 LineInput::Interrupted => return false, // ^C aborts the stream
799 LineInput::Eof => return true, // ^D = EOF 852 LineInput::Eof => return true, // ^D = EOF
800 } 853 }
801 } 854 }
802} 855}
@@ -855,12 +908,12 @@ fn cat_file(pipe: &mut Pipeline, path: &str) -> bool {
855 true 908 true
856} 909}
857 910
858fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> bool { 911fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> ReadResult {
859 let h = match rsnix::fs::resolve(path) { 912 let h = match rsnix::fs::resolve(path) {
860 Some(h) => h, 913 Some(h) => h,
861 None => { 914 None => {
862 term_err(&alloc::format!("cat: {}: no such file", path)); 915 term_err(&alloc::format!("cat: {}: no such file", path));
863 return true; 916 return ReadResult::Done;
864 } 917 }
865 }; 918 };
866 let mut buf = [0u8; 512]; 919 let mut buf = [0u8; 512];
@@ -869,7 +922,7 @@ fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> bool {
869 let mut stopped = false; 922 let mut stopped = false;
870 'outer: loop { 923 'outer: loop {
871 if tty::interrupt_pressed() { 924 if tty::interrupt_pressed() {
872 return false; 925 return ReadResult::Aborted;
873 } 926 }
874 match rsnix::fs::read(h, &mut buf, pos) { 927 match rsnix::fs::read(h, &mut buf, pos) {
875 Ok(0) | Err(_) => break, 928 Ok(0) | Err(_) => break,
@@ -885,16 +938,20 @@ fn cat_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> bool {
885 break 'outer; 938 break 'outer;
886 } 939 }
887 if !pipe.line(line) { 940 if !pipe.line(line) {
888 return false; 941 return ReadResult::Aborted;
889 } 942 }
890 } 943 }
891 } 944 }
892 } 945 }
893 } 946 }
894 if !stopped && !pending.is_empty() && pending != marker && !pipe.line(&pending) { 947 if !stopped && !pending.is_empty() && pending != marker && !pipe.line(&pending) {
895 return false; 948 return ReadResult::Aborted;
949 }
950 if stopped {
951 ReadResult::Marker
952 } else {
953 ReadResult::Done
896 } 954 }
897 true
898} 955}
899 956
900// --------------------------------------------------------------------------- 957// ---------------------------------------------------------------------------
@@ -998,7 +1055,18 @@ fn cmd_hexdump(p: &Parsed) {
998 hexdump_file(&mut pipe, path); 1055 hexdump_file(&mut pipe, path);
999 } 1056 }
1000 Input::Til(marker) => { 1057 Input::Til(marker) => {
1001 hexdump_stdin_until(&mut pipe, marker); 1058 if p.args.is_empty() {
1059 hexdump_stdin_until(&mut pipe, marker);
1060 } else {
1061 // `hexdump <path> TIL <marker>` — the paths are the input
1062 // source, truncated at the first marker line.
1063 for path in &p.args {
1064 match hexdump_file_until(&mut pipe, path, marker) {
1065 ReadResult::Done => {}
1066 ReadResult::Marker | ReadResult::Aborted => break,
1067 }
1068 }
1069 }
1002 } 1070 }
1003 Input::UseTil(path, marker) => { 1071 Input::UseTil(path, marker) => {
1004 hexdump_file_until(&mut pipe, path, marker); 1072 hexdump_file_until(&mut pipe, path, marker);
@@ -1101,12 +1169,12 @@ fn hexdump_stdin_until(pipe: &mut Pipeline, marker: &str) {
1101 ctx.flush(); 1169 ctx.flush();
1102} 1170}
1103 1171
1104fn hexdump_file_until(pipe: &mut Pipeline, path: &str, marker: &str) { 1172fn hexdump_file_until(pipe: &mut Pipeline, path: &str, marker: &str) -> ReadResult {
1105 let h = match rsnix::fs::resolve(path) { 1173 let h = match rsnix::fs::resolve(path) {
1106 Some(h) => h, 1174 Some(h) => h,
1107 None => { 1175 None => {
1108 term_err(&alloc::format!("hexdump: {}: no such file", path)); 1176 term_err(&alloc::format!("hexdump: {}: no such file", path));
1109 return; 1177 return ReadResult::Done;
1110 } 1178 }
1111 }; 1179 };
1112 let mut ctx = HexCtx { 1180 let mut ctx = HexCtx {
@@ -1149,6 +1217,13 @@ fn hexdump_file_until(pipe: &mut Pipeline, path: &str, marker: &str) {
1149 ctx.feed(pending.as_bytes()); 1217 ctx.feed(pending.as_bytes());
1150 } 1218 }
1151 ctx.flush(); 1219 ctx.flush();
1220 if !ctx.alive {
1221 ReadResult::Aborted
1222 } else if stopped {
1223 ReadResult::Marker
1224 } else {
1225 ReadResult::Done
1226 }
1152} 1227}
1153 1228
1154// --------------------------------------------------------------------------- 1229// ---------------------------------------------------------------------------
@@ -1242,7 +1317,11 @@ fn cmd_mem(p: &Parsed) {
1242 let ps = rsnix::memory::page_stats(); 1317 let ps = rsnix::memory::page_stats();
1243 1318
1244 let mut pipe = Pipeline::new(&p.filters, &p.output); 1319 let mut pipe = Pipeline::new(&p.filters, &p.output);
1245 let _ = pipe.line(&alloc::format!("memory map (key=0x{:x}, {} entries)", key, map.len())); 1320 let _ = pipe.line(&alloc::format!(
1321 "memory map (key=0x{:x}, {} entries)",
1322 key,
1323 map.len()
1324 ));
1246 let _ = pipe.line(&alloc::format!( 1325 let _ = pipe.line(&alloc::format!(
1247 " usable {:4} MB Conventional", 1326 " usable {:4} MB Conventional",
1248 conventional_kb / 1024 1327 conventional_kb / 1024
@@ -1304,7 +1383,11 @@ fn cmd_mem(p: &Parsed) {
1304fn cmd_mmap(p: &Parsed) { 1383fn cmd_mmap(p: &Parsed) {
1305 let (map, key) = rsnix::memory::get_map(); 1384 let (map, key) = rsnix::memory::get_map();
1306 let mut pipe = Pipeline::new(&p.filters, &p.output); 1385 let mut pipe = Pipeline::new(&p.filters, &p.output);
1307 let _ = pipe.line(&alloc::format!("memory map (key=0x{:x}, {} entries):", key, map.len())); 1386 let _ = pipe.line(&alloc::format!(
1387 "memory map (key=0x{:x}, {} entries):",
1388 key,
1389 map.len()
1390 ));
1308 let _ = pipe.line(" # type start pages size"); 1391 let _ = pipe.line(" # type start pages size");
1309 for (i, d) in map.iter().enumerate() { 1392 for (i, d) in map.iter().enumerate() {
1310 let l = alloc::format!( 1393 let l = alloc::format!(