From d17d185fff39168941814e88e6814b686e295f13 Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Mon, 6 May 2024 11:15:17 -0400 Subject: [PATCH] fix for crash without arrays built in; allow some job status changes while the jobs list is frozen; change format for interactive shell terminated job notifications; allow some job notifications while running traps --- CWRU/CWRU.chlog | 50 +++++++++++++++++++++++++ MANIFEST | 1 + config-top.h | 2 +- doc/bash.1 | 25 ------------- execute_cmd.c | 6 ++- jobs.c | 97 ++++++++++++++++++++++++++++++++++++++---------- jobs.h | 11 ++++-- nojobs.c | 2 +- parse.y | 2 + shell.c | 4 +- subst.c | 6 ++- support/siglen.c | 28 ++++++++++++++ tests/jobs.right | 50 ++++++++++++------------- 13 files changed, 205 insertions(+), 79 deletions(-) create mode 100644 support/siglen.c diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index 03280dce..c4cb5621 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -9277,3 +9277,53 @@ subst.c - function_substitute: make sure we unbind the local REPLY we created at the current (fake) context From a report by Koichi Murase + + 4/30 + ---- + +subst.c + - expand_arrayref: fix crash from freeing memory that's only allocated + and initialized if ARRAY_VARS is defined + Report and patch from Henrik Lindström + +jobs.h,jobs.c + - JLIST_POSIX: new format argument for pretty_print_job; implements + POSIX requirement that jobs display the status of all background + jobs and all jobs the user hasn't been notified about (unused yet) + - JLIST_BGONLY: new format argument for pretty_print_job; restricts + output to background jobs only (unused yet) + +jobs.c,jobs.h,execute_cmd.c,subst.c + - freeze_jobs_list: now takes an int argument with the new value of + jobs_list_frozen; prep for having different values with different + meanings; changed callers + +jobs.c + - wait_for_any_job: return right away if jobs_list_frozen > 0; allow + job status changes (e.g., J_NOTIFIED) if jobs_list_frozen < 0; use + this instead of testing executing_funsub directly + - notify_and_cleanup: allow notification and status change if + jobs_list_frozen < 0; don't delete any dead jobs + - should_notify: takes a job index and returns 1 if the shell would + notify the user about it, given the current job state + - pretty_print_job: if the jobs list is frozen, only print status + about jobs for which the shell would notify users (by calling + should_notify()) + +subst.c + - function_substitute: freeze the jobs list with value -1 so jobs + can change status and possibly inhibit printing by `jobs' + + 5/2 + --- +jobs.c + - notify_and_cleanup: allow job notifications if an interactive shell + is running a trap (interactive == 0 && interactive_shell && running_trap) + Fixes report by Koichi Murase on 11/14/2022 + - print_pipeline: don't print an extra space before the pipeline; push + that into pretty_print_job; print the space after the pid if we + print one + +jobs.h + - LONGEST_SIGNAL_DESC: update to 27 (macos SIGPROF). This changes the + test output diff --git a/MANIFEST b/MANIFEST index 5c51c945..0d2f5f06 100644 --- a/MANIFEST +++ b/MANIFEST @@ -728,6 +728,7 @@ support/mkinstalldirs f 755 support/mkversion.sh f 755 support/mksignames.c f support/signames.c f +support/siglen.c f support/bashbug.sh f support/bashbug.sh.in f support/man2html.c f diff --git a/config-top.h b/config-top.h index 40e599fc..b6e73c4b 100644 --- a/config-top.h +++ b/config-top.h @@ -47,7 +47,7 @@ /* Define DONT_REPORT_SIGTERM if you don't want to see `Terminated' message when a job exits due to SIGTERM, since that's the default signal sent - by the kill builtin. */ + by the kill builtin. Only effective for non-interactive shells. */ #define DONT_REPORT_SIGTERM /* Define DONT_REPORT_BROKEN_PIPE_WRITE_ERRORS if you don't want builtins diff --git a/doc/bash.1 b/doc/bash.1 index 233eda53..0e21f409 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -14,31 +14,6 @@ .if \n(zY=1 .ig zY .TH BASH 1 "2024 April 23" "GNU Bash 5.3" .\" -.\" There's some problem with having a `@' -.\" in a tagged paragraph with the BSD man macros. -.\" It has to do with `@' appearing in the }1 macro. -.\" This is a problem on 4.3 BSD and Ultrix, but Sun -.\" appears to have fixed it. -.\" If you're seeing the characters -.\" `@u-3p' appearing before the lines reading -.\" `possible-hostname-completions -.\" and `complete-hostname' down in READLINE, -.\" then uncomment this redefinition. -.\" -.\" .de }1 -.\" .ds ]X \&\\*(]B\\ -.\" .nr )E 0 -.\" .if !"\\$1"" .nr )I \\$1n -.\" .}f -.\" .ll \\n(LLu -.\" .in \\n()Ru+\\n(INu+\\n()Iu -.\" .ti \\n(INu -.\" .ie !\\n()Iu+\\n()Ru-\w\\*(]Xu-3p \{\\*(]X -.\" .br\} -.\" .el \\*(]X\h|\\n()Iu+\\n()Ru\c -.\" .}f -.\" .. -.\" .ie \n(.g \{\ .ds ' \(aq .ds " \(dq diff --git a/execute_cmd.c b/execute_cmd.c index 8600de20..5a9477a4 100644 --- a/execute_cmd.c +++ b/execute_cmd.c @@ -2695,7 +2695,7 @@ execute_pipeline (COMMAND *command, int asynchronous, int pipe_in, int pipe_out, prev = NO_PIPE; add_unwind_protect (uw_restore_stdin, (void *) (intptr_t) lstdin); lastpipe_flag = 1; - old_frozen = freeze_jobs_list (); + old_frozen = freeze_jobs_list (1); lastpipe_jid = stop_pipeline (0, (COMMAND *)NULL); /* XXX */ add_unwind_protect (uw_lastpipe_cleanup, (void *) (intptr_t) old_frozen); #if defined (JOB_CONTROL) @@ -2831,6 +2831,10 @@ execute_connection (COMMAND *command, int asynchronous, int pipe_in, int pipe_ou #endif QUIT; +#if defined (JOB_CONTROL) + if (command->value.Connection->connector == ';' && job_control && interactive) + notify_and_cleanup (); +#endif optimize_connection_fork (command); /* XXX */ exec_result = execute_command_internal (command->value.Connection->second, asynchronous, pipe_in, pipe_out, diff --git a/jobs.c b/jobs.c index dbc07a02..c0c9cb2b 100644 --- a/jobs.c +++ b/jobs.c @@ -337,10 +337,11 @@ static SigHandler *old_cont = (SigHandler *)SIG_DFL; /* A place to temporarily save the current pipeline. */ static struct pipeline_saver *saved_pipeline; -/* Set this to non-zero whenever you don't want the jobs list to change at +/* Set this to 1 whenever you don't want the jobs list to change at all: no jobs deleted and no status change notifications. This is used, for example, when executing SIGCHLD traps, which may run arbitrary - commands. */ + commands. Set to -1 if you allow status change notifications but no + jobs deleted. 0 means everything is allowed. */ static int jobs_list_frozen; static char retcode_name_buffer[64]; @@ -1905,19 +1906,26 @@ printable_job_status (int j, PROCESS *p, int format) /* This is the way to print out information on a job if you know the index. FORMAT is: - JLIST_NORMAL) [1]+ Running emacs + JLIST_STANDARD) [1]+ Running emacs JLIST_LONG ) [1]+ 2378 Running emacs -1 ) [1]+ 2378 emacs - JLIST_NORMAL) [1]+ Stopped ls | more + JLIST_STANDARD) [1]+ Stopped ls | more JLIST_LONG ) [1]+ 2369 Stopped ls 2367 | more JLIST_PID_ONLY) Just list the pid of the process group leader (really the process group). JLIST_CHANGED_ONLY) - Use format JLIST_NORMAL, but list only jobs about which - the user has not been notified. */ + Use format JLIST_STANDARD, but list only jobs about which + the user has not been notified. + JLIST_POSIX) + Use format JLIST_STANDARD, list all background jobs, running + and stopped, plus jobs about which the user has not been + notified (that would be notified) + JLIST_BGONLY) + Use format JLIST_STANDARD, but restrict output to background + jobs only. */ /* Print status for pipeline P. If JOB_INDEX is >= 0, it is the index into the JOBS array corresponding to this pipeline. FORMAT is as described @@ -1945,9 +1953,14 @@ print_pipeline (PROCESS *p, int job_index, int format, FILE *stream) fprintf (stream, format ? " " : " |"); if (format != JLIST_STANDARD) - fprintf (stream, "%5ld", (long)p->pid); + { + fprintf (stream, "%5ld", (long)p->pid); + if (p == first) + fprintf (stream, " "); + } - fprintf (stream, " "); + if (p != first) + fprintf (stream, " "); if (format > -1 && job_index >= 0) { @@ -2021,6 +2034,29 @@ print_pipeline (PROCESS *p, int job_index, int format, FILE *stream) fflush (stream); } +/* We want to print information about a job if it's running or terminated in + the background, if it's stopped, or if it was a foreground job terminated + due to a signal that we don't ignore (SIGINT and possibly SIGTERM and + SIGPIPE). If we change this, change the conditions in notify_of_job_status() + so they stay consistent. + + This is for use by the jobs builtin. */ + +static int +should_notify (int job) +{ + /* Background jobs, stopped jobs whether they were in the foreground or + background. */ + if ((IS_FOREGROUND (job) == 0) || STOPPED (job)) + return 1; + + /* Foreground job killed by a signal we report on. */ + if (DEADJOB (job) && IS_FOREGROUND (job) && job_killed_by_signal (job)) + return 1; + + return 0; /* catch-all */ +} + /* Print information to STREAM about jobs[JOB_INDEX] according to FORMAT. Must be called with SIGCHLD blocked or queued with queue_sigchld */ static void @@ -2028,6 +2064,13 @@ pretty_print_job (int job_index, int format, FILE *stream) { register PROCESS *p; + /* If the jobs list is frozen, skip jobs we would remove and not report on */ + if (jobs_list_frozen && should_notify (job_index) == 0) + return; + + if (format == JLIST_BGONLY && IS_FOREGROUND (job_index)) + return; + /* Format only pid information about the process group leader? */ if (format == JLIST_PID_ONLY) { @@ -2041,6 +2084,12 @@ pretty_print_job (int job_index, int format, FILE *stream) return; format = JLIST_STANDARD; } + else if (format == JLIST_POSIX) + { + if (IS_FOREGROUND (job_index) && IS_NOTIFIED (job_index)) + return; + format = JLIST_STANDARD; + } if (format != JLIST_NONINTERACTIVE) fprintf (stream, "[%d]%c ", job_index + 1, @@ -2050,8 +2099,10 @@ pretty_print_job (int job_index, int format, FILE *stream) if (format == JLIST_NONINTERACTIVE) format = JLIST_LONG; - p = jobs[job_index]->pipe; + if (format == JLIST_STANDARD) + fprintf (stream, "%c", ' '); /* used to be in print_pipeline */ + p = jobs[job_index]->pipe; print_pipeline (p, job_index, format, stream); /* We have printed information about this job. When the job's @@ -3275,7 +3326,7 @@ wait_for_any_job (int flags, struct procstat *ps) sigset_t set, oset; /* Allow funsubs to run this, but don't remove jobs from the jobs table. */ - if (jobs_list_frozen && executing_funsub == 0) + if (jobs_list_frozen > 0) return -1; /* First see if there are any unnotified dead jobs that we can report on */ @@ -3297,13 +3348,11 @@ return_job: if (jobs_list_frozen == 0) /* must be running a funsub to get here */ { notify_of_job_status (); /* XXX */ -#if 1 /* kre@munnari.oz.au 01/30/2024 */ + + /* kre@munnari.oz.au 01/30/2024 */ delete_job (i, posixly_correct ? DEL_NOBGPID : 0); -#else - delete_job (i, 0); -#endif } - else + else /* if (jobs_list_frozen < 0) */ /* status changes only */ jobs[i]->flags |= J_NOTIFIED; /* clean up later */ #if defined (COPROCESS_SUPPORT) coproc_reap (); @@ -3371,12 +3420,15 @@ return_job: void notify_and_cleanup (void) { - if (jobs_list_frozen) + if (jobs_list_frozen > 0) return; - if (interactive || interactive_shell == 0 || sourcelevel) + if (interactive || interactive_shell == 0 || sourcelevel || (interactive_shell && running_trap)) notify_of_job_status (); + if (jobs_list_frozen < 0) + return; /* status changes only */ + cleanup_dead_jobs (); } @@ -4357,14 +4409,19 @@ notify_of_job_status (void) if (termsig && WIFSIGNALED (s) && termsig != SIGINT && termsig != SIGPIPE) #endif { +#if 0 fprintf (stderr, "%s", j_strsignal (termsig)); if (WIFCORED (s)) fprintf (stderr, _(" (core dumped)")); fprintf (stderr, "\n"); +#else + print_pipeline (jobs[job]->pipe, job, JLIST_STANDARD, stderr); +#endif } - /* foreground jobs that exit cleanly */ + /* foreground jobs that exit cleanly or due to a signal we + don't report on */ jobs[job]->flags |= J_NOTIFIED; } else if (job_control) @@ -4970,12 +5027,12 @@ itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", j /* Here to allow other parts of the shell (like the trap stuff) to freeze and unfreeze the jobs list. */ int -freeze_jobs_list (void) +freeze_jobs_list (int n) { int o; o = jobs_list_frozen; - jobs_list_frozen = 1; + jobs_list_frozen = n; return o; } diff --git a/jobs.h b/jobs.h index 2ec52f59..31c4fd32 100644 --- a/jobs.h +++ b/jobs.h @@ -34,9 +34,14 @@ #define JLIST_PID_ONLY 2 #define JLIST_CHANGED_ONLY 3 #define JLIST_NONINTERACTIVE 4 +#define JLIST_POSIX 5 +#define JLIST_BGONLY 6 -/* I looked it up. For pretty_print_job (). The real answer is 24. */ -#define LONGEST_SIGNAL_DESC 24 +/* I looked it up. For pretty_print_job (). The real answer is 27 + (macOS SIGPROF) but the makefile or configure can override it. */ +#ifndef LONGEST_SIGNAL_DESC +# define LONGEST_SIGNAL_DESC 27 +#endif /* Defines for the wait_for_* functions and for the wait builtin to use */ #define JWAIT_PERROR (1 << 0) @@ -301,7 +306,7 @@ extern int give_terminal_to (pid_t, int); extern void run_sigchld_trap (int); -extern int freeze_jobs_list (void); +extern int freeze_jobs_list (int); extern int unfreeze_jobs_list (void); extern void set_jobs_list_frozen (int); extern int jobs_list_frozen_status (void); diff --git a/nojobs.c b/nojobs.c index ad10b062..c429b6e3 100644 --- a/nojobs.c +++ b/nojobs.c @@ -1010,7 +1010,7 @@ describe_pid (pid_t pid) } int -freeze_jobs_list (void) +freeze_jobs_list (int n) { return 0; } diff --git a/parse.y b/parse.y index 575dc642..b347fbb5 100644 --- a/parse.y +++ b/parse.y @@ -7242,9 +7242,11 @@ flush_parser_state (sh_parser_state_t *ps) FREE (ps->token_state); ps->token_state = 0; +#if defined (ARRAY_VARS) if (ps->pipestatus) array_dispose (ps->pipestatus); ps->pipestatus = 0; +#endif /* We want to free the saved token and leave the current token for error reporting and cleanup. */ diff --git a/shell.c b/shell.c index 3592e48b..84ac5c4e 100644 --- a/shell.c +++ b/shell.c @@ -448,7 +448,7 @@ main (int argc, char **argv, char **env) /* Fix for the `infinite process creation' bug when running shell scripts from startup files on System V. */ - login_shell = make_login_shell = 0; + login_shell = make_login_shell = su_shell = 0; /* If this shell has already been run, then reinitialize it to a vanilla state. */ @@ -1996,7 +1996,7 @@ shell_reinitialize (void) no_rc = no_profile = 1; /* Things that get 0. */ - login_shell = make_login_shell = executing = 0; + login_shell = make_login_shell = su_shell = executing = 0; debugging = debugging_mode = 0; do_version = line_number = last_command_exit_value = 0; forced_interactive = interactive_shell = interactive = 0; diff --git a/subst.c b/subst.c index e84deb9c..175cd5c3 100644 --- a/subst.c +++ b/subst.c @@ -7088,8 +7088,10 @@ function_substitute (char *string, int quoted, int flags) add_unwind_protect (uw_unbind_localvar, "REPLY"); } - old_frozen = freeze_jobs_list (); +#if 1 /* TAG:bash-5.3 myoga.murase@gmail.com 04/30/2024 */ + old_frozen = freeze_jobs_list (-1); add_unwind_protect (uw_lastpipe_cleanup, (void *) (intptr_t) old_frozen); +#endif #if defined (JOB_CONTROL) unwind_protect_var (pipeline_pgrp); @@ -7806,7 +7808,9 @@ expand_arrayref: temp = quote_var_value (temp, quoted, pflags); +#if defined (ARRAY_VARS) FREE (tt); +#endif } else temp = (char *)NULL; diff --git a/support/siglen.c b/support/siglen.c new file mode 100644 index 00000000..56a5761c --- /dev/null +++ b/support/siglen.c @@ -0,0 +1,28 @@ +#include +#include +#include + +#include + +int +main(int argc, char **argv) +{ + int i, l, longest, verb; + size_t len; + char *msg; + + verb = (argc > 1 && argv[1] && strcmp (argv[1], "-l") == 0); + + longest = 0; + for (i = 0; i < NSIG; i++) { + msg = strsignal (i); + len = msg ? strlen (msg) : 0; + l = len; + if (msg && verb) + printf ("%d\t%s\n", l, msg); + if (l > longest) + longest = l; + } + printf ("%d\n", longest); + exit (0); +} diff --git a/tests/jobs.right b/tests/jobs.right index 173743ae..f8037bc8 100644 --- a/tests/jobs.right +++ b/tests/jobs.right @@ -16,17 +16,17 @@ Waiting for job 6 job 6 returns 0 Waiting for job 7 job 7 returns 0 -[1] Running sleep 2 & -[2] Running sleep 2 & -[3] Running sleep 2 & -[4]- Running sleep 2 & -[5]+ Running ( sleep 2; exit 4 ) & +[1] Running sleep 2 & +[2] Running sleep 2 & +[3] Running sleep 2 & +[4]- Running sleep 2 & +[5]+ Running ( sleep 2; exit 4 ) & 4 0 i killed it 12 -[1]- Running sleep 20 & -[3]+ Running sleep 20 & +[1]- Running sleep 20 & +[3]+ Running sleep 20 & 5: ok 1 ./jobs5.sub: line 40: wait: %8: no such job 2: ok 2 @@ -53,7 +53,7 @@ async list wait for child forked wait-when-no-children posix jobs output -[1]+ Done sleep 1 +[1]+ Done sleep 1 wait-for-job ./jobs.tests: line 96: wait: %2: no such job 127 @@ -84,36 +84,36 @@ wait-for-non-child ./jobs.tests: line 150: wait: pid 1 is not a child of this shell 127 3 -- 1 2 3 -- 1 - 2 - 3 -[1] Running sleep 300 & -[2]- Running sleep 350 & -[3]+ Running sleep 400 & +[1] Running sleep 300 & +[2]- Running sleep 350 & +[3]+ Running sleep 400 & running jobs: -[1] Running sleep 300 & -[2]- Running sleep 350 & -[3]+ Running sleep 400 & +[1] Running sleep 300 & +[2]- Running sleep 350 & +[3]+ Running sleep 400 & ./jobs.tests: line 167: kill: %4: no such job ./jobs.tests: line 169: jobs: %4: no such job current job: -[3]+ Running sleep 400 & +[3]+ Running sleep 400 & previous job: -[2]- Running sleep 350 & +[2]- Running sleep 350 & after kill -STOP running jobs: -[1] Running sleep 300 & -[3]- Running sleep 400 & +[1] Running sleep 300 & +[3]- Running sleep 400 & stopped jobs: -[2]+ Stopped sleep 350 +[2]+ Stopped sleep 350 after disown -[2]+ Stopped sleep 350 -[3]- Running sleep 400 & +[2]+ Stopped sleep 350 +[3]- Running sleep 400 & running jobs: -[3]- Running sleep 400 & +[3]- Running sleep 400 & stopped jobs: -[2]+ Stopped sleep 350 +[2]+ Stopped sleep 350 after kill -s CONT running jobs: -[2]+ Running sleep 350 & -[3]- Running sleep 400 & +[2]+ Running sleep 350 & +[3]- Running sleep 400 & stopped jobs: after kill -STOP, backgrounding %3: [3]+ sleep 400 & -- 2.47.2