From 1b1bc28c0ae0fd10e9d3854b1d8cc8125623e1d9 Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Mon, 20 Aug 2018 22:50:27 -0500 Subject: [PATCH] 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`). --- CHANGELOG.md | 1 + src/parse_execution.cpp | 26 ++++++++++++++++++++++++++ src/reader.cpp | 33 ++++++++++++++++++++++----------- src/reader.h | 11 +++++++++++ 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a7a724a3..f3f2188cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/src/parse_execution.cpp b/src/parse_execution.cpp index 15289ba56..b0ca7fa3d 100644 --- a/src/parse_execution.cpp +++ b/src/parse_execution.cpp @@ -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. diff --git a/src/reader.cpp b/src/reader.cpp index 53d9f3a9f..bb7b4745d 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -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); diff --git a/src/reader.h b/src/reader.h index 1dff23a6f..cf5f2029d 100644 --- a/src/reader.h +++ b/src/reader.h @@ -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