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.rs | 426 ----------------------------------------------- src/builtins/command.rs | 235 ++++++++++++++++++++++++++ src/builtins/mod.rs | 194 +++++++++++++++++++++ src/command.rs | 274 ------------------------------ src/env.rs | 48 ++---- src/info.rs | 40 +++++ src/main.rs | 5 +- src/pipeline/command.rs | 100 +++++++++++ src/pipeline/mod.rs | 154 +++++++++++++++++ src/state/history/mod.rs | 6 +- src/state/readline.rs | 10 +- 11 files changed, 750 insertions(+), 742 deletions(-) delete mode 100644 src/builtins.rs create mode 100644 src/builtins/command.rs create mode 100644 src/builtins/mod.rs delete mode 100644 src/command.rs create mode 100644 src/info.rs create mode 100644 src/pipeline/command.rs create mode 100644 src/pipeline/mod.rs (limited to 'src') diff --git a/src/builtins.rs b/src/builtins.rs deleted file mode 100644 index 638496b..0000000 --- a/src/builtins.rs +++ /dev/null @@ -1,426 +0,0 @@ -use async_std::io::{ReadExt as _, WriteExt as _}; -use std::os::unix::io::{AsRawFd as _, FromRawFd as _, IntoRawFd as _}; -use std::os::unix::process::ExitStatusExt as _; - -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)); - } - - 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) - } - } - - 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(()) - } - } - - 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(()) - } - } - - fn setup_command(mut self, cmd: &mut crate::command::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) }); - } - } - } -} - -type Builtin = &'static (dyn Fn( - &crate::parse::Exe, - &crate::command::Env, - 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 -}); - -pub struct Command { - exe: crate::parse::Exe, - f: Builtin, - io: Io, -} - -impl Command { - pub fn new(exe: &crate::parse::Exe) -> Option { - 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::command::Env) -> anyhow::Result { - let Self { f, exe, io } = self; - (f)(&exe, env, io) - } -} - -pub struct Child { - fut: std::pin::Pin< - Box< - dyn std::future::Future - + Sync - + Send, - >, - >, - wrapped_child: Option>, -} - -impl 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) - } - } -} - -// clippy can't tell that the type is necessary -#[allow(clippy::unnecessary_wraps)] -fn cd( - exe: &crate::parse::Exe, - env: &crate::command::Env, - io: Io, -) -> anyhow::Result { - async fn async_cd( - exe: &crate::parse::Exe, - _env: &crate::command::Env, - io: 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(Child { - fut: Box::pin(async move { async_cd(&exe, &env, io).await }), - wrapped_child: None, - }) -} - -// 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::command::Env, - io: Io, -) -> anyhow::Result { - async fn async_echo( - exe: &crate::parse::Exe, - _env: &crate::command::Env, - io: 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(Child { - fut: Box::pin(async move { async_echo(&exe, &env, io).await }), - wrapped_child: None, - }) -} - -fn and( - exe: &crate::parse::Exe, - env: &crate::command::Env, - io: Io, -) -> anyhow::Result { - let exe = exe.shift(); - if env.latest_status().success() { - let mut cmd = crate::command::Command::new(&exe); - io.setup_command(&mut cmd); - Ok(Child { - fut: Box::pin(async move { unreachable!() }), - wrapped_child: Some(Box::new(cmd.spawn(env)?)), - }) - } else { - let env = env.clone(); - Ok(Child { - fut: Box::pin(async move { *env.latest_status() }), - wrapped_child: None, - }) - } -} - -fn or( - exe: &crate::parse::Exe, - env: &crate::command::Env, - io: Io, -) -> anyhow::Result { - let exe = exe.shift(); - if env.latest_status().success() { - let env = env.clone(); - Ok(Child { - fut: Box::pin(async move { *env.latest_status() }), - wrapped_child: None, - }) - } else { - let mut cmd = crate::command::Command::new(&exe); - io.setup_command(&mut cmd); - Ok(Child { - fut: Box::pin(async move { unreachable!() }), - wrapped_child: Some(Box::new(cmd.spawn(env)?)), - }) - } -} - -fn command( - exe: &crate::parse::Exe, - env: &crate::command::Env, - io: Io, -) -> anyhow::Result { - let exe = exe.shift(); - let mut cmd = crate::command::Command::new_binary(&exe); - io.setup_command(&mut cmd); - Ok(Child { - fut: Box::pin(async move { unreachable!() }), - wrapped_child: Some(Box::new(cmd.spawn(env)?)), - }) -} - -fn builtin( - exe: &crate::parse::Exe, - env: &crate::command::Env, - io: Io, -) -> anyhow::Result { - let exe = exe.shift(); - let mut cmd = crate::command::Command::new_builtin(&exe); - io.setup_command(&mut cmd); - Ok(Child { - fut: Box::pin(async move { unreachable!() }), - wrapped_child: Some(Box::new(cmd.spawn(env)?)), - }) -} - -fn home() -> std::path::PathBuf { - std::env::var_os("HOME").unwrap().into() -} 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() +} diff --git a/src/command.rs b/src/command.rs deleted file mode 100644 index 8f19a26..0000000 --- a/src/command.rs +++ /dev/null @@ -1,274 +0,0 @@ -use async_std::io::ReadExt as _; -use async_std::os::unix::process::CommandExt as _; -use async_std::stream::StreamExt as _; -use std::os::unix::io::FromRawFd as _; -use std::os::unix::process::ExitStatusExt as _; - -const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0); - -#[derive(Clone)] -pub struct Env { - latest_status: async_std::process::ExitStatus, -} - -impl Env { - pub fn new(code: i32) -> Self { - Self { - latest_status: async_std::process::ExitStatus::from_raw( - code << 8, - ), - } - } - - pub fn set_status(&mut self, status: async_std::process::ExitStatus) { - self.latest_status = status; - } - - pub fn latest_status(&self) -> &async_std::process::ExitStatus { - &self.latest_status - } -} - -pub enum Command { - Binary(async_std::process::Command), - Builtin(crate::builtins::Command), -} - -impl Command { - 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) - } - - pub fn stdin(&mut self, fh: std::fs::File) { - match self { - Self::Binary(cmd) => { - cmd.stdin(fh); - } - Self::Builtin(cmd) => { - cmd.stdin(fh); - } - } - } - - pub fn stdout(&mut self, fh: std::fs::File) { - match self { - Self::Binary(cmd) => { - cmd.stdout(fh); - } - Self::Builtin(cmd) => { - cmd.stdout(fh); - } - } - } - - pub fn stderr(&mut self, fh: std::fs::File) { - match self { - Self::Binary(cmd) => { - cmd.stderr(fh); - } - Self::Builtin(cmd) => { - cmd.stderr(fh); - } - } - } - - pub unsafe fn pre_exec(&mut self, f: F) - where - F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, - { - match self { - Self::Binary(cmd) => { - cmd.pre_exec(f); - } - Self::Builtin(cmd) => { - cmd.pre_exec(f); - } - } - } - - pub fn spawn(self, env: &Env) -> anyhow::Result { - match self { - Self::Binary(mut cmd) => Ok(Child::Binary(cmd.spawn()?)), - Self::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)), - } - } -} - -pub enum Child { - Binary(async_std::process::Child), - Builtin(crate::builtins::Child), -} - -impl Child { - pub fn id(&self) -> Option { - match self { - Self::Binary(child) => Some(child.id()), - Self::Builtin(child) => child.id(), - } - } - - #[async_recursion::async_recursion] - pub async fn status(self) -> anyhow::Result { - match self { - Self::Binary(child) => Ok(child.status_no_drop().await?), - Self::Builtin(child) => Ok(child.status().await?), - } - } -} - -pub async fn run() -> anyhow::Result { - let (code, pipeline) = read_data().await?; - let env = Env::new(code); - let children = spawn_children(&pipeline, &env)?; - let count = children.len(); - - let mut children: futures_util::stream::FuturesUnordered<_> = - children - .into_iter() - .enumerate() - .map(|(i, child)| async move { - (child.status().await, i == count - 1) - }) - .collect(); - let mut final_status = None; - while let Some((status, last)) = children.next().await { - let status = status.unwrap_or_else(|_| { - async_std::process::ExitStatus::from_raw(1 << 8) - }); - // this conversion is safe because the Signal enum is repr(i32) - #[allow(clippy::as_conversions)] - if status.signal() == Some(nix::sys::signal::Signal::SIGINT as i32) { - nix::sys::signal::raise(nix::sys::signal::Signal::SIGINT)?; - } - if last { - final_status = Some(status); - } - } - - let final_status = final_status.unwrap(); - if let Some(signal) = final_status.signal() { - nix::sys::signal::raise(signal.try_into().unwrap())?; - } - Ok(final_status.code().unwrap()) -} - -async fn read_data() -> anyhow::Result<(i32, crate::parse::Pipeline)> { - // Safety: this code is only called by crate::history::run_pipeline, which - // passes data through on fd 3, and which will not spawn this process - // unless the pipe was successfully opened on that fd - let mut fd3 = unsafe { async_std::fs::File::from_raw_fd(3) }; - let mut be_bytes = [0; 4]; - fd3.read_exact(&mut be_bytes).await?; - let code = i32::from_be_bytes(be_bytes); - let mut pipeline = String::new(); - fd3.read_to_string(&mut pipeline).await?; - let ast = crate::parse::Pipeline::parse(&pipeline)?; - Ok((code, ast)) -} - -fn spawn_children( - pipeline: &crate::parse::Pipeline, - env: &Env, -) -> anyhow::Result> { - let mut cmds: Vec<_> = pipeline.exes().iter().map(Command::new).collect(); - for i in 0..(cmds.len() - 1) { - let (r, w) = pipe()?; - cmds[i].stdout(w); - cmds[i + 1].stdin(r); - } - - let mut children = vec![]; - let mut pg_pid = None; - for mut cmd in cmds.drain(..) { - // Safety: setpgid is an async-signal-safe function - unsafe { - cmd.pre_exec(move || { - setpgid_child(pg_pid)?; - Ok(()) - }); - } - let child = cmd.spawn(env)?; - if let Some(id) = child.id() { - let child_pid = id_to_pid(id); - setpgid_parent(child_pid, pg_pid)?; - if pg_pid.is_none() { - pg_pid = Some(child_pid); - set_foreground_pg(child_pid)?; - } - } - children.push(child); - } - Ok(children) -} - -fn pipe() -> anyhow::Result<(std::fs::File, std::fs::File)> { - let (r, w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; - // Safety: these file descriptors were just returned by pipe2 above, which - // means they must be valid otherwise that call would have returned an - // error - Ok((unsafe { std::fs::File::from_raw_fd(r) }, unsafe { - std::fs::File::from_raw_fd(w) - })) -} - -fn set_foreground_pg(pg: nix::unistd::Pid) -> anyhow::Result<()> { - let pty = nix::fcntl::open( - "/dev/tty", - nix::fcntl::OFlag::empty(), - nix::sys::stat::Mode::empty(), - )?; - nix::unistd::tcsetpgrp(pty, pg)?; - nix::unistd::close(pty)?; - 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(()) -} - -fn setpgid_child(pg: Option) -> std::io::Result<()> { - nix::unistd::setpgid(PID0, pg.unwrap_or(PID0))?; - Ok(()) -} - -fn setpgid_parent( - pid: nix::unistd::Pid, - pg: Option, -) -> anyhow::Result<()> { - 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. 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) - } - })?; - Ok(()) -} - -fn id_to_pid(id: u32) -> nix::unistd::Pid { - nix::unistd::Pid::from_raw(id.try_into().unwrap()) -} - -fn neg_pid(pid: nix::unistd::Pid) -> nix::unistd::Pid { - nix::unistd::Pid::from_raw(-pid.as_raw()) -} diff --git a/src/env.rs b/src/env.rs index 3ff2576..e88ec7c 100644 --- a/src/env.rs +++ b/src/env.rs @@ -1,40 +1,24 @@ -pub fn pwd() -> anyhow::Result { - let mut pwd = std::env::current_dir()?.display().to_string(); - if let Ok(home) = std::env::var("HOME") { - if pwd.starts_with(&home) { - pwd.replace_range(..home.len(), "~"); - } - } - Ok(pwd) -} +use std::os::unix::process::ExitStatusExt as _; -pub fn user() -> anyhow::Result { - Ok(users::get_current_username() - .ok_or_else(|| anyhow::anyhow!("couldn't get username"))? - .to_string_lossy() - .into_owned()) +#[derive(Clone)] +pub struct Env { + latest_status: async_std::process::ExitStatus, } -#[allow(clippy::unnecessary_wraps)] -pub fn prompt_char() -> anyhow::Result { - if users::get_current_uid() == 0 { - Ok("#".into()) - } else { - Ok("$".into()) +impl Env { + pub fn new(code: i32) -> Self { + Self { + latest_status: async_std::process::ExitStatus::from_raw( + code << 8, + ), + } } -} -pub fn hostname() -> anyhow::Result { - let mut hostname = hostname::get()?.to_string_lossy().into_owned(); - if let Some(idx) = hostname.find('.') { - hostname.truncate(idx); + pub fn set_status(&mut self, status: async_std::process::ExitStatus) { + self.latest_status = status; } - Ok(hostname) -} -#[allow(clippy::unnecessary_wraps)] -pub fn time(offset: time::UtcOffset) -> anyhow::Result { - Ok(crate::format::time( - time::OffsetDateTime::now_utc().to_offset(offset), - )) + pub fn latest_status(&self) -> &async_std::process::ExitStatus { + &self.latest_status + } } diff --git a/src/info.rs b/src/info.rs new file mode 100644 index 0000000..3ff2576 --- /dev/null +++ b/src/info.rs @@ -0,0 +1,40 @@ +pub fn pwd() -> anyhow::Result { + let mut pwd = std::env::current_dir()?.display().to_string(); + if let Ok(home) = std::env::var("HOME") { + if pwd.starts_with(&home) { + pwd.replace_range(..home.len(), "~"); + } + } + Ok(pwd) +} + +pub fn user() -> anyhow::Result { + Ok(users::get_current_username() + .ok_or_else(|| anyhow::anyhow!("couldn't get username"))? + .to_string_lossy() + .into_owned()) +} + +#[allow(clippy::unnecessary_wraps)] +pub fn prompt_char() -> anyhow::Result { + if users::get_current_uid() == 0 { + Ok("#".into()) + } else { + Ok("$".into()) + } +} + +pub fn hostname() -> anyhow::Result { + let mut hostname = hostname::get()?.to_string_lossy().into_owned(); + if let Some(idx) = hostname.find('.') { + hostname.truncate(idx); + } + Ok(hostname) +} + +#[allow(clippy::unnecessary_wraps)] +pub fn time(offset: time::UtcOffset) -> anyhow::Result { + Ok(crate::format::time( + time::OffsetDateTime::now_utc().to_offset(offset), + )) +} diff --git a/src/main.rs b/src/main.rs index 91a3526..9709a32 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,11 +13,12 @@ #![allow(clippy::type_complexity)] mod builtins; -mod command; mod env; mod event; mod format; +mod info; mod parse; +mod pipeline; mod state; use async_std::stream::StreamExt as _; @@ -45,7 +46,7 @@ fn get_offset() -> time::UtcOffset { async fn async_main() -> anyhow::Result { if std::env::args().nth(1).as_deref() == Some("--internal-cmd-runner") { - return command::run().await; + return pipeline::run().await; } let mut input = textmode::Input::new().await?; diff --git a/src/pipeline/command.rs b/src/pipeline/command.rs new file mode 100644 index 0000000..5a7798c --- /dev/null +++ b/src/pipeline/command.rs @@ -0,0 +1,100 @@ +use async_std::os::unix::process::CommandExt as _; + +pub enum Command { + Binary(async_std::process::Command), + Builtin(crate::builtins::Command), +} + +impl Command { + 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) + } + + pub fn stdin(&mut self, fh: std::fs::File) { + match self { + Self::Binary(cmd) => { + cmd.stdin(fh); + } + Self::Builtin(cmd) => { + cmd.stdin(fh); + } + } + } + + pub fn stdout(&mut self, fh: std::fs::File) { + match self { + Self::Binary(cmd) => { + cmd.stdout(fh); + } + Self::Builtin(cmd) => { + cmd.stdout(fh); + } + } + } + + pub fn stderr(&mut self, fh: std::fs::File) { + match self { + Self::Binary(cmd) => { + cmd.stderr(fh); + } + Self::Builtin(cmd) => { + cmd.stderr(fh); + } + } + } + + pub unsafe fn pre_exec(&mut self, f: F) + where + F: 'static + FnMut() -> std::io::Result<()> + Send + Sync, + { + match self { + Self::Binary(cmd) => { + cmd.pre_exec(f); + } + Self::Builtin(cmd) => { + cmd.pre_exec(f); + } + } + } + + pub fn spawn(self, env: &crate::env::Env) -> anyhow::Result { + match self { + Self::Binary(mut cmd) => Ok(Child::Binary(cmd.spawn()?)), + Self::Builtin(cmd) => Ok(Child::Builtin(cmd.spawn(env)?)), + } + } +} + +pub enum Child { + Binary(async_std::process::Child), + Builtin(crate::builtins::Child), +} + +impl Child { + pub fn id(&self) -> Option { + match self { + Self::Binary(child) => Some(child.id()), + Self::Builtin(child) => child.id(), + } + } + + #[async_recursion::async_recursion] + pub async fn status(self) -> anyhow::Result { + match self { + Self::Binary(child) => Ok(child.status_no_drop().await?), + Self::Builtin(child) => Ok(child.status().await?), + } + } +} diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs new file mode 100644 index 0000000..b1e4c21 --- /dev/null +++ b/src/pipeline/mod.rs @@ -0,0 +1,154 @@ +use async_std::io::ReadExt as _; +use async_std::stream::StreamExt as _; +use std::os::unix::io::FromRawFd as _; +use std::os::unix::process::ExitStatusExt as _; + +const PID0: nix::unistd::Pid = nix::unistd::Pid::from_raw(0); + +mod command; +pub use command::{Child, Command}; + +pub async fn run() -> anyhow::Result { + let (code, pipeline) = read_data().await?; + let env = crate::env::Env::new(code); + let children = spawn_children(&pipeline, &env)?; + let count = children.len(); + + let mut children: futures_util::stream::FuturesUnordered<_> = + children + .into_iter() + .enumerate() + .map(|(i, child)| async move { + (child.status().await, i == count - 1) + }) + .collect(); + let mut final_status = None; + while let Some((status, last)) = children.next().await { + let status = status.unwrap_or_else(|_| { + async_std::process::ExitStatus::from_raw(1 << 8) + }); + // this conversion is safe because the Signal enum is repr(i32) + #[allow(clippy::as_conversions)] + if status.signal() == Some(nix::sys::signal::Signal::SIGINT as i32) { + nix::sys::signal::raise(nix::sys::signal::Signal::SIGINT)?; + } + if last { + final_status = Some(status); + } + } + + let final_status = final_status.unwrap(); + if let Some(signal) = final_status.signal() { + nix::sys::signal::raise(signal.try_into().unwrap())?; + } + Ok(final_status.code().unwrap()) +} + +async fn read_data() -> anyhow::Result<(i32, crate::parse::Pipeline)> { + // Safety: this code is only called by crate::history::run_pipeline, which + // passes data through on fd 3, and which will not spawn this process + // unless the pipe was successfully opened on that fd + let mut fd3 = unsafe { async_std::fs::File::from_raw_fd(3) }; + let mut be_bytes = [0; 4]; + fd3.read_exact(&mut be_bytes).await?; + let code = i32::from_be_bytes(be_bytes); + let mut pipeline = String::new(); + fd3.read_to_string(&mut pipeline).await?; + let ast = crate::parse::Pipeline::parse(&pipeline)?; + Ok((code, ast)) +} + +fn spawn_children( + pipeline: &crate::parse::Pipeline, + env: &crate::env::Env, +) -> anyhow::Result> { + let mut cmds: Vec<_> = pipeline.exes().iter().map(Command::new).collect(); + for i in 0..(cmds.len() - 1) { + let (r, w) = pipe()?; + cmds[i].stdout(w); + cmds[i + 1].stdin(r); + } + + let mut children = vec![]; + let mut pg_pid = None; + for mut cmd in cmds.drain(..) { + // Safety: setpgid is an async-signal-safe function + unsafe { + cmd.pre_exec(move || { + setpgid_child(pg_pid)?; + Ok(()) + }); + } + let child = cmd.spawn(env)?; + if let Some(id) = child.id() { + let child_pid = id_to_pid(id); + setpgid_parent(child_pid, pg_pid)?; + if pg_pid.is_none() { + pg_pid = Some(child_pid); + set_foreground_pg(child_pid)?; + } + } + children.push(child); + } + Ok(children) +} + +fn pipe() -> anyhow::Result<(std::fs::File, std::fs::File)> { + let (r, w) = nix::unistd::pipe2(nix::fcntl::OFlag::O_CLOEXEC)?; + // Safety: these file descriptors were just returned by pipe2 above, which + // means they must be valid otherwise that call would have returned an + // error + Ok((unsafe { std::fs::File::from_raw_fd(r) }, unsafe { + std::fs::File::from_raw_fd(w) + })) +} + +fn set_foreground_pg(pg: nix::unistd::Pid) -> anyhow::Result<()> { + let pty = nix::fcntl::open( + "/dev/tty", + nix::fcntl::OFlag::empty(), + nix::sys::stat::Mode::empty(), + )?; + nix::unistd::tcsetpgrp(pty, pg)?; + nix::unistd::close(pty)?; + 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(()) +} + +fn setpgid_child(pg: Option) -> std::io::Result<()> { + nix::unistd::setpgid(PID0, pg.unwrap_or(PID0))?; + Ok(()) +} + +fn setpgid_parent( + pid: nix::unistd::Pid, + pg: Option, +) -> anyhow::Result<()> { + 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. 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) + } + })?; + Ok(()) +} + +fn id_to_pid(id: u32) -> nix::unistd::Pid { + nix::unistd::Pid::from_raw(id.try_into().unwrap()) +} + +fn neg_pid(pid: nix::unistd::Pid) -> nix::unistd::Pid { + nix::unistd::Pid::from_raw(-pid.as_raw()) +} diff --git a/src/state/history/mod.rs b/src/state/history/mod.rs index 7423727..a7b5d88 100644 --- a/src/state/history/mod.rs +++ b/src/state/history/mod.rs @@ -99,7 +99,7 @@ impl History { run_commands( ast.clone(), async_std::sync::Arc::clone(&entry), - crate::command::Env::new(0), + crate::env::Env::new(0), input_r, resize_r, event_w, @@ -516,7 +516,7 @@ impl ExitInfo { fn run_commands( ast: crate::parse::Commands, entry: async_std::sync::Arc>, - mut env: crate::command::Env, + mut env: crate::env::Env, input_r: async_std::channel::Receiver>, resize_r: async_std::channel::Receiver<(u16, u16)>, event_w: async_std::channel::Sender, @@ -560,7 +560,7 @@ fn run_commands( async fn run_pipeline( pipeline: &crate::parse::Pipeline, pty: &pty::Pty, - env: &crate::command::Env, + env: &crate::env::Env, ) -> (async_std::process::ExitStatus, bool) { let mut cmd = pty_process::Command::new(std::env::current_exe().unwrap()); cmd.arg("--internal-cmd-runner"); diff --git a/src/state/readline.rs b/src/state/readline.rs index 41ffbe7..014efd8 100644 --- a/src/state/readline.rs +++ b/src/state/readline.rs @@ -24,11 +24,11 @@ impl Readline { focus: bool, offset: time::UtcOffset, ) -> anyhow::Result<()> { - let pwd = crate::env::pwd()?; - let user = crate::env::user()?; - let hostname = crate::env::hostname()?; - let time = crate::env::time(offset)?; - let prompt_char = crate::env::prompt_char()?; + let pwd = crate::info::pwd()?; + let user = crate::info::user()?; + let hostname = crate::info::hostname()?; + let time = crate::info::time(offset)?; + let prompt_char = crate::info::prompt_char()?; let id = format!("{}@{}", user, hostname); let idlen: u16 = id.len().try_into().unwrap(); -- cgit v1.2.3-54-g00ecf