Rust · 4493 bytes Raw Blame History
1 //! Subshell execution
2 //!
3 //! Subshells execute commands in a subprocess, providing isolation:
4 //! - Variable changes don't affect the parent shell
5 //! - Directory changes (cd) don't affect the parent
6 //! - `exit` terminates the subshell, not the parent
7
8 use rush_expand::Context;
9 use rush_parser::ast::Subshell;
10 use crate::command::ExecutionResult;
11
12 /// Execute commands in a subshell (subprocess)
13 ///
14 /// Syntax: (command; command; ...)
15 ///
16 /// The commands run in a forked child process, so any state changes
17 /// (variables, working directory, etc.) are isolated from the parent shell.
18 #[cfg(unix)]
19 pub fn execute_subshell(
20 subshell: &Subshell,
21 context: &mut Context,
22 ) -> Result<ExecutionResult, String> {
23 use nix::sys::wait::{waitpid, WaitStatus};
24 use nix::unistd::{fork, ForkResult};
25 use std::process;
26
27 // Fork the process
28 match unsafe { fork() } {
29 Ok(ForkResult::Child) => {
30 // Child process: execute the commands
31 let exit_code = execute_subshell_child(&subshell.commands, context);
32 process::exit(exit_code);
33 }
34 Ok(ForkResult::Parent { child }) => {
35 // Parent process: wait for child to complete
36 match waitpid(child, None) {
37 Ok(WaitStatus::Exited(_, exit_code)) => {
38 // Child exited normally
39 Ok(crate::command::exit_code_to_result(exit_code))
40 }
41 Ok(WaitStatus::Signaled(_, signal, _)) => {
42 // Child was killed by a signal
43 // Return 128 + signal number (bash convention)
44 Ok(crate::command::exit_code_to_result(128 + signal as i32))
45 }
46 Ok(_) => {
47 // Other wait status (stopped, continued, etc.)
48 Ok(crate::command::exit_code_to_result(1))
49 }
50 Err(e) => {
51 Err(format!("Failed to wait for subshell: {}", e))
52 }
53 }
54 }
55 Err(e) => {
56 Err(format!("Failed to fork for subshell: {}", e))
57 }
58 }
59 }
60
61 /// Execute commands in the child process
62 /// Returns the exit code to use for process::exit()
63 #[cfg(unix)]
64 pub(crate) fn execute_subshell_child(
65 commands: &[rush_parser::CompleteCommand],
66 context: &mut Context,
67 ) -> i32 {
68 let mut last_exit_code = 0;
69
70 for cmd in commands {
71 match crate::control_flow::execute_complete_command(cmd, context) {
72 Ok(result) => {
73 last_exit_code = result.exit_code();
74 context.set_exit_status(last_exit_code);
75 }
76 Err(e) => {
77 eprintln!("rush: {}", e);
78 return 1;
79 }
80 }
81 }
82
83 last_exit_code
84 }
85
86 /// Execute a subshell in the background
87 /// Returns (pid, pgid, command_string)
88 #[cfg(unix)]
89 pub fn execute_subshell_background(
90 subshell: &Subshell,
91 context: &mut Context,
92 ) -> Result<(nix::unistd::Pid, nix::unistd::Pid, String), String> {
93 use nix::unistd::{fork, ForkResult, setpgid, Pid};
94
95 // Build a command string for display
96 let command_string = format!("(subshell with {} commands)", subshell.commands.len());
97
98 // Fork the process
99 match unsafe { fork() } {
100 Ok(ForkResult::Child) => {
101 // Child process: create new process group and execute
102 let pid = std::process::id() as i32;
103 let _ = setpgid(Pid::from_raw(pid), Pid::from_raw(pid));
104
105 let exit_code = execute_subshell_child(&subshell.commands, context);
106 std::process::exit(exit_code);
107 }
108 Ok(ForkResult::Parent { child }) => {
109 // Create new process group for the child
110 let _ = setpgid(child, child);
111
112 // Don't wait - that's what makes it background
113 Ok((child, child, command_string))
114 }
115 Err(e) => {
116 Err(format!("Failed to fork for background subshell: {}", e))
117 }
118 }
119 }
120
121 /// Fallback for non-Unix platforms
122 #[cfg(not(unix))]
123 pub fn execute_subshell(
124 _subshell: &Subshell,
125 _context: &mut Context,
126 ) -> Result<ExecutionResult, String> {
127 Err("Subshells are not supported on this platform".to_string())
128 }
129
130 #[cfg(not(unix))]
131 pub fn execute_subshell_background(
132 _subshell: &Subshell,
133 _context: &mut Context,
134 ) -> Result<(i32, i32, String), String> {
135 Err("Background subshells are not supported on this platform".to_string())
136 }
137