📄 jobs.c
字号:
# if defined (DRAIN_OUTPUT) draino (tty, shell_tty_info.sg_ospeed);# endif /* DRAIN_OUTPUT */ ioctl (tty, TIOCSETN, &shell_tty_info); ioctl (tty, TIOCSETC, &shell_tchars); ioctl (tty, TIOCSLTC, &shell_ltchars);#endif /* NEW_TTY_DRIVER */#if defined (TERMIO_TTY_DRIVER) ioctl (tty, TCSETAW, &shell_tty_info);#endif /* TERMIO_TTY_DRIVER */#if defined (TERMIOS_TTY_DRIVER) if (tcsetattr (tty, TCSADRAIN, &shell_tty_info) < 0) { /* Only print an error message if we're really interactive at this time. */ if (interactive) sys_error ("[%ld: %d (%d)] tcsetattr", (long)getpid (), shell_level, tty); return -1; }#endif /* TERMIOS_TTY_DRIVER */ } return 0;}/* Given an index into the jobs array JOB, return the PROCESS struct of the last process in that job's pipeline. This is the one whose exit status counts. Must be called with SIGCHLD blocked or queued. */static PROCESS *find_last_proc (job, block) int job; int block;{ register PROCESS *p; sigset_t set, oset; if (block) BLOCK_CHILD (set, oset); p = jobs[job]->pipe; while (p && p->next != jobs[job]->pipe) p = p->next; if (block) UNBLOCK_CHILD (oset); return (p);}static pid_tfind_last_pid (job, block) int job; int block;{ PROCESS *p; p = find_last_proc (job, block); /* Possible race condition here. */ return p->pid;} /* Wait for a particular child of the shell to finish executing. This low-level function prints an error message if PID is not a child of this shell. It returns -1 if it fails, or whatever wait_for returns otherwise. If the child is not found in the jobs table, it returns 127. */intwait_for_single_pid (pid) pid_t pid;{ register PROCESS *child; sigset_t set, oset; int r, job; BLOCK_CHILD (set, oset); child = find_pipeline (pid, 0, (int *)NULL); UNBLOCK_CHILD (oset); if (child == 0) { r = bgp_search (pid); if (r >= 0) return r; } if (child == 0) { internal_error (_("wait: pid %ld is not a child of this shell"), (long)pid); return (127); } r = wait_for (pid); /* POSIX.2: if we just waited for a job, we can remove it from the jobs table. */ BLOCK_CHILD (set, oset); job = find_job (pid, 0, NULL); if (job != NO_JOB && jobs[job] && DEADJOB (job)) jobs[job]->flags |= J_NOTIFIED; UNBLOCK_CHILD (oset); /* If running in posix mode, remove the job from the jobs table immediately */ if (posixly_correct) { cleanup_dead_jobs (); bgp_delete (pid); } return r;}/* Wait for all of the background processes started by this shell to finish. */voidwait_for_background_pids (){ register int i, r, waited_for; sigset_t set, oset; pid_t pid; for (waited_for = 0;;) { BLOCK_CHILD (set, oset); /* find first running job; if none running in foreground, break */ /* 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; } if (i == js.j_jobslots) { UNBLOCK_CHILD (oset); break; } /* now wait for the last pid in that job. */ pid = find_last_pid (i, 0); UNBLOCK_CHILD (oset); QUIT; errno = 0; /* XXX */ r = wait_for_single_pid (pid); if (r == -1) { /* If we're mistaken about job state, compensate. */ if (errno == ECHILD) mark_all_jobs_as_dead (); } else waited_for++; } /* POSIX.2 says the shell can discard the statuses of all completed jobs if `wait' is called with no arguments. */ mark_dead_jobs_as_notified (1); cleanup_dead_jobs (); bgp_clear ();}/* Make OLD_SIGINT_HANDLER the SIGINT signal handler. */#define INVALID_SIGNAL_HANDLER (SigHandler *)wait_for_background_pidsstatic SigHandler *old_sigint_handler = INVALID_SIGNAL_HANDLER;static voidrestore_sigint_handler (){ if (old_sigint_handler != INVALID_SIGNAL_HANDLER) { set_signal_handler (SIGINT, old_sigint_handler); old_sigint_handler = INVALID_SIGNAL_HANDLER; }}static int wait_sigint_received;/* Handle SIGINT while we are waiting for children in a script to exit. The `wait' builtin should be interruptible, but all others should be effectively ignored (i.e. not cause the shell to exit). */static sighandlerwait_sigint_handler (sig) int sig;{ SigHandler *sigint_handler; if (interrupt_immediately || (this_shell_builtin && this_shell_builtin == wait_builtin)) { last_command_exit_value = EXECUTION_FAILURE; restore_sigint_handler (); /* If we got a SIGINT while in `wait', and SIGINT is trapped, do what POSIX.2 says (see builtins/wait.def for more info). */ if (this_shell_builtin && this_shell_builtin == wait_builtin && signal_is_trapped (SIGINT) && ((sigint_handler = trap_to_sighandler (SIGINT)) == trap_handler)) { interrupt_immediately = 0; trap_handler (SIGINT); /* set pending_traps[SIGINT] */ wait_signal_received = SIGINT; longjmp (wait_intr_buf, 1); } ADDINTERRUPT; QUIT; } /* XXX - should this be interrupt_state? If it is, the shell will act as if it got the SIGINT interrupt. */ wait_sigint_received = 1; /* Otherwise effectively ignore the SIGINT and allow the running job to be killed. */ SIGRETURN (0);}static intprocess_exit_signal (status) WAIT status;{ return (WIFSIGNALED (status) ? WTERMSIG (status) : 0);}static intprocess_exit_status (status) WAIT status;{ if (WIFSIGNALED (status)) return (128 + WTERMSIG (status)); else if (WIFSTOPPED (status) == 0) return (WEXITSTATUS (status)); else return (EXECUTION_SUCCESS);}static WAITjob_signal_status (job) int job;{ register PROCESS *p; WAIT s; p = jobs[job]->pipe; do { s = p->status; if (WIFSIGNALED(s) || WIFSTOPPED(s)) break; p = p->next; } while (p != jobs[job]->pipe); return s;} /* Return the exit status of the last process in the pipeline for job JOB. This is the exit status of the entire job. */static WAITraw_job_exit_status (job) int job;{ register PROCESS *p; int fail; WAIT ret; if (pipefail_opt) { fail = 0; p = jobs[job]->pipe; do { if (WSTATUS (p->status) != EXECUTION_SUCCESS) fail = WSTATUS(p->status); p = p->next; } while (p != jobs[job]->pipe); WSTATUS (ret) = fail; return ret; } for (p = jobs[job]->pipe; p->next != jobs[job]->pipe; p = p->next) ; return (p->status);}/* Return the exit status of job JOB. This is the exit status of the last (rightmost) process in the job's pipeline, modified if the job was killed by a signal or stopped. */intjob_exit_status (job) int job;{ return (process_exit_status (raw_job_exit_status (job)));}intjob_exit_signal (job) int job;{ return (process_exit_signal (raw_job_exit_status (job)));}#define FIND_CHILD(pid, child) \ do \ { \ child = find_pipeline (pid, 0, (int *)NULL); \ if (child == 0) \ { \ give_terminal_to (shell_pgrp, 0); \ UNBLOCK_CHILD (oset); \ internal_error (_("wait_for: No record of process %ld"), (long)pid); \ restore_sigint_handler (); \ return (termination_state = 127); \ } \ } \ while (0)/* Wait for pid (one of our children) to terminate, then return the termination state. Returns 127 if PID is not found in the jobs table. Returns -1 if waitchld() returns -1, indicating that there are no unwaited-for child processes. */intwait_for (pid) pid_t pid;{ int job, termination_state, r; WAIT s; register PROCESS *child; sigset_t set, oset; /* In the case that this code is interrupted, and we longjmp () out of it, we are relying on the code in throw_to_top_level () to restore the top-level signal mask. */ BLOCK_CHILD (set, oset); /* Ignore interrupts while waiting for a job run without job control to finish. We don't want the shell to exit if an interrupt is received, only if one of the jobs run is killed via SIGINT. If job control is not set, the job will be run in the same pgrp as the shell, and the shell will see any signals the job gets. In fact, we want this set every time the waiting shell and the waited- for process are in the same process group, including command substitution. */ /* This is possibly a race condition -- should it go in stop_pipeline? */ wait_sigint_received = 0; if (job_control == 0 || (subshell_environment&SUBSHELL_COMSUB)) { old_sigint_handler = set_signal_handler (SIGINT, wait_sigint_handler); if (old_sigint_handler == SIG_IGN) set_signal_handler (SIGINT, old_sigint_handler); } termination_state = last_command_exit_value; if (interactive && job_control == 0) QUIT; /* Check for terminating signals and exit the shell if we receive one */ CHECK_TERMSIG; /* If we say wait_for (), then we have a record of this child somewhere. If it and none of its peers are running, don't call waitchld(). */ job = NO_JOB; do { FIND_CHILD (pid, child); /* If this child is part of a job, then we are really waiting for the job to finish. Otherwise, we are waiting for the child to finish. We check for JDEAD in case the job state has been set by waitchld after receipt of a SIGCHLD. */ if (job == NO_JOB) job = find_job (pid, 0, NULL); /* waitchld() takes care of setting the state of the job. If the job has already exited before this is called, sigchld_handler will have called waitchld and the state will be set to JDEAD. */ if (PRUNNING(child) || (job != NO_JOB && RUNNING (job))) {#if defined (WAITPID_BROKEN) /* SCOv4 */ sigset_t suspend_set; sigemptyset (&suspend_set); sigsuspend (&suspend_set);#else /* !WAITPID_BROKEN */# if defined (MUST_UNBLOCK_CHLD) struct sigaction act, oact; sigset_t nullset, chldset; sigemptyset (&nullset); sigemptyset (&chldset); sigprocmask (SIG_SETMASK, &nullset, &chldset); act.sa_handler = SIG_DFL; sigemptyset (&act.sa_mask); sigemptyset (&oact.sa_mask); act.sa_flags = 0; sigaction (SIGCHLD, &act, &oact);# endif queue_sigchld = 1; r = waitchld (pid, 1);# if defined (MUST_UNBLOCK_CHLD) sigaction (SIGCHLD, &oact, (struct sigaction *)NULL); sigprocmask (SIG_SETMASK, &chldset, (sigset_t *)NULL);# endif queue_sigchld = 0; if (r == -1 && errno == ECHILD && this_shell_builtin == wait_builtin) { termination_state = -1; goto wait_for_return; } /* If child is marked as running, but waitpid() returns -1/ECHILD, there is something wrong. Somewhere, wait should have returned that child's pid. Mark the child as not running and the job, if it exists, as JDEAD. */ if (r == -1 && errno == ECHILD) { child->running = PS_DONE; WSTATUS (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; js.c_reaped++; js.j_ndead++; } }#endif /* WAITPID_BROKEN */ } /* If the shell is interactive, and job control is disabled, see if the foreground process has died due to SIGINT and jump out of the wait loop if it has. waitchld has already restored the old SIGINT signal handler. */ if (interactive && job_control == 0) QUIT; /* Check for terminating signals and exit the shell if we receive one */ CHECK_TERMSIG; } while (PRUNNING (child) || (job != NO_JOB && RUNNING (job))); /* The exit state of the command is either the termination state of the child, or the termination state of the job. If a job, the status of the last child in the pipeline is the significant one. If the command or job was terminated by a signal, note that value also. */ termination_state = (job != NO_JOB) ? job_exit_status (job) : process_exit_status (child->status); last_command_exit_signal = (job != NO_JOB) ? job_exit_signal (job) : process_exit_signal (child->status); /* XXX */ if ((job != NO_JOB && JOBSTATE (job) == JSTOPPED) || WIFSTOPPED (child->status)) termination_state = 128 + WSTOPSIG (child->status); if (job == NO_JOB || IS_JOBCONTROL (job)) { /* XXX - under what circumstances is a job not present in the jobs table (job == NO_JOB)? 1. command substitution In the case of command substitution, at least, it's probably not the right thing to give the terminal to the shell's process group, even though there is code in subst.c:command_substitute to work around it. Things that don't: $PROMPT_COMMAND execution process substitution */#if 0if (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); } /* If the command did not exit cleanly, or the job is just being stopped, then reset the tty state back to what it was before this command. Reset the tty state and notify the user of the job termination only if the shell is interactive. Clean up any dead jobs in either case. */ if (job != NO_JOB) { if (interactive_shell && subshell_environment == 0) { /* This used to use `child->status'. That's wrong, however, for pipelines. `child' is the first process in the pipeline. It's likely that the process we want to check for abnormal termination or stopping is the last process in the pipeline, especially if it's long-lived and the first process is short-lived. Since we know we have a job here, we can check all the processes in this job's pipeline and see if one of them stopped or terminated due to a signal. We might want to change this later to just check the last process in the pipeline. If no process exits due to a signal, S is left as the status of the last job in the pipeline. */ s = job_signal_status (job); if (WIFSIGNALED (s) || WIFSTOPPED (s)) { set_tty_state (); /* If the current job was stopped or killed by a signal, and the user has requested it, get a possibly new window size */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -