summaryrefslogtreecommitdiffstats
path: root/src/kash/jobs.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/kash/jobs.c')
-rw-r--r--src/kash/jobs.c1521
1 files changed, 1521 insertions, 0 deletions
diff --git a/src/kash/jobs.c b/src/kash/jobs.c
new file mode 100644
index 0000000..43f359b
--- /dev/null
+++ b/src/kash/jobs.c
@@ -0,0 +1,1521 @@
+/* $NetBSD: jobs.c,v 1.63 2005/06/01 15:41:19 lukem 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.
+ */
+
+#if 0
+#ifndef lint
+static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95";
+#else
+__RCSID("$NetBSD: jobs.c,v 1.63 2005/06/01 15:41:19 lukem Exp $");
+#endif /* not lint */
+#endif
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/types.h>
+
+#include "shell.h"
+#if JOBS && !defined(_MSC_VER)
+# include <termios.h>
+#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 "init.h"
+#include "shinstance.h"
+
+//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(shinstance *, struct job *);
+STATIC void freejob(shinstance *, struct job *);
+STATIC struct job *getjob(shinstance *, const char *, int);
+STATIC shpid dowait(shinstance *, int, struct job *);
+STATIC shpid waitproc(shinstance *, int, struct job *, int *);
+STATIC void cmdtxt(shinstance *, union node *);
+STATIC void cmdlist(shinstance *, union node *, int);
+STATIC void cmdredirlist(shinstance *, union node *, int);
+STATIC void cmdputs(shinstance *, const char *);
+STATIC shpid forkparent(shinstance *psh, struct job *jp, union node *n, int mode, shpid pid);
+STATIC void forkchild(shinstance *psh, shpid pgrp, union node *n, int mode);
+#ifdef KASH_USE_FORKSHELL2
+# ifndef SH_FORKED_MODE
+struct forkshell2args
+{
+ shinstance *psh;
+ int mode;
+ shpid pgrp; /**< The forkchild() pgrp argument (-1 if not in group). */
+ union node *n;
+ void *argp; /**< Points to child callback data following this structure. */
+ int (* child)(shinstance *, union node *, void *);
+ struct stackmark smark; /* do we need this? */
+};
+static int forkshell2_thread(shinstance *psh, void *argp);
+# endif
+#endif
+
+
+/*
+ * 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(shinstance *psh, int on)
+{
+ if (on == psh->jobctl || psh->rootshell == 0)
+ return;
+ if (on) {
+ int err;
+ int i;
+ if (psh->ttyfd != -1)
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ if ((psh->ttyfd = shfile_open(&psh->fdtab, "/dev/tty", O_RDWR, 0)) == -1) {
+ for (i = 0; i < 3; i++) {
+ if (shfile_isatty(&psh->fdtab, i)
+ && (psh->ttyfd = shfile_dup(&psh->fdtab, i)) != -1)
+ break;
+ }
+ if (i == 3)
+ goto out;
+ }
+ /* Move to a high fd */
+ for (i = 10; i > 2; i--) {
+ if ((err = shfile_fcntl(&psh->fdtab, psh->ttyfd, F_DUPFD, (1 << i) - 1)) != -1)
+ break;
+ }
+ if (err != -1) {
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = err;
+ }
+ err = shfile_cloexec(&psh->fdtab, psh->ttyfd, 1);
+ if (err == -1) {
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = -1;
+ goto out;
+ }
+ do { /* while we are in the background */
+ if ((psh->initialpgrp = sh_tcgetpgrp(psh, psh->ttyfd)) < 0) {
+out:
+ out2str(psh, "sh: can't access tty; job control turned off\n");
+ mflag(psh) = 0;
+ return;
+ }
+ if (psh->initialpgrp == -1)
+ psh->initialpgrp = sh_getpgrp(psh);
+ else if (psh->initialpgrp != sh_getpgrp(psh)) {
+ sh_killpg(psh, 0, SIGTTIN);
+ continue;
+ }
+ } while (0);
+
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ setsignal(psh, SIGTTIN);
+ if (sh_getpgid(psh, 0) != psh->rootpid && sh_setpgid(psh, 0, psh->rootpid) == -1)
+ error(psh, "Cannot set process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ if (sh_tcsetpgrp(psh, psh->ttyfd, psh->rootpid) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ } else { /* turning job control off */
+ if (sh_getpgid(psh, 0) != psh->initialpgrp && sh_setpgid(psh, 0, psh->initialpgrp) == -1)
+ error(psh, "Cannot set process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ if (sh_tcsetpgrp(psh, psh->ttyfd, psh->initialpgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ shfile_close(&psh->fdtab, psh->ttyfd);
+ psh->ttyfd = -1;
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ setsignal(psh, SIGTTIN);
+ }
+ psh->jobctl = on;
+}
+
+
+#ifdef mkinit
+INCLUDE <stdlib.h>
+
+SHELLPROC {
+ psh->backgndpid = -1;
+#if JOBS
+ psh->jobctl = 0;
+#endif
+}
+
+#endif
+
+
+
+#if JOBS
+int
+fgcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+ int status;
+
+ nextopt(psh, "");
+ jp = getjob(psh, *psh->argptr, 0);
+ if (jp->jobctl == 0)
+ error(psh, "job not created under job control");
+ out1fmt(psh, "%s", jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(psh, " | %s", jp->ps[i].cmd );
+ out1c(psh, '\n');
+ output_flushall(psh);
+
+ for (i = 0; i < jp->nprocs; i++)
+ if (sh_tcsetpgrp(psh, psh->ttyfd, jp->ps[i].pid) != -1)
+ break;
+
+ if (i >= jp->nprocs) {
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ restartjob(psh, jp);
+ INTOFF;
+ status = waitforjob(psh, jp);
+ INTON;
+ return status;
+}
+
+static void
+set_curjob(shinstance *psh, struct job *jp, int mode)
+{
+ struct job *jp1, *jp2;
+ int i, ji;
+
+ ji = (int)(jp - psh->jobtab);
+
+ /* first remove from list */
+ if (ji == psh->curjob)
+ psh->curjob = jp->prev_job;
+ else {
+ for (i = 0; i < psh->njobs; i++) {
+ if (psh->jobtab[i].prev_job != ji)
+ continue;
+ psh->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 (psh->curjob != -1 && psh->jobtab[psh->curjob].state == JOBSTOPPED) {
+ for (jp1 = psh->jobtab + psh->curjob; ; jp1 = jp2) {
+ if (jp1->prev_job == -1)
+ break;
+ jp2 = psh->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 psh->curjob */
+ jp->prev_job = psh->curjob;
+ psh->curjob = ji;
+ break;
+ }
+}
+
+int
+bgcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+
+ nextopt(psh, "");
+ do {
+ jp = getjob(psh, *psh->argptr, 0);
+ if (jp->jobctl == 0)
+ error(psh, "job not created under job control");
+ set_curjob(psh, jp, 1);
+ out1fmt(psh, "[%ld] %s", (long)(jp - psh->jobtab + 1), jp->ps[0].cmd);
+ for (i = 1; i < jp->nprocs; i++)
+ out1fmt(psh, " | %s", jp->ps[i].cmd );
+ out1c(psh, '\n');
+ output_flushall(psh);
+ restartjob(psh, jp);
+ } while (*psh->argptr && *++psh->argptr);
+ return 0;
+}
+
+
+STATIC void
+restartjob(shinstance *psh, struct job *jp)
+{
+ struct procstat *ps;
+ int i;
+
+ if (jp->state == JOBDONE)
+ return;
+ INTOFF;
+ for (i = 0; i < jp->nprocs; i++)
+ if (sh_killpg(psh, jp->ps[i].pid, SIGCONT) != -1)
+ break;
+ if (i >= jp->nprocs)
+ error(psh, "Cannot continue job (%s)", sh_strerror(psh, errno));
+ for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) {
+ if (WIFSTOPPED(ps->status)) {
+ ps->status = -1;
+ jp->state = JOBRUNNING;
+ }
+ }
+ INTON;
+}
+#endif
+
+static void
+showjob(shinstance *psh, struct output *out, struct job *jp, int mode)
+{
+ int procno;
+ int st;
+ struct procstat *ps;
+ size_t col;
+ char s[64];
+
+#if JOBS
+ if (mode & SHOW_PGID) {
+ /* just output process (group) id of pipeline */
+ outfmt(out, "%" SHPID_PRI "\n", 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)) {
+ TRACE((psh, "showjob: freeing job %d\n", jp - psh->jobtab + 1));
+ freejob(psh, jp);
+ }
+ return;
+ }
+
+ for (ps = jp->ps; --procno >= 0; ps++) { /* for each process */
+ if (ps == jp->ps)
+ fmtstr(s, 16, "[%ld] %c ",
+ (long)(jp - psh->jobtab + 1),
+#if JOBS
+ jp == psh->jobtab + psh->curjob ? '+' :
+ psh->curjob != -1 && jp == psh->jobtab +
+ psh->jobtab[psh->curjob].prev_job ? '-' :
+#endif
+ ' ');
+ else
+ fmtstr(s, 16, " " );
+ col = strlen(s);
+ if (mode & SHOW_PID) {
+ fmtstr(s + col, 16, "%" SHPID_PRI " ", 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 {
+ const char *pszSigNm;
+#if JOBS
+ if (WIFSTOPPED(ps->status))
+ st = WSTOPSIG(ps->status);
+ else /* WIFSIGNALED(ps->status) */
+#endif
+ st = WTERMSIG(ps->status);
+ st &= 0x7f;
+ pszSigNm = st < NSIG ? strsignal(st) : NULL;
+ if (pszSigNm)
+ scopyn(pszSigNm, s + col, 32);
+ else
+ fmtstr(s + col, 16, "Signal %d", st);
+ 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->changed = 0;
+ if (jp->state == JOBDONE && !(mode & SHOW_NO_FREE))
+ freejob(psh, jp);
+}
+
+
+int
+jobscmd(shinstance *psh, int argc, char **argv)
+{
+ int mode, m;
+ int sv = psh->jobs_invalid;
+
+ psh->jobs_invalid = 0;
+ mode = 0;
+ while ((m = nextopt(psh, "lp")))
+ if (m == 'l')
+ mode = SHOW_PID;
+ else
+ mode = SHOW_PGID;
+ if (*psh->argptr)
+ do
+ showjob(psh, psh->out1, getjob(psh, *psh->argptr,0), mode);
+ while (*++psh->argptr);
+ else
+ showjobs(psh, psh->out1, mode);
+ psh->jobs_invalid = sv;
+ 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(shinstance *psh, struct output *out, int mode)
+{
+ int jobno;
+ struct job *jp;
+ int silent = 0;
+ shpid gotpid;
+
+ TRACE((psh, "showjobs(%x) called\n", mode));
+
+ /* If not even one one job changed, there is nothing to do */
+ gotpid = dowait(psh, 0, NULL);
+ while (dowait(psh, 0, NULL) > 0)
+ continue;
+#ifdef JOBS
+ /*
+ * Check if we are not in our foreground group, and if not
+ * put us in it.
+ */
+ if (mflag(psh) && gotpid != -1 && sh_tcgetpgrp(psh, psh->ttyfd) != sh_getpid(psh)) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, sh_getpid(psh)) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ TRACE((psh, "repaired tty process group\n"));
+ silent = 1;
+ }
+#endif
+ if (psh->jobs_invalid)
+ return;
+
+ for (jobno = 1, jp = psh->jobtab ; jobno <= psh->njobs ; jobno++, jp++) {
+ if (!jp->used)
+ continue;
+ if (jp->nprocs == 0) {
+ freejob(psh, jp);
+ continue;
+ }
+ if ((mode & SHOW_CHANGED) && !jp->changed)
+ continue;
+ if (silent && jp->changed) {
+ jp->changed = 0;
+ continue;
+ }
+ showjob(psh, out, jp, mode);
+ }
+}
+
+/*
+ * Mark a job structure as unused.
+ */
+
+STATIC void
+freejob(shinstance *psh, struct job *jp)
+{
+ INTOFF;
+ if (jp->ps != &jp->ps0) {
+ ckfree(psh, jp->ps);
+ jp->ps = &jp->ps0;
+ }
+ jp->nprocs = 0;
+ jp->used = 0;
+#if JOBS
+ set_curjob(psh, jp, 0);
+#endif
+ INTON;
+}
+
+
+
+int
+waitcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *job;
+ int status, retval;
+ struct job *jp;
+
+ nextopt(psh, "");
+
+ if (!*psh->argptr) {
+ /* wait for all jobs */
+ jp = psh->jobtab;
+ if (psh->jobs_invalid)
+ return 0;
+ for (;;) {
+ if (jp >= psh->jobtab + psh->njobs) {
+ /* no running procs */
+ return 0;
+ }
+ if (!jp->used || jp->state != JOBRUNNING) {
+ jp++;
+ continue;
+ }
+ if (dowait(psh, 1, (struct job *)NULL) == -1)
+ return 128 + SIGINT;
+ jp = psh->jobtab;
+ }
+ }
+
+ retval = 127; /* XXXGCC: -Wuninitialized */
+ for (; *psh->argptr; psh->argptr++) {
+ job = getjob(psh, *psh->argptr, 1);
+ if (!job) {
+ retval = 127;
+ continue;
+ }
+ /* loop until process terminated or stopped */
+ while (job->state == JOBRUNNING) {
+ if (dowait(psh, 1, (struct job *)NULL) == -1)
+ return 128 + SIGINT;
+ }
+ status = job->ps[job->nprocs].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;
+ }
+ if (!iflag(psh))
+ freejob(psh, job);
+ }
+ return retval;
+}
+
+
+
+int
+jobidcmd(shinstance *psh, int argc, char **argv)
+{
+ struct job *jp;
+ int i;
+
+ nextopt(psh, "");
+ jp = getjob(psh, *psh->argptr, 0);
+ for (i = 0 ; i < jp->nprocs ; ) {
+ out1fmt(psh, "%" SHPID_PRI, jp->ps[i].pid);
+ out1c(psh, ++i < jp->nprocs ? ' ' : '\n');
+ }
+ return 0;
+}
+
+shpid
+getjobpgrp(shinstance *psh, const char *name)
+{
+ struct job *jp;
+
+ jp = getjob(psh, name, 1);
+ if (jp == 0)
+ return 0;
+ return -jp->ps[0].pid;
+}
+
+/*
+ * Convert a job name to a job structure.
+ */
+
+STATIC struct job *
+getjob(shinstance *psh, 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 = psh->curjob;
+#endif
+ err_msg = "No current job";
+ } else if (name[0] == '%') {
+ if (is_number(name + 1)) {
+ jobno = number(psh, name + 1) - 1;
+ } else if (!name[2]) {
+ switch (name[1]) {
+#if JOBS
+ case 0:
+ case '+':
+ case '%':
+ jobno = psh->curjob;
+ err_msg = "No current job";
+ break;
+ case '-':
+ jobno = psh->curjob;
+ if (jobno != -1)
+ jobno = psh->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 = psh->jobtab, i = psh->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(psh, name);
+ for (jp = psh->jobtab, i = psh->njobs ; --i >= 0 ; jp++) {
+ if (jp->used && jp->nprocs > 0
+ && jp->ps[jp->nprocs - 1].pid == pid)
+ return jp;
+ }
+ }
+
+ if (!psh->jobs_invalid && jobno >= 0 && jobno < psh->njobs) {
+ jp = psh->jobtab + jobno;
+ if (jp->used)
+ return jp;
+ }
+ if (!noerror)
+ error(psh, err_msg, name);
+ return 0;
+}
+
+
+
+/*
+ * Return a new job structure,
+ */
+
+struct job *
+makejob(shinstance *psh, union node *node, int nprocs)
+{
+ int i;
+ struct job *jp;
+
+ if (psh->jobs_invalid) {
+ for (i = psh->njobs, jp = psh->jobtab ; --i >= 0 ; jp++) {
+ if (jp->used)
+ freejob(psh, jp);
+ }
+ psh->jobs_invalid = 0;
+ }
+
+ for (i = psh->njobs, jp = psh->jobtab ; ; jp++) {
+ if (--i < 0) {
+ INTOFF;
+ if (psh->njobs == 0) {
+ psh->jobtab = ckmalloc(psh, 4 * sizeof psh->jobtab[0]);
+ } else {
+ jp = ckmalloc(psh, (psh->njobs + 4) * sizeof psh->jobtab[0]);
+ memcpy(jp, psh->jobtab, psh->njobs * sizeof jp[0]);
+ /* Relocate `ps' pointers */
+ for (i = 0; i < psh->njobs; i++)
+ if (jp[i].ps == &psh->jobtab[i].ps0)
+ jp[i].ps = &jp[i].ps0;
+ ckfree(psh, psh->jobtab);
+ psh->jobtab = jp;
+ }
+ jp = psh->jobtab + psh->njobs;
+ for (i = 4 ; --i >= 0 ; psh->jobtab[psh->njobs++].used = 0) { /*empty*/ }
+ INTON;
+ break;
+ }
+ if (jp->used == 0)
+ break;
+ }
+ INTOFF;
+ jp->state = JOBRUNNING;
+ jp->used = 1;
+ jp->changed = 0;
+ jp->nprocs = 0;
+#if JOBS
+ jp->jobctl = psh->jobctl;
+ set_curjob(psh, jp, 1);
+#endif
+ if (nprocs > 1) {
+ jp->ps = ckmalloc(psh, nprocs * sizeof (struct procstat));
+ } else {
+ jp->ps = &jp->ps0;
+ }
+ INTON;
+ TRACE((psh, "makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+ jp - psh->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).
+ */
+
+#ifndef KASH_USE_FORKSHELL2
+shpid
+forkshell(shinstance *psh, struct job *jp, union node *n, int mode)
+{
+ int pid;
+
+ TRACE((psh, "forkshell(%%%d, %p, %d) called\n", jp - psh->jobtab, n, mode));
+ switch ((pid = sh_fork(psh))) {
+ case -1:
+ TRACE((psh, "Fork failed, errno=%d\n", errno));
+ INTON;
+ error(psh, "Cannot fork");
+ return -1; /* won't get here */
+ case 0:
+ forkchild(psh, jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid, n, mode);
+ return 0;
+ default:
+ return forkparent(psh, jp, n, mode, pid);
+ }
+}
+#else /* KASH_USE_FORKSHELL2 */
+shpid
+forkshell2(shinstance *psh, struct job *jp, union node *n, int mode,
+ int (*child)(struct shinstance *, void *, union node *),
+ union node *nchild, void *argp, size_t arglen,
+ void (*setupchild)(struct shinstance *, struct shinstance *, void *))
+{
+ shpid pid;
+
+# ifdef SH_FORKED_MODE
+ /*
+ * fork variant.
+ */
+ pid = sh_fork(psh);
+ if (pid == 0)
+ {
+ /* child */
+ forkchild(psh, jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid, n, mode);
+ sh__exit(psh, child(psh, nchild, argp));
+ return 0;
+ }
+
+ /* parent */
+ if (pid != -1)
+ return forkparent(psh, jp, n, mode, pid);
+ TRACE((psh, "Fork failed, errno=%d\n", errno));
+ INTON;
+ (void)arglen;
+ (void)setupchild;
+ error(psh, "Cannot fork");
+ return -1; /* won't get here */
+
+# else
+ /*
+ * Clone the shell and start a thread to service the subshell.
+ */
+ struct shinstance *pshchild;
+
+ TRACE((psh, "forkshell2(%%%d, %p, %d, %p, %p, %p, %d) called\n",
+ jp - psh->jobtab, n, mode, child, nchild, argp, (int)arglen));
+
+ pshchild = sh_create_child_shell(psh);
+ if (pshchild) {
+ /* pack arguments */
+ struct forkshell2args *args = (struct forkshell2args *)sh_calloc(pshchild, sizeof(*args) + arglen, 1);
+ args->psh = pshchild;
+ args->argp = memcpy(args + 1, argp, arglen);
+ args->child = child;
+ args->mode = mode;
+ args->pgrp = jp == NULL || jp->nprocs == 0 ? -1 : jp->ps[0].pid;
+ setstackmark(pshchild, &args->smark);
+ args->n = copyparsetree(pshchild, n);
+ if (setupchild)
+ setupchild(pshchild, psh, args->argp);
+
+ /* start the thread */
+ pid = sh_thread_start(psh, pshchild, forkshell2_thread, args);
+ if (pid >= 0)
+ return forkparent(psh, jp, n, mode, pid);
+ error(psh, "sh_start_child_thread failed (%d)!", (int)pid);
+ }
+ else
+ error(psh, "sh_create_child_shell failed!");
+ return -1;
+# endif
+}
+#endif
+
+STATIC shpid
+forkparent(shinstance *psh, struct job *jp, union node *n, int mode, shpid pid)
+{
+ shpid pgrp;
+
+ if (psh->rootshell && mode != FORK_NOJOB && mflag(psh)) {
+ if (jp == NULL || jp->nprocs == 0)
+ pgrp = pid;
+ else
+ pgrp = jp->ps[0].pid;
+ /* This can fail because we are doing it in the child also */
+ (void)sh_setpgid(psh, pid, pgrp);
+ }
+ if (mode == FORK_BG)
+ psh->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(psh, ps, n);
+ }
+ TRACE((psh, "In parent shell: child = %" SHPID_PRI "\n", pid));
+ return pid;
+}
+
+STATIC void
+forkchild(shinstance *psh, shpid pgrp, union node *n, int mode)
+{
+ int wasroot;
+ const char *devnull = _PATH_DEVNULL;
+ const char *nullerr = "Can't open %s";
+
+ wasroot = psh->rootshell;
+ TRACE((psh, "Child shell %" SHPID_PRI "\n", sh_getpid(psh)));
+ psh->rootshell = 0;
+
+ closescript(psh);
+ clear_traps(psh);
+#if JOBS
+ psh->jobctl = 0; /* do job control only in root shell */
+ if (wasroot && mode != FORK_NOJOB && mflag(psh)) {
+ if (pgrp == -1)
+ pgrp = sh_getpid(psh);
+ /* This can fail because we are doing it in the parent also.
+ And we must ignore SIGTTOU at this point or we'll be stopped! */
+ (void)sh_setpgid(psh, 0, pgrp);
+ if (mode == FORK_FG) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, pgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ setsignal(psh, SIGTSTP);
+ setsignal(psh, SIGTTOU);
+ } else
+#endif
+ if (mode == FORK_BG) {
+ ignoresig(psh, SIGINT);
+ ignoresig(psh, SIGQUIT);
+ if (pgrp == -1 && ! fd0_redirected_p(psh)) {
+ shfile_close(&psh->fdtab, 0);
+ if (shfile_open(&psh->fdtab, devnull, O_RDONLY, 0) != 0)
+ error(psh, nullerr, devnull);
+ }
+ }
+ if (wasroot && iflag(psh)) {
+ setsignal(psh, SIGINT);
+ setsignal(psh, SIGQUIT);
+ setsignal(psh, SIGTERM);
+ }
+
+ psh->jobs_invalid = 1;
+}
+
+#if defined(KASH_USE_FORKSHELL2) && !defined(SH_FORKED_MODE)
+/** thread procedure */
+static int forkshell2_thread(shinstance *psh, void *argp)
+{
+ struct forkshell2args * volatile volargs = (struct forkshell2args *)argp;
+ struct jmploc jmp;
+ TRACE2((psh, "forkshell2_thread:\n"));
+
+ if (setjmp(jmp.loc) == 0) {
+ struct forkshell2args * const args = volargs;
+
+ forkchild(psh, args->pgrp, args->n, args->mode);
+
+ psh->handler = &jmp;
+ return args->child(psh, args->n, args->argp);
+ }
+
+ /*
+ * (We longjmp'ed here.)
+ *
+ * This is copied from main() and simplified:
+ */
+ for (;;) {
+ psh = volargs->psh; /* longjmp paranoia */
+
+ if (psh->exception != EXSHELLPROC) {
+ if (psh->exception == EXEXEC)
+ psh->exitstatus = psh->exerrno;
+ else if (psh->exception == EXERROR)
+ psh->exitstatus = 2;
+ TRACE2((psh, "forkshell2_thread: exception=%d -> exitshell2(,%d)\n", psh->exception, psh->exitstatus));
+ return exitshell2(psh, psh->exitstatus);
+ }
+
+ /* EXSHELLPROC - tryexec gets us here and it wants to run a program
+ hoping (?) it's a shell script. We must reset the shell state and
+ turn ourselves into a root shell before doing so. */
+ TRACE2((psh, "forkshell2_thread: exception=EXSHELLPROC\n"));
+ psh->rootpid = /*getpid()*/ psh->pid;
+ psh->rootshell = 1;
+ psh->minusc = NULL;
+
+ reset(psh);
+ popstackmark(psh, &volargs->smark);
+
+ FORCEINTON; /* enable interrupts */
+
+ /* state3: */
+ if (sflag(psh) == 0) {
+# ifdef SIGTSTP
+ static int sigs[] = { SIGINT, SIGQUIT, SIGHUP, SIGPIPE, SIGTSTP };
+# else
+ static int sigs[] = { SIGINT, SIGQUIT, SIGHUP, SIGPIPE };
+# endif
+ unsigned i;
+ for (i = 0; i < K_ELEMENTS(sigs); i++)
+ setsignal(psh, sigs[i]);
+ }
+
+ if (setjmp(jmp.loc) == 0) {
+ psh->handler = &jmp;
+ cmdloop(psh, 1);
+ TRACE2((psh, "forkshell2_thread: cmdloop returned -> exitshell2(,%d)\n", psh->exitstatus));
+ return exitshell2(psh, psh->exitstatus);
+ }
+ }
+}
+#endif
+
+
+/*
+ * 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(shinstance *psh, struct job *jp)
+{
+#if JOBS
+ shpid mypgrp = sh_getpgrp(psh);
+#endif
+ int status;
+ int st;
+
+ INTOFF;
+ TRACE((psh, "waitforjob(%%%d) called\n", jp - psh->jobtab + 1));
+ while (jp->state == JOBRUNNING) {
+ dowait(psh, 1, jp);
+ }
+#if JOBS
+ if (jp->jobctl) {
+ if (sh_tcsetpgrp(psh, psh->ttyfd, mypgrp) == -1)
+ error(psh, "Cannot set tty process group (%s) at %d",
+ sh_strerror(psh, errno), __LINE__);
+ }
+ if (jp->state == JOBSTOPPED && psh->curjob != jp - psh->jobtab)
+ set_curjob(psh, jp, 2);
+#endif
+ status = jp->ps[jp->nprocs - 1].status;
+ /* 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;
+ TRACE((psh, "waitforjob: job %d, nproc %d, status %x, st %x\n",
+ jp - psh->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)
+ sh_raise_sigint(psh);/*raise(SIGINT);*/
+ }
+#endif
+ if (! JOBS || jp->state == JOBDONE)
+ freejob(psh, jp);
+ INTON;
+ return st;
+}
+
+
+
+/*
+ * Wait for a process to terminate.
+ */
+
+STATIC shpid
+dowait(shinstance *psh, int block, struct job *job)
+{
+ shpid pid;
+ int status;
+ struct procstat *sp;
+ struct job *jp;
+ struct job *thisjob;
+ int done;
+ int stopped;
+
+ TRACE((psh, "dowait(%d) called\n", block));
+ do {
+ pid = waitproc(psh, block, job, &status);
+ TRACE((psh, "wait returns pid %" SHPID_PRI ", status %d\n", pid, status));
+ } while (pid == -1 && errno == EINTR && psh->gotsig[SIGINT - 1] == 0);
+ if (pid <= 0)
+ return pid;
+ INTOFF;
+ thisjob = NULL;
+ for (jp = psh->jobtab ; jp < psh->jobtab + psh->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) {
+ TRACE((psh, "Job %d: changing status of proc %" SHPID_PRI " from 0x%x to 0x%x\n",
+ jp - psh->jobtab + 1, pid, sp->status, status));
+ sp->status = status;
+ thisjob = 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) {
+ TRACE((psh, "Job %d: changing state from %d to %d\n", jp - psh->jobtab + 1, jp->state, state));
+ jp->state = state;
+#if JOBS
+ if (done)
+ set_curjob(psh, jp, 0);
+#endif
+ }
+ }
+ }
+ }
+
+ if (thisjob && thisjob->state != JOBRUNNING) {
+ int mode = 0;
+ if (!psh->rootshell || !iflag(psh))
+ mode = SHOW_SIGNALLED;
+ if (job == thisjob)
+ mode = SHOW_SIGNALLED | SHOW_NO_FREE;
+ if (mode)
+ showjob(psh, psh->out2, thisjob, mode);
+ else {
+ TRACE((psh, "Not printing status, rootshell=%d, job=%p\n",
+ psh->rootshell, job));
+ thisjob->changed = 1;
+ }
+ }
+
+ 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.
+ */
+STATIC shpid
+waitproc(shinstance *psh, int block, struct job *jp, int *status)
+{
+ int flags = 0;
+
+#if JOBS
+ if (jp != NULL && jp->jobctl)
+ flags |= WUNTRACED;
+#endif
+ if (block == 0)
+ flags |= WNOHANG;
+ return sh_waitpid(psh, -1, status, flags);
+}
+
+/*
+ * return 1 if there are stopped jobs, otherwise 0
+ */
+//int job_warning = 0;
+int
+stoppedjobs(shinstance *psh)
+{
+ int jobno;
+ struct job *jp;
+
+ if (psh->job_warning || psh->jobs_invalid)
+ return (0);
+ for (jobno = 1, jp = psh->jobtab; jobno <= psh->njobs; jobno++, jp++) {
+ if (jp->used == 0)
+ continue;
+ if (jp->state == JOBSTOPPED) {
+ out2str(psh, "You have stopped jobs.\n");
+ psh->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(shinstance *psh, struct procstat *ps, union node *n)
+{
+ int len;
+
+ psh->cmdnextc = ps->cmd;
+ if (iflag(psh) || mflag(psh) || sizeof(ps->cmd) < 100)
+ len = sizeof(ps->cmd);
+ else
+ len = sizeof(ps->cmd) / 10;
+ psh->cmdnleft = len;
+ cmdtxt(psh, n);
+ if (psh->cmdnleft <= 0) {
+ char *p = ps->cmd + len - 4;
+ p[0] = '.';
+ p[1] = '.';
+ p[2] = '.';
+ p[3] = 0;
+ } else
+ *psh->cmdnextc = '\0';
+ TRACE((psh, "commandtext: ps->cmd %x, end %x, left %d\n\t\"%s\"\n",
+ ps->cmd, psh->cmdnextc, psh->cmdnleft, ps->cmd));
+}
+
+
+STATIC void
+cmdtxt(shinstance *psh, union node *n)
+{
+ union node *np;
+ struct nodelist *lp;
+ const char *p;
+ int i;
+ char s[2];
+
+ if (n == NULL || psh->cmdnleft <= 0)
+ return;
+ switch (n->type) {
+ case NSEMI:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, "; ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NAND:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, " && ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NOR:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, " || ");
+ cmdtxt(psh, n->nbinary.ch2);
+ break;
+ case NPIPE:
+ for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) {
+ cmdtxt(psh, lp->n);
+ if (lp->next)
+ cmdputs(psh, " | ");
+ }
+ break;
+ case NSUBSHELL:
+ cmdputs(psh, "(");
+ cmdtxt(psh, n->nredir.n);
+ cmdputs(psh, ")");
+ break;
+ case NREDIR:
+ case NBACKGND:
+ cmdtxt(psh, n->nredir.n);
+ break;
+ case NIF:
+ cmdputs(psh, "if ");
+ cmdtxt(psh, n->nif.test);
+ cmdputs(psh, "; then ");
+ cmdtxt(psh, n->nif.ifpart);
+ if (n->nif.elsepart) {
+ cmdputs(psh, "; else ");
+ cmdtxt(psh, n->nif.elsepart);
+ }
+ cmdputs(psh, "; fi");
+ break;
+ case NWHILE:
+ cmdputs(psh, "while ");
+ goto until;
+ case NUNTIL:
+ cmdputs(psh, "until ");
+until:
+ cmdtxt(psh, n->nbinary.ch1);
+ cmdputs(psh, "; do ");
+ cmdtxt(psh, n->nbinary.ch2);
+ cmdputs(psh, "; done");
+ break;
+ case NFOR:
+ cmdputs(psh, "for ");
+ cmdputs(psh, n->nfor.var);
+ cmdputs(psh, " in ");
+ cmdlist(psh, n->nfor.args, 1);
+ cmdputs(psh, "; do ");
+ cmdtxt(psh, n->nfor.body);
+ cmdputs(psh, "; done");
+ break;
+ case NCASE:
+ cmdputs(psh, "case ");
+ cmdputs(psh, n->ncase.expr->narg.text);
+ cmdputs(psh, " in ");
+ for (np = n->ncase.cases; np; np = np->nclist.next) {
+ cmdtxt(psh, np->nclist.pattern);
+ cmdputs(psh, ") ");
+ cmdtxt(psh, np->nclist.body);
+ cmdputs(psh, ";; ");
+ }
+ cmdputs(psh, "esac");
+ break;
+ case NDEFUN:
+ cmdputs(psh, n->narg.text);
+ cmdputs(psh, "() { ... }");
+ break;
+ case NCMD:
+ cmdlist(psh, n->ncmd.args, 1);
+ cmdredirlist(psh, n->ncmd.redirect, 0);
+ break;
+ case NARG:
+ cmdputs(psh, 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) {
+ s[0] = n->nfile.fd + '0';
+ s[1] = '\0';
+ cmdputs(psh, s);
+ }
+ cmdputs(psh, p);
+ if (n->type == NTOFD || n->type == NFROMFD) {
+ s[0] = n->ndup.dupfd + '0';
+ s[1] = '\0';
+ cmdputs(psh, s);
+ } else {
+ cmdtxt(psh, n->nfile.fname);
+ }
+ break;
+ case NHERE:
+ case NXHERE:
+ cmdputs(psh, "<<...");
+ break;
+ default:
+ cmdputs(psh, "???");
+ break;
+ }
+}
+
+STATIC void
+cmdlist(shinstance *psh, union node *np, int sep)
+{
+ for (; np; np = np->narg.next) {
+ if (!sep)
+ cmdputs(psh, " ");
+ cmdtxt(psh, np);
+ if (sep && np->narg.next)
+ cmdputs(psh, " ");
+ }
+}
+
+STATIC void
+cmdredirlist(shinstance *psh, union node *np, int sep)
+{
+ for (; np; np = np->nfile.next) {
+ if (!sep)
+ cmdputs(psh, " ");
+ cmdtxt(psh, np);
+ if (sep && np->nfile.next)
+ cmdputs(psh, " ");
+ }
+}
+
+
+STATIC void
+cmdputs(shinstance *psh, 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 = psh->cmdnextc;
+ nleft = psh->cmdnleft;
+ while (nleft > 0 && (c = *p++) != 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
+ c = *str++;
+ break;
+ case CTLENDVAR:
+ if (quoted & 1) {
+ c = '"';
+ str = "}";
+ } else
+ c = '}';
+ quoted >>= 1;
+ subtype = 0;
+ break;
+ case CTLBACKQ:
+ c = '$';
+ str = "(...)";
+ break;
+ case CTLBACKQ+CTLQUOTE:
+ c = '"';
+ str = "$(...)\"";
+ break;
+ case CTLARI:
+ c = '$';
+ str = "((";
+ break;
+ case CTLENDARI:
+ c = ')';
+ str = ")";
+ break;
+ case CTLQUOTEMARK:
+ quoted ^= 1;
+ c = '"';
+ break;
+ case '=':
+ if (subtype == 0)
+ break;
+ str = vstype[subtype & VSTYPE];
+ if (subtype & VSNUL)
+ c = ':';
+ else
+ c = *str++;
+ if (c != '}')
+ quoted <<= 1;
+ break;
+ case '\'':
+ case '\\':
+ case '"':
+ case '$':
+ /* These can only happen inside quotes */
+ cc[0] = c;
+ str = cc;
+ c = '\\';
+ break;
+ default:
+ break;
+ }
+ do {
+ *nextc++ = c;
+ } while (--nleft > 0 && str && (c = *str++));
+ str = 0;
+ }
+ if ((quoted & 1) && nleft) {
+ *nextc++ = '"';
+ nleft--;
+ }
+ psh->cmdnleft = nleft;
+ psh->cmdnextc = nextc;
+}