moros/src/user/shell.rs

421 lines
16 KiB
Rust
Raw Normal View History

2019-12-31 12:10:03 +00:00
use crate::{print, user, kernel};
use crate::kernel::vga::Color;
use alloc::format;
2020-01-25 09:48:06 +00:00
use alloc::vec;
use alloc::vec::Vec;
use alloc::string::String;
2019-12-28 17:08:11 +00:00
2020-01-01 08:07:09 +00:00
#[repr(u8)]
pub enum ExitCode {
CommandSuccessful = 0,
CommandUnknown = 1,
CommandError = 2,
ShellExit = 255,
}
2019-12-31 12:10:03 +00:00
pub struct Shell {
cmd: String,
prompt: String,
history: Vec<String>,
2019-12-31 12:10:03 +00:00
history_index: usize,
2020-01-25 09:48:06 +00:00
autocomplete: Vec<String>,
autocomplete_index: usize,
2019-12-28 17:08:11 +00:00
}
2019-12-31 12:10:03 +00:00
impl Shell {
pub fn new() -> Self {
Shell {
cmd: String::new(),
2020-01-07 09:48:09 +00:00
prompt: String::from("> "),
history: Vec::new(),
2019-12-31 12:10:03 +00:00
history_index: 0,
2020-01-25 09:48:06 +00:00
autocomplete: Vec::new(),
autocomplete_index: 0,
2019-12-31 12:10:03 +00:00
}
}
2019-12-29 13:43:36 +00:00
2020-01-01 08:07:09 +00:00
pub fn run(&mut self) -> user::shell::ExitCode {
self.load_history();
print!("\n");
2019-12-31 12:10:03 +00:00
self.print_prompt();
loop {
let (x, y) = kernel::vga::cursor_position();
let c = kernel::console::get_char();
match c {
2019-12-31 17:16:52 +00:00
'\0' => {
2019-12-31 12:10:03 +00:00
continue;
2019-12-29 09:34:08 +00:00
}
2020-01-07 09:48:09 +00:00
'\x03' => { // Ctrl C
2020-01-07 09:48:30 +00:00
if self.cmd.len() > 0 {
self.cmd.clear();
print!("\n\n");
2020-01-07 09:48:30 +00:00
self.print_prompt();
} else {
return ExitCode::CommandSuccessful;
}
2020-01-07 09:48:09 +00:00
},
'\n' => { // Newline
self.update_history();
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2019-12-31 12:10:03 +00:00
print!("\n");
2019-12-31 22:14:17 +00:00
if self.cmd.len() > 0 {
// Add or move command to history at the end
let cmd = self.cmd.clone();
if let Some(pos) = self.history.iter().position(|s| *s == *cmd) {
self.history.remove(pos);
2019-12-31 22:14:17 +00:00
}
self.history.push(cmd);
self.history_index = self.history.len();
2019-12-31 12:10:03 +00:00
let line = self.cmd.clone();
2020-01-01 08:07:09 +00:00
match self.exec(&line) {
ExitCode::CommandSuccessful => {
self.save_history();
},
ExitCode::ShellExit => {
return ExitCode::CommandSuccessful
},
_ => {
print!("?\n")
},
2019-12-31 12:10:03 +00:00
}
self.cmd.clear();
}
print!("\n");
2019-12-31 12:10:03 +00:00
self.print_prompt();
},
2020-01-25 09:48:06 +00:00
'\t' => { // Tab
self.update_history();
2020-01-25 09:48:06 +00:00
self.print_autocomplete();
},
2019-12-31 17:16:52 +00:00
'↑' => { // Arrow up
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2019-12-31 12:10:03 +00:00
if self.history.len() > 0 {
if self.history_index > 0 {
self.history_index -= 1;
}
let cmd = &self.history[self.history_index];
kernel::vga::clear_row();
self.print_prompt();
print!("{}", cmd);
2019-12-31 12:10:03 +00:00
}
},
2019-12-31 17:16:52 +00:00
'↓' => { // Arrow down
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
if self.history_index < self.history.len() {
self.history_index += 1;
let cmd = if self.history_index < self.history.len() {
&self.history[self.history_index]
} else {
&self.cmd
};
kernel::vga::clear_row();
self.print_prompt();
print!("{}", cmd);
2019-12-31 12:10:03 +00:00
}
},
2020-01-07 09:48:09 +00:00
'←' => { // Arrow left
self.update_history();
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2020-01-07 09:48:09 +00:00
if x > self.prompt.len() {
kernel::vga::set_cursor_position(x - 1, y);
kernel::vga::set_writer_position(x - 1, y);
}
},
'→' => { // Arrow right
self.update_history();
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2020-01-07 09:48:09 +00:00
if x < self.prompt.len() + self.cmd.len() {
kernel::vga::set_cursor_position(x + 1, y);
kernel::vga::set_writer_position(x + 1, y);
}
},
'\x08' => { // Backspace
self.update_history();
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2020-01-07 09:48:09 +00:00
let cmd = self.cmd.clone();
if cmd.len() > 0 && x > self.prompt.len() {
2020-01-07 09:48:09 +00:00
let (before_cursor, mut after_cursor) = cmd.split_at(x - 1 - self.prompt.len());
if after_cursor.len() > 0 {
after_cursor = &after_cursor[1..];
}
self.cmd.clear();
self.cmd.push_str(before_cursor);
self.cmd.push_str(after_cursor);
2020-01-07 09:48:09 +00:00
kernel::vga::clear_row();
self.print_prompt();
print!("{}", self.cmd);
2020-01-07 09:48:09 +00:00
kernel::vga::set_cursor_position(x - 1, y);
kernel::vga::set_writer_position(x - 1, y);
}
},
2019-12-31 17:16:52 +00:00
c => {
self.update_history();
2020-01-25 09:48:06 +00:00
self.update_autocomplete();
2020-01-23 20:30:53 +00:00
if c.is_ascii() && kernel::vga::is_printable(c as u8) {
2020-01-07 09:48:09 +00:00
let cmd = self.cmd.clone();
let (before_cursor, after_cursor) = cmd.split_at(x - self.prompt.len());
self.cmd.clear();
self.cmd.push_str(before_cursor);
self.cmd.push(c);
self.cmd.push_str(after_cursor);
2020-01-07 09:48:09 +00:00
kernel::vga::clear_row();
self.print_prompt();
print!("{}", self.cmd);
2020-01-07 09:48:09 +00:00
kernel::vga::set_cursor_position(x + 1, y);
kernel::vga::set_writer_position(x + 1, y);
2019-12-31 12:10:03 +00:00
}
},
2019-12-29 09:34:08 +00:00
}
2019-12-31 12:10:03 +00:00
}
}
// Called when a key other than up or down is pressed while in history
// mode. The history index point to a command that will be selected and
// the index will be reset to the length of the history vector to signify
// that the editor is no longer in history mode.
pub fn update_history(&mut self) {
if self.history_index != self.history.len() {
self.cmd = self.history[self.history_index].clone();
self.history_index = self.history.len();
}
}
pub fn load_history(&mut self) {
if let Some(home) = kernel::process::env("HOME") {
let pathname = format!("{}/.shell_history", home);
if let Some(file) = kernel::fs::File::open(&pathname) {
let contents = file.read_to_string();
for line in contents.split('\n') {
let cmd = line.trim();
if cmd.len() > 0 {
self.history.push(cmd.into());
}
}
}
self.history_index = self.history.len();
}
}
pub fn save_history(&mut self) {
if let Some(home) = kernel::process::env("HOME") {
let pathname = format!("{}/.shell_history", home);
let mut contents = String::new();
for cmd in &self.history {
contents.push_str(&format!("{}\n", cmd));
}
let mut file = match kernel::fs::File::open(&pathname) {
Some(file) => file,
None => kernel::fs::File::create(&pathname).unwrap(),
};
file.write(&contents.as_bytes()).unwrap();
}
}
2020-01-25 09:48:06 +00:00
pub fn print_autocomplete(&mut self) {
let mut args = self.parse(&self.cmd);
let i = args.len() - 1;
if self.autocomplete_index == 0 {
if args.len() == 1 {
// Autocomplete command
let autocomplete_commands = vec![ // TODO: scan /bin
"copy", "delete", "edit", "help", "move", "print", "quit", "read", "write", "sleep", "clear"
];
self.autocomplete = vec![args[i].into()];
for cmd in autocomplete_commands {
if cmd.starts_with(args[i]) {
self.autocomplete.push(cmd.into());
}
}
} else {
// Autocomplete path
let pathname = kernel::fs::realpath(args[i]);
let dirname = kernel::fs::dirname(&pathname);
let filename = kernel::fs::filename(&pathname);
self.autocomplete = vec![args[i].into()];
2020-01-25 09:48:06 +00:00
if let Some(dir) = kernel::fs::Dir::open(dirname) {
let sep = if dirname.ends_with("/") { "" } else { "/" };
2020-01-25 09:48:06 +00:00
for entry in dir.read() {
if entry.name().starts_with(filename) {
self.autocomplete.push(format!("{}{}{}", dirname, sep, entry.name()));
2020-01-25 09:48:06 +00:00
}
}
}
}
}
self.autocomplete_index = (self.autocomplete_index + 1) % self.autocomplete.len();
args[i] = &self.autocomplete[self.autocomplete_index];
let cmd = args.join(" ");
kernel::vga::clear_row();
self.print_prompt();
print!("{}", cmd);
2020-01-25 09:48:06 +00:00
}
// Called when a key other than tab is pressed while in autocomplete mode.
// The autocomplete index point to an argument that will be added to the
// command and the index will be reset to signify that the editor is no
// longer in autocomplete mode.
2020-01-25 09:48:06 +00:00
pub fn update_autocomplete(&mut self) {
if self.autocomplete_index != 0 {
let mut args = self.parse(&self.cmd);
let i = args.len() - 1;
args[i] = &self.autocomplete[self.autocomplete_index];
self.cmd = args.join(" ");
self.autocomplete_index = 0;
self.autocomplete = vec!["".into()];
}
}
pub fn parse<'a>(&self, cmd: &'a str) -> Vec<&'a str> {
//let args: Vec<&str> = cmd.split_whitespace().collect();
let mut args: Vec<&str> = Vec::new();
2020-01-06 21:26:48 +00:00
let mut i = 0;
let mut n = cmd.len();
let mut is_quote = false;
for (j, c) in cmd.char_indices() {
2020-01-12 08:19:16 +00:00
if c == '#' && !is_quote {
n = j; // Discard comments
break;
} else if c == ' ' && !is_quote {
2020-01-06 21:26:48 +00:00
if i != j {
args.push(&cmd[i..j]);
2020-01-06 21:26:48 +00:00
}
i = j + 1;
} else if c == '"' {
is_quote = !is_quote;
if !is_quote {
args.push(&cmd[i..j]);
2020-01-06 21:26:48 +00:00
}
i = j + 1;
}
}
if i < n {
if is_quote {
n -= 1;
}
args.push(&cmd[i..n]);
2020-01-06 21:26:48 +00:00
}
2020-01-25 09:48:06 +00:00
if n == 0 || cmd.chars().last().unwrap() == ' ' {
args.push("");
}
2020-01-06 21:26:48 +00:00
args
}
2020-01-01 08:07:09 +00:00
pub fn exec(&self, cmd: &str) -> ExitCode {
2020-01-06 21:26:48 +00:00
let args = self.parse(cmd);
2020-01-01 08:07:09 +00:00
match args[0] {
2020-01-25 09:48:06 +00:00
"" => ExitCode::CommandSuccessful,
2020-01-17 18:55:09 +00:00
"a" | "alias" => ExitCode::CommandUnknown,
"b" => ExitCode::CommandUnknown,
"c" | "copy" => user::copy::main(&args),
"d" | "del" | "delete" => user::delete::main(&args),
"e" | "edit" => user::editor::main(&args),
"f" | "find" => ExitCode::CommandUnknown,
"g" | "go" | "goto" => self.change_dir(&args),
2020-01-17 19:10:27 +00:00
"h" | "help" => user::help::main(&args),
2020-01-17 18:55:09 +00:00
"i" => ExitCode::CommandUnknown,
"j" | "jump" => ExitCode::CommandUnknown,
"k" | "kill" => ExitCode::CommandUnknown,
"l" | "list" => user::list::main(&args),
"m" | "move" => user::r#move::main(&args),
"n" => ExitCode::CommandUnknown,
"o" => ExitCode::CommandUnknown,
"p" | "print" => user::print::main(&args),
"q" | "quit" | "exit" => ExitCode::ShellExit,
"r" | "read" => user::read::main(&args),
"s" => ExitCode::CommandUnknown,
"t" => ExitCode::CommandUnknown,
"u" => ExitCode::CommandUnknown,
"v" => ExitCode::CommandUnknown,
"w" | "write" => user::write::main(&args),
"x" => ExitCode::CommandUnknown,
"y" => ExitCode::CommandUnknown,
"z" => ExitCode::CommandUnknown,
"shell" => user::shell::main(&args),
"sleep" => user::sleep::main(&args),
"clear" => user::clear::main(&args),
"login" => user::login::main(&args),
"base64" => user::base64::main(&args),
"halt" => user::halt::main(&args),
Add network stack (#12) * Display MAC address * Add kernel::pci::find_device * Mask lower bits of 16-bit Memory Space BAR * Use array instead of vector for MAC address * Split interrupts module * Use IRQ constants instead of InterruptIndex enum * Replace kernel::sleep by kernel::time * Add kernel::idt::set_irq_handler * Add interrupt handler for RTL8139 * Enable bus mastering for RTL8139 * Setup NIC * Add features for vga/serial and qwerty/dvorak * Add smoltcp crate * Use EthernetAddress from smoltcp * Add RTL8139 struct to implement smoltcp Device * Save detected device * Add kernel::mem::translate_addr * Use physical address of rx_buffer * Add command to read raw network data * Parse packet header and length * Fix missing ascii on last line * Take CRC into account for packet length * Fix compilation error * Move buffer pointers after packet received * Use buffer slice instead of clone in RxToken * Add packet transmission and dhcp client * Configure network interface with DHCP client * Add debug mode to network interface * Clean dhcp command output * Add ip command * Clean up commands output * Count number of packets transmitted and received * Add route command * Add kernel::random::rand16 * Handle carriage return char * Add HTTP client * Improve http command output * Add DNS resolver command * Parse DNS responses to A IN queries * Resolve http host * Check if interface is ready before operations * Add timeout to polling loops * Fix sleep during polling * Add verbose arg to http command * Add State struct to Device struct * Add subcommand config and dump to net command * Add MTU to RX_BUFFER_LEN when using WRAP * Fix first transmission index * Refactor TxToken implementation * Add user agent to http requests * Add more comments to code * Add llvm-tools-preview component to readme * Add method to translate IRQ into system interrupt * Clear IRQ mask in set_irq_handler * Refactor driver code * Sleep less rather than more * Add rand32 * Disable RTL8139 interrupts * Use arrays instead of vectors for buffers * Add minimum sleep duration * Add phy_addr to dry init * Use CAPR and CBR to compute rx buffer offset * Add debug for alloc issue with continuous physical memory * Fix timeout in loops * Add unused buffer to push the rx buffer into contiguous memory * Add doc about network * Update readme * Add read /net/<proto>/<host>/<path> subcommand
2020-02-02 17:55:20 +00:00
"hex" => user::hex::main(&args), // TODO: Rename to `dump`
"net" => user::net::main(&args),
"route" => user::route::main(&args),
"dhcp" => user::dhcp::main(&args),
"http" => user::http::main(&args),
"tcp" => user::tcp::main(&args),
Add network stack (#12) * Display MAC address * Add kernel::pci::find_device * Mask lower bits of 16-bit Memory Space BAR * Use array instead of vector for MAC address * Split interrupts module * Use IRQ constants instead of InterruptIndex enum * Replace kernel::sleep by kernel::time * Add kernel::idt::set_irq_handler * Add interrupt handler for RTL8139 * Enable bus mastering for RTL8139 * Setup NIC * Add features for vga/serial and qwerty/dvorak * Add smoltcp crate * Use EthernetAddress from smoltcp * Add RTL8139 struct to implement smoltcp Device * Save detected device * Add kernel::mem::translate_addr * Use physical address of rx_buffer * Add command to read raw network data * Parse packet header and length * Fix missing ascii on last line * Take CRC into account for packet length * Fix compilation error * Move buffer pointers after packet received * Use buffer slice instead of clone in RxToken * Add packet transmission and dhcp client * Configure network interface with DHCP client * Add debug mode to network interface * Clean dhcp command output * Add ip command * Clean up commands output * Count number of packets transmitted and received * Add route command * Add kernel::random::rand16 * Handle carriage return char * Add HTTP client * Improve http command output * Add DNS resolver command * Parse DNS responses to A IN queries * Resolve http host * Check if interface is ready before operations * Add timeout to polling loops * Fix sleep during polling * Add verbose arg to http command * Add State struct to Device struct * Add subcommand config and dump to net command * Add MTU to RX_BUFFER_LEN when using WRAP * Fix first transmission index * Refactor TxToken implementation * Add user agent to http requests * Add more comments to code * Add llvm-tools-preview component to readme * Add method to translate IRQ into system interrupt * Clear IRQ mask in set_irq_handler * Refactor driver code * Sleep less rather than more * Add rand32 * Disable RTL8139 interrupts * Use arrays instead of vectors for buffers * Add minimum sleep duration * Add phy_addr to dry init * Use CAPR and CBR to compute rx buffer offset * Add debug for alloc issue with continuous physical memory * Fix timeout in loops * Add unused buffer to push the rx buffer into contiguous memory * Add doc about network * Update readme * Add read /net/<proto>/<host>/<path> subcommand
2020-02-02 17:55:20 +00:00
"host" => user::host::main(&args),
"ip" => user::ip::main(&args),
"geotime" => user::geotime::main(&args),
"colors" => user::colors::main(&args),
"mkfs" => user::mkfs::main(&args),
2020-01-17 18:55:09 +00:00
_ => ExitCode::CommandUnknown,
2020-01-01 08:07:09 +00:00
}
}
2019-12-31 12:10:03 +00:00
fn print_prompt(&self) {
let (fg, bg) = kernel::vga::color();
kernel::vga::set_color(Color::Magenta, bg);
print!("{}", self.prompt);
kernel::vga::set_color(fg, bg);
2019-12-28 17:08:11 +00:00
}
fn change_dir(&self, args: &[&str]) -> ExitCode {
match args.len() {
1 => {
print!("{}\n", kernel::process::dir());
ExitCode::CommandSuccessful
},
2 => {
let pathname = kernel::fs::realpath(args[1]);
if kernel::fs::Dir::open(&pathname).is_some() {
kernel::process::set_dir(&pathname);
ExitCode::CommandSuccessful
} else {
print!("File not found '{}'\n", pathname);
ExitCode::CommandError
}
},
_ => {
ExitCode::CommandError
}
}
}
2019-12-28 17:08:11 +00:00
}
2020-01-01 08:07:24 +00:00
pub fn main(args: &[&str]) -> ExitCode {
let mut shell = Shell::new();
match args.len() {
1 => {
return shell.run();
},
2 => {
let pathname = args[1];
if let Some(file) = kernel::fs::File::open(pathname) {
for line in file.read_to_string().split("\n") {
2020-01-01 08:55:33 +00:00
if line.len() > 0 {
shell.exec(line);
}
2020-01-01 08:07:24 +00:00
}
ExitCode::CommandSuccessful
} else {
print!("File not found '{}'\n", pathname);
ExitCode::CommandError
}
},
_ => {
ExitCode::CommandError
},
}
}