improved pipes

This commit is contained in:
Daniel Bulant 2023-08-06 12:43:16 +02:00
parent a796812556
commit 1e9c49de0f
5 changed files with 214 additions and 203 deletions

77
Cargo.lock generated
View file

@ -176,6 +176,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "os_pipe"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ae859aa07428ca9a929b936690f8b12dc5f11dd8c6992a18ca93919f28bc177"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "os_str_bytes"
version = "6.0.0"
@ -228,6 +238,7 @@ dependencies = [
"anyhow",
"clap",
"filedescriptor",
"os_pipe",
"termion",
"utf8-chars",
]
@ -347,3 +358,69 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"

View file

@ -2,7 +2,7 @@
name = "rush"
version = "0.1.0"
authors = ["Daniel Bulant <danbulant@gmail.com>"]
edition = "2018"
edition = "2021"
description = "A simple rust shell"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -12,6 +12,7 @@ utf8-chars = "1.0.0"
termion = "1.5.6"
filedescriptor = "0.8.1"
clap = "3.1.0"
os_pipe = "1.1.4"
[dependencies.anyhow]
version = "1.0.54"

View file

@ -670,6 +670,6 @@ pub fn build_tree(tokens: Vec<Token>) -> Result<Vec<Expression>> {
}
}
}
// dbg!(&expressions);
dbg!(&expressions);
Ok(expressions)
}

View file

@ -14,89 +14,6 @@ trait GetValue {
fn get(&mut self, ctx: &mut vars::Context) -> Result<Variable>;
}
struct ExecResult {
cmd: Option<Command>,
child: Option<Child>
}
impl ExecResult {
fn new(cmd: Option<Command>, child: Option<Child>) -> Self {
Self { cmd, child }
}
/// Spawns the result, running the command (if any). Non-command results won't be spawned (like let statements)
fn spawn(&mut self) -> &mut Self {
if !self.started() {
match &mut self.cmd {
None => {},
Some(cmd) => {
self.child = Some(cmd.spawn().unwrap());
}
}
}
self
}
/// Checks if the result was spawned before by checking the child property. Non-command results won't ever be spawned (like let statements)
fn started(&self) -> bool {
matches!(self.child, Some(_))
}
/// A simple wrapper for redirecting current result (self) into STDIO (files or streams).
///
/// Does spawn the current result
fn redirect_into<T: std::io::Write>(mut self, into: &mut T) -> &mut T {
match &mut self.cmd {
None => {},
Some(cmd) => {
cmd.stdout(Stdio::piped());
self.spawn();
let child = self.child.unwrap();
let mut stdout = child.stdout.unwrap();
io::copy(&mut stdout, into).unwrap();
}
}
into
}
/// A shorthand for redirecting current result into the next one
///
/// Uses `redirect_from_result` of the next result. Spawns this result, but not the next one.
fn redirect_into_result(&mut self, into: &mut ExecResult) -> &mut Self {
into.redirect_from_result(self).unwrap();
self
}
/// Redirects the `from` into the current pending result
///
/// Doesn't spawn the current result
fn redirect_from<T: Into<Stdio>>(&mut self, from: T) -> &mut Self {
match &mut self.cmd {
None => {},
Some(cmd) => {
cmd.stdin(from);
}
}
self
}
/// A shortcut for redirecting a previous result into the current one
///
/// Spawns the previous result to obtain the output, but not the current one (self)
fn redirect_from_result(&mut self, into: &mut ExecResult) -> io::Result<&mut Self> {
if matches!(self.cmd, None) {
return Ok(self);
}
match &mut self.cmd {
None => {},
Some(source) => {
source.stdout(Stdio::piped());
match &mut into.cmd {
None => {},
Some(target) => {
target.stdin(source.spawn()?.stdout.unwrap());
}
};
}
}
Ok(self)
}
}
impl GetValue for CommandValue {
fn get(self: &mut CommandValue, ctx: &mut vars::Context) -> Result<Variable> {
match self {
@ -179,6 +96,31 @@ impl ExecExpression for Expression {
}
}
impl ExecExpression for Command {
fn exec(&mut self, ctx: &mut Context) -> Result<Option<Command>> {
let overrides = ctx.get_overrides()?;
if let Some(stdout) = overrides.stdout { self.stdout(stdout); }
if let Some(stderr) = overrides.stderr { self.stderr(stderr); }
if let Some(stdin) = overrides.stdin { self.stdin(stdin); }
let name = self.get_program().to_str().unwrap_or("unknown").to_string();
let out = self.spawn()
.with_context(|| "Failed to spawn process ".to_string() + &name)?
.wait()
.with_context(|| "Failed to wait for process")?;
ctx.set_var(String::from("?"), Variable::I32(out.code().unwrap_or(-1)));
Ok(None)
}
}
impl ExecExpression for Option<Command> {
fn exec(&mut self, ctx: &mut Context) -> Result<Option<Command>> {
match self {
None => Ok(None),
Some(cmd) => cmd.exec(ctx)
}
}
}
impl ExecExpression for BreakExpression {
fn exec(self: &mut BreakExpression, ctx: &mut vars::Context) -> Result<Option<Command>> {
if ctx.break_num > 0 { ctx.break_num -= 1; return Ok(None) }
@ -199,26 +141,15 @@ impl ExecExpression for WhileExpression {
ctx.add_scope();
let mut res = None;
loop {
match condition.spawn() {
Err(_) => {
res = Some(condition);
break;
},
Ok(mut child) => {
if !child.wait()?.success() {
res = Some(condition);
break
} else {
match res {
None => {},
Some(mut cmd) => {
wait_child(cmd.spawn()?, ctx)?;
}
}
let condres = condition.exec(ctx)?;
let code = ctx.get_last_exit_code().unwrap_or(1);
if code == 0 {
res = self.contents.exec(ctx)?
} else {
res = condres;
break;
}
}
};
if ctx.break_num > 0 {
ctx.break_num -= 1;
break;
@ -261,12 +192,7 @@ impl ExecExpression for ForExpression {
} else {
for (i, val) in arr.iter().enumerate() {
process(i, val.clone(), ctx, &arg_key, &arg_value)?;
match res {
None => {},
Some(mut cmd) => {
wait_child(cmd.spawn()?, ctx)?;
}
}
res.exec(ctx)?;
res = self.contents.exec(ctx)?;
ctx.pop_scope();
if ctx.break_num > 0 {
@ -282,12 +208,7 @@ impl ExecExpression for ForExpression {
} else {
for (i, char) in str.chars().enumerate() {
process(i, Variable::String(char.to_string()), ctx, &arg_key, &arg_value)?;
match res {
None => {},
Some(mut cmd) => {
wait_child(cmd.spawn()?, ctx)?;
}
}
res.exec(ctx)?;
res = self.contents.exec(ctx)?;
ctx.pop_scope();
if ctx.break_num > 0 {
@ -312,18 +233,13 @@ impl ExecExpression for IfExpression {
Some(cmd) => cmd
};
ctx.add_scope();
let res = match condition.spawn() {
Result::Err(_) => {
self.else_contents.exec(ctx)?
},
Result::Ok(mut res) => {
if !res.wait()?.success() {
self.else_contents.exec(ctx)?
let mut res = condition.exec(ctx)?;
let code = ctx.get_last_exit_code().unwrap_or(1);
if code == 0 {
res = self.contents.exec(ctx)?;
} else {
self.contents.exec(ctx)?
res = self.else_contents.exec(ctx)?;
}
}
};
ctx.pop_scope();
Ok(res)
@ -357,15 +273,14 @@ impl ExecExpression for Vec<CommandValue> {
impl ExecExpression for RedirectTargetExpression {
fn exec(self: &mut RedirectTargetExpression, ctx: &mut vars::Context) -> Result<Option<Command>> {
if ctx.break_num > 0 { return Ok(None) }
let (reader, writer) = os_pipe::pipe()?;
let mut src = self.source.exec(ctx)?.unwrap();
let mut target = self.target.exec(ctx)?.unwrap();
src.stdout(Stdio::piped());
match src.spawn() {
Result::Err(e) => { println!("Error executing: {}", e)},
Result::Ok(res) => {
target.stdin(res.stdout.unwrap());
}
}
target.stdin(reader);
ctx.add_scope();
ctx.scopes.last_mut().unwrap().stdout_override = Some(writer);
src.exec(ctx)?;
ctx.pop_scope();
Ok(Some(target))
}
@ -382,23 +297,11 @@ impl ExecExpression for FileTargetExpression {
todo!("Redirect without target file");
}
};
let command = match src {
Some(mut cmd) => {
cmd.stdout(Stdio::piped());
let file = File::create(target.to_string());
match file {
Result::Err(e) => println!("Error: {}", e),
Result::Ok(mut file) => {
match cmd.spawn() {
Result::Err(e) => {
println!("Error executing command: {}", e);
},
Result::Ok(res) => {
io::copy(&mut res.stdout.unwrap(), &mut file)?;
}
}
}
}
let file = File::create(target.to_string())?;
cmd.stdout(file);
cmd
},
None => { bail!("Invalid command provided for file target"); }
@ -422,10 +325,7 @@ impl ExecExpression for FileSourceExpression {
None => { bail!("Invalid command") },
Some(cmd) => cmd
};
let source = match File::open(source) {
Result::Err(e) => bail!("Cannot open file: {}", e),
Result::Ok(file) => file
};
let source = File::open(source).with_context(|| "Couldn't open file to read")?;
command.stdin(source);
Ok(Some(command))
@ -437,6 +337,7 @@ impl ExecExpression for Vec<Expression> {
if ctx.break_num > 0 { return Ok(None) }
let mut last = None;
for expr in self {
last.exec(ctx)?;
last = expr.exec(ctx)?;
if ctx.break_num > 0 { return Ok(last) }
}
@ -451,21 +352,14 @@ impl ExecExpression for OrExpression {
None => bail!("Invalid OR expression"),
Some(cmd) => cmd
};
let res = match first.spawn() {
Result::Err(_) => {
self.second.exec(ctx)?
},
Result::Ok(mut res) => {
if res.wait()?.success() {
Some(first)
first.exec(ctx)?;
let code = ctx.get_last_exit_code().unwrap_or(1);
if code == 0 {
Ok(Some(first))
} else {
self.second.exec(ctx)?
self.second.exec(ctx)
}
}
};
Ok(res)
}
}
impl ExecExpression for AndExpression {
@ -475,43 +369,20 @@ impl ExecExpression for AndExpression {
None => bail!("Invalid AND expression"),
Some(cmd) => cmd
};
let res = match first.spawn() {
Result::Err(_) => {
Some(first)
},
Result::Ok(mut res) => {
if !res.wait()?.success() {
Some(first)
first.exec(ctx)?;
let code = ctx.get_last_exit_code().unwrap_or(1);
if code == 0 {
self.second.exec(ctx)
} else {
self.second.exec(ctx)?
Ok(Some(first))
}
}
};
Ok(res)
}
}
fn wait_child(mut child: Child, ctx: &mut Context) -> Result<()> {
let out = child.wait()?;
ctx.set_var(String::from("?"), Variable::I32(out.code().unwrap_or(1)));
Ok(())
}
pub fn exec_tree(tree: Vec<Expression>, ctx: &mut vars::Context) -> Result<()> {
for mut expression in tree {
let cmd = expression.exec(ctx)?;
match cmd {
None => {},
Some(mut cmd) => match cmd.spawn() {
Result::Err(e) => {
println!("Error executing: {}", e);
},
Result::Ok(mut res) => {
wait_child(res, ctx)?;
}
}
}
let mut cmd = expression.exec(ctx)?;
cmd.exec(ctx)?;
if ctx.break_num > 0 { bail!("Too many break statements") }
}
Ok(())

View file

@ -1,6 +1,7 @@
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};
use anyhow::{bail, Result};
use os_pipe::{PipeReader, PipeWriter};
use crate::parser::ast::FunctionDefinitionExpression;
#[derive(Debug, Clone)]
@ -171,6 +172,12 @@ pub enum AnyFunction<'a> {
UserDefined(&'a mut FunctionDefinitionExpression)
}
pub struct Overrides {
pub stdin: Option<PipeReader>,
pub stdout: Option<PipeWriter>,
pub stderr: Option<PipeWriter>
}
#[derive(Debug)]
pub struct Scope {
/// list of variables
@ -178,7 +185,10 @@ pub struct Scope {
/// list of functions
pub func: HashMap<String, FunctionDefinitionExpression>,
/// list of file descriptors, to be closed when the scope is left
pub fd: Vec<usize>
pub fd: Vec<usize>,
pub stdin_override: Option<PipeReader>,
pub stdout_override: Option<PipeWriter>,
pub stderr_override: Option<PipeWriter>
}
#[derive(Debug)]
@ -213,7 +223,10 @@ impl Context {
let scope = Scope {
func: HashMap::new(),
vars: HashMap::new(),
fd: Vec::new()
fd: Vec::new(),
stdin_override: None,
stdout_override: None,
stderr_override: None
};
self.scopes.push(scope);
}
@ -241,6 +254,14 @@ impl Context {
None
}
pub fn get_last_exit_code(&mut self) -> Option<i32> {
let var = self.get_var("?");
match var {
Some(Variable::I32(int)) => Some(*int),
_ => None,
}
}
pub fn set_var(&mut self, key: String, val: Variable) {
let vars = &mut self.scopes.last_mut().unwrap().vars;
if key.starts_with("env::") {
@ -275,4 +296,45 @@ impl Context {
let func = &mut self.scopes.last_mut().unwrap().func;
func.insert(key, val);
}
/// Gets relevant overrides. Should only be used before running a command, as it will clone all pipes
pub fn get_overrides(&self) -> Result<Overrides> {
let mut overrides = Overrides {
stdin: None,
stdout: None,
stderr: None
};
for scope in self.scopes.iter().rev() {
match overrides.stdin {
Some(_) => {}
None => {
match &scope.stdin_override {
Some(stdin) => overrides.stdin = Some(stdin.try_clone()?),
None => {}
}
}
}
match overrides.stderr {
Some(_) => {}
None => {
match &scope.stderr_override {
Some(stderr) => overrides.stderr = Some(stderr.try_clone()?),
None => {}
}
}
}
match overrides.stdout {
Some(_) => {}
None => {
match &scope.stdout_override {
Some(stdout) => overrides.stdout = Some(stdout.try_clone()?),
None => {}
}
}
}
}
Ok(overrides)
}
}