From 4142936f657004f88e259583136f25f566b0b1c0 Mon Sep 17 00:00:00 2001 From: Jesse Luehrs Date: Mon, 3 Jan 2022 05:33:21 -0500 Subject: split up code into more files --- src/builtins/command.rs | 235 ++++++++++++++++++++++++++++++++++++++++++++++++ src/builtins/mod.rs | 194 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 429 insertions(+) create mode 100644 src/builtins/command.rs create mode 100644 src/builtins/mod.rs (limited to 'src/builtins') diff --git a/src/builtins/command.rs b/src/builtins/command.rs new file mode 100644 index 0000000..6dfa56c --- /dev/null +++ b/src/builtins/command.rs @@ -0,0 +1,235 @@ +use async_std::io::{ReadExt as _, WriteExt as _}; +use std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _}; + +pub struct Command { + exe: crate::parse::Exe, + f: super::Builtin, + io: Io, +} + +impl Command { + pub fn new(exe: &crate::parse::Exe) -> Option { + super::BUILTINS.get(exe.exe()).map(|f| Self { + exe: exe.clone(), + f, + io: Io::new(), + }) + } + + pub fn stdin(&mut self, fh: std::fs::File) { + self.io.set_stdin(fh); + } + + pub fn stdout(&mut self, fh: std::fs::File) { + self.io.set_stdout(fh); + } + + pub fn stderr(&mut self, fh: std::fs::File) { + self.io.set_stderr(fh); + } + + pub unsafe fn pre_exec(&mut self, f: F) + where + F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, + { + self.io.pre_exec(f); + } + + pub fn spawn(self, env: &crate::env::Env) -> anyhow::Result { + let Self { f, exe, io } = self; + (f)(&exe, env, io) + } +} + +pub struct Io { + fds: std::collections::HashMap< + std::os::unix::io::RawFd, + std::os::unix::io::RawFd, + >, + pre_exec: Option< + Box std::io::Result<()> + Send + Sync>, + >, +} + +impl Io { + fn new() -> Self { + let mut fds = std::collections::HashMap::new(); + fds.insert(0.as_raw_fd(), 0.as_raw_fd()); + fds.insert(1.as_raw_fd(), 1.as_raw_fd()); + fds.insert(2.as_raw_fd(), 2.as_raw_fd()); + Self { + fds, + pre_exec: None, + } + } + + fn stdin(&self) -> Option { + self.fds + .get(&0.as_raw_fd()) + .copied() + .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) }) + } + + fn set_stdin(&mut self, stdin: T) { + if let Some(fd) = self.fds.get(&0.as_raw_fd()) { + if *fd > 2 { + drop(unsafe { async_std::fs::File::from_raw_fd(*fd) }); + } + } + self.fds.insert(0.as_raw_fd(), stdin.into_raw_fd()); + } + + fn stdout(&self) -> Option { + self.fds + .get(&1.as_raw_fd()) + .copied() + .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) }) + } + + fn set_stdout(&mut self, stdout: T) { + if let Some(fd) = self.fds.get(&1.as_raw_fd()) { + if *fd > 2 { + drop(unsafe { async_std::fs::File::from_raw_fd(*fd) }); + } + } + self.fds.insert(1.as_raw_fd(), stdout.into_raw_fd()); + } + + fn stderr(&self) -> Option { + self.fds + .get(&2.as_raw_fd()) + .copied() + .map(|fd| unsafe { async_std::fs::File::from_raw_fd(fd) }) + } + + fn set_stderr(&mut self, stderr: T) { + if let Some(fd) = self.fds.get(&2.as_raw_fd()) { + if *fd > 2 { + drop(unsafe { async_std::fs::File::from_raw_fd(*fd) }); + } + } + self.fds.insert(2.as_raw_fd(), stderr.into_raw_fd()); + } + + pub unsafe fn pre_exec(&mut self, f: F) + where + F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, + { + self.pre_exec = Some(Box::new(f)); + } + + pub async fn read_stdin(&self, buf: &mut [u8]) -> anyhow::Result { + if let Some(mut fh) = self.stdin() { + let res = fh.read(buf).await; + let _ = fh.into_raw_fd(); + Ok(res?) + } else { + Ok(0) + } + } + + pub async fn write_stdout(&self, buf: &[u8]) -> anyhow::Result<()> { + if let Some(mut fh) = self.stdout() { + let res = fh.write_all(buf).await; + let _ = fh.into_raw_fd(); + Ok(res.map(|_| ())?) + } else { + Ok(()) + } + } + + pub async fn write_stderr(&self, buf: &[u8]) -> anyhow::Result<()> { + if let Some(mut fh) = self.stderr() { + let res = fh.write_all(buf).await; + let _ = fh.into_raw_fd(); + Ok(res.map(|_| ())?) + } else { + Ok(()) + } + } + + pub fn setup_command(mut self, cmd: &mut crate::pipeline::Command) { + if let Some(stdin) = self.stdin() { + let stdin = stdin.into_raw_fd(); + if stdin != 0 { + cmd.stdin(unsafe { std::fs::File::from_raw_fd(stdin) }); + self.fds.remove(&0.as_raw_fd()); + } + } + if let Some(stdout) = self.stdout() { + let stdout = stdout.into_raw_fd(); + if stdout != 1 { + cmd.stdout(unsafe { std::fs::File::from_raw_fd(stdout) }); + self.fds.remove(&1.as_raw_fd()); + } + } + if let Some(stderr) = self.stderr() { + let stderr = stderr.into_raw_fd(); + if stderr != 2 { + cmd.stderr(unsafe { std::fs::File::from_raw_fd(stderr) }); + self.fds.remove(&2.as_raw_fd()); + } + } + if let Some(pre_exec) = self.pre_exec.take() { + unsafe { cmd.pre_exec(pre_exec) }; + } + } +} + +impl Drop for Io { + fn drop(&mut self) { + for fd in self.fds.values() { + if *fd > 2 { + drop(unsafe { std::fs::File::from_raw_fd(*fd) }); + } + } + } +} + +pub struct Child { + fut: std::pin::Pin< + Box< + dyn std::future::Future + + Sync + + Send, + >, + >, + wrapped_child: Option>, +} + +impl Child { + pub fn new_fut(fut: F) -> Self + where + F: std::future::Future + + Sync + + Send + + 'static, + { + Self { + fut: Box::pin(fut), + wrapped_child: None, + } + } + + pub fn new_wrapped(child: crate::pipeline::Child) -> Self { + Self { + fut: Box::pin(async move { unreachable!() }), + wrapped_child: Some(Box::new(child)), + } + } + + pub fn id(&self) -> Option { + self.wrapped_child.as_ref().and_then(|cmd| cmd.id()) + } + + #[async_recursion::async_recursion] + pub async fn status( + self, + ) -> anyhow::Result { + if let Some(child) = self.wrapped_child { + child.status().await + } else { + Ok(self.fut.await) + } + } +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs new file mode 100644 index 0000000..5c7d4b6 --- /dev/null +++ b/src/builtins/mod.rs @@ -0,0 +1,194 @@ +use std::os::unix::process::ExitStatusExt as _; + +pub mod command; +pub use command::{Child, Command}; + +type Builtin = &'static (dyn Fn( + &crate::parse::Exe, + &crate::env::Env, + command::Io, +) -> anyhow::Result + + Sync + + Send); + +#[allow(clippy::as_conversions)] +static BUILTINS: once_cell::sync::Lazy< + std::collections::HashMap<&'static str, Builtin>, +> = once_cell::sync::Lazy::new(|| { + let mut builtins = std::collections::HashMap::new(); + builtins.insert("cd", &cd as Builtin); + builtins.insert("echo", &echo); + builtins.insert("and", &and); + builtins.insert("or", &or); + builtins.insert("command", &command); + builtins.insert("builtin", &builtin); + builtins +}); + +// clippy can't tell that the type is necessary +#[allow(clippy::unnecessary_wraps)] +fn cd( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + async fn async_cd( + exe: &crate::parse::Exe, + _env: &crate::env::Env, + io: command::Io, + ) -> 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 + io.write_stderr(b"unimplemented\n").await.unwrap(); + return async_std::process::ExitStatus::from_raw(1 << 8); + } + } else { + unreachable!() + } + } else { + dir.into() + }; + let code = match std::env::set_current_dir(&dir) { + Ok(()) => 0, + Err(e) => { + io.write_stderr( + format!( + "{}: {}: {}\n", + exe.exe(), + crate::format::io_error(&e), + dir.display() + ) + .as_bytes(), + ) + .await + .unwrap(); + 1 + } + }; + async_std::process::ExitStatus::from_raw(code << 8) + } + + let exe = exe.clone(); + let env = env.clone(); + Ok(command::Child::new_fut(async move { + async_cd(&exe, &env, io).await + })) +} + +// clippy can't tell that the type is necessary +#[allow(clippy::unnecessary_wraps)] +// mostly just for testing and ensuring that builtins work, i'll likely remove +// this later, since the binary seems totally fine +fn echo( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + async fn async_echo( + exe: &crate::parse::Exe, + _env: &crate::env::Env, + io: command::Io, + ) -> std::process::ExitStatus { + macro_rules! write_stdout { + ($bytes:expr) => { + if let Err(e) = io.write_stdout($bytes).await { + io.write_stderr(format!("echo: {}", e).as_bytes()) + .await + .unwrap(); + return async_std::process::ExitStatus::from_raw(1 << 8); + } + }; + } + let count = exe.args().count(); + for (i, arg) in exe.args().enumerate() { + write_stdout!(arg.as_bytes()); + if i == count - 1 { + write_stdout!(b"\n"); + } else { + write_stdout!(b" "); + } + } + + async_std::process::ExitStatus::from_raw(0) + } + + let exe = exe.clone(); + let env = env.clone(); + Ok(command::Child::new_fut(async move { + async_echo(&exe, &env, io).await + })) +} + +fn and( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + let exe = exe.shift(); + if env.latest_status().success() { + let mut cmd = crate::pipeline::Command::new(&exe); + io.setup_command(&mut cmd); + Ok(command::Child::new_wrapped(cmd.spawn(env)?)) + } else { + let env = env.clone(); + Ok(command::Child::new_fut(async move { *env.latest_status() })) + } +} + +fn or( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + let exe = exe.shift(); + if env.latest_status().success() { + let env = env.clone(); + Ok(command::Child::new_fut(async move { *env.latest_status() })) + } else { + let mut cmd = crate::pipeline::Command::new(&exe); + io.setup_command(&mut cmd); + Ok(command::Child::new_wrapped(cmd.spawn(env)?)) + } +} + +fn command( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + let exe = exe.shift(); + let mut cmd = crate::pipeline::Command::new_binary(&exe); + io.setup_command(&mut cmd); + Ok(command::Child::new_wrapped(cmd.spawn(env)?)) +} + +fn builtin( + exe: &crate::parse::Exe, + env: &crate::env::Env, + io: command::Io, +) -> anyhow::Result { + let exe = exe.shift(); + let mut cmd = crate::pipeline::Command::new_builtin(&exe); + io.setup_command(&mut cmd); + Ok(command::Child::new_wrapped(cmd.spawn(env)?)) +} + +fn home() -> std::path::PathBuf { + std::env::var_os("HOME").unwrap().into() +} -- cgit v1.2.3-54-g00ecf