summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--shell/src/lib.rs173
1 files changed, 116 insertions, 57 deletions
diff --git a/shell/src/lib.rs b/shell/src/lib.rs
index 7e1439e..299956d 100644
--- a/shell/src/lib.rs
+++ b/shell/src/lib.rs
@@ -28,57 +28,91 @@ pub fn run() -> ! {
28 serial_print!("> "); 28 serial_print!("> ");
29 29
30 // Read one line from keyboard 30 // Read one line from keyboard
31 let line = read_line(); 31 match read_line() {
32 32 LineInput::Interrupted => continue, // ^C — fresh prompt
33 // Parse and execute 33 LineInput::Eof => {
34 let trimmed = line.trim(); 34 // ^D on an empty line: EOF, exit the shell.
35 if trimmed.is_empty() { 35 cmd_poweroff();
36 continue;
37 }
38
39 let (cmd, args) = split_first_word(trimmed);
40
41 match cmd {
42 "help" => cmd_help(),
43 "echo" => cmd_echo(args),
44 "clear" => cmd_clear(),
45 "halt" => cmd_halt(),
46 "poweroff" | "exit" => cmd_poweroff(),
47 "hello" => {
48 gop_println!("World!");
49 serial_println!("World!");
50 } 36 }
51 "info" => cmd_info(), 37 LineInput::Line(line) => {
52 "mem" => cmd_mem(), 38 // Parse and execute
53 "mmap" => cmd_mmap(), 39 let trimmed = line.trim();
54 "stress" => cmd_stress(args), 40 if trimmed.is_empty() {
55 "date" => cmd_date(), 41 continue;
56 "pci" => cmd_pci(), 42 }
57 "ls" => cmd_ls(args), 43
58 "cat" => cmd_cat(args), 44 let (cmd, args) = split_first_word(trimmed);
59 "hexdump" => cmd_hexdump(args), 45
60 "mkdir" => cmd_mkdir(args), 46 match cmd {
61 "touch" => cmd_touch(args), 47 "help" => cmd_help(),
62 "rm" => cmd_rm(args), 48 "echo" => cmd_echo(args),
63 _ => { 49 "clear" => cmd_clear(),
64 gop_println!("unknown command: {}", cmd); 50 "halt" => cmd_halt(),
65 serial_println!("unknown command: {}", cmd); 51 "poweroff" | "exit" => cmd_poweroff(),
52 "hello" => {
53 gop_println!("World!");
54 serial_println!("World!");
55 }
56 "info" => cmd_info(),
57 "mem" => cmd_mem(),
58 "mmap" => cmd_mmap(),
59 "stress" => cmd_stress(args),
60 "date" => cmd_date(),
61 "pci" => cmd_pci(),
62 "ls" => cmd_ls(args),
63 "cat" => cmd_cat(args),
64 "hexdump" => cmd_hexdump(args),
65 "mkdir" => cmd_mkdir(args),
66 "touch" => cmd_touch(args),
67 "rm" => cmd_rm(args),
68 _ => {
69 gop_println!("unknown command: {}", cmd);
70 serial_println!("unknown command: {}", cmd);
71 }
72 }
66 } 73 }
67 } 74 }
68 } 75 }
69} 76}
70 77
78/// Result of one line-input session.
79enum LineInput {
80 Line(heapless_str::String<LINE_BUF_SIZE>),
81 Interrupted, // ^C
82 Eof, // ^D on an empty line
83}
84
71// --------------------------------------------------------------------------- 85// ---------------------------------------------------------------------------
72// Line input (PS/2 keyboard, no heap) 86// Line input (PS/2 keyboard, no heap)
73// --------------------------------------------------------------------------- 87// ---------------------------------------------------------------------------
74fn read_line() -> heapless_str::String<LINE_BUF_SIZE> { 88fn read_line() -> LineInput {
75 let mut buf = heapless_str::String::<LINE_BUF_SIZE>::new(); 89 let mut buf = heapless_str::String::<LINE_BUF_SIZE>::new();
76 loop { 90 loop {
77 match ps2::poll_key() { 91 match ps2::poll_key() {
78 KeyEvent::Enter => { 92 KeyEvent::Enter => {
79 rsnix::gop::put_char('\n'); 93 rsnix::gop::put_char('\n');
80 rsnix::serial::write_str("\r\n"); 94 rsnix::serial::write_str("\r\n");
81 return buf; 95 return LineInput::Line(buf);
96 }
97 KeyEvent::CtrlC => {
98 // Cancel the current line — like a real terminal, echo ^C.
99 buf.clear();
100 rsnix::gop::put_char('^');
101 rsnix::gop::put_char('C');
102 rsnix::gop::put_char('\n');
103 rsnix::serial::write_str("^C\r\n");
104 return LineInput::Interrupted;
105 }
106 KeyEvent::CtrlD => {
107 if buf.is_empty() {
108 // EOF on an empty line — exit the shell.
109 rsnix::gop::put_char('^');
110 rsnix::gop::put_char('D');
111 rsnix::gop::put_char('\n');
112 rsnix::serial::write_str("^D\r\n");
113 return LineInput::Eof;
114 }
115 // ^D with text on the line: ignored (bash semantics).
82 } 116 }
83 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { 117 KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => {
84 if buf.push(c).is_ok() { 118 if buf.push(c).is_ok() {
@@ -163,8 +197,7 @@ fn cmd_echo(args: &str) {
163 let content = alloc::format!("{}\n", text.trim_end()); 197 let content = alloc::format!("{}\n", text.trim_end());
164 let result = rsnix::fs::ensure_file(path).and_then(|h| { 198 let result = rsnix::fs::ensure_file(path).and_then(|h| {
165 if append { 199 if append {
166 rsnix::fs::write(h, content.as_bytes(), rsnix::fs::size(h)) 200 rsnix::fs::write(h, content.as_bytes(), rsnix::fs::size(h)).map(|_| ())
167 .map(|_| ())
168 } else { 201 } else {
169 let _ = rsnix::fs::set_len(h, 0); 202 let _ = rsnix::fs::set_len(h, 0);
170 rsnix::fs::write(h, content.as_bytes(), 0).map(|_| ()) 203 rsnix::fs::write(h, content.as_bytes(), 0).map(|_| ())
@@ -204,9 +237,7 @@ fn cmd_ls(args: &str) {
204 for (name, kind, size) in list { 237 for (name, kind, size) in list {
205 match kind { 238 match kind {
206 rsnix::fs::FileType::Dir => out.push_str(&alloc::format!("{}/\n", name)), 239 rsnix::fs::FileType::Dir => out.push_str(&alloc::format!("{}/\n", name)),
207 rsnix::fs::FileType::Char => { 240 rsnix::fs::FileType::Char => out.push_str(&alloc::format!("{}@\n", name)),
208 out.push_str(&alloc::format!("{}@\n", name))
209 }
210 rsnix::fs::FileType::Regular => { 241 rsnix::fs::FileType::Regular => {
211 out.push_str(&alloc::format!("{} {} B\n", name, size)) 242 out.push_str(&alloc::format!("{} {} B\n", name, size))
212 } 243 }
@@ -225,18 +256,28 @@ fn cmd_cat(args: &str) {
225 serial_println!("usage: cat <path> [path...]"); 256 serial_println!("usage: cat <path> [path...]");
226 return; 257 return;
227 } 258 }
228 let mut out = alloc::string::String::new();
229 let mut buf = [0u8; 512]; 259 let mut buf = [0u8; 512];
230 for p in paths { 260 for p in paths {
231 match rsnix::fs::resolve(p) { 261 match rsnix::fs::resolve(p) {
232 None => out.push_str(&alloc::format!("cat: {}: no such file\n", p)), 262 None => {
263 let msg = alloc::format!("cat: {}: no such file\n", p);
264 gop_print!("{}", msg);
265 serial_print!("{}", msg);
266 }
233 Some(h) => { 267 Some(h) => {
234 let mut pos = 0u64; 268 let mut pos = 0u64;
235 loop { 269 loop {
270 if interrupt_pressed() {
271 gop_println!("^C");
272 serial_println!("^C");
273 return;
274 }
236 match rsnix::fs::read(h, &mut buf, pos) { 275 match rsnix::fs::read(h, &mut buf, pos) {
237 Ok(0) | Err(_) => break, 276 Ok(0) | Err(_) => break,
238 Ok(n) => { 277 Ok(n) => {
239 out.push_str(core::str::from_utf8(&buf[..n]).unwrap_or("?")); 278 let chunk = core::str::from_utf8(&buf[..n]).unwrap_or("?");
279 gop_print!("{}", chunk);
280 serial_print!("{}", chunk);
240 pos += n as u64; 281 pos += n as u64;
241 } 282 }
242 } 283 }
@@ -244,8 +285,6 @@ fn cmd_cat(args: &str) {
244 } 285 }
245 } 286 }
246 } 287 }
247 gop_print!("{}", out);
248 serial_print!("{}", out);
249} 288}
250 289
251fn cmd_hexdump(args: &str) { 290fn cmd_hexdump(args: &str) {
@@ -263,15 +302,20 @@ fn cmd_hexdump(args: &str) {
263 return; 302 return;
264 } 303 }
265 }; 304 };
266 let mut out = alloc::string::String::new();
267 let mut buf = [0u8; 512]; 305 let mut buf = [0u8; 512];
268 let mut pos = 0u64; 306 let mut pos = 0u64;
269 use core::fmt::Write as _; 307 loop {
270 'outer: loop { 308 if interrupt_pressed() {
309 gop_println!("^C");
310 serial_println!("^C");
311 return;
312 }
271 let n = match rsnix::fs::read(h, &mut buf, pos) { 313 let n = match rsnix::fs::read(h, &mut buf, pos) {
272 Ok(0) | Err(_) => break, 314 Ok(0) | Err(_) => break,
273 Ok(n) => n, 315 Ok(n) => n,
274 }; 316 };
317 let mut out = alloc::string::String::new();
318 use core::fmt::Write as _;
275 for i in (0..n).step_by(16) { 319 for i in (0..n).step_by(16) {
276 let _ = core::write!(out, "{:08x} ", pos + i as u64); 320 let _ = core::write!(out, "{:08x} ", pos + i as u64);
277 for j in 0..16 { 321 for j in 0..16 {
@@ -294,14 +338,23 @@ fn cmd_hexdump(args: &str) {
294 }); 338 });
295 } 339 }
296 out.push_str("|\n"); 340 out.push_str("|\n");
297 if i + 16 >= n {
298 pos += n as u64;
299 continue 'outer;
300 }
301 } 341 }
342 gop_print!("{}", out);
343 serial_print!("{}", out);
344 pos += n as u64;
302 } 345 }
303 gop_print!("{}", out); 346}
304 serial_print!("{}", out); 347
348/// Returns true if the user pressed ^C since the last check; drains the
349/// keyboard buffer of any other buffered keys.
350fn interrupt_pressed() -> bool {
351 let mut pressed = false;
352 while let Some(ev) = ps2::try_poll_key() {
353 if matches!(ev, KeyEvent::CtrlC) {
354 pressed = true;
355 }
356 }
357 pressed
305} 358}
306 359
307fn cmd_mkdir(args: &str) { 360fn cmd_mkdir(args: &str) {
@@ -346,8 +399,14 @@ fn cmd_rm(args: &str) {
346 match rsnix::fs::remove_path(path) { 399 match rsnix::fs::remove_path(path) {
347 Ok(()) => {} 400 Ok(()) => {}
348 Err(()) => { 401 Err(()) => {
349 gop_println!("rm: cannot remove '{}' (missing, dir not empty, or busy)", path); 402 gop_println!(
350 serial_println!("rm: cannot remove '{}' (missing, dir not empty, or busy)", path); 403 "rm: cannot remove '{}' (missing, dir not empty, or busy)",
404 path
405 );
406 serial_println!(
407 "rm: cannot remove '{}' (missing, dir not empty, or busy)",
408 path
409 );
351 } 410 }
352 } 411 }
353} 412}