feat(runtime): set foreground job properly

This commit is contained in:
buffet 2022-09-23 21:07:57 +00:00
parent 0bfa8b3c8f
commit f0698457ed
5 changed files with 64 additions and 27 deletions

View file

@ -5,7 +5,7 @@ use oyster_parser::ast::Code;
use oyster_runtime::{Shell, Status}; use oyster_runtime::{Shell, Status};
fn main() { fn main() {
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
let mut exit_code = Status::SUCCESS; let mut exit_code = Status::SUCCESS;
loop { loop {

View file

@ -13,7 +13,7 @@ thiserror = "1.0.35"
[dependencies.nix] [dependencies.nix]
version = "~0.25.0" version = "~0.25.0"
default-features = false default-features = false
features = [ "fs", "ioctl", "process", "term" ] features = [ "fs", "ioctl", "process", "signal", "term" ]
[dev-dependencies] [dev-dependencies]
insta = "^1.15.0" insta = "^1.15.0"

View file

@ -5,6 +5,10 @@ mod pipeline;
use std::io; use std::io;
use nix::sys::{
signal::{self, SaFlags, SigAction, SigHandler, Signal},
signalfd::SigSet,
};
use oyster_parser::ast; use oyster_parser::ast;
use thiserror::Error; use thiserror::Error;
@ -33,12 +37,25 @@ pub enum RuntimeError {
#[error("failed to spawn process: {0}")] #[error("failed to spawn process: {0}")]
SpawnFailed(#[source] io::Error), SpawnFailed(#[source] io::Error),
#[error("waitpid error: {0}")]
WaidPid(nix::Error),
} }
#[derive(Default)]
pub struct Shell; pub struct Shell;
impl Shell { impl Shell {
pub fn new() -> Result<Shell, nix::Error> {
let ignore = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty());
unsafe {
signal::sigaction(Signal::SIGTSTP, &ignore)?;
signal::sigaction(Signal::SIGTTOU, &ignore)?;
}
Ok(Shell)
}
pub fn run<'a>(&mut self, code: &'a ast::Code) -> Result<Status, RuntimeError> { pub fn run<'a>(&mut self, code: &'a ast::Code) -> Result<Status, RuntimeError> {
let mut last_status = Status::SUCCESS; let mut last_status = Status::SUCCESS;

View file

@ -3,16 +3,16 @@ use std::{
ffi::{OsStr, OsString}, ffi::{OsStr, OsString},
fs::File, fs::File,
io, io,
os::unix::{ os::unix::{io::FromRawFd, process::CommandExt},
io::FromRawFd,
process::{CommandExt, ExitStatusExt},
},
process::{Child, Command, Stdio}, process::{Child, Command, Stdio},
slice::Iter, slice::Iter,
}; };
use nix::{ use nix::{
errno::Errno,
fcntl::OFlag, fcntl::OFlag,
libc,
sys::wait::{self, WaitPidFlag, WaitStatus},
unistd::{self, Pid}, unistd::{self, Pid},
}; };
use oyster_parser::ast::{self, Redirect}; use oyster_parser::ast::{self, Redirect};
@ -44,6 +44,15 @@ impl<'a> PreparedCommand<'a> {
cmd.stderr(self.stderr.map_or(Stdio::inherit(), Stdio::from)); cmd.stderr(self.stderr.map_or(Stdio::inherit(), Stdio::from));
cmd.process_group(pgid.as_raw()); cmd.process_group(pgid.as_raw());
if *pgid == Pid::from_raw(0) {
unsafe {
cmd.pre_exec(move || {
let _ = unistd::tcsetpgrp(libc::STDIN_FILENO, unistd::getpid());
Ok(())
});
}
}
let child = match cmd.spawn() { let child = match cmd.spawn() {
Ok(child) => child, Ok(child) => child,
Err(err) => { Err(err) => {
@ -92,7 +101,6 @@ impl Shell {
&mut self, &mut self,
pipeline: &ast::Pipeline, pipeline: &ast::Pipeline,
) -> Result<Status, RuntimeError> { ) -> Result<Status, RuntimeError> {
let mut children = Vec::with_capacity(pipeline.0.len());
let mut cmds = pipeline.0.iter().map(PreparedCommand::from); let mut cmds = pipeline.0.iter().map(PreparedCommand::from);
let mut pgid = Pid::from_raw(0); let mut pgid = Pid::from_raw(0);
@ -106,28 +114,40 @@ impl Shell {
Redirect::Stdout => last_cmd.stdout = Some(input), Redirect::Stdout => last_cmd.stdout = Some(input),
} }
children.push(last_cmd.spawn(&mut pgid)?); last_cmd.spawn(&mut pgid)?;
last_cmd = cmd; last_cmd = cmd;
} }
children.push(last_cmd.spawn(&mut pgid)?); last_cmd.spawn(&mut pgid)?;
// TODO: kill children if error occured // TODO: kill children if error occured
// TODO: set foreground group then wait for foreground group
let mut last_status = Status::SUCCESS; let status = wait_pgid(pgid)?;
for mut c in children { let _ = unistd::tcsetpgrp(libc::STDIN_FILENO, unistd::getpgid(None).unwrap());
// TODO: handle error
let status = c.wait().unwrap(); Ok(status)
last_status = Status( }
status
.code()
.unwrap_or_else(|| Status::SIG_BASE.0 + status.signal().unwrap()),
);
} }
Ok(last_status) /// Wait for all processes of the specified job to terminate.
fn wait_pgid(pgid: Pid) -> Result<Status, RuntimeError> {
let pgid = Pid::from_raw(-pgid.as_raw());
let mut last_status = Status::SUCCESS;
loop {
match wait::waitpid(pgid, Some(WaitPidFlag::WUNTRACED)) {
Ok(WaitStatus::Exited(_, code)) => last_status = Status(code),
Ok(WaitStatus::Signaled(_, signal, _)) => {
last_status = Status(Status::SIG_BASE.0 + signal as i32)
}
Ok(WaitStatus::Stopped(_, _)) => todo!("put job into background job list"),
Ok(_) => (),
Err(err) => match err {
Errno::ECHILD => break Ok(last_status), // no more children
_ => break Err(RuntimeError::WaidPid(err)),
},
}
} }
} }

View file

@ -76,7 +76,7 @@ fn simple_command() {
}; };
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
shell.run(&ast).unwrap(); shell.run(&ast).unwrap();
}); });
@ -107,7 +107,7 @@ fn pipeline() {
}; };
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
shell.run(&ast).unwrap(); shell.run(&ast).unwrap();
}); });
@ -126,7 +126,7 @@ fn command_not_found() {
}; };
// XXX: this relies on the command actually not existing, as unsetting PATH is rather complex // XXX: this relies on the command actually not existing, as unsetting PATH is rather complex
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
let actual = shell.run(&ast); let actual = shell.run(&ast);
assert_snapshot!(actual); assert_snapshot!(actual);
@ -143,7 +143,7 @@ fn permission_denied() {
)]))]) )]))])
}; };
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
let actual = shell.run(&ast); let actual = shell.run(&ast);
assert_snapshot!(actual); assert_snapshot!(actual);
@ -164,7 +164,7 @@ fn multipart_word() {
}; };
let actual = collect_output(|| { let actual = collect_output(|| {
let mut shell = Shell::default(); let mut shell = Shell::new().unwrap();
shell.run(&ast).unwrap(); shell.run(&ast).unwrap();
}); });