diff options
author | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-01 22:55:37 +0000 |
---|---|---|
committer | Kiyoshi Aman <kiyoshi.aman+adelie@gmail.com> | 2019-02-03 18:22:05 -0600 |
commit | 5b57d28ffb6e1ef86b50f7d05d977826eae89bfe (patch) | |
tree | 154a22fe556b49e6927197336f8bf91b12eacd5e /bin/sh/jobs.c | |
download | userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.gz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.bz2 userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.tar.xz userland-5b57d28ffb6e1ef86b50f7d05d977826eae89bfe.zip |
initial population
Diffstat (limited to 'bin/sh/jobs.c')
-rw-r--r-- | bin/sh/jobs.c | 1812 |
1 files changed, 1812 insertions, 0 deletions
diff --git a/bin/sh/jobs.c b/bin/sh/jobs.c new file mode 100644 index 0000000..d066bb8 --- /dev/null +++ b/bin/sh/jobs.c @@ -0,0 +1,1812 @@ +/* $NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. 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 <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: jobs.c,v 1.103 2018/12/03 02:38:30 kre Exp $"); +#endif +#endif /* not lint */ + +#include <stdio.h> +#include <fcntl.h> +#include <signal.h> +#include <errno.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 +#if OLD_TTY_DRIVER +#include "sgtty.h" +#else +#include <termios.h> +#endif +#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 "var.h" +#include "options.h" +#include "builtins.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" + + +#ifndef WCONTINUED +#define WCONTINUED 0 /* So we can compile on old systems */ +#endif +#ifndef WIFCONTINUED +#define WIFCONTINUED(x) (0) /* ditto */ +#endif + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static int jobs_invalid; /* set in child */ +MKINIT pid_t backgndpid = -1; /* pid of last background process */ +#if JOBS +int initialpgrp; /* pgrp of shell on invocation */ +static int curjob = -1; /* current job */ +#endif +static int ttyfd = -1; + +STATIC void restartjob(struct job *); +STATIC void freejob(struct job *); +STATIC struct job *getjob(const char *, int); +STATIC int dowait(int, struct job *, struct job **); +#define WBLOCK 1 +#define WNOFREE 2 +#define WSILENT 4 +STATIC int jobstatus(const struct job *, int); +STATIC int waitproc(int, struct job *, int *); +STATIC void cmdtxt(union node *); +STATIC void cmdlist(union node *, int); +STATIC void cmdputs(const char *); +inline static void cmdputi(int); + +#ifdef SYSV +STATIC int onsigchild(void); +#endif + +#ifdef OLD_TTY_DRIVER +static pid_t tcgetpgrp(int fd); +static int tcsetpgrp(int fd, pid_t pgrp); + +static pid_t +tcgetpgrp(int fd) +{ + pid_t pgrp; + if (ioctl(fd, TIOCGPGRP, (char *)&pgrp) == -1) + return -1; + else + return pgrp; +} + +static int +tcsetpgrp(int fd, pid_tpgrp) +{ + return ioctl(fd, TIOCSPGRP, (char *)&pgrp); +} +#endif + +static void +ttyfd_change(int from, int to) +{ + if (ttyfd == from) + ttyfd = to; +} + +/* + * 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. + */ + +MKINIT int jobctl; + +void +setjobctl(int on) +{ +#ifdef OLD_TTY_DRIVER + int ldisc; +#endif + + if (on == jobctl || rootshell == 0) + return; + if (on) { +#if defined(FIOCLEX) || defined(FD_CLOEXEC) + int i; + + if (ttyfd != -1) + sh_close(ttyfd); + if ((ttyfd = open("/dev/tty", O_RDWR)) == -1) { + for (i = 0; i < 3; i++) { + if (isatty(i) && (ttyfd = dup(i)) != -1) + break; + } + if (i == 3) + goto out; + } + ttyfd = to_upper_fd(ttyfd); /* Move to a high fd */ + register_sh_fd(ttyfd, ttyfd_change); +#else + out2str("sh: Need FIOCLEX or FD_CLOEXEC to support job control"); + goto out; +#endif + do { /* while we are in the background */ + if ((initialpgrp = tcgetpgrp(ttyfd)) < 0) { + out: + out2str("sh: can't access tty; job control turned off\n"); + mflag = 0; + return; + } + if (initialpgrp == -1) + initialpgrp = getpgrp(); + else if (initialpgrp != getpgrp()) { + killpg(0, SIGTTIN); + continue; + } + } while (0); + +#ifdef OLD_TTY_DRIVER + if (ioctl(ttyfd, TIOCGETD, (char *)&ldisc) < 0 + || ldisc != NTTYDISC) { + out2str("sh: need new tty driver to run job control; job control turned off\n"); + mflag = 0; + return; + } +#endif + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + if (getpgrp() != rootpid && setpgid(0, rootpid) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, rootpid) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } else { /* turning job control off */ + if (getpgrp() != initialpgrp && setpgid(0, initialpgrp) == -1) + error("Cannot set process group (%s) at %d", + strerror(errno), __LINE__); + if (tcsetpgrp(ttyfd, initialpgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + sh_close(ttyfd); + ttyfd = -1; + setsignal(SIGTSTP, 0); + setsignal(SIGTTOU, 0); + setsignal(SIGTTIN, 0); + } + jobctl = on; +} + + +#ifdef mkinit +INCLUDE <stdlib.h> + +SHELLPROC { + backgndpid = -1; +#if JOBS + jobctl = 0; +#endif +} + +#endif + + + +#if JOBS +static int +do_fgcmd(const char *arg_ptr) +{ + struct job *jp; + int i; + int status; + + if (jobs_invalid) + error("No current jobs"); + jp = getjob(arg_ptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + out1fmt("%s", jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + + for (i = 0; i < jp->nprocs; i++) + if (tcsetpgrp(ttyfd, jp->ps[i].pid) != -1) + break; + + if (i >= jp->nprocs) { + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + restartjob(jp); + INTOFF; + status = waitforjob(jp); + INTON; + return status; +} + +int +fgcmd(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argptr); +} + +int +fgcmd_percent(int argc, char **argv) +{ + nextopt(""); + return do_fgcmd(*argv); +} + +static void +set_curjob(struct job *jp, int mode) +{ + struct job *jp1, *jp2; + int i, ji; + + ji = jp - jobtab; + + /* first remove from list */ + if (ji == curjob) + curjob = jp->prev_job; + else { + for (i = 0; i < njobs; i++) { + if (jobtab[i].prev_job != ji) + continue; + jobtab[i].prev_job = jp->prev_job; + break; + } + } + + /* Then re-insert in correct position */ + switch (mode) { + case 0: /* job being deleted */ + jp->prev_job = -1; + break; + case 1: /* newly created job or backgrounded job, + put after all stopped jobs. */ + if (curjob != -1 && jobtab[curjob].state == JOBSTOPPED) { + for (jp1 = jobtab + curjob; ; jp1 = jp2) { + if (jp1->prev_job == -1) + break; + jp2 = jobtab + jp1->prev_job; + if (jp2->state != JOBSTOPPED) + break; + } + jp->prev_job = jp1->prev_job; + jp1->prev_job = ji; + break; + } + /* FALLTHROUGH */ + case 2: /* newly stopped job - becomes curjob */ + jp->prev_job = curjob; + curjob = ji; + break; + } +} + +int +bgcmd(int argc, char **argv) +{ + struct job *jp; + int i; + + nextopt(""); + if (jobs_invalid) + error("No current jobs"); + do { + jp = getjob(*argptr, 0); + if (jp->jobctl == 0) + error("job not created under job control"); + set_curjob(jp, 1); + out1fmt("[%ld] %s", (long)(jp - jobtab + 1), jp->ps[0].cmd); + for (i = 1; i < jp->nprocs; i++) + out1fmt(" | %s", jp->ps[i].cmd ); + out1c('\n'); + flushall(); + restartjob(jp); + } while (*argptr && *++argptr); + return 0; +} + + +STATIC void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + INTOFF; + for (i = 0; i < jp->nprocs; i++) + if (killpg(jp->ps[i].pid, SIGCONT) != -1) + break; + if (i >= jp->nprocs) + error("Cannot continue job (%s)", strerror(errno)); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + VTRACE(DBG_JOBS, ( + "restartjob: [%zu] pid %d status change" + " from %#x (stopped) to -1 (running)\n", + (size_t)(jp-jobtab+1), ps->pid, ps->status)); + ps->status = -1; + jp->state = JOBRUNNING; + } + } + INTON; +} +#endif + +inline static void +cmdputi(int n) +{ + char str[20]; + + fmtstr(str, sizeof str, "%d", n); + cmdputs(str); +} + +static void +showjob(struct output *out, struct job *jp, int mode) +{ + int procno; + int st; + struct procstat *ps; + int col; + char s[64]; + +#if JOBS + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + outfmt(out, "%ld\n", (long)jp->ps->pid); + return; + } +#endif + + procno = jp->nprocs; + if (!procno) + return; + + if (mode & SHOW_PID) + mode |= SHOW_MULTILINE; + + if ((procno > 1 && !(mode & SHOW_MULTILINE)) + || (mode & SHOW_SIGNALLED)) { + /* See if we have more than one status to report */ + ps = jp->ps; + st = ps->status; + do { + int st1 = ps->status; + if (st1 != st) + /* yes - need multi-line output */ + mode |= SHOW_MULTILINE; + if (st1 == -1 || !(mode & SHOW_SIGNALLED) || WIFEXITED(st1)) + continue; + if (WIFSTOPPED(st1) || ((st1 = WTERMSIG(st1) & 0x7f) + && st1 != SIGINT && st1 != SIGPIPE)) + mode |= SHOW_ISSIG; + + } while (ps++, --procno); + procno = jp->nprocs; + } + + if (mode & SHOW_SIGNALLED && !(mode & SHOW_ISSIG)) { + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) { + VTRACE(DBG_JOBS, ("showjob: freeing job %d\n", + jp - jobtab + 1)); + freejob(jp); + } + return; + } + + for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */ + if (ps == jp->ps) + fmtstr(s, 16, "[%ld] %c ", + (long)(jp - jobtab + 1), +#if JOBS + jp - jobtab == curjob ? + '+' : + curjob != -1 && + jp - jobtab == jobtab[curjob].prev_job ? + '-' : +#endif + ' '); + else + fmtstr(s, 16, " " ); + col = strlen(s); + if (mode & SHOW_PID) { + fmtstr(s + col, 16, "%ld ", (long)ps->pid); + col += strlen(s + col); + } + if (ps->status == -1) { + scopy("Running", s + col); + } else if (WIFEXITED(ps->status)) { + st = WEXITSTATUS(ps->status); + if (st) + fmtstr(s + col, 16, "Done(%d)", st); + else + fmtstr(s + col, 16, "Done"); + } else { +#if JOBS + if (WIFSTOPPED(ps->status)) + st = WSTOPSIG(ps->status); + else /* WIFSIGNALED(ps->status) */ +#endif + st = WTERMSIG(ps->status); + scopyn(strsignal(st), s + col, 32); + if (WCOREDUMP(ps->status)) { + col += strlen(s + col); + scopyn(" (core dumped)", s + col, 64 - col); + } + } + col += strlen(s + col); + outstr(s, out); + do { + outc(' ', out); + col++; + } while (col < 30); + outstr(ps->cmd, out); + if (mode & SHOW_MULTILINE) { + if (procno > 0) { + outc(' ', out); + outc('|', out); + } + } else { + while (--procno >= 0) + outfmt(out, " | %s", (++ps)->cmd ); + } + outc('\n', out); + } + flushout(out); + jp->flags &= ~JOBCHANGED; + if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE)) + freejob(jp); +} + +int +jobscmd(int argc, char **argv) +{ + int mode, m; + + mode = 0; + while ((m = nextopt("lp"))) + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + if (!iflag) + mode |= SHOW_NO_FREE; + if (*argptr) + do + showjob(out1, getjob(*argptr,0), mode); + while (*++argptr); + else + showjobs(out1, 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. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(struct output *out, int mode) +{ + int jobno; + struct job *jp; + int silent = 0, gotpid; + + CTRACE(DBG_JOBS, ("showjobs(%x) called\n", mode)); + + /* If not even one one job changed, there is nothing to do */ + gotpid = dowait(WSILENT, NULL, NULL); + while (dowait(WSILENT, NULL, NULL) > 0) + continue; +#ifdef JOBS + /* + * Check if we are not in our foreground group, and if not + * put us in it. + */ + if (mflag && gotpid != -1 && tcgetpgrp(ttyfd) != getpid()) { + if (tcsetpgrp(ttyfd, getpid()) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + VTRACE(DBG_JOBS|DBG_INPUT, ("repaired tty process group\n")); + silent = 1; + } +#endif + + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (!jp->used) + continue; + if (jp->nprocs == 0) { + if (!jobs_invalid) + freejob(jp); + continue; + } + if ((mode & SHOW_CHANGED) && !(jp->flags & JOBCHANGED)) + continue; + if (silent && (jp->flags & JOBCHANGED)) { + jp->flags &= ~JOBCHANGED; + continue; + } + showjob(out, jp, mode); + } +} + +/* + * Mark a job structure as unused. + */ + +STATIC void +freejob(struct job *jp) +{ + INTOFF; + if (jp->ps != &jp->ps0) { + ckfree(jp->ps); + jp->ps = &jp->ps0; + } + jp->nprocs = 0; + jp->used = 0; +#if JOBS + set_curjob(jp, 0); +#endif + INTON; +} + +/* + * Extract the status of a completed job (for $?) + */ +STATIC int +jobstatus(const struct job *jp, int raw) +{ + int status = 0; + int retval; + + if ((jp->flags & JPIPEFAIL) && jp->nprocs) { + int i; + + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status != 0) + status = jp->ps[i].status; + } else + status = jp->ps[jp->nprocs ? jp->nprocs - 1 : 0].status; + + if (raw) + return status; + + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + retval = WSTOPSIG(status) + 128; +#endif + else { + /* XXX: limits number of signals */ + retval = WTERMSIG(status) + 128; + } + + return retval; +} + + + +int +waitcmd(int argc, char **argv) +{ + struct job *job, *last; + int retval; + struct job *jp; + int i; + int any = 0; + int found; + char *pid = NULL, *fpid; + char **arg; + char idstring[20]; + + while ((i = nextopt("np:")) != '\0') { + switch (i) { + case 'n': + any = 1; + break; + case 'p': + if (pid) + error("more than one -p unsupported"); + pid = optionarg; + break; + } + } + + if (pid != NULL) { + if (!validname(pid, '\0', NULL)) + error("invalid name: -p '%s'", pid); + if (unsetvar(pid, 0)) + error("%s readonly", pid); + } + + /* + * If we have forked, and not yet created any new jobs, then + * we have no children, whatever jobtab claims, + * so simply return in that case. + * + * The return code is 127 if we had any pid args (none are found) + * or if we had -n (nothing exited), but 0 for plain old "wait". + */ + if (jobs_invalid) { + CTRACE(DBG_WAIT, ("builtin wait%s%s in child, invalid jobtab\n", + any ? " -n" : "", *argptr ? " pid..." : "")); + return (any || *argptr) ? 127 : 0; + } + + /* clear stray flags left from previous waitcmd */ + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + jp->flags &= ~JOBWANTED; + jp->ref = NULL; + } + + CTRACE(DBG_WAIT, + ("builtin wait%s%s\n", any ? " -n" : "", *argptr ? " pid..." : "")); + + /* + * First, validate the jobnum args, count how many refer to + * (different) running jobs, and if we had -n, and found that one has + * already finished, we return that one. Otherwise remember + * which ones we are looking for (JOBWANTED). + */ + found = 0; + last = NULL; + for (arg = argptr; *arg; arg++) { + last = jp = getjob(*arg, 1); + if (!jp) + continue; + if (jp->ref == NULL) + jp->ref = *arg; + if (any && jp->state == JOBDONE) { + /* + * We just want any of them, and this one is + * ready for consumption, bon apetit ... + */ + retval = jobstatus(jp, 0); + if (pid) + setvar(pid, *arg, 0); + if (!iflag) + freejob(jp); + CTRACE(DBG_WAIT, ("wait -n found %s already done: %d\n", *arg, retval)); + return retval; + } + if (!(jp->flags & JOBWANTED)) { + /* + * It is possible to list the same job several + * times - the obvious "wait 1 1 1" or + * "wait %% %2 102" where job 2 is current and pid 102 + * However many times it is requested, it is found once. + */ + found++; + jp->flags |= JOBWANTED; + } + job = jp; + } + + VTRACE(DBG_WAIT, ("wait %s%s%sfound %d candidates (last %s)\n", + any ? "-n " : "", *argptr ? *argptr : "", + argptr[0] && argptr[1] ? "... " : " ", found, + job ? (job->ref ? job->ref : "<no-arg>") : "none")); + + /* + * If we were given a list of jobnums: + * and none of those exist, then we're done. + */ + if (*argptr && found == 0) + return 127; + + /* + * Otherwise we need to wait for something to complete + * When it does, we check and see if it is one of the + * jobs we're waiting on, and if so, we clean it up. + * If we had -n, then we're done, otherwise we do it all again + * until all we had listed are done, of if there were no + * jobnum args, all are done. + */ + + retval = any || *argptr ? 127 : 0; + fpid = NULL; + for (;;) { + VTRACE(DBG_WAIT, ("wait waiting (%d remain): ", found)); + for (jp = jobtab, i = njobs; --i >= 0; jp++) { + if (jp->used && jp->flags & JOBWANTED && + jp->state == JOBDONE) + break; + if (jp->used && jp->state == JOBRUNNING) + break; + } + if (i < 0) { + CTRACE(DBG_WAIT, ("nothing running (ret: %d) fpid %s\n", + retval, fpid ? fpid : "unset")); + if (pid && fpid) + setvar(pid, fpid, 0); + return retval; + } + VTRACE(DBG_WAIT, ("found @%d/%d state: %d\n", njobs-i, njobs, + jp->state)); + + /* + * There is at least 1 job running, so we can + * safely wait() for something to exit. + */ + if (jp->state == JOBRUNNING) { + job = NULL; + if ((i = dowait(WBLOCK|WNOFREE, NULL, &job)) == -1) + return 128 + lastsig(); + + /* + * one of the job's processes exited, + * but there are more + */ + if (job->state == JOBRUNNING) + continue; + } else + job = jp; /* we want this, and it is done */ + + if (job->flags & JOBWANTED || (*argptr == 0 && any)) { + int rv; + + job->flags &= ~JOBWANTED; /* got it */ + rv = jobstatus(job, 0); + VTRACE(DBG_WAIT, ( + "wanted %d (%s) done: st=%d", i, + job->ref ? job->ref : "", rv)); + if (any || job == last) { + retval = rv; + fpid = job->ref; + + VTRACE(DBG_WAIT, (" save")); + if (pid) { + /* + * don't need fpid unless we are going + * to return it. + */ + if (fpid == NULL) { + /* + * this only happens with "wait -n" + * (that is, no pid args) + */ + snprintf(idstring, sizeof idstring, + "%d", job->ps[ job->nprocs ? + job->nprocs-1 : + 0 ].pid); + fpid = idstring; + } + VTRACE(DBG_WAIT, (" (for %s)", fpid)); + } + } + + if (job->state == JOBDONE) { + VTRACE(DBG_WAIT, (" free")); + freejob(job); + } + + if (any || (found > 0 && --found == 0)) { + if (pid && fpid) + setvar(pid, fpid, 0); + VTRACE(DBG_WAIT, (" return %d\n", retval)); + return retval; + } + VTRACE(DBG_WAIT, ("\n")); + continue; + } + + /* this is to handle "wait" (no args) */ + if (found == 0 && job->state == JOBDONE) { + VTRACE(DBG_JOBS|DBG_WAIT, ("Cleanup: %d\n", i)); + freejob(job); + } + } +} + + +int +jobidcmd(int argc, char **argv) +{ + struct job *jp; + int i; + int pg = 0, onep = 0, job = 0; + + while ((i = nextopt("gjp"))) { + switch (i) { + case 'g': pg = 1; break; + case 'j': job = 1; break; + case 'p': onep = 1; break; + } + } + CTRACE(DBG_JOBS, ("jobidcmd%s%s%s%s %s\n", pg ? " -g" : "", + onep ? " -p" : "", job ? " -j" : "", jobs_invalid ? " [inv]" : "", + *argptr ? *argptr : "<implicit %%>")); + if (pg + onep + job > 1) + error("-g -j and -p options cannot be combined"); + + if (argptr[0] && argptr[1]) + error("usage: jobid [-g|-p|-r] jobid"); + + jp = getjob(*argptr, 0); + if (job) { + out1fmt("%%%zu\n", (size_t)(jp - jobtab + 1)); + return 0; + } + if (pg) { + if (jp->pgrp != 0) { + out1fmt("%ld\n", (long)jp->pgrp); + return 0; + } + return 1; + } + if (onep) { + i = jp->nprocs - 1; + if (i < 0) + return 1; + out1fmt("%ld\n", (long)jp->ps[i].pid); + return 0; + } + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%ld", (long)jp->ps[i].pid); + out1c(++i < jp->nprocs ? ' ' : '\n'); + } + return 0; +} + +int +getjobpgrp(const char *name) +{ + struct job *jp; + + if (jobs_invalid) + error("No such job: %s", name); + jp = getjob(name, 1); + if (jp == 0) + return 0; + return -jp->ps[0].pid; +} + +/* + * Convert a job name to a job structure. + */ + +STATIC struct job * +getjob(const char *name, int noerror) +{ + int jobno = -1; + struct job *jp; + int pid; + int i; + const char *err_msg = "No such job: %s"; + + if (name == NULL) { +#if JOBS + jobno = curjob; +#endif + err_msg = "No current job"; + } else if (name[0] == '%') { + if (is_number(name + 1)) { + jobno = number(name + 1) - 1; + } else if (!name[1] || !name[2]) { + switch (name[1]) { +#if JOBS + case 0: + case '+': + case '%': + jobno = curjob; + err_msg = "No current job"; + break; + case '-': + jobno = curjob; + if (jobno != -1) + jobno = jobtab[jobno].prev_job; + err_msg = "No previous job"; + break; +#endif + default: + goto check_pattern; + } + } else { + struct job *found; + check_pattern: + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (!jp->used || jp->nprocs <= 0) + continue; + if ((name[1] == '?' + && strstr(jp->ps[0].cmd, name + 2)) + || prefix(name + 1, jp->ps[0].cmd)) { + if (found) { + err_msg = "%s: ambiguous"; + found = 0; + break; + } + found = jp; + } + } + if (found) + return found; + } + + } else if (is_number(name)) { + pid = number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + + if (jobno >= 0 && jobno < njobs) { + jp = jobtab + jobno; + if (jp->used) + return jp; + } + if (!noerror) + error(err_msg, name); + return 0; +} + + + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node, int nprocs) +{ + int i; + struct job *jp; + + if (jobs_invalid) { + for (i = njobs, jp = jobtab ; --i >= 0 ; jp++) { + if (jp->used) + freejob(jp); + } + jobs_invalid = 0; + } + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; njobs++) { + jobtab[njobs].used = 0; + jobtab[njobs].prev_job = -1; + } + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = JOBRUNNING; + jp->used = 1; + jp->flags = pipefail ? JPIPEFAIL : 0; + jp->nprocs = 0; + jp->pgrp = 0; +#if JOBS + jp->jobctl = jobctl; + set_curjob(jp, 1); +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + VTRACE(DBG_JOBS, ("makejob(%p, %d)%s returns %%%d\n", (void *)node, + nprocs, (jp->flags&JPIPEFAIL)?" PF":"", jp - jobtab + 1)); + 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). + */ + +int +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + int serrno; + + CTRACE(DBG_JOBS, ("forkshell(%%%d, %p, %d) called\n", + jp - jobtab, n, mode)); + + switch ((pid = fork())) { + case -1: + serrno = errno; + VTRACE(DBG_JOBS, ("Fork failed, errno=%d\n", serrno)); + INTON; + error("Cannot fork (%s)", strerror(serrno)); + break; + case 0: + SHELL_FORKED(); + forkchild(jp, n, mode, 0); + return 0; + default: + return forkparent(jp, n, mode, pid); + } +} + +int +forkparent(struct job *jp, union node *n, int mode, pid_t pid) +{ + int pgrp; + + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + jp->pgrp = pgrp; + /* This can fail because we are doing it in the child also */ + (void)setpgid(pid, pgrp); + } + if (mode == FORK_BG) + backgndpid = pid; /* set $! */ + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd[0] = 0; + if (/* iflag && rootshell && */ n) + commandtext(ps, n); + } + CTRACE(DBG_JOBS, ("In parent shell: child = %d (mode %d)\n",pid,mode)); + return pid; +} + +void +forkchild(struct job *jp, union node *n, int mode, int vforked) +{ + int wasroot; + int pgrp; + const char *devnull = _PATH_DEVNULL; + const char *nullerr = "Can't open %s"; + + wasroot = rootshell; + CTRACE(DBG_JOBS, ("Child shell %d %sforked from %d (mode %d)\n", + getpid(), vforked?"v":"", getppid(), mode)); + + if (!vforked) { + rootshell = 0; + handler = &main_handler; + } + + closescript(vforked); + clear_traps(vforked); +#if JOBS + if (!vforked) + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || 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) { + if (tcsetpgrp(ttyfd, pgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + setsignal(SIGTSTP, vforked); + setsignal(SIGTTOU, vforked); + } else if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT, vforked); + ignoresig(SIGQUIT, vforked); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(devnull, O_RDONLY) != 0) + error(nullerr, devnull); + } + } +#endif + if (wasroot && iflag) { + setsignal(SIGINT, vforked); + setsignal(SIGQUIT, vforked); + setsignal(SIGTERM, vforked); + } + + if (!vforked) + jobs_invalid = 1; +} + +/* + * 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. + */ + +int +waitforjob(struct job *jp) +{ +#if JOBS + int mypgrp = getpgrp(); +#endif + int status; + int st; + + INTOFF; + VTRACE(DBG_JOBS, ("waitforjob(%%%d) called\n", jp - jobtab + 1)); + while (jp->state == JOBRUNNING) { + dowait(WBLOCK, jp, NULL); + } +#if JOBS + if (jp->jobctl) { + if (tcsetpgrp(ttyfd, mypgrp) == -1) + error("Cannot set tty process group (%s) at %d", + strerror(errno), __LINE__); + } + if (jp->state == JOBSTOPPED && curjob != jp - jobtab) + set_curjob(jp, 2); +#endif + status = jobstatus(jp, 1); + + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + + VTRACE(DBG_JOBS, ("waitforjob: job %d, nproc %d, status %d, st %x\n", + jp - jobtab + 1, jp->nprocs, status, st)); +#if JOBS + if (jp->jobctl) { + /* + * 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 (WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + raise(SIGINT); + } +#endif + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + INTON; + return st; +} + + + +/* + * Wait for a process to terminate. + */ + +STATIC int +dowait(int flags, struct job *job, struct job **changed) +{ + int pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + int done; + int stopped; + + VTRACE(DBG_JOBS|DBG_PROCS, ("dowait(%x) called\n", flags)); + + if (changed != NULL) + *changed = NULL; + + do { + pid = waitproc(flags & WBLOCK, job, &status); + VTRACE(DBG_JOBS|DBG_PROCS, ("wait returns pid %d, status %#x\n", + pid, status)); + } while (pid == -1 && errno == EINTR && pendingsigs == 0); + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && + (sp->status==-1 || WIFSTOPPED(sp->status))) { + VTRACE(DBG_JOBS | DBG_PROCS, + ("Job %d: changing status of proc %d from %#x to %#x\n", + jp - jobtab + 1, pid, + sp->status, status)); + if (WIFCONTINUED(status)) { + if (sp->status != -1) + jp->flags |= JOBCHANGED; + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + if (changed != NULL) + *changed = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done ? JOBDONE : JOBSTOPPED; + + if (jp->state != state) { + VTRACE(DBG_JOBS, + ("Job %d: changing state from %d to %d\n", + jp - jobtab + 1, jp->state, state)); + jp->state = state; +#if JOBS + if (done) + set_curjob(jp, 0); +#endif + } + } + } + } + + if (thisjob && + (thisjob->state != JOBRUNNING || thisjob->flags & JOBCHANGED)) { + int mode = 0; + + if (!rootshell || !iflag) + mode = SHOW_SIGNALLED; + if ((job == thisjob && (flags & WNOFREE) == 0) || + job != thisjob) + mode = SHOW_SIGNALLED | SHOW_NO_FREE; + if (mode && (flags & WSILENT) == 0) + showjob(out2, thisjob, mode); + else { + VTRACE(DBG_JOBS, + ("Not printing status, rootshell=%d, job=%p\n", + rootshell, job)); + thisjob->flags |= JOBCHANGED; + } + } + + INTON; + 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 its + * 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, struct job *jp, int *status) +{ +#ifdef BSD + int flags = 0; + +#if JOBS + if (mflag || (jp != NULL && jp->jobctl)) + flags |= WUNTRACED | WCONTINUED; +#endif + if (block == 0) + flags |= WNOHANG; + VTRACE(DBG_WAIT, ("waitproc: doing waitpid(flags=%#x)\n", flags)); + return waitpid(-1, status, flags); +#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 = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning || jobs_invalid) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command). + */ + +STATIC char *cmdnextc; +STATIC int cmdnleft; + +void +commandtext(struct procstat *ps, union node *n) +{ + int len; + + cmdnextc = ps->cmd; + if (iflag || mflag || sizeof(ps->cmd) <= 60) + len = sizeof(ps->cmd); + else if (sizeof ps->cmd <= 400) + len = 50; + else if (sizeof ps->cmd <= 800) + len = 80; + else + len = sizeof(ps->cmd) / 10; + cmdnleft = len; + cmdtxt(n); + if (cmdnleft <= 0) { + char *p = ps->cmd + len - 4; + p[0] = '.'; + p[1] = '.'; + p[2] = '.'; + p[3] = 0; + } else + *cmdnextc = '\0'; + + VTRACE(DBG_JOBS, + ("commandtext: ps->cmd %p, end %p, left %d\n\t\"%s\"\n", + ps->cmd, cmdnextc, cmdnleft, ps->cmd)); +} + + +STATIC void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + const char *p; + int i; + + if (n == NULL || cmdnleft <= 0) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NDNOT: + cmdputs("! "); + /* FALLTHROUGH */ + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + if (n->npipe.backgnd) + cmdputs(" &"); + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + if (n->nif.elsepart) { + cmdputs("; else "); + cmdtxt(n->nif.elsepart); + } + cmdputs("; fi"); + break; + case NWHILE: + cmdputs("while "); + goto until; + case NUNTIL: + cmdputs("until "); + until: + cmdtxt(n->nbinary.ch1); + cmdputs("; do "); + cmdtxt(n->nbinary.ch2); + cmdputs("; done"); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + cmdputs("; do "); + cmdtxt(n->nfor.body); + cmdputs("; done"); + break; + 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); + switch (n->type) { /* switch (not if) for later */ + case NCLISTCONT: + cmdputs(";& "); + break; + default: + cmdputs(";; "); + break; + } + } + cmdputs("esac"); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() { ... }"); + break; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + if (n->ncmd.backgnd) + cmdputs(" &"); + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + p = ">"; i = 1; goto redir; + case NCLOBBER: + p = ">|"; i = 1; goto redir; + case NAPPEND: + p = ">>"; i = 1; goto redir; + case NTOFD: + p = ">&"; i = 1; goto redir; + case NFROM: + p = "<"; i = 0; goto redir; + case NFROMFD: + p = "<&"; i = 0; goto redir; + case NFROMTO: + p = "<>"; i = 0; goto redir; + redir: + if (n->nfile.fd != i) + cmdputi(n->nfile.fd); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd < 0) + cmdputs("-"); + else + cmdputi(n->ndup.dupfd); + } else { + cmdtxt(n->nfile.fname); + } + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + +STATIC void +cmdlist(union node *np, int sep) +{ + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(" "); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(" "); + } +} + + +STATIC void +cmdputs(const char *s) +{ + const char *p, *str = 0; + char c, cc[2] = " "; + char *nextc; + int nleft; + int subtype = 0; + int quoted = 0; + static char vstype[16][4] = { "", "}", "-", "+", "?", "=", + "#", "##", "%", "%%", "}" }; + + p = s; + nextc = cmdnextc; + nleft = cmdnleft; + while (nleft > 0 && (c = *p++) != 0) { + switch (c) { + case CTLNONL: + c = '\0'; + break; + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if (subtype & VSLINENO) { /* undo LINENO hack */ + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#LINENO"; /*}*/ + else + str = "${LINENO"; /*}*/ + while (is_digit(*p)) + p++; + } else if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; /*}*/ + else + str = "${"; /*}*/ + if (!(subtype & VSQUOTE) != !(quoted & 1)) { + quoted ^= 1; + c = '"'; + } else { + c = *str++; + } + break; + case CTLENDVAR: /*{*/ + c = '}'; + if (quoted & 1) + str = "\""; + quoted >>= 1; + subtype = 0; + break; + case CTLBACKQ: + c = '$'; + str = "(...)"; + break; + case CTLBACKQ+CTLQUOTE: + c = '"'; + str = "$(...)\""; + break; + case CTLARI: + c = '$'; + if (*p == ' ') + p++; + str = "(("; /*))*/ + break; + case CTLENDARI: /*((*/ + c = ')'; + str = ")"; + break; + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case CTLQUOTEEND: + quoted >>= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + c = *str++; /*{*/ + if (c != '}') + quoted <<= 1; + else if (*p == CTLENDVAR) + c = *str++; + subtype = 0; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + if (c != '\0') do { /* c == 0 implies nothing in str */ + *nextc++ = c; + } while (--nleft > 0 && str && (c = *str++)); + str = 0; + } + if ((quoted & 1) && nleft) { + *nextc++ = '"'; + nleft--; + } + cmdnleft = nleft; + cmdnextc = nextc; +} |