Skip to content

Commit

Permalink
Handle Divert resulting in subshells
Browse files Browse the repository at this point in the history
This commit modifies the parameter function to Env::start_subshell and
Env::run_in_subshell so that it returns a yash_env::exec::Result.
Env::start_subshell now can consistently handle Divert the function may
return.
  • Loading branch information
magicant committed Oct 8, 2021
1 parent 6dfd044 commit cfa9849
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 18 deletions.
29 changes: 26 additions & 3 deletions yash-env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ use std::collections::HashMap;
use std::fmt::Debug;
use std::future::ready;
use std::future::Future;
use std::ops::ControlFlow::Break;
use std::pin::Pin;
use std::rc::Rc;
use yash_syntax::alias::AliasSet;
Expand Down Expand Up @@ -175,14 +176,29 @@ impl Env {
/// Although this function is `async`, it does not wait for the child to
/// finish, which means the parent and child processes will run
/// concurrently.
///
/// If function `f` returns an `Err(Divert::...)`, it is handled as follows:
///
/// - `Interrupt` and `Exit` with `Some(exit_status)` override the exit
/// status in `Env`.
/// - Other `Divert` values are ignored.
pub async fn start_subshell<F>(&mut self, f: F) -> nix::Result<Pid>
where
F: for<'a> FnOnce(&'a mut Env) -> Pin<Box<dyn Future<Output = ()> + 'a>> + 'static,
F: for<'a> FnOnce(&'a mut Env) -> Pin<Box<dyn Future<Output = self::exec::Result> + 'a>>
+ 'static,
{
let mut f = Some(f);
let task: ChildProcessTask = Box::new(move |env| {
if let Some(f) = f.take() {
Box::pin(f(env))
Box::pin(async move {
use self::exec::Divert::{Exit, Interrupt};
match f(env).await {
Break(Exit(Some(exit_status))) | Break(Interrupt(Some(exit_status))) => {
env.exit_status = exit_status
}
_ => (),
}
})
} else {
Box::pin(ready(()))
}
Expand Down Expand Up @@ -213,6 +229,9 @@ impl Env {
/// the current shell continues waiting for the subshell to finish, so it
/// must be resumed by some other means.
///
/// See [`start_subshell`](Self::start_subshell) for the behavior of the
/// subshell in case function `f` returns an `Err(Divert::...)`.
///
/// # Return value
///
/// This function usually returns the exit status of the subshell that is
Expand All @@ -221,7 +240,8 @@ impl Env {
/// [`new_child_process`](System::new_child_process), the error is returned.
pub async fn run_in_subshell<F>(&mut self, f: F) -> nix::Result<ExitStatus>
where
F: for<'a> FnOnce(&'a mut Env) -> Pin<Box<dyn Future<Output = ()> + 'a>> + 'static,
F: for<'a> FnOnce(&'a mut Env) -> Pin<Box<dyn Future<Output = self::exec::Result> + 'a>>
+ 'static,
{
// TODO Use a virtual subshell when possible
let child_pid = self.start_subshell(f).await?;
Expand Down Expand Up @@ -282,6 +302,7 @@ mod tests {
use futures_util::task::LocalSpawnExt;
use std::cell::Cell;
use std::cell::RefCell;
use std::ops::ControlFlow::Continue;

/// Helper function to perform a test in a virtual system with an executor.
pub fn in_virtual_system<F, Fut>(f: F)
Expand Down Expand Up @@ -337,6 +358,7 @@ mod tests {
.run_in_subshell(move |env| {
Box::pin(async move {
env.exit_status = status;
Continue(())
})
})
.await;
Expand Down Expand Up @@ -366,6 +388,7 @@ mod tests {
.start_subshell(|env| {
Box::pin(async move {
env.exit_status = ExitStatus(42);
Continue(())
})
})
.await
Expand Down
16 changes: 3 additions & 13 deletions yash-semantics/src/command_impl/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,27 +141,17 @@ async fn connect_pipe_and_execute_command(
env: &mut Env,
pipes: PipeSet,
command: Rc<syntax::Command>,
) {
) -> Result {
match pipes.move_to_stdin_stdout(env) {
Ok(()) => (),
Err(errno) => {
env.print_system_error(errno, &format_args!("cannot connect pipes in the pipeline"))
.await;
env.exit_status = ExitStatus::NOEXEC;
return;
return Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)));
}
}

// TODO This part of code should be the same as subshell executor. Extract a function.
match command.execute(env).await {
Continue(()) => (),
Break(Divert::Interrupt(exit_status) | Divert::Exit(exit_status)) => {
if let Some(exit_status) = exit_status {
env.exit_status = exit_status;
}
}
Break(divert) => todo!("subshell finished with {:?}", divert),
}
command.execute(env).await
}

async fn pid_or_fail(env: &mut Env, pid: std::result::Result<Pid, Errno>) -> Result<Pid> {
Expand Down
5 changes: 3 additions & 2 deletions yash-semantics/src/command_impl/simple_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ async fn execute_external_utility(
for redir in &*redirs {
if let Err(e) = env.perform_redir(redir).await {
e.handle(&mut env).await;
return;
return Continue(());
}
}

Expand All @@ -253,7 +253,8 @@ async fn execute_external_utility(
errno.desc().into(),
&location,
)
.await
.await;
Continue(())
})
})
.await;
Expand Down

0 comments on commit cfa9849

Please sign in to comment.