mirror of
https://github.com/fish-shell/fish-shell
synced 2024-11-10 15:14:44 +00:00
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:
parent
54d8d169b5
commit
1b1bc28c0a
4 changed files with 60 additions and 11 deletions
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
11
src/reader.h
11
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
|
||||
|
|
Loading…
Reference in a new issue