/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Job control for UNIX Shell */ #include <sys/termio.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/param.h> #include <fcntl.h> #include <errno.h> #include <signal.h> #include "defs.h" /* * one of these for each active job */ struct job { struct job *j_nxtp; /* next job in job ID order */ struct job *j_curp; /* next job in job currency order */ struct termios j_stty; /* termio save area when job stops */ pid_t j_pid; /* job leader's process ID */ pid_t j_pgid; /* job's process group ID */ pid_t j_tgid; /* job's foreground process group ID */ uint_t j_jid; /* job ID */ ushort_t j_xval; /* exit code, or exit or stop signal */ ushort_t j_flag; /* various status flags defined below */ char *j_pwd; /* job's working directory */ char *j_cmd; /* cmd used to invoke this job */ }; /* defines for j_flag */ #define J_DUMPED 0001 /* job has core dumped */ #define J_NOTIFY 0002 /* job has changed status */ #define J_SAVETTY 0004 /* job was stopped in foreground, and its */ /* termio settings were saved */ #define J_STOPPED 0010 /* job has been stopped */ #define J_SIGNALED 0020 /* job has received signal; j_xval has it */ #define J_DONE 0040 /* job has finished */ #define J_RUNNING 0100 /* job is currently running */ #define J_FOREGND 0200 /* job was put in foreground by shell */ /* options to the printjob() function defined below */ #define PR_CUR 00001 /* print job currency ('+', '-', or ' ') */ #define PR_JID 00002 /* print job ID */ #define PR_PGID 00004 /* print job's process group ID */ #define PR_STAT 00010 /* print status obtained from wait */ #define PR_CMD 00020 /* print cmd that invoked job */ #define PR_AMP 00040 /* print a '&' if in the background */ #define PR_PWD 00100 /* print jobs present working directory */ #define PR_DFL (PR_CUR|PR_JID|PR_STAT|PR_CMD) /* default options */ #define PR_LONG (PR_DFL|PR_PGID|PR_PWD) /* long options */ static struct termios mystty; /* default termio settings */ static int eofflg, jobcnt, /* number of active jobs */ jobdone, /* number of active but finished jobs */ jobnote; /* jobs requiring notification */ static pid_t svpgid, /* saved process group ID */ svtgid; /* saved foreground process group ID */ static struct job *jobcur, /* active jobs listed in currency order */ **nextjob, *thisjob, *joblst; /* active jobs listed in job ID order */ static void printjob(struct job *, int); static struct job * pgid2job(pid_t pgid) { struct job *jp; for (jp = joblst; jp != 0 && jp->j_pid != pgid; jp = jp->j_nxtp) continue; return (jp); } static struct job * str2job(char *cmd, char *job, int mustbejob) { struct job *jp, *njp; int i; if (*job != '%') jp = pgid2job(stoi(job)); else if (*++job == 0 || *job == '+' || *job == '%' || *job == '-') { jp = jobcur; if (*job == '-' && jp) jp = jp->j_curp; } else if (*job >= '0' && *job <= '9') { i = stoi(job); for (jp = joblst; jp && jp->j_jid != i; jp = jp->j_nxtp) continue; } else if (*job == '?') { int j; char *p; i = strlen(++job); jp = 0; for (njp = jobcur; njp; njp = njp->j_curp) { if (njp->j_jid == 0) continue; for (p = njp->j_cmd, j = strlen(p); j >= i; p++, j--) { if (strncmp(job, p, i) == 0) { if (jp != 0) failed((unsigned char *)cmd, ambiguous); jp = njp; break; } } } } else { i = strlen(job); jp = 0; for (njp = jobcur; njp; njp = njp->j_curp) { if (njp->j_jid == 0) continue; if (strncmp(job, njp->j_cmd, i) == 0) { if (jp != 0) failed((unsigned char *)cmd, ambiguous); jp = njp; } } } if (mustbejob && (jp == 0 || jp->j_jid == 0)) failed((unsigned char *)cmd, nosuchjob); return (jp); } static void freejob(struct job *jp) { struct job **njp; struct job **cjp; for (njp = &joblst; *njp != jp; njp = &(*njp)->j_nxtp) continue; for (cjp = &jobcur; *cjp != jp; cjp = &(*cjp)->j_curp) continue; *njp = jp->j_nxtp; *cjp = jp->j_curp; free(jp); jobcnt--; jobdone--; } /* * Collect the foreground job. * Used in the case where the subshell wants * to exit, but needs to wait until the fg job * is done. */ void collect_fg_job(void) { struct job *jp; pid_t pid; int stat; for (jp = joblst; jp; jp = jp->j_nxtp) if (jp->j_flag & J_FOREGND) break; if (!jp) /* no foreground job */ return; /* * Wait on fg job until wait succeeds * or it fails due to no waitable children. */ while (1) { errno = 0; pid = waitpid(jp->j_pid, &stat, 0); if (pid == jp->j_pid || (pid == -1 && errno == ECHILD)) break; } } /* * analyze the status of a job */ static int statjob(struct job *jp, int stat, int fg, int rc) { pid_t tgid; int done = 0; if (WIFCONTINUED(stat)) { if (jp->j_flag & J_STOPPED) { jp->j_flag &= ~(J_STOPPED|J_SIGNALED|J_SAVETTY); jp->j_flag |= J_RUNNING; if (!fg && jp->j_jid) { jp->j_flag |= J_NOTIFY; jobnote++; } } } else if (WIFSTOPPED(stat)) { jp->j_xval = WSTOPSIG(stat); jp->j_flag &= ~J_RUNNING; jp->j_flag |= (J_SIGNALED|J_STOPPED); jp->j_pgid = getpgid(jp->j_pid); jp->j_tgid = jp->j_pgid; if (fg) { if (tgid = settgid(mypgid, jp->j_pgid)) jp->j_tgid = tgid; else { jp->j_flag |= J_SAVETTY; tcgetattr(0, &jp->j_stty); (void) tcsetattr(0, TCSANOW, &mystty); } } if (jp->j_jid) { jp->j_flag |= J_NOTIFY; jobnote++; } } else { jp->j_flag &= ~J_RUNNING; jp->j_flag |= J_DONE; done++; jobdone++; if (WIFSIGNALED(stat)) { jp->j_xval = WTERMSIG(stat); jp->j_flag |= J_SIGNALED; if (WCOREDUMP(stat)) jp->j_flag |= J_DUMPED; if (!fg || jp->j_xval != SIGINT) { jp->j_flag |= J_NOTIFY; jobnote++; } } else { /* WIFEXITED */ jp->j_xval = WEXITSTATUS(stat); jp->j_flag &= ~J_SIGNALED; if (!fg && jp->j_jid) { jp->j_flag |= J_NOTIFY; jobnote++; } } if (fg) { if (!settgid(mypgid, jp->j_pgid) || !settgid(mypgid, getpgid(jp->j_pid))) tcgetattr(0, &mystty); } } if (rc) { exitval = jp->j_xval; if (jp->j_flag & J_SIGNALED) exitval |= SIGFLG; exitset(); } if (done && !(jp->j_flag & J_NOTIFY)) freejob(jp); return (done); } /* * collect the status of jobs that have recently exited or stopped - * if wnohang == WNOHANG, wait until error, or all jobs are accounted for; * * called after each command is executed, with wnohang == 0, and as part * of "wait" builtin with wnohang == WNOHANG * * We do not need to call chktrap here if waitpid(2) is called with * wnohang == 0, because that only happens from syswait() which is called * from builtin() where chktrap() is already called. */ static void collectjobs(wnohang) { pid_t pid; struct job *jp; int stat, n; int wflags; if ((flags & (monitorflg|jcflg|jcoff)) == (monitorflg|jcflg)) wflags = WUNTRACED|WCONTINUED; else wflags = 0; for (n = jobcnt - jobdone; n > 0; n--) { if ((pid = waitpid(-1, &stat, wnohang|wflags)) <= 0) break; if (jp = pgid2job(pid)) (void) statjob(jp, stat, 0, 0); } } void freejobs() { struct job *jp; collectjobs(WNOHANG); if (jobnote) { int savefd = setb(2); for (jp = joblst; jp; jp = jp->j_nxtp) { if (jp->j_flag & J_NOTIFY) { if (jp->j_jid) printjob(jp, PR_DFL); else if (jp->j_flag & J_FOREGND) printjob(jp, PR_STAT); else printjob(jp, PR_STAT|PR_PGID); } } (void) setb(savefd); } if (jobdone) { for (jp = joblst; jp; jp = jp->j_nxtp) { if (jp->j_flag & J_DONE) freejob(jp); } } } static void waitjob(struct job *jp) { int stat; int done; pid_t pid = jp->j_pid; int wflags; int ret = 0; int err = 0; if ((flags & (monitorflg|jcflg|jcoff)) == (monitorflg|jcflg)) wflags = WUNTRACED; else wflags = 0; do { errno = 0; ret = waitpid(pid, &stat, wflags|WNOWAIT); err = errno; if (ret == -1 && err == ECHILD) { stat = 0; break; } } while (ret != pid); done = statjob(jp, stat, 1, 1); waitpid(pid, 0, wflags); if (done && exitval && (flags & errflg)) exitsh(exitval); flags |= eflag; } /* * modify the foreground process group to *new* only if the * current foreground process group is equal to *expected* */ int settgid(new, expected) pid_t new, expected; { pid_t current = tcgetpgrp(0); if (current != expected) return (current); if (new != current) tcsetpgrp(0, new); return (0); } static void restartjob(struct job *jp, int fg) { if (jp != jobcur) { struct job *t; for (t = jobcur; t->j_curp != jp; t = t->j_curp) ; t->j_curp = jp->j_curp; jp->j_curp = jobcur; jobcur = jp; } if (fg) { if (jp->j_flag & J_SAVETTY) { jp->j_stty.c_lflag &= ~TOSTOP; jp->j_stty.c_lflag |= (mystty.c_lflag&TOSTOP); jp->j_stty.c_cc[VSUSP] = mystty.c_cc[VSUSP]; jp->j_stty.c_cc[VDSUSP] = mystty.c_cc[VDSUSP]; (void) tcsetattr(0, TCSADRAIN, &jp->j_stty); } (void) settgid(jp->j_tgid, mypgid); } (void) kill(-(jp->j_pgid), SIGCONT); if (jp->j_tgid != jp->j_pgid) (void) kill(-(jp->j_tgid), SIGCONT); jp->j_flag &= ~(J_STOPPED|J_SIGNALED|J_SAVETTY); jp->j_flag |= J_RUNNING; if (fg) { jp->j_flag |= J_FOREGND; printjob(jp, PR_JID|PR_CMD); waitjob(jp); } else { jp->j_flag &= ~J_FOREGND; printjob(jp, PR_JID|PR_CMD|PR_AMP); } } static void printjob(struct job *jp, int propts) { int sp = 0; if (jp->j_flag & J_NOTIFY) { jobnote--; jp->j_flag &= ~J_NOTIFY; } if (propts & PR_JID) { prc_buff('['); prn_buff(jp->j_jid); prc_buff(']'); sp = 1; } if (propts & PR_CUR) { while (sp-- > 0) prc_buff(SPACE); sp = 1; if (jobcur == jp) prc_buff('+'); else if (jobcur != 0 && jobcur->j_curp == jp) prc_buff('-'); else sp++; } if (propts & PR_PGID) { while (sp-- > 0) prc_buff(SPACE); prn_buff(jp->j_pid); sp = 1; } if (propts & PR_STAT) { char *gmsg; while (sp-- > 0) prc_buff(SPACE); sp = 28; if (jp->j_flag & J_SIGNALED) { char *sigstr, *strsignal(); if ((sigstr = strsignal(jp->j_xval)) != NULL) { sp -= strlen(sigstr); prs_buff(sigstr); } else { itos(jp->j_xval); gmsg = gettext(signalnum); sp -= strlen(numbuf) + strlen(gmsg); prs_buff((unsigned char *)gmsg); prs_buff(numbuf); } if (jp->j_flag & J_DUMPED) { gmsg = gettext(coredump); sp -= strlen(gmsg); prs_buff((unsigned char *)gmsg); } } else if (jp->j_flag & J_DONE) { itos(jp->j_xval); gmsg = gettext(exited); sp -= strlen(gmsg) + strlen(numbuf) + 2; prs_buff((unsigned char *)gmsg); prc_buff('('); itos(jp->j_xval); prs_buff(numbuf); prc_buff(')'); } else { gmsg = gettext(running); sp -= strlen(gmsg); prs_buff((unsigned char *)gmsg); } if (sp < 1) sp = 1; } if (propts & PR_CMD) { while (sp-- > 0) prc_buff(SPACE); prs_buff(jp->j_cmd); sp = 1; } if (propts & PR_AMP) { while (sp-- > 0) prc_buff(SPACE); prc_buff('&'); sp = 1; } if (propts & PR_PWD) { while (sp-- > 0) prc_buff(SPACE); prs_buff("(wd: "); prs_buff(jp->j_pwd); prc_buff(')'); } prc_buff(NL); flushb(); } /* * called to initialize job control for each new input file to the shell, * and after the "exec" builtin */ void startjobs() { svpgid = mypgid; if (tcgetattr(0, &mystty) == -1 || (svtgid = tcgetpgrp(0)) == -1) { flags &= ~jcflg; return; } flags |= jcflg; handle(SIGTTOU, SIG_IGN); handle(SIGTSTP, SIG_DFL); if (mysid != mypgid) { setpgid(0, 0); mypgid = mypid; (void) settgid(mypgid, svpgid); } } int endjobs(check_if) int check_if; { if ((flags & (jcoff|jcflg)) != jcflg) return (1); if (check_if && jobcnt && eofflg++ == 0) { struct job *jp; if (check_if & JOB_STOPPED) { for (jp = joblst; jp; jp = jp->j_nxtp) { if (jp->j_jid && (jp->j_flag & J_STOPPED)) { prs(_gettext(jobsstopped)); prc(NL); return (0); } } } if (check_if & JOB_RUNNING) { for (jp = joblst; jp; jp = jp->j_nxtp) { if (jp->j_jid && (jp->j_flag & J_RUNNING)) { prs(_gettext(jobsrunning)); prc(NL); return (0); } } } } if (svpgid != mypgid) { (void) settgid(svtgid, mypgid); setpgid(0, svpgid); } return (1); } /* * called by the shell to reserve a job slot for a job about to be spawned */ void deallocjob() { free(thisjob); jobcnt--; } void allocjob(char *cmd, uchar_t *cwd, int monitor) { struct job *jp, **jpp; int jid, cmdlen, cwdlen; cmdlen = strlen(cmd) + 1; if (cmd[cmdlen-2] == '&') { cmd[cmdlen-3] = 0; cmdlen -= 2; } cwdlen = strlen(cwd) + 1; jp = (struct job *) alloc(sizeof (struct job) + cmdlen + cwdlen); if (jp == 0) error(nostack); jobcnt++; jp->j_cmd = ((char *)jp) + sizeof (struct job); strcpy(jp->j_cmd, cmd); jp->j_pwd = jp->j_cmd + cmdlen; strcpy(jp->j_pwd, cwd); jpp = &joblst; if (monitor) { for (; *jpp; jpp = &(*jpp)->j_nxtp) if ((*jpp)->j_jid != 0) break; for (jid = 1; *jpp; jpp = &(*jpp)->j_nxtp, jid++) if ((*jpp)->j_jid != jid) break; } else jid = 0; jp->j_jid = jid; nextjob = jpp; thisjob = jp; } void clearjobs(void) { struct job *jp, *sjp; for (jp = joblst; jp; jp = sjp) { sjp = jp->j_nxtp; free(jp); } joblst = NULL; jobcnt = 0; jobnote = 0; jobdone = 0; } void makejob(int monitor, int fg) { if (monitor) { mypgid = mypid; setpgid(0, 0); if (fg) tcsetpgrp(0, mypid); handle(SIGTTOU, SIG_DFL); handle(SIGTSTP, SIG_DFL); } else if (!fg) { #ifdef NICE nice(NICE); #endif handle(SIGTTIN, SIG_IGN); handle(SIGINT, SIG_IGN); handle(SIGQUIT, SIG_IGN); if (!ioset) renamef(chkopen(devnull, 0), 0); } } /* * called by the shell after job has been spawned, to fill in the * job slot, and wait for the job if in the foreground */ void postjob(pid, fg) pid_t pid; int fg; { int propts; thisjob->j_nxtp = *nextjob; *nextjob = thisjob; thisjob->j_curp = jobcur; jobcur = thisjob; if (thisjob->j_jid) { thisjob->j_pgid = pid; propts = PR_JID|PR_PGID; } else { thisjob->j_pgid = mypgid; propts = PR_PGID; } thisjob->j_flag = J_RUNNING; thisjob->j_tgid = thisjob->j_pgid; thisjob->j_pid = pid; eofflg = 0; if (fg) { thisjob->j_flag |= J_FOREGND; waitjob(thisjob); } else { if (flags & ttyflg) printjob(thisjob, propts); assnum(&pcsadr, (long)pid); } } /* * the builtin "jobs" command */ void sysjobs(argc, argv) int argc; char *argv[]; { char *cmd = *argv; struct job *jp; int propts, c; extern int opterr, i; int savoptind = optind; int loptind = -1; int savopterr = opterr; int savsp = _sp; char *savoptarg = optarg; optind = 1; opterr = 0; _sp = 1; propts = 0; if ((flags & jcflg) == 0) failed((unsigned char *)cmd, nojc); while ((c = getopt(argc, argv, "lpx")) != -1) { if (propts) { gfailure(usage, jobsuse); goto err; } switch (c) { case 'x': propts = -1; break; case 'p': propts = PR_PGID; break; case 'l': propts = PR_LONG; break; case '?': gfailure(usage, jobsuse); goto err; } } loptind = optind; err: optind = savoptind; optarg = savoptarg; opterr = savopterr; _sp = savsp; if (loptind == -1) return; if (propts == -1) { unsigned char *bp; char *cp; unsigned char *savebp; for (savebp = bp = locstak(); loptind < argc; loptind++) { cp = argv[loptind]; if (*cp == '%') { jp = str2job(cmd, cp, 1); itos(jp->j_pid); cp = (char *)numbuf; } while (*cp) { if (bp >= brkend) growstak(bp); *bp++ = *cp++; } if (bp >= brkend) growstak(bp); *bp++ = SPACE; } endstak(bp); execexp(savebp, 0); return; } collectjobs(WNOHANG); if (propts == 0) propts = PR_DFL; if (loptind == argc) { for (jp = joblst; jp; jp = jp->j_nxtp) { if (jp->j_jid) printjob(jp, propts); } } else do printjob(str2job(cmd, argv[loptind++], 1), propts); while (loptind < argc); } /* * the builtin "fg" and "bg" commands */ void sysfgbg(int argc, char *argv[]) { char *cmd = *argv; int fg; if ((flags & jcflg) == 0) failed((unsigned char *)cmd, nojc); fg = eq("fg", cmd); if (*++argv == 0) { struct job *jp; for (jp = jobcur; ; jp = jp->j_curp) { if (jp == 0) failed((unsigned char *)cmd, nocurjob); if (jp->j_jid) break; } restartjob(jp, fg); } else { do { restartjob(str2job(cmd, *argv, 1), fg); } while (*++argv); } } /* * the builtin "wait" commands */ void syswait(argc, argv) int argc; char *argv[]; { char *cmd = *argv; struct job *jp; int stat; int wflags; if ((flags & (monitorflg|jcflg|jcoff)) == (monitorflg|jcflg)) wflags = WUNTRACED; else wflags = 0; if (argc == 1) collectjobs(0); else while (--argc) { if ((jp = str2job(cmd, *++argv, 0)) == 0) continue; if (!(jp->j_flag & J_RUNNING)) continue; if (waitpid(jp->j_pid, &stat, wflags) <= 0) break; (void) statjob(jp, stat, 0, 1); } } static void sigv(char *cmd, int sig, char *args) { int pgrp = 0; int stopme = 0; pid_t id; if (*args == '%') { struct job *jp; jp = str2job(cmd, args, 1); id = jp->j_pgid; pgrp++; } else { if (*args == '-') { pgrp++; args++; } id = 0; do { if (*args < '0' || *args > '9') { failure(cmd, badid); return; } id = (id * 10) + (*args - '0'); } while (*++args); if (id == 0) { id = mypgid; pgrp++; } } if (sig == SIGSTOP) { if (id == mysid || id == mypid && mypgid == mysid) { failure(cmd, loginsh); return; } if (id == mypgid && mypgid != svpgid) { (void) settgid(svtgid, mypgid); setpgid(0, svpgid); stopme++; } } if (pgrp) id = -id; if (kill(id, sig) < 0) { switch (errno) { case EPERM: failure(cmd, eacces); break; case EINVAL: failure(cmd, badsig); break; default: if (pgrp) failure(cmd, nosuchpgid); else failure(cmd, nosuchpid); break; } } else if (sig == SIGTERM && pgrp) (void) kill(id, SIGCONT); if (stopme) { setpgid(0, mypgid); (void) settgid(mypgid, svpgid); } } void sysstop(int argc, char *argv[]) { char *cmd = *argv; if (argc <= 1) { gfailure(usage, stopuse); return; } while (*++argv) sigv(cmd, SIGSTOP, *argv); } void syskill(int argc, char *argv[]) { char *cmd = *argv; int sig = SIGTERM; if (argc == 1) { gfailure(usage, killuse); return; } if (argv[1][0] == '-') { if (argc == 2) { int i; int cnt = 0; char sep = 0; char buf[12]; if (!eq(argv[1], "-l")) { gfailure(usage, killuse); return; } for (i = 1; i < MAXTRAP; i++) { if (sig2str(i, buf) < 0) continue; if (sep) prc_buff(sep); prs_buff(buf); if ((flags & ttyflg) && (++cnt % 10)) sep = TAB; else sep = NL; } prc_buff(NL); return; } if (str2sig(&argv[1][1], &sig)) { failure(cmd, badsig); return; } argv++; } while (*++argv) sigv(cmd, sig, *argv); } void syssusp(int argc, char *argv[]) { if (argc != 1) failed((unsigned char *)argv[0], badopt); sigv(argv[0], SIGSTOP, "0"); } void hupforegnd(void) { struct job *jp; (void) sighold(SIGCHLD); for (jp = joblst; jp != NULL; jp = jp->j_nxtp) { if (jp->j_flag & J_FOREGND) { (void) kill(jp->j_pid, SIGHUP); break; } } (void) sigrelse(SIGCHLD); }