summaryrefslogtreecommitdiffstats
path: root/src/runner/command.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/runner/command.rs')
-rw-r--r--src/runner/command.rs203
1 files changed, 203 insertions, 0 deletions
diff --git a/src/runner/command.rs b/src/runner/command.rs
new file mode 100644
index 0000000..cbc8dee
--- /dev/null
+++ b/src/runner/command.rs
@@ -0,0 +1,203 @@
+use crate::runner::prelude::*;
+
+pub struct Command {
+ inner: Inner,
+ exe: std::path::PathBuf,
+ redirects: Vec<crate::parse::Redirect>,
+ pre_exec: Option<
+ Box<dyn FnMut() -> std::io::Result<()> + Send + Sync + 'static>,
+ >,
+}
+
+impl Command {
+ pub fn new(exe: crate::parse::Exe, io: super::builtins::Io) -> Self {
+ let exe_path = exe.exe().to_path_buf();
+ let redirects = exe.redirects().to_vec();
+ Self {
+ inner: super::builtins::Command::new(exe, io).map_or_else(
+ |exe| Self::new_binary(&exe).inner,
+ Inner::Builtin,
+ ),
+ exe: exe_path,
+ redirects,
+ pre_exec: None,
+ }
+ }
+
+ pub fn new_binary(exe: &crate::parse::Exe) -> Self {
+ let exe_path = exe.exe().to_path_buf();
+ let redirects = exe.redirects().to_vec();
+ let mut cmd = tokio::process::Command::new(exe.exe());
+ cmd.args(exe.args());
+ Self {
+ inner: Inner::Binary(cmd),
+ exe: exe_path,
+ redirects,
+ pre_exec: None,
+ }
+ }
+
+ pub fn new_builtin(
+ exe: crate::parse::Exe,
+ io: super::builtins::Io,
+ ) -> Self {
+ let exe_path = exe.exe().to_path_buf();
+ let redirects = exe.redirects().to_vec();
+ Self {
+ inner: super::builtins::Command::new(exe, io)
+ .map_or_else(|_| todo!(), Inner::Builtin),
+ exe: exe_path,
+ redirects,
+ pre_exec: None,
+ }
+ }
+
+ pub fn stdin(&mut self, fh: std::fs::File) {
+ match &mut self.inner {
+ Inner::Binary(cmd) => {
+ cmd.stdin(fh);
+ }
+ Inner::Builtin(cmd) => {
+ cmd.stdin(fh);
+ }
+ }
+ }
+
+ pub fn stdout(&mut self, fh: std::fs::File) {
+ match &mut self.inner {
+ Inner::Binary(cmd) => {
+ cmd.stdout(fh);
+ }
+ Inner::Builtin(cmd) => {
+ cmd.stdout(fh);
+ }
+ }
+ }
+
+ pub fn stderr(&mut self, fh: std::fs::File) {
+ match &mut self.inner {
+ Inner::Binary(cmd) => {
+ cmd.stderr(fh);
+ }
+ Inner::Builtin(cmd) => {
+ cmd.stderr(fh);
+ }
+ }
+ }
+
+ // Safety: see pre_exec in tokio::process::Command (this is just a
+ // wrapper)
+ pub unsafe fn pre_exec<F>(&mut self, f: F)
+ where
+ F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
+ {
+ self.pre_exec = Some(Box::new(f));
+ }
+
+ pub fn spawn(self, env: &Env) -> Result<Child> {
+ let Self {
+ inner,
+ exe,
+ redirects,
+ pre_exec,
+ } = self;
+
+ #[allow(clippy::as_conversions)]
+ let pre_exec = pre_exec.map_or_else(
+ || {
+ let redirects = redirects.clone();
+ Box::new(move || {
+ apply_redirects(&redirects)?;
+ Ok(())
+ })
+ as Box<dyn FnMut() -> std::io::Result<()> + Send + Sync>
+ },
+ |mut pre_exec| {
+ let redirects = redirects.clone();
+ Box::new(move || {
+ apply_redirects(&redirects)?;
+ pre_exec()?;
+ Ok(())
+ })
+ },
+ );
+ match inner {
+ Inner::Binary(mut cmd) => {
+ // Safety: open, dup2, and close are async-signal-safe
+ // functions
+ unsafe { cmd.pre_exec(pre_exec) };
+ Ok(Child::Binary(cmd.spawn().map_err(|e| {
+ anyhow!(
+ "{}: {}",
+ crate::format::io_error(&e),
+ exe.display()
+ )
+ })?))
+ }
+ Inner::Builtin(mut cmd) => {
+ // Safety: open, dup2, and close are async-signal-safe
+ // functions
+ unsafe { cmd.pre_exec(pre_exec) };
+ cmd.apply_redirects(&redirects);
+ Ok(Child::Builtin(cmd.spawn(env)?))
+ }
+ }
+ }
+}
+
+pub enum Inner {
+ Binary(tokio::process::Command),
+ Builtin(super::builtins::Command),
+}
+
+pub enum Child {
+ Binary(tokio::process::Child),
+ Builtin(super::builtins::Child),
+}
+
+impl Child {
+ pub fn id(&self) -> Option<u32> {
+ match self {
+ Self::Binary(child) => child.id(),
+ Self::Builtin(child) => child.id(),
+ }
+ }
+
+ pub fn status(
+ self,
+ ) -> std::pin::Pin<
+ Box<
+ dyn std::future::Future<Output = Result<std::process::ExitStatus>>
+ + Send
+ + Sync,
+ >,
+ > {
+ Box::pin(async move {
+ match self {
+ // this case is handled by waitpid
+ Self::Binary(_) => unreachable!(),
+ Self::Builtin(child) => Ok(child.status().await?),
+ }
+ })
+ }
+}
+
+fn apply_redirects(
+ redirects: &[crate::parse::Redirect],
+) -> std::io::Result<()> {
+ for redirect in redirects {
+ match &redirect.to {
+ crate::parse::RedirectTarget::Fd(fd) => {
+ nix::unistd::dup2(*fd, redirect.from)?;
+ }
+ crate::parse::RedirectTarget::File(path) => {
+ let fd = redirect.dir.open(path)?;
+ if fd != redirect.from {
+ nix::unistd::dup2(fd, redirect.from)?;
+ nix::unistd::close(fd)?;
+ }
+ }
+ }
+ }
+ Ok(())
+}