diff --git a/kernel/libk/src/vfs/file/mod.rs b/kernel/libk/src/vfs/file/mod.rs index 6e43fdf0..cd6dadf8 100644 --- a/kernel/libk/src/vfs/file/mod.rs +++ b/kernel/libk/src/vfs/file/mod.rs @@ -609,11 +609,13 @@ impl FileSet { /// Removes and closes a [FileRef] from the struct pub fn close_file(&mut self, fd: RawFd) -> Result<(), Error> { // Do nothing, file will be dropped and closed - if self.map.remove(&fd).is_some() { - Ok(()) - } else { - Err(Error::InvalidFile) - } + // TODO call File's close() and return its status + let _ = self.take_file(fd)?; + Ok(()) + } + + pub fn take_file(&mut self, fd: RawFd) -> Result { + self.map.remove(&fd).ok_or(Error::InvalidFile) } /// Removes all [FileRef]s from the struct which do not pass the `predicate` check diff --git a/kernel/src/syscall/imp/sys_process.rs b/kernel/src/syscall/imp/sys_process.rs index 39bf39dd..82088a1c 100644 --- a/kernel/src/syscall/imp/sys_process.rs +++ b/kernel/src/syscall/imp/sys_process.rs @@ -100,7 +100,7 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result) -> Result { + &SpawnOption::MoveFile { source, child } => { + if let Ok(src_file) = io.files.take_file(source) { + child_io.files.set_file(child, src_file)?; + } + } + &SpawnOption::CopyFile { source, child } => { if let Ok(src_file) = io.files.file(source) { - child_io.files.set_file(child, src_file.clone())?; + child_io.files.set_file(child, src_file.send()?)?; } } &SpawnOption::SetProcessGroup(pgroup) => { @@ -184,7 +189,13 @@ pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result {result:?}"); + //} + + result } pub(crate) fn wait_process( diff --git a/lib/abi/src/process/mod.rs b/lib/abi/src/process/mod.rs index d5e9fb11..da3f8302 100644 --- a/lib/abi/src/process/mod.rs +++ b/lib/abi/src/process/mod.rs @@ -49,13 +49,21 @@ pub enum ProcessOption { /// Defines an optional argument for controlling process creation #[derive(Debug)] pub enum SpawnOption { - /// Indicates a new process should inherit a file descriptor from its creator - InheritFile { - /// FD on the creator side + MoveFile { source: RawFd, - /// What FD number should be used in the child child: RawFd, }, + CopyFile { + source: RawFd, + child: RawFd, + }, + // /// Indicates a new process should inherit a file descriptor from its creator + // InheritFile { + // /// FD on the creator side + // source: RawFd, + // /// What FD number should be used in the child + // child: RawFd, + // }, /// The new process should be placed in the specified group SetProcessGroup(ProcessGroupId), /// Gain terminal control for the given FD diff --git a/userspace/shell/Cargo.toml b/userspace/shell/Cargo.toml index 92eb49d1..640b044a 100644 --- a/userspace/shell/Cargo.toml +++ b/userspace/shell/Cargo.toml @@ -16,3 +16,6 @@ yggdrasil-abi = { path = "../../lib/abi" } [target.'cfg(unix)'.dependencies] libc = "*" + +[lints] +workspace = true diff --git a/userspace/shell/src/builtins.rs b/userspace/shell/src/builtins.rs index 89496edc..6b072f78 100644 --- a/userspace/shell/src/builtins.rs +++ b/userspace/shell/src/builtins.rs @@ -12,7 +12,9 @@ pub type BuiltinCommand = fn(&[String], &mut HashMap) -> Result< static BUILTINS: &[(&str, BuiltinCommand)] = &[ ("echo", b_echo), ("set", b_set), + #[cfg(target_os = "yggdrasil")] ("cd", b_cd), + #[cfg(target_os = "yggdrasil")] ("pwd", b_pwd), ("which", b_which), ("exit", b_exit), @@ -94,6 +96,7 @@ fn b_exit(args: &[String], _envs: &mut HashMap) -> Result) -> Result { let path = if args.is_empty() { "/" @@ -104,6 +107,7 @@ fn b_cd(args: &[String], _envs: &mut HashMap) -> Result) -> Result { if !args.is_empty() { eprintln!("Usage: pwd"); diff --git a/userspace/shell/src/main.rs b/userspace/shell/src/main.rs index 8cd0cf1d..83dc080d 100644 --- a/userspace/shell/src/main.rs +++ b/userspace/shell/src/main.rs @@ -5,12 +5,13 @@ use std::{ env, fs::File, io::{self, stdin, stdout, BufRead, BufReader, Stdin, Write}, - os::fd::{FromRawFd, IntoRawFd, OwnedFd}, + os::fd::{FromRawFd, IntoRawFd}, path::Path, process::{Child, ExitCode, Stdio}, }; use clap::Parser; +use parser::Command; mod builtins; mod parser; @@ -20,8 +21,9 @@ mod sys; pub enum Error { #[error("{0}")] IoError(#[from] io::Error), + #[cfg(any(target_os = "yggdrasil", rust_analyzer))] #[error("{0:?}")] - RtError(yggdrasil_rt::Error) + RtError(yggdrasil_rt::Error), } #[derive(Parser)] @@ -87,7 +89,7 @@ impl Input { // TODO group pipeline commands into a single process group pub fn exec( interactive: bool, - pipeline: &[parser::Command], + command: &Command, env: &mut HashMap, ) -> Result { // Pipeline "a | b | c" execution: @@ -98,12 +100,23 @@ pub fn exec( // // Pipe count: command count - 1 - if pipeline.is_empty() { + if command.commands.is_empty() { return Ok(Outcome::ok()); } - if pipeline.len() == 1 { - let command = &pipeline[0]; + let stdin = if let Some(path) = command.stdin.as_ref() { + Some(File::open(path)?) + } else { + None + }; + let stdout = if let Some(path) = command.stdout.as_ref() { + Some(File::create(path)?) + } else { + None + }; + + if command.commands.len() == 1 { + let command = &command.commands[0]; let (cmd, args) = command.words.split_first().unwrap(); if let Some(builtin) = builtins::get_builtin(cmd) { @@ -113,16 +126,17 @@ pub fn exec( let mut inputs = vec![]; let mut outputs = vec![]; - let mut pipe_fds = vec![]; - inputs.push(Stdio::inherit()); - for _ in 1..pipeline.len() { + if let Some(stdin) = stdin { + inputs.push(unsafe { Stdio::from_raw_fd(stdin.into_raw_fd()) }); + } else { + inputs.push(Stdio::inherit()); + } + for _ in 1..command.commands.len() { let pipe = sys::create_pipe()?; let read_fd = pipe.read.into_raw_fd(); let write_fd = pipe.write.into_raw_fd(); - pipe_fds.push(unsafe { OwnedFd::from_raw_fd(read_fd) }); - pipe_fds.push(unsafe { OwnedFd::from_raw_fd(write_fd) }); let input = unsafe { Stdio::from_raw_fd(read_fd) }; let output = unsafe { Stdio::from_raw_fd(write_fd) }; @@ -130,15 +144,19 @@ pub fn exec( inputs.push(input); outputs.push(output); } - outputs.push(Stdio::inherit()); + if let Some(stdout) = stdout { + outputs.push(unsafe { Stdio::from_raw_fd(stdout.into_raw_fd()) }); + } else { + outputs.push(Stdio::inherit()); + } assert_eq!(inputs.len(), outputs.len()); - assert_eq!(inputs.len(), pipeline.len()); + assert_eq!(inputs.len(), command.commands.len()); let mut elements = vec![]; let ios = inputs.drain(..).zip(outputs.drain(..)); - for (command, (input, output)) in pipeline.iter().zip(ios) { + for (command, (input, output)) in command.commands.iter().zip(ios) { let (cmd, args) = command.words.split_first().unwrap(); let element = PipelineElement { @@ -154,8 +172,6 @@ pub fn exec( let pipeline = Pipeline { elements, env }; let pipeline = sys::spawn_pipeline(interactive, pipeline)?; - drop(pipe_fds); - let status = sys::wait_for_pipeline(interactive, pipeline)?; Ok(status) @@ -182,7 +198,17 @@ fn run(mut input: Input, vars: &mut HashMap) -> io::Result line.trim(), None => line, }; - let cmd = parser::parse_line(vars, line).unwrap(); + let cmd = match parser::parse_line(vars, line) { + Ok(cmd) => cmd, + Err(error) if input.is_interactive() => { + eprintln!("stdin: {error}"); + continue; + } + Err(error) => { + eprintln!("Command error: {error}"); + return Ok(ExitCode::FAILURE); + } + }; let q_code = match exec(input.is_interactive(), &cmd, vars) { Ok(Outcome::ExitShell(code)) => { diff --git a/userspace/shell/src/parser.rs b/userspace/shell/src/parser.rs index 34006394..67f9015f 100644 --- a/userspace/shell/src/parser.rs +++ b/userspace/shell/src/parser.rs @@ -1,157 +1,143 @@ -use std::collections::HashMap; +use std::{collections::HashMap, mem, path::PathBuf}; + +use nom::{ + branch::alt, + bytes::complete::{is_a, tag}, + character::complete::{alphanumeric1, space0, u8 as num_u8}, + combinator::{map, recognize, value}, + multi::many1, + sequence::{preceded, terminated}, + IResult, Parser, +}; #[derive(Debug, PartialEq)] -pub struct Command { +pub struct PipelineElement { pub words: Vec, } +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Lex(#[from] nom::Err>), +} + #[derive(Debug, PartialEq)] -pub enum Token { - Word(String), +pub struct Command { + pub commands: Vec, + pub stdin: Option, + pub stdout: Option, + pub stderr: Option, +} + +#[derive(Debug, Clone)] +enum Token<'a> { + Word(&'a str), Pipe, + Output(u8), + Input, } -fn lex_skip_whitespace(mut input: &[u8]) -> &[u8] { - while input.first().map(u8::is_ascii_whitespace).unwrap_or(false) { - input = &input[1..]; - } - input +fn lex_word(i: &str) -> IResult<&str, Token> { + map( + recognize(many1(alt((alphanumeric1, is_a("_-+=%!@/.[]:"))))), + Token::Word, + ) + .parse(i) } -pub fn lex_word(mut input: &[u8]) -> Result<(Token, &[u8]), &[u8]> { - let mut buffer = String::new(); - while !input.is_empty() { - if input[0].is_ascii_whitespace() || input[0] == b'"' { - break; - } - - buffer.push(input[0] as char); - input = &input[1..]; - } - - Ok((Token::Word(buffer), input)) +fn lex_output(i: &str) -> IResult<&str, Token> { + alt(( + map(terminated(num_u8, tag(">")), Token::Output), + value(Token::Output(1), tag(">")), + )) + .parse(i) } -pub fn lex_token(mut input: &[u8]) -> Result<(Option, &[u8]), &[u8]> { - input = lex_skip_whitespace(input); - - let Some(&ch) = input.first() else { - return Ok((None, &[])); - }; - - match ch { - b'|' => Ok((Some(Token::Pipe), &input[1..])), - b'"' => todo!(), - _ => lex_word(input).map(|(x, y)| (Some(x), y)), - } +fn lex_input(i: &str) -> IResult<&str, Token> { + value(Token::Input, tag("<")).parse(i) } -pub fn lex_line(mut input: &[u8]) -> Result, &[u8]> { - let mut res = Vec::new(); - while let (Some(token), output) = lex_token(input)? { - res.push(token); - input = output; - } - Ok(res) +fn lex_pipe(i: &str) -> IResult<&str, Token> { + value(Token::Pipe, tag("|")).parse(i) } -pub fn collect_pipeline(tokens: &[Token]) -> Vec { - let mut pipeline = Vec::new(); - let mut current = None; +fn lex_token(i: &str) -> IResult<&str, Token> { + preceded(space0, alt((lex_output, lex_word, lex_input, lex_pipe))).parse(i) +} - for token in tokens { +fn parse_command(_env: &HashMap, input: &[Token]) -> Result { + let mut elements = vec![]; + let mut stdin = None; + let mut stdout = None; + let mut stderr = None; + + let mut current = vec![]; + + let mut it = input.into_iter(); + while let Some(token) = it.next() { match token { - Token::Word(word) => { - let current = current.get_or_insert_with(|| Command { words: vec![] }); - current.words.push(word.clone()); + &Token::Word(word) => { + current.push(word.into()); } Token::Pipe => { - if let Some(current) = current.take() { - pipeline.push(current); + if current.is_empty() { + todo!(); } + + elements.push(PipelineElement { + words: mem::replace(&mut current, vec![]), + }); + } + Token::Output(1) => { + // TODO ok_or + let path = it.next().unwrap(); + let Token::Word(word) = path else { + todo!(); + }; + stdout = Some(PathBuf::from(word)); + } + Token::Output(2) => { + // TODO ok_or + let path = it.next().unwrap(); + let Token::Word(word) = path else { + todo!(); + }; + stderr = Some(PathBuf::from(word)); + } + Token::Input => { + // TODO ok_or + let path = it.next().unwrap(); + let Token::Word(word) = path else { + todo!(); + }; + stdin = Some(PathBuf::from(word)); + } + Token::Output(_) => { + todo!(); } } } - if let Some(current) = current { - pipeline.push(current); + if !current.is_empty() { + elements.push(PipelineElement { words: current }); } - pipeline + Ok(Command { + commands: elements, + stdin, + stdout, + stderr, + }) } -pub fn parse_line(_env: &HashMap, input: &str) -> Result, ()> { - let tokens = lex_line(input.as_bytes()).map_err(|_| ())?; - let pipeline = collect_pipeline(&tokens); - Ok(pipeline) -} - -#[cfg(test)] -mod tests { - use crate::parser::{lex_line, lex_skip_whitespace, lex_token, Command, Token}; - - use super::collect_pipeline; - - #[test] - fn collect() { - let tokens = lex_line(b"abc def | ghi jkl | mno pqr").unwrap(); - let pipeline = collect_pipeline(&tokens); - - assert_eq!( - &pipeline, - &[ - Command { - words: vec!["abc".to_owned(), "def".to_owned()] - }, - Command { - words: vec!["ghi".to_owned(), "jkl".to_owned()] - }, - Command { - words: vec!["mno".to_owned(), "pqr".to_owned()] - }, - ] - ); - } - - #[test] - fn skip_whitespace() { - let w = b" \t\na"; - assert_eq!(lex_skip_whitespace(w), b"a"); - - let w = b""; - assert_eq!(lex_skip_whitespace(w), b""); - - let w = b"a"; - assert_eq!(lex_skip_whitespace(w), b"a"); - } - - #[test] - fn line_tokens() { - let w = b"abc def"; - assert_eq!( - lex_line(w).unwrap(), - vec![Token::Word("abc".to_owned()), Token::Word("def".to_owned())] - ); - - let w = b"abc def | ghi jkl"; - assert_eq!( - lex_line(w).unwrap(), - vec![ - Token::Word("abc".to_owned()), - Token::Word("def".to_owned()), - Token::Pipe, - Token::Word("ghi".to_owned()), - Token::Word("jkl".to_owned()), - ] - ); - } - - #[test] - fn token() { - let w = b"abc def"; - let (t0, w) = lex_token(w).unwrap(); - assert_eq!(t0, Some(Token::Word("abc".to_owned()))); - let (t1, w) = lex_token(w).unwrap(); - assert_eq!(t1, Some(Token::Word("def".to_owned()))); - } +pub fn parse_line(env: &HashMap, input: &str) -> Result { + let mut input = input; + let mut tokens = vec![]; + while !input.is_empty() { + let (tail, token) = lex_token(input).map_err(|error| error.map_input(String::from))?; + tokens.push(token); + input = tail; + } + + parse_command(env, &tokens) } diff --git a/userspace/shell/src/sys/mod.rs b/userspace/shell/src/sys/mod.rs index b4867ca8..c22a358a 100644 --- a/userspace/shell/src/sys/mod.rs +++ b/userspace/shell/src/sys/mod.rs @@ -1,6 +1,6 @@ -#[cfg(unix)] +#[cfg(any(unix, rust_analyzer))] pub mod unix; -#[cfg(unix)] +#[cfg(any(unix, rust_analyzer))] pub use unix as imp; #[cfg(target_os = "yggdrasil")] diff --git a/userspace/shell/src/sys/unix.rs b/userspace/shell/src/sys/unix.rs index 33290a6f..425f08b3 100644 --- a/userspace/shell/src/sys/unix.rs +++ b/userspace/shell/src/sys/unix.rs @@ -8,7 +8,7 @@ use std::{ process::{Child, Command, ExitStatus, Stdio}, }; -use crate::Outcome; +use crate::{Outcome, Pipeline, SpawnedPipeline}; pub struct Pipe { pub read: OwnedFd, @@ -63,3 +63,53 @@ impl From for Outcome { } } } + +pub fn init_signal_handler() {} + +pub fn spawn_pipeline( + interactive: bool, + pipeline: Pipeline<'_>, +) -> Result { + let mut children = vec![]; + for element in pipeline.elements { + let mut command = Command::new(element.command); + command + .args(element.args) + .envs(pipeline.env.iter()) + .stdin(element.input) + .stdout(element.output); + + children.push(command.spawn()?); + } + + Ok(SpawnedPipeline { children }) +} + +pub fn wait_for_pipeline( + interactive: bool, + mut pipeline: SpawnedPipeline, +) -> Result { + for mut child in pipeline.children.drain(..) { + child.wait()?; + } + + Ok(Outcome::ok()) + // let self_group_id = process::group_id(); + + // for mut child in pipeline.children.drain(..) { + // let status = child.wait()?; + + // if !status.success() { + // if interactive { + // set_terminal_group(self_group_id).ok(); + // } + // return Ok(Outcome::from(status)); + // } + // } + + // if interactive { + // set_terminal_group(self_group_id).ok(); + // } + + // Ok(Outcome::ok()) +} diff --git a/userspace/term/src/main.rs b/userspace/term/src/main.rs index 5f901826..4d66d0a6 100644 --- a/userspace/term/src/main.rs +++ b/userspace/term/src/main.rs @@ -4,7 +4,7 @@ use std::{ fs::File, io::{Read, Write}, os::{ - fd::{AsRawFd, FromRawFd, RawFd}, + fd::{self, AsRawFd, FromRawFd, IntoRawFd, RawFd}, yggdrasil::{ self, io::{ @@ -262,14 +262,19 @@ impl Terminal<'_> { poll.add(conn_fd)?; poll.add(pty_master_fd)?; - let pty_slave_fd = pty_slave.as_raw_fd(); + let pty_slave_stdin = pty_slave.into_raw_fd(); + let pty_slave_stdout = fd::clone_fd(pty_slave_stdin)?; + let pty_slave_stderr = fd::clone_fd(pty_slave_stdin)?; + + debug_trace!("stdin = {pty_slave_stdin:?}, stdout = {pty_slave_stdout:?}, stderr = {pty_slave_stderr:?}"); + let group_id = yggdrasil::process::create_process_group(); let shell = unsafe { Command::new("/bin/sh") .arg("-l") - .stdin(Stdio::from_raw_fd(pty_slave_fd)) - .stdout(Stdio::from_raw_fd(pty_slave_fd)) - .stderr(Stdio::from_raw_fd(pty_slave_fd)) + .stdin(Stdio::from_raw_fd(pty_slave_stdin)) + .stdout(Stdio::from_raw_fd(pty_slave_stdout)) + .stderr(Stdio::from_raw_fd(pty_slave_stderr)) .process_group(group_id) .gain_terminal(0) .spawn()?