summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-01-05 16:40:58 -0500
committerJesse Luehrs <doy@tozt.net>2022-01-05 16:40:58 -0500
commitd22732d1c3f4629f81f8fe0212dcc06df40be81f (patch)
tree01e7961cdbd2b944383ff0a54f422c512d1c9a1b
parent2e773c6836463339c73b532d66edf334d412daf5 (diff)
downloadnbsh-d22732d1c3f4629f81f8fe0212dcc06df40be81f.tar.gz
nbsh-d22732d1c3f4629f81f8fe0212dcc06df40be81f.zip
basic input/output redirection
-rw-r--r--src/parse.rs72
-rw-r--r--src/pipeline/command.rs99
2 files changed, 158 insertions, 13 deletions
diff --git a/src/parse.rs b/src/parse.rs
index 45c83c5..9abfaac 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -4,10 +4,60 @@ use pest::Parser as _;
#[grammar = "shell.pest"]
struct Shell;
+#[derive(Debug, Clone)]
+pub enum RedirectTarget {
+ Fd(std::os::unix::io::RawFd),
+ File(std::path::PathBuf),
+}
+
+#[derive(Debug, Clone)]
+pub enum Direction {
+ In,
+ Out,
+}
+
+impl Direction {
+ fn parse(c: u8) -> Option<Self> {
+ Some(match c {
+ b'>' => Self::Out,
+ b'<' => Self::In,
+ _ => return None,
+ })
+ }
+}
+
+#[derive(Debug, Clone)]
+pub struct Redirect {
+ pub from: std::os::unix::io::RawFd,
+ pub to: RedirectTarget,
+ pub dir: Direction,
+}
+
+impl Redirect {
+ fn parse(s: &str) -> Self {
+ let (from, to) = s.split_once(&['<', '>'][..]).unwrap();
+ let dir = Direction::parse(s.as_bytes()[from.len()]).unwrap();
+ let from = if from.is_empty() {
+ match dir {
+ Direction::In => 0,
+ Direction::Out => 1,
+ }
+ } else {
+ from.parse().unwrap()
+ };
+ let to = to.strip_prefix('&').map_or_else(
+ || RedirectTarget::File(to.into()),
+ |fd| RedirectTarget::Fd(fd.parse().unwrap()),
+ );
+ Self { from, to, dir }
+ }
+}
+
#[derive(Debug)]
pub struct Word {
word: String,
interpolate: bool,
+ quoted: bool,
}
impl Word {
@@ -24,6 +74,10 @@ impl Word {
word.as_rule(),
Rule::bareword | Rule::double_string
),
+ quoted: matches!(
+ word.as_rule(),
+ Rule::single_string | Rule::double_string
+ ),
}
}
}
@@ -32,6 +86,7 @@ impl Word {
pub struct Exe {
exe: Word,
args: Vec<Word>,
+ redirects: Vec<Redirect>,
}
impl Exe {
@@ -39,8 +94,17 @@ impl Exe {
assert!(matches!(pair.as_rule(), Rule::exe));
let mut iter = pair.into_inner();
let exe = Word::build_ast(iter.next().unwrap());
- let args = iter.map(Word::build_ast).collect();
- Self { exe, args }
+ let (args, redirects): (_, Vec<_>) =
+ iter.map(Word::build_ast).partition(|word| {
+ word.quoted || !word.word.contains(&['<', '>'][..])
+ });
+ let redirects =
+ redirects.iter().map(|r| Redirect::parse(&r.word)).collect();
+ Self {
+ exe,
+ args,
+ redirects,
+ }
}
pub fn exe(&self) -> &str {
@@ -51,6 +115,10 @@ impl Exe {
self.args.iter().map(|arg| arg.word.as_ref())
}
+ pub fn redirects(&self) -> &[Redirect] {
+ &self.redirects
+ }
+
pub fn shift(&mut self) {
self.exe = self.args.remove(0);
}
diff --git a/src/pipeline/command.rs b/src/pipeline/command.rs
index a6b363a..3a898d1 100644
--- a/src/pipeline/command.rs
+++ b/src/pipeline/command.rs
@@ -3,6 +3,10 @@ use crate::pipeline::prelude::*;
pub struct Command {
inner: Inner,
exe: String,
+ redirects: Vec<crate::parse::Redirect>,
+ pre_exec: Option<
+ Box<dyn FnMut() -> std::io::Result<()> + Send + Sync + 'static>,
+ >,
}
pub enum Inner {
Binary(async_std::process::Command),
@@ -12,32 +16,41 @@ pub enum Inner {
impl Command {
pub fn new(exe: crate::parse::Exe) -> Self {
let exe_str = exe.exe().to_string();
+ let redirects = exe.redirects().to_vec();
Self {
inner: super::builtins::Command::new(exe).map_or_else(
|exe| Self::new_binary(exe).inner,
Inner::Builtin,
),
exe: exe_str,
+ redirects,
+ pre_exec: None,
}
}
#[allow(clippy::needless_pass_by_value)]
pub fn new_binary(exe: crate::parse::Exe) -> Self {
let exe_str = exe.exe().to_string();
+ let redirects = exe.redirects().to_vec();
let mut cmd = async_std::process::Command::new(exe.exe());
cmd.args(exe.args());
Self {
inner: Inner::Binary(cmd),
exe: exe_str,
+ redirects,
+ pre_exec: None,
}
}
pub fn new_builtin(exe: crate::parse::Exe) -> Self {
let exe_str = exe.exe().to_string();
+ let redirects = exe.redirects().to_vec();
Self {
inner: super::builtins::Command::new(exe)
.map_or_else(|_| todo!(), Inner::Builtin),
exe: exe_str,
+ redirects,
+ pre_exec: None,
}
}
@@ -80,28 +93,51 @@ impl Command {
where
F: 'static + FnMut() -> std::io::Result<()> + Send + Sync,
{
- match &mut self.inner {
- Inner::Binary(cmd) => {
- cmd.pre_exec(f);
- }
- Inner::Builtin(cmd) => {
- cmd.pre_exec(f);
- }
- }
+ self.pre_exec = Some(Box::new(f));
}
pub fn spawn(self, env: &Env) -> anyhow::Result<Child> {
- match self.inner {
+ 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) => {
+ unsafe { cmd.pre_exec(pre_exec) };
Ok(Child::Binary(cmd.spawn().map_err(|e| {
anyhow::anyhow!(
"{}: {}",
crate::format::io_error(&e),
- self.exe
+ exe
)
})?))
}
- Inner::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)),
+ Inner::Builtin(mut cmd) => {
+ unsafe { cmd.pre_exec(pre_exec) };
+ Ok(Child::Builtin(cmd.spawn(env)?))
+ }
}
}
}
@@ -138,3 +174,44 @@ impl<'a> Child<'a> {
})
}
}
+
+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) => {
+ use nix::fcntl::OFlag;
+ use nix::sys::stat::Mode;
+ let fd = match redirect.dir {
+ crate::parse::Direction::In => nix::fcntl::open(
+ path,
+ OFlag::O_NOCTTY | OFlag::O_RDONLY,
+ Mode::empty(),
+ )?,
+ crate::parse::Direction::Out => nix::fcntl::open(
+ path,
+ OFlag::O_CREAT
+ | OFlag::O_NOCTTY
+ | OFlag::O_WRONLY
+ | OFlag::O_TRUNC,
+ Mode::S_IRUSR
+ | Mode::S_IWUSR
+ | Mode::S_IRGRP
+ | Mode::S_IWGRP
+ | Mode::S_IROTH
+ | Mode::S_IWOTH,
+ )?,
+ };
+ if fd != redirect.from {
+ nix::unistd::dup2(fd, redirect.from)?;
+ nix::unistd::close(fd)?;
+ }
+ }
+ }
+ }
+ Ok(())
+}