shell.rs
1 //! Shell: command dispatch plus prompt/path/server submodules. 2 3 use core::fmt::Write; 4 use core::sync::atomic::{AtomicU32, Ordering}; 5 6 use alloc::string::String as AllocString; 7 8 use crate::programs::coreutils; 9 10 mod path; 11 mod prompt; 12 mod server; 13 14 pub use path::{apply_cd, display_cwd, ensure_filesystem_hierarchy, home_dir, resolve_path}; 15 pub use prompt::{build_motd, build_prompt}; 16 pub use server::{spawn, task}; 17 18 const CTRL_L: u8 = 0x0c; 19 const CTRL_P: u8 = 0x10; 20 const CTRL_N: u8 = 0x0e; 21 const CTRL_W: u8 = 0x17; 22 const CTRL_U: u8 = 0x15; 23 24 static TERM_WIDTH: AtomicU32 = AtomicU32::new(80); 25 26 pub fn set_terminal_width(width: u32) { 27 TERM_WIDTH.store(width, Ordering::Relaxed); 28 } 29 30 pub fn terminal_width() -> u32 { 31 TERM_WIDTH.load(Ordering::Relaxed) 32 } 33 34 pub fn dispatch(cmd: &str, cwd: &mut AllocString) -> (AllocString, bool) { 35 let cmd = cmd.trim(); 36 37 if cmd == "exit" || cmd == "quit" { 38 let mut out = AllocString::new(); 39 let _ = write!(out, "\x1b[33mgoodbye!\x1b[0m\r\n"); 40 return (out, true); 41 } 42 43 if let Some(argument) = cmd.strip_prefix("cd ") { 44 let previous = cwd.clone(); 45 apply_cd(cwd, argument); 46 if !crate::filesystems::sd::directory_exists(cwd.as_str()) { 47 *cwd = previous; 48 return (coreutils::fmt_error(&"no such directory"), false); 49 } 50 return (AllocString::new(), false); 51 } 52 53 if cmd == "cd" { 54 *cwd = home_dir(); 55 return (AllocString::new(), false); 56 } 57 58 let (name, args) = match cmd.find(' ') { 59 Some(position) => (&cmd[..position], cmd[position + 1..].trim()), 60 None => (cmd, ""), 61 }; 62 63 let out = match name { 64 "help" | "h" => coreutils::help::run(), 65 "ls" => { 66 if args.is_empty() { 67 coreutils::ls::run(cwd) 68 } else { 69 let mut target = cwd.clone(); 70 apply_cd(&mut target, args); 71 coreutils::ls::run(&target) 72 } 73 } 74 "pwd" => { 75 let mut output = AllocString::new(); 76 let _ = write!(output, "{}\r\n", cwd); 77 output 78 } 79 "mkdir" => coreutils::mkdir::run(cwd, args), 80 "cp" => coreutils::cp::run(args), 81 "mv" => coreutils::mv::run(args), 82 "rm" => coreutils::rm::run(cwd, args), 83 "cat" => coreutils::cat::run(cwd, args), 84 "touch" => coreutils::touch::run(cwd, args), 85 "uptime" => coreutils::uptime::run(), 86 "free" => coreutils::free::run(), 87 "date" => coreutils::date::run(), 88 "df" => coreutils::df::run(), 89 "whoami" => coreutils::whoami::run(), 90 "hostname" => coreutils::hostname::run(), 91 "ifconfig" => coreutils::ifconfig::run(), 92 "sensors" => coreutils::sensors::run(), 93 "wakecause" => coreutils::wakecause::run(), 94 "logstatus" => coreutils::logstatus::run(), 95 "microfetch" | "fetch" => crate::programs::microfetch::run(), 96 "microtop" | "top" => crate::programs::microtop::render_frame(terminal_width() as u16, 24), 97 "reboot" => esp_hal::system::software_reset(), 98 "clear" => { 99 let mut output = AllocString::new(); 100 let _ = write!(output, "\x1b[2J\x1b[H"); 101 output 102 } 103 "" => AllocString::new(), 104 unknown => { 105 let mut output = AllocString::new(); 106 let _ = write!(output, "\x1b[31mcommand not found: {}\x1b[0m\r\n", unknown); 107 output 108 } 109 }; 110 111 (out, false) 112 } 113 114 const HISTORY_FILE: &str = ".MSH_HIST"; 115 116 pub(super) fn load_history(history: &mut crate::services::ssh::history::History<256>) { 117 let home = home_dir(); 118 if let Ok(contents) = crate::filesystems::sd::read_file_at::<4096>(home.as_str(), HISTORY_FILE) 119 { 120 if let Ok(text) = core::str::from_utf8(contents.as_slice()) { 121 for line in text.lines() { 122 let line = line.trim(); 123 if !line.is_empty() { 124 let _ = history.add(line); 125 } 126 } 127 } 128 } 129 } 130 131 pub(super) fn save_history(history: &crate::services::ssh::history::History<256>) { 132 let home = home_dir(); 133 let mut buf = AllocString::new(); 134 for entry in history.iter() { 135 buf.push_str(entry); 136 buf.push('\n'); 137 } 138 let path = resolve_path(home.as_str(), HISTORY_FILE); 139 let _ = crate::filesystems::sd::write_file_chunk(path.as_str(), 0, buf.as_bytes()); 140 }