/* list jobs for top-level notification */ void j_notify(void) { Job *j, *tmp; sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); for (j = job_list; j; j = j->next) { #ifdef JOBS if (Flag(FMONITOR) && (j->flags & JF_CHANGED)) j_print(j, JP_MEDIUM, shl_out); #endif /* JOBS */ /* Remove job after doing reports so there aren't * multiple +/- jobs. */ if (j->state == PEXITED || j->state == PSIGNALLED) j->flags |= JF_REMOVE; } for (j = job_list; j; j = tmp) { tmp = j->next; if (j->flags & JF_REMOVE) remove_job(j, "notify"); } shf_flush(shl_out); sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); }
/* list jobs for jobs built-in */ int j_jobs(const char *cp, int slp, int nflag) /* 0: short, 1: long, 2: pgrp */ { Job *j, *tmp; int how; int zflag = 0; sigset_t omask; sigprocmask(SIG_BLOCK, &sm_sigchld, &omask); if (nflag < 0) { /* kludge: print zombies */ nflag = 0; zflag = 1; } if (cp) { int ecode; if ((j = j_lookup(cp, &ecode)) == (Job *) 0) { sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); bi_errorf("%s: %s", cp, lookup_msgs[ecode]); return 1; } } else j = job_list; how = slp == 0 ? JP_MEDIUM : (slp == 1 ? JP_LONG : JP_PGRP); for (; j; j = j->next) { if ((!(j->flags & JF_ZOMBIE) || zflag) && (!nflag || (j->flags & JF_CHANGED))) { j_print(j, how, shl_stdout); if (j->state == PEXITED || j->state == PSIGNALLED) j->flags |= JF_REMOVE; } if (cp) break; } /* Remove jobs after printing so there won't be multiple + or - jobs */ for (j = job_list; j; j = tmp) { tmp = j->next; if (j->flags & JF_REMOVE) remove_job(j, "jobs"); } sigprocmask(SIG_SETMASK, &omask, (sigset_t *) 0); return 0; }
/* * wait for job to complete or change state * * If jobs are compiled in then this routine expects sigchld to be blocked. */ static int j_waitj(Job *j, int flags, /* see JW_* */ const char *where) { int rv; /* * No auto-notify on the job we are waiting on. */ j->flags |= JF_WAITING; if (flags & JW_ASYNCNOTIFY) j->flags |= JF_W_ASYNCNOTIFY; if (!Flag(FMONITOR)) flags |= JW_STOPPEDWAIT; while ((volatile int) j->state == PRUNNING || ((flags & JW_STOPPEDWAIT) && (volatile int) j->state == PSTOPPED)) { sigsuspend(&sm_default); if (fatal_trap) { int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); runtraps(TF_FATAL); j->flags |= oldf; /* not reached... */ } if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); return -rv; } } j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); if (j->flags & JF_FG) { int status; j->flags &= ~JF_FG; #ifdef JOBS if (Flag(FMONITOR) && ttypgrp_ok && j->pgrp) { /* * Save the tty's current pgrp so it can be restored * when the job is foregrounded. This is to * deal with things like the GNU su which does * a fork/exec instead of an exec (the fork means * the execed shell gets a different pid from its * pgrp, so naturally it sets its pgrp and gets hosed * when it gets foregrounded by the parent shell, which * has restored the tty's pgrp to that of the su * process). */ if (j->state == PSTOPPED && (j->saved_ttypgrp = tcgetpgrp(tty_fd)) >= 0) j->flags |= JF_SAVEDTTYPGRP; if (tcsetpgrp(tty_fd, our_pgrp) < 0) { warningf(true, "j_waitj: tcsetpgrp(%d, %d) failed: %s", tty_fd, (int) our_pgrp, strerror(errno)); } if (j->state == PSTOPPED) { j->flags |= JF_SAVEDTTY; tcgetattr(tty_fd, &j->ttystate); } } #endif /* JOBS */ if (tty_fd >= 0) { /* Only restore tty settings if job was originally * started in the foreground. Problems can be * caused by things like `more foobar &' which will * typically get and save the shell's vi/emacs tty * settings before setting up the tty for itself; * when more exits, it restores the `original' * settings, and things go down hill from there... */ if (j->state == PEXITED && j->status == 0 && (j->flags & JF_USETTYMODE)) { tcgetattr(tty_fd, &tty_state); } else { tcsetattr(tty_fd, TCSADRAIN, &tty_state); /* Don't use tty mode if job is stopped and * later restarted and exits. Consider * the sequence: * vi foo (stopped) * ... * stty something * ... * fg (vi; ZZ) * mode should be that of the stty, not what * was before the vi started. */ if (j->state == PSTOPPED) j->flags &= ~JF_USETTYMODE; } } #ifdef JOBS /* If it looks like user hit ^C to kill a job, pretend we got * one too to break out of for loops, etc. (at&t ksh does this * even when not monitoring, but this doesn't make sense since * a tty generated ^C goes to the whole process group) */ status = j->last_proc->status; if (Flag(FMONITOR) && j->state == PSIGNALLED && WIFSIGNALED(status) && (sigtraps[WTERMSIG(status)].flags & TF_TTY_INTR)) trapsig(WTERMSIG(status)); #endif /* JOBS */ } j_usrtime = j->usrtime; j_systime = j->systime; rv = j->status; if (!(flags & JW_ASYNCNOTIFY) && (!Flag(FMONITOR) || j->state != PSTOPPED)) { j_print(j, JP_SHORT, shl_out); shf_flush(shl_out); } if (j->state != PSTOPPED && (!Flag(FMONITOR) || !(flags & JW_ASYNCNOTIFY))) remove_job(j, where); return rv; }
/* * Called only when a process in j has exited/stopped (ie, called only * from j_sigchld()). If no processes are running, the job status * and state are updated, asynchronous job notification is done and, * if unneeded, the job is removed. * * If jobs are compiled in then this routine expects sigchld to be blocked. */ static void check_job(Job *j) { int jstate; Proc *p; /* XXX debugging (nasty - interrupt routine using shl_out) */ if (!(j->flags & JF_STARTED)) { internal_errorf(0, "check_job: job started (flags 0x%x)", j->flags); return; } jstate = PRUNNING; for (p=j->proc_list; p != (Proc *) 0; p = p->next) { if (p->state == PRUNNING) return; /* some processes still running */ if (p->state > jstate) jstate = p->state; } j->state = jstate; switch (j->last_proc->state) { case PEXITED: j->status = WEXITSTATUS(j->last_proc->status); break; case PSIGNALLED: j->status = 128 + WTERMSIG(j->last_proc->status); break; default: j->status = 0; break; } /* Note when co-process dies: can't be done in j_wait() nor * remove_job() since neither may be called for non-interactive * shells. */ if (j->state == PEXITED || j->state == PSIGNALLED) { /* No need to keep co-process input any more * (at least, this is what ksh93d thinks) */ if (coproc.job == j) { coproc.job = (void *) 0; /* XXX would be nice to get the closes out of here * so they aren't done in the signal handler. * Would mean a check in coproc_getfd() to * do "if job == 0 && write >= 0, close write". */ coproc_write_close(coproc.write); } /* Do we need to keep the output? */ if (j->coproc_id && j->coproc_id == coproc.id && --coproc.njobs == 0) coproc_readw_close(coproc.read); } j->flags |= JF_CHANGED; #ifdef JOBS if (Flag(FMONITOR) && !(j->flags & JF_XXCOM)) { /* Only put stopped jobs at the front to avoid confusing * the user (don't want finished jobs effecting %+ or %-) */ if (j->state == PSTOPPED) put_job(j, PJ_ON_FRONT); if (Flag(FNOTIFY) && (j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY)) != JF_WAITING) { /* Look for the real file descriptor 2 */ { struct env *ep; int fd = 2; for (ep = e; ep; ep = ep->oenv) if (ep->savefd && ep->savefd[2]) fd = ep->savefd[2]; shf_reopen(fd, SHF_WR, shl_j); } /* Can't call j_notify() as it removes jobs. The job * must stay in the job list as j_waitj() may be * running with this job. */ j_print(j, JP_MEDIUM, shl_j); shf_flush(shl_j); if (!(j->flags & JF_WAITING) && j->state != PSTOPPED) remove_job(j, "notify"); } } #endif /* JOBS */ if (!Flag(FMONITOR) && !(j->flags & (JF_WAITING|JF_FG)) && j->state != PSTOPPED) { if (j == async_job || (j->flags & JF_KNOWN)) { j->flags |= JF_ZOMBIE; j->job = -1; nzombie++; } else remove_job(j, "checkjob"); } }
/* * wait for job to complete or change state * * If jobs are compiled in then this routine expects sigchld to be blocked. */ static int j_waitj(Job *j, int flags, /* see JW_* */ const char *where) { int rv; /* * No auto-notify on the job we are waiting on. */ j->flags |= JF_WAITING; if (flags & JW_ASYNCNOTIFY) j->flags |= JF_W_ASYNCNOTIFY; flags |= JW_STOPPEDWAIT; while ((volatile int) j->state == PRUNNING || ((flags & JW_STOPPEDWAIT) && (volatile int) j->state == PSTOPPED)) { if (fatal_trap) { int oldf = j->flags & (JF_WAITING|JF_W_ASYNCNOTIFY); j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); runtraps(TF_FATAL); j->flags |= oldf; /* not reached... */ } if ((flags & JW_INTERRUPT) && (rv = trap_pending())) { j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); return -rv; } } j->flags &= ~(JF_WAITING|JF_W_ASYNCNOTIFY); if (j->flags & JF_FG) { j->flags &= ~JF_FG; if (tty_fd >= 0) { /* Only restore tty settings if job was originally * started in the foreground. Problems can be * caused by things like `more foobar &' which will * typically get and save the shell's vi/emacs tty * settings before setting up the tty for itself; * when more exits, it restores the `original' * settings, and things go down hill from there... */ if (j->state == PEXITED && j->status == 0 && (j->flags & JF_USETTYMODE)) { tcgetattr(tty_fd, &tty_state); } else { tcsetattr(tty_fd, TCSADRAIN, &tty_state); /* Don't use tty mode if job is stopped and * later restarted and exits. Consider * the sequence: * vi foo (stopped) * ... * stty something * ... * fg (vi; ZZ) * mode should be that of the stty, not what * was before the vi started. */ if (j->state == PSTOPPED) j->flags &= ~JF_USETTYMODE; } } } j_usrtime = j->usrtime; j_systime = j->systime; rv = j->status; if (!(flags & JW_ASYNCNOTIFY) && (j->state != PSTOPPED)) { j_print(j, JP_SHORT, shl_out); shf_flush(shl_out); } if (j->state != PSTOPPED && (!(flags & JW_ASYNCNOTIFY))) remove_job(j, where); return rv; }