diff options
Diffstat (limited to 'jobs.c')
-rw-r--r-- | jobs.c | 130 |
1 files changed, 102 insertions, 28 deletions
@@ -3,7 +3,7 @@ /* This file works with both POSIX and BSD systems. It implements job control. */ -/* Copyright (C) 1989-2005 Free Software Foundation, Inc. +/* Copyright (C) 1989-2006 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -77,7 +77,15 @@ extern int errno; #endif /* !errno */ #define DEFAULT_CHILD_MAX 32 -#define MAX_JOBS_IN_ARRAY 4096 /* testing */ +#if !defined (DEBUG) +#define MAX_JOBS_IN_ARRAY 4096 /* production */ +#else +#define MAX_JOBS_IN_ARRAY 128 /* testing */ +#endif + +/* Flag values for second argument to delete_job */ +#define DEL_WARNSTOPPED 1 /* warn about deleting stopped jobs */ +#define DEL_NOBGPID 2 /* don't add pgrp leader to bgpids */ /* Take care of system dependencies that must be handled when waiting for children. The arguments to the WAITPID macro match those to the Posix.1 @@ -134,10 +142,10 @@ typedef int sh_job_map_func_t __P((JOB *, int, int, int)); /* Variables used here but defined in other files. */ extern int subshell_environment, line_number; extern int posixly_correct, shell_level; -extern int interrupt_immediately; extern int last_command_exit_value, last_command_exit_signal; extern int loop_level, breaking; extern int sourcelevel; +extern int running_trap; extern sh_builtin_func_t *this_shell_builtin; extern char *shell_name, *this_command_name; extern sigset_t top_level_mask; @@ -307,6 +315,10 @@ static int jobs_list_frozen; static char retcode_name_buffer[64]; +/* flags to detect pid wraparound */ +static pid_t first_pid = NO_PID; +static int pid_wrap = -1; + #if !defined (_POSIX_VERSION) /* These are definitions to map POSIX 1003.1 functions onto existing BSD @@ -328,11 +340,13 @@ tcgetpgrp (fd) #endif /* !_POSIX_VERSION */ -/* Initialize the global job stats structure. */ +/* Initialize the global job stats structure and other bookkeeping variables */ void init_job_stats () { js = zerojs; + first_pid = NO_PID; + pid_wrap = -1; } /* Return the working directory for the current process. Unlike @@ -746,7 +760,7 @@ bgp_search (pid) static void bgp_prune () { - struct pidstat *ps, *p; + struct pidstat *ps; while (bgpids.npid > js.c_childmax) { @@ -808,12 +822,14 @@ cleanup_dead_jobs () QUEUE_SIGCHLD(os); - /* XXX could use js.j_firstj here */ + /* XXX could use js.j_firstj and js.j_lastj here */ for (i = 0; i < js.j_jobslots; i++) { #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("cleanup_dead_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("cleanup_dead_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i] && DEADJOB (i) && IS_NOTIFIED (i)) @@ -840,6 +856,30 @@ processes_in_job (job) return nproc; } +static void +delete_old_job (pid) + pid_t pid; +{ + PROCESS *p; + int job; + + job = find_job (pid, 0, &p); + if (job != NO_JOB) + { +#ifdef DEBUG + itrace ("delete_old_job: found pid %d in job %d with state %d", pid, job, jobs[job]->state); +#endif + if (JOBSTATE (job) == JDEAD) + delete_job (job, DEL_NOBGPID); + else + { + internal_warning (_("forked pid %d appears in running job %d"), pid, job); + if (p) + p->pid = 0; + } + } +} + /* Reallocate and compress the jobs list. This returns with a jobs array whose size is a multiple of JOB_SLOTS and can hold the current number of jobs. Heuristics are used to minimize the number of new reallocs. */ @@ -911,7 +951,7 @@ realloc_jobs_list () the jobs array to some predefined maximum. Called when the shell is not the foreground process (subshell_environment != 0). Returns the first available slot in the compacted list. If that value is js.j_jobslots, then - the list needs to be reallocated. The jobs array is in new memory if + the list needs to be reallocated. The jobs array may be in new memory if this returns > 0 and < js.j_jobslots. FLAGS is reserved for future use. */ static int compact_jobs_list (flags) @@ -929,29 +969,33 @@ compact_jobs_list (flags) /* Delete the job at INDEX from the job list. Must be called with SIGCHLD blocked. */ void -delete_job (job_index, warn_stopped) - int job_index, warn_stopped; +delete_job (job_index, dflags) + int job_index, dflags; { register JOB *temp; PROCESS *proc; - int ndel, status; - pid_t pid; + int ndel; if (js.j_jobslots == 0 || jobs_list_frozen) return; - if (warn_stopped && subshell_environment == 0 && STOPPED (job_index)) + if ((dflags & DEL_WARNSTOPPED) && subshell_environment == 0 && STOPPED (job_index)) internal_warning (_("deleting stopped job %d with process group %ld"), job_index+1, (long)jobs[job_index]->pgrp); temp = jobs[job_index]; + if (temp == 0) + return; if (job_index == js.j_current || job_index == js.j_previous) reset_current (); - proc = find_last_proc (job_index, 0); - /* Could do this just for J_ASYNC jobs, but we save all. */ - bgp_add (proc->pid, process_exit_status (proc->status)); + if ((dflags & DEL_NOBGPID) == 0) + { + proc = find_last_proc (job_index, 0); + /* Could do this just for J_ASYNC jobs, but we save all. */ + if (proc) + bgp_add (proc->pid, process_exit_status (proc->status)); + } jobs[job_index] = (JOB *)NULL; - if (temp == js.j_lastmade) js.j_lastmade = 0; else if (temp == js.j_lastasync) @@ -1129,6 +1173,8 @@ map_over_jobs (func, arg1, arg2) #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("map_over_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("map_over_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i]) { @@ -1183,8 +1229,9 @@ hangup_all_jobs () { if (jobs[i]) { - if ((jobs[i]->flags & J_NOHUP) == 0) - killpg (jobs[i]->pgrp, SIGHUP); + if (jobs[i]->flags & J_NOHUP) + continue; + killpg (jobs[i]->pgrp, SIGHUP); if (STOPPED (i)) killpg (jobs[i]->pgrp, SIGCONT); } @@ -1261,12 +1308,14 @@ find_job (pid, alive_only, procp) register int i; PROCESS *p; - /* XXX could use js.j_firstj here */ + /* XXX could use js.j_firstj here, and should check js.j_lastj */ for (i = 0; i < js.j_jobslots; i++) { #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("find_job: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("find_job: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i]) { @@ -1735,6 +1784,13 @@ make_child (command, async_p) /* In the parent. Remember the pid of the child just created as the proper pgrp if this is the first child. */ + if (first_pid == NO_PID) + first_pid = pid; + else if (pid_wrap == -1 && pid < first_pid) + pid_wrap = 0; + else if (pid_wrap == 0 && pid >= first_pid) + pid_wrap = 1; + if (job_control) { if (pipeline_pgrp == 0) @@ -1768,6 +1824,9 @@ make_child (command, async_p) last_asynchronous_pid = 1; #endif + if (pid_wrap > 0) + delete_old_job (pid); + #if !defined (RECYCLES_PIDS) /* Only check for saved status if we've saved more than CHILD_MAX statuses, unless the system recycles pids. */ @@ -1952,7 +2011,7 @@ find_last_proc (job, block) BLOCK_CHILD (set, oset); p = jobs[job]->pipe; - while (p->next != jobs[job]->pipe) + while (p && p->next != jobs[job]->pipe) p = p->next; if (block) @@ -2036,12 +2095,14 @@ wait_for_background_pids () BLOCK_CHILD (set, oset); /* find first running job; if none running in foreground, break */ - /* XXX could use js.j_firstj here */ + /* XXX could use js.j_firstj and js.j_lastj here */ for (i = 0; i < js.j_jobslots; i++) { #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("wait_for_background_pids: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("wait_for_background_pids: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i] && RUNNING (i) && IS_FOREGROUND (i) == 0) break; @@ -2307,6 +2368,7 @@ wait_for (pid) { child->running = PS_DONE; child->status = 0; /* XXX -- can't find true status */ + js.c_living = 0; /* no living child processes */ if (job != NO_JOB) { jobs[job]->state = JDEAD; @@ -2358,7 +2420,6 @@ wait_for (pid) if (job == NO_JOB) itrace("wait_for: job == NO_JOB, giving the terminal to shell_pgrp (%ld)", (long)shell_pgrp); #endif - give_terminal_to (shell_pgrp, 0); } @@ -2764,14 +2825,14 @@ start_job (job, foreground) if (foreground) { pid_t pid; - int s; + int st; pid = find_last_pid (job, 0); UNBLOCK_CHILD (oset); - s = wait_for (pid); + st = wait_for (pid); shell_tty_info = save_stty; set_tty_state (); - return (s); + return (st); } else { @@ -2907,6 +2968,7 @@ waitchld (wpid, block) : 0; if (sigchld || block == 0) waitpid_flags |= WNOHANG; + CHECK_TERMSIG; pid = WAITPID (-1, &status, waitpid_flags); /* WCONTINUED may be rejected by waitpid as invalid even when defined */ @@ -2933,13 +2995,17 @@ waitchld (wpid, block) /* If waitpid returns 0, there are running children. If it returns -1, the only other error POSIX says it can return is EINTR. */ + CHECK_TERMSIG; if (pid <= 0) continue; /* jumps right to the test */ /* children_exited is used to run traps on SIGCHLD. We don't want to run the trap if a process is just being continued. */ if (WIFCONTINUED(status) == 0) - children_exited++; + { + children_exited++; + js.c_living--; + } /* Locate our PROCESS for this pid. */ child = find_process (pid, 1, &job); /* want living procs only */ @@ -3164,7 +3230,7 @@ set_job_status_and_cleanup (job) temp_handler = trap_to_sighandler (SIGINT); restore_sigint_handler (); if (temp_handler == SIG_DFL) - termination_unwind_protect (SIGINT); + termsig_handler (SIGINT); else if (temp_handler != SIG_IGN) (*temp_handler) (SIGINT); } @@ -3679,9 +3745,11 @@ delete_all_jobs (running_only) #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("delete_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("delete_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i] && (running_only == 0 || (running_only && RUNNING(i)))) - delete_job (i, 1); + delete_job (i, DEL_WARNSTOPPED); } if (running_only == 0) { @@ -3733,6 +3801,8 @@ count_all_jobs () #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("count_all_jobs: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("count_all_jobs: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i] && DEADJOB(i) == 0) n++; @@ -3806,6 +3876,8 @@ mark_dead_jobs_as_notified (force) #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif if (jobs[i] && DEADJOB (i)) { @@ -3857,6 +3929,8 @@ itrace("mark_dead_jobs_as_notified: child_max = %d ndead = %d ndeadproc = %d", j #if defined (DEBUG) if (i < js.j_firstj && jobs[i]) itrace("mark_dead_jobs_as_notified: job %d non-null before js.j_firstj (%d)", i, js.j_firstj); + if (i > js.j_lastj && jobs[i]) + itrace("mark_dead_jobs_as_notified: job %d non-null after js.j_lastj (%d)", i, js.j_lastj); #endif /* If marking this job as notified would drop us down below child_max, don't mark it so we can keep at least child_max |