Protect against loss of background jobs on exec

`exec` now exhibits the same behavior as `exit` and prompts the user to
confirm their intention to end the current process if there are
background jobs running. Running `exec` again immediately thereafter
will force the exec to go through.

Additionally, background jobs are reaped upon exec to prevent process
leaking (same as `exit`).
This commit is contained in:
Mahmoud Al-Qudsi 2018-08-20 22:50:27 -05:00
parent 54d8d169b5
commit 1b1bc28c0a
4 changed files with 60 additions and 11 deletions

View file

@ -21,6 +21,7 @@ fish 3.0 is a major release which brings with it both improvements in functional
## Notable fixes and improvements
- A new feature flags mechanism is added for staging deprecations and breaking changes. Feature flags may be specified at launch with `fish --features ...` or by setting the universal `fish_features` variable. (#4940)
- `exec` now triggers the same safety features as `exit` and prompts for confirmation if background jobs are running.
- `wait` builtin is added for waiting on processes (#4498).
- `read` has a new `--delimiter` option as a better alternative to the `IFS` variable (#4256).
- `read` writes directly to stdout if called without arguments (#4407)

View file

@ -737,6 +737,32 @@ parse_execution_result_t parse_execution_context_t::populate_plain_process(
return parse_execution_errored;
}
// Protect against exec with background processes running
static uint32_t last_exec_run_counter = -1;
if (process_type == INTERNAL_EXEC) {
job_iterator_t jobs;
bool have_bg = false;
const job_t *bg = nullptr;
while ((bg = jobs.next())) {
if (!job_is_completed(bg)) {
have_bg = true;
break;
}
}
if (have_bg) {
/* debug(1, "Background jobs remain! run_counter: %u, last_exec_run_count: %u", reader_run_count(), last_exec_run_counter); */
if (isatty(STDIN_FILENO) && reader_run_count() - 1 != last_exec_run_counter) {
reader_bg_job_warning();
last_exec_run_counter = reader_run_count();
return parse_execution_errored;
}
else {
kill_background_jobs();
}
}
}
wcstring path_to_external_command;
if (process_type == EXTERNAL || process_type == INTERNAL_EXEC) {
// Determine the actual command. This may be an implicit cd.

View file

@ -2220,7 +2220,7 @@ bool shell_is_exiting() {
return end_loop;
}
static void bg_job_warning() {
void reader_bg_job_warning() {
fputws(_(L"There are still jobs active:\n"), stdout);
fputws(_(L"\n PID Command\n"), stdout);
@ -2235,11 +2235,19 @@ static void bg_job_warning() {
fputws(_(L"Use 'disown PID' to remove jobs from the list without terminating them.\n"), stdout);
}
void kill_background_jobs() {
job_iterator_t jobs;
while (job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
if (job_is_stopped(j)) job_signal(j, SIGCONT);
job_signal(j, SIGHUP);
}
}
}
/// This function is called when the main loop notices that end_loop has been set while in
/// interactive mode. It checks if it is ok to exit.
static void handle_end_loop() {
job_iterator_t jobs;
if (!reader_exit_forced()) {
const parser_t &parser = parser_t::principal_parser();
for (size_t i = 0; i < parser.block_count(); i++) {
@ -2251,6 +2259,7 @@ static void handle_end_loop() {
}
bool bg_jobs = false;
job_iterator_t jobs;
while (const job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
bg_jobs = true;
@ -2260,7 +2269,7 @@ static void handle_end_loop() {
reader_data_t *data = current_data();
if (!data->prev_end_loop && bg_jobs) {
bg_job_warning();
reader_bg_job_warning();
reader_exit(0, 0);
data->prev_end_loop = 1;
return;
@ -2268,13 +2277,7 @@ static void handle_end_loop() {
}
// Kill remaining jobs before exiting.
jobs.reset();
while (job_t *j = jobs.next()) {
if (!job_is_completed(j)) {
if (job_is_stopped(j)) job_signal(j, SIGCONT);
job_signal(j, SIGHUP);
}
}
kill_background_jobs();
}
static bool selection_is_at_top() {
@ -2289,6 +2292,13 @@ static bool selection_is_at_top() {
return true;
}
static uint32_t run_count = 0;
/// Returns the current interactive loop count
uint32_t reader_run_count() {
return run_count;
}
/// Read interactively. Read input from stdin while providing editing facilities.
static int read_i() {
reader_push(history_session_id());
@ -2305,6 +2315,7 @@ static int read_i() {
while ((!data->end_loop) && (!sanity_check())) {
event_fire_generic(L"fish_prompt");
run_count++;
if (is_breakpoint && function_exists(DEBUG_PROMPT_FUNCTION_NAME)) {
reader_set_left_prompt(DEBUG_PROMPT_FUNCTION_NAME);

View file

@ -227,4 +227,15 @@ wcstring completion_apply_to_command_line(const wcstring &val_str, complete_flag
const wcstring &command_line, size_t *inout_cursor_pos,
bool append_only);
/// Terminate all background jobs
void kill_background_jobs();
/// Print warning with list of backgrounded jobs
void reader_bg_job_warning();
/// Return the current interactive reads loop count. Useful for determining how many commands have
/// been executed between invocations of code.
uint32_t reader_run_count();
#endif