feat(runtime): set foreground job properly
This commit is contained in:
parent
0bfa8b3c8f
commit
f0698457ed
5 changed files with 64 additions and 27 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)),
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue