Skip to content

Commit dc97de7

Browse files
committed
sh: properly handle sigint in interactive mode
1 parent 9e1f34e commit dc97de7

File tree

5 files changed

+105
-63
lines changed

5 files changed

+105
-63
lines changed

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: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ 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};
21+
use nix::libc;
2122
use std::error::Error;
2223
use std::io;
2324
use std::io::Write;
@@ -140,7 +141,14 @@ fn standard_repl(shell: &mut Shell) {
140141
flush_stdout();
141142
}
142143
std::thread::sleep(Duration::from_millis(16));
144+
shell.signal_manager.reset_sigint_count();
143145
shell.handle_async_events();
146+
if shell.signal_manager.get_sigint_count() > 0 {
147+
program_buffer.clear();
148+
line_buffer.clear();
149+
println!();
150+
eprint!("{}", shell.get_ps1());
151+
}
144152
if shell.set_options.vi {
145153
return;
146154
}
@@ -149,7 +157,7 @@ fn standard_repl(shell: &mut Shell) {
149157

150158
fn vi_repl(shell: &mut Shell) {
151159
let mut editor = ViEditor::default();
152-
let mut buffer = Vec::new();
160+
let mut program_buffer = Vec::new();
153161
let mut print_ps2 = false;
154162
clear_line();
155163
flush_stdout();
@@ -158,29 +166,29 @@ fn vi_repl(shell: &mut Shell) {
158166
while let Some(c) = read_nonblocking_char() {
159167
match editor.process_new_input(c, shell) {
160168
Ok(Action::Execute(command)) => {
161-
buffer.extend(command.iter());
162-
if buffer.ends_with(b"\\\n") {
169+
program_buffer.extend(command.iter());
170+
if program_buffer.ends_with(b"\\\n") {
163171
continue;
164172
}
165-
let program_string = match std::str::from_utf8(&buffer) {
173+
let program_string = match std::str::from_utf8(&program_buffer) {
166174
Ok(buf) => buf,
167175
Err(_) => {
168176
eprintln!("sh: invalid utf-8 sequence");
169-
buffer.clear();
177+
program_buffer.clear();
170178
continue;
171179
}
172180
};
173181
println!();
174182
shell.terminal.reset();
175183
match shell.execute_program(program_string) {
176184
Ok(_) => {
177-
buffer.clear();
185+
program_buffer.clear();
178186
print_ps2 = false;
179187
}
180188
Err(syntax_err) => {
181189
if !syntax_err.could_be_resolved_with_more_input {
182190
eprintln!("sh: syntax error: {}", syntax_err.message);
183-
buffer.clear();
191+
program_buffer.clear();
184192
} else {
185193
print_ps2 = true;
186194
}
@@ -205,7 +213,14 @@ fn vi_repl(shell: &mut Shell) {
205213
flush_stdout()
206214
}
207215
std::thread::sleep(Duration::from_millis(16));
216+
shell.signal_manager.reset_sigint_count();
208217
shell.handle_async_events();
218+
if shell.signal_manager.get_sigint_count() > 0 {
219+
program_buffer.clear();
220+
editor.reset_current_line();
221+
println!();
222+
eprint!("{}", shell.get_ps1());
223+
}
209224
if !shell.set_options.vi {
210225
return;
211226
}
@@ -218,24 +233,14 @@ fn interactive_shell(shell: &mut Shell) {
218233
nix::unistd::tcsetpgrp(io::stdin().as_fd(), pgid).unwrap();
219234
}
220235
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-
}
236+
unsafe { handle_signal_ignore(Signal::SigQuit) }
237+
unsafe { handle_signal_ignore(Signal::SigTerm) }
238+
unsafe { handle_signal_write_to_signal_buffer(Signal::SigInt) }
228239
if shell.set_options.monitor {
229240
// 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-
}
241+
unsafe { handle_signal_ignore(Signal::SigTtin) }
242+
unsafe { handle_signal_ignore(Signal::SigTtou) }
243+
unsafe { handle_signal_ignore(Signal::SigTstp) }
239244
}
240245
loop {
241246
if shell.set_options.vi {

sh/src/shell/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ impl Shell {
264264
}
265265

266266
pub fn process_signals(&mut self) {
267-
while let Some(action) = self.signal_manager.get_pending_action() {
268-
self.execute_action(action.clone())
267+
while let Some(action) = self.signal_manager.get_pending_action().cloned() {
268+
self.execute_action(action)
269269
}
270270
}
271271

@@ -971,6 +971,7 @@ impl Shell {
971971
history,
972972
set_options,
973973
is_interactive,
974+
signal_manager: SignalManager::new(is_interactive),
974975
..Default::default()
975976
}
976977
}
@@ -1032,7 +1033,7 @@ impl Default for Shell {
10321033
is_interactive: false,
10331034
last_lineno: 0,
10341035
exit_action: TrapAction::Default,
1035-
signal_manager: SignalManager::default(),
1036+
signal_manager: SignalManager::new(false),
10361037
background_jobs: JobManager::default(),
10371038
history: History::new(32767),
10381039
umask: !0o022 & 0o777,

sh/src/signals.rs

Lines changed: 62 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ pub const SIGNALS: &[Signal] = &[
260260
static mut SIGNAL_WRITE: Option<RawFd> = None;
261261
static mut SIGNAL_READ: Option<RawFd> = None;
262262

263-
extern "C" fn handle_signals(signal: libc::c_int) {
263+
extern "C" fn write_signal_to_buffer(signal: libc::c_int) {
264264
// SIGNAL_WRITE is never modified after the initial
265265
// setup, and is a valid file descriptor, so this is safe
266266
let fd = unsafe { BorrowedFd::borrow_raw(SIGNAL_WRITE.unwrap()) };
@@ -304,12 +304,50 @@ fn get_pending_signal() -> Option<Signal> {
304304
}
305305
}
306306

307+
pub unsafe fn handle_signal_ignore(signal: Signal) {
308+
sigaction(
309+
signal.into(),
310+
&SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()),
311+
)
312+
.unwrap();
313+
}
314+
315+
pub unsafe fn handle_signal_default(signal: Signal) {
316+
sigaction(
317+
signal.into(),
318+
&SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()),
319+
)
320+
.unwrap();
321+
}
322+
323+
pub unsafe fn handle_signal_write_to_signal_buffer(signal: Signal) {
324+
sigaction(
325+
signal.into(),
326+
&SigAction::new(
327+
SigHandler::Handler(write_signal_to_buffer),
328+
SaFlags::empty(),
329+
SigSet::empty(),
330+
),
331+
)
332+
.unwrap();
333+
}
334+
307335
#[derive(Clone)]
308336
pub struct SignalManager {
309337
actions: [TrapAction; Signal::Count as usize],
338+
is_interactive: bool,
339+
sigint_count: u32,
310340
}
311341

312342
impl SignalManager {
343+
pub fn new(is_interactive: bool) -> Self {
344+
Self {
345+
actions: [const { TrapAction::Default }; Signal::Count as usize],
346+
is_interactive,
347+
sigint_count: 0,
348+
}
349+
}
350+
313351
pub fn reset(&mut self) {
314352
for signal in SIGNALS {
315353
let signal = *signal;
@@ -336,44 +374,35 @@ impl SignalManager {
336374

337375
pub fn set_action(&mut self, signal: Signal, action: TrapAction) {
338376
assert!(signal != Signal::SigKill && signal != Signal::SigStop);
377+
378+
if self.is_interactive
379+
&& signal == Signal::SigInt
380+
&& (action == TrapAction::Ignore || action == TrapAction::Default)
381+
{
382+
// in interactive mode we always want catch sigint
383+
unsafe { handle_signal_write_to_signal_buffer(Signal::SigInt) };
384+
self.actions[signal as usize] = action;
385+
return;
386+
}
339387
match action {
340388
TrapAction::Default => {
341-
unsafe {
342-
sigaction(
343-
signal.into(),
344-
&SigAction::new(SigHandler::SigDfl, SaFlags::empty(), SigSet::empty()),
345-
)
346-
.unwrap()
347-
};
389+
unsafe { handle_signal_default(signal) };
348390
}
349391
TrapAction::Ignore => {
350-
unsafe {
351-
sigaction(
352-
signal.into(),
353-
&SigAction::new(SigHandler::SigIgn, SaFlags::empty(), SigSet::empty()),
354-
)
355-
.unwrap()
356-
};
392+
unsafe { handle_signal_ignore(signal) };
357393
}
358394
TrapAction::Commands(_) => {
359-
unsafe {
360-
sigaction(
361-
signal.into(),
362-
&SigAction::new(
363-
SigHandler::Handler(handle_signals),
364-
SaFlags::empty(),
365-
SigSet::empty(),
366-
),
367-
)
368-
.unwrap()
369-
};
395+
unsafe { handle_signal_write_to_signal_buffer(signal) };
370396
}
371397
}
372398
self.actions[signal as usize] = action;
373399
}
374400

375-
pub fn get_pending_action(&self) -> Option<&TrapAction> {
401+
pub fn get_pending_action(&mut self) -> Option<&TrapAction> {
376402
if let Some(signal) = get_pending_signal() {
403+
if signal == Signal::SigInt {
404+
self.sigint_count += 1;
405+
}
377406
Some(&self.actions[signal as usize])
378407
} else {
379408
None
@@ -385,12 +414,12 @@ impl SignalManager {
385414
.iter()
386415
.map(move |&signal| (signal, &self.actions[signal as usize]))
387416
}
388-
}
389417

390-
impl Default for SignalManager {
391-
fn default() -> Self {
392-
Self {
393-
actions: [const { TrapAction::Default }; Signal::Count as usize],
394-
}
418+
pub fn reset_sigint_count(&mut self) {
419+
self.sigint_count = 0;
420+
}
421+
422+
pub fn get_sigint_count(&self) -> u32 {
423+
self.sigint_count
395424
}
396425
}

0 commit comments

Comments
 (0)