summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Luehrs <doy@tozt.net>2022-01-03 01:19:02 -0500
committerJesse Luehrs <doy@tozt.net>2022-01-03 01:46:28 -0500
commit234d71241399ae40d498d7953e9516f5cc5a471c (patch)
tree12ff8e2e983a7b4ffdf082780ab3cd473dbf86ac
parent0115a566afa763c9732e03765d50a2ace4008d18 (diff)
downloadnbsh-234d71241399ae40d498d7953e9516f5cc5a471c.tar.gz
nbsh-234d71241399ae40d498d7953e9516f5cc5a471c.zip
start adding back builtin support
-rw-r--r--Cargo.lock12
-rw-r--r--Cargo.toml1
-rw-r--r--src/builtins.rs285
-rw-r--r--src/command.rs58
4 files changed, 194 insertions, 162 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 63a8de5..27fd957 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -109,6 +109,17 @@ dependencies = [
]
[[package]]
+name = "async-recursion"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "async-std"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -492,6 +503,7 @@ name = "nbsh"
version = "0.1.0"
dependencies = [
"anyhow",
+ "async-recursion",
"async-std",
"futures-lite",
"futures-util",
diff --git a/Cargo.toml b/Cargo.toml
index e132517..ca89db4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ license = "MIT"
[dependencies]
anyhow = "1.0.52"
+async-recursion = "0.3.2"
async-std = { version = "1.10.0", features = ["unstable"] }
futures-lite = "1.12.0"
futures-util = "0.3.19"
diff --git a/src/builtins.rs b/src/builtins.rs
index e55f6ec..014eadd 100644
--- a/src/builtins.rs
+++ b/src/builtins.rs
@@ -1,79 +1,43 @@
use async_std::io::WriteExt as _;
use std::os::unix::process::ExitStatusExt as _;
-type BuiltinFunc = &'static (dyn for<'a> Fn(
- &'a crate::parse::Exe,
- &'a crate::command::Env,
-) -> std::pin::Pin<
- Box<
- dyn std::future::Future<Output = std::process::ExitStatus>
- + Sync
- + Send
- + 'a,
- >,
-> + Sync
+type Builtin = &'static (dyn Fn(
+ &crate::parse::Exe,
+ &crate::command::Env,
+) -> anyhow::Result<Child>
+ + Sync
+ Send);
+#[allow(clippy::as_conversions)]
static BUILTINS: once_cell::sync::Lazy<
- std::collections::HashMap<&'static str, BuiltinFunc>,
+ std::collections::HashMap<&'static str, Builtin>,
> = once_cell::sync::Lazy::new(|| {
- // all this does is convince the type system to do the right thing, i
- // don't think there's any way to just do it directly through annotations
- // or casts or whatever
- fn coerce_builtin<F>(f: &'static F) -> BuiltinFunc
- where
- F: for<'a> Fn(
- &'a crate::parse::Exe,
- &'a crate::command::Env,
- ) -> std::pin::Pin<
- Box<
- dyn std::future::Future<Output = std::process::ExitStatus>
- + Sync
- + Send
- + 'a,
- >,
- > + Sync
- + Send
- + 'static,
- {
- f
- }
-
let mut builtins = std::collections::HashMap::new();
- builtins.insert("cd", coerce_builtin(&|exe, env| Box::pin(cd(exe, env))));
- builtins
- .insert("and", coerce_builtin(&|exe, env| Box::pin(and(exe, env))));
- builtins.insert("or", coerce_builtin(&|exe, env| Box::pin(or(exe, env))));
- builtins.insert(
- "command",
- coerce_builtin(&|exe, env| Box::pin(command(exe, env))),
- );
- builtins.insert(
- "builtin",
- coerce_builtin(&|exe, env| Box::pin(builtin(exe, env))),
- );
+ builtins.insert("cd", &cd as Builtin);
+ builtins.insert("and", &and);
+ builtins.insert("or", &or);
+ builtins.insert("command", &command);
+ builtins.insert("builtin", &builtin);
builtins
});
-pub struct Builtin {
- f: BuiltinFunc,
+pub struct Command {
+ exe: crate::parse::Exe,
+ f: Builtin,
stdin: Box<dyn async_std::io::Read>,
stdout: Box<dyn async_std::io::Write>,
stderr: Box<dyn async_std::io::Write>,
}
-impl Builtin {
+impl Command {
pub fn new(exe: &crate::parse::Exe) -> Option<Self> {
- if let Some(f) = BUILTINS.get(exe.exe()) {
- Some(Self {
- f,
- stdin: Box::new(async_std::io::stdin()),
- stdout: Box::new(async_std::io::stdout()),
- stderr: Box::new(async_std::io::stderr()),
- })
- } else {
- None
- }
+ BUILTINS.get(exe.exe()).map(|f| Self {
+ exe: exe.clone(),
+ f,
+ stdin: Box::new(async_std::io::stdin()),
+ stdout: Box::new(async_std::io::stdout()),
+ stderr: Box::new(async_std::io::stderr()),
+ })
}
pub fn stdin(&mut self, fh: std::fs::File) {
@@ -87,128 +51,169 @@ impl Builtin {
pub fn stderr(&mut self, fh: std::fs::File) {
self.stderr = Box::new(async_std::fs::File::from(fh));
}
+
+ pub fn spawn(self, env: &crate::command::Env) -> anyhow::Result<Child> {
+ (self.f)(&self.exe, env)
+ }
}
-pub fn run(
- exe: &crate::parse::Exe,
- env: &crate::command::Env,
-) -> Option<
- std::pin::Pin<
+pub struct Child {
+ fut: std::pin::Pin<
Box<
- dyn std::future::Future<Output = async_std::process::ExitStatus>
- + Send
- + Sync,
+ dyn std::future::Future<Output = std::process::ExitStatus>
+ + Sync
+ + Send,
>,
>,
-> {
- // the closure form doesn't work without explicit type annotations
- #[allow(clippy::option_if_let_else)]
- if let Some(f) = BUILTINS.get(exe.exe()) {
- let exe = exe.clone();
- let env = env.clone();
- Some(Box::pin(async move { f(&exe, &env).await }))
- } else {
- None
+ wrapped_child: Option<Box<crate::command::Child>>,
+}
+
+impl Child {
+ pub fn id(&self) -> Option<u32> {
+ self.wrapped_child.as_ref().and_then(|cmd| cmd.id())
+ }
+
+ #[async_recursion::async_recursion]
+ pub async fn status(
+ self,
+ ) -> anyhow::Result<async_std::process::ExitStatus> {
+ if let Some(child) = self.wrapped_child {
+ child.status().await
+ } else {
+ Ok(self.fut.await)
+ }
}
}
-async fn cd(
+fn cd(
exe: &crate::parse::Exe,
env: &crate::command::Env,
-) -> async_std::process::ExitStatus {
- let dir = exe
- .args()
- .into_iter()
- .map(std::convert::AsRef::as_ref)
- .next()
- .unwrap_or("");
-
- let dir = if dir.is_empty() {
- home()
- } else if dir.starts_with('~') {
- let path: std::path::PathBuf = dir.into();
- if let std::path::Component::Normal(prefix) =
- path.components().next().unwrap()
- {
- if prefix.to_str() == Some("~") {
- home().join(path.strip_prefix(prefix).unwrap())
+) -> anyhow::Result<Child> {
+ async fn async_cd(
+ exe: &crate::parse::Exe,
+ _env: &crate::command::Env,
+ ) -> std::process::ExitStatus {
+ let dir = exe
+ .args()
+ .into_iter()
+ .map(std::convert::AsRef::as_ref)
+ .next()
+ .unwrap_or("");
+
+ let dir = if dir.is_empty() {
+ home()
+ } else if dir.starts_with('~') {
+ let path: std::path::PathBuf = dir.into();
+ if let std::path::Component::Normal(prefix) =
+ path.components().next().unwrap()
+ {
+ if prefix.to_str() == Some("~") {
+ home().join(path.strip_prefix(prefix).unwrap())
+ } else {
+ // TODO
+ async_std::io::stderr()
+ .write(b"unimplemented\n")
+ .await
+ .unwrap();
+ return async_std::process::ExitStatus::from_raw(1 << 8);
+ }
} else {
- // TODO
+ unreachable!()
+ }
+ } else {
+ dir.into()
+ };
+ let code = match std::env::set_current_dir(&dir) {
+ Ok(()) => 0,
+ Err(e) => {
async_std::io::stderr()
- .write(b"unimplemented\n")
+ .write(
+ format!(
+ "{}: {}: {}\n",
+ exe.exe(),
+ crate::format::io_error(&e),
+ dir.display()
+ )
+ .as_bytes(),
+ )
.await
.unwrap();
- return async_std::process::ExitStatus::from_raw(1 << 8);
+ 1
}
- } else {
- unreachable!()
- }
- } else {
- dir.into()
- };
- let code = match std::env::set_current_dir(&dir) {
- Ok(()) => 0,
- Err(e) => {
- async_std::io::stderr()
- .write(
- format!(
- "{}: {}: {}\n",
- exe.exe(),
- crate::format::io_error(&e),
- dir.display()
- )
- .as_bytes(),
- )
- .await
- .unwrap();
- 1
- }
- };
- async_std::process::ExitStatus::from_raw(code << 8)
+ };
+ async_std::process::ExitStatus::from_raw(code << 8)
+ }
+
+ let exe = exe.clone();
+ let env = env.clone();
+ Ok(Child {
+ fut: Box::pin(async move { async_cd(&exe, &env).await }),
+ wrapped_child: None,
+ })
}
-async fn and(
+fn and(
exe: &crate::parse::Exe,
env: &crate::command::Env,
-) -> async_std::process::ExitStatus {
+) -> anyhow::Result<Child> {
let exe = exe.shift();
if env.latest_status().success() {
- todo!()
- // super::run_exe(&exe, env).await
+ let cmd = crate::command::Command::new(&exe);
+ Ok(Child {
+ fut: Box::pin(async move { unreachable!() }),
+ wrapped_child: Some(Box::new(cmd.spawn(env)?)),
+ })
} else {
- *env.latest_status()
+ let env = env.clone();
+ Ok(Child {
+ fut: Box::pin(async move { *env.latest_status() }),
+ wrapped_child: None,
+ })
}
}
-async fn or(
+fn or(
exe: &crate::parse::Exe,
env: &crate::command::Env,
-) -> async_std::process::ExitStatus {
+) -> anyhow::Result<Child> {
let exe = exe.shift();
if env.latest_status().success() {
- *env.latest_status()
+ let env = env.clone();
+ Ok(Child {
+ fut: Box::pin(async move { *env.latest_status() }),
+ wrapped_child: None,
+ })
} else {
- todo!()
- // super::run_exe(&exe, env).await
+ let cmd = crate::command::Command::new(&exe);
+ Ok(Child {
+ fut: Box::pin(async move { unreachable!() }),
+ wrapped_child: Some(Box::new(cmd.spawn(env)?)),
+ })
}
}
-async fn command(
+fn command(
exe: &crate::parse::Exe,
env: &crate::command::Env,
-) -> async_std::process::ExitStatus {
+) -> anyhow::Result<Child> {
let exe = exe.shift();
- // super::run_binary(&exe, env).await;
- *env.latest_status()
+ let cmd = crate::command::Command::new_binary(&exe);
+ Ok(Child {
+ fut: Box::pin(async move { unreachable!() }),
+ wrapped_child: Some(Box::new(cmd.spawn(env)?)),
+ })
}
-async fn builtin(
+fn builtin(
exe: &crate::parse::Exe,
env: &crate::command::Env,
-) -> async_std::process::ExitStatus {
+) -> anyhow::Result<Child> {
let exe = exe.shift();
- run(&exe, env).unwrap().await;
- *env.latest_status()
+ let cmd = crate::command::Command::new_builtin(&exe);
+ Ok(Child {
+ fut: Box::pin(async move { unreachable!() }),
+ wrapped_child: Some(Box::new(cmd.spawn(env)?)),
+ })
}
fn home() -> std::path::PathBuf {
diff --git a/src/command.rs b/src/command.rs
index b1453af..92d9b39 100644
--- a/src/command.rs
+++ b/src/command.rs
@@ -27,21 +27,26 @@ impl Env {
}
}
-enum Command {
+pub enum Command {
Binary(async_std::process::Command),
- Builtin(crate::builtins::Builtin),
+ Builtin(crate::builtins::Command),
}
impl Command {
- fn new(exe: &crate::parse::Exe) -> Self {
- crate::builtins::Builtin::new(exe).map_or_else(
- || {
- let mut cmd = async_std::process::Command::new(exe.exe());
- cmd.args(exe.args());
- Self::Binary(cmd)
- },
- Self::Builtin,
- )
+ pub fn new(exe: &crate::parse::Exe) -> Self {
+ crate::builtins::Command::new(exe)
+ .map_or_else(|| Self::new_binary(exe), Self::Builtin)
+ }
+
+ pub fn new_binary(exe: &crate::parse::Exe) -> Self {
+ let mut cmd = async_std::process::Command::new(exe.exe());
+ cmd.args(exe.args());
+ Self::Binary(cmd)
+ }
+
+ pub fn new_builtin(exe: &crate::parse::Exe) -> Self {
+ crate::builtins::Command::new(exe)
+ .map_or_else(|| todo!(), Self::Builtin)
}
fn stdin(&mut self, fh: std::fs::File) {
@@ -89,34 +94,35 @@ impl Command {
}
}
- fn spawn(self, env: &Env) -> anyhow::Result<Child> {
+ pub fn spawn(self, env: &Env) -> anyhow::Result<Child> {
match self {
Self::Binary(mut cmd) => {
let child = cmd.spawn()?;
Ok(Child::Binary(child))
}
- Self::Builtin(cmd) => Ok(Child::Builtin(cmd)),
+ Self::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)),
}
}
}
-enum Child {
+pub enum Child {
Binary(async_std::process::Child),
- Builtin(crate::builtins::Builtin),
+ Builtin(crate::builtins::Child),
}
impl Child {
- fn id(&self) -> Option<u32> {
+ pub fn id(&self) -> Option<u32> {
match self {
Self::Binary(child) => Some(child.id()),
- Self::Builtin(child) => todo!(),
+ Self::Builtin(child) => child.id(),
}
}
- async fn status(self) -> anyhow::Result<std::process::ExitStatus> {
+ #[async_recursion::async_recursion]
+ pub async fn status(self) -> anyhow::Result<std::process::ExitStatus> {
match self {
Self::Binary(child) => Ok(child.status_no_drop().await?),
- Self::Builtin(child) => todo!(),
+ Self::Builtin(child) => Ok(child.status().await?),
}
}
}
@@ -214,7 +220,15 @@ fn set_foreground_pg(pg: nix::unistd::Pid) -> anyhow::Result<()> {
)?;
nix::unistd::tcsetpgrp(pty, pg)?;
nix::unistd::close(pty)?;
- nix::sys::signal::kill(neg_pid(pg), nix::sys::signal::Signal::SIGCONT)?;
+ nix::sys::signal::kill(neg_pid(pg), nix::sys::signal::Signal::SIGCONT)
+ .or_else(|e| {
+ // the process group has already exited
+ if e == nix::errno::Errno::ESRCH {
+ Ok(())
+ } else {
+ Err(e)
+ }
+ })?;
Ok(())
}
@@ -230,8 +244,8 @@ fn setpgid_parent(
nix::unistd::setpgid(pid, pg.unwrap_or(PID0)).or_else(|e| {
// EACCES means that the child already called exec, but if it did,
// then it also must have already called setpgid itself, so we don't
- // care
- if e == nix::errno::Errno::EACCES {
+ // care. ESRCH means that the process already exited, which is similar
+ if e == nix::errno::Errno::EACCES || e == nix::errno::Errno::ESRCH {
Ok(())
} else {
Err(e)