2022-09-15 20:16:37 +00:00
|
|
|
use std::{
|
|
|
|
borrow::Cow,
|
|
|
|
ffi::{OsStr, OsString},
|
|
|
|
fs::File,
|
|
|
|
io,
|
2022-10-20 13:06:53 +00:00
|
|
|
os::unix::{
|
|
|
|
io::{AsRawFd, FromRawFd, RawFd},
|
|
|
|
process::CommandExt,
|
|
|
|
},
|
|
|
|
process::{self, Command, Stdio},
|
2022-09-15 20:16:37 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
use nix::{
|
2022-09-23 21:07:57 +00:00
|
|
|
errno::Errno,
|
2022-09-15 20:16:37 +00:00
|
|
|
fcntl::OFlag,
|
2022-09-23 21:07:57 +00:00
|
|
|
libc,
|
2022-09-23 22:04:16 +00:00
|
|
|
sys::{
|
2022-09-25 12:17:55 +00:00
|
|
|
signal::{self, SaFlags, SigAction, SigHandler},
|
|
|
|
signalfd::SigSet,
|
2022-09-23 22:04:16 +00:00
|
|
|
wait::{self, WaitPidFlag, WaitStatus},
|
|
|
|
},
|
2022-10-20 13:06:53 +00:00
|
|
|
unistd::{self, ForkResult, Pid},
|
2022-09-15 20:16:37 +00:00
|
|
|
};
|
|
|
|
use oyster_parser::ast::{self, Redirect};
|
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
use crate::{builtins::Builtin, RuntimeError, Shell, Status};
|
|
|
|
|
|
|
|
/// The specific kind of the command.
|
|
|
|
enum CommandKind {
|
|
|
|
External,
|
|
|
|
Builtin(Builtin),
|
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
|
|
|
|
/// A command that's ready to be run.
|
|
|
|
struct PreparedCommand<'a> {
|
2022-10-20 13:06:53 +00:00
|
|
|
kind: CommandKind,
|
2022-09-15 20:16:37 +00:00
|
|
|
cmd: Cow<'a, str>,
|
2022-10-20 13:06:53 +00:00
|
|
|
args: Vec<Cow<'a, str>>,
|
2022-09-15 20:16:37 +00:00
|
|
|
redirect: Redirect,
|
|
|
|
stdin: Option<File>,
|
|
|
|
stdout: Option<File>,
|
|
|
|
stderr: Option<File>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> PreparedCommand<'a> {
|
2022-10-10 17:52:16 +00:00
|
|
|
/// Create a new PreparedCommand.
|
2022-10-20 13:06:53 +00:00
|
|
|
fn new(command: &'a ast::Command, shell: &Shell) -> Self {
|
|
|
|
let mut words = command.0.iter().map(|word| {
|
|
|
|
let mut words = word.0.iter();
|
|
|
|
|
2022-10-20 13:18:23 +00:00
|
|
|
let mut s = match words.next().unwrap() {
|
2022-10-20 13:06:53 +00:00
|
|
|
ast::WordPart::Text(text) => Cow::from(*text),
|
|
|
|
};
|
|
|
|
|
|
|
|
for part in words {
|
|
|
|
match part {
|
|
|
|
ast::WordPart::Text(text) => s.to_mut().push_str(text),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s
|
|
|
|
});
|
2022-10-20 13:18:23 +00:00
|
|
|
let cmd = words.next().unwrap();
|
2022-10-20 13:06:53 +00:00
|
|
|
let args = words.collect();
|
2022-10-10 17:52:16 +00:00
|
|
|
let redirect = command.1;
|
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
let kind = match shell.builtins().get(&cmd) {
|
2022-10-20 14:00:17 +00:00
|
|
|
Some(builtin) => CommandKind::Builtin(*builtin),
|
2022-10-20 13:06:53 +00:00
|
|
|
None => CommandKind::External,
|
|
|
|
};
|
|
|
|
|
2022-10-10 17:52:16 +00:00
|
|
|
PreparedCommand {
|
2022-10-20 13:06:53 +00:00
|
|
|
kind,
|
2022-10-10 17:52:16 +00:00
|
|
|
cmd,
|
|
|
|
args,
|
|
|
|
redirect,
|
|
|
|
stdin: None,
|
|
|
|
stdout: None,
|
|
|
|
stderr: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-15 20:16:37 +00:00
|
|
|
/// Run this command with the given context.
|
2022-10-20 13:06:53 +00:00
|
|
|
fn spawn(self, shell: &mut Shell, pgid: &mut Pid) -> Result<(), RuntimeError> {
|
|
|
|
let pre_exec = {
|
|
|
|
let pgid = *pgid;
|
|
|
|
move || {
|
|
|
|
let _ = unistd::setpgid(Pid::from_raw(0), pgid);
|
|
|
|
let _ = unistd::tcsetpgrp(libc::STDIN_FILENO, unistd::getpid());
|
|
|
|
|
|
|
|
let default = SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty());
|
|
|
|
unsafe {
|
|
|
|
let _ = signal::sigaction(signal::Signal::SIGTSTP, &default);
|
|
|
|
let _ = signal::sigaction(signal::Signal::SIGTTOU, &default);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let args = self.args.iter().map(|w| match w {
|
2022-09-15 20:16:37 +00:00
|
|
|
Cow::Borrowed(s) => Cow::Borrowed(OsStr::new(s)),
|
|
|
|
Cow::Owned(s) => Cow::Owned(OsString::from(s)),
|
|
|
|
});
|
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
let child = match self.kind {
|
|
|
|
CommandKind::External => {
|
|
|
|
let mut cmd = Command::new(self.cmd.as_ref());
|
|
|
|
cmd.args(args);
|
|
|
|
cmd.stdin(self.stdin.map_or(Stdio::inherit(), Stdio::from));
|
|
|
|
cmd.stdout(self.stdout.map_or(Stdio::inherit(), Stdio::from));
|
|
|
|
cmd.stderr(self.stderr.map_or(Stdio::inherit(), Stdio::from));
|
2022-09-25 12:17:55 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
unsafe {
|
|
|
|
cmd.pre_exec(pre_exec);
|
|
|
|
}
|
2022-09-25 12:17:55 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
let child = match cmd.spawn() {
|
|
|
|
Ok(child) => child,
|
|
|
|
Err(err) => {
|
|
|
|
return Err(match err.kind() {
|
|
|
|
io::ErrorKind::NotFound => {
|
|
|
|
RuntimeError::CommandNotFound(self.cmd.into_owned())
|
|
|
|
}
|
|
|
|
io::ErrorKind::PermissionDenied => {
|
|
|
|
RuntimeError::PermissionDenied(self.cmd.into_owned())
|
|
|
|
}
|
|
|
|
_ => RuntimeError::SpawnFailed(err),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
};
|
2022-09-23 21:07:57 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
Pid::from_raw(child.id() as i32)
|
|
|
|
}
|
|
|
|
CommandKind::Builtin(builtin) => {
|
|
|
|
match unsafe { unistd::fork() }.map_err(RuntimeError::ForkFailed)? {
|
|
|
|
ForkResult::Parent { child } => child,
|
|
|
|
ForkResult::Child => {
|
|
|
|
fn redirect(old: Option<File>, new: RawFd) {
|
|
|
|
if let Some(old) = old {
|
|
|
|
let _ = unistd::dup2(old.as_raw_fd(), new);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
redirect(self.stdin, libc::STDIN_FILENO);
|
|
|
|
redirect(self.stdout, libc::STDOUT_FILENO);
|
|
|
|
redirect(self.stderr, libc::STDERR_FILENO);
|
|
|
|
|
|
|
|
let _ = pre_exec();
|
|
|
|
(builtin.fun)(shell, &self.args);
|
|
|
|
process::exit(0);
|
2022-09-15 20:16:37 +00:00
|
|
|
}
|
2022-10-20 13:06:53 +00:00
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
if *pgid == Pid::from_raw(0) {
|
2022-10-20 13:06:53 +00:00
|
|
|
*pgid = child;
|
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
|
|
|
|
// prevent race conditions
|
2022-10-20 13:06:53 +00:00
|
|
|
let _ = unistd::setpgid(child, *pgid);
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
Ok(())
|
2022-09-15 20:16:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Shell {
|
|
|
|
pub(crate) fn run_pipeline(
|
|
|
|
&mut self,
|
|
|
|
pipeline: &ast::Pipeline,
|
|
|
|
) -> Result<Status, RuntimeError> {
|
|
|
|
let mut pgid = Pid::from_raw(0);
|
2022-09-23 22:03:10 +00:00
|
|
|
let status = (|| {
|
2022-10-20 13:06:53 +00:00
|
|
|
let mut cmds = pipeline.0.iter();
|
|
|
|
|
|
|
|
let mut last_cmd = cmds
|
|
|
|
.next()
|
|
|
|
.map(|cmd| PreparedCommand::new(cmd, self))
|
2022-10-20 13:18:23 +00:00
|
|
|
.unwrap();
|
2022-10-20 13:06:53 +00:00
|
|
|
for cmd in cmds {
|
|
|
|
let mut cmd = PreparedCommand::new(cmd, self);
|
2022-09-23 22:03:10 +00:00
|
|
|
let (output, input) = create_pipe()?;
|
|
|
|
cmd.stdin = Some(output);
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-09-23 22:03:10 +00:00
|
|
|
match last_cmd.redirect {
|
|
|
|
Redirect::None => (),
|
|
|
|
Redirect::Stdout => last_cmd.stdout = Some(input),
|
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
last_cmd.spawn(self, &mut pgid)?;
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-09-23 22:03:10 +00:00
|
|
|
last_cmd = cmd;
|
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-10-20 13:06:53 +00:00
|
|
|
last_cmd.spawn(self, &mut pgid)?;
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-09-23 22:03:10 +00:00
|
|
|
wait_pgid(pgid)
|
|
|
|
})();
|
2022-09-15 20:16:37 +00:00
|
|
|
|
2022-09-23 22:04:16 +00:00
|
|
|
if status.is_err() && pgid != Pid::from_raw(0) {
|
|
|
|
let _ = signal::killpg(pgid, signal::Signal::SIGTERM);
|
|
|
|
let _ = signal::killpg(pgid, signal::Signal::SIGCONT);
|
|
|
|
}
|
|
|
|
|
2022-09-23 21:07:57 +00:00
|
|
|
let _ = unistd::tcsetpgrp(libc::STDIN_FILENO, unistd::getpgid(None).unwrap());
|
|
|
|
|
2022-09-23 22:03:10 +00:00
|
|
|
status
|
2022-09-23 21:07:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// 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)),
|
|
|
|
},
|
|
|
|
}
|
2022-09-15 20:16:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a new unix pipe.
|
|
|
|
fn create_pipe() -> Result<(File, File), RuntimeError> {
|
|
|
|
let (output, input) =
|
|
|
|
unistd::pipe2(OFlag::O_CLOEXEC).map_err(RuntimeError::PipeCreationFailed)?;
|
|
|
|
|
|
|
|
Ok(unsafe { (File::from_raw_fd(output), File::from_raw_fd(input)) })
|
|
|
|
}
|