Skip to content

Commit 0773ce6

Browse files
authored
Merge pull request #393 from grisenti/main
sh: fixes
2 parents d15e7d8 + fe573c9 commit 0773ce6

File tree

6 files changed

+161
-79
lines changed

6 files changed

+161
-79
lines changed

sh/src/builtin/read.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn read_until_from_non_blocking_fd(
8181
}
8282
}
8383
// might receive signals while reading
84-
shell.update_global_state();
84+
shell.handle_async_events();
8585
std::thread::sleep(Duration::from_millis(16));
8686
}
8787
if !buffer.is_empty() {

sh/src/builtin/trap.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use crate::signals::Signal;
1414
use std::fmt::Display;
1515
use std::str::FromStr;
1616

17-
#[derive(Clone)]
17+
#[derive(Clone, PartialEq, Eq)]
1818
pub enum TrapAction {
1919
Default,
2020
Ignore,

sh/src/cli/vi/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,13 @@ impl ViEditor {
821821
}
822822
Ok(Action::None)
823823
}
824+
825+
pub fn reset_current_line(&mut self) {
826+
self.edit_line.clear();
827+
self.cursor.position = 0;
828+
self.current_history_command = 0;
829+
self.mode = EditorMode::Insert;
830+
}
824831
}
825832

826833
impl Default for ViEditor {

sh/src/main.rs

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ use crate::cli::args::{parse_args, ExecutionMode};
1111
use crate::cli::terminal::is_attached_to_terminal;
1212
use crate::cli::{clear_line, set_cursor_pos};
1313
use crate::shell::Shell;
14-
use crate::signals::setup_signal_handling;
14+
use crate::signals::{
15+
handle_signal_ignore, handle_signal_write_to_signal_buffer, setup_signal_handling, Signal,
16+
};
1517
use crate::utils::is_process_in_foreground;
1618
use cli::terminal::read_nonblocking_char;
1719
use cli::vi::{Action, ViEditor};
1820
use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory};
19-
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigSet};
20-
use nix::sys::signal::{SigHandler, Signal as NixSignal};
2121
use std::error::Error;
2222
use std::io;
2323
use std::io::Write;
@@ -140,7 +140,14 @@ fn standard_repl(shell: &mut Shell) {
140140
flush_stdout();
141141
}
142142
std::thread::sleep(Duration::from_millis(16));
143-
shell.update_global_state();
143+
shell.signal_manager.reset_sigint_count();
144+
shell.handle_async_events();
145+
if shell.signal_manager.get_sigint_count() > 0 {
146+
program_buffer.clear();
147+
line_buffer.clear();
148+
println!();
149+
eprint!("{}", shell.get_ps1());
150+
}
144151
if shell.set_options.vi {
145152
return;
146153
}
@@ -149,7 +156,7 @@ fn standard_repl(shell: &mut Shell) {
149156

150157
fn vi_repl(shell: &mut Shell) {
151158
let mut editor = ViEditor::default();
152-
let mut buffer = Vec::new();
159+
let mut program_buffer = Vec::new();
153160
let mut print_ps2 = false;
154161
clear_line();
155162
flush_stdout();
@@ -158,29 +165,29 @@ fn vi_repl(shell: &mut Shell) {
158165
while let Some(c) = read_nonblocking_char() {
159166
match editor.process_new_input(c, shell) {
160167
Ok(Action::Execute(command)) => {
161-
buffer.extend(command.iter());
162-
if buffer.ends_with(b"\\\n") {
168+
program_buffer.extend(command.iter());
169+
if program_buffer.ends_with(b"\\\n") {
163170
continue;
164171
}
165-
let program_string = match std::str::from_utf8(&buffer) {
172+
let program_string = match std::str::from_utf8(&program_buffer) {
166173
Ok(buf) => buf,
167174
Err(_) => {
168175
eprintln!("sh: invalid utf-8 sequence");
169-
buffer.clear();
176+
program_buffer.clear();
170177
continue;
171178
}
172179
};
173180
println!();
174181
shell.terminal.reset();
175182
match shell.execute_program(program_string) {
176183
Ok(_) => {
177-
buffer.clear();
184+
program_buffer.clear();
178185
print_ps2 = false;
179186
}
180187
Err(syntax_err) => {
181188
if !syntax_err.could_be_resolved_with_more_input {
182189
eprintln!("sh: syntax error: {}", syntax_err.message);
183-
buffer.clear();
190+
program_buffer.clear();
184191
} else {
185192
print_ps2 = true;
186193
}
@@ -205,7 +212,14 @@ fn vi_repl(shell: &mut Shell) {
205212
flush_stdout()
206213
}
207214
std::thread::sleep(Duration::from_millis(16));
208-
shell.update_global_state();
215+
shell.signal_manager.reset_sigint_count();
216+
shell.handle_async_events();
217+
if shell.signal_manager.get_sigint_count() > 0 {
218+
program_buffer.clear();
219+
editor.reset_current_line();
220+
println!();
221+
eprint!("{}", shell.get_ps1());
222+
}
209223
if !shell.set_options.vi {
210224
return;
211225
}
@@ -218,24 +232,14 @@ fn interactive_shell(shell: &mut Shell) {
218232
nix::unistd::tcsetpgrp(io::stdin().as_fd(), pgid).unwrap();
219233
}
220234
shell.terminal.set_nonblocking_no_echo();
221-
let ignore_action = SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty());
222-
unsafe {
223-
sigaction(NixSignal::SIGQUIT, &ignore_action).unwrap();
224-
}
225-
unsafe {
226-
sigaction(NixSignal::SIGTERM, &ignore_action).unwrap();
227-
}
235+
unsafe { handle_signal_ignore(Signal::SigQuit) }
236+
unsafe { handle_signal_ignore(Signal::SigTerm) }
237+
unsafe { handle_signal_write_to_signal_buffer(Signal::SigInt) }
228238
if shell.set_options.monitor {
229239
// job control signals
230-
unsafe {
231-
sigaction(NixSignal::SIGTTIN, &ignore_action).unwrap();
232-
}
233-
unsafe {
234-
sigaction(NixSignal::SIGTTOU, &ignore_action).unwrap();
235-
}
236-
unsafe {
237-
sigaction(NixSignal::SIGTSTP, &ignore_action).unwrap();
238-
}
240+
unsafe { handle_signal_ignore(Signal::SigTtin) }
241+
unsafe { handle_signal_ignore(Signal::SigTtou) }
242+
unsafe { handle_signal_ignore(Signal::SigTstp) }
239243
}
240244
loop {
241245
if shell.set_options.vi {

sh/src/shell/mod.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ use crate::utils::{
3434
use crate::wordexp::{expand_word, expand_word_to_string, word_to_pattern};
3535
use nix::errno::Errno;
3636
use nix::libc;
37+
use nix::sys::signal::kill;
38+
use nix::sys::signal::Signal as NixSignal;
3739
use nix::sys::wait::{WaitPidFlag, WaitStatus};
38-
use nix::unistd::{getcwd, getpgrp, getpid, getppid, setpgid, ForkResult, Pid};
40+
use nix::unistd::{getcwd, getpgid, getpgrp, getpid, getppid, setpgid, tcsetpgrp, ForkResult, Pid};
3941
use std::collections::HashMap;
4042
use std::ffi::{CString, OsString};
4143
use std::fmt::{Display, Formatter};
@@ -217,15 +219,15 @@ impl Shell {
217219
return Ok(signal_to_exit_status(signal));
218220
}
219221
WaitStatus::StillAlive => {
220-
self.update_global_state();
222+
self.handle_async_events();
221223
std::thread::sleep(Duration::from_millis(16));
222224
}
223225
_ => unreachable!(),
224226
}
225227
}
226228
}
227229

228-
pub fn update_global_state(&mut self) {
230+
pub fn handle_async_events(&mut self) {
229231
self.process_signals();
230232
if self.set_options.monitor {
231233
if let Err(err) = self.background_jobs.update_jobs() {
@@ -262,8 +264,8 @@ impl Shell {
262264
}
263265

264266
pub fn process_signals(&mut self) {
265-
while let Some(action) = self.signal_manager.get_pending_action() {
266-
self.execute_action(action.clone())
267+
while let Some(action) = self.signal_manager.get_pending_action().cloned() {
268+
self.execute_action(action)
267269
}
268270
}
269271

@@ -729,17 +731,22 @@ impl Shell {
729731
match fork()? {
730732
ForkResult::Child => {
731733
self.become_subshell();
732-
setpgid(Pid::from_raw(0), Pid::from_raw(0))
733-
.expect("failed to create new process group for pipeline");
734+
// this should never fail as both arguments are valid
735+
setpgid(Pid::from_raw(0), Pid::from_raw(0)).unwrap();
734736
let pipeline_pgid = getpgrp();
737+
// wait for the parent process to put the subshell in the foreground
738+
if let Err(err) = kill(Pid::from_raw(0), NixSignal::SIGTSTP) {
739+
self.eprint(&format!("sh: internal call to kill failed ({err})"));
740+
self.exit(1);
741+
}
735742

736743
let mut current_stdin = libc::STDIN_FILENO;
737744
for command in pipeline.commands.head() {
738745
let (read_pipe, write_pipe) = pipe()?;
739746
match fork()? {
740747
ForkResult::Child => {
741-
setpgid(Pid::from_raw(0), pipeline_pgid)
742-
.expect("failed to set pipeline pgid");
748+
// should never fail as `pipeline_pgid` is a valid process group
749+
setpgid(Pid::from_raw(0), pipeline_pgid).unwrap();
743750
drop(read_pipe);
744751
dup2(current_stdin, libc::STDIN_FILENO)?;
745752
dup2(write_pipe.as_raw_fd(), libc::STDOUT_FILENO)?;
@@ -763,12 +770,46 @@ impl Shell {
763770
self.exit(return_status);
764771
}
765772
ForkResult::Parent { child } => {
766-
if is_process_in_foreground() {
767-
nix::unistd::tcsetpgrp(io::stdin().as_fd(), child).unwrap();
768-
pipeline_exit_status = self.wait_child_process(child)?;
769-
nix::unistd::tcsetpgrp(io::stdin().as_fd(), getpgrp()).unwrap();
770-
} else {
771-
pipeline_exit_status = self.wait_child_process(child)?;
773+
loop {
774+
match waitpid(child, Some(WaitPidFlag::WNOHANG | WaitPidFlag::WUNTRACED))? {
775+
WaitStatus::Exited(_, _) => {
776+
// the only way this happened is if there was an error before going
777+
// the child went to sleep
778+
return Ok(1);
779+
}
780+
WaitStatus::Signaled(_, _, _) => {
781+
self.eprint("sh: unsynchronised pipeline was terminated by another process\n");
782+
return Ok(1);
783+
}
784+
WaitStatus::Continued(_) => {
785+
self.eprint("sh: unsynchronised pipeline was restarted by another process\n");
786+
return Ok(1);
787+
}
788+
WaitStatus::Stopped(_, _) => {
789+
if is_process_in_foreground() {
790+
// should never fail as child is a valid process id and
791+
// in the same session as the current shell
792+
let child_gpid = getpgid(Some(child)).unwrap();
793+
// should never fail as stdin is a valid file descriptor and
794+
// child gpid is valid and in the same session
795+
tcsetpgrp(io::stdin().as_fd(), child_gpid).unwrap();
796+
kill(child, NixSignal::SIGCONT).unwrap();
797+
pipeline_exit_status = self.wait_child_process(child)?;
798+
// should never fail
799+
tcsetpgrp(io::stdin().as_fd(), getpgrp()).unwrap();
800+
break;
801+
} else {
802+
kill(child, NixSignal::SIGCONT).unwrap();
803+
pipeline_exit_status = self.wait_child_process(child)?;
804+
break;
805+
}
806+
}
807+
WaitStatus::StillAlive => {
808+
self.handle_async_events();
809+
std::thread::sleep(Duration::from_millis(16));
810+
}
811+
_ => unreachable!(),
812+
}
772813
}
773814
}
774815
}
@@ -958,6 +999,7 @@ impl Shell {
958999
history,
9591000
set_options,
9601001
is_interactive,
1002+
signal_manager: SignalManager::new(is_interactive),
9611003
..Default::default()
9621004
}
9631005
}
@@ -1019,7 +1061,7 @@ impl Default for Shell {
10191061
is_interactive: false,
10201062
last_lineno: 0,
10211063
exit_action: TrapAction::Default,
1022-
signal_manager: SignalManager::default(),
1064+
signal_manager: SignalManager::new(false),
10231065
background_jobs: JobManager::default(),
10241066
history: History::new(32767),
10251067
umask: !0o022 & 0o777,

0 commit comments

Comments
 (0)