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};
fn main() {
let mut shell = Shell::default();
let mut shell = Shell::new().unwrap();
let mut exit_code = Status::SUCCESS;
loop {

View file

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

View file

@ -5,6 +5,10 @@ mod pipeline;
use std::io;
use nix::sys::{
signal::{self, SaFlags, SigAction, SigHandler, Signal},
signalfd::SigSet,
};
use oyster_parser::ast;
use thiserror::Error;
@ -33,12 +37,25 @@ pub enum RuntimeError {
#[error("failed to spawn process: {0}")]
SpawnFailed(#[source] io::Error),
#[error("waitpid error: {0}")]
WaidPid(nix::Error),
}
#[derive(Default)]
pub struct 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> {
let mut last_status = Status::SUCCESS;

View file

@ -3,16 +3,16 @@ use std::{
ffi::{OsStr, OsString},
fs::File,
io,
os::unix::{
io::FromRawFd,
process::{CommandExt, ExitStatusExt},
},
os::unix::{io::FromRawFd, process::CommandExt},
process::{Child, Command, Stdio},
slice::Iter,
};
use nix::{
errno::Errno,
fcntl::OFlag,
libc,
sys::wait::{self, WaitPidFlag, WaitStatus},
unistd::{self, Pid},
};
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.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() {
Ok(child) => child,
Err(err) => {
@ -92,7 +101,6 @@ impl Shell {
&mut self,
pipeline: &ast::Pipeline,
) -> Result<Status, RuntimeError> {
let mut children = Vec::with_capacity(pipeline.0.len());
let mut cmds = pipeline.0.iter().map(PreparedCommand::from);
let mut pgid = Pid::from_raw(0);
@ -106,28 +114,40 @@ impl Shell {
Redirect::Stdout => last_cmd.stdout = Some(input),
}
children.push(last_cmd.spawn(&mut pgid)?);
last_cmd.spawn(&mut pgid)?;
last_cmd = cmd;
}
children.push(last_cmd.spawn(&mut pgid)?);
last_cmd.spawn(&mut pgid)?;
// TODO: kill children if error occured
// TODO: set foreground group then wait for foreground group
let mut last_status = Status::SUCCESS;
for mut c in children {
// TODO: handle error
let status = c.wait().unwrap();
last_status = Status(
status
.code()
.unwrap_or_else(|| Status::SIG_BASE.0 + status.signal().unwrap()),
);
let status = wait_pgid(pgid)?;
let _ = unistd::tcsetpgrp(libc::STDIN_FILENO, unistd::getpgid(None).unwrap());
Ok(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)),
},
}
Ok(last_status)
}
}

View file

@ -76,7 +76,7 @@ fn simple_command() {
};
let actual = collect_output(|| {
let mut shell = Shell::default();
let mut shell = Shell::new().unwrap();
shell.run(&ast).unwrap();
});
@ -107,7 +107,7 @@ fn pipeline() {
};
let actual = collect_output(|| {
let mut shell = Shell::default();
let mut shell = Shell::new().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
let mut shell = Shell::default();
let mut shell = Shell::new().unwrap();
let actual = shell.run(&ast);
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);
assert_snapshot!(actual);
@ -164,7 +164,7 @@ fn multipart_word() {
};
let actual = collect_output(|| {
let mut shell = Shell::default();
let mut shell = Shell::new().unwrap();
shell.run(&ast).unwrap();
});