| /*- |
| * Copyright (c) 1991, 1993 |
| * The Regents of the University of California. All rights reserved. |
| * Copyright (c) 1997-2005 |
| * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by |
| * Kenneth Almquist. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the University nor the names of its contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
| * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
| * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
| * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include <fcntl.h> |
| #include <signal.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <paths.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #ifdef BSD |
| #include <sys/wait.h> |
| #include <sys/time.h> |
| #include <sys/resource.h> |
| #endif |
| #include <sys/ioctl.h> |
| |
| #include "shell.h" |
| #if JOBS |
| #include <termios.h> |
| #undef CEOF /* syntax.h redefines this */ |
| #endif |
| #include "redir.h" |
| #include "show.h" |
| #include "main.h" |
| #include "parser.h" |
| #include "nodes.h" |
| #include "jobs.h" |
| #include "options.h" |
| #include "trap.h" |
| #include "syntax.h" |
| #include "input.h" |
| #include "output.h" |
| #include "memalloc.h" |
| #include "error.h" |
| #include "mystring.h" |
| #include "system.h" |
| |
| /* mode flags for set_curjob */ |
| #define CUR_DELETE 2 |
| #define CUR_RUNNING 1 |
| #define CUR_STOPPED 0 |
| |
| /* mode flags for dowait */ |
| #define DOWAIT_NORMAL 0 |
| #define DOWAIT_BLOCK 1 |
| |
| /* array of jobs */ |
| static struct job *jobtab; |
| /* size of array */ |
| static unsigned njobs; |
| /* pid of last background process */ |
| pid_t backgndpid; |
| |
| #if JOBS |
| /* pgrp of shell on invocation */ |
| static int initialpgrp; |
| /* control terminal */ |
| static int ttyfd = -1; |
| #endif |
| |
| /* current job */ |
| static struct job *curjob; |
| /* number of presumed living untracked jobs */ |
| static int jobless; |
| |
| STATIC void set_curjob(struct job *, unsigned); |
| STATIC int jobno(const struct job *); |
| STATIC int sprint_status(char *, int, int); |
| STATIC void freejob(struct job *); |
| STATIC struct job *getjob(const char *, int); |
| STATIC struct job *growjobtab(void); |
| STATIC void forkchild(struct job *, union node *, int); |
| STATIC void forkparent(struct job *, union node *, int, pid_t); |
| STATIC int dowait(int, struct job *); |
| #ifdef SYSV |
| STATIC int onsigchild(void); |
| #endif |
| STATIC int waitproc(int, int *); |
| STATIC char *commandtext(union node *); |
| STATIC void cmdtxt(union node *); |
| STATIC void cmdlist(union node *, int); |
| STATIC void cmdputs(const char *); |
| STATIC void showpipe(struct job *, struct output *); |
| STATIC int getstatus(struct job *); |
| |
| #if JOBS |
| static int restartjob(struct job *, int); |
| static void xtcsetpgrp(int, pid_t); |
| #endif |
| |
| STATIC void |
| set_curjob(struct job *jp, unsigned mode) |
| { |
| struct job *jp1; |
| struct job **jpp, **curp; |
| |
| /* first remove from list */ |
| jpp = curp = &curjob; |
| do { |
| jp1 = *jpp; |
| if (jp1 == jp) |
| break; |
| jpp = &jp1->prev_job; |
| } while (1); |
| *jpp = jp1->prev_job; |
| |
| /* Then re-insert in correct position */ |
| jpp = curp; |
| switch (mode) { |
| default: |
| #ifdef DEBUG |
| abort(); |
| #endif |
| case CUR_DELETE: |
| /* job being deleted */ |
| break; |
| case CUR_RUNNING: |
| /* newly created job or backgrounded job, |
| put after all stopped jobs. */ |
| do { |
| jp1 = *jpp; |
| if (!JOBS || !jp1 || jp1->state != JOBSTOPPED) |
| break; |
| jpp = &jp1->prev_job; |
| } while (1); |
| /* FALLTHROUGH */ |
| #if JOBS |
| case CUR_STOPPED: |
| #endif |
| /* newly stopped job - becomes curjob */ |
| jp->prev_job = *jpp; |
| *jpp = jp; |
| break; |
| } |
| } |
| |
| #if JOBS |
| /* |
| * Turn job control on and off. |
| * |
| * Note: This code assumes that the third arg to ioctl is a character |
| * pointer, which is true on Berkeley systems but not System V. Since |
| * System V doesn't have job control yet, this isn't a problem now. |
| * |
| * Called with interrupts off. |
| */ |
| |
| int jobctl; |
| |
| void |
| setjobctl(int on) |
| { |
| int fd; |
| int pgrp; |
| |
| if (on == jobctl || rootshell == 0) |
| return; |
| if (on) { |
| int ofd; |
| ofd = fd = open(_PATH_TTY, O_RDWR); |
| if (fd < 0) { |
| fd += 3; |
| while (!isatty(fd) && --fd >= 0) |
| ; |
| } |
| fd = fcntl(fd, F_DUPFD, 10); |
| close(ofd); |
| if (fd < 0) |
| goto out; |
| fcntl(fd, F_SETFD, FD_CLOEXEC); |
| do { /* while we are in the background */ |
| if ((pgrp = tcgetpgrp(fd)) < 0) { |
| out: |
| sh_warnx("can't access tty; job control turned off"); |
| mflag = on = 0; |
| goto close; |
| } |
| if (pgrp == getpgrp()) |
| break; |
| killpg(0, SIGTTIN); |
| } while (1); |
| initialpgrp = pgrp; |
| |
| setsignal(SIGTSTP); |
| setsignal(SIGTTOU); |
| setsignal(SIGTTIN); |
| pgrp = rootpid; |
| setpgid(0, pgrp); |
| xtcsetpgrp(fd, pgrp); |
| } else { |
| /* turning job control off */ |
| fd = ttyfd; |
| pgrp = initialpgrp; |
| xtcsetpgrp(fd, pgrp); |
| setpgid(0, pgrp); |
| setsignal(SIGTSTP); |
| setsignal(SIGTTOU); |
| setsignal(SIGTTIN); |
| close: |
| close(fd); |
| fd = -1; |
| } |
| ttyfd = fd; |
| jobctl = on; |
| } |
| #endif |
| |
| |
| int |
| killcmd(argc, argv) |
| int argc; |
| char **argv; |
| { |
| int signo = -1; |
| int list = 0; |
| int i; |
| pid_t pid; |
| struct job *jp; |
| |
| if (argc <= 1) { |
| usage: |
| sh_error( |
| "Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n" |
| "kill -l [exitstatus]" |
| ); |
| } |
| |
| if (**++argv == '-') { |
| signo = decode_signal(*argv + 1, 1); |
| if (signo < 0) { |
| int c; |
| |
| while ((c = nextopt("ls:")) != '\0') |
| switch (c) { |
| default: |
| #ifdef DEBUG |
| abort(); |
| #endif |
| case 'l': |
| list = 1; |
| break; |
| case 's': |
| signo = decode_signal(optionarg, 1); |
| if (signo < 0) { |
| sh_error( |
| "invalid signal number or name: %s", |
| optionarg |
| ); |
| } |
| break; |
| } |
| argv = argptr; |
| } else |
| argv++; |
| } |
| |
| if (!list && signo < 0) |
| signo = SIGTERM; |
| |
| if ((signo < 0 || !*argv) ^ list) { |
| goto usage; |
| } |
| |
| if (list) { |
| struct output *out; |
| |
| out = out1; |
| if (!*argv) { |
| outstr("0\n", out); |
| for (i = 1; i < NSIG; i++) { |
| outfmt(out, snlfmt, signal_name(i)); |
| } |
| return 0; |
| } |
| signo = number(*argv); |
| if (signo > 128) |
| signo -= 128; |
| if (0 < signo && signo < NSIG) |
| outfmt(out, snlfmt, signal_name(signo)); |
| else |
| sh_error("invalid signal number or exit status: %s", |
| *argv); |
| return 0; |
| } |
| |
| i = 0; |
| do { |
| if (**argv == '%') { |
| jp = getjob(*argv, 0); |
| pid = -jp->ps[0].pid; |
| } else |
| pid = **argv == '-' ? |
| -number(*argv + 1) : number(*argv); |
| if (kill(pid, signo) != 0) { |
| sh_warnx("%s\n", strerror(errno)); |
| i = 1; |
| } |
| } while (*++argv); |
| |
| return i; |
| } |
| |
| STATIC int |
| jobno(const struct job *jp) |
| { |
| return jp - jobtab + 1; |
| } |
| |
| #if JOBS |
| int |
| fgcmd(int argc, char **argv) |
| { |
| struct job *jp; |
| struct output *out; |
| int mode; |
| int retval; |
| |
| mode = (**argv == 'f') ? FORK_FG : FORK_BG; |
| nextopt(nullstr); |
| argv = argptr; |
| out = out1; |
| do { |
| jp = getjob(*argv, 1); |
| if (mode == FORK_BG) { |
| set_curjob(jp, CUR_RUNNING); |
| outfmt(out, "[%d] ", jobno(jp)); |
| } |
| outstr(jp->ps->cmd, out); |
| showpipe(jp, out); |
| retval = restartjob(jp, mode); |
| } while (*argv && *++argv); |
| return retval; |
| } |
| |
| int bgcmd(int, char **) __attribute__((__alias__("fgcmd"))); |
| |
| |
| STATIC int |
| restartjob(struct job *jp, int mode) |
| { |
| struct procstat *ps; |
| int i; |
| int status; |
| pid_t pgid; |
| |
| INTOFF; |
| if (jp->state == JOBDONE) |
| goto out; |
| jp->state = JOBRUNNING; |
| pgid = jp->ps->pid; |
| if (mode == FORK_FG) |
| xtcsetpgrp(ttyfd, pgid); |
| killpg(pgid, SIGCONT); |
| ps = jp->ps; |
| i = jp->nprocs; |
| do { |
| if (WIFSTOPPED(ps->status)) { |
| ps->status = -1; |
| } |
| } while (ps++, --i); |
| out: |
| status = (mode == FORK_FG) ? waitforjob(jp) : 0; |
| INTON; |
| return status; |
| } |
| #endif |
| |
| STATIC int |
| sprint_status(char *s, int status, int sigonly) |
| { |
| int col; |
| int st; |
| |
| col = 0; |
| st = WEXITSTATUS(status); |
| if (!WIFEXITED(status)) { |
| #if JOBS |
| st = WSTOPSIG(status); |
| if (!WIFSTOPPED(status)) |
| #endif |
| st = WTERMSIG(status); |
| if (sigonly) { |
| if (st == SIGINT || st == SIGPIPE) |
| goto out; |
| #if JOBS |
| if (WIFSTOPPED(status)) |
| goto out; |
| #endif |
| } |
| col = fmtstr(s, 32, strsignal(st)); |
| if (WCOREDUMP(status)) { |
| col += fmtstr(s + col, 16, " (core dumped)"); |
| } |
| } else if (!sigonly) { |
| if (st) |
| col = fmtstr(s, 16, "Done(%d)", st); |
| else |
| col = fmtstr(s, 16, "Done"); |
| } |
| |
| out: |
| return col; |
| } |
| |
| static void |
| showjob(struct output *out, struct job *jp, int mode) |
| { |
| struct procstat *ps; |
| struct procstat *psend; |
| int col; |
| int indent; |
| char s[80]; |
| |
| ps = jp->ps; |
| |
| if (mode & SHOW_PGID) { |
| /* just output process (group) id of pipeline */ |
| outfmt(out, "%d\n", ps->pid); |
| return; |
| } |
| |
| col = fmtstr(s, 16, "[%d] ", jobno(jp)); |
| indent = col; |
| |
| if (jp == curjob) |
| s[col - 2] = '+'; |
| else if (curjob && jp == curjob->prev_job) |
| s[col - 2] = '-'; |
| |
| if (mode & SHOW_PID) |
| col += fmtstr(s + col, 16, "%d ", ps->pid); |
| |
| psend = ps + jp->nprocs; |
| |
| if (jp->state == JOBRUNNING) { |
| scopy("Running", s + col); |
| col += strlen("Running"); |
| } else { |
| int status = psend[-1].status; |
| #if JOBS |
| if (jp->state == JOBSTOPPED) |
| status = jp->stopstatus; |
| #endif |
| col += sprint_status(s + col, status, 0); |
| } |
| |
| goto start; |
| |
| do { |
| /* for each process */ |
| col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3; |
| |
| start: |
| outfmt( |
| out, "%s%*c%s", |
| s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd |
| ); |
| if (!(mode & SHOW_PID)) { |
| showpipe(jp, out); |
| break; |
| } |
| if (++ps == psend) { |
| outcslow('\n', out); |
| break; |
| } |
| } while (1); |
| |
| jp->changed = 0; |
| |
| if (jp->state == JOBDONE) { |
| TRACE(("showjob: freeing job %d\n", jobno(jp))); |
| freejob(jp); |
| } |
| } |
| |
| |
| int |
| jobscmd(int argc, char **argv) |
| { |
| int mode, m; |
| struct output *out; |
| |
| mode = 0; |
| while ((m = nextopt("lp"))) |
| if (m == 'l') |
| mode = SHOW_PID; |
| else |
| mode = SHOW_PGID; |
| |
| out = out1; |
| argv = argptr; |
| if (*argv) |
| do |
| showjob(out, getjob(*argv,0), mode); |
| while (*++argv); |
| else |
| showjobs(out, mode); |
| |
| return 0; |
| } |
| |
| |
| /* |
| * Print a list of jobs. If "change" is nonzero, only print jobs whose |
| * statuses have changed since the last call to showjobs. |
| */ |
| |
| void |
| showjobs(struct output *out, int mode) |
| { |
| struct job *jp; |
| |
| TRACE(("showjobs(%x) called\n", mode)); |
| |
| /* If not even one one job changed, there is nothing to do */ |
| while (dowait(DOWAIT_NORMAL, NULL) > 0) |
| continue; |
| |
| for (jp = curjob; jp; jp = jp->prev_job) { |
| if (!(mode & SHOW_CHANGED) || jp->changed) |
| showjob(out, jp, mode); |
| } |
| } |
| |
| /* |
| * Mark a job structure as unused. |
| */ |
| |
| STATIC void |
| freejob(struct job *jp) |
| { |
| struct procstat *ps; |
| int i; |
| |
| INTOFF; |
| for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { |
| if (ps->cmd != nullstr) |
| ckfree(ps->cmd); |
| } |
| if (jp->ps != &jp->ps0) |
| ckfree(jp->ps); |
| jp->used = 0; |
| set_curjob(jp, CUR_DELETE); |
| INTON; |
| } |
| |
| |
| |
| int |
| waitcmd(int argc, char **argv) |
| { |
| struct job *job; |
| int retval; |
| struct job *jp; |
| |
| EXSIGON(); |
| |
| nextopt(nullstr); |
| retval = 0; |
| |
| argv = argptr; |
| if (!*argv) { |
| /* wait for all jobs */ |
| for (;;) { |
| jp = curjob; |
| while (1) { |
| if (!jp) { |
| /* no running procs */ |
| goto out; |
| } |
| if (jp->state == JOBRUNNING) |
| break; |
| jp->waited = 1; |
| jp = jp->prev_job; |
| } |
| dowait(DOWAIT_BLOCK, 0); |
| } |
| } |
| |
| retval = 127; |
| do { |
| if (**argv != '%') { |
| pid_t pid = number(*argv); |
| job = curjob; |
| goto start; |
| do { |
| if (job->ps[job->nprocs - 1].pid == pid) |
| break; |
| job = job->prev_job; |
| start: |
| if (!job) |
| goto repeat; |
| } while (1); |
| } else |
| job = getjob(*argv, 0); |
| /* loop until process terminated or stopped */ |
| while (job->state == JOBRUNNING) |
| dowait(DOWAIT_BLOCK, 0); |
| job->waited = 1; |
| retval = getstatus(job); |
| repeat: |
| ; |
| } while (*++argv); |
| |
| out: |
| return retval; |
| } |
| |
| |
| |
| /* |
| * Convert a job name to a job structure. |
| */ |
| |
| STATIC struct job * |
| getjob(const char *name, int getctl) |
| { |
| struct job *jp; |
| struct job *found; |
| const char *err_msg = "No such job: %s"; |
| unsigned num; |
| int c; |
| const char *p; |
| char *(*match)(const char *, const char *); |
| |
| jp = curjob; |
| p = name; |
| if (!p) |
| goto currentjob; |
| |
| if (*p != '%') |
| goto err; |
| |
| c = *++p; |
| if (!c) |
| goto currentjob; |
| |
| if (!p[1]) { |
| if (c == '+' || c == '%') { |
| currentjob: |
| err_msg = "No current job"; |
| goto check; |
| } else if (c == '-') { |
| if (jp) |
| jp = jp->prev_job; |
| err_msg = "No previous job"; |
| check: |
| if (!jp) |
| goto err; |
| goto gotit; |
| } |
| } |
| |
| if (is_number(p)) { |
| num = atoi(p); |
| if (num < njobs) { |
| jp = jobtab + num - 1; |
| if (jp->used) |
| goto gotit; |
| goto err; |
| } |
| } |
| |
| match = prefix; |
| if (*p == '?') { |
| match = strstr; |
| p++; |
| } |
| |
| found = 0; |
| while (1) { |
| if (!jp) |
| goto err; |
| if (match(jp->ps[0].cmd, p)) { |
| if (found) |
| goto err; |
| found = jp; |
| err_msg = "%s: ambiguous"; |
| } |
| jp = jp->prev_job; |
| } |
| |
| gotit: |
| #if JOBS |
| err_msg = "job %s not created under job control"; |
| if (getctl && jp->jobctl == 0) |
| goto err; |
| #endif |
| return jp; |
| err: |
| sh_error(err_msg, name); |
| } |
| |
| |
| |
| /* |
| * Return a new job structure. |
| * Called with interrupts off. |
| */ |
| |
| struct job * |
| makejob(union node *node, int nprocs) |
| { |
| int i; |
| struct job *jp; |
| |
| for (i = njobs, jp = jobtab ; ; jp++) { |
| if (--i < 0) { |
| jp = growjobtab(); |
| break; |
| } |
| if (jp->used == 0) |
| break; |
| if (jp->state != JOBDONE || !jp->waited) |
| continue; |
| if (jobctl) |
| continue; |
| freejob(jp); |
| break; |
| } |
| memset(jp, 0, sizeof(*jp)); |
| #if JOBS |
| if (jobctl) |
| jp->jobctl = 1; |
| #endif |
| jp->prev_job = curjob; |
| curjob = jp; |
| jp->used = 1; |
| jp->ps = &jp->ps0; |
| if (nprocs > 1) { |
| jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); |
| } |
| TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, |
| jobno(jp))); |
| return jp; |
| } |
| |
| STATIC struct job * |
| growjobtab(void) |
| { |
| size_t len; |
| ptrdiff_t offset; |
| struct job *jp, *jq; |
| |
| len = njobs * sizeof(*jp); |
| jq = jobtab; |
| jp = ckrealloc(jq, len + 4 * sizeof(*jp)); |
| |
| offset = (char *)jp - (char *)jq; |
| if (offset) { |
| /* Relocate pointers */ |
| size_t l = len; |
| |
| jq = (struct job *)((char *)jq + l); |
| while (l) { |
| l -= sizeof(*jp); |
| jq--; |
| #define joff(p) ((struct job *)((char *)(p) + l)) |
| #define jmove(p) (p) = (void *)((char *)(p) + offset) |
| if (likely(joff(jp)->ps == &jq->ps0)) |
| jmove(joff(jp)->ps); |
| if (joff(jp)->prev_job) |
| jmove(joff(jp)->prev_job); |
| } |
| if (curjob) |
| jmove(curjob); |
| #undef joff |
| #undef jmove |
| } |
| |
| njobs += 4; |
| jobtab = jp; |
| jp = (struct job *)((char *)jp + len); |
| jq = jp + 3; |
| do { |
| jq->used = 0; |
| } while (--jq >= jp); |
| return jp; |
| } |
| |
| |
| /* |
| * Fork off a subshell. If we are doing job control, give the subshell its |
| * own process group. Jp is a job structure that the job is to be added to. |
| * N is the command that will be evaluated by the child. Both jp and n may |
| * be NULL. The mode parameter can be one of the following: |
| * FORK_FG - Fork off a foreground process. |
| * FORK_BG - Fork off a background process. |
| * FORK_NOJOB - Like FORK_FG, but don't give the process its own |
| * process group even if job control is on. |
| * |
| * When job control is turned off, background processes have their standard |
| * input redirected to /dev/null (except for the second and later processes |
| * in a pipeline). |
| * |
| * Called with interrupts off. |
| */ |
| |
| STATIC inline void |
| forkchild(struct job *jp, union node *n, int mode) |
| { |
| int oldlvl; |
| |
| TRACE(("Child shell %d\n", getpid())); |
| oldlvl = shlvl; |
| shlvl++; |
| |
| closescript(); |
| clear_traps(); |
| #if JOBS |
| /* do job control only in root shell */ |
| jobctl = 0; |
| if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { |
| pid_t pgrp; |
| |
| if (jp->nprocs == 0) |
| pgrp = getpid(); |
| else |
| pgrp = jp->ps[0].pid; |
| /* This can fail because we are doing it in the parent also */ |
| (void)setpgid(0, pgrp); |
| if (mode == FORK_FG) |
| xtcsetpgrp(ttyfd, pgrp); |
| setsignal(SIGTSTP); |
| setsignal(SIGTTOU); |
| } else |
| #endif |
| if (mode == FORK_BG) { |
| ignoresig(SIGINT); |
| ignoresig(SIGQUIT); |
| if (jp->nprocs == 0) { |
| close(0); |
| if (open(_PATH_DEVNULL, O_RDONLY) != 0) |
| sh_error("Can't open %s", _PATH_DEVNULL); |
| } |
| } |
| if (!oldlvl && iflag) { |
| setsignal(SIGINT); |
| setsignal(SIGQUIT); |
| setsignal(SIGTERM); |
| } |
| for (jp = curjob; jp; jp = jp->prev_job) |
| freejob(jp); |
| jobless = 0; |
| } |
| |
| STATIC inline void |
| forkparent(struct job *jp, union node *n, int mode, pid_t pid) |
| { |
| TRACE(("In parent shell: child = %d\n", pid)); |
| if (!jp) { |
| while (jobless && dowait(DOWAIT_NORMAL, 0) > 0); |
| jobless++; |
| return; |
| } |
| #if JOBS |
| if (mode != FORK_NOJOB && jp->jobctl) { |
| int pgrp; |
| |
| if (jp->nprocs == 0) |
| pgrp = pid; |
| else |
| pgrp = jp->ps[0].pid; |
| /* This can fail because we are doing it in the child also */ |
| (void)setpgid(pid, pgrp); |
| } |
| #endif |
| if (mode == FORK_BG) { |
| backgndpid = pid; /* set $! */ |
| set_curjob(jp, CUR_RUNNING); |
| } |
| if (jp) { |
| struct procstat *ps = &jp->ps[jp->nprocs++]; |
| ps->pid = pid; |
| ps->status = -1; |
| ps->cmd = nullstr; |
| if (jobctl && n) |
| ps->cmd = commandtext(n); |
| } |
| } |
| |
| int |
| forkshell(struct job *jp, union node *n, int mode) |
| { |
| int pid; |
| |
| TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); |
| pid = fork(); |
| if (pid < 0) { |
| TRACE(("Fork failed, errno=%d", errno)); |
| if (jp) |
| freejob(jp); |
| sh_error("Cannot fork"); |
| } |
| if (pid == 0) |
| forkchild(jp, n, mode); |
| else |
| forkparent(jp, n, mode, pid); |
| return pid; |
| } |
| |
| /* |
| * Wait for job to finish. |
| * |
| * Under job control we have the problem that while a child process is |
| * running interrupts generated by the user are sent to the child but not |
| * to the shell. This means that an infinite loop started by an inter- |
| * active user may be hard to kill. With job control turned off, an |
| * interactive user may place an interactive program inside a loop. If |
| * the interactive program catches interrupts, the user doesn't want |
| * these interrupts to also abort the loop. The approach we take here |
| * is to have the shell ignore interrupt signals while waiting for a |
| * forground process to terminate, and then send itself an interrupt |
| * signal if the child process was terminated by an interrupt signal. |
| * Unfortunately, some programs want to do a bit of cleanup and then |
| * exit on interrupt; unless these processes terminate themselves by |
| * sending a signal to themselves (instead of calling exit) they will |
| * confuse this approach. |
| * |
| * Called with interrupts off. |
| */ |
| |
| int |
| waitforjob(struct job *jp) |
| { |
| int st; |
| |
| TRACE(("waitforjob(%%%d) called\n", jobno(jp))); |
| while (jp->state == JOBRUNNING) { |
| dowait(DOWAIT_BLOCK, jp); |
| } |
| st = getstatus(jp); |
| #if JOBS |
| if (jp->jobctl) { |
| xtcsetpgrp(ttyfd, rootpid); |
| /* |
| * This is truly gross. |
| * If we're doing job control, then we did a TIOCSPGRP which |
| * caused us (the shell) to no longer be in the controlling |
| * session -- so we wouldn't have seen any ^C/SIGINT. So, we |
| * intuit from the subprocess exit status whether a SIGINT |
| * occurred, and if so interrupt ourselves. Yuck. - mycroft |
| */ |
| if (jp->sigint) |
| raise(SIGINT); |
| } |
| #endif |
| if (! JOBS || jp->state == JOBDONE) |
| freejob(jp); |
| return st; |
| } |
| |
| |
| |
| /* |
| * Wait for a process to terminate. |
| */ |
| |
| STATIC int |
| dowait(int block, struct job *job) |
| { |
| int pid; |
| int status; |
| struct job *jp; |
| struct job *thisjob; |
| int state; |
| |
| TRACE(("dowait(%d) called\n", block)); |
| pid = waitproc(block, &status); |
| TRACE(("wait returns pid %d, status=%d\n", pid, status)); |
| if (pid <= 0) |
| return pid; |
| INTOFF; |
| thisjob = NULL; |
| for (jp = curjob; jp; jp = jp->prev_job) { |
| struct procstat *sp; |
| struct procstat *spend; |
| if (jp->state == JOBDONE) |
| continue; |
| state = JOBDONE; |
| spend = jp->ps + jp->nprocs; |
| sp = jp->ps; |
| do { |
| if (sp->pid == pid) { |
| TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->status, status)); |
| sp->status = status; |
| thisjob = jp; |
| } |
| if (sp->status == -1) |
| state = JOBRUNNING; |
| #if JOBS |
| if (state == JOBRUNNING) |
| continue; |
| if (WIFSTOPPED(sp->status)) { |
| jp->stopstatus = sp->status; |
| state = JOBSTOPPED; |
| } |
| #endif |
| } while (++sp < spend); |
| if (thisjob) |
| goto gotjob; |
| } |
| if (!JOBS || !WIFSTOPPED(status)) |
| jobless--; |
| goto out; |
| |
| gotjob: |
| if (state != JOBRUNNING) { |
| thisjob->changed = 1; |
| |
| if (thisjob->state != state) { |
| TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, state)); |
| thisjob->state = state; |
| #if JOBS |
| if (state == JOBSTOPPED) { |
| set_curjob(thisjob, CUR_STOPPED); |
| } |
| #endif |
| } |
| } |
| |
| out: |
| INTON; |
| |
| if (thisjob && thisjob == job) { |
| char s[48 + 1]; |
| int len; |
| |
| len = sprint_status(s, status, 1); |
| if (len) { |
| s[len] = '\n'; |
| s[len + 1] = 0; |
| outstr(s, out2); |
| } |
| } |
| return pid; |
| } |
| |
| |
| |
| /* |
| * Do a wait system call. If job control is compiled in, we accept |
| * stopped processes. If block is zero, we return a value of zero |
| * rather than blocking. |
| * |
| * System V doesn't have a non-blocking wait system call. It does |
| * have a SIGCLD signal that is sent to a process when one of it's |
| * children dies. The obvious way to use SIGCLD would be to install |
| * a handler for SIGCLD which simply bumped a counter when a SIGCLD |
| * was received, and have waitproc bump another counter when it got |
| * the status of a process. Waitproc would then know that a wait |
| * system call would not block if the two counters were different. |
| * This approach doesn't work because if a process has children that |
| * have not been waited for, System V will send it a SIGCLD when it |
| * installs a signal handler for SIGCLD. What this means is that when |
| * a child exits, the shell will be sent SIGCLD signals continuously |
| * until is runs out of stack space, unless it does a wait call before |
| * restoring the signal handler. The code below takes advantage of |
| * this (mis)feature by installing a signal handler for SIGCLD and |
| * then checking to see whether it was called. If there are any |
| * children to be waited for, it will be. |
| * |
| * If neither SYSV nor BSD is defined, we don't implement nonblocking |
| * waits at all. In this case, the user will not be informed when |
| * a background process until the next time she runs a real program |
| * (as opposed to running a builtin command or just typing return), |
| * and the jobs command may give out of date information. |
| */ |
| |
| #ifdef SYSV |
| STATIC int gotsigchild; |
| |
| STATIC int onsigchild() { |
| gotsigchild = 1; |
| } |
| #endif |
| |
| |
| STATIC int |
| waitproc(int block, int *status) |
| { |
| #ifdef BSD |
| int flags = 0; |
| |
| #if JOBS |
| if (jobctl) |
| flags |= WUNTRACED; |
| #endif |
| if (block == 0) |
| flags |= WNOHANG; |
| return wait3(status, flags, (struct rusage *)NULL); |
| #else |
| #ifdef SYSV |
| int (*save)(); |
| |
| if (block == 0) { |
| gotsigchild = 0; |
| save = signal(SIGCLD, onsigchild); |
| signal(SIGCLD, save); |
| if (gotsigchild == 0) |
| return 0; |
| } |
| return wait(status); |
| #else |
| if (block == 0) |
| return 0; |
| return wait(status); |
| #endif |
| #endif |
| } |
| |
| /* |
| * return 1 if there are stopped jobs, otherwise 0 |
| */ |
| int job_warning; |
| int |
| stoppedjobs(void) |
| { |
| struct job *jp; |
| int retval; |
| |
| retval = 0; |
| if (job_warning) |
| goto out; |
| jp = curjob; |
| if (jp && jp->state == JOBSTOPPED) { |
| out2str("You have stopped jobs.\n"); |
| job_warning = 2; |
| retval++; |
| } |
| |
| out: |
| return retval; |
| } |
| |
| /* |
| * Return a string identifying a command (to be printed by the |
| * jobs command). |
| */ |
| |
| STATIC char *cmdnextc; |
| |
| STATIC char * |
| commandtext(union node *n) |
| { |
| char *name; |
| |
| STARTSTACKSTR(cmdnextc); |
| cmdtxt(n); |
| name = stackblock(); |
| TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", |
| name, cmdnextc, ps->cmd)); |
| return savestr(name); |
| } |
| |
| |
| STATIC void |
| cmdtxt(union node *n) |
| { |
| union node *np; |
| struct nodelist *lp; |
| const char *p; |
| char s[2]; |
| |
| if (!n) |
| return; |
| switch (n->type) { |
| default: |
| #if DEBUG |
| abort(); |
| #endif |
| case NPIPE: |
| lp = n->npipe.cmdlist; |
| for (;;) { |
| cmdtxt(lp->n); |
| lp = lp->next; |
| if (!lp) |
| break; |
| cmdputs(" | "); |
| } |
| break; |
| case NSEMI: |
| p = "; "; |
| goto binop; |
| case NAND: |
| p = " && "; |
| goto binop; |
| case NOR: |
| p = " || "; |
| binop: |
| cmdtxt(n->nbinary.ch1); |
| cmdputs(p); |
| n = n->nbinary.ch2; |
| goto donode; |
| case NREDIR: |
| case NBACKGND: |
| n = n->nredir.n; |
| goto donode; |
| case NNOT: |
| cmdputs("!"); |
| n = n->nnot.com; |
| donode: |
| cmdtxt(n); |
| break; |
| case NIF: |
| cmdputs("if "); |
| cmdtxt(n->nif.test); |
| cmdputs("; then "); |
| n = n->nif.ifpart; |
| if (n->nif.elsepart) { |
| cmdtxt(n); |
| cmdputs("; else "); |
| n = n->nif.elsepart; |
| } |
| p = "; fi"; |
| goto dotail; |
| case NSUBSHELL: |
| cmdputs("("); |
| n = n->nredir.n; |
| p = ")"; |
| goto dotail; |
| case NWHILE: |
| p = "while "; |
| goto until; |
| case NUNTIL: |
| p = "until "; |
| until: |
| cmdputs(p); |
| cmdtxt(n->nbinary.ch1); |
| n = n->nbinary.ch2; |
| p = "; done"; |
| dodo: |
| cmdputs("; do "); |
| dotail: |
| cmdtxt(n); |
| goto dotail2; |
| case NFOR: |
| cmdputs("for "); |
| cmdputs(n->nfor.var); |
| cmdputs(" in "); |
| cmdlist(n->nfor.args, 1); |
| n = n->nfor.body; |
| p = "; done"; |
| goto dodo; |
| case NDEFUN: |
| cmdputs(n->narg.text); |
| p = "() { ... }"; |
| goto dotail2; |
| case NCMD: |
| cmdlist(n->ncmd.args, 1); |
| cmdlist(n->ncmd.redirect, 0); |
| break; |
| case NARG: |
| p = n->narg.text; |
| dotail2: |
| cmdputs(p); |
| break; |
| case NHERE: |
| case NXHERE: |
| p = "<<..."; |
| goto dotail2; |
| case NCASE: |
| cmdputs("case "); |
| cmdputs(n->ncase.expr->narg.text); |
| cmdputs(" in "); |
| for (np = n->ncase.cases; np; np = np->nclist.next) { |
| cmdtxt(np->nclist.pattern); |
| cmdputs(") "); |
| cmdtxt(np->nclist.body); |
| cmdputs(";; "); |
| } |
| p = "esac"; |
| goto dotail2; |
| case NTO: |
| p = ">"; |
| goto redir; |
| case NCLOBBER: |
| p = ">|"; |
| goto redir; |
| case NAPPEND: |
| p = ">>"; |
| goto redir; |
| case NTOFD: |
| p = ">&"; |
| goto redir; |
| case NFROM: |
| p = "<"; |
| goto redir; |
| case NFROMFD: |
| p = "<&"; |
| goto redir; |
| case NFROMTO: |
| p = "<>"; |
| redir: |
| s[0] = n->nfile.fd + '0'; |
| s[1] = '\0'; |
| cmdputs(s); |
| cmdputs(p); |
| if (n->type == NTOFD || n->type == NFROMFD) { |
| s[0] = n->ndup.dupfd + '0'; |
| p = s; |
| goto dotail2; |
| } else { |
| n = n->nfile.fname; |
| goto donode; |
| } |
| } |
| } |
| |
| STATIC void |
| cmdlist(union node *np, int sep) |
| { |
| for (; np; np = np->narg.next) { |
| if (!sep) |
| cmdputs(spcstr); |
| cmdtxt(np); |
| if (sep && np->narg.next) |
| cmdputs(spcstr); |
| } |
| } |
| |
| |
| STATIC void |
| cmdputs(const char *s) |
| { |
| const char *p, *str; |
| char cc[2] = " "; |
| char *nextc; |
| signed char c; |
| int subtype = 0; |
| int quoted = 0; |
| static const char vstype[VSTYPE + 1][4] = { |
| "", "}", "-", "+", "?", "=", |
| "%", "%%", "#", "##", |
| }; |
| |
| nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); |
| p = s; |
| while ((c = *p++) != 0) { |
| str = 0; |
| switch (c) { |
| case CTLESC: |
| c = *p++; |
| break; |
| case CTLVAR: |
| subtype = *p++; |
| if ((subtype & VSTYPE) == VSLENGTH) |
| str = "${#"; |
| else |
| str = "${"; |
| if (!(subtype & VSQUOTE) != !(quoted & 1)) { |
| quoted ^= 1; |
| c = '"'; |
| } else |
| goto dostr; |
| break; |
| case CTLENDVAR: |
| str = "\"}" + !(quoted & 1); |
| quoted >>= 1; |
| subtype = 0; |
| goto dostr; |
| case CTLBACKQ: |
| str = "$(...)"; |
| goto dostr; |
| case CTLBACKQ+CTLQUOTE: |
| str = "\"$(...)\""; |
| goto dostr; |
| case CTLARI: |
| str = "$(("; |
| goto dostr; |
| case CTLENDARI: |
| str = "))"; |
| goto dostr; |
| case CTLQUOTEMARK: |
| quoted ^= 1; |
| c = '"'; |
| break; |
| case '=': |
| if (subtype == 0) |
| break; |
| if ((subtype & VSTYPE) != VSNORMAL) |
| quoted <<= 1; |
| str = vstype[subtype & VSTYPE]; |
| if (subtype & VSNUL) |
| c = ':'; |
| else |
| goto checkstr; |
| break; |
| case '\'': |
| case '\\': |
| case '"': |
| case '$': |
| /* These can only happen inside quotes */ |
| cc[0] = c; |
| str = cc; |
| c = '\\'; |
| break; |
| default: |
| break; |
| } |
| USTPUTC(c, nextc); |
| checkstr: |
| if (!str) |
| continue; |
| dostr: |
| while ((c = *str++)) { |
| USTPUTC(c, nextc); |
| } |
| } |
| if (quoted & 1) { |
| USTPUTC('"', nextc); |
| } |
| *nextc = 0; |
| cmdnextc = nextc; |
| } |
| |
| |
| STATIC void |
| showpipe(struct job *jp, struct output *out) |
| { |
| struct procstat *sp; |
| struct procstat *spend; |
| |
| spend = jp->ps + jp->nprocs; |
| for (sp = jp->ps + 1; sp < spend; sp++) |
| outfmt(out, " | %s", sp->cmd); |
| outcslow('\n', out); |
| flushall(); |
| } |
| |
| |
| #if JOBS |
| STATIC void |
| xtcsetpgrp(int fd, pid_t pgrp) |
| { |
| if (tcsetpgrp(fd, pgrp)) |
| sh_error("Cannot set tty process group (%s)", strerror(errno)); |
| } |
| #endif |
| |
| |
| STATIC int |
| getstatus(struct job *job) { |
| int status; |
| int retval; |
| |
| status = job->ps[job->nprocs - 1].status; |
| retval = WEXITSTATUS(status); |
| if (!WIFEXITED(status)) { |
| #if JOBS |
| retval = WSTOPSIG(status); |
| if (!WIFSTOPPED(status)) |
| #endif |
| { |
| /* XXX: limits number of signals */ |
| retval = WTERMSIG(status); |
| #if JOBS |
| if (retval == SIGINT) |
| job->sigint = 1; |
| #endif |
| } |
| retval += 128; |
| } |
| TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", |
| jobno(job), job->nprocs, status, retval)); |
| return retval; |
| } |