From 670f3864e08003b89a362f381a12d509611db870 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Mon, 19 Feb 2024 01:04:17 -0700 Subject: Refresh paths every loop Implements the path refresh at the start of each REPL loop. On this commit, it is printing out how long it needed to refresh all the paths in milliseconds. --- src/main.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7fc8991..272eec1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,18 +4,21 @@ use std::io; use std::io::Write; use std::path::Path; use std::process::Command; +use std::time::SystemTime; fn eval(paths: &[&str], prompt: &str) { let mut bins: Vec = Vec::new(); - for path in paths { - let files = fs::read_dir(path).expect("Unable to read files in your path"); - for file in files { - bins.push(file.unwrap().path().display().to_string()); + loop { + let now = SystemTime::now(); + for path in paths { + let files = fs::read_dir(path).expect("Unable to read files in your path"); + for file in files { + bins.push(file.unwrap().path().display().to_string()); + } } - } + println!("Refresh: {} ms", now.elapsed().unwrap().as_millis()); - loop { // Output the prompt io::stdout().flush().unwrap(); print!("{}", prompt); -- cgit v1.2.3 From 2e5cc53499947c32b01ea5e1787ed505bc286969 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Mon, 19 Feb 2024 01:40:58 -0700 Subject: Refresh path only if command is not found This is a modified implementation of the 'refresh path on every iteration of the loop' idea. It instead, only refreshes the path if the command is not found. After the first refresh, if the command still is not found, it throws and error. --- src/main.rs | 53 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/src/main.rs b/src/main.rs index 272eec1..4d56375 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,21 +4,11 @@ use std::io; use std::io::Write; use std::path::Path; use std::process::Command; -use std::time::SystemTime; fn eval(paths: &[&str], prompt: &str) { let mut bins: Vec = Vec::new(); loop { - let now = SystemTime::now(); - for path in paths { - let files = fs::read_dir(path).expect("Unable to read files in your path"); - for file in files { - bins.push(file.unwrap().path().display().to_string()); - } - } - println!("Refresh: {} ms", now.elapsed().unwrap().as_millis()); - // Output the prompt io::stdout().flush().unwrap(); print!("{}", prompt); @@ -46,7 +36,7 @@ fn eval(paths: &[&str], prompt: &str) { // Parse command and arguments let mut split = input.split(' '); - let mut cmd = match split.next() { + let cmd = match split.next() { Some(str) if str.trim().is_empty() => continue, Some(str) => str.trim(), None => continue, @@ -80,16 +70,41 @@ fn eval(paths: &[&str], prompt: &str) { // Check if the file exists, if given a pull or relative path // TODO: Check if file at the path is executable (i.e. +x) - if !Path::new(cmd).exists() { + let mut cmd = String::from(cmd); + if !Path::new(cmd.as_str()).exists() { + let b = bins.clone(); // Check if the command exists in $PATH if a full or relative path // was not given, or if the path does not exist - cmd = match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { - Some(cmd) => cmd, - None => { - println!("Command not found"); - continue; - } - }; + // + // If the command is not found the first time, try refreshing the + // path first, and only print an error if if it's not found after + // the path refresh + cmd = String::from( + match b + .clone() + .iter() + .find(|b| b.split("/").last().unwrap() == cmd) + { + Some(cmd) => cmd, + None => { + for path in paths { + let files = + fs::read_dir(path).expect("Unable to read files in your path"); + for file in files { + bins.push(file.unwrap().path().display().to_string()); + } + } + + match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { + Some(cmd) => cmd, + None => { + println!("dwvsh: error: command not found..."); + continue; + } + } + } + }, + ); } // Run the command (and wait for it to finish) -- cgit v1.2.3 From 08be85a2fc508450c6361af4ef38a7dcd3efbde5 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Mon, 19 Feb 2024 02:33:36 -0700 Subject: Better handling of errors during the fork Adds two additional error checks when the shell forks: 1. Checks for permission (+r, +x) 2. Checks if the file exists The first error may occur if the user does not have read access to the file, or if the file is not executable. The second error may occur if a file was removed from the $PATH, and the $PATH hasn't been refreshed yet. --- src/main.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4d56375..c27bad7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -108,10 +108,19 @@ fn eval(paths: &[&str], prompt: &str) { } // Run the command (and wait for it to finish) - let mut child = match Command::new(cmd).args(args).spawn() { + let mut child = match Command::new(&cmd).args(args).spawn() { Ok(ch) => ch, - Err(_) => { - println!("Unable to fork"); + Err(err) => { + match err.kind() { + std::io::ErrorKind::PermissionDenied => println!( + "dwvsh: error: permission denied trying to fork to {}...", + &cmd + ), + // TODO: Refresh paths if error kind is NotFound + std::io::ErrorKind::NotFound => println!("dwvsh: error: command not found..."), + _ => println!("dwvsh: error: unable to fork..."), + } + continue; } }; -- cgit v1.2.3 From d98ab111eebfcf5e6327daaec17b5de578d64a41 Mon Sep 17 00:00:00 2001 From: Rory Dudley Date: Mon, 19 Feb 2024 17:09:07 -0700 Subject: Path refresh refactor, comments, and error messages The 'eval' function was renamed to 'repl'. The code to refresh the $PATH was moved into it's own function: 'prefresh', and is now being called in three locations: - At the beginning of 'repl', before the main loop - Inside the main loop, possibly during a path search if the command is not initially found - Inside the main loop, if the call to Command::spawn() throws an error, and the error is ErrorKind::NotFound Doc comments were added for each function, as well as a few more comments throughout that detail the program's control flow. The error messages in the main repl loop were cleaned up to have a more consistent style, and to provide more/better detail. --- src/main.rs | 145 ++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 43 deletions(-) diff --git a/src/main.rs b/src/main.rs index c27bad7..1dc0217 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,70 @@ use ctrlc; use std::fs; -use std::io; -use std::io::Write; +use std::io::{self, Write}; use std::path::Path; use std::process::Command; -fn eval(paths: &[&str], prompt: &str) { +/// Refresh the shell's $PATH +/// +/// This function caches all valid paths within within the directories +/// specified. +/// +/// # Arguments +/// * `paths` - A reference to a vector that holds a list to the shell $PATHs +/// +/// # Returns +/// * `bins: Vec` - A new cache of all valid file paths in $PATH +/// +/// # Examples +/// ``` +/// let paths = vec!["/bin"]; +/// let paths = paths.into_iter().map(Path::new).collect(); +/// let mut bins = prefresh(&paths); +/// ... +/// // A situation occurs where the $PATH needs to be refreshed +/// bins = prefresh(&paths) +/// ``` +fn prefresh(paths: &Vec<&Path>) -> Vec { let mut bins: Vec = Vec::new(); + for path in paths { + let files = fs::read_dir(path).expect( + format!( + "dwvsh: error: unable to read the contents of {}", + path.display().to_string() + ) + .as_str(), + ); + + for file in files { + bins.push(file.unwrap().path().display().to_string()); + } + } + + bins +} + +/// Starts the main shell loop +/// +/// # Arguments +/// * `paths` - A reference to a vector that holds a list to the shell $PATHs +/// * `prompt` - A string slice indicating the shell's prompt +/// +/// # Examples +/// ``` +/// fn main() { +/// let paths = vec!["/bin"]; +/// let paths = paths.into_iter().map(Path::new).collect(); +/// let prompt = "|> "; +/// ... +/// repl(&paths, prompt); +/// } +/// ``` +fn repl(paths: &Vec<&Path>, prompt: &str) { + // Initial path refresh on startup + let mut bins: Vec = prefresh(paths); + + // Main shell loop loop { // Output the prompt io::stdout().flush().unwrap(); @@ -18,7 +75,7 @@ fn eval(paths: &[&str], prompt: &str) { let mut input = String::new(); let bytes = io::stdin() .read_line(&mut input) - .expect("Unable to evaluate the input string"); + .expect("dwvsh: error: unable to evaluate the input string"); // Check if we've reached EOF (i.e. ) if bytes == 0 { @@ -34,11 +91,11 @@ fn eval(paths: &[&str], prompt: &str) { break; } - // Parse command and arguments + // Parse command let mut split = input.split(' '); - let cmd = match split.next() { + let mut cmd = match split.next() { Some(str) if str.trim().is_empty() => continue, - Some(str) => str.trim(), + Some(str) => String::from(str.trim()), None => continue, }; @@ -62,7 +119,7 @@ fn eval(paths: &[&str], prompt: &str) { match std::env::set_current_dir(path) { Ok(_) => continue, Err(_) => { - println!("cd: Unable to change into {}", path); + println!("cd: unable to change into {}", path); continue; } } @@ -70,7 +127,6 @@ fn eval(paths: &[&str], prompt: &str) { // Check if the file exists, if given a pull or relative path // TODO: Check if file at the path is executable (i.e. +x) - let mut cmd = String::from(cmd); if !Path::new(cmd.as_str()).exists() { let b = bins.clone(); // Check if the command exists in $PATH if a full or relative path @@ -79,32 +135,23 @@ fn eval(paths: &[&str], prompt: &str) { // If the command is not found the first time, try refreshing the // path first, and only print an error if if it's not found after // the path refresh - cmd = String::from( - match b - .clone() - .iter() - .find(|b| b.split("/").last().unwrap() == cmd) - { - Some(cmd) => cmd, - None => { - for path in paths { - let files = - fs::read_dir(path).expect("Unable to read files in your path"); - for file in files { - bins.push(file.unwrap().path().display().to_string()); - } - } - - match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { - Some(cmd) => cmd, - None => { - println!("dwvsh: error: command not found..."); - continue; - } + cmd = match b + .clone() + .iter() + .find(|b| b.split("/").last().unwrap() == cmd) + { + Some(cmd) => cmd.clone(), + None => { + bins = prefresh(&paths); + match bins.iter().find(|b| b.split("/").last().unwrap() == cmd) { + Some(cmd) => cmd.clone(), + None => { + println!("dwvsh: {}: command not found", cmd); + continue; } } - }, - ); + } + } } // Run the command (and wait for it to finish) @@ -112,15 +159,23 @@ fn eval(paths: &[&str], prompt: &str) { Ok(ch) => ch, Err(err) => { match err.kind() { - std::io::ErrorKind::PermissionDenied => println!( - "dwvsh: error: permission denied trying to fork to {}...", - &cmd - ), - // TODO: Refresh paths if error kind is NotFound - std::io::ErrorKind::NotFound => println!("dwvsh: error: command not found..."), - _ => println!("dwvsh: error: unable to fork..."), + // Occurs when the user doesn't have read access, or if the +x bit is not set + std::io::ErrorKind::PermissionDenied => { + println!("dwvsh: permission denied: trying to fork to {}", &cmd) + } + + // Occurs if a command was removed from the path + // If this is the case, refresh the path + std::io::ErrorKind::NotFound => { + bins = prefresh(&paths); + println!("dwvsh: {}: command not found", cmd); + } + + // Otherwise print the OS error + _ => println!("dwvsh: fork: {}", err), } + // Restart the loop continue; } }; @@ -128,10 +183,13 @@ fn eval(paths: &[&str], prompt: &str) { } } +/// Shell entry +/// +/// Shell setup and entry fn main() { // Define paths // TODO: Hardcoded path should only be the fallback - let paths = [ + let paths = vec![ "/bin", "/sbin", "/usr/bin", @@ -139,6 +197,7 @@ fn main() { "/usr/local/bin", "/usr/local/sbin", ]; + let paths = paths.into_iter().map(Path::new).collect(); // Set the prompt let prompt = "|> "; @@ -148,8 +207,8 @@ fn main() { print!("\n{}", prompt); io::stdout().flush().unwrap(); }) - .expect("Unable to set handler"); + .expect("dwvsh: signals: unable to set sigint handler"); // Begin evaluating commands - eval(&paths, prompt); + repl(&paths, prompt); } -- cgit v1.2.3