/* Forward decls for varinit[] */
#if ENABLE_LOCALE_SUPPORT
-static void change_lc_all(const char *value);
-static void change_lc_ctype(const char *value);
+static void
+change_lc_all(const char *value)
+{
+ if (value && *value != '\0')
+ setlocale(LC_ALL, value);
+}
+static void
+change_lc_ctype(const char *value)
+{
+ if (value && *value != '\0')
+ setlocale(LC_CTYPE, value);
+}
#endif
#if ENABLE_ASH_MAIL
static void chkmail(void);
#endif
-/* jobs.h */
-
-/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
-#define FORK_FG 0
-#define FORK_BG 1
-#define FORK_NOJOB 2
-
-/* mode flags for showjob(s) */
-#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */
-#define SHOW_PID 0x04 /* include process pid */
-#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */
-
-
-/*
- * A job structure contains information about a job. A job is either a
- * single process or a set of processes contained in a pipeline. In the
- * latter case, pidlist will be non-NULL, and will point to a -1 terminated
- * array of pids.
- */
-
-struct procstat {
- pid_t pid; /* process id */
- int status; /* last process status from wait() */
- char *cmd; /* text of command being run */
-};
-
-struct job {
- struct procstat ps0; /* status of process */
- struct procstat *ps; /* status or processes when more than one */
-#if JOBS
- int stopstatus; /* status of a stopped job */
-#endif
- uint32_t
- nprocs: 16, /* number of processes */
- state: 8,
-#define JOBRUNNING 0 /* at least one proc running */
-#define JOBSTOPPED 1 /* all procs are stopped */
-#define JOBDONE 2 /* all procs are completed */
-#if JOBS
- sigint: 1, /* job was killed by SIGINT */
- jobctl: 1, /* job running under job control */
-#endif
- waited: 1, /* true if this entry has been waited for */
- used: 1, /* true if this entry is in used */
- changed: 1; /* true if status has changed */
- struct job *prev_job; /* previous job */
-};
-
-static pid_t backgndpid; /* pid of last background process */
-static int job_warning; /* user was warned about stopped jobs */
-#if JOBS
-static int jobctl; /* true if doing job control */
-#endif
-
-static struct job *makejob(union node *, int);
-static int forkshell(struct job *, union node *, int);
-static int waitforjob(struct job *);
-
-#if ! JOBS
-#define setjobctl(on) /* do nothing */
-#else
-static void setjobctl(int);
-static void showjobs(FILE *, int);
-#endif
-
-
/* main.h */
static void readcmdfile(char *);
#endif /* ASH_ALIAS */
-/* ============ Routines to expand arguments to commands
- *
- * We have to deal with backquotes, shell variables, and file metacharacters.
- */
+/* ============ jobs.c */
+
+/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */
+#define FORK_FG 0
+#define FORK_BG 1
+#define FORK_NOJOB 2
+
+/* mode flags for showjob(s) */
+#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */
+#define SHOW_PID 0x04 /* include process pid */
+#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */
-/*
- * expandarg flags
- */
-#define EXP_FULL 0x1 /* perform word splitting & file globbing */
-#define EXP_TILDE 0x2 /* do normal tilde expansion */
-#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
-#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
-#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
-#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */
-#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
-#define EXP_WORD 0x80 /* expand word in parameter expansion */
-#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */
-/*
- * _rmescape() flags
- */
-#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */
-#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */
-#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */
-#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */
-#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
/*
- * Structure specifying which parts of the string should be searched
- * for IFS characters.
+ * A job structure contains information about a job. A job is either a
+ * single process or a set of processes contained in a pipeline. In the
+ * latter case, pidlist will be non-NULL, and will point to a -1 terminated
+ * array of pids.
*/
-struct ifsregion {
- struct ifsregion *next; /* next region in list */
- int begoff; /* offset of start of region */
- int endoff; /* offset of end of region */
- int nulonly; /* search for nul bytes only */
+
+struct procstat {
+ pid_t pid; /* process id */
+ int status; /* last process status from wait() */
+ char *cmd; /* text of command being run */
};
-struct arglist {
- struct strlist *list;
- struct strlist **lastp;
+struct job {
+ struct procstat ps0; /* status of process */
+ struct procstat *ps; /* status or processes when more than one */
+#if JOBS
+ int stopstatus; /* status of a stopped job */
+#endif
+ uint32_t
+ nprocs: 16, /* number of processes */
+ state: 8,
+#define JOBRUNNING 0 /* at least one proc running */
+#define JOBSTOPPED 1 /* all procs are stopped */
+#define JOBDONE 2 /* all procs are completed */
+#if JOBS
+ sigint: 1, /* job was killed by SIGINT */
+ jobctl: 1, /* job running under job control */
+#endif
+ waited: 1, /* true if this entry has been waited for */
+ used: 1, /* true if this entry is in used */
+ changed: 1; /* true if status has changed */
+ struct job *prev_job; /* previous job */
};
-/* output of current string */
-static char *expdest;
-/* list of back quote expressions */
-static struct nodelist *argbackq;
-/* first struct in list of ifs regions */
-static struct ifsregion ifsfirst;
-/* last struct in list */
-static struct ifsregion *ifslastp;
-/* holds expanded arg list */
-static struct arglist exparg;
+static pid_t backgndpid; /* pid of last background process */
+static int job_warning; /* user was warned about stopped jobs */
+#if JOBS
+static int jobctl; /* true if doing job control */
+#endif
-/*
- * Our own itoa().
- */
-static int
-cvtnum(arith_t num)
-{
- int len;
+static struct job *makejob(union node *, int);
+static int forkshell(struct job *, union node *, int);
+static int waitforjob(struct job *);
- expdest = makestrspace(32, expdest);
-#if ENABLE_ASH_MATH_SUPPORT_64
- len = fmtstr(expdest, 32, "%lld", (long long) num);
+#if ! JOBS
+#define setjobctl(on) /* do nothing */
#else
- len = fmtstr(expdest, 32, "%ld", num);
+static void setjobctl(int);
+static void showjobs(FILE *, int);
#endif
- STADJUST(len, expdest);
- return len;
-}
-
-static size_t
-esclen(const char *start, const char *p)
-{
- size_t esc = 0;
-
- while (p > start && *--p == CTLESC) {
- esc++;
- }
- return esc;
-}
/*
- * Remove any CTLESC characters from a string.
+ * Set the signal handler for the specified signal. The routine figures
+ * out what it should be set to.
*/
-static char *
-_rmescapes(char *str, int flag)
+static void
+setsignal(int signo)
{
- char *p, *q, *r;
- static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
- unsigned inquotes;
- int notescaped;
- int globbing;
+ int action;
+ char *t, tsig;
+ struct sigaction act;
- p = strpbrk(str, qchars);
- if (!p) {
- return str;
+ t = trap[signo];
+ if (t == NULL)
+ action = S_DFL;
+ else if (*t != '\0')
+ action = S_CATCH;
+ else
+ action = S_IGN;
+ if (rootshell && action == S_DFL) {
+ switch (signo) {
+ case SIGINT:
+ if (iflag || minusc || sflag == 0)
+ action = S_CATCH;
+ break;
+ case SIGQUIT:
+#if DEBUG
+ if (debug)
+ break;
+#endif
+ /* FALLTHROUGH */
+ case SIGTERM:
+ if (iflag)
+ action = S_IGN;
+ break;
+#if JOBS
+ case SIGTSTP:
+ case SIGTTOU:
+ if (mflag)
+ action = S_IGN;
+ break;
+#endif
+ }
}
- q = p;
- r = str;
- if (flag & RMESCAPE_ALLOC) {
- size_t len = p - str;
- size_t fulllen = len + strlen(p) + 1;
- if (flag & RMESCAPE_GROW) {
- r = makestrspace(fulllen, expdest);
- } else if (flag & RMESCAPE_HEAP) {
- r = ckmalloc(fulllen);
- } else {
- r = stalloc(fulllen);
- }
- q = r;
- if (len > 0) {
- q = memcpy(q, str, len) + len;
- }
- }
- inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
- globbing = flag & RMESCAPE_GLOB;
- notescaped = globbing;
- while (*p) {
- if (*p == CTLQUOTEMARK) {
- inquotes = ~inquotes;
- p++;
- notescaped = globbing;
- continue;
- }
- if (*p == '\\') {
- /* naked back slash */
- notescaped = 0;
- goto copy;
+ t = &sigmode[signo - 1];
+ tsig = *t;
+ if (tsig == 0) {
+ /*
+ * current setting unknown
+ */
+ if (sigaction(signo, 0, &act) == -1) {
+ /*
+ * Pretend it worked; maybe we should give a warning
+ * here, but other shells don't. We don't alter
+ * sigmode, so that we retry every time.
+ */
+ return;
}
- if (*p == CTLESC) {
- p++;
- if (notescaped && inquotes && *p != '/') {
- *q++ = '\\';
- }
+ if (act.sa_handler == SIG_IGN) {
+ if (mflag
+ && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
+ ) {
+ tsig = S_IGN; /* don't hard ignore these */
+ } else
+ tsig = S_HARD_IGN;
+ } else {
+ tsig = S_RESET; /* force to be set */
}
- notescaped = globbing;
- copy:
- *q++ = *p++;
}
- *q = '\0';
- if (flag & RMESCAPE_GROW) {
- expdest = r;
- STADJUST(q - r + 1, expdest);
+ if (tsig == S_HARD_IGN || tsig == action)
+ return;
+ switch (action) {
+ case S_CATCH:
+ act.sa_handler = onsig;
+ break;
+ case S_IGN:
+ act.sa_handler = SIG_IGN;
+ break;
+ default:
+ act.sa_handler = SIG_DFL;
}
- return r;
+ *t = action;
+ act.sa_flags = 0;
+ sigfillset(&act.sa_mask);
+ sigaction(signo, &act, 0);
}
-#define rmescapes(p) _rmescapes((p), 0)
-#define pmatch(a, b) !fnmatch((a), (b), 0)
+/* mode flags for set_curjob */
+#define CUR_DELETE 2
+#define CUR_RUNNING 1
+#define CUR_STOPPED 0
-/*
- * Prepare a pattern for a expmeta (internal glob(3)) call.
- *
- * Returns an stalloced string.
- */
-static char *
-preglob(const char *pattern, int quoted, int flag)
-{
- flag |= RMESCAPE_GLOB;
- if (quoted) {
- flag |= RMESCAPE_QUOTED;
- }
- return _rmescapes((char *)pattern, flag);
-}
+/* mode flags for dowait */
+#define DOWAIT_NORMAL 0
+#define DOWAIT_BLOCK 1
+
+#if JOBS
+/* pgrp of shell on invocation */
+static int initialpgrp;
+static int ttyfd = -1;
+#endif
+/* array of jobs */
+static struct job *jobtab;
+/* size of array */
+static unsigned njobs;
+/* current job */
+static struct job *curjob;
+/* number of presumed living untracked jobs */
+static int jobless;
-/*
- * Put a string on the stack.
- */
static void
-memtodest(const char *p, size_t len, int syntax, int quotes)
+set_curjob(struct job *jp, unsigned mode)
{
- char *q = expdest;
+ struct job *jp1;
+ struct job **jpp, **curp;
- q = makestrspace(len * 2, q);
+ /* first remove from list */
+ jpp = curp = &curjob;
+ do {
+ jp1 = *jpp;
+ if (jp1 == jp)
+ break;
+ jpp = &jp1->prev_job;
+ } while (1);
+ *jpp = jp1->prev_job;
- while (len--) {
- int c = SC2INT(*p++);
- if (!c)
- continue;
- if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK))
- USTPUTC(CTLESC, q);
- USTPUTC(c, q);
+ /* Then re-insert in correct position */
+ jpp = curp;
+ switch (mode) {
+ default:
+#if DEBUG
+ abort();
+#endif
+ case CUR_DELETE:
+ /* job being deleted */
+ break;
+ case CUR_RUNNING:
+ /* newly created job or backgrounded job,
+ put after all stopped jobs. */
+ do {
+ jp1 = *jpp;
+#if JOBS
+ if (!jp1 || jp1->state != JOBSTOPPED)
+#endif
+ break;
+ jpp = &jp1->prev_job;
+ } while (1);
+ /* FALLTHROUGH */
+#if JOBS
+ case CUR_STOPPED:
+#endif
+ /* newly stopped job - becomes curjob */
+ jp->prev_job = *jpp;
+ *jpp = jp;
+ break;
}
-
- expdest = q;
}
-static void
-strtodest(const char *p, int syntax, int quotes)
+#if JOBS || DEBUG
+static int
+jobno(const struct job *jp)
{
- memtodest(p, strlen(p), syntax, quotes);
+ return jp - jobtab + 1;
}
+#endif
/*
- * Record the fact that we have to scan this region of the
- * string for IFS characters.
+ * Convert a job name to a job structure.
*/
-static void
-recordregion(int start, int end, int nulonly)
+static struct job *
+getjob(const char *name, int getctl)
{
- struct ifsregion *ifsp;
+ struct job *jp;
+ struct job *found;
+ const char *err_msg = "No such job: %s";
+ unsigned num;
+ int c;
+ const char *p;
+ char *(*match)(const char *, const char *);
- if (ifslastp == NULL) {
- ifsp = &ifsfirst;
- } else {
- INT_OFF;
- ifsp = ckmalloc(sizeof(*ifsp));
- ifsp->next = NULL;
- ifslastp->next = ifsp;
- INT_ON;
- }
- ifslastp = ifsp;
- ifslastp->begoff = start;
- ifslastp->endoff = end;
- ifslastp->nulonly = nulonly;
-}
+ jp = curjob;
+ p = name;
+ if (!p)
+ goto currentjob;
-static void
-removerecordregions(int endoff)
-{
- if (ifslastp == NULL)
- return;
+ if (*p != '%')
+ goto err;
- if (ifsfirst.endoff > endoff) {
- while (ifsfirst.next != NULL) {
- struct ifsregion *ifsp;
- INT_OFF;
- ifsp = ifsfirst.next->next;
- free(ifsfirst.next);
- ifsfirst.next = ifsp;
- INT_ON;
+ c = *++p;
+ if (!c)
+ goto currentjob;
+
+ if (!p[1]) {
+ if (c == '+' || c == '%') {
+ currentjob:
+ err_msg = "No current job";
+ goto check;
}
- if (ifsfirst.begoff > endoff)
- ifslastp = NULL;
- else {
- ifslastp = &ifsfirst;
- ifsfirst.endoff = endoff;
+ if (c == '-') {
+ if (jp)
+ jp = jp->prev_job;
+ err_msg = "No previous job";
+ check:
+ if (!jp)
+ goto err;
+ goto gotit;
}
- return;
}
- ifslastp = &ifsfirst;
- while (ifslastp->next && ifslastp->next->begoff < endoff)
- ifslastp=ifslastp->next;
- while (ifslastp->next != NULL) {
- struct ifsregion *ifsp;
- INT_OFF;
- ifsp = ifslastp->next->next;
- free(ifslastp->next);
- ifslastp->next = ifsp;
- INT_ON;
- }
- if (ifslastp->endoff > endoff)
- ifslastp->endoff = endoff;
-}
-
-static char *
-exptilde(char *startp, char *p, int flag)
-{
- char c;
- char *name;
- struct passwd *pw;
- const char *home;
- int quotes = flag & (EXP_FULL | EXP_CASE);
- int startloc;
-
- name = p + 1;
-
- while ((c = *++p) != '\0') {
- switch (c) {
- case CTLESC:
- return startp;
- case CTLQUOTEMARK:
- return startp;
- case ':':
- if (flag & EXP_VARTILDE)
- goto done;
- break;
- case '/':
- case CTLENDVAR:
- goto done;
+ if (is_number(p)) {
+ num = atoi(p);
+ if (num < njobs) {
+ jp = jobtab + num - 1;
+ if (jp->used)
+ goto gotit;
+ goto err;
}
}
- done:
- *p = '\0';
- if (*name == '\0') {
- home = lookupvar(homestr);
- } else {
- pw = getpwnam(name);
- if (pw == NULL)
- goto lose;
- home = pw->pw_dir;
- }
- if (!home || !*home)
- goto lose;
- *p = c;
- startloc = expdest - (char *)stackblock();
- strtodest(home, SQSYNTAX, quotes);
- recordregion(startloc, expdest - (char *)stackblock(), 0);
- return p;
- lose:
- *p = c;
- return startp;
-}
-
-/*
- * Execute a command inside back quotes. If it's a builtin command, we
- * want to save its output in a block obtained from malloc. Otherwise
- * we fork off a subprocess and get the output of the command via a pipe.
- * Should be called with interrupts off.
- */
-struct backcmd { /* result of evalbackcmd */
- int fd; /* file descriptor to read from */
- char *buf; /* buffer */
- int nleft; /* number of chars in buffer */
- struct job *jp; /* job structure for command */
-};
-
-/* These forward decls are needed to use "eval" code for backticks handling: */
-static int back_exitstatus; /* exit status of backquoted command */
-#define EV_EXIT 01 /* exit after evaluating tree */
-static void evaltree(union node *, int);
-
-static void
-evalbackcmd(union node *n, struct backcmd *result)
-{
- int saveherefd;
- result->fd = -1;
- result->buf = NULL;
- result->nleft = 0;
- result->jp = NULL;
- if (n == NULL) {
- goto out;
+ match = prefix;
+ if (*p == '?') {
+ match = strstr;
+ p++;
}
- saveherefd = herefd;
- herefd = -1;
-
- {
- int pip[2];
- struct job *jp;
-
- if (pipe(pip) < 0)
- ash_msg_and_raise_error("Pipe call failed");
- jp = makejob(n, 1);
- if (forkshell(jp, n, FORK_NOJOB) == 0) {
- FORCE_INT_ON;
- close(pip[0]);
- if (pip[1] != 1) {
- close(1);
- copyfd(pip[1], 1);
- close(pip[1]);
- }
- eflag = 0;
- evaltree(n, EV_EXIT); /* actually evaltreenr... */
- /* NOTREACHED */
+ found = 0;
+ while (1) {
+ if (!jp)
+ goto err;
+ if (match(jp->ps[0].cmd, p)) {
+ if (found)
+ goto err;
+ found = jp;
+ err_msg = "%s: ambiguous";
}
- close(pip[1]);
- result->fd = pip[0];
- result->jp = jp;
+ jp = jp->prev_job;
}
- herefd = saveherefd;
- out:
- TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
- result->fd, result->buf, result->nleft, result->jp));
+
+ gotit:
+#if JOBS
+ err_msg = "job %s not created under job control";
+ if (getctl && jp->jobctl == 0)
+ goto err;
+#endif
+ return jp;
+ err:
+ ash_msg_and_raise_error(err_msg, name);
}
/*
- * Expand stuff in backwards quotes.
+ * Mark a job structure as unused.
*/
static void
-expbackq(union node *cmd, int quoted, int quotes)
+freejob(struct job *jp)
{
- struct backcmd in;
+ struct procstat *ps;
int i;
- char buf[128];
- char *p;
- char *dest;
- int startloc;
- int syntax = quoted? DQSYNTAX : BASESYNTAX;
- struct stackmark smark;
INT_OFF;
- setstackmark(&smark);
- dest = expdest;
- startloc = dest - (char *)stackblock();
- grabstackstr(dest);
- evalbackcmd(cmd, &in);
- popstackmark(&smark);
-
- p = in.buf;
- i = in.nleft;
- if (i == 0)
- goto read;
- for (;;) {
- memtodest(p, i, syntax, quotes);
- read:
- if (in.fd < 0)
- break;
- i = safe_read(in.fd, buf, sizeof(buf));
- TRACE(("expbackq: read returns %d\n", i));
- if (i <= 0)
- break;
- p = buf;
- }
-
- if (in.buf)
- free(in.buf);
- if (in.fd >= 0) {
- close(in.fd);
- back_exitstatus = waitforjob(in.jp);
+ for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) {
+ if (ps->cmd != nullstr)
+ free(ps->cmd);
}
+ if (jp->ps != &jp->ps0)
+ free(jp->ps);
+ jp->used = 0;
+ set_curjob(jp, CUR_DELETE);
INT_ON;
+}
- /* Eat all trailing newlines */
- dest = expdest;
- for (; dest > (char *)stackblock() && dest[-1] == '\n';)
- STUNPUTC(dest);
- expdest = dest;
-
- if (quoted == 0)
- recordregion(startloc, dest - (char *)stackblock(), 0);
- TRACE(("evalbackq: size=%d: \"%.*s\"\n",
- (dest - (char *)stackblock()) - startloc,
- (dest - (char *)stackblock()) - startloc,
- stackblock() + startloc));
+#if JOBS
+static void
+xtcsetpgrp(int fd, pid_t pgrp)
+{
+ if (tcsetpgrp(fd, pgrp))
+ ash_msg_and_raise_error("Cannot set tty process group (%m)");
}
-#if ENABLE_ASH_MATH_SUPPORT
/*
- * Expand arithmetic expression. Backup to start of expression,
- * evaluate, place result in (backed up) result, adjust string position.
+ * Turn job control on and off.
+ *
+ * Note: This code assumes that the third arg to ioctl is a character
+ * pointer, which is true on Berkeley systems but not System V. Since
+ * System V doesn't have job control yet, this isn't a problem now.
+ *
+ * Called with interrupts off.
*/
static void
-expari(int quotes)
+setjobctl(int on)
{
- char *p, *start;
- int begoff;
- int flag;
- int len;
-
- /* ifsfree(); */
-
- /*
- * This routine is slightly over-complicated for
- * efficiency. Next we scan backwards looking for the
- * start of arithmetic.
- */
- start = stackblock();
- p = expdest - 1;
- *p = '\0';
- p--;
- do {
- int esc;
+ int fd;
+ int pgrp;
- while (*p != CTLARI) {
- p--;
-#if DEBUG
- if (p < start) {
- ash_msg_and_raise_error("missing CTLARI (shouldn't happen)");
- }
-#endif
- }
-
- esc = esclen(start, p);
- if (!(esc % 2)) {
- break;
+ if (on == jobctl || rootshell == 0)
+ return;
+ if (on) {
+ int ofd;
+ ofd = fd = open(_PATH_TTY, O_RDWR);
+ if (fd < 0) {
+ /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails.
+ * That sometimes helps to acquire controlling tty.
+ * Obviously, a workaround for bugs when someone
+ * failed to provide a controlling tty to bash! :) */
+ fd += 3;
+ while (!isatty(fd) && --fd >= 0)
+ ;
}
+ fd = fcntl(fd, F_DUPFD, 10);
+ close(ofd);
+ if (fd < 0)
+ goto out;
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ do { /* while we are in the background */
+ pgrp = tcgetpgrp(fd);
+ if (pgrp < 0) {
+ out:
+ ash_msg("can't access tty; job control turned off");
+ mflag = on = 0;
+ goto close;
+ }
+ if (pgrp == getpgrp())
+ break;
+ killpg(0, SIGTTIN);
+ } while (1);
+ initialpgrp = pgrp;
- p -= esc + 1;
- } while (1);
+ setsignal(SIGTSTP);
+ setsignal(SIGTTOU);
+ setsignal(SIGTTIN);
+ pgrp = rootpid;
+ setpgid(0, pgrp);
+ xtcsetpgrp(fd, pgrp);
+ } else {
+ /* turning job control off */
+ fd = ttyfd;
+ pgrp = initialpgrp;
+ xtcsetpgrp(fd, pgrp);
+ setpgid(0, pgrp);
+ setsignal(SIGTSTP);
+ setsignal(SIGTTOU);
+ setsignal(SIGTTIN);
+ close:
+ close(fd);
+ fd = -1;
+ }
+ ttyfd = fd;
+ jobctl = on;
+}
- begoff = p - start;
+static int
+killcmd(int argc, char **argv)
+{
+ int signo = -1;
+ int list = 0;
+ int i;
+ pid_t pid;
+ struct job *jp;
- removerecordregions(begoff);
+ if (argc <= 1) {
+ usage:
+ ash_msg_and_raise_error(
+"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
+"kill -l [exitstatus]"
+ );
+ }
- flag = p[1];
+ if (**++argv == '-') {
+ signo = get_signum(*argv + 1);
+ if (signo < 0) {
+ int c;
- expdest = p;
+ while ((c = nextopt("ls:")) != '\0') {
+ switch (c) {
+ default:
+#if DEBUG
+ abort();
+#endif
+ case 'l':
+ list = 1;
+ break;
+ case 's':
+ signo = get_signum(optionarg);
+ if (signo < 0) {
+ ash_msg_and_raise_error(
+ "invalid signal number or name: %s",
+ optionarg
+ );
+ }
+ break;
+ }
+ }
+ argv = argptr;
+ } else
+ argv++;
+ }
- if (quotes)
- rmescapes(p + 2);
+ if (!list && signo < 0)
+ signo = SIGTERM;
- len = cvtnum(dash_arith(p + 2));
+ if ((signo < 0 || !*argv) ^ list) {
+ goto usage;
+ }
- if (flag != '"')
- recordregion(begoff, begoff + len, 0);
-}
-#endif
+ if (list) {
+ const char *name;
-/* argstr needs it */
-static char *evalvar(char *p, int flag);
+ if (!*argv) {
+ for (i = 1; i < NSIG; i++) {
+ name = get_signame(i);
+ if (isdigit(*name))
+ out1fmt(snlfmt, name);
+ }
+ return 0;
+ }
+ name = get_signame(signo);
+ if (!isdigit(*name))
+ ash_msg_and_raise_error("invalid signal number or exit status: %s", *argptr);
+ out1fmt(snlfmt, name);
+ return 0;
+ }
+
+ i = 0;
+ do {
+ if (**argv == '%') {
+ jp = getjob(*argv, 0);
+ pid = -jp->ps[0].pid;
+ } else {
+ pid = **argv == '-' ?
+ -number(*argv + 1) : number(*argv);
+ }
+ if (kill(pid, signo) != 0) {
+ ash_msg("(%d) - %m", pid);
+ i = 1;
+ }
+ } while (*++argv);
+
+ return i;
+}
-/*
- * Perform variable and command substitution. If EXP_FULL is set, output CTLESC
- * characters to allow for further processing. Otherwise treat
- * $@ like $* since no splitting will be performed.
- */
static void
-argstr(char *p, int flag)
+showpipe(struct job *jp, FILE *out)
{
- static const char spclchars[] = {
- '=',
- ':',
- CTLQUOTEMARK,
- CTLENDVAR,
- CTLESC,
- CTLVAR,
- CTLBACKQ,
- CTLBACKQ | CTLQUOTE,
-#if ENABLE_ASH_MATH_SUPPORT
- CTLENDARI,
-#endif
- 0
- };
- const char *reject = spclchars;
- int c;
- int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */
- int breakall = flag & EXP_WORD;
- int inquotes;
- size_t length;
- int startloc;
+ struct procstat *sp;
+ struct procstat *spend;
- if (!(flag & EXP_VARTILDE)) {
- reject += 2;
- } else if (flag & EXP_VARTILDE2) {
- reject++;
- }
- inquotes = 0;
- length = 0;
- if (flag & EXP_TILDE) {
- char *q;
+ spend = jp->ps + jp->nprocs;
+ for (sp = jp->ps + 1; sp < spend; sp++)
+ fprintf(out, " | %s", sp->cmd);
+ outcslow('\n', out);
+ flush_stdout_stderr();
+}
- flag &= ~EXP_TILDE;
- tilde:
- q = p;
- if (*q == CTLESC && (flag & EXP_QWORD))
- q++;
- if (*q == '~')
- p = exptilde(p, q, flag);
- }
- start:
- startloc = expdest - (char *)stackblock();
- for (;;) {
- length += strcspn(p + length, reject);
- c = p[length];
- if (c && (!(c & 0x80)
-#if ENABLE_ASH_MATH_SUPPORT
- || c == CTLENDARI
-#endif
- )) {
- /* c == '=' || c == ':' || c == CTLENDARI */
- length++;
- }
- if (length > 0) {
- int newloc;
- expdest = stack_nputstr(p, length, expdest);
- newloc = expdest - (char *)stackblock();
- if (breakall && !inquotes && newloc > startloc) {
- recordregion(startloc, newloc, 0);
- }
- startloc = newloc;
- }
- p += length + 1;
- length = 0;
- switch (c) {
- case '\0':
- goto breakloop;
- case '=':
- if (flag & EXP_VARTILDE2) {
- p--;
- continue;
- }
- flag |= EXP_VARTILDE2;
- reject++;
- /* fall through */
- case ':':
- /*
- * sort of a hack - expand tildes in variable
- * assignments (after the first '=' and after ':'s).
- */
- if (*--p == '~') {
- goto tilde;
- }
- continue;
- }
+static int
+restartjob(struct job *jp, int mode)
+{
+ struct procstat *ps;
+ int i;
+ int status;
+ pid_t pgid;
- switch (c) {
- case CTLENDVAR: /* ??? */
- goto breakloop;
- case CTLQUOTEMARK:
- /* "$@" syntax adherence hack */
- if (
- !inquotes &&
- !memcmp(p, dolatstr, 4) &&
- (p[4] == CTLQUOTEMARK || (
- p[4] == CTLENDVAR &&
- p[5] == CTLQUOTEMARK
- ))
- ) {
- p = evalvar(p + 1, flag) + 1;
- goto start;
- }
- inquotes = !inquotes;
- addquote:
- if (quotes) {
- p--;
- length++;
- startloc++;
- }
- break;
- case CTLESC:
- startloc++;
- length++;
- goto addquote;
- case CTLVAR:
- p = evalvar(p, flag);
- goto start;
- case CTLBACKQ:
- c = 0;
- case CTLBACKQ|CTLQUOTE:
- expbackq(argbackq->n, c, quotes);
- argbackq = argbackq->next;
- goto start;
-#if ENABLE_ASH_MATH_SUPPORT
- case CTLENDARI:
- p--;
- expari(quotes);
- goto start;
-#endif
+ INT_OFF;
+ if (jp->state == JOBDONE)
+ goto out;
+ jp->state = JOBRUNNING;
+ pgid = jp->ps->pid;
+ if (mode == FORK_FG)
+ xtcsetpgrp(ttyfd, pgid);
+ killpg(pgid, SIGCONT);
+ ps = jp->ps;
+ i = jp->nprocs;
+ do {
+ if (WIFSTOPPED(ps->status)) {
+ ps->status = -1;
}
- }
- breakloop:
- ;
+ } while (ps++, --i);
+ out:
+ status = (mode == FORK_FG) ? waitforjob(jp) : 0;
+ INT_ON;
+ return status;
}
-static char *
-scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
- int zero)
+static int
+fg_bgcmd(int argc, char **argv)
{
- char *loc;
- char *loc2;
- char c;
+ struct job *jp;
+ FILE *out;
+ int mode;
+ int retval;
- loc = startp;
- loc2 = rmesc;
+ mode = (**argv == 'f') ? FORK_FG : FORK_BG;
+ nextopt(nullstr);
+ argv = argptr;
+ out = stdout;
do {
- int match;
- const char *s = loc2;
- c = *loc2;
- if (zero) {
- *loc2 = '\0';
- s = rmesc;
+ jp = getjob(*argv, 1);
+ if (mode == FORK_BG) {
+ set_curjob(jp, CUR_RUNNING);
+ fprintf(out, "[%d] ", jobno(jp));
}
- match = pmatch(str, s);
- *loc2 = c;
- if (match)
- return loc;
- if (quotes && *loc == CTLESC)
- loc++;
- loc++;
- loc2++;
- } while (c);
- return 0;
+ outstr(jp->ps->cmd, out);
+ showpipe(jp, out);
+ retval = restartjob(jp, mode);
+ } while (*argv && *++argv);
+ return retval;
}
+#endif
-static char *
-scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
- int zero)
+static int
+sprint_status(char *s, int status, int sigonly)
{
- int esc = 0;
- char *loc;
- char *loc2;
+ int col;
+ int st;
- for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
- int match;
- char c = *loc2;
- const char *s = loc2;
- if (zero) {
- *loc2 = '\0';
- s = rmesc;
+ col = 0;
+ if (!WIFEXITED(status)) {
+#if JOBS
+ if (WIFSTOPPED(status))
+ st = WSTOPSIG(status);
+ else
+#endif
+ st = WTERMSIG(status);
+ if (sigonly) {
+ if (st == SIGINT || st == SIGPIPE)
+ goto out;
+#if JOBS
+ if (WIFSTOPPED(status))
+ goto out;
+#endif
}
- match = pmatch(str, s);
- *loc2 = c;
- if (match)
- return loc;
- loc--;
- if (quotes) {
- if (--esc < 0) {
- esc = esclen(startp, loc);
- }
- if (esc % 2) {
- esc--;
- loc--;
- }
+ st &= 0x7f;
+ col = fmtstr(s, 32, strsignal(st));
+ if (WCOREDUMP(status)) {
+ col += fmtstr(s + col, 16, " (core dumped)");
}
+ } else if (!sigonly) {
+ st = WEXITSTATUS(status);
+ if (st)
+ col = fmtstr(s, 16, "Done(%d)", st);
+ else
+ col = fmtstr(s, 16, "Done");
}
- return 0;
+ out:
+ return col;
}
-static void varunset(const char *, const char *, const char *, int) ATTRIBUTE_NORETURN;
-static void
-varunset(const char *end, const char *var, const char *umsg, int varflags)
+/*
+ * Do a wait system call. If job control is compiled in, we accept
+ * stopped processes. If block is zero, we return a value of zero
+ * rather than blocking.
+ *
+ * System V doesn't have a non-blocking wait system call. It does
+ * have a SIGCLD signal that is sent to a process when one of it's
+ * children dies. The obvious way to use SIGCLD would be to install
+ * a handler for SIGCLD which simply bumped a counter when a SIGCLD
+ * was received, and have waitproc bump another counter when it got
+ * the status of a process. Waitproc would then know that a wait
+ * system call would not block if the two counters were different.
+ * This approach doesn't work because if a process has children that
+ * have not been waited for, System V will send it a SIGCLD when it
+ * installs a signal handler for SIGCLD. What this means is that when
+ * a child exits, the shell will be sent SIGCLD signals continuously
+ * until is runs out of stack space, unless it does a wait call before
+ * restoring the signal handler. The code below takes advantage of
+ * this (mis)feature by installing a signal handler for SIGCLD and
+ * then checking to see whether it was called. If there are any
+ * children to be waited for, it will be.
+ *
+ * If neither SYSV nor BSD is defined, we don't implement nonblocking
+ * waits at all. In this case, the user will not be informed when
+ * a background process until the next time she runs a real program
+ * (as opposed to running a builtin command or just typing return),
+ * and the jobs command may give out of date information.
+ */
+static int
+waitproc(int block, int *status)
{
- const char *msg;
- const char *tail;
+ int flags = 0;
- tail = nullstr;
- msg = "parameter not set";
- if (umsg) {
- if (*end == CTLENDVAR) {
- if (varflags & VSNUL)
- tail = " or null";
- } else
- msg = umsg;
- }
- ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
+#if JOBS
+ if (jobctl)
+ flags |= WUNTRACED;
+#endif
+ if (block == 0)
+ flags |= WNOHANG;
+ return wait3(status, flags, (struct rusage *)NULL);
}
-static const char *
-subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes)
+/*
+ * Wait for a process to terminate.
+ */
+static int
+dowait(int block, struct job *job)
{
- char *startp;
- char *loc;
- int saveherefd = herefd;
- struct nodelist *saveargbackq = argbackq;
- int amount;
- char *rmesc, *rmescend;
- int zero;
- char *(*scan)(char *, char *, char *, char *, int , int);
-
- herefd = -1;
- argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
- STPUTC('\0', expdest);
- herefd = saveherefd;
- argbackq = saveargbackq;
- startp = stackblock() + startloc;
-
- switch (subtype) {
- case VSASSIGN:
- setvar(str, startp, 0);
- amount = startp - expdest;
- STADJUST(amount, expdest);
- return startp;
-
- case VSQUESTION:
- varunset(p, str, startp, varflags);
- /* NOTREACHED */
- }
-
- subtype -= VSTRIMRIGHT;
-#if DEBUG
- if (subtype < 0 || subtype > 3)
- abort();
-#endif
+ int pid;
+ int status;
+ struct job *jp;
+ struct job *thisjob;
+ int state;
- rmesc = startp;
- rmescend = stackblock() + strloc;
- if (quotes) {
- rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
- if (rmesc != startp) {
- rmescend = expdest;
- startp = stackblock() + startloc;
+ TRACE(("dowait(%d) called\n", block));
+ pid = waitproc(block, &status);
+ TRACE(("wait returns pid %d, status=%d\n", pid, status));
+ if (pid <= 0)
+ return pid;
+ INT_OFF;
+ thisjob = NULL;
+ for (jp = curjob; jp; jp = jp->prev_job) {
+ struct procstat *sp;
+ struct procstat *spend;
+ if (jp->state == JOBDONE)
+ continue;
+ state = JOBDONE;
+ spend = jp->ps + jp->nprocs;
+ sp = jp->ps;
+ do {
+ if (sp->pid == pid) {
+ TRACE(("Job %d: changing status of proc %d "
+ "from 0x%x to 0x%x\n",
+ jobno(jp), pid, sp->status, status));
+ sp->status = status;
+ thisjob = jp;
+ }
+ if (sp->status == -1)
+ state = JOBRUNNING;
+#if JOBS
+ if (state == JOBRUNNING)
+ continue;
+ if (WIFSTOPPED(sp->status)) {
+ jp->stopstatus = sp->status;
+ state = JOBSTOPPED;
+ }
+#endif
+ } while (++sp < spend);
+ if (thisjob)
+ goto gotjob;
+ }
+#if JOBS
+ if (!WIFSTOPPED(status))
+#endif
+
+ jobless--;
+ goto out;
+
+ gotjob:
+ if (state != JOBRUNNING) {
+ thisjob->changed = 1;
+
+ if (thisjob->state != state) {
+ TRACE(("Job %d: changing state from %d to %d\n",
+ jobno(thisjob), thisjob->state, state));
+ thisjob->state = state;
+#if JOBS
+ if (state == JOBSTOPPED) {
+ set_curjob(thisjob, CUR_STOPPED);
+ }
+#endif
}
}
- rmescend--;
- str = stackblock() + strloc;
- preglob(str, varflags & VSQUOTE, 0);
- /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
- zero = subtype >> 1;
- /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
- scan = (subtype & 1) ^ zero ? scanleft : scanright;
+ out:
+ INT_ON;
- loc = scan(startp, rmesc, rmescend, str, quotes, zero);
- if (loc) {
- if (zero) {
- memmove(startp, loc, str - loc);
- loc = startp + (str - loc) - 1;
+ if (thisjob && thisjob == job) {
+ char s[48 + 1];
+ int len;
+
+ len = sprint_status(s, status, 1);
+ if (len) {
+ s[len] = '\n';
+ s[len + 1] = 0;
+ out2str(s);
}
- *loc = '\0';
- amount = loc - expdest;
- STADJUST(amount, expdest);
}
- return loc;
+ return pid;
}
-/*
- * Add the value of a specialized variable to the stack string.
- */
-static ssize_t
-varvalue(char *name, int varflags, int flags)
+#if JOBS
+static void
+showjob(FILE *out, struct job *jp, int mode)
{
- int num;
- char *p;
- int i;
- int sep = 0;
- int sepq = 0;
- ssize_t len = 0;
- char **ap;
- int syntax;
- int quoted = varflags & VSQUOTE;
- int subtype = varflags & VSTYPE;
- int quotes = flags & (EXP_FULL | EXP_CASE);
+ struct procstat *ps;
+ struct procstat *psend;
+ int col;
+ int indent;
+ char s[80];
- if (quoted && (flags & EXP_FULL))
- sep = 1 << CHAR_BIT;
+ ps = jp->ps;
- syntax = quoted ? DQSYNTAX : BASESYNTAX;
- switch (*name) {
- case '$':
- num = rootpid;
- goto numvar;
- case '?':
- num = exitstatus;
- goto numvar;
- case '#':
- num = shellparam.nparam;
- goto numvar;
- case '!':
- num = backgndpid;
- if (num == 0)
- return -1;
- numvar:
- len = cvtnum(num);
- break;
- case '-':
- p = makestrspace(NOPTS, expdest);
- for (i = NOPTS - 1; i >= 0; i--) {
- if (optlist[i]) {
- USTPUTC(optletters(i), p);
- len++;
- }
- }
- expdest = p;
- break;
- case '@':
- if (sep)
- goto param;
- /* fall through */
- case '*':
- sep = ifsset() ? SC2INT(ifsval()[0]) : ' ';
- if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK))
- sepq = 1;
- param:
- ap = shellparam.p;
- if (!ap)
- return -1;
- while ((p = *ap++)) {
- size_t partlen;
+ if (mode & SHOW_PGID) {
+ /* just output process (group) id of pipeline */
+ fprintf(out, "%d\n", ps->pid);
+ return;
+ }
- partlen = strlen(p);
- len += partlen;
+ col = fmtstr(s, 16, "[%d] ", jobno(jp));
+ indent = col;
- if (!(subtype == VSPLUS || subtype == VSLENGTH))
- memtodest(p, partlen, syntax, quotes);
+ if (jp == curjob)
+ s[col - 2] = '+';
+ else if (curjob && jp == curjob->prev_job)
+ s[col - 2] = '-';
- if (*ap && sep) {
- char *q;
+ if (mode & SHOW_PID)
+ col += fmtstr(s + col, 16, "%d ", ps->pid);
- len++;
- if (subtype == VSPLUS || subtype == VSLENGTH) {
- continue;
- }
- q = expdest;
- if (sepq)
- STPUTC(CTLESC, q);
- STPUTC(sep, q);
- expdest = q;
- }
- }
- return len;
- case '0':
- case '1':
- case '2':
- case '3':
- case '4':
- case '5':
- case '6':
- case '7':
- case '8':
- case '9':
- num = atoi(name);
- if (num < 0 || num > shellparam.nparam)
- return -1;
- p = num ? shellparam.p[num - 1] : arg0;
- goto value;
- default:
- p = lookupvar(name);
- value:
- if (!p)
- return -1;
+ psend = ps + jp->nprocs;
- len = strlen(p);
- if (!(subtype == VSPLUS || subtype == VSLENGTH))
- memtodest(p, len, syntax, quotes);
- return len;
+ if (jp->state == JOBRUNNING) {
+ strcpy(s + col, "Running");
+ col += sizeof("Running") - 1;
+ } else {
+ int status = psend[-1].status;
+ if (jp->state == JOBSTOPPED)
+ status = jp->stopstatus;
+ col += sprint_status(s + col, status, 0);
}
- if (subtype == VSPLUS || subtype == VSLENGTH)
- STADJUST(-len, expdest);
- return len;
+ goto start;
+
+ do {
+ /* for each process */
+ col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3;
+ start:
+ fprintf(out, "%s%*c%s",
+ s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd
+ );
+ if (!(mode & SHOW_PID)) {
+ showpipe(jp, out);
+ break;
+ }
+ if (++ps == psend) {
+ outcslow('\n', out);
+ break;
+ }
+ } while (1);
+
+ jp->changed = 0;
+
+ if (jp->state == JOBDONE) {
+ TRACE(("showjob: freeing job %d\n", jobno(jp)));
+ freejob(jp);
+ }
}
-/*
- * Expand a variable, and return a pointer to the next character in the
- * input string.
- */
-static char *
-evalvar(char *p, int flag)
+static int
+jobscmd(int argc, char **argv)
{
- int subtype;
- int varflags;
- char *var;
- int patloc;
- int c;
- int startloc;
- ssize_t varlen;
- int easy;
- int quotes;
- int quoted;
+ int mode, m;
+ FILE *out;
- quotes = flag & (EXP_FULL | EXP_CASE);
- varflags = *p++;
- subtype = varflags & VSTYPE;
- quoted = varflags & VSQUOTE;
- var = p;
- easy = (!quoted || (*var == '@' && shellparam.nparam));
- startloc = expdest - (char *)stackblock();
- p = strchr(p, '=') + 1;
+ mode = 0;
+ while ((m = nextopt("lp"))) {
+ if (m == 'l')
+ mode = SHOW_PID;
+ else
+ mode = SHOW_PGID;
+ }
- again:
- varlen = varvalue(var, varflags, flag);
- if (varflags & VSNUL)
- varlen--;
+ out = stdout;
+ argv = argptr;
+ if (*argv) {
+ do
+ showjob(out, getjob(*argv,0), mode);
+ while (*++argv);
+ } else
+ showjobs(out, mode);
- if (subtype == VSPLUS) {
- varlen = -1 - varlen;
- goto vsplus;
- }
+ return 0;
+}
- if (subtype == VSMINUS) {
- vsplus:
- if (varlen < 0) {
- argstr(
- p, flag | EXP_TILDE |
- (quoted ? EXP_QWORD : EXP_WORD)
- );
- goto end;
- }
- if (easy)
- goto record;
- goto end;
- }
+/*
+ * Print a list of jobs. If "change" is nonzero, only print jobs whose
+ * statuses have changed since the last call to showjobs.
+ */
+static void
+showjobs(FILE *out, int mode)
+{
+ struct job *jp;
- if (subtype == VSASSIGN || subtype == VSQUESTION) {
- if (varlen < 0) {
- if (subevalvar(p, var, 0, subtype, startloc, varflags, 0)) {
- varflags &= ~VSNUL;
- /*
- * Remove any recorded regions beyond
- * start of variable
- */
- removerecordregions(startloc);
- goto again;
- }
- goto end;
- }
- if (easy)
- goto record;
- goto end;
- }
+ TRACE(("showjobs(%x) called\n", mode));
- if (varlen < 0 && uflag)
- varunset(p, var, 0, 0);
+ /* If not even one one job changed, there is nothing to do */
+ while (dowait(DOWAIT_NORMAL, NULL) > 0)
+ continue;
- if (subtype == VSLENGTH) {
- cvtnum(varlen > 0 ? varlen : 0);
- goto record;
+ for (jp = curjob; jp; jp = jp->prev_job) {
+ if (!(mode & SHOW_CHANGED) || jp->changed)
+ showjob(out, jp, mode);
}
+}
+#endif /* JOBS */
- if (subtype == VSNORMAL) {
- if (!easy)
- goto end;
- record:
- recordregion(startloc, expdest - (char *)stackblock(), quoted);
- goto end;
- }
+static int
+getstatus(struct job *job)
+{
+ int status;
+ int retval;
-#if DEBUG
- switch (subtype) {
- case VSTRIMLEFT:
- case VSTRIMLEFTMAX:
- case VSTRIMRIGHT:
- case VSTRIMRIGHTMAX:
- break;
- default:
- abort();
- }
+ status = job->ps[job->nprocs - 1].status;
+ retval = WEXITSTATUS(status);
+ if (!WIFEXITED(status)) {
+#if JOBS
+ retval = WSTOPSIG(status);
+ if (!WIFSTOPPED(status))
+#endif
+ {
+ /* XXX: limits number of signals */
+ retval = WTERMSIG(status);
+#if JOBS
+ if (retval == SIGINT)
+ job->sigint = 1;
#endif
-
- if (varlen >= 0) {
- /*
- * Terminate the string and start recording the pattern
- * right after it
- */
- STPUTC('\0', expdest);
- patloc = expdest - (char *)stackblock();
- if (subevalvar(p, NULL, patloc, subtype,
- startloc, varflags, quotes) == 0) {
- int amount = expdest - (
- (char *)stackblock() + patloc - 1
- );
- STADJUST(-amount, expdest);
}
- /* Remove any recorded regions beyond start of variable */
- removerecordregions(startloc);
- goto record;
+ retval += 128;
}
+ TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n",
+ jobno(job), job->nprocs, status, retval));
+ return retval;
+}
- end:
- if (subtype != VSNORMAL) { /* skip to end of alternative */
- int nesting = 1;
+static int
+waitcmd(int argc, char **argv)
+{
+ struct job *job;
+ int retval;
+ struct job *jp;
+
+ EXSIGON;
+
+ nextopt(nullstr);
+ retval = 0;
+
+ argv = argptr;
+ if (!*argv) {
+ /* wait for all jobs */
for (;;) {
- c = *p++;
- if (c == CTLESC)
- p++;
- else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
- if (varlen >= 0)
- argbackq = argbackq->next;
- } else if (c == CTLVAR) {
- if ((*p++ & VSTYPE) != VSNORMAL)
- nesting++;
- } else if (c == CTLENDVAR) {
- if (--nesting == 0)
+ jp = curjob;
+ while (1) {
+ if (!jp) {
+ /* no running procs */
+ goto out;
+ }
+ if (jp->state == JOBRUNNING)
break;
+ jp->waited = 1;
+ jp = jp->prev_job;
}
+ dowait(DOWAIT_BLOCK, 0);
}
}
- return p;
+
+ retval = 127;
+ do {
+ if (**argv != '%') {
+ pid_t pid = number(*argv);
+ job = curjob;
+ goto start;
+ do {
+ if (job->ps[job->nprocs - 1].pid == pid)
+ break;
+ job = job->prev_job;
+ start:
+ if (!job)
+ goto repeat;
+ } while (1);
+ } else
+ job = getjob(*argv, 0);
+ /* loop until process terminated or stopped */
+ while (job->state == JOBRUNNING)
+ dowait(DOWAIT_BLOCK, 0);
+ job->waited = 1;
+ retval = getstatus(job);
+ repeat:
+ ;
+ } while (*++argv);
+
+ out:
+ return retval;
}
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list. The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- */
-static void
-ifsbreakup(char *string, struct arglist *arglist)
+static struct job *
+growjobtab(void)
{
- struct ifsregion *ifsp;
- struct strlist *sp;
- char *start;
- char *p;
- char *q;
- const char *ifs, *realifs;
- int ifsspc;
- int nulonly;
+ size_t len;
+ ptrdiff_t offset;
+ struct job *jp, *jq;
- start = string;
- if (ifslastp != NULL) {
- ifsspc = 0;
- nulonly = 0;
- realifs = ifsset() ? ifsval() : defifs;
- ifsp = &ifsfirst;
- do {
- p = string + ifsp->begoff;
- nulonly = ifsp->nulonly;
- ifs = nulonly ? nullstr : realifs;
- ifsspc = 0;
- while (p < string + ifsp->endoff) {
- q = p;
- if (*p == CTLESC)
- p++;
- if (!strchr(ifs, *p)) {
- p++;
- continue;
- }
- if (!nulonly)
- ifsspc = (strchr(defifs, *p) != NULL);
- /* Ignore IFS whitespace at start */
- if (q == start && ifsspc) {
- p++;
- start = p;
- continue;
- }
- *q = '\0';
- sp = stalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
- p++;
- if (!nulonly) {
- for (;;) {
- if (p >= string + ifsp->endoff) {
- break;
- }
- q = p;
- if (*p == CTLESC)
- p++;
- if (strchr(ifs, *p) == NULL ) {
- p = q;
- break;
- } else if (strchr(defifs, *p) == NULL) {
- if (ifsspc) {
- p++;
- ifsspc = 0;
- } else {
- p = q;
- break;
- }
- } else
- p++;
- }
- }
- start = p;
- } /* while */
- ifsp = ifsp->next;
- } while (ifsp != NULL);
- if (nulonly)
- goto add;
- }
+ len = njobs * sizeof(*jp);
+ jq = jobtab;
+ jp = ckrealloc(jq, len + 4 * sizeof(*jp));
- if (!*start)
- return;
+ offset = (char *)jp - (char *)jq;
+ if (offset) {
+ /* Relocate pointers */
+ size_t l = len;
- add:
- sp = stalloc(sizeof(*sp));
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
+ jq = (struct job *)((char *)jq + l);
+ while (l) {
+ l -= sizeof(*jp);
+ jq--;
+#define joff(p) ((struct job *)((char *)(p) + l))
+#define jmove(p) (p) = (void *)((char *)(p) + offset)
+ if (joff(jp)->ps == &jq->ps0)
+ jmove(joff(jp)->ps);
+ if (joff(jp)->prev_job)
+ jmove(joff(jp)->prev_job);
+ }
+ if (curjob)
+ jmove(curjob);
+#undef joff
+#undef jmove
+ }
+
+ njobs += 4;
+ jobtab = jp;
+ jp = (struct job *)((char *)jp + len);
+ jq = jp + 3;
+ do {
+ jq->used = 0;
+ } while (--jq >= jp);
+ return jp;
}
-static void
-ifsfree(void)
+/*
+ * Return a new job structure.
+ * Called with interrupts off.
+ */
+static struct job *
+makejob(union node *node, int nprocs)
{
- struct ifsregion *p;
+ int i;
+ struct job *jp;
- INT_OFF;
- p = ifsfirst.next;
- do {
- struct ifsregion *ifsp;
- ifsp = p->next;
- free(p);
- p = ifsp;
- } while (p);
- ifslastp = NULL;
- ifsfirst.next = NULL;
- INT_ON;
+ for (i = njobs, jp = jobtab; ; jp++) {
+ if (--i < 0) {
+ jp = growjobtab();
+ break;
+ }
+ if (jp->used == 0)
+ break;
+ if (jp->state != JOBDONE || !jp->waited)
+ continue;
+#if JOBS
+ if (jobctl)
+ continue;
+#endif
+ freejob(jp);
+ break;
+ }
+ memset(jp, 0, sizeof(*jp));
+#if JOBS
+ if (jobctl)
+ jp->jobctl = 1;
+#endif
+ jp->prev_job = curjob;
+ curjob = jp;
+ jp->used = 1;
+ jp->ps = &jp->ps0;
+ if (nprocs > 1) {
+ jp->ps = ckmalloc(nprocs * sizeof(struct procstat));
+ }
+ TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
+ jobno(jp)));
+ return jp;
}
+#if JOBS
/*
- * Add a file name to the list.
+ * Return a string identifying a command (to be printed by the
+ * jobs command).
*/
+static char *cmdnextc;
+
static void
-addfname(const char *name)
+cmdputs(const char *s)
{
- struct strlist *sp;
+ const char *p, *str;
+ char c, cc[2] = " ";
+ char *nextc;
+ int subtype = 0;
+ int quoted = 0;
+ static const char vstype[VSTYPE + 1][4] = {
+ "", "}", "-", "+", "?", "=",
+ "%", "%%", "#", "##"
+ };
- sp = stalloc(sizeof(*sp));
- sp->text = ststrdup(name);
- *exparg.lastp = sp;
- exparg.lastp = &sp->next;
+ nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
+ p = s;
+ while ((c = *p++) != 0) {
+ str = 0;
+ switch (c) {
+ case CTLESC:
+ c = *p++;
+ break;
+ case CTLVAR:
+ subtype = *p++;
+ if ((subtype & VSTYPE) == VSLENGTH)
+ str = "${#";
+ else
+ str = "${";
+ if (!(subtype & VSQUOTE) == !(quoted & 1))
+ goto dostr;
+ quoted ^= 1;
+ c = '"';
+ break;
+ case CTLENDVAR:
+ str = "\"}" + !(quoted & 1);
+ quoted >>= 1;
+ subtype = 0;
+ goto dostr;
+ case CTLBACKQ:
+ str = "$(...)";
+ goto dostr;
+ case CTLBACKQ+CTLQUOTE:
+ str = "\"$(...)\"";
+ goto dostr;
+#if ENABLE_ASH_MATH_SUPPORT
+ case CTLARI:
+ str = "$((";
+ goto dostr;
+ case CTLENDARI:
+ str = "))";
+ goto dostr;
+#endif
+ case CTLQUOTEMARK:
+ quoted ^= 1;
+ c = '"';
+ break;
+ case '=':
+ if (subtype == 0)
+ break;
+ if ((subtype & VSTYPE) != VSNORMAL)
+ quoted <<= 1;
+ str = vstype[subtype & VSTYPE];
+ if (subtype & VSNUL)
+ c = ':';
+ else
+ goto checkstr;
+ break;
+ case '\'':
+ case '\\':
+ case '"':
+ case '$':
+ /* These can only happen inside quotes */
+ cc[0] = c;
+ str = cc;
+ c = '\\';
+ break;
+ default:
+ break;
+ }
+ USTPUTC(c, nextc);
+ checkstr:
+ if (!str)
+ continue;
+ dostr:
+ while ((c = *str++)) {
+ USTPUTC(c, nextc);
+ }
+ }
+ if (quoted & 1) {
+ USTPUTC('"', nextc);
+ }
+ *nextc = 0;
+ cmdnextc = nextc;
}
-static char *expdir;
+/* cmdtxt() and cmdlist() call each other */
+static void cmdtxt(union node *n);
-/*
- * Do metacharacter (i.e. *, ?, [...]) expansion.
- */
static void
-expmeta(char *enddir, char *name)
+cmdlist(union node *np, int sep)
{
- char *p;
- const char *cp;
- char *start;
- char *endname;
- int metaflag;
- struct stat statb;
- DIR *dirp;
- struct dirent *dp;
- int atend;
- int matchdot;
-
- metaflag = 0;
- start = name;
- for (p = name; *p; p++) {
- if (*p == '*' || *p == '?')
- metaflag = 1;
- else if (*p == '[') {
- char *q = p + 1;
- if (*q == '!')
- q++;
- for (;;) {
- if (*q == '\\')
- q++;
- if (*q == '/' || *q == '\0')
- break;
- if (*++q == ']') {
- metaflag = 1;
- break;
- }
- }
- } else if (*p == '\\')
- p++;
- else if (*p == '/') {
- if (metaflag)
- goto out;
- start = p + 1;
- }
- }
- out:
- if (metaflag == 0) { /* we've reached the end of the file name */
- if (enddir != expdir)
- metaflag++;
- p = name;
- do {
- if (*p == '\\')
- p++;
- *enddir++ = *p;
- } while (*p++);
- if (metaflag == 0 || lstat(expdir, &statb) >= 0)
- addfname(expdir);
- return;
- }
- endname = p;
- if (name < start) {
- p = name;
- do {
- if (*p == '\\')
- p++;
- *enddir++ = *p++;
- } while (p < start);
- }
- if (enddir == expdir) {
- cp = ".";
- } else if (enddir == expdir + 1 && *expdir == '/') {
- cp = "/";
- } else {
- cp = expdir;
- enddir[-1] = '\0';
- }
- dirp = opendir(cp);
- if (dirp == NULL)
- return;
- if (enddir != expdir)
- enddir[-1] = '/';
- if (*endname == 0) {
- atend = 1;
- } else {
- atend = 0;
- *endname++ = '\0';
- }
- matchdot = 0;
- p = start;
- if (*p == '\\')
- p++;
- if (*p == '.')
- matchdot++;
- while (! intpending && (dp = readdir(dirp)) != NULL) {
- if (dp->d_name[0] == '.' && ! matchdot)
- continue;
- if (pmatch(start, dp->d_name)) {
- if (atend) {
- strcpy(enddir, dp->d_name);
- addfname(expdir);
- } else {
- for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
- continue;
- p[-1] = '/';
- expmeta(p, endname);
- }
- }
+ for (; np; np = np->narg.next) {
+ if (!sep)
+ cmdputs(spcstr);
+ cmdtxt(np);
+ if (sep && np->narg.next)
+ cmdputs(spcstr);
}
- closedir(dirp);
- if (! atend)
- endname[-1] = '/';
}
-static struct strlist *
-msort(struct strlist *list, int len)
+static void
+cmdtxt(union node *n)
{
- struct strlist *p, *q = NULL;
- struct strlist **lpp;
- int half;
- int n;
+ union node *np;
+ struct nodelist *lp;
+ const char *p;
+ char s[2];
- if (len <= 1)
- return list;
- half = len >> 1;
- p = list;
- for (n = half; --n >= 0; ) {
- q = p;
- p = p->next;
- }
- q->next = NULL; /* terminate first half of list */
- q = msort(list, half); /* sort first half of list */
- p = msort(p, len - half); /* sort second half */
- lpp = &list;
- for (;;) {
-#if ENABLE_LOCALE_SUPPORT
- if (strcoll(p->text, q->text) < 0)
-#else
- if (strcmp(p->text, q->text) < 0)
+ if (!n)
+ return;
+ switch (n->type) {
+ default:
+#if DEBUG
+ abort();
#endif
- {
- *lpp = p;
- lpp = &p->next;
- p = *lpp;
- if (p == NULL) {
- *lpp = q;
- break;
- }
- } else {
- *lpp = q;
- lpp = &q->next;
- q = *lpp;
- if (q == NULL) {
- *lpp = p;
+ case NPIPE:
+ lp = n->npipe.cmdlist;
+ for (;;) {
+ cmdtxt(lp->n);
+ lp = lp->next;
+ if (!lp)
break;
- }
+ cmdputs(" | ");
}
- }
- return list;
-}
-
-/*
- * Sort the results of file name expansion. It calculates the number of
- * strings to sort and then calls msort (short for merge sort) to do the
- * work.
- */
-static struct strlist *
-expsort(struct strlist *str)
-{
- int len;
- struct strlist *sp;
-
- len = 0;
- for (sp = str; sp; sp = sp->next)
- len++;
- return msort(str, len);
-}
-
-static void
-expandmeta(struct strlist *str, int flag)
-{
- static const char metachars[] = {
- '*', '?', '[', 0
- };
- /* TODO - EXP_REDIR */
-
- while (str) {
- struct strlist **savelastp;
- struct strlist *sp;
- char *p;
-
- if (fflag)
- goto nometa;
- if (!strpbrk(str->text, metachars))
- goto nometa;
- savelastp = exparg.lastp;
-
- INT_OFF;
- p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
- {
- int i = strlen(str->text);
- expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+ break;
+ case NSEMI:
+ p = "; ";
+ goto binop;
+ case NAND:
+ p = " && ";
+ goto binop;
+ case NOR:
+ p = " || ";
+ binop:
+ cmdtxt(n->nbinary.ch1);
+ cmdputs(p);
+ n = n->nbinary.ch2;
+ goto donode;
+ case NREDIR:
+ case NBACKGND:
+ n = n->nredir.n;
+ goto donode;
+ case NNOT:
+ cmdputs("!");
+ n = n->nnot.com;
+ donode:
+ cmdtxt(n);
+ break;
+ case NIF:
+ cmdputs("if ");
+ cmdtxt(n->nif.test);
+ cmdputs("; then ");
+ n = n->nif.ifpart;
+ if (n->nif.elsepart) {
+ cmdtxt(n);
+ cmdputs("; else ");
+ n = n->nif.elsepart;
+ }
+ p = "; fi";
+ goto dotail;
+ case NSUBSHELL:
+ cmdputs("(");
+ n = n->nredir.n;
+ p = ")";
+ goto dotail;
+ case NWHILE:
+ p = "while ";
+ goto until;
+ case NUNTIL:
+ p = "until ";
+ until:
+ cmdputs(p);
+ cmdtxt(n->nbinary.ch1);
+ n = n->nbinary.ch2;
+ p = "; done";
+ dodo:
+ cmdputs("; do ");
+ dotail:
+ cmdtxt(n);
+ goto dotail2;
+ case NFOR:
+ cmdputs("for ");
+ cmdputs(n->nfor.var);
+ cmdputs(" in ");
+ cmdlist(n->nfor.args, 1);
+ n = n->nfor.body;
+ p = "; done";
+ goto dodo;
+ case NDEFUN:
+ cmdputs(n->narg.text);
+ p = "() { ... }";
+ goto dotail2;
+ case NCMD:
+ cmdlist(n->ncmd.args, 1);
+ cmdlist(n->ncmd.redirect, 0);
+ break;
+ case NARG:
+ p = n->narg.text;
+ dotail2:
+ cmdputs(p);
+ break;
+ case NHERE:
+ case NXHERE:
+ p = "<<...";
+ goto dotail2;
+ case NCASE:
+ cmdputs("case ");
+ cmdputs(n->ncase.expr->narg.text);
+ cmdputs(" in ");
+ for (np = n->ncase.cases; np; np = np->nclist.next) {
+ cmdtxt(np->nclist.pattern);
+ cmdputs(") ");
+ cmdtxt(np->nclist.body);
+ cmdputs(";; ");
}
-
- expmeta(expdir, p);
- free(expdir);
- if (p != str->text)
- free(p);
- INT_ON;
- if (exparg.lastp == savelastp) {
- /*
- * no matches
- */
- nometa:
- *exparg.lastp = str;
- rmescapes(str->text);
- exparg.lastp = &str->next;
- } else {
- *exparg.lastp = NULL;
- *savelastp = sp = expsort(*savelastp);
- while (sp->next != NULL)
- sp = sp->next;
- exparg.lastp = &sp->next;
+ p = "esac";
+ goto dotail2;
+ case NTO:
+ p = ">";
+ goto redir;
+ case NCLOBBER:
+ p = ">|";
+ goto redir;
+ case NAPPEND:
+ p = ">>";
+ goto redir;
+ case NTOFD:
+ p = ">&";
+ goto redir;
+ case NFROM:
+ p = "<";
+ goto redir;
+ case NFROMFD:
+ p = "<&";
+ goto redir;
+ case NFROMTO:
+ p = "<>";
+ redir:
+ s[0] = n->nfile.fd + '0';
+ s[1] = '\0';
+ cmdputs(s);
+ cmdputs(p);
+ if (n->type == NTOFD || n->type == NFROMFD) {
+ s[0] = n->ndup.dupfd + '0';
+ p = s;
+ goto dotail2;
}
- str = str->next;
- }
-}
-
-/*
- * Perform variable substitution and command substitution on an argument,
- * placing the resulting list of arguments in arglist. If EXP_FULL is true,
- * perform splitting and file name expansion. When arglist is NULL, perform
- * here document expansion.
- */
-static void
-expandarg(union node *arg, struct arglist *arglist, int flag)
-{
- struct strlist *sp;
- char *p;
-
- argbackq = arg->narg.backquote;
- STARTSTACKSTR(expdest);
- ifsfirst.next = NULL;
- ifslastp = NULL;
- argstr(arg->narg.text, flag);
- p = _STPUTC('\0', expdest);
- expdest = p - 1;
- if (arglist == NULL) {
- return; /* here document expanded */
- }
- p = grabstackstr(p);
- exparg.lastp = &exparg.list;
- /*
- * TODO - EXP_REDIR
- */
- if (flag & EXP_FULL) {
- ifsbreakup(p, &exparg);
- *exparg.lastp = NULL;
- exparg.lastp = &exparg.list;
- expandmeta(exparg.list, flag);
- } else {
- if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
- rmescapes(p);
- sp = stalloc(sizeof(*sp));
- sp->text = p;
- *exparg.lastp = sp;
- exparg.lastp = &sp->next;
- }
- if (ifsfirst.next)
- ifsfree();
- *exparg.lastp = NULL;
- if (exparg.list) {
- *arglist->lastp = exparg.list;
- arglist->lastp = exparg.lastp;
+ n = n->nfile.fname;
+ goto donode;
}
}
-/*
- * Expand shell variables and backquotes inside a here document.
- */
-static void
-expandhere(union node *arg, int fd)
-{
- herefd = fd;
- expandarg(arg, (struct arglist *)NULL, 0);
- full_write(fd, stackblock(), expdest - (char *)stackblock());
-}
-
-/*
- * Returns true if the pattern matches the string.
- */
-static int
-patmatch(char *pattern, const char *string)
-{
- return pmatch(preglob(pattern, 0, 0), string);
-}
-
-/*
- * See if a pattern matches in a case statement.
- */
-static int
-casematch(union node *pattern, char *val)
-{
- struct stackmark smark;
- int result;
-
- setstackmark(&smark);
- argbackq = pattern->narg.backquote;
- STARTSTACKSTR(expdest);
- ifslastp = NULL;
- argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
- STACKSTRNUL(expdest);
- result = patmatch(stackblock(), val);
- popstackmark(&smark);
- return result;
-}
-
-
-/* ============ find_command */
-
-static int is_safe_applet(char *name)
+static char *
+commandtext(union node *n)
{
- /* It isn't a bug to have non-existent applet here... */
- /* ...just a waste of space... */
- static const char safe_applets[][8] = {
- "["
- USE_AWK (, "awk" )
- USE_CAT (, "cat" )
- USE_CHMOD (, "chmod" )
- USE_CHOWN (, "chown" )
- USE_CP (, "cp" )
- USE_CUT (, "cut" )
- USE_DD (, "dd" )
- USE_ECHO (, "echo" )
- USE_FIND (, "find" )
- USE_HEXDUMP(, "hexdump")
- USE_LN (, "ln" )
- USE_LS (, "ls" )
- USE_MKDIR (, "mkdir" )
- USE_RM (, "rm" )
- USE_SORT (, "sort" )
- USE_TEST (, "test" )
- USE_TOUCH (, "touch" )
- USE_XARGS (, "xargs" )
- };
- int n = sizeof(safe_applets) / sizeof(safe_applets[0]);
- int i;
- for (i = 0; i < n; i++)
- if (strcmp(safe_applets[i], name) == 0)
- return 1;
+ char *name;
- return 0;
+ STARTSTACKSTR(cmdnextc);
+ cmdtxt(n);
+ name = stackblock();
+ TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
+ name, cmdnextc, cmdnextc));
+ return ckstrdup(name);
}
-
-struct builtincmd {
- const char *name;
- int (*builtin)(int, char **);
- /* unsigned flags; */
-};
-#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1)
-#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2)
-#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4)
-
-struct cmdentry {
- int cmdtype;
- union param {
- int index;
- const struct builtincmd *cmd;
- struct funcnode *func;
- } u;
-};
-/* values of cmdtype */
-#define CMDUNKNOWN -1 /* no entry in table for command */
-#define CMDNORMAL 0 /* command is an executable program */
-#define CMDFUNCTION 1 /* command is a shell function */
-#define CMDBUILTIN 2 /* command is a shell builtin */
-
-/* action to find_command() */
-#define DO_ERR 0x01 /* prints errors */
-#define DO_ABS 0x02 /* checks absolute paths */
-#define DO_NOFUNC 0x04 /* don't return shell functions, for command */
-#define DO_ALTPATH 0x08 /* using alternate path */
-#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */
-
-static void find_command(char *, struct cmdentry *, int, const char *);
-
-
-/* ============ Hashing commands */
+#endif /* JOBS */
/*
- * When commands are first encountered, they are entered in a hash table.
- * This ensures that a full path search will not have to be done for them
- * on each invocation.
+ * 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.
*
- * We should investigate converting to a linear search, even though that
- * would make the command name "hash" a misnomer.
+ * When job control is turned off, background processes have their standard
+ * input redirected to /dev/null (except for the second and later processes
+ * in a pipeline).
+ *
+ * Called with interrupts off.
+ */
+/*
+ * Clear traps on a fork.
*/
-
-#define CMDTABLESIZE 31 /* should be prime */
-#define ARB 1 /* actual size determined at run time */
-
-struct tblentry {
- struct tblentry *next; /* next entry in hash chain */
- union param param; /* definition of builtin function */
- short cmdtype; /* index identifying command */
- char rehash; /* if set, cd done since entry created */
- char cmdname[ARB]; /* name of command */
-};
-
-static struct tblentry *cmdtable[CMDTABLESIZE];
-static int builtinloc = -1; /* index in path of %builtin, or -1 */
-
static void
-tryexec(char *cmd, char **argv, char **envp)
+clear_traps(void)
{
- int repeated = 0;
- struct BB_applet *a;
- int argc = 0;
- char **c;
+ char **tp;
- if (strchr(cmd, '/') == NULL
- && (a = find_applet_by_name(cmd)) != NULL
- && is_safe_applet(cmd)
- ) {
- c = argv;
- while (*c != NULL) {
- c++; argc++;
+ for (tp = trap; tp < &trap[NSIG]; tp++) {
+ if (*tp && **tp) { /* trap not NULL or SIG_IGN */
+ INT_OFF;
+ free(*tp);
+ *tp = NULL;
+ if (tp != &trap[0])
+ setsignal(tp - trap);
+ INT_ON;
}
- applet_name = cmd;
- exit(a->main(argc, argv));
- }
-#if ENABLE_FEATURE_SH_STANDALONE_SHELL
- if (find_applet_by_name(cmd) != NULL) {
- /* re-exec ourselves with the new arguments */
- execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp);
- /* If they called chroot or otherwise made the binary no longer
- * executable, fall through */
}
-#endif
+}
+/* lives far away from here, needed for forkchild */
+static void closescript(void);
+static void
+forkchild(struct job *jp, union node *n, int mode)
+{
+ int oldlvl;
- repeat:
-#ifdef SYSV
- do {
- execve(cmd, argv, envp);
- } while (errno == EINTR);
-#else
- execve(cmd, argv, envp);
-#endif
- if (repeated++) {
- free(argv);
- } else if (errno == ENOEXEC) {
- char **ap;
- char **new;
+ TRACE(("Child shell %d\n", getpid()));
+ oldlvl = shlvl;
+ shlvl++;
- for (ap = argv; *ap; ap++)
- ;
- ap = new = ckmalloc((ap - argv + 2) * sizeof(char *));
- ap[1] = cmd;
- *ap = cmd = (char *)DEFAULT_SHELL;
- ap += 2;
- argv++;
- while ((*ap++ = *argv++))
- ;
- argv = new;
- goto repeat;
+ closescript();
+ clear_traps();
+#if JOBS
+ /* do job control only in root shell */
+ jobctl = 0;
+ if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
+ pid_t pgrp;
+
+ if (jp->nprocs == 0)
+ pgrp = getpid();
+ else
+ pgrp = jp->ps[0].pid;
+ /* This can fail because we are doing it in the parent also */
+ (void)setpgid(0, pgrp);
+ if (mode == FORK_FG)
+ xtcsetpgrp(ttyfd, pgrp);
+ setsignal(SIGTSTP);
+ setsignal(SIGTTOU);
+ } else
+#endif
+ if (mode == FORK_BG) {
+ ignoresig(SIGINT);
+ ignoresig(SIGQUIT);
+ if (jp->nprocs == 0) {
+ close(0);
+ if (open(bb_dev_null, O_RDONLY) != 0)
+ ash_msg_and_raise_error("Can't open %s", bb_dev_null);
+ }
+ }
+ if (!oldlvl && iflag) {
+ setsignal(SIGINT);
+ setsignal(SIGQUIT);
+ setsignal(SIGTERM);
}
+ for (jp = curjob; jp; jp = jp->prev_job)
+ freejob(jp);
+ jobless = 0;
}
-/*
- * Exec a program. Never returns. If you change this routine, you may
- * have to change the find_command routine as well.
- */
-#define environment() listvars(VEXPORT, VUNSET, 0)
-static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
static void
-shellexec(char **argv, const char *path, int idx)
+forkparent(struct job *jp, union node *n, int mode, pid_t pid)
{
- char *cmdname;
- int e;
- char **envp;
- int exerrno;
+ TRACE(("In parent shell: child = %d\n", pid));
+ if (!jp) {
+ while (jobless && dowait(DOWAIT_NORMAL, 0) > 0);
+ jobless++;
+ return;
+ }
+#if JOBS
+ if (mode != FORK_NOJOB && jp->jobctl) {
+ int pgrp;
- clearredir(1);
- envp = environment();
- if (strchr(argv[0], '/') || is_safe_applet(argv[0])
-#if ENABLE_FEATURE_SH_STANDALONE_SHELL
- || find_applet_by_name(argv[0])
+ if (jp->nprocs == 0)
+ pgrp = pid;
+ else
+ pgrp = jp->ps[0].pid;
+ /* This can fail because we are doing it in the child also */
+ setpgid(pid, pgrp);
+ }
#endif
- ) {
- tryexec(argv[0], argv, envp);
- e = errno;
- } else {
- e = ENOENT;
- while ((cmdname = padvance(&path, argv[0])) != NULL) {
- if (--idx < 0 && pathopt == NULL) {
- tryexec(cmdname, argv, envp);
- if (errno != ENOENT && errno != ENOTDIR)
- e = errno;
- }
- stunalloc(cmdname);
- }
+ if (mode == FORK_BG) {
+ backgndpid = pid; /* set $! */
+ set_curjob(jp, CUR_RUNNING);
}
-
- /* Map to POSIX errors */
- switch (e) {
- case EACCES:
- exerrno = 126;
- break;
- case ENOENT:
- exerrno = 127;
- break;
- default:
- exerrno = 2;
- break;
+ if (jp) {
+ struct procstat *ps = &jp->ps[jp->nprocs++];
+ ps->pid = pid;
+ ps->status = -1;
+ ps->cmd = nullstr;
+#if JOBS
+ if (jobctl && n)
+ ps->cmd = commandtext(n);
+#endif
}
- exitstatus = exerrno;
- TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
- argv[0], e, suppressint ));
- ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
- /* NOTREACHED */
}
-static void
-printentry(struct tblentry *cmdp)
+static int
+forkshell(struct job *jp, union node *n, int mode)
{
- int idx;
- const char *path;
- char *name;
+ int pid;
- idx = cmdp->param.index;
- path = pathval();
- do {
- name = padvance(&path, cmdp->cmdname);
- stunalloc(name);
- } while (--idx >= 0);
- out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr));
+ TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
+ pid = fork();
+ if (pid < 0) {
+ TRACE(("Fork failed, errno=%d", errno));
+ if (jp)
+ freejob(jp);
+ ash_msg_and_raise_error("Cannot fork");
+ }
+ if (pid == 0)
+ forkchild(jp, n, mode);
+ else
+ forkparent(jp, n, mode, pid);
+ return pid;
}
/*
- * Clear out command entries. The argument specifies the first entry in
- * PATH which has changed.
+ * 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
+ * foreground process to terminate, and then send itself an interrupt
+ * signal if the child process was terminated by an interrupt signal.
+ * Unfortunately, some programs want to do a bit of cleanup and then
+ * exit on interrupt; unless these processes terminate themselves by
+ * sending a signal to themselves (instead of calling exit) they will
+ * confuse this approach.
+ *
+ * Called with interrupts off.
*/
-static void
-clearcmdentry(int firstchange)
+static int
+waitforjob(struct job *jp)
{
- struct tblentry **tblp;
- struct tblentry **pp;
- struct tblentry *cmdp;
+ int st;
- INT_OFF;
- for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
- pp = tblp;
- while ((cmdp = *pp) != NULL) {
- if ((cmdp->cmdtype == CMDNORMAL &&
- cmdp->param.index >= firstchange)
- || (cmdp->cmdtype == CMDBUILTIN &&
- builtinloc >= firstchange)
- ) {
- *pp = cmdp->next;
- free(cmdp);
- } else {
- pp = &cmdp->next;
- }
- }
+ TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
+ while (jp->state == JOBRUNNING) {
+ dowait(DOWAIT_BLOCK, jp);
+ }
+ st = getstatus(jp);
+#if JOBS
+ if (jp->jobctl) {
+ xtcsetpgrp(ttyfd, rootpid);
+ /*
+ * This is truly gross.
+ * If we're doing job control, then we did a TIOCSPGRP which
+ * caused us (the shell) to no longer be in the controlling
+ * session -- so we wouldn't have seen any ^C/SIGINT. So, we
+ * intuit from the subprocess exit status whether a SIGINT
+ * occurred, and if so interrupt ourselves. Yuck. - mycroft
+ */
+ if (jp->sigint)
+ raise(SIGINT);
}
- INT_ON;
+ if (jp->state == JOBDONE)
+#endif
+ freejob(jp);
+ return st;
}
/*
- * Locate a command in the command hash table. If "add" is nonzero,
- * add the command to the table if it is not already present. The
- * variable "lastcmdentry" is set to point to the address of the link
- * pointing to the entry, so that delete_cmd_entry can delete the
- * entry.
- *
- * Interrupts must be off if called with add != 0.
+ * return 1 if there are stopped jobs, otherwise 0
*/
-static struct tblentry **lastcmdentry;
-
-static struct tblentry *
-cmdlookup(const char *name, int add)
+static int
+stoppedjobs(void)
{
- unsigned int hashval;
- const char *p;
- struct tblentry *cmdp;
- struct tblentry **pp;
+ struct job *jp;
+ int retval;
- p = name;
- hashval = (unsigned char)*p << 4;
- while (*p)
- hashval += (unsigned char)*p++;
- hashval &= 0x7FFF;
- pp = &cmdtable[hashval % CMDTABLESIZE];
- for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
- if (strcmp(cmdp->cmdname, name) == 0)
- break;
- pp = &cmdp->next;
- }
- if (add && cmdp == NULL) {
- cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB
- + strlen(name) + 1);
- cmdp->next = NULL;
- cmdp->cmdtype = CMDUNKNOWN;
- strcpy(cmdp->cmdname, name);
+ retval = 0;
+ if (job_warning)
+ goto out;
+ jp = curjob;
+ if (jp && jp->state == JOBSTOPPED) {
+ out2str("You have stopped jobs.\n");
+ job_warning = 2;
+ retval++;
}
- lastcmdentry = pp;
- return cmdp;
+ out:
+ return retval;
}
+
+/* ============ Routines to expand arguments to commands
+ *
+ * We have to deal with backquotes, shell variables, and file metacharacters.
+ */
+
/*
- * Delete the command entry returned on the last lookup.
+ * expandarg flags
*/
-static void
-delete_cmd_entry(void)
-{
- struct tblentry *cmdp;
+#define EXP_FULL 0x1 /* perform word splitting & file globbing */
+#define EXP_TILDE 0x2 /* do normal tilde expansion */
+#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */
+#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */
+#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */
+#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */
+#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */
+#define EXP_WORD 0x80 /* expand word in parameter expansion */
+#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */
+/*
+ * _rmescape() flags
+ */
+#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */
+#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */
+#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */
+#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */
+#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
- INT_OFF;
- cmdp = *lastcmdentry;
- *lastcmdentry = cmdp->next;
- if (cmdp->cmdtype == CMDFUNCTION)
- freefunc(cmdp->param.func);
- free(cmdp);
- INT_ON;
-}
+/*
+ * Structure specifying which parts of the string should be searched
+ * for IFS characters.
+ */
+struct ifsregion {
+ struct ifsregion *next; /* next region in list */
+ int begoff; /* offset of start of region */
+ int endoff; /* offset of end of region */
+ int nulonly; /* search for nul bytes only */
+};
+
+struct arglist {
+ struct strlist *list;
+ struct strlist **lastp;
+};
+
+/* output of current string */
+static char *expdest;
+/* list of back quote expressions */
+static struct nodelist *argbackq;
+/* first struct in list of ifs regions */
+static struct ifsregion ifsfirst;
+/* last struct in list */
+static struct ifsregion *ifslastp;
+/* holds expanded arg list */
+static struct arglist exparg;
/*
- * Add a new command entry, replacing any existing command entry for
- * the same name - except special builtins.
+ * Our own itoa().
*/
-static void
-addcmdentry(char *name, struct cmdentry *entry)
+static int
+cvtnum(arith_t num)
{
- struct tblentry *cmdp;
+ int len;
- cmdp = cmdlookup(name, 1);
- if (cmdp->cmdtype == CMDFUNCTION) {
- freefunc(cmdp->param.func);
+ expdest = makestrspace(32, expdest);
+#if ENABLE_ASH_MATH_SUPPORT_64
+ len = fmtstr(expdest, 32, "%lld", (long long) num);
+#else
+ len = fmtstr(expdest, 32, "%ld", num);
+#endif
+ STADJUST(len, expdest);
+ return len;
+}
+
+static size_t
+esclen(const char *start, const char *p)
+{
+ size_t esc = 0;
+
+ while (p > start && *--p == CTLESC) {
+ esc++;
}
- cmdp->cmdtype = entry->cmdtype;
- cmdp->param = entry->u;
- cmdp->rehash = 0;
+ return esc;
}
-static int
-hashcmd(int argc, char **argv)
+/*
+ * Remove any CTLESC characters from a string.
+ */
+static char *
+_rmescapes(char *str, int flag)
{
- struct tblentry **pp;
- struct tblentry *cmdp;
- int c;
- struct cmdentry entry;
- char *name;
+ char *p, *q, *r;
+ static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 };
+ unsigned inquotes;
+ int notescaped;
+ int globbing;
- while ((c = nextopt("r")) != '\0') {
- clearcmdentry(0);
- return 0;
+ p = strpbrk(str, qchars);
+ if (!p) {
+ return str;
}
- if (*argptr == NULL) {
- for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
- for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
- if (cmdp->cmdtype == CMDNORMAL)
- printentry(cmdp);
+ q = p;
+ r = str;
+ if (flag & RMESCAPE_ALLOC) {
+ size_t len = p - str;
+ size_t fulllen = len + strlen(p) + 1;
+
+ if (flag & RMESCAPE_GROW) {
+ r = makestrspace(fulllen, expdest);
+ } else if (flag & RMESCAPE_HEAP) {
+ r = ckmalloc(fulllen);
+ } else {
+ r = stalloc(fulllen);
+ }
+ q = r;
+ if (len > 0) {
+ q = memcpy(q, str, len) + len;
+ }
+ }
+ inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED;
+ globbing = flag & RMESCAPE_GLOB;
+ notescaped = globbing;
+ while (*p) {
+ if (*p == CTLQUOTEMARK) {
+ inquotes = ~inquotes;
+ p++;
+ notescaped = globbing;
+ continue;
+ }
+ if (*p == '\\') {
+ /* naked back slash */
+ notescaped = 0;
+ goto copy;
+ }
+ if (*p == CTLESC) {
+ p++;
+ if (notescaped && inquotes && *p != '/') {
+ *q++ = '\\';
}
}
- return 0;
+ notescaped = globbing;
+ copy:
+ *q++ = *p++;
}
- c = 0;
- while ((name = *argptr) != NULL) {
- cmdp = cmdlookup(name, 0);
- if (cmdp != NULL
- && (cmdp->cmdtype == CMDNORMAL
- || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
- delete_cmd_entry();
- find_command(name, &entry, DO_ERR, pathval());
- if (entry.cmdtype == CMDUNKNOWN)
- c = 1;
- argptr++;
+ *q = '\0';
+ if (flag & RMESCAPE_GROW) {
+ expdest = r;
+ STADJUST(q - r + 1, expdest);
+ }
+ return r;
+}
+#define rmescapes(p) _rmescapes((p), 0)
+
+#define pmatch(a, b) !fnmatch((a), (b), 0)
+
+/*
+ * Prepare a pattern for a expmeta (internal glob(3)) call.
+ *
+ * Returns an stalloced string.
+ */
+static char *
+preglob(const char *pattern, int quoted, int flag)
+{
+ flag |= RMESCAPE_GLOB;
+ if (quoted) {
+ flag |= RMESCAPE_QUOTED;
}
- return c;
+ return _rmescapes((char *)pattern, flag);
}
/*
- * Called when a cd is done. Marks all commands so the next time they
- * are executed they will be rehashed.
+ * Put a string on the stack.
*/
static void
-hashcd(void)
+memtodest(const char *p, size_t len, int syntax, int quotes)
{
- struct tblentry **pp;
- struct tblentry *cmdp;
+ char *q = expdest;
- for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
- for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
- if (cmdp->cmdtype == CMDNORMAL || (
- cmdp->cmdtype == CMDBUILTIN &&
- !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) &&
- builtinloc > 0
- ))
- cmdp->rehash = 1;
- }
+ q = makestrspace(len * 2, q);
+
+ while (len--) {
+ int c = SC2INT(*p++);
+ if (!c)
+ continue;
+ if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK))
+ USTPUTC(CTLESC, q);
+ USTPUTC(c, q);
}
+
+ expdest = q;
+}
+
+static void
+strtodest(const char *p, int syntax, int quotes)
+{
+ memtodest(p, strlen(p), syntax, quotes);
}
/*
- * Fix command hash table when PATH changed.
- * Called before PATH is changed. The argument is the new value of PATH;
- * pathval() still returns the old value at this point.
- * Called with interrupts off.
+ * Record the fact that we have to scan this region of the
+ * string for IFS characters.
*/
static void
-changepath(const char *newval)
+recordregion(int start, int end, int nulonly)
{
- const char *old, *new;
- int idx;
- int firstchange;
- int idx_bltin;
+ struct ifsregion *ifsp;
- old = pathval();
- new = newval;
- firstchange = 9999; /* assume no change */
- idx = 0;
- idx_bltin = -1;
- for (;;) {
- if (*old != *new) {
- firstchange = idx;
- if ((*old == '\0' && *new == ':')
- || (*old == ':' && *new == '\0'))
- firstchange++;
- old = new; /* ignore subsequent differences */
- }
- if (*new == '\0')
- break;
- if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
- idx_bltin = idx;
- if (*new == ':') {
- idx++;
- }
- new++, old++;
+ if (ifslastp == NULL) {
+ ifsp = &ifsfirst;
+ } else {
+ INT_OFF;
+ ifsp = ckmalloc(sizeof(*ifsp));
+ ifsp->next = NULL;
+ ifslastp->next = ifsp;
+ INT_ON;
}
- if (builtinloc < 0 && idx_bltin >= 0)
- builtinloc = idx_bltin; /* zap builtins */
- if (builtinloc >= 0 && idx_bltin < 0)
- firstchange = 0;
- clearcmdentry(firstchange);
- builtinloc = idx_bltin;
+ ifslastp = ifsp;
+ ifslastp->begoff = start;
+ ifslastp->endoff = end;
+ ifslastp->nulonly = nulonly;
}
-#define TEOF 0
-#define TNL 1
-#define TREDIR 2
-#define TWORD 3
-#define TSEMI 4
-#define TBACKGND 5
-#define TAND 6
-#define TOR 7
-#define TPIPE 8
-#define TLP 9
-#define TRP 10
-#define TENDCASE 11
-#define TENDBQUOTE 12
-#define TNOT 13
-#define TCASE 14
-#define TDO 15
-#define TDONE 16
-#define TELIF 17
-#define TELSE 18
-#define TESAC 19
-#define TFI 20
-#define TFOR 21
-#define TIF 22
-#define TIN 23
-#define TTHEN 24
-#define TUNTIL 25
-#define TWHILE 26
-#define TBEGIN 27
-#define TEND 28
-
-/* first char is indicating which tokens mark the end of a list */
-static const char *const tokname_array[] = {
- "\1end of file",
- "\0newline",
- "\0redirection",
- "\0word",
- "\0;",
- "\0&",
- "\0&&",
- "\0||",
- "\0|",
- "\0(",
- "\1)",
- "\1;;",
- "\1`",
-#define KWDOFFSET 13
- /* the following are keywords */
- "\0!",
- "\0case",
- "\1do",
- "\1done",
- "\1elif",
- "\1else",
- "\1esac",
- "\1fi",
- "\0for",
- "\0if",
- "\0in",
- "\1then",
- "\0until",
- "\0while",
- "\0{",
- "\1}",
-};
-
-static const char *
-tokname(int tok)
+static void
+removerecordregions(int endoff)
{
- static char buf[16];
+ if (ifslastp == NULL)
+ return;
- if (tok >= TSEMI)
- buf[0] = '"';
- sprintf(buf + (tok >= TSEMI), "%s%c",
- tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
- return buf;
-}
+ if (ifsfirst.endoff > endoff) {
+ while (ifsfirst.next != NULL) {
+ struct ifsregion *ifsp;
+ INT_OFF;
+ ifsp = ifsfirst.next->next;
+ free(ifsfirst.next);
+ ifsfirst.next = ifsp;
+ INT_ON;
+ }
+ if (ifsfirst.begoff > endoff)
+ ifslastp = NULL;
+ else {
+ ifslastp = &ifsfirst;
+ ifsfirst.endoff = endoff;
+ }
+ return;
+ }
-/* Wrapper around strcmp for qsort/bsearch/... */
-static int
-pstrcmp(const void *a, const void *b)
-{
- return strcmp((const char *) a, (*(const char *const *) b) + 1);
+ ifslastp = &ifsfirst;
+ while (ifslastp->next && ifslastp->next->begoff < endoff)
+ ifslastp=ifslastp->next;
+ while (ifslastp->next != NULL) {
+ struct ifsregion *ifsp;
+ INT_OFF;
+ ifsp = ifslastp->next->next;
+ free(ifslastp->next);
+ ifslastp->next = ifsp;
+ INT_ON;
+ }
+ if (ifslastp->endoff > endoff)
+ ifslastp->endoff = endoff;
}
-static const char *const *
-findkwd(const char *s)
+static char *
+exptilde(char *startp, char *p, int flag)
{
- return bsearch(s, tokname_array + KWDOFFSET,
- (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
- sizeof(const char *), pstrcmp);
+ char c;
+ char *name;
+ struct passwd *pw;
+ const char *home;
+ int quotes = flag & (EXP_FULL | EXP_CASE);
+ int startloc;
+
+ name = p + 1;
+
+ while ((c = *++p) != '\0') {
+ switch (c) {
+ case CTLESC:
+ return startp;
+ case CTLQUOTEMARK:
+ return startp;
+ case ':':
+ if (flag & EXP_VARTILDE)
+ goto done;
+ break;
+ case '/':
+ case CTLENDVAR:
+ goto done;
+ }
+ }
+ done:
+ *p = '\0';
+ if (*name == '\0') {
+ home = lookupvar(homestr);
+ } else {
+ pw = getpwnam(name);
+ if (pw == NULL)
+ goto lose;
+ home = pw->pw_dir;
+ }
+ if (!home || !*home)
+ goto lose;
+ *p = c;
+ startloc = expdest - (char *)stackblock();
+ strtodest(home, SQSYNTAX, quotes);
+ recordregion(startloc, expdest - (char *)stackblock(), 0);
+ return p;
+ lose:
+ *p = c;
+ return startp;
}
/*
- * Locate and print what a word is...
+ * Execute a command inside back quotes. If it's a builtin command, we
+ * want to save its output in a block obtained from malloc. Otherwise
+ * we fork off a subprocess and get the output of the command via a pipe.
+ * Should be called with interrupts off.
*/
-#if ENABLE_ASH_CMDCMD
-static int
-describe_command(char *command, int describe_command_verbose)
-#else
-#define describe_command_verbose 1
-static int
-describe_command(char *command)
-#endif
-{
- struct cmdentry entry;
- struct tblentry *cmdp;
-#if ENABLE_ASH_ALIAS
- const struct alias *ap;
-#endif
- const char *path = pathval();
+struct backcmd { /* result of evalbackcmd */
+ int fd; /* file descriptor to read from */
+ char *buf; /* buffer */
+ int nleft; /* number of chars in buffer */
+ struct job *jp; /* job structure for command */
+};
- if (describe_command_verbose) {
- out1str(command);
- }
+/* These forward decls are needed to use "eval" code for backticks handling: */
+static int back_exitstatus; /* exit status of backquoted command */
+#define EV_EXIT 01 /* exit after evaluating tree */
+static void evaltree(union node *, int);
- /* First look at the keywords */
- if (findkwd(command)) {
- out1str(describe_command_verbose ? " is a shell keyword" : command);
- goto out;
- }
+static void
+evalbackcmd(union node *n, struct backcmd *result)
+{
+ int saveherefd;
-#if ENABLE_ASH_ALIAS
- /* Then look at the aliases */
- ap = lookupalias(command, 0);
- if (ap != NULL) {
- if (describe_command_verbose) {
- out1fmt(" is an alias for %s", ap->val);
- } else {
- out1str("alias ");
- printalias(ap);
- return 0;
- }
+ result->fd = -1;
+ result->buf = NULL;
+ result->nleft = 0;
+ result->jp = NULL;
+ if (n == NULL) {
goto out;
}
-#endif
- /* Then check if it is a tracked alias */
- cmdp = cmdlookup(command, 0);
- if (cmdp != NULL) {
- entry.cmdtype = cmdp->cmdtype;
- entry.u = cmdp->param;
- } else {
- /* Finally use brute force */
- find_command(command, &entry, DO_ABS, path);
- }
-
- switch (entry.cmdtype) {
- case CMDNORMAL: {
- int j = entry.u.index;
- char *p;
- if (j == -1) {
- p = command;
- } else {
- do {
- p = padvance(&path, command);
- stunalloc(p);
- } while (--j >= 0);
- }
- if (describe_command_verbose) {
- out1fmt(" is%s %s",
- (cmdp ? " a tracked alias for" : nullstr), p
- );
- } else {
- out1str(p);
- }
- break;
- }
- case CMDFUNCTION:
- if (describe_command_verbose) {
- out1str(" is a shell function");
- } else {
- out1str(command);
- }
- break;
+ saveherefd = herefd;
+ herefd = -1;
- case CMDBUILTIN:
- if (describe_command_verbose) {
- out1fmt(" is a %sshell builtin",
- IS_BUILTIN_SPECIAL(entry.u.cmd) ?
- "special " : nullstr
- );
- } else {
- out1str(command);
- }
- break;
+ {
+ int pip[2];
+ struct job *jp;
- default:
- if (describe_command_verbose) {
- out1str(": not found\n");
+ if (pipe(pip) < 0)
+ ash_msg_and_raise_error("Pipe call failed");
+ jp = makejob(n, 1);
+ if (forkshell(jp, n, FORK_NOJOB) == 0) {
+ FORCE_INT_ON;
+ close(pip[0]);
+ if (pip[1] != 1) {
+ close(1);
+ copyfd(pip[1], 1);
+ close(pip[1]);
+ }
+ eflag = 0;
+ evaltree(n, EV_EXIT); /* actually evaltreenr... */
+ /* NOTREACHED */
}
- return 127;
+ close(pip[1]);
+ result->fd = pip[0];
+ result->jp = jp;
}
+ herefd = saveherefd;
out:
- outstr("\n", stdout);
- return 0;
+ TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n",
+ result->fd, result->buf, result->nleft, result->jp));
}
-static int
-typecmd(int argc, char **argv)
+/*
+ * Expand stuff in backwards quotes.
+ */
+static void
+expbackq(union node *cmd, int quoted, int quotes)
{
+ struct backcmd in;
int i;
- int err = 0;
+ char buf[128];
+ char *p;
+ char *dest;
+ int startloc;
+ int syntax = quoted? DQSYNTAX : BASESYNTAX;
+ struct stackmark smark;
- for (i = 1; i < argc; i++) {
-#if ENABLE_ASH_CMDCMD
- err |= describe_command(argv[i], 1);
-#else
- err |= describe_command(argv[i]);
-#endif
+ INT_OFF;
+ setstackmark(&smark);
+ dest = expdest;
+ startloc = dest - (char *)stackblock();
+ grabstackstr(dest);
+ evalbackcmd(cmd, &in);
+ popstackmark(&smark);
+
+ p = in.buf;
+ i = in.nleft;
+ if (i == 0)
+ goto read;
+ for (;;) {
+ memtodest(p, i, syntax, quotes);
+ read:
+ if (in.fd < 0)
+ break;
+ i = safe_read(in.fd, buf, sizeof(buf));
+ TRACE(("expbackq: read returns %d\n", i));
+ if (i <= 0)
+ break;
+ p = buf;
}
- return err;
+
+ if (in.buf)
+ free(in.buf);
+ if (in.fd >= 0) {
+ close(in.fd);
+ back_exitstatus = waitforjob(in.jp);
+ }
+ INT_ON;
+
+ /* Eat all trailing newlines */
+ dest = expdest;
+ for (; dest > (char *)stackblock() && dest[-1] == '\n';)
+ STUNPUTC(dest);
+ expdest = dest;
+
+ if (quoted == 0)
+ recordregion(startloc, dest - (char *)stackblock(), 0);
+ TRACE(("evalbackq: size=%d: \"%.*s\"\n",
+ (dest - (char *)stackblock()) - startloc,
+ (dest - (char *)stackblock()) - startloc,
+ stackblock() + startloc));
}
-#if ENABLE_ASH_CMDCMD
-static int
-commandcmd(int argc, char **argv)
+#if ENABLE_ASH_MATH_SUPPORT
+/*
+ * Expand arithmetic expression. Backup to start of expression,
+ * evaluate, place result in (backed up) result, adjust string position.
+ */
+static void
+expari(int quotes)
{
- int c;
- enum {
- VERIFY_BRIEF = 1,
- VERIFY_VERBOSE = 2,
- } verify = 0;
+ char *p, *start;
+ int begoff;
+ int flag;
+ int len;
- while ((c = nextopt("pvV")) != '\0')
- if (c == 'V')
- verify |= VERIFY_VERBOSE;
- else if (c == 'v')
- verify |= VERIFY_BRIEF;
-#if DEBUG
- else if (c != 'p')
- abort();
-#endif
- if (verify)
- return describe_command(*argptr, verify - VERIFY_BRIEF);
+ /* ifsfree(); */
- return 0;
-}
+ /*
+ * This routine is slightly over-complicated for
+ * efficiency. Next we scan backwards looking for the
+ * start of arithmetic.
+ */
+ start = stackblock();
+ p = expdest - 1;
+ *p = '\0';
+ p--;
+ do {
+ int esc;
+
+ while (*p != CTLARI) {
+ p--;
+#if DEBUG
+ if (p < start) {
+ ash_msg_and_raise_error("missing CTLARI (shouldn't happen)");
+ }
#endif
+ }
+ esc = esclen(start, p);
+ if (!(esc % 2)) {
+ break;
+ }
-/* ============ eval.c */
+ p -= esc + 1;
+ } while (1);
-static int funcblocksize; /* size of structures in function */
-static int funcstringsize; /* size of strings in node */
-static void *funcblock; /* block to allocate function from */
-static char *funcstring; /* block to allocate strings from */
+ begoff = p - start;
-/* flags in argument to evaltree */
-#define EV_EXIT 01 /* exit after evaluating tree */
-#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
-#define EV_BACKCMD 04 /* command executing within back quotes */
+ removerecordregions(begoff);
-static const short nodesize[26] = {
- SHELL_ALIGN(sizeof(struct ncmd)),
- SHELL_ALIGN(sizeof(struct npipe)),
- SHELL_ALIGN(sizeof(struct nredir)),
- SHELL_ALIGN(sizeof(struct nredir)),
- SHELL_ALIGN(sizeof(struct nredir)),
- SHELL_ALIGN(sizeof(struct nbinary)),
- SHELL_ALIGN(sizeof(struct nbinary)),
- SHELL_ALIGN(sizeof(struct nbinary)),
- SHELL_ALIGN(sizeof(struct nif)),
- SHELL_ALIGN(sizeof(struct nbinary)),
- SHELL_ALIGN(sizeof(struct nbinary)),
- SHELL_ALIGN(sizeof(struct nfor)),
- SHELL_ALIGN(sizeof(struct ncase)),
- SHELL_ALIGN(sizeof(struct nclist)),
- SHELL_ALIGN(sizeof(struct narg)),
- SHELL_ALIGN(sizeof(struct narg)),
- SHELL_ALIGN(sizeof(struct nfile)),
- SHELL_ALIGN(sizeof(struct nfile)),
- SHELL_ALIGN(sizeof(struct nfile)),
- SHELL_ALIGN(sizeof(struct nfile)),
- SHELL_ALIGN(sizeof(struct nfile)),
- SHELL_ALIGN(sizeof(struct ndup)),
- SHELL_ALIGN(sizeof(struct ndup)),
- SHELL_ALIGN(sizeof(struct nhere)),
- SHELL_ALIGN(sizeof(struct nhere)),
- SHELL_ALIGN(sizeof(struct nnot)),
-};
+ flag = p[1];
-static void calcsize(union node *n);
+ expdest = p;
-static void
-sizenodelist(struct nodelist *lp)
-{
- while (lp) {
- funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
- calcsize(lp->n);
- lp = lp->next;
- }
+ if (quotes)
+ rmescapes(p + 2);
+
+ len = cvtnum(dash_arith(p + 2));
+
+ if (flag != '"')
+ recordregion(begoff, begoff + len, 0);
}
+#endif
+
+/* argstr needs it */
+static char *evalvar(char *p, int flag);
+/*
+ * Perform variable and command substitution. If EXP_FULL is set, output CTLESC
+ * characters to allow for further processing. Otherwise treat
+ * $@ like $* since no splitting will be performed.
+ */
static void
-calcsize(union node *n)
+argstr(char *p, int flag)
{
- if (n == NULL)
- return;
- funcblocksize += nodesize[n->type];
- switch (n->type) {
- case NCMD:
- calcsize(n->ncmd.redirect);
- calcsize(n->ncmd.args);
- calcsize(n->ncmd.assign);
- break;
- case NPIPE:
- sizenodelist(n->npipe.cmdlist);
- break;
- case NREDIR:
- case NBACKGND:
- case NSUBSHELL:
- calcsize(n->nredir.redirect);
- calcsize(n->nredir.n);
- break;
- case NAND:
- case NOR:
- case NSEMI:
- case NWHILE:
- case NUNTIL:
- calcsize(n->nbinary.ch2);
- calcsize(n->nbinary.ch1);
- break;
- case NIF:
- calcsize(n->nif.elsepart);
- calcsize(n->nif.ifpart);
- calcsize(n->nif.test);
- break;
- case NFOR:
- funcstringsize += strlen(n->nfor.var) + 1;
- calcsize(n->nfor.body);
- calcsize(n->nfor.args);
- break;
- case NCASE:
- calcsize(n->ncase.cases);
- calcsize(n->ncase.expr);
- break;
- case NCLIST:
- calcsize(n->nclist.body);
- calcsize(n->nclist.pattern);
- calcsize(n->nclist.next);
- break;
- case NDEFUN:
- case NARG:
- sizenodelist(n->narg.backquote);
- funcstringsize += strlen(n->narg.text) + 1;
- calcsize(n->narg.next);
- break;
- case NTO:
- case NCLOBBER:
- case NFROM:
- case NFROMTO:
- case NAPPEND:
- calcsize(n->nfile.fname);
- calcsize(n->nfile.next);
- break;
- case NTOFD:
- case NFROMFD:
- calcsize(n->ndup.vname);
- calcsize(n->ndup.next);
- break;
- case NHERE:
- case NXHERE:
- calcsize(n->nhere.doc);
- calcsize(n->nhere.next);
- break;
- case NNOT:
- calcsize(n->nnot.com);
- break;
+ static const char spclchars[] = {
+ '=',
+ ':',
+ CTLQUOTEMARK,
+ CTLENDVAR,
+ CTLESC,
+ CTLVAR,
+ CTLBACKQ,
+ CTLBACKQ | CTLQUOTE,
+#if ENABLE_ASH_MATH_SUPPORT
+ CTLENDARI,
+#endif
+ 0
};
+ const char *reject = spclchars;
+ int c;
+ int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */
+ int breakall = flag & EXP_WORD;
+ int inquotes;
+ size_t length;
+ int startloc;
+
+ if (!(flag & EXP_VARTILDE)) {
+ reject += 2;
+ } else if (flag & EXP_VARTILDE2) {
+ reject++;
+ }
+ inquotes = 0;
+ length = 0;
+ if (flag & EXP_TILDE) {
+ char *q;
+
+ flag &= ~EXP_TILDE;
+ tilde:
+ q = p;
+ if (*q == CTLESC && (flag & EXP_QWORD))
+ q++;
+ if (*q == '~')
+ p = exptilde(p, q, flag);
+ }
+ start:
+ startloc = expdest - (char *)stackblock();
+ for (;;) {
+ length += strcspn(p + length, reject);
+ c = p[length];
+ if (c && (!(c & 0x80)
+#if ENABLE_ASH_MATH_SUPPORT
+ || c == CTLENDARI
+#endif
+ )) {
+ /* c == '=' || c == ':' || c == CTLENDARI */
+ length++;
+ }
+ if (length > 0) {
+ int newloc;
+ expdest = stack_nputstr(p, length, expdest);
+ newloc = expdest - (char *)stackblock();
+ if (breakall && !inquotes && newloc > startloc) {
+ recordregion(startloc, newloc, 0);
+ }
+ startloc = newloc;
+ }
+ p += length + 1;
+ length = 0;
+
+ switch (c) {
+ case '\0':
+ goto breakloop;
+ case '=':
+ if (flag & EXP_VARTILDE2) {
+ p--;
+ continue;
+ }
+ flag |= EXP_VARTILDE2;
+ reject++;
+ /* fall through */
+ case ':':
+ /*
+ * sort of a hack - expand tildes in variable
+ * assignments (after the first '=' and after ':'s).
+ */
+ if (*--p == '~') {
+ goto tilde;
+ }
+ continue;
+ }
+
+ switch (c) {
+ case CTLENDVAR: /* ??? */
+ goto breakloop;
+ case CTLQUOTEMARK:
+ /* "$@" syntax adherence hack */
+ if (
+ !inquotes &&
+ !memcmp(p, dolatstr, 4) &&
+ (p[4] == CTLQUOTEMARK || (
+ p[4] == CTLENDVAR &&
+ p[5] == CTLQUOTEMARK
+ ))
+ ) {
+ p = evalvar(p + 1, flag) + 1;
+ goto start;
+ }
+ inquotes = !inquotes;
+ addquote:
+ if (quotes) {
+ p--;
+ length++;
+ startloc++;
+ }
+ break;
+ case CTLESC:
+ startloc++;
+ length++;
+ goto addquote;
+ case CTLVAR:
+ p = evalvar(p, flag);
+ goto start;
+ case CTLBACKQ:
+ c = 0;
+ case CTLBACKQ|CTLQUOTE:
+ expbackq(argbackq->n, c, quotes);
+ argbackq = argbackq->next;
+ goto start;
+#if ENABLE_ASH_MATH_SUPPORT
+ case CTLENDARI:
+ p--;
+ expari(quotes);
+ goto start;
+#endif
+ }
+ }
+ breakloop:
+ ;
}
static char *
-nodeckstrdup(char *s)
+scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
+ int zero)
{
- char *rtn = funcstring;
+ char *loc;
+ char *loc2;
+ char c;
- strcpy(funcstring, s);
- funcstring += strlen(s) + 1;
- return rtn;
+ loc = startp;
+ loc2 = rmesc;
+ do {
+ int match;
+ const char *s = loc2;
+ c = *loc2;
+ if (zero) {
+ *loc2 = '\0';
+ s = rmesc;
+ }
+ match = pmatch(str, s);
+ *loc2 = c;
+ if (match)
+ return loc;
+ if (quotes && *loc == CTLESC)
+ loc++;
+ loc++;
+ loc2++;
+ } while (c);
+ return 0;
}
-static union node *copynode(union node *);
+static char *
+scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes,
+ int zero)
+{
+ int esc = 0;
+ char *loc;
+ char *loc2;
-static struct nodelist *
-copynodelist(struct nodelist *lp)
+ for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
+ int match;
+ char c = *loc2;
+ const char *s = loc2;
+ if (zero) {
+ *loc2 = '\0';
+ s = rmesc;
+ }
+ match = pmatch(str, s);
+ *loc2 = c;
+ if (match)
+ return loc;
+ loc--;
+ if (quotes) {
+ if (--esc < 0) {
+ esc = esclen(startp, loc);
+ }
+ if (esc % 2) {
+ esc--;
+ loc--;
+ }
+ }
+ }
+ return 0;
+}
+
+static void varunset(const char *, const char *, const char *, int) ATTRIBUTE_NORETURN;
+static void
+varunset(const char *end, const char *var, const char *umsg, int varflags)
{
- struct nodelist *start;
- struct nodelist **lpp;
+ const char *msg;
+ const char *tail;
- lpp = &start;
- while (lp) {
- *lpp = funcblock;
- funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist));
- (*lpp)->n = copynode(lp->n);
- lp = lp->next;
- lpp = &(*lpp)->next;
+ tail = nullstr;
+ msg = "parameter not set";
+ if (umsg) {
+ if (*end == CTLENDVAR) {
+ if (varflags & VSNUL)
+ tail = " or null";
+ } else
+ msg = umsg;
}
- *lpp = NULL;
- return start;
+ ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail);
}
-static union node *
-copynode(union node *n)
+static const char *
+subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes)
{
- union node *new;
+ char *startp;
+ char *loc;
+ int saveherefd = herefd;
+ struct nodelist *saveargbackq = argbackq;
+ int amount;
+ char *rmesc, *rmescend;
+ int zero;
+ char *(*scan)(char *, char *, char *, char *, int , int);
- if (n == NULL)
- return NULL;
- new = funcblock;
- funcblock = (char *) funcblock + nodesize[n->type];
+ herefd = -1;
+ argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0);
+ STPUTC('\0', expdest);
+ herefd = saveherefd;
+ argbackq = saveargbackq;
+ startp = stackblock() + startloc;
- switch (n->type) {
- case NCMD:
- new->ncmd.redirect = copynode(n->ncmd.redirect);
- new->ncmd.args = copynode(n->ncmd.args);
- new->ncmd.assign = copynode(n->ncmd.assign);
- break;
- case NPIPE:
- new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
- new->npipe.backgnd = n->npipe.backgnd;
- break;
- case NREDIR:
- case NBACKGND:
- case NSUBSHELL:
- new->nredir.redirect = copynode(n->nredir.redirect);
- new->nredir.n = copynode(n->nredir.n);
- break;
- case NAND:
- case NOR:
- case NSEMI:
- case NWHILE:
- case NUNTIL:
- new->nbinary.ch2 = copynode(n->nbinary.ch2);
- new->nbinary.ch1 = copynode(n->nbinary.ch1);
- break;
- case NIF:
- new->nif.elsepart = copynode(n->nif.elsepart);
- new->nif.ifpart = copynode(n->nif.ifpart);
- new->nif.test = copynode(n->nif.test);
- break;
- case NFOR:
- new->nfor.var = nodeckstrdup(n->nfor.var);
- new->nfor.body = copynode(n->nfor.body);
- new->nfor.args = copynode(n->nfor.args);
- break;
- case NCASE:
- new->ncase.cases = copynode(n->ncase.cases);
- new->ncase.expr = copynode(n->ncase.expr);
- break;
- case NCLIST:
- new->nclist.body = copynode(n->nclist.body);
- new->nclist.pattern = copynode(n->nclist.pattern);
- new->nclist.next = copynode(n->nclist.next);
- break;
- case NDEFUN:
- case NARG:
- new->narg.backquote = copynodelist(n->narg.backquote);
- new->narg.text = nodeckstrdup(n->narg.text);
- new->narg.next = copynode(n->narg.next);
- break;
- case NTO:
- case NCLOBBER:
- case NFROM:
- case NFROMTO:
- case NAPPEND:
- new->nfile.fname = copynode(n->nfile.fname);
- new->nfile.fd = n->nfile.fd;
- new->nfile.next = copynode(n->nfile.next);
- break;
- case NTOFD:
- case NFROMFD:
- new->ndup.vname = copynode(n->ndup.vname);
- new->ndup.dupfd = n->ndup.dupfd;
- new->ndup.fd = n->ndup.fd;
- new->ndup.next = copynode(n->ndup.next);
- break;
- case NHERE:
- case NXHERE:
- new->nhere.doc = copynode(n->nhere.doc);
- new->nhere.fd = n->nhere.fd;
- new->nhere.next = copynode(n->nhere.next);
- break;
- case NNOT:
- new->nnot.com = copynode(n->nnot.com);
- break;
- };
- new->type = n->type;
- return new;
+ switch (subtype) {
+ case VSASSIGN:
+ setvar(str, startp, 0);
+ amount = startp - expdest;
+ STADJUST(amount, expdest);
+ return startp;
+
+ case VSQUESTION:
+ varunset(p, str, startp, varflags);
+ /* NOTREACHED */
+ }
+
+ subtype -= VSTRIMRIGHT;
+#if DEBUG
+ if (subtype < 0 || subtype > 3)
+ abort();
+#endif
+
+ rmesc = startp;
+ rmescend = stackblock() + strloc;
+ if (quotes) {
+ rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
+ if (rmesc != startp) {
+ rmescend = expdest;
+ startp = stackblock() + startloc;
+ }
+ }
+ rmescend--;
+ str = stackblock() + strloc;
+ preglob(str, varflags & VSQUOTE, 0);
+
+ /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
+ zero = subtype >> 1;
+ /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
+ scan = (subtype & 1) ^ zero ? scanleft : scanright;
+
+ loc = scan(startp, rmesc, rmescend, str, quotes, zero);
+ if (loc) {
+ if (zero) {
+ memmove(startp, loc, str - loc);
+ loc = startp + (str - loc) - 1;
+ }
+ *loc = '\0';
+ amount = loc - expdest;
+ STADJUST(amount, expdest);
+ }
+ return loc;
}
/*
- * Make a copy of a parse tree.
+ * Add the value of a specialized variable to the stack string.
*/
-static struct funcnode *
-copyfunc(union node *n)
+static ssize_t
+varvalue(char *name, int varflags, int flags)
{
- struct funcnode *f;
- size_t blocksize;
+ int num;
+ char *p;
+ int i;
+ int sep = 0;
+ int sepq = 0;
+ ssize_t len = 0;
+ char **ap;
+ int syntax;
+ int quoted = varflags & VSQUOTE;
+ int subtype = varflags & VSTYPE;
+ int quotes = flags & (EXP_FULL | EXP_CASE);
- funcblocksize = offsetof(struct funcnode, n);
- funcstringsize = 0;
- calcsize(n);
- blocksize = funcblocksize;
- f = ckmalloc(blocksize + funcstringsize);
- funcblock = (char *) f + offsetof(struct funcnode, n);
- funcstring = (char *) f + blocksize;
- copynode(n);
- f->count = 0;
- return f;
-}
+ if (quoted && (flags & EXP_FULL))
+ sep = 1 << CHAR_BIT;
-/*
- * Define a shell function.
- */
-static void
-defun(char *name, union node *func)
-{
- struct cmdentry entry;
+ syntax = quoted ? DQSYNTAX : BASESYNTAX;
+ switch (*name) {
+ case '$':
+ num = rootpid;
+ goto numvar;
+ case '?':
+ num = exitstatus;
+ goto numvar;
+ case '#':
+ num = shellparam.nparam;
+ goto numvar;
+ case '!':
+ num = backgndpid;
+ if (num == 0)
+ return -1;
+ numvar:
+ len = cvtnum(num);
+ break;
+ case '-':
+ p = makestrspace(NOPTS, expdest);
+ for (i = NOPTS - 1; i >= 0; i--) {
+ if (optlist[i]) {
+ USTPUTC(optletters(i), p);
+ len++;
+ }
+ }
+ expdest = p;
+ break;
+ case '@':
+ if (sep)
+ goto param;
+ /* fall through */
+ case '*':
+ sep = ifsset() ? SC2INT(ifsval()[0]) : ' ';
+ if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK))
+ sepq = 1;
+ param:
+ ap = shellparam.p;
+ if (!ap)
+ return -1;
+ while ((p = *ap++)) {
+ size_t partlen;
- INT_OFF;
- entry.cmdtype = CMDFUNCTION;
- entry.u.func = copyfunc(func);
- addcmdentry(name, &entry);
- INT_ON;
-}
+ partlen = strlen(p);
+ len += partlen;
-static int evalskip; /* set if we are skipping commands */
-/* reasons for skipping commands (see comment on breakcmd routine) */
-#define SKIPBREAK (1 << 0)
-#define SKIPCONT (1 << 1)
-#define SKIPFUNC (1 << 2)
-#define SKIPFILE (1 << 3)
-#define SKIPEVAL (1 << 4)
-static int skipcount; /* number of levels to skip */
-static int funcnest; /* depth of function calls */
+ if (!(subtype == VSPLUS || subtype == VSLENGTH))
+ memtodest(p, partlen, syntax, quotes);
-/* forward decl way out to parsing code - dotrap needs it */
-static int evalstring(char *s, int mask);
+ if (*ap && sep) {
+ char *q;
+
+ len++;
+ if (subtype == VSPLUS || subtype == VSLENGTH) {
+ continue;
+ }
+ q = expdest;
+ if (sepq)
+ STPUTC(CTLESC, q);
+ STPUTC(sep, q);
+ expdest = q;
+ }
+ }
+ return len;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ num = atoi(name);
+ if (num < 0 || num > shellparam.nparam)
+ return -1;
+ p = num ? shellparam.p[num - 1] : arg0;
+ goto value;
+ default:
+ p = lookupvar(name);
+ value:
+ if (!p)
+ return -1;
+
+ len = strlen(p);
+ if (!(subtype == VSPLUS || subtype == VSLENGTH))
+ memtodest(p, len, syntax, quotes);
+ return len;
+ }
+
+ if (subtype == VSPLUS || subtype == VSLENGTH)
+ STADJUST(-len, expdest);
+ return len;
+}
/*
- * Called to execute a trap. Perhaps we should avoid entering new trap
- * handlers while we are executing a trap handler.
+ * Expand a variable, and return a pointer to the next character in the
+ * input string.
*/
-static int
-dotrap(void)
+static char *
+evalvar(char *p, int flag)
{
- char *p;
- char *q;
- int i;
- int savestatus;
- int skip = 0;
+ int subtype;
+ int varflags;
+ char *var;
+ int patloc;
+ int c;
+ int startloc;
+ ssize_t varlen;
+ int easy;
+ int quotes;
+ int quoted;
- savestatus = exitstatus;
- pendingsigs = 0;
- xbarrier();
+ quotes = flag & (EXP_FULL | EXP_CASE);
+ varflags = *p++;
+ subtype = varflags & VSTYPE;
+ quoted = varflags & VSQUOTE;
+ var = p;
+ easy = (!quoted || (*var == '@' && shellparam.nparam));
+ startloc = expdest - (char *)stackblock();
+ p = strchr(p, '=') + 1;
- for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
- if (!*q)
- continue;
- *q = '\0';
+ again:
+ varlen = varvalue(var, varflags, flag);
+ if (varflags & VSNUL)
+ varlen--;
- p = trap[i + 1];
- if (!p)
- continue;
- skip = evalstring(p, SKIPEVAL);
- exitstatus = savestatus;
- if (skip)
- break;
+ if (subtype == VSPLUS) {
+ varlen = -1 - varlen;
+ goto vsplus;
}
- return skip;
-}
+ if (subtype == VSMINUS) {
+ vsplus:
+ if (varlen < 0) {
+ argstr(
+ p, flag | EXP_TILDE |
+ (quoted ? EXP_QWORD : EXP_WORD)
+ );
+ goto end;
+ }
+ if (easy)
+ goto record;
+ goto end;
+ }
-/* forward declarations - evaluation is fairly recursive business... */
-static void evalloop(union node *, int);
-static void evalfor(union node *, int);
-static void evalcase(union node *, int);
-static void evalsubshell(union node *, int);
-static void expredir(union node *);
-static void evalpipe(union node *, int);
-static void evalcommand(union node *, int);
-static int evalbltin(const struct builtincmd *, int, char **);
-static void prehash(union node *);
+ if (subtype == VSASSIGN || subtype == VSQUESTION) {
+ if (varlen < 0) {
+ if (subevalvar(p, var, 0, subtype, startloc, varflags, 0)) {
+ varflags &= ~VSNUL;
+ /*
+ * Remove any recorded regions beyond
+ * start of variable
+ */
+ removerecordregions(startloc);
+ goto again;
+ }
+ goto end;
+ }
+ if (easy)
+ goto record;
+ goto end;
+ }
+
+ if (varlen < 0 && uflag)
+ varunset(p, var, 0, 0);
+
+ if (subtype == VSLENGTH) {
+ cvtnum(varlen > 0 ? varlen : 0);
+ goto record;
+ }
-/*
- * Evaluate a parse tree. The value is left in the global variable
- * exitstatus.
- */
-static void
-evaltree(union node *n, int flags)
-{
- int checkexit = 0;
- void (*evalfn)(union node *, int);
- unsigned isor;
- int status;
- if (n == NULL) {
- TRACE(("evaltree(NULL) called\n"));
- goto out;
+ if (subtype == VSNORMAL) {
+ if (!easy)
+ goto end;
+ record:
+ recordregion(startloc, expdest - (char *)stackblock(), quoted);
+ goto end;
}
- TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
- getpid(), n, n->type, flags));
- switch (n->type) {
- default:
+
#if DEBUG
- out1fmt("Node type = %d\n", n->type);
- fflush(stdout);
+ switch (subtype) {
+ case VSTRIMLEFT:
+ case VSTRIMLEFTMAX:
+ case VSTRIMRIGHT:
+ case VSTRIMRIGHTMAX:
break;
+ default:
+ abort();
+ }
#endif
- case NNOT:
- evaltree(n->nnot.com, EV_TESTED);
- status = !exitstatus;
- goto setstatus;
- case NREDIR:
- expredir(n->nredir.redirect);
- status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
- if (!status) {
- evaltree(n->nredir.n, flags & EV_TESTED);
- status = exitstatus;
- }
- popredir(0);
- goto setstatus;
- case NCMD:
- evalfn = evalcommand;
- checkexit:
- if (eflag && !(flags & EV_TESTED))
- checkexit = ~0;
- goto calleval;
- case NFOR:
- evalfn = evalfor;
- goto calleval;
- case NWHILE:
- case NUNTIL:
- evalfn = evalloop;
- goto calleval;
- case NSUBSHELL:
- case NBACKGND:
- evalfn = evalsubshell;
- goto calleval;
- case NPIPE:
- evalfn = evalpipe;
- goto checkexit;
- case NCASE:
- evalfn = evalcase;
- goto calleval;
- case NAND:
- case NOR:
- case NSEMI:
-#if NAND + 1 != NOR
-#error NAND + 1 != NOR
-#endif
-#if NOR + 1 != NSEMI
-#error NOR + 1 != NSEMI
-#endif
- isor = n->type - NAND;
- evaltree(
- n->nbinary.ch1,
- (flags | ((isor >> 1) - 1)) & EV_TESTED
- );
- if (!exitstatus == isor)
- break;
- if (!evalskip) {
- n = n->nbinary.ch2;
- evaln:
- evalfn = evaltree;
- calleval:
- evalfn(n, flags);
- break;
- }
- break;
- case NIF:
- evaltree(n->nif.test, EV_TESTED);
- if (evalskip)
- break;
- if (exitstatus == 0) {
- n = n->nif.ifpart;
- goto evaln;
- } else if (n->nif.elsepart) {
- n = n->nif.elsepart;
- goto evaln;
+
+ if (varlen >= 0) {
+ /*
+ * Terminate the string and start recording the pattern
+ * right after it
+ */
+ STPUTC('\0', expdest);
+ patloc = expdest - (char *)stackblock();
+ if (subevalvar(p, NULL, patloc, subtype,
+ startloc, varflags, quotes) == 0) {
+ int amount = expdest - (
+ (char *)stackblock() + patloc - 1
+ );
+ STADJUST(-amount, expdest);
}
- goto success;
- case NDEFUN:
- defun(n->narg.text, n->narg.next);
- success:
- status = 0;
- setstatus:
- exitstatus = status;
- break;
+ /* Remove any recorded regions beyond start of variable */
+ removerecordregions(startloc);
+ goto record;
}
- out:
- if ((checkexit & exitstatus))
- evalskip |= SKIPEVAL;
- else if (pendingsigs && dotrap())
- goto exexit;
- if (flags & EV_EXIT) {
- exexit:
- raise_exception(EXEXIT);
+ end:
+ if (subtype != VSNORMAL) { /* skip to end of alternative */
+ int nesting = 1;
+ for (;;) {
+ c = *p++;
+ if (c == CTLESC)
+ p++;
+ else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) {
+ if (varlen >= 0)
+ argbackq = argbackq->next;
+ } else if (c == CTLVAR) {
+ if ((*p++ & VSTYPE) != VSNORMAL)
+ nesting++;
+ } else if (c == CTLENDVAR) {
+ if (--nesting == 0)
+ break;
+ }
+ }
}
+ return p;
}
-#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
-static
-#endif
-void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
-
-static int loopnest; /* current loop nesting level */
-
+/*
+ * Break the argument string into pieces based upon IFS and add the
+ * strings to the argument list. The regions of the string to be
+ * searched for IFS characters have been stored by recordregion.
+ */
static void
-evalloop(union node *n, int flags)
+ifsbreakup(char *string, struct arglist *arglist)
{
- int status;
-
- loopnest++;
- status = 0;
- flags &= EV_TESTED;
- for (;;) {
- int i;
+ struct ifsregion *ifsp;
+ struct strlist *sp;
+ char *start;
+ char *p;
+ char *q;
+ const char *ifs, *realifs;
+ int ifsspc;
+ int nulonly;
- evaltree(n->nbinary.ch1, EV_TESTED);
- if (evalskip) {
- skipping:
- if (evalskip == SKIPCONT && --skipcount <= 0) {
- evalskip = 0;
- continue;
- }
- if (evalskip == SKIPBREAK && --skipcount <= 0)
- evalskip = 0;
- break;
- }
- i = exitstatus;
- if (n->type != NWHILE)
- i = !i;
- if (i != 0)
- break;
- evaltree(n->nbinary.ch2, flags);
- status = exitstatus;
- if (evalskip)
- goto skipping;
+ start = string;
+ if (ifslastp != NULL) {
+ ifsspc = 0;
+ nulonly = 0;
+ realifs = ifsset() ? ifsval() : defifs;
+ ifsp = &ifsfirst;
+ do {
+ p = string + ifsp->begoff;
+ nulonly = ifsp->nulonly;
+ ifs = nulonly ? nullstr : realifs;
+ ifsspc = 0;
+ while (p < string + ifsp->endoff) {
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (!strchr(ifs, *p)) {
+ p++;
+ continue;
+ }
+ if (!nulonly)
+ ifsspc = (strchr(defifs, *p) != NULL);
+ /* Ignore IFS whitespace at start */
+ if (q == start && ifsspc) {
+ p++;
+ start = p;
+ continue;
+ }
+ *q = '\0';
+ sp = stalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
+ p++;
+ if (!nulonly) {
+ for (;;) {
+ if (p >= string + ifsp->endoff) {
+ break;
+ }
+ q = p;
+ if (*p == CTLESC)
+ p++;
+ if (strchr(ifs, *p) == NULL ) {
+ p = q;
+ break;
+ } else if (strchr(defifs, *p) == NULL) {
+ if (ifsspc) {
+ p++;
+ ifsspc = 0;
+ } else {
+ p = q;
+ break;
+ }
+ } else
+ p++;
+ }
+ }
+ start = p;
+ } /* while */
+ ifsp = ifsp->next;
+ } while (ifsp != NULL);
+ if (nulonly)
+ goto add;
}
- loopnest--;
- exitstatus = status;
-}
-
-static void
-evalfor(union node *n, int flags)
-{
- struct arglist arglist;
- union node *argp;
- struct strlist *sp;
- struct stackmark smark;
- setstackmark(&smark);
- arglist.lastp = &arglist.list;
- for (argp = n->nfor.args; argp; argp = argp->narg.next) {
- expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
- /* XXX */
- if (evalskip)
- goto out;
- }
- *arglist.lastp = NULL;
+ if (!*start)
+ return;
- exitstatus = 0;
- loopnest++;
- flags &= EV_TESTED;
- for (sp = arglist.list; sp; sp = sp->next) {
- setvar(n->nfor.var, sp->text, 0);
- evaltree(n->nfor.body, flags);
- if (evalskip) {
- if (evalskip == SKIPCONT && --skipcount <= 0) {
- evalskip = 0;
- continue;
- }
- if (evalskip == SKIPBREAK && --skipcount <= 0)
- evalskip = 0;
- break;
- }
- }
- loopnest--;
- out:
- popstackmark(&smark);
+ add:
+ sp = stalloc(sizeof(*sp));
+ sp->text = start;
+ *arglist->lastp = sp;
+ arglist->lastp = &sp->next;
}
static void
-evalcase(union node *n, int flags)
+ifsfree(void)
{
- union node *cp;
- union node *patp;
- struct arglist arglist;
- struct stackmark smark;
+ struct ifsregion *p;
- setstackmark(&smark);
- arglist.lastp = &arglist.list;
- expandarg(n->ncase.expr, &arglist, EXP_TILDE);
- exitstatus = 0;
- for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
- for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
- if (casematch(patp, arglist.list->text)) {
- if (evalskip == 0) {
- evaltree(cp->nclist.body, flags);
- }
- goto out;
- }
- }
- }
- out:
- popstackmark(&smark);
+ INT_OFF;
+ p = ifsfirst.next;
+ do {
+ struct ifsregion *ifsp;
+ ifsp = p->next;
+ free(p);
+ p = ifsp;
+ } while (p);
+ ifslastp = NULL;
+ ifsfirst.next = NULL;
+ INT_ON;
}
/*
- * Kick off a subshell to evaluate a tree.
+ * Add a file name to the list.
*/
static void
-evalsubshell(union node *n, int flags)
+addfname(const char *name)
{
- struct job *jp;
- int backgnd = (n->type == NBACKGND);
- int status;
+ struct strlist *sp;
- expredir(n->nredir.redirect);
- if (!backgnd && flags & EV_EXIT && !trap[0])
- goto nofork;
- INT_OFF;
- jp = makejob(n, 1);
- if (forkshell(jp, n, backgnd) == 0) {
- INT_ON;
- flags |= EV_EXIT;
- if (backgnd)
- flags &=~ EV_TESTED;
- nofork:
- redirect(n->nredir.redirect, 0);
- evaltreenr(n->nredir.n, flags);
- /* never returns */
- }
- status = 0;
- if (! backgnd)
- status = waitforjob(jp);
- exitstatus = status;
- INT_ON;
+ sp = stalloc(sizeof(*sp));
+ sp->text = ststrdup(name);
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
}
+static char *expdir;
+
/*
- * Compute the names of the files in a redirection list.
+ * Do metacharacter (i.e. *, ?, [...]) expansion.
*/
-static void fixredir(union node *, const char *, int);
static void
-expredir(union node *n)
+expmeta(char *enddir, char *name)
{
- union node *redir;
-
- for (redir = n; redir; redir = redir->nfile.next) {
- struct arglist fn;
+ char *p;
+ const char *cp;
+ char *start;
+ char *endname;
+ int metaflag;
+ struct stat statb;
+ DIR *dirp;
+ struct dirent *dp;
+ int atend;
+ int matchdot;
- memset(&fn, 0, sizeof(fn));
- fn.lastp = &fn.list;
- switch (redir->type) {
- case NFROMTO:
- case NFROM:
- case NTO:
- case NCLOBBER:
- case NAPPEND:
- expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
- redir->nfile.expfname = fn.list->text;
- break;
- case NFROMFD:
- case NTOFD:
- if (redir->ndup.vname) {
- expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
- if (fn.list == NULL)
- ash_msg_and_raise_error("redir error");
- fixredir(redir, fn.list->text, 1);
+ metaflag = 0;
+ start = name;
+ for (p = name; *p; p++) {
+ if (*p == '*' || *p == '?')
+ metaflag = 1;
+ else if (*p == '[') {
+ char *q = p + 1;
+ if (*q == '!')
+ q++;
+ for (;;) {
+ if (*q == '\\')
+ q++;
+ if (*q == '/' || *q == '\0')
+ break;
+ if (*++q == ']') {
+ metaflag = 1;
+ break;
+ }
+ }
+ } else if (*p == '\\')
+ p++;
+ else if (*p == '/') {
+ if (metaflag)
+ goto out;
+ start = p + 1;
+ }
+ }
+ out:
+ if (metaflag == 0) { /* we've reached the end of the file name */
+ if (enddir != expdir)
+ metaflag++;
+ p = name;
+ do {
+ if (*p == '\\')
+ p++;
+ *enddir++ = *p;
+ } while (*p++);
+ if (metaflag == 0 || lstat(expdir, &statb) >= 0)
+ addfname(expdir);
+ return;
+ }
+ endname = p;
+ if (name < start) {
+ p = name;
+ do {
+ if (*p == '\\')
+ p++;
+ *enddir++ = *p++;
+ } while (p < start);
+ }
+ if (enddir == expdir) {
+ cp = ".";
+ } else if (enddir == expdir + 1 && *expdir == '/') {
+ cp = "/";
+ } else {
+ cp = expdir;
+ enddir[-1] = '\0';
+ }
+ dirp = opendir(cp);
+ if (dirp == NULL)
+ return;
+ if (enddir != expdir)
+ enddir[-1] = '/';
+ if (*endname == 0) {
+ atend = 1;
+ } else {
+ atend = 0;
+ *endname++ = '\0';
+ }
+ matchdot = 0;
+ p = start;
+ if (*p == '\\')
+ p++;
+ if (*p == '.')
+ matchdot++;
+ while (! intpending && (dp = readdir(dirp)) != NULL) {
+ if (dp->d_name[0] == '.' && ! matchdot)
+ continue;
+ if (pmatch(start, dp->d_name)) {
+ if (atend) {
+ strcpy(enddir, dp->d_name);
+ addfname(expdir);
+ } else {
+ for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';)
+ continue;
+ p[-1] = '/';
+ expmeta(p, endname);
}
- break;
}
}
+ closedir(dirp);
+ if (! atend)
+ endname[-1] = '/';
}
-/*
- * Evaluate a pipeline. All the processes in the pipeline are children
- * of the process creating the pipeline. (This differs from some versions
- * of the shell, which make the last process in a pipeline the parent
- * of all the rest.)
- */
-static void
-evalpipe(union node *n, int flags)
+static struct strlist *
+msort(struct strlist *list, int len)
{
- struct job *jp;
- struct nodelist *lp;
- int pipelen;
- int prevfd;
- int pip[2];
+ struct strlist *p, *q = NULL;
+ struct strlist **lpp;
+ int half;
+ int n;
- TRACE(("evalpipe(0x%lx) called\n", (long)n));
- pipelen = 0;
- for (lp = n->npipe.cmdlist; lp; lp = lp->next)
- pipelen++;
- flags |= EV_EXIT;
- INT_OFF;
- jp = makejob(n, pipelen);
- prevfd = -1;
- for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
- prehash(lp->n);
- pip[1] = -1;
- if (lp->next) {
- if (pipe(pip) < 0) {
- close(prevfd);
- ash_msg_and_raise_error("Pipe call failed");
- }
- }
- if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
- INT_ON;
- if (pip[1] >= 0) {
- close(pip[0]);
- }
- if (prevfd > 0) {
- dup2(prevfd, 0);
- close(prevfd);
+ if (len <= 1)
+ return list;
+ half = len >> 1;
+ p = list;
+ for (n = half; --n >= 0; ) {
+ q = p;
+ p = p->next;
+ }
+ q->next = NULL; /* terminate first half of list */
+ q = msort(list, half); /* sort first half of list */
+ p = msort(p, len - half); /* sort second half */
+ lpp = &list;
+ for (;;) {
+#if ENABLE_LOCALE_SUPPORT
+ if (strcoll(p->text, q->text) < 0)
+#else
+ if (strcmp(p->text, q->text) < 0)
+#endif
+ {
+ *lpp = p;
+ lpp = &p->next;
+ p = *lpp;
+ if (p == NULL) {
+ *lpp = q;
+ break;
}
- if (pip[1] > 1) {
- dup2(pip[1], 1);
- close(pip[1]);
+ } else {
+ *lpp = q;
+ lpp = &q->next;
+ q = *lpp;
+ if (q == NULL) {
+ *lpp = p;
+ break;
}
- evaltreenr(lp->n, flags);
- /* never returns */
}
- if (prevfd >= 0)
- close(prevfd);
- prevfd = pip[0];
- close(pip[1]);
- }
- if (n->npipe.backgnd == 0) {
- exitstatus = waitforjob(jp);
- TRACE(("evalpipe: job done exit status %d\n", exitstatus));
}
- INT_ON;
+ return list;
}
-static struct localvar *localvars;
-
/*
- * Called after a function returns.
- * Interrupts must be off.
+ * Sort the results of file name expansion. It calculates the number of
+ * strings to sort and then calls msort (short for merge sort) to do the
+ * work.
*/
-static void
-poplocalvars(void)
+static struct strlist *
+expsort(struct strlist *str)
{
- struct localvar *lvp;
- struct var *vp;
+ int len;
+ struct strlist *sp;
- while ((lvp = localvars) != NULL) {
- localvars = lvp->next;
- vp = lvp->vp;
- TRACE(("poplocalvar %s", vp ? vp->text : "-"));
- if (vp == NULL) { /* $- saved */
- memcpy(optlist, lvp->text, sizeof(optlist));
- free((char*)lvp->text);
- optschanged();
- } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
- unsetvar(vp->text);
- } else {
- if (vp->func)
- (*vp->func)(strchrnul(lvp->text, '=') + 1);
- if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
- free((char*)vp->text);
- vp->flags = lvp->flags;
- vp->text = lvp->text;
- }
- free(lvp);
- }
+ len = 0;
+ for (sp = str; sp; sp = sp->next)
+ len++;
+ return msort(str, len);
}
-static int
-evalfun(struct funcnode *func, int argc, char **argv, int flags)
+static void
+expandmeta(struct strlist *str, int flag)
{
- volatile struct shparam saveparam;
- struct localvar *volatile savelocalvars;
- struct jmploc *volatile savehandler;
- struct jmploc jmploc;
- int e;
+ static const char metachars[] = {
+ '*', '?', '[', 0
+ };
+ /* TODO - EXP_REDIR */
- saveparam = shellparam;
- savelocalvars = localvars;
- e = setjmp(jmploc.loc);
- if (e) {
- goto funcdone;
+ while (str) {
+ struct strlist **savelastp;
+ struct strlist *sp;
+ char *p;
+
+ if (fflag)
+ goto nometa;
+ if (!strpbrk(str->text, metachars))
+ goto nometa;
+ savelastp = exparg.lastp;
+
+ INT_OFF;
+ p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP);
+ {
+ int i = strlen(str->text);
+ expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */
+ }
+
+ expmeta(expdir, p);
+ free(expdir);
+ if (p != str->text)
+ free(p);
+ INT_ON;
+ if (exparg.lastp == savelastp) {
+ /*
+ * no matches
+ */
+ nometa:
+ *exparg.lastp = str;
+ rmescapes(str->text);
+ exparg.lastp = &str->next;
+ } else {
+ *exparg.lastp = NULL;
+ *savelastp = sp = expsort(*savelastp);
+ while (sp->next != NULL)
+ sp = sp->next;
+ exparg.lastp = &sp->next;
+ }
+ str = str->next;
}
- INT_OFF;
- savehandler = exception_handler;
- exception_handler = &jmploc;
- localvars = NULL;
- shellparam.malloc = 0;
- func->count++;
- funcnest++;
- INT_ON;
- shellparam.nparam = argc - 1;
- shellparam.p = argv + 1;
-#if ENABLE_ASH_GETOPTS
- shellparam.optind = 1;
- shellparam.optoff = -1;
-#endif
- evaltree(&func->n, flags & EV_TESTED);
-funcdone:
- INT_OFF;
- funcnest--;
- freefunc(func);
- poplocalvars();
- localvars = savelocalvars;
- freeparam(&shellparam);
- shellparam = saveparam;
- exception_handler = savehandler;
- INT_ON;
- evalskip &= ~SKIPFUNC;
- return e;
}
-#if ENABLE_ASH_CMDCMD
-static char **
-parse_command_args(char **argv, const char **path)
+/*
+ * Perform variable substitution and command substitution on an argument,
+ * placing the resulting list of arguments in arglist. If EXP_FULL is true,
+ * perform splitting and file name expansion. When arglist is NULL, perform
+ * here document expansion.
+ */
+static void
+expandarg(union node *arg, struct arglist *arglist, int flag)
{
- char *cp, c;
+ struct strlist *sp;
+ char *p;
- for (;;) {
- cp = *++argv;
- if (!cp)
- return 0;
- if (*cp++ != '-')
- break;
- c = *cp++;
- if (!c)
- break;
- if (c == '-' && !*cp) {
- argv++;
- break;
- }
- do {
- switch (c) {
- case 'p':
- *path = defpath;
- break;
- default:
- /* run 'typecmd' for other options */
- return 0;
- }
- c = *cp++;
- } while (c);
+ argbackq = arg->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifsfirst.next = NULL;
+ ifslastp = NULL;
+ argstr(arg->narg.text, flag);
+ p = _STPUTC('\0', expdest);
+ expdest = p - 1;
+ if (arglist == NULL) {
+ return; /* here document expanded */
+ }
+ p = grabstackstr(p);
+ exparg.lastp = &exparg.list;
+ /*
+ * TODO - EXP_REDIR
+ */
+ if (flag & EXP_FULL) {
+ ifsbreakup(p, &exparg);
+ *exparg.lastp = NULL;
+ exparg.lastp = &exparg.list;
+ expandmeta(exparg.list, flag);
+ } else {
+ if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */
+ rmescapes(p);
+ sp = stalloc(sizeof(*sp));
+ sp->text = p;
+ *exparg.lastp = sp;
+ exparg.lastp = &sp->next;
+ }
+ if (ifsfirst.next)
+ ifsfree();
+ *exparg.lastp = NULL;
+ if (exparg.list) {
+ *arglist->lastp = exparg.list;
+ arglist->lastp = exparg.lastp;
}
- return argv;
}
-#endif
/*
- * Make a variable a local variable. When a variable is made local, it's
- * value and flags are saved in a localvar structure. The saved values
- * will be restored when the shell function returns. We handle the name
- * "-" as a special case.
+ * Expand shell variables and backquotes inside a here document.
*/
static void
-mklocal(char *name)
+expandhere(union node *arg, int fd)
{
- struct localvar *lvp;
- struct var **vpp;
- struct var *vp;
-
- INT_OFF;
- lvp = ckmalloc(sizeof(struct localvar));
- if (LONE_DASH(name)) {
- char *p;
- p = ckmalloc(sizeof(optlist));
- lvp->text = memcpy(p, optlist, sizeof(optlist));
- vp = NULL;
- } else {
- char *eq;
+ herefd = fd;
+ expandarg(arg, (struct arglist *)NULL, 0);
+ full_write(fd, stackblock(), expdest - (char *)stackblock());
+}
- vpp = hashvar(name);
- vp = *findvar(vpp, name);
- eq = strchr(name, '=');
- if (vp == NULL) {
- if (eq)
- setvareq(name, VSTRFIXED);
- else
- setvar(name, NULL, VSTRFIXED);
- vp = *vpp; /* the new variable */
- lvp->flags = VUNSET;
- } else {
- lvp->text = vp->text;
- lvp->flags = vp->flags;
- vp->flags |= VSTRFIXED|VTEXTFIXED;
- if (eq)
- setvareq(name, 0);
- }
- }
- lvp->vp = vp;
- lvp->next = localvars;
- localvars = lvp;
- INT_ON;
+/*
+ * Returns true if the pattern matches the string.
+ */
+static int
+patmatch(char *pattern, const char *string)
+{
+ return pmatch(preglob(pattern, 0, 0), string);
}
/*
- * The "local" command.
+ * See if a pattern matches in a case statement.
*/
static int
-localcmd(int argc, char **argv)
+casematch(union node *pattern, char *val)
{
- char *name;
+ struct stackmark smark;
+ int result;
- argv = argptr;
- while ((name = *argv++) != NULL) {
- mklocal(name);
- }
- return 0;
+ setstackmark(&smark);
+ argbackq = pattern->narg.backquote;
+ STARTSTACKSTR(expdest);
+ ifslastp = NULL;
+ argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+ STACKSTRNUL(expdest);
+ result = patmatch(stackblock(), val);
+ popstackmark(&smark);
+ return result;
}
-/* Forward declarations for builtintab[] */
-#if JOBS
-static int fg_bgcmd(int, char **);
-#endif
-static int breakcmd(int, char **);
-#if ENABLE_ASH_CMDCMD
-static int commandcmd(int, char **);
-#endif
-static int dotcmd(int, char **);
-static int evalcmd(int, char **);
-#if ENABLE_ASH_BUILTIN_ECHO
-static int echocmd(int, char **);
-#endif
-#if ENABLE_ASH_BUILTIN_TEST
-static int testcmd(int, char **);
-#endif
-static int execcmd(int, char **);
-static int exitcmd(int, char **);
-static int exportcmd(int, char **);
-static int falsecmd(int, char **);
-#if ENABLE_ASH_GETOPTS
-static int getoptscmd(int, char **);
-#endif
-static int hashcmd(int, char **);
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
-static int helpcmd(int argc, char **argv);
-#endif
-#if JOBS
-static int jobscmd(int, char **);
-#endif
-#if ENABLE_ASH_MATH_SUPPORT
-static int letcmd(int, char **);
-#endif
-static int pwdcmd(int, char **);
-static int readcmd(int, char **);
-static int returncmd(int, char **);
-static int setcmd(int, char **);
-static int shiftcmd(int, char **);
-static int timescmd(int, char **);
-static int trapcmd(int, char **);
-static int truecmd(int, char **);
-static int typecmd(int, char **);
-static int umaskcmd(int, char **);
-static int unsetcmd(int, char **);
-static int waitcmd(int, char **);
-static int ulimitcmd(int, char **);
-#if JOBS
-static int killcmd(int, char **);
-#endif
-#define BUILTIN_NOSPEC "0"
-#define BUILTIN_SPECIAL "1"
-#define BUILTIN_REGULAR "2"
-#define BUILTIN_SPEC_REG "3"
-#define BUILTIN_ASSIGN "4"
-#define BUILTIN_SPEC_ASSG "5"
-#define BUILTIN_REG_ASSG "6"
-#define BUILTIN_SPEC_REG_ASSG "7"
+/* ============ find_command */
-/* make sure to keep these in proper order since it is searched via bsearch() */
-static const struct builtincmd builtintab[] = {
- { BUILTIN_SPEC_REG ".", dotcmd },
- { BUILTIN_SPEC_REG ":", truecmd },
-#if ENABLE_ASH_BUILTIN_TEST
- { BUILTIN_REGULAR "[", testcmd },
- { BUILTIN_REGULAR "[[", testcmd },
-#endif
-#if ENABLE_ASH_ALIAS
- { BUILTIN_REG_ASSG "alias", aliascmd },
-#endif
-#if JOBS
- { BUILTIN_REGULAR "bg", fg_bgcmd },
-#endif
- { BUILTIN_SPEC_REG "break", breakcmd },
- { BUILTIN_REGULAR "cd", cdcmd },
- { BUILTIN_NOSPEC "chdir", cdcmd },
-#if ENABLE_ASH_CMDCMD
- { BUILTIN_REGULAR "command", commandcmd },
-#endif
- { BUILTIN_SPEC_REG "continue", breakcmd },
-#if ENABLE_ASH_BUILTIN_ECHO
- { BUILTIN_REGULAR "echo", echocmd },
-#endif
- { BUILTIN_SPEC_REG "eval", evalcmd },
- { BUILTIN_SPEC_REG "exec", execcmd },
- { BUILTIN_SPEC_REG "exit", exitcmd },
- { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
- { BUILTIN_REGULAR "false", falsecmd },
-#if JOBS
- { BUILTIN_REGULAR "fg", fg_bgcmd },
-#endif
-#if ENABLE_ASH_GETOPTS
- { BUILTIN_REGULAR "getopts", getoptscmd },
-#endif
- { BUILTIN_NOSPEC "hash", hashcmd },
-#if !ENABLE_FEATURE_SH_EXTRA_QUIET
- { BUILTIN_NOSPEC "help", helpcmd },
-#endif
-#if JOBS
- { BUILTIN_REGULAR "jobs", jobscmd },
- { BUILTIN_REGULAR "kill", killcmd },
-#endif
-#if ENABLE_ASH_MATH_SUPPORT
- { BUILTIN_NOSPEC "let", letcmd },
-#endif
- { BUILTIN_ASSIGN "local", localcmd },
- { BUILTIN_NOSPEC "pwd", pwdcmd },
- { BUILTIN_REGULAR "read", readcmd },
- { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
- { BUILTIN_SPEC_REG "return", returncmd },
- { BUILTIN_SPEC_REG "set", setcmd },
- { BUILTIN_SPEC_REG "shift", shiftcmd },
- { BUILTIN_SPEC_REG "source", dotcmd },
-#if ENABLE_ASH_BUILTIN_TEST
- { BUILTIN_REGULAR "test", testcmd },
-#endif
- { BUILTIN_SPEC_REG "times", timescmd },
- { BUILTIN_SPEC_REG "trap", trapcmd },
- { BUILTIN_REGULAR "true", truecmd },
- { BUILTIN_NOSPEC "type", typecmd },
- { BUILTIN_NOSPEC "ulimit", ulimitcmd },
- { BUILTIN_REGULAR "umask", umaskcmd },
-#if ENABLE_ASH_ALIAS
- { BUILTIN_REGULAR "unalias", unaliascmd },
-#endif
- { BUILTIN_SPEC_REG "unset", unsetcmd },
- { BUILTIN_REGULAR "wait", waitcmd },
+static int is_safe_applet(char *name)
+{
+ /* It isn't a bug to have non-existent applet here... */
+ /* ...just a waste of space... */
+ static const char safe_applets[][8] = {
+ "["
+ USE_AWK (, "awk" )
+ USE_CAT (, "cat" )
+ USE_CHMOD (, "chmod" )
+ USE_CHOWN (, "chown" )
+ USE_CP (, "cp" )
+ USE_CUT (, "cut" )
+ USE_DD (, "dd" )
+ USE_ECHO (, "echo" )
+ USE_FIND (, "find" )
+ USE_HEXDUMP(, "hexdump")
+ USE_LN (, "ln" )
+ USE_LS (, "ls" )
+ USE_MKDIR (, "mkdir" )
+ USE_RM (, "rm" )
+ USE_SORT (, "sort" )
+ USE_TEST (, "test" )
+ USE_TOUCH (, "touch" )
+ USE_XARGS (, "xargs" )
+ };
+ int n = sizeof(safe_applets) / sizeof(safe_applets[0]);
+ int i;
+ for (i = 0; i < n; i++)
+ if (strcmp(safe_applets[i], name) == 0)
+ return 1;
+
+ return 0;
+}
+
+struct builtincmd {
+ const char *name;
+ int (*builtin)(int, char **);
+ /* unsigned flags; */
};
+#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1)
+#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2)
+#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4)
-#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0]))
+struct cmdentry {
+ int cmdtype;
+ union param {
+ int index;
+ const struct builtincmd *cmd;
+ struct funcnode *func;
+ } u;
+};
+/* values of cmdtype */
+#define CMDUNKNOWN -1 /* no entry in table for command */
+#define CMDNORMAL 0 /* command is an executable program */
+#define CMDFUNCTION 1 /* command is a shell function */
+#define CMDBUILTIN 2 /* command is a shell builtin */
-#define COMMANDCMD (builtintab + 5 + \
- 2 * ENABLE_ASH_BUILTIN_TEST + \
- ENABLE_ASH_ALIAS + \
- ENABLE_ASH_JOB_CONTROL)
-#define EXECCMD (builtintab + 7 + \
- 2 * ENABLE_ASH_BUILTIN_TEST + \
- ENABLE_ASH_ALIAS + \
- ENABLE_ASH_JOB_CONTROL + \
- ENABLE_ASH_CMDCMD + \
- ENABLE_ASH_BUILTIN_ECHO)
+/* action to find_command() */
+#define DO_ERR 0x01 /* prints errors */
+#define DO_ABS 0x02 /* checks absolute paths */
+#define DO_NOFUNC 0x04 /* don't return shell functions, for command */
+#define DO_ALTPATH 0x08 /* using alternate path */
+#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */
-/*
- * Search the table of builtin commands.
- */
-static struct builtincmd *
-find_builtin(const char *name)
-{
- struct builtincmd *bp;
+static void find_command(char *, struct cmdentry *, int, const char *);
- bp = bsearch(
- name, builtintab, NUMBUILTINS, sizeof(builtintab[0]),
- pstrcmp
- );
- return bp;
-}
+
+/* ============ Hashing commands */
/*
- * Resolve a command name. If you change this routine, you may have to
- * change the shellexec routine as well.
+ * When commands are first encountered, they are entered in a hash table.
+ * This ensures that a full path search will not have to be done for them
+ * on each invocation.
+ *
+ * We should investigate converting to a linear search, even though that
+ * would make the command name "hash" a misnomer.
*/
+
+#define CMDTABLESIZE 31 /* should be prime */
+#define ARB 1 /* actual size determined at run time */
+
+struct tblentry {
+ struct tblentry *next; /* next entry in hash chain */
+ union param param; /* definition of builtin function */
+ short cmdtype; /* index identifying command */
+ char rehash; /* if set, cd done since entry created */
+ char cmdname[ARB]; /* name of command */
+};
+
+static struct tblentry *cmdtable[CMDTABLESIZE];
+static int builtinloc = -1; /* index in path of %builtin, or -1 */
+
static void
-find_command(char *name, struct cmdentry *entry, int act, const char *path)
+tryexec(char *cmd, char **argv, char **envp)
{
- struct tblentry *cmdp;
- int idx;
- int prev;
- char *fullname;
- struct stat statb;
- int e;
- int updatetbl;
- struct builtincmd *bcmd;
+ int repeated = 0;
+ struct BB_applet *a;
+ int argc = 0;
+ char **c;
- /* If name contains a slash, don't use PATH or hash table */
- if (strchr(name, '/') != NULL) {
- entry->u.index = -1;
- if (act & DO_ABS) {
- while (stat(name, &statb) < 0) {
-#ifdef SYSV
- if (errno == EINTR)
- continue;
-#endif
- entry->cmdtype = CMDUNKNOWN;
- return;
- }
+ if (strchr(cmd, '/') == NULL
+ && (a = find_applet_by_name(cmd)) != NULL
+ && is_safe_applet(cmd)
+ ) {
+ c = argv;
+ while (*c != NULL) {
+ c++; argc++;
}
- entry->cmdtype = CMDNORMAL;
- return;
+ applet_name = cmd;
+ exit(a->main(argc, argv));
}
-
#if ENABLE_FEATURE_SH_STANDALONE_SHELL
- if (find_applet_by_name(name)) {
- entry->cmdtype = CMDNORMAL;
- entry->u.index = -1;
- return;
+ if (find_applet_by_name(cmd) != NULL) {
+ /* re-exec ourselves with the new arguments */
+ execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp);
+ /* If they called chroot or otherwise made the binary no longer
+ * executable, fall through */
}
#endif
- if (is_safe_applet(name)) {
- entry->cmdtype = CMDNORMAL;
- entry->u.index = -1;
- return;
- }
+ repeat:
+#ifdef SYSV
+ do {
+ execve(cmd, argv, envp);
+ } while (errno == EINTR);
+#else
+ execve(cmd, argv, envp);
+#endif
+ if (repeated++) {
+ free(argv);
+ } else if (errno == ENOEXEC) {
+ char **ap;
+ char **new;
- updatetbl = (path == pathval());
- if (!updatetbl) {
- act |= DO_ALTPATH;
- if (strstr(path, "%builtin") != NULL)
- act |= DO_ALTBLTIN;
+ for (ap = argv; *ap; ap++)
+ ;
+ ap = new = ckmalloc((ap - argv + 2) * sizeof(char *));
+ ap[1] = cmd;
+ *ap = cmd = (char *)DEFAULT_SHELL;
+ ap += 2;
+ argv++;
+ while ((*ap++ = *argv++))
+ ;
+ argv = new;
+ goto repeat;
}
+}
- /* If name is in the table, check answer will be ok */
- cmdp = cmdlookup(name, 0);
- if (cmdp != NULL) {
- int bit;
+/*
+ * Exec a program. Never returns. If you change this routine, you may
+ * have to change the find_command routine as well.
+ */
+#define environment() listvars(VEXPORT, VUNSET, 0)
+static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN;
+static void
+shellexec(char **argv, const char *path, int idx)
+{
+ char *cmdname;
+ int e;
+ char **envp;
+ int exerrno;
- switch (cmdp->cmdtype) {
- default:
-#if DEBUG
- abort();
+ clearredir(1);
+ envp = environment();
+ if (strchr(argv[0], '/') || is_safe_applet(argv[0])
+#if ENABLE_FEATURE_SH_STANDALONE_SHELL
+ || find_applet_by_name(argv[0])
#endif
- case CMDNORMAL:
- bit = DO_ALTPATH;
- break;
- case CMDFUNCTION:
- bit = DO_NOFUNC;
- break;
- case CMDBUILTIN:
- bit = DO_ALTBLTIN;
- break;
+ ) {
+ tryexec(argv[0], argv, envp);
+ e = errno;
+ } else {
+ e = ENOENT;
+ while ((cmdname = padvance(&path, argv[0])) != NULL) {
+ if (--idx < 0 && pathopt == NULL) {
+ tryexec(cmdname, argv, envp);
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ }
+ stunalloc(cmdname);
}
- if (act & bit) {
- updatetbl = 0;
- cmdp = NULL;
- } else if (cmdp->rehash == 0)
- /* if not invalidated by cd, we're done */
- goto success;
}
- /* If %builtin not in path, check for builtin next */
- bcmd = find_builtin(name);
- if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || (
- act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0
- )))
- goto builtin_success;
+ /* Map to POSIX errors */
+ switch (e) {
+ case EACCES:
+ exerrno = 126;
+ break;
+ case ENOENT:
+ exerrno = 127;
+ break;
+ default:
+ exerrno = 2;
+ break;
+ }
+ exitstatus = exerrno;
+ TRACE(("shellexec failed for %s, errno %d, suppressint %d\n",
+ argv[0], e, suppressint ));
+ ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found"));
+ /* NOTREACHED */
+}
+
+static void
+printentry(struct tblentry *cmdp)
+{
+ int idx;
+ const char *path;
+ char *name;
+
+ idx = cmdp->param.index;
+ path = pathval();
+ do {
+ name = padvance(&path, cmdp->cmdname);
+ stunalloc(name);
+ } while (--idx >= 0);
+ out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr));
+}
- /* We have to search path. */
- prev = -1; /* where to start */
- if (cmdp && cmdp->rehash) { /* doing a rehash */
- if (cmdp->cmdtype == CMDBUILTIN)
- prev = builtinloc;
- else
- prev = cmdp->param.index;
- }
+/*
+ * Clear out command entries. The argument specifies the first entry in
+ * PATH which has changed.
+ */
+static void
+clearcmdentry(int firstchange)
+{
+ struct tblentry **tblp;
+ struct tblentry **pp;
+ struct tblentry *cmdp;
- e = ENOENT;
- idx = -1;
- loop:
- while ((fullname = padvance(&path, name)) != NULL) {
- stunalloc(fullname);
- idx++;
- if (pathopt) {
- if (prefix(pathopt, "builtin")) {
- if (bcmd)
- goto builtin_success;
- continue;
- } else if (!(act & DO_NOFUNC) &&
- prefix(pathopt, "func")) {
- /* handled below */
+ INT_OFF;
+ for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) {
+ pp = tblp;
+ while ((cmdp = *pp) != NULL) {
+ if ((cmdp->cmdtype == CMDNORMAL &&
+ cmdp->param.index >= firstchange)
+ || (cmdp->cmdtype == CMDBUILTIN &&
+ builtinloc >= firstchange)
+ ) {
+ *pp = cmdp->next;
+ free(cmdp);
} else {
- /* ignore unimplemented options */
- continue;
+ pp = &cmdp->next;
}
}
- /* if rehash, don't redo absolute path names */
- if (fullname[0] == '/' && idx <= prev) {
- if (idx < prev)
- continue;
- TRACE(("searchexec \"%s\": no change\n", name));
- goto success;
- }
- while (stat(fullname, &statb) < 0) {
-#ifdef SYSV
- if (errno == EINTR)
- continue;
-#endif
- if (errno != ENOENT && errno != ENOTDIR)
- e = errno;
- goto loop;
- }
- e = EACCES; /* if we fail, this will be the error */
- if (!S_ISREG(statb.st_mode))
- continue;
- if (pathopt) { /* this is a %func directory */
- stalloc(strlen(fullname) + 1);
- readcmdfile(fullname);
- cmdp = cmdlookup(name, 0);
- if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION)
- ash_msg_and_raise_error("%s not defined in %s", name, fullname);
- stunalloc(fullname);
- goto success;
- }
- TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
- if (!updatetbl) {
- entry->cmdtype = CMDNORMAL;
- entry->u.index = idx;
- return;
- }
- INT_OFF;
- cmdp = cmdlookup(name, 1);
- cmdp->cmdtype = CMDNORMAL;
- cmdp->param.index = idx;
- INT_ON;
- goto success;
- }
-
- /* We failed. If there was an entry for this command, delete it */
- if (cmdp && updatetbl)
- delete_cmd_entry();
- if (act & DO_ERR)
- ash_msg("%s: %s", name, errmsg(e, "not found"));
- entry->cmdtype = CMDUNKNOWN;
- return;
-
- builtin_success:
- if (!updatetbl) {
- entry->cmdtype = CMDBUILTIN;
- entry->u.cmd = bcmd;
- return;
}
- INT_OFF;
- cmdp = cmdlookup(name, 1);
- cmdp->cmdtype = CMDBUILTIN;
- cmdp->param.cmd = bcmd;
INT_ON;
- success:
- cmdp->rehash = 0;
- entry->cmdtype = cmdp->cmdtype;
- entry->u = cmdp->param;
}
/*
- * Execute a simple command.
+ * Locate a command in the command hash table. If "add" is nonzero,
+ * add the command to the table if it is not already present. The
+ * variable "lastcmdentry" is set to point to the address of the link
+ * pointing to the entry, so that delete_cmd_entry can delete the
+ * entry.
+ *
+ * Interrupts must be off if called with add != 0.
*/
-static int back_exitstatus; /* exit status of backquoted command */
-static int
-isassignment(const char *p)
-{
- const char *q = endofname(p);
- if (p == q)
- return 0;
- return *q == '=';
-}
-static int
-bltincmd(int argc, char **argv)
+static struct tblentry **lastcmdentry;
+
+static struct tblentry *
+cmdlookup(const char *name, int add)
{
- /* Preserve exitstatus of a previous possible redirection
- * as POSIX mandates */
- return back_exitstatus;
+ unsigned int hashval;
+ const char *p;
+ struct tblentry *cmdp;
+ struct tblentry **pp;
+
+ p = name;
+ hashval = (unsigned char)*p << 4;
+ while (*p)
+ hashval += (unsigned char)*p++;
+ hashval &= 0x7FFF;
+ pp = &cmdtable[hashval % CMDTABLESIZE];
+ for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+ if (strcmp(cmdp->cmdname, name) == 0)
+ break;
+ pp = &cmdp->next;
+ }
+ if (add && cmdp == NULL) {
+ cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB
+ + strlen(name) + 1);
+ cmdp->next = NULL;
+ cmdp->cmdtype = CMDUNKNOWN;
+ strcpy(cmdp->cmdname, name);
+ }
+ lastcmdentry = pp;
+ return cmdp;
}
+
+/*
+ * Delete the command entry returned on the last lookup.
+ */
static void
-evalcommand(union node *cmd, int flags)
+delete_cmd_entry(void)
{
- static const struct builtincmd bltin = {
- "\0\0", bltincmd
- };
- struct stackmark smark;
- union node *argp;
- struct arglist arglist;
- struct arglist varlist;
- char **argv;
- int argc;
- const struct strlist *sp;
- struct cmdentry cmdentry;
- struct job *jp;
- char *lastarg;
- const char *path;
- int spclbltin;
- int cmd_is_exec;
- int status;
- char **nargv;
- struct builtincmd *bcmd;
- int pseudovarflag = 0;
+ struct tblentry *cmdp;
- /* First expand the arguments. */
- TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
- setstackmark(&smark);
- back_exitstatus = 0;
+ INT_OFF;
+ cmdp = *lastcmdentry;
+ *lastcmdentry = cmdp->next;
+ if (cmdp->cmdtype == CMDFUNCTION)
+ freefunc(cmdp->param.func);
+ free(cmdp);
+ INT_ON;
+}
- cmdentry.cmdtype = CMDBUILTIN;
- cmdentry.u.cmd = &bltin;
- varlist.lastp = &varlist.list;
- *varlist.lastp = NULL;
- arglist.lastp = &arglist.list;
- *arglist.lastp = NULL;
+/*
+ * Add a new command entry, replacing any existing command entry for
+ * the same name - except special builtins.
+ */
+static void
+addcmdentry(char *name, struct cmdentry *entry)
+{
+ struct tblentry *cmdp;
- argc = 0;
- if (cmd->ncmd.args) {
- bcmd = find_builtin(cmd->ncmd.args->narg.text);
- pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+ cmdp = cmdlookup(name, 1);
+ if (cmdp->cmdtype == CMDFUNCTION) {
+ freefunc(cmdp->param.func);
}
+ cmdp->cmdtype = entry->cmdtype;
+ cmdp->param = entry->u;
+ cmdp->rehash = 0;
+}
- for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
- struct strlist **spp;
-
- spp = arglist.lastp;
- if (pseudovarflag && isassignment(argp->narg.text))
- expandarg(argp, &arglist, EXP_VARTILDE);
- else
- expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+static int
+hashcmd(int argc, char **argv)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
+ int c;
+ struct cmdentry entry;
+ char *name;
- for (sp = *spp; sp; sp = sp->next)
- argc++;
+ while ((c = nextopt("r")) != '\0') {
+ clearcmdentry(0);
+ return 0;
+ }
+ if (*argptr == NULL) {
+ for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
+ for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+ if (cmdp->cmdtype == CMDNORMAL)
+ printentry(cmdp);
+ }
+ }
+ return 0;
}
-
- argv = nargv = stalloc(sizeof(char *) * (argc + 1));
- for (sp = arglist.list; sp; sp = sp->next) {
- TRACE(("evalcommand arg: %s\n", sp->text));
- *nargv++ = sp->text;
+ c = 0;
+ while ((name = *argptr) != NULL) {
+ cmdp = cmdlookup(name, 0);
+ if (cmdp != NULL
+ && (cmdp->cmdtype == CMDNORMAL
+ || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0)))
+ delete_cmd_entry();
+ find_command(name, &entry, DO_ERR, pathval());
+ if (entry.cmdtype == CMDUNKNOWN)
+ c = 1;
+ argptr++;
}
- *nargv = NULL;
-
- lastarg = NULL;
- if (iflag && funcnest == 0 && argc > 0)
- lastarg = nargv[-1];
+ return c;
+}
- preverrout_fd = 2;
- expredir(cmd->ncmd.redirect);
- status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
+/*
+ * Called when a cd is done. Marks all commands so the next time they
+ * are executed they will be rehashed.
+ */
+static void
+hashcd(void)
+{
+ struct tblentry **pp;
+ struct tblentry *cmdp;
- path = vpath.text;
- for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
- struct strlist **spp;
- char *p;
+ for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
+ for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
+ if (cmdp->cmdtype == CMDNORMAL || (
+ cmdp->cmdtype == CMDBUILTIN &&
+ !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) &&
+ builtinloc > 0
+ ))
+ cmdp->rehash = 1;
+ }
+ }
+}
- spp = varlist.lastp;
- expandarg(argp, &varlist, EXP_VARTILDE);
+/*
+ * Fix command hash table when PATH changed.
+ * Called before PATH is changed. The argument is the new value of PATH;
+ * pathval() still returns the old value at this point.
+ * Called with interrupts off.
+ */
+static void
+changepath(const char *newval)
+{
+ const char *old, *new;
+ int idx;
+ int firstchange;
+ int idx_bltin;
- /*
- * Modify the command lookup path, if a PATH= assignment
- * is present
- */
- p = (*spp)->text;
- if (varequal(p, path))
- path = p;
+ old = pathval();
+ new = newval;
+ firstchange = 9999; /* assume no change */
+ idx = 0;
+ idx_bltin = -1;
+ for (;;) {
+ if (*old != *new) {
+ firstchange = idx;
+ if ((*old == '\0' && *new == ':')
+ || (*old == ':' && *new == '\0'))
+ firstchange++;
+ old = new; /* ignore subsequent differences */
+ }
+ if (*new == '\0')
+ break;
+ if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin"))
+ idx_bltin = idx;
+ if (*new == ':') {
+ idx++;
+ }
+ new++, old++;
}
+ if (builtinloc < 0 && idx_bltin >= 0)
+ builtinloc = idx_bltin; /* zap builtins */
+ if (builtinloc >= 0 && idx_bltin < 0)
+ firstchange = 0;
+ clearcmdentry(firstchange);
+ builtinloc = idx_bltin;
+}
- /* Print the command if xflag is set. */
- if (xflag) {
- int n;
- const char *p = " %s";
+#define TEOF 0
+#define TNL 1
+#define TREDIR 2
+#define TWORD 3
+#define TSEMI 4
+#define TBACKGND 5
+#define TAND 6
+#define TOR 7
+#define TPIPE 8
+#define TLP 9
+#define TRP 10
+#define TENDCASE 11
+#define TENDBQUOTE 12
+#define TNOT 13
+#define TCASE 14
+#define TDO 15
+#define TDONE 16
+#define TELIF 17
+#define TELSE 18
+#define TESAC 19
+#define TFI 20
+#define TFOR 21
+#define TIF 22
+#define TIN 23
+#define TTHEN 24
+#define TUNTIL 25
+#define TWHILE 26
+#define TBEGIN 27
+#define TEND 28
- p++;
- dprintf(preverrout_fd, p, expandstr(ps4val()));
+/* first char is indicating which tokens mark the end of a list */
+static const char *const tokname_array[] = {
+ "\1end of file",
+ "\0newline",
+ "\0redirection",
+ "\0word",
+ "\0;",
+ "\0&",
+ "\0&&",
+ "\0||",
+ "\0|",
+ "\0(",
+ "\1)",
+ "\1;;",
+ "\1`",
+#define KWDOFFSET 13
+ /* the following are keywords */
+ "\0!",
+ "\0case",
+ "\1do",
+ "\1done",
+ "\1elif",
+ "\1else",
+ "\1esac",
+ "\1fi",
+ "\0for",
+ "\0if",
+ "\0in",
+ "\1then",
+ "\0until",
+ "\0while",
+ "\0{",
+ "\1}",
+};
- sp = varlist.list;
- for (n = 0; n < 2; n++) {
- while (sp) {
- dprintf(preverrout_fd, p, sp->text);
- sp = sp->next;
- if (*p == '%') {
- p--;
- }
- }
- sp = arglist.list;
- }
- full_write(preverrout_fd, "\n", 1);
- }
+static const char *
+tokname(int tok)
+{
+ static char buf[16];
- cmd_is_exec = 0;
- spclbltin = -1;
+ if (tok >= TSEMI)
+ buf[0] = '"';
+ sprintf(buf + (tok >= TSEMI), "%s%c",
+ tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0));
+ return buf;
+}
- /* Now locate the command. */
- if (argc) {
- const char *oldpath;
- int cmd_flag = DO_ERR;
+/* Wrapper around strcmp for qsort/bsearch/... */
+static int
+pstrcmp(const void *a, const void *b)
+{
+ return strcmp((const char *) a, (*(const char *const *) b) + 1);
+}
- path += 5;
- oldpath = path;
- for (;;) {
- find_command(argv[0], &cmdentry, cmd_flag, path);
- if (cmdentry.cmdtype == CMDUNKNOWN) {
- status = 127;
- flush_stderr();
- goto bail;
- }
+static const char *const *
+findkwd(const char *s)
+{
+ return bsearch(s, tokname_array + KWDOFFSET,
+ (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET,
+ sizeof(const char *), pstrcmp);
+}
- /* implement bltin and command here */
- if (cmdentry.cmdtype != CMDBUILTIN)
- break;
- if (spclbltin < 0)
- spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
- if (cmdentry.u.cmd == EXECCMD)
- cmd_is_exec++;
+/*
+ * Locate and print what a word is...
+ */
#if ENABLE_ASH_CMDCMD
- if (cmdentry.u.cmd == COMMANDCMD) {
- path = oldpath;
- nargv = parse_command_args(argv, &path);
- if (!nargv)
- break;
- argc -= nargv - argv;
- argv = nargv;
- cmd_flag |= DO_NOFUNC;
- } else
+static int
+describe_command(char *command, int describe_command_verbose)
+#else
+#define describe_command_verbose 1
+static int
+describe_command(char *command)
#endif
- break;
- }
+{
+ struct cmdentry entry;
+ struct tblentry *cmdp;
+#if ENABLE_ASH_ALIAS
+ const struct alias *ap;
+#endif
+ const char *path = pathval();
+
+ if (describe_command_verbose) {
+ out1str(command);
}
- if (status) {
- /* We have a redirection error. */
- if (spclbltin > 0)
- raise_exception(EXERROR);
- bail:
- exitstatus = status;
+ /* First look at the keywords */
+ if (findkwd(command)) {
+ out1str(describe_command_verbose ? " is a shell keyword" : command);
goto out;
}
- /* Execute the command. */
- switch (cmdentry.cmdtype) {
- default:
- /* Fork off a child process if necessary. */
- if (!(flags & EV_EXIT) || trap[0]) {
- INT_OFF;
- jp = makejob(cmd, 1);
- if (forkshell(jp, cmd, FORK_FG) != 0) {
- exitstatus = waitforjob(jp);
- INT_ON;
- break;
- }
- FORCE_INT_ON;
+#if ENABLE_ASH_ALIAS
+ /* Then look at the aliases */
+ ap = lookupalias(command, 0);
+ if (ap != NULL) {
+ if (describe_command_verbose) {
+ out1fmt(" is an alias for %s", ap->val);
+ } else {
+ out1str("alias ");
+ printalias(ap);
+ return 0;
}
- listsetvar(varlist.list, VEXPORT|VSTACK);
- shellexec(argv, path, cmdentry.u.index);
- /* NOTREACHED */
+ goto out;
+ }
+#endif
+ /* Then check if it is a tracked alias */
+ cmdp = cmdlookup(command, 0);
+ if (cmdp != NULL) {
+ entry.cmdtype = cmdp->cmdtype;
+ entry.u = cmdp->param;
+ } else {
+ /* Finally use brute force */
+ find_command(command, &entry, DO_ABS, path);
+ }
- case CMDBUILTIN:
- cmdenviron = varlist.list;
- if (cmdenviron) {
- struct strlist *list = cmdenviron;
- int i = VNOSET;
- if (spclbltin > 0 || argc == 0) {
- i = 0;
- if (cmd_is_exec && argc > 1)
- i = VEXPORT;
- }
- listsetvar(list, i);
+ switch (entry.cmdtype) {
+ case CMDNORMAL: {
+ int j = entry.u.index;
+ char *p;
+ if (j == -1) {
+ p = command;
+ } else {
+ do {
+ p = padvance(&path, command);
+ stunalloc(p);
+ } while (--j >= 0);
}
- if (evalbltin(cmdentry.u.cmd, argc, argv)) {
- int exit_status;
- int i, j;
-
- i = exception;
- if (i == EXEXIT)
- goto raise;
-
- exit_status = 2;
- j = 0;
- if (i == EXINT)
- j = SIGINT;
- if (i == EXSIG)
- j = pendingsigs;
- if (j)
- exit_status = j + 128;
- exitstatus = exit_status;
-
- if (i == EXINT || spclbltin > 0) {
- raise:
- longjmp(exception_handler->loc, 1);
- }
- FORCE_INT_ON;
+ if (describe_command_verbose) {
+ out1fmt(" is%s %s",
+ (cmdp ? " a tracked alias for" : nullstr), p
+ );
+ } else {
+ out1str(p);
}
break;
+ }
case CMDFUNCTION:
- listsetvar(varlist.list, 0);
- if (evalfun(cmdentry.u.func, argc, argv, flags))
- goto raise;
+ if (describe_command_verbose) {
+ out1str(" is a shell function");
+ } else {
+ out1str(command);
+ }
break;
- }
+ case CMDBUILTIN:
+ if (describe_command_verbose) {
+ out1fmt(" is a %sshell builtin",
+ IS_BUILTIN_SPECIAL(entry.u.cmd) ?
+ "special " : nullstr
+ );
+ } else {
+ out1str(command);
+ }
+ break;
+
+ default:
+ if (describe_command_verbose) {
+ out1str(": not found\n");
+ }
+ return 127;
+ }
out:
- popredir(cmd_is_exec);
- if (lastarg)
- /* dsl: I think this is intended to be used to support
- * '_' in 'vi' command mode during line editing...
- * However I implemented that within libedit itself.
- */
- setvar("_", lastarg, 0);
- popstackmark(&smark);
+ outstr("\n", stdout);
+ return 0;
}
static int
-evalbltin(const struct builtincmd *cmd, int argc, char **argv)
+typecmd(int argc, char **argv)
{
- char *volatile savecmdname;
- struct jmploc *volatile savehandler;
- struct jmploc jmploc;
int i;
+ int err = 0;
- savecmdname = commandname;
- i = setjmp(jmploc.loc);
- if (i)
- goto cmddone;
- savehandler = exception_handler;
- exception_handler = &jmploc;
- commandname = argv[0];
- argptr = argv + 1;
- optptr = NULL; /* initialize nextopt */
- exitstatus = (*cmd->builtin)(argc, argv);
- flush_stdout_stderr();
- cmddone:
- exitstatus |= ferror(stdout);
- clearerr(stdout);
- commandname = savecmdname;
- exsig = 0;
- exception_handler = savehandler;
-
- return i;
+ for (i = 1; i < argc; i++) {
+#if ENABLE_ASH_CMDCMD
+ err |= describe_command(argv[i], 1);
+#else
+ err |= describe_command(argv[i]);
+#endif
+ }
+ return err;
}
+#if ENABLE_ASH_CMDCMD
static int
-goodname(const char *p)
+commandcmd(int argc, char **argv)
{
- return !*endofname(p);
-}
-
+ int c;
+ enum {
+ VERIFY_BRIEF = 1,
+ VERIFY_VERBOSE = 2,
+ } verify = 0;
-/*
- * Search for a command. This is called before we fork so that the
- * location of the command will be available in the parent as well as
- * the child. The check for "goodname" is an overly conservative
- * check that the name will not be subject to expansion.
- */
-static void
-prehash(union node *n)
-{
- struct cmdentry entry;
+ while ((c = nextopt("pvV")) != '\0')
+ if (c == 'V')
+ verify |= VERIFY_VERBOSE;
+ else if (c == 'v')
+ verify |= VERIFY_BRIEF;
+#if DEBUG
+ else if (c != 'p')
+ abort();
+#endif
+ if (verify)
+ return describe_command(*argptr, verify - VERIFY_BRIEF);
- if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text))
- find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
+ return 0;
}
+#endif
-/*
- * Builtin commands. Builtin commands whose functions are closely
- * tied to evaluation are implemented here.
- */
+/* ============ eval.c */
-/*
- * Handle break and continue commands. Break, continue, and return are
- * all handled by setting the evalskip flag. The evaluation routines
- * above all check this flag, and if it is set they start skipping
- * commands rather than executing them. The variable skipcount is
- * the number of loops to break/continue, or the number of function
- * levels to return. (The latter is always 1.) It should probably
- * be an error to break out of more loops than exist, but it isn't
- * in the standard shell so we don't make it one here.
- */
+static int funcblocksize; /* size of structures in function */
+static int funcstringsize; /* size of strings in node */
+static void *funcblock; /* block to allocate function from */
+static char *funcstring; /* block to allocate strings from */
+
+/* flags in argument to evaltree */
+#define EV_EXIT 01 /* exit after evaluating tree */
+#define EV_TESTED 02 /* exit status is checked; ignore -e flag */
+#define EV_BACKCMD 04 /* command executing within back quotes */
+
+static const short nodesize[26] = {
+ SHELL_ALIGN(sizeof(struct ncmd)),
+ SHELL_ALIGN(sizeof(struct npipe)),
+ SHELL_ALIGN(sizeof(struct nredir)),
+ SHELL_ALIGN(sizeof(struct nredir)),
+ SHELL_ALIGN(sizeof(struct nredir)),
+ SHELL_ALIGN(sizeof(struct nbinary)),
+ SHELL_ALIGN(sizeof(struct nbinary)),
+ SHELL_ALIGN(sizeof(struct nbinary)),
+ SHELL_ALIGN(sizeof(struct nif)),
+ SHELL_ALIGN(sizeof(struct nbinary)),
+ SHELL_ALIGN(sizeof(struct nbinary)),
+ SHELL_ALIGN(sizeof(struct nfor)),
+ SHELL_ALIGN(sizeof(struct ncase)),
+ SHELL_ALIGN(sizeof(struct nclist)),
+ SHELL_ALIGN(sizeof(struct narg)),
+ SHELL_ALIGN(sizeof(struct narg)),
+ SHELL_ALIGN(sizeof(struct nfile)),
+ SHELL_ALIGN(sizeof(struct nfile)),
+ SHELL_ALIGN(sizeof(struct nfile)),
+ SHELL_ALIGN(sizeof(struct nfile)),
+ SHELL_ALIGN(sizeof(struct nfile)),
+ SHELL_ALIGN(sizeof(struct ndup)),
+ SHELL_ALIGN(sizeof(struct ndup)),
+ SHELL_ALIGN(sizeof(struct nhere)),
+ SHELL_ALIGN(sizeof(struct nhere)),
+ SHELL_ALIGN(sizeof(struct nnot)),
+};
-static int
-breakcmd(int argc, char **argv)
-{
- int n = argc > 1 ? number(argv[1]) : 1;
+static void calcsize(union node *n);
- if (n <= 0)
- ash_msg_and_raise_error(illnum, argv[1]);
- if (n > loopnest)
- n = loopnest;
- if (n > 0) {
- evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
- skipcount = n;
+static void
+sizenodelist(struct nodelist *lp)
+{
+ while (lp) {
+ funcblocksize += SHELL_ALIGN(sizeof(struct nodelist));
+ calcsize(lp->n);
+ lp = lp->next;
}
- return 0;
}
-/*
- * The return command.
- */
-static int
-returncmd(int argc, char **argv)
+static void
+calcsize(union node *n)
{
- /*
- * If called outside a function, do what ksh does;
- * skip the rest of the file.
- */
- evalskip = funcnest ? SKIPFUNC : SKIPFILE;
- return argv[1] ? number(argv[1]) : exitstatus;
+ if (n == NULL)
+ return;
+ funcblocksize += nodesize[n->type];
+ switch (n->type) {
+ case NCMD:
+ calcsize(n->ncmd.redirect);
+ calcsize(n->ncmd.args);
+ calcsize(n->ncmd.assign);
+ break;
+ case NPIPE:
+ sizenodelist(n->npipe.cmdlist);
+ break;
+ case NREDIR:
+ case NBACKGND:
+ case NSUBSHELL:
+ calcsize(n->nredir.redirect);
+ calcsize(n->nredir.n);
+ break;
+ case NAND:
+ case NOR:
+ case NSEMI:
+ case NWHILE:
+ case NUNTIL:
+ calcsize(n->nbinary.ch2);
+ calcsize(n->nbinary.ch1);
+ break;
+ case NIF:
+ calcsize(n->nif.elsepart);
+ calcsize(n->nif.ifpart);
+ calcsize(n->nif.test);
+ break;
+ case NFOR:
+ funcstringsize += strlen(n->nfor.var) + 1;
+ calcsize(n->nfor.body);
+ calcsize(n->nfor.args);
+ break;
+ case NCASE:
+ calcsize(n->ncase.cases);
+ calcsize(n->ncase.expr);
+ break;
+ case NCLIST:
+ calcsize(n->nclist.body);
+ calcsize(n->nclist.pattern);
+ calcsize(n->nclist.next);
+ break;
+ case NDEFUN:
+ case NARG:
+ sizenodelist(n->narg.backquote);
+ funcstringsize += strlen(n->narg.text) + 1;
+ calcsize(n->narg.next);
+ break;
+ case NTO:
+ case NCLOBBER:
+ case NFROM:
+ case NFROMTO:
+ case NAPPEND:
+ calcsize(n->nfile.fname);
+ calcsize(n->nfile.next);
+ break;
+ case NTOFD:
+ case NFROMFD:
+ calcsize(n->ndup.vname);
+ calcsize(n->ndup.next);
+ break;
+ case NHERE:
+ case NXHERE:
+ calcsize(n->nhere.doc);
+ calcsize(n->nhere.next);
+ break;
+ case NNOT:
+ calcsize(n->nnot.com);
+ break;
+ };
}
-static int
-falsecmd(int argc, char **argv)
+static char *
+nodeckstrdup(char *s)
{
- return 1;
-}
+ char *rtn = funcstring;
-static int
-truecmd(int argc, char **argv)
-{
- return 0;
+ strcpy(funcstring, s);
+ funcstring += strlen(s) + 1;
+ return rtn;
}
-static int
-execcmd(int argc, char **argv)
+static union node *copynode(union node *);
+
+static struct nodelist *
+copynodelist(struct nodelist *lp)
{
- if (argc > 1) {
- iflag = 0; /* exit on error */
- mflag = 0;
- optschanged();
- shellexec(argv + 1, pathval(), 0);
+ struct nodelist *start;
+ struct nodelist **lpp;
+
+ lpp = &start;
+ while (lp) {
+ *lpp = funcblock;
+ funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist));
+ (*lpp)->n = copynode(lp->n);
+ lp = lp->next;
+ lpp = &(*lpp)->next;
}
- return 0;
+ *lpp = NULL;
+ return start;
}
+static union node *
+copynode(union node *n)
+{
+ union node *new;
-/* ============ input.c
- *
- * This implements the input routines used by the parser.
- */
-
-#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
-
-enum {
- INPUT_PUSH_FILE = 1,
- INPUT_NOFILE_OK = 2,
-};
+ if (n == NULL)
+ return NULL;
+ new = funcblock;
+ funcblock = (char *) funcblock + nodesize[n->type];
-/*
- * NEOF is returned by parsecmd when it encounters an end of file. It
- * must be distinct from NULL, so we use the address of a variable that
- * happens to be handy.
- */
-static int plinno = 1; /* input line number */
-/* number of characters left in input buffer */
-static int parsenleft; /* copy of parsefile->nleft */
-static int parselleft; /* copy of parsefile->lleft */
-/* next character in input buffer */
-static char *parsenextc; /* copy of parsefile->nextc */
+ switch (n->type) {
+ case NCMD:
+ new->ncmd.redirect = copynode(n->ncmd.redirect);
+ new->ncmd.args = copynode(n->ncmd.args);
+ new->ncmd.assign = copynode(n->ncmd.assign);
+ break;
+ case NPIPE:
+ new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
+ new->npipe.backgnd = n->npipe.backgnd;
+ break;
+ case NREDIR:
+ case NBACKGND:
+ case NSUBSHELL:
+ new->nredir.redirect = copynode(n->nredir.redirect);
+ new->nredir.n = copynode(n->nredir.n);
+ break;
+ case NAND:
+ case NOR:
+ case NSEMI:
+ case NWHILE:
+ case NUNTIL:
+ new->nbinary.ch2 = copynode(n->nbinary.ch2);
+ new->nbinary.ch1 = copynode(n->nbinary.ch1);
+ break;
+ case NIF:
+ new->nif.elsepart = copynode(n->nif.elsepart);
+ new->nif.ifpart = copynode(n->nif.ifpart);
+ new->nif.test = copynode(n->nif.test);
+ break;
+ case NFOR:
+ new->nfor.var = nodeckstrdup(n->nfor.var);
+ new->nfor.body = copynode(n->nfor.body);
+ new->nfor.args = copynode(n->nfor.args);
+ break;
+ case NCASE:
+ new->ncase.cases = copynode(n->ncase.cases);
+ new->ncase.expr = copynode(n->ncase.expr);
+ break;
+ case NCLIST:
+ new->nclist.body = copynode(n->nclist.body);
+ new->nclist.pattern = copynode(n->nclist.pattern);
+ new->nclist.next = copynode(n->nclist.next);
+ break;
+ case NDEFUN:
+ case NARG:
+ new->narg.backquote = copynodelist(n->narg.backquote);
+ new->narg.text = nodeckstrdup(n->narg.text);
+ new->narg.next = copynode(n->narg.next);
+ break;
+ case NTO:
+ case NCLOBBER:
+ case NFROM:
+ case NFROMTO:
+ case NAPPEND:
+ new->nfile.fname = copynode(n->nfile.fname);
+ new->nfile.fd = n->nfile.fd;
+ new->nfile.next = copynode(n->nfile.next);
+ break;
+ case NTOFD:
+ case NFROMFD:
+ new->ndup.vname = copynode(n->ndup.vname);
+ new->ndup.dupfd = n->ndup.dupfd;
+ new->ndup.fd = n->ndup.fd;
+ new->ndup.next = copynode(n->ndup.next);
+ break;
+ case NHERE:
+ case NXHERE:
+ new->nhere.doc = copynode(n->nhere.doc);
+ new->nhere.fd = n->nhere.fd;
+ new->nhere.next = copynode(n->nhere.next);
+ break;
+ case NNOT:
+ new->nnot.com = copynode(n->nnot.com);
+ break;
+ };
+ new->type = n->type;
+ return new;
+}
-static int checkkwd;
-/* values of checkkwd variable */
-#define CHKALIAS 0x1
-#define CHKKWD 0x2
-#define CHKNL 0x4
+/*
+ * Make a copy of a parse tree.
+ */
+static struct funcnode *
+copyfunc(union node *n)
+{
+ struct funcnode *f;
+ size_t blocksize;
+ funcblocksize = offsetof(struct funcnode, n);
+ funcstringsize = 0;
+ calcsize(n);
+ blocksize = funcblocksize;
+ f = ckmalloc(blocksize + funcstringsize);
+ funcblock = (char *) f + offsetof(struct funcnode, n);
+ funcstring = (char *) f + blocksize;
+ copynode(n);
+ f->count = 0;
+ return f;
+}
+/*
+ * Define a shell function.
+ */
static void
-popstring(void)
+defun(char *name, union node *func)
{
- struct strpush *sp = parsefile->strpush;
+ struct cmdentry entry;
INT_OFF;
-#if ENABLE_ASH_ALIAS
- if (sp->ap) {
- if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
- checkkwd |= CHKALIAS;
- }
- if (sp->string != sp->ap->val) {
- free(sp->string);
- }
- sp->ap->flag &= ~ALIASINUSE;
- if (sp->ap->flag & ALIASDEAD) {
- unalias(sp->ap->name);
- }
- }
-#endif
- parsenextc = sp->prevstring;
- parsenleft = sp->prevnleft;
-/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
- parsefile->strpush = sp->prev;
- if (sp != &(parsefile->basestrpush))
- free(sp);
+ entry.cmdtype = CMDFUNCTION;
+ entry.u.func = copyfunc(func);
+ addcmdentry(name, &entry);
INT_ON;
}
-static int
-preadfd(void)
-{
- int nr;
- char *buf = parsefile->buf;
- parsenextc = buf;
-
- retry:
-#if ENABLE_FEATURE_EDITING
- if (!iflag || parsefile->fd)
- nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
- else {
-#if ENABLE_FEATURE_TAB_COMPLETION
- line_input_state->path_lookup = pathval();
-#endif
- nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state);
- if (nr == 0) {
- /* Ctrl+C pressed */
- if (trap[SIGINT]) {
- buf[0] = '\n';
- buf[1] = '\0';
- raise(SIGINT);
- return 1;
- }
- goto retry;
- }
- if (nr < 0 && errno == 0) {
- /* Ctrl+D presend */
- nr = 0;
- }
- }
-#else
- nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
-#endif
+static int evalskip; /* set if we are skipping commands */
+/* reasons for skipping commands (see comment on breakcmd routine) */
+#define SKIPBREAK (1 << 0)
+#define SKIPCONT (1 << 1)
+#define SKIPFUNC (1 << 2)
+#define SKIPFILE (1 << 3)
+#define SKIPEVAL (1 << 4)
+static int skipcount; /* number of levels to skip */
+static int funcnest; /* depth of function calls */
- if (nr < 0) {
- if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
- int flags = fcntl(0, F_GETFL, 0);
- if (flags >= 0 && flags & O_NONBLOCK) {
- flags &=~ O_NONBLOCK;
- if (fcntl(0, F_SETFL, flags) >= 0) {
- out2str("sh: turning off NDELAY mode\n");
- goto retry;
- }
- }
- }
- }
- return nr;
-}
+/* forward decl way out to parsing code - dotrap needs it */
+static int evalstring(char *s, int mask);
/*
- * Refill the input buffer and return the next input character:
- *
- * 1) If a string was pushed back on the input, pop it;
- * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
- * from a string so we can't refill the buffer, return EOF.
- * 3) If the is more stuff in this buffer, use it else call read to fill it.
- * 4) Process input up to the next newline, deleting nul characters.
+ * Called to execute a trap. Perhaps we should avoid entering new trap
+ * handlers while we are executing a trap handler.
*/
static int
-preadbuffer(void)
+dotrap(void)
{
+ char *p;
char *q;
- int more;
- char savec;
+ int i;
+ int savestatus;
+ int skip = 0;
- while (parsefile->strpush) {
-#if ENABLE_ASH_ALIAS
- if (parsenleft == -1 && parsefile->strpush->ap &&
- parsenextc[-1] != ' ' && parsenextc[-1] != '\t') {
- return PEOA;
- }
-#endif
- popstring();
- if (--parsenleft >= 0)
- return SC2INT(*parsenextc++);
- }
- if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
- return PEOF;
- flush_stdout_stderr();
+ savestatus = exitstatus;
+ pendingsigs = 0;
+ xbarrier();
- more = parselleft;
- if (more <= 0) {
- again:
- more = preadfd();
- if (more <= 0) {
- parselleft = parsenleft = EOF_NLEFT;
- return PEOF;
- }
- }
+ for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
+ if (!*q)
+ continue;
+ *q = '\0';
- q = parsenextc;
+ p = trap[i + 1];
+ if (!p)
+ continue;
+ skip = evalstring(p, SKIPEVAL);
+ exitstatus = savestatus;
+ if (skip)
+ break;
+ }
- /* delete nul characters */
- for (;;) {
- int c;
+ return skip;
+}
- more--;
- c = *q;
+/* forward declarations - evaluation is fairly recursive business... */
+static void evalloop(union node *, int);
+static void evalfor(union node *, int);
+static void evalcase(union node *, int);
+static void evalsubshell(union node *, int);
+static void expredir(union node *);
+static void evalpipe(union node *, int);
+static void evalcommand(union node *, int);
+static int evalbltin(const struct builtincmd *, int, char **);
+static void prehash(union node *);
- if (!c)
- memmove(q, q + 1, more);
- else {
- q++;
- if (c == '\n') {
- parsenleft = q - parsenextc - 1;
- break;
- }
+/*
+ * Evaluate a parse tree. The value is left in the global variable
+ * exitstatus.
+ */
+static void
+evaltree(union node *n, int flags)
+{
+ int checkexit = 0;
+ void (*evalfn)(union node *, int);
+ unsigned isor;
+ int status;
+ if (n == NULL) {
+ TRACE(("evaltree(NULL) called\n"));
+ goto out;
+ }
+ TRACE(("pid %d, evaltree(%p: %d, %d) called\n",
+ getpid(), n, n->type, flags));
+ switch (n->type) {
+ default:
+#if DEBUG
+ out1fmt("Node type = %d\n", n->type);
+ fflush(stdout);
+ break;
+#endif
+ case NNOT:
+ evaltree(n->nnot.com, EV_TESTED);
+ status = !exitstatus;
+ goto setstatus;
+ case NREDIR:
+ expredir(n->nredir.redirect);
+ status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
+ if (!status) {
+ evaltree(n->nredir.n, flags & EV_TESTED);
+ status = exitstatus;
+ }
+ popredir(0);
+ goto setstatus;
+ case NCMD:
+ evalfn = evalcommand;
+ checkexit:
+ if (eflag && !(flags & EV_TESTED))
+ checkexit = ~0;
+ goto calleval;
+ case NFOR:
+ evalfn = evalfor;
+ goto calleval;
+ case NWHILE:
+ case NUNTIL:
+ evalfn = evalloop;
+ goto calleval;
+ case NSUBSHELL:
+ case NBACKGND:
+ evalfn = evalsubshell;
+ goto calleval;
+ case NPIPE:
+ evalfn = evalpipe;
+ goto checkexit;
+ case NCASE:
+ evalfn = evalcase;
+ goto calleval;
+ case NAND:
+ case NOR:
+ case NSEMI:
+#if NAND + 1 != NOR
+#error NAND + 1 != NOR
+#endif
+#if NOR + 1 != NSEMI
+#error NOR + 1 != NSEMI
+#endif
+ isor = n->type - NAND;
+ evaltree(
+ n->nbinary.ch1,
+ (flags | ((isor >> 1) - 1)) & EV_TESTED
+ );
+ if (!exitstatus == isor)
+ break;
+ if (!evalskip) {
+ n = n->nbinary.ch2;
+ evaln:
+ evalfn = evaltree;
+ calleval:
+ evalfn(n, flags);
+ break;
}
-
- if (more <= 0) {
- parsenleft = q - parsenextc - 1;
- if (parsenleft < 0)
- goto again;
+ break;
+ case NIF:
+ evaltree(n->nif.test, EV_TESTED);
+ if (evalskip)
break;
+ if (exitstatus == 0) {
+ n = n->nif.ifpart;
+ goto evaln;
+ } else if (n->nif.elsepart) {
+ n = n->nif.elsepart;
+ goto evaln;
}
+ goto success;
+ case NDEFUN:
+ defun(n->narg.text, n->narg.next);
+ success:
+ status = 0;
+ setstatus:
+ exitstatus = status;
+ break;
}
- parselleft = more;
-
- savec = *q;
- *q = '\0';
+ out:
+ if ((checkexit & exitstatus))
+ evalskip |= SKIPEVAL;
+ else if (pendingsigs && dotrap())
+ goto exexit;
- if (vflag) {
- out2str(parsenextc);
+ if (flags & EV_EXIT) {
+ exexit:
+ raise_exception(EXEXIT);
}
-
- *q = savec;
-
- return SC2INT(*parsenextc++);
}
-#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer())
-
-#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
-#define pgetc_macro() pgetc()
-static int
-pgetc(void)
-{
- return pgetc_as_macro();
-}
-#else
-#define pgetc_macro() pgetc_as_macro()
-static int
-pgetc(void)
-{
- return pgetc_macro();
-}
+#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3)
+static
#endif
+void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__));
-/*
- * Same as pgetc(), but ignores PEOA.
- */
-#if ENABLE_ASH_ALIAS
-static int
-pgetc2(void)
-{
- int c;
+static int loopnest; /* current loop nesting level */
- do {
- c = pgetc_macro();
- } while (c == PEOA);
- return c;
-}
-#else
-static int
-pgetc2(void)
+static void
+evalloop(union node *n, int flags)
{
- return pgetc_macro();
-}
-#endif
+ int status;
-/*
- * Read a line from the script.
- */
-static char *
-pfgets(char *line, int len)
-{
- char *p = line;
- int nleft = len;
- int c;
+ loopnest++;
+ status = 0;
+ flags &= EV_TESTED;
+ for (;;) {
+ int i;
- while (--nleft > 0) {
- c = pgetc2();
- if (c == PEOF) {
- if (p == line)
- return NULL;
+ evaltree(n->nbinary.ch1, EV_TESTED);
+ if (evalskip) {
+ skipping:
+ if (evalskip == SKIPCONT && --skipcount <= 0) {
+ evalskip = 0;
+ continue;
+ }
+ if (evalskip == SKIPBREAK && --skipcount <= 0)
+ evalskip = 0;
break;
}
- *p++ = c;
- if (c == '\n')
+ i = exitstatus;
+ if (n->type != NWHILE)
+ i = !i;
+ if (i != 0)
break;
+ evaltree(n->nbinary.ch2, flags);
+ status = exitstatus;
+ if (evalskip)
+ goto skipping;
}
- *p = '\0';
- return line;
-}
-
-/*
- * Undo the last call to pgetc. Only one character may be pushed back.
- * PEOF may be pushed back.
- */
-static void
-pungetc(void)
-{
- parsenleft++;
- parsenextc--;
+ loopnest--;
+ exitstatus = status;
}
-/*
- * Push a string back onto the input at this current parsefile level.
- * We handle aliases this way.
- */
static void
-pushstring(char *s, void *ap)
+evalfor(union node *n, int flags)
{
- struct strpush *sp;
- size_t len;
+ struct arglist arglist;
+ union node *argp;
+ struct strlist *sp;
+ struct stackmark smark;
- len = strlen(s);
- INT_OFF;
-/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
- if (parsefile->strpush) {
- sp = ckmalloc(sizeof(struct strpush));
- sp->prev = parsefile->strpush;
- parsefile->strpush = sp;
- } else
- sp = parsefile->strpush = &(parsefile->basestrpush);
- sp->prevstring = parsenextc;
- sp->prevnleft = parsenleft;
-#if ENABLE_ASH_ALIAS
- sp->ap = (struct alias *)ap;
- if (ap) {
- ((struct alias *)ap)->flag |= ALIASINUSE;
- sp->string = s;
+ setstackmark(&smark);
+ arglist.lastp = &arglist.list;
+ for (argp = n->nfor.args; argp; argp = argp->narg.next) {
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD);
+ /* XXX */
+ if (evalskip)
+ goto out;
}
-#endif
- parsenextc = s;
- parsenleft = len;
- INT_ON;
-}
-
-/*
- * To handle the "." command, a stack of input files is used. Pushfile
- * adds a new entry to the stack and popfile restores the previous level.
- */
-static void
-pushfile(void)
-{
- struct parsefile *pf;
-
- parsefile->nleft = parsenleft;
- parsefile->lleft = parselleft;
- parsefile->nextc = parsenextc;
- parsefile->linno = plinno;
- pf = ckmalloc(sizeof(*pf));
- pf->prev = parsefile;
- pf->fd = -1;
- pf->strpush = NULL;
- pf->basestrpush.prev = NULL;
- parsefile = pf;
-}
-
-static void
-popfile(void)
-{
- struct parsefile *pf = parsefile;
+ *arglist.lastp = NULL;
- INT_OFF;
- if (pf->fd >= 0)
- close(pf->fd);
- if (pf->buf)
- free(pf->buf);
- while (pf->strpush)
- popstring();
- parsefile = pf->prev;
- free(pf);
- parsenleft = parsefile->nleft;
- parselleft = parsefile->lleft;
- parsenextc = parsefile->nextc;
- plinno = parsefile->linno;
- INT_ON;
+ exitstatus = 0;
+ loopnest++;
+ flags &= EV_TESTED;
+ for (sp = arglist.list; sp; sp = sp->next) {
+ setvar(n->nfor.var, sp->text, 0);
+ evaltree(n->nfor.body, flags);
+ if (evalskip) {
+ if (evalskip == SKIPCONT && --skipcount <= 0) {
+ evalskip = 0;
+ continue;
+ }
+ if (evalskip == SKIPBREAK && --skipcount <= 0)
+ evalskip = 0;
+ break;
+ }
+ }
+ loopnest--;
+ out:
+ popstackmark(&smark);
}
-/*
- * Return to top level.
- */
static void
-popallfiles(void)
+evalcase(union node *n, int flags)
{
- while (parsefile != &basepf)
- popfile();
-}
+ union node *cp;
+ union node *patp;
+ struct arglist arglist;
+ struct stackmark smark;
-/*
- * Close the file(s) that the shell is reading commands from. Called
- * after a fork is done.
- */
-static void
-closescript(void)
-{
- popallfiles();
- if (parsefile->fd > 0) {
- close(parsefile->fd);
- parsefile->fd = 0;
+ setstackmark(&smark);
+ arglist.lastp = &arglist.list;
+ expandarg(n->ncase.expr, &arglist, EXP_TILDE);
+ exitstatus = 0;
+ for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
+ for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
+ if (casematch(patp, arglist.list->text)) {
+ if (evalskip == 0) {
+ evaltree(cp->nclist.body, flags);
+ }
+ goto out;
+ }
+ }
}
+ out:
+ popstackmark(&smark);
}
/*
- * Like setinputfile, but takes an open file descriptor. Call this with
- * interrupts off.
+ * Kick off a subshell to evaluate a tree.
*/
static void
-setinputfd(int fd, int push)
-{
- fcntl(fd, F_SETFD, FD_CLOEXEC);
- if (push) {
- pushfile();
- parsefile->buf = 0;
- }
- parsefile->fd = fd;
- if (parsefile->buf == NULL)
- parsefile->buf = ckmalloc(IBUFSIZ);
- parselleft = parsenleft = 0;
- plinno = 1;
-}
-
-/*
- * Set the input to take input from a file. If push is set, push the
- * old input onto the stack first.
- */
-static int
-setinputfile(const char *fname, int flags)
+evalsubshell(union node *n, int flags)
{
- int fd;
- int fd2;
+ struct job *jp;
+ int backgnd = (n->type == NBACKGND);
+ int status;
+ expredir(n->nredir.redirect);
+ if (!backgnd && flags & EV_EXIT && !trap[0])
+ goto nofork;
INT_OFF;
- fd = open(fname, O_RDONLY);
- if (fd < 0) {
- if (flags & INPUT_NOFILE_OK)
- goto out;
- ash_msg_and_raise_error("Can't open %s", fname);
- }
- if (fd < 10) {
- fd2 = copyfd(fd, 10);
- close(fd);
- if (fd2 < 0)
- ash_msg_and_raise_error("Out of file descriptors");
- fd = fd2;
+ jp = makejob(n, 1);
+ if (forkshell(jp, n, backgnd) == 0) {
+ INT_ON;
+ flags |= EV_EXIT;
+ if (backgnd)
+ flags &=~ EV_TESTED;
+ nofork:
+ redirect(n->nredir.redirect, 0);
+ evaltreenr(n->nredir.n, flags);
+ /* never returns */
}
- setinputfd(fd, flags & INPUT_PUSH_FILE);
- out:
+ status = 0;
+ if (! backgnd)
+ status = waitforjob(jp);
+ exitstatus = status;
INT_ON;
- return fd;
}
/*
- * Like setinputfile, but takes input from a string.
+ * Compute the names of the files in a redirection list.
*/
+static void fixredir(union node *, const char *, int);
static void
-setinputstring(char *string)
+expredir(union node *n)
{
- INT_OFF;
- pushfile();
- parsenextc = string;
- parsenleft = strlen(string);
- parsefile->buf = NULL;
- plinno = 1;
- INT_ON;
-}
+ union node *redir;
+ for (redir = n; redir; redir = redir->nfile.next) {
+ struct arglist fn;
-/* ============ jobs.c */
+ memset(&fn, 0, sizeof(fn));
+ fn.lastp = &fn.list;
+ switch (redir->type) {
+ case NFROMTO:
+ case NFROM:
+ case NTO:
+ case NCLOBBER:
+ case NAPPEND:
+ expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
+ redir->nfile.expfname = fn.list->text;
+ break;
+ case NFROMFD:
+ case NTOFD:
+ if (redir->ndup.vname) {
+ expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
+ if (fn.list == NULL)
+ ash_msg_and_raise_error("redir error");
+ fixredir(redir, fn.list->text, 1);
+ }
+ break;
+ }
+ }
+}
/*
- * Set the signal handler for the specified signal. The routine figures
- * out what it should be set to.
+ * Evaluate a pipeline. All the processes in the pipeline are children
+ * of the process creating the pipeline. (This differs from some versions
+ * of the shell, which make the last process in a pipeline the parent
+ * of all the rest.)
*/
static void
-setsignal(int signo)
+evalpipe(union node *n, int flags)
{
- int action;
- char *t, tsig;
- struct sigaction act;
+ struct job *jp;
+ struct nodelist *lp;
+ int pipelen;
+ int prevfd;
+ int pip[2];
- t = trap[signo];
- if (t == NULL)
- action = S_DFL;
- else if (*t != '\0')
- action = S_CATCH;
- else
- action = S_IGN;
- if (rootshell && action == S_DFL) {
- switch (signo) {
- case SIGINT:
- if (iflag || minusc || sflag == 0)
- action = S_CATCH;
- break;
- case SIGQUIT:
-#if DEBUG
- if (debug)
- break;
-#endif
- /* FALLTHROUGH */
- case SIGTERM:
- if (iflag)
- action = S_IGN;
- break;
-#if JOBS
- case SIGTSTP:
- case SIGTTOU:
- if (mflag)
- action = S_IGN;
- break;
-#endif
+ TRACE(("evalpipe(0x%lx) called\n", (long)n));
+ pipelen = 0;
+ for (lp = n->npipe.cmdlist; lp; lp = lp->next)
+ pipelen++;
+ flags |= EV_EXIT;
+ INT_OFF;
+ jp = makejob(n, pipelen);
+ prevfd = -1;
+ for (lp = n->npipe.cmdlist; lp; lp = lp->next) {
+ prehash(lp->n);
+ pip[1] = -1;
+ if (lp->next) {
+ if (pipe(pip) < 0) {
+ close(prevfd);
+ ash_msg_and_raise_error("Pipe call failed");
+ }
+ }
+ if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
+ INT_ON;
+ if (pip[1] >= 0) {
+ close(pip[0]);
+ }
+ if (prevfd > 0) {
+ dup2(prevfd, 0);
+ close(prevfd);
+ }
+ if (pip[1] > 1) {
+ dup2(pip[1], 1);
+ close(pip[1]);
+ }
+ evaltreenr(lp->n, flags);
+ /* never returns */
}
+ if (prevfd >= 0)
+ close(prevfd);
+ prevfd = pip[0];
+ close(pip[1]);
+ }
+ if (n->npipe.backgnd == 0) {
+ exitstatus = waitforjob(jp);
+ TRACE(("evalpipe: job done exit status %d\n", exitstatus));
}
+ INT_ON;
+}
- t = &sigmode[signo - 1];
- tsig = *t;
- if (tsig == 0) {
- /*
- * current setting unknown
- */
- if (sigaction(signo, 0, &act) == -1) {
- /*
- * Pretend it worked; maybe we should give a warning
- * here, but other shells don't. We don't alter
- * sigmode, so that we retry every time.
- */
- return;
- }
- if (act.sa_handler == SIG_IGN) {
- if (mflag
- && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)
- ) {
- tsig = S_IGN; /* don't hard ignore these */
- } else
- tsig = S_HARD_IGN;
+static struct localvar *localvars;
+
+/*
+ * Called after a function returns.
+ * Interrupts must be off.
+ */
+static void
+poplocalvars(void)
+{
+ struct localvar *lvp;
+ struct var *vp;
+
+ while ((lvp = localvars) != NULL) {
+ localvars = lvp->next;
+ vp = lvp->vp;
+ TRACE(("poplocalvar %s", vp ? vp->text : "-"));
+ if (vp == NULL) { /* $- saved */
+ memcpy(optlist, lvp->text, sizeof(optlist));
+ free((char*)lvp->text);
+ optschanged();
+ } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) {
+ unsetvar(vp->text);
} else {
- tsig = S_RESET; /* force to be set */
+ if (vp->func)
+ (*vp->func)(strchrnul(lvp->text, '=') + 1);
+ if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0)
+ free((char*)vp->text);
+ vp->flags = lvp->flags;
+ vp->text = lvp->text;
}
+ free(lvp);
}
- if (tsig == S_HARD_IGN || tsig == action)
- return;
- switch (action) {
- case S_CATCH:
- act.sa_handler = onsig;
- break;
- case S_IGN:
- act.sa_handler = SIG_IGN;
- break;
- default:
- act.sa_handler = SIG_DFL;
- }
- *t = action;
- act.sa_flags = 0;
- sigfillset(&act.sa_mask);
- sigaction(signo, &act, 0);
}
-/* mode flags for set_curjob */
-#define CUR_DELETE 2
-#define CUR_RUNNING 1
-#define CUR_STOPPED 0
-
-/* mode flags for dowait */
-#define DOWAIT_NORMAL 0
-#define DOWAIT_BLOCK 1
+static int
+evalfun(struct funcnode *func, int argc, char **argv, int flags)
+{
+ volatile struct shparam saveparam;
+ struct localvar *volatile savelocalvars;
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int e;
-#if JOBS
-/* pgrp of shell on invocation */
-static int initialpgrp;
-static int ttyfd = -1;
+ saveparam = shellparam;
+ savelocalvars = localvars;
+ e = setjmp(jmploc.loc);
+ if (e) {
+ goto funcdone;
+ }
+ INT_OFF;
+ savehandler = exception_handler;
+ exception_handler = &jmploc;
+ localvars = NULL;
+ shellparam.malloc = 0;
+ func->count++;
+ funcnest++;
+ INT_ON;
+ shellparam.nparam = argc - 1;
+ shellparam.p = argv + 1;
+#if ENABLE_ASH_GETOPTS
+ shellparam.optind = 1;
+ shellparam.optoff = -1;
#endif
-/* array of jobs */
-static struct job *jobtab;
-/* size of array */
-static unsigned njobs;
-/* current job */
-static struct job *curjob;
-/* number of presumed living untracked jobs */
-static int jobless;
+ evaltree(&func->n, flags & EV_TESTED);
+funcdone:
+ INT_OFF;
+ funcnest--;
+ freefunc(func);
+ poplocalvars();
+ localvars = savelocalvars;
+ freeparam(&shellparam);
+ shellparam = saveparam;
+ exception_handler = savehandler;
+ INT_ON;
+ evalskip &= ~SKIPFUNC;
+ return e;
+}
-static void
-set_curjob(struct job *jp, unsigned mode)
+#if ENABLE_ASH_CMDCMD
+static char **
+parse_command_args(char **argv, const char **path)
{
- struct job *jp1;
- struct job **jpp, **curp;
+ char *cp, c;
- /* first remove from list */
- jpp = curp = &curjob;
- do {
- jp1 = *jpp;
- if (jp1 == jp)
+ for (;;) {
+ cp = *++argv;
+ if (!cp)
+ return 0;
+ if (*cp++ != '-')
break;
- jpp = &jp1->prev_job;
- } while (1);
- *jpp = jp1->prev_job;
-
- /* Then re-insert in correct position */
- jpp = curp;
- switch (mode) {
- default:
-#if DEBUG
- abort();
-#endif
- case CUR_DELETE:
- /* job being deleted */
- break;
- case CUR_RUNNING:
- /* newly created job or backgrounded job,
- put after all stopped jobs. */
+ c = *cp++;
+ if (!c)
+ break;
+ if (c == '-' && !*cp) {
+ argv++;
+ break;
+ }
do {
- jp1 = *jpp;
-#if JOBS
- if (!jp1 || jp1->state != JOBSTOPPED)
-#endif
+ switch (c) {
+ case 'p':
+ *path = defpath;
break;
- jpp = &jp1->prev_job;
- } while (1);
- /* FALLTHROUGH */
-#if JOBS
- case CUR_STOPPED:
-#endif
- /* newly stopped job - becomes curjob */
- jp->prev_job = *jpp;
- *jpp = jp;
- break;
+ default:
+ /* run 'typecmd' for other options */
+ return 0;
+ }
+ c = *cp++;
+ } while (c);
}
-}
-
-#if JOBS || DEBUG
-static int
-jobno(const struct job *jp)
-{
- return jp - jobtab + 1;
+ return argv;
}
#endif
/*
- * Convert a job name to a job structure.
+ * Make a variable a local variable. When a variable is made local, it's
+ * value and flags are saved in a localvar structure. The saved values
+ * will be restored when the shell function returns. We handle the name
+ * "-" as a special case.
*/
-static struct job *
-getjob(const char *name, int getctl)
+static void
+mklocal(char *name)
{
- struct job *jp;
- struct job *found;
- const char *err_msg = "No such job: %s";
- unsigned num;
- int c;
- const char *p;
- char *(*match)(const char *, const char *);
-
- jp = curjob;
- p = name;
- if (!p)
- goto currentjob;
-
- if (*p != '%')
- goto err;
-
- c = *++p;
- if (!c)
- goto currentjob;
-
- if (!p[1]) {
- if (c == '+' || c == '%') {
- currentjob:
- err_msg = "No current job";
- goto check;
- }
- if (c == '-') {
- if (jp)
- jp = jp->prev_job;
- err_msg = "No previous job";
- check:
- if (!jp)
- goto err;
- goto gotit;
- }
- }
-
- if (is_number(p)) {
- num = atoi(p);
- if (num < njobs) {
- jp = jobtab + num - 1;
- if (jp->used)
- goto gotit;
- goto err;
- }
- }
+ struct localvar *lvp;
+ struct var **vpp;
+ struct var *vp;
- match = prefix;
- if (*p == '?') {
- match = strstr;
- p++;
- }
+ INT_OFF;
+ lvp = ckmalloc(sizeof(struct localvar));
+ if (LONE_DASH(name)) {
+ char *p;
+ p = ckmalloc(sizeof(optlist));
+ lvp->text = memcpy(p, optlist, sizeof(optlist));
+ vp = NULL;
+ } else {
+ char *eq;
- found = 0;
- while (1) {
- if (!jp)
- goto err;
- if (match(jp->ps[0].cmd, p)) {
- if (found)
- goto err;
- found = jp;
- err_msg = "%s: ambiguous";
+ vpp = hashvar(name);
+ vp = *findvar(vpp, name);
+ eq = strchr(name, '=');
+ if (vp == NULL) {
+ if (eq)
+ setvareq(name, VSTRFIXED);
+ else
+ setvar(name, NULL, VSTRFIXED);
+ vp = *vpp; /* the new variable */
+ lvp->flags = VUNSET;
+ } else {
+ lvp->text = vp->text;
+ lvp->flags = vp->flags;
+ vp->flags |= VSTRFIXED|VTEXTFIXED;
+ if (eq)
+ setvareq(name, 0);
}
- jp = jp->prev_job;
}
-
- gotit:
-#if JOBS
- err_msg = "job %s not created under job control";
- if (getctl && jp->jobctl == 0)
- goto err;
-#endif
- return jp;
- err:
- ash_msg_and_raise_error(err_msg, name);
+ lvp->vp = vp;
+ lvp->next = localvars;
+ localvars = lvp;
+ INT_ON;
}
/*
- * Mark a job structure as unused.
+ * The "local" command.
*/
-static void
-freejob(struct job *jp)
+static int
+localcmd(int argc, char **argv)
{
- struct procstat *ps;
- int i;
+ char *name;
- INT_OFF;
- for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) {
- if (ps->cmd != nullstr)
- free(ps->cmd);
+ argv = argptr;
+ while ((name = *argv++) != NULL) {
+ mklocal(name);
}
- if (jp->ps != &jp->ps0)
- free(jp->ps);
- jp->used = 0;
- set_curjob(jp, CUR_DELETE);
- INT_ON;
+ return 0;
}
+/* Forward declarations for builtintab[] */
#if JOBS
-static void
-xtcsetpgrp(int fd, pid_t pgrp)
+static int fg_bgcmd(int, char **);
+#endif
+static int breakcmd(int, char **);
+#if ENABLE_ASH_CMDCMD
+static int commandcmd(int, char **);
+#endif
+static int dotcmd(int, char **);
+static int evalcmd(int, char **);
+#if ENABLE_ASH_BUILTIN_ECHO
+static int echocmd(int, char **);
+#endif
+#if ENABLE_ASH_BUILTIN_TEST
+static int testcmd(int, char **);
+#endif
+static int execcmd(int, char **);
+static int exitcmd(int, char **);
+static int exportcmd(int, char **);
+static int falsecmd(int, char **);
+#if ENABLE_ASH_GETOPTS
+static int getoptscmd(int, char **);
+#endif
+static int hashcmd(int, char **);
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+static int helpcmd(int argc, char **argv);
+#endif
+#if JOBS
+static int jobscmd(int, char **);
+#endif
+#if ENABLE_ASH_MATH_SUPPORT
+static int letcmd(int, char **);
+#endif
+static int pwdcmd(int, char **);
+static int readcmd(int, char **);
+static int returncmd(int, char **);
+static int setcmd(int, char **);
+static int shiftcmd(int, char **);
+static int timescmd(int, char **);
+static int trapcmd(int, char **);
+static int truecmd(int, char **);
+static int typecmd(int, char **);
+static int umaskcmd(int, char **);
+static int unsetcmd(int, char **);
+static int waitcmd(int, char **);
+static int ulimitcmd(int, char **);
+#if JOBS
+static int killcmd(int, char **);
+#endif
+
+#define BUILTIN_NOSPEC "0"
+#define BUILTIN_SPECIAL "1"
+#define BUILTIN_REGULAR "2"
+#define BUILTIN_SPEC_REG "3"
+#define BUILTIN_ASSIGN "4"
+#define BUILTIN_SPEC_ASSG "5"
+#define BUILTIN_REG_ASSG "6"
+#define BUILTIN_SPEC_REG_ASSG "7"
+
+/* make sure to keep these in proper order since it is searched via bsearch() */
+static const struct builtincmd builtintab[] = {
+ { BUILTIN_SPEC_REG ".", dotcmd },
+ { BUILTIN_SPEC_REG ":", truecmd },
+#if ENABLE_ASH_BUILTIN_TEST
+ { BUILTIN_REGULAR "[", testcmd },
+ { BUILTIN_REGULAR "[[", testcmd },
+#endif
+#if ENABLE_ASH_ALIAS
+ { BUILTIN_REG_ASSG "alias", aliascmd },
+#endif
+#if JOBS
+ { BUILTIN_REGULAR "bg", fg_bgcmd },
+#endif
+ { BUILTIN_SPEC_REG "break", breakcmd },
+ { BUILTIN_REGULAR "cd", cdcmd },
+ { BUILTIN_NOSPEC "chdir", cdcmd },
+#if ENABLE_ASH_CMDCMD
+ { BUILTIN_REGULAR "command", commandcmd },
+#endif
+ { BUILTIN_SPEC_REG "continue", breakcmd },
+#if ENABLE_ASH_BUILTIN_ECHO
+ { BUILTIN_REGULAR "echo", echocmd },
+#endif
+ { BUILTIN_SPEC_REG "eval", evalcmd },
+ { BUILTIN_SPEC_REG "exec", execcmd },
+ { BUILTIN_SPEC_REG "exit", exitcmd },
+ { BUILTIN_SPEC_REG_ASSG "export", exportcmd },
+ { BUILTIN_REGULAR "false", falsecmd },
+#if JOBS
+ { BUILTIN_REGULAR "fg", fg_bgcmd },
+#endif
+#if ENABLE_ASH_GETOPTS
+ { BUILTIN_REGULAR "getopts", getoptscmd },
+#endif
+ { BUILTIN_NOSPEC "hash", hashcmd },
+#if !ENABLE_FEATURE_SH_EXTRA_QUIET
+ { BUILTIN_NOSPEC "help", helpcmd },
+#endif
+#if JOBS
+ { BUILTIN_REGULAR "jobs", jobscmd },
+ { BUILTIN_REGULAR "kill", killcmd },
+#endif
+#if ENABLE_ASH_MATH_SUPPORT
+ { BUILTIN_NOSPEC "let", letcmd },
+#endif
+ { BUILTIN_ASSIGN "local", localcmd },
+ { BUILTIN_NOSPEC "pwd", pwdcmd },
+ { BUILTIN_REGULAR "read", readcmd },
+ { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd },
+ { BUILTIN_SPEC_REG "return", returncmd },
+ { BUILTIN_SPEC_REG "set", setcmd },
+ { BUILTIN_SPEC_REG "shift", shiftcmd },
+ { BUILTIN_SPEC_REG "source", dotcmd },
+#if ENABLE_ASH_BUILTIN_TEST
+ { BUILTIN_REGULAR "test", testcmd },
+#endif
+ { BUILTIN_SPEC_REG "times", timescmd },
+ { BUILTIN_SPEC_REG "trap", trapcmd },
+ { BUILTIN_REGULAR "true", truecmd },
+ { BUILTIN_NOSPEC "type", typecmd },
+ { BUILTIN_NOSPEC "ulimit", ulimitcmd },
+ { BUILTIN_REGULAR "umask", umaskcmd },
+#if ENABLE_ASH_ALIAS
+ { BUILTIN_REGULAR "unalias", unaliascmd },
+#endif
+ { BUILTIN_SPEC_REG "unset", unsetcmd },
+ { BUILTIN_REGULAR "wait", waitcmd },
+};
+
+#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0]))
+
+#define COMMANDCMD (builtintab + 5 + \
+ 2 * ENABLE_ASH_BUILTIN_TEST + \
+ ENABLE_ASH_ALIAS + \
+ ENABLE_ASH_JOB_CONTROL)
+#define EXECCMD (builtintab + 7 + \
+ 2 * ENABLE_ASH_BUILTIN_TEST + \
+ ENABLE_ASH_ALIAS + \
+ ENABLE_ASH_JOB_CONTROL + \
+ ENABLE_ASH_CMDCMD + \
+ ENABLE_ASH_BUILTIN_ECHO)
+
+/*
+ * Search the table of builtin commands.
+ */
+static struct builtincmd *
+find_builtin(const char *name)
{
- if (tcsetpgrp(fd, pgrp))
- ash_msg_and_raise_error("Cannot set tty process group (%m)");
+ struct builtincmd *bp;
+
+ bp = bsearch(
+ name, builtintab, NUMBUILTINS, sizeof(builtintab[0]),
+ pstrcmp
+ );
+ return bp;
}
/*
- * Turn job control on and off.
- *
- * Note: This code assumes that the third arg to ioctl is a character
- * pointer, which is true on Berkeley systems but not System V. Since
- * System V doesn't have job control yet, this isn't a problem now.
- *
- * Called with interrupts off.
+ * Resolve a command name. If you change this routine, you may have to
+ * change the shellexec routine as well.
*/
static void
-setjobctl(int on)
+find_command(char *name, struct cmdentry *entry, int act, const char *path)
{
- int fd;
- int pgrp;
-
- if (on == jobctl || rootshell == 0)
- return;
- if (on) {
- int ofd;
- ofd = fd = open(_PATH_TTY, O_RDWR);
- if (fd < 0) {
- /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails.
- * That sometimes helps to acquire controlling tty.
- * Obviously, a workaround for bugs when someone
- * failed to provide a controlling tty to bash! :) */
- fd += 3;
- while (!isatty(fd) && --fd >= 0)
- ;
- }
- fd = fcntl(fd, F_DUPFD, 10);
- close(ofd);
- if (fd < 0)
- goto out;
- fcntl(fd, F_SETFD, FD_CLOEXEC);
- do { /* while we are in the background */
- pgrp = tcgetpgrp(fd);
- if (pgrp < 0) {
- out:
- ash_msg("can't access tty; job control turned off");
- mflag = on = 0;
- goto close;
+ struct tblentry *cmdp;
+ int idx;
+ int prev;
+ char *fullname;
+ struct stat statb;
+ int e;
+ int updatetbl;
+ struct builtincmd *bcmd;
+
+ /* If name contains a slash, don't use PATH or hash table */
+ if (strchr(name, '/') != NULL) {
+ entry->u.index = -1;
+ if (act & DO_ABS) {
+ while (stat(name, &statb) < 0) {
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ entry->cmdtype = CMDUNKNOWN;
+ return;
}
- if (pgrp == getpgrp())
- break;
- killpg(0, SIGTTIN);
- } while (1);
- initialpgrp = pgrp;
+ }
+ entry->cmdtype = CMDNORMAL;
+ return;
+ }
- setsignal(SIGTSTP);
- setsignal(SIGTTOU);
- setsignal(SIGTTIN);
- pgrp = rootpid;
- setpgid(0, pgrp);
- xtcsetpgrp(fd, pgrp);
- } else {
- /* turning job control off */
- fd = ttyfd;
- pgrp = initialpgrp;
- xtcsetpgrp(fd, pgrp);
- setpgid(0, pgrp);
- setsignal(SIGTSTP);
- setsignal(SIGTTOU);
- setsignal(SIGTTIN);
- close:
- close(fd);
- fd = -1;
+#if ENABLE_FEATURE_SH_STANDALONE_SHELL
+ if (find_applet_by_name(name)) {
+ entry->cmdtype = CMDNORMAL;
+ entry->u.index = -1;
+ return;
}
- ttyfd = fd;
- jobctl = on;
-}
+#endif
-static int
-killcmd(int argc, char **argv)
-{
- int signo = -1;
- int list = 0;
- int i;
- pid_t pid;
- struct job *jp;
+ if (is_safe_applet(name)) {
+ entry->cmdtype = CMDNORMAL;
+ entry->u.index = -1;
+ return;
+ }
- if (argc <= 1) {
- usage:
- ash_msg_and_raise_error(
-"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
-"kill -l [exitstatus]"
- );
+ updatetbl = (path == pathval());
+ if (!updatetbl) {
+ act |= DO_ALTPATH;
+ if (strstr(path, "%builtin") != NULL)
+ act |= DO_ALTBLTIN;
}
- if (**++argv == '-') {
- signo = get_signum(*argv + 1);
- if (signo < 0) {
- int c;
+ /* If name is in the table, check answer will be ok */
+ cmdp = cmdlookup(name, 0);
+ if (cmdp != NULL) {
+ int bit;
- while ((c = nextopt("ls:")) != '\0') {
- switch (c) {
- default:
+ switch (cmdp->cmdtype) {
+ default:
#if DEBUG
- abort();
+ abort();
#endif
- case 'l':
- list = 1;
- break;
- case 's':
- signo = get_signum(optionarg);
- if (signo < 0) {
- ash_msg_and_raise_error(
- "invalid signal number or name: %s",
- optionarg
- );
- }
- break;
- }
- }
- argv = argptr;
- } else
- argv++;
+ case CMDNORMAL:
+ bit = DO_ALTPATH;
+ break;
+ case CMDFUNCTION:
+ bit = DO_NOFUNC;
+ break;
+ case CMDBUILTIN:
+ bit = DO_ALTBLTIN;
+ break;
+ }
+ if (act & bit) {
+ updatetbl = 0;
+ cmdp = NULL;
+ } else if (cmdp->rehash == 0)
+ /* if not invalidated by cd, we're done */
+ goto success;
}
- if (!list && signo < 0)
- signo = SIGTERM;
+ /* If %builtin not in path, check for builtin next */
+ bcmd = find_builtin(name);
+ if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || (
+ act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0
+ )))
+ goto builtin_success;
- if ((signo < 0 || !*argv) ^ list) {
- goto usage;
+ /* We have to search path. */
+ prev = -1; /* where to start */
+ if (cmdp && cmdp->rehash) { /* doing a rehash */
+ if (cmdp->cmdtype == CMDBUILTIN)
+ prev = builtinloc;
+ else
+ prev = cmdp->param.index;
}
- if (list) {
- const char *name;
-
- if (!*argv) {
- for (i = 1; i < NSIG; i++) {
- name = get_signame(i);
- if (isdigit(*name))
- out1fmt(snlfmt, name);
+ e = ENOENT;
+ idx = -1;
+ loop:
+ while ((fullname = padvance(&path, name)) != NULL) {
+ stunalloc(fullname);
+ idx++;
+ if (pathopt) {
+ if (prefix(pathopt, "builtin")) {
+ if (bcmd)
+ goto builtin_success;
+ continue;
+ } else if (!(act & DO_NOFUNC) &&
+ prefix(pathopt, "func")) {
+ /* handled below */
+ } else {
+ /* ignore unimplemented options */
+ continue;
}
- return 0;
}
- name = get_signame(signo);
- if (!isdigit(*name))
- ash_msg_and_raise_error("invalid signal number or exit status: %s", *argptr);
- out1fmt(snlfmt, name);
- return 0;
- }
-
- i = 0;
- do {
- if (**argv == '%') {
- jp = getjob(*argv, 0);
- pid = -jp->ps[0].pid;
- } else {
- pid = **argv == '-' ?
- -number(*argv + 1) : number(*argv);
+ /* if rehash, don't redo absolute path names */
+ if (fullname[0] == '/' && idx <= prev) {
+ if (idx < prev)
+ continue;
+ TRACE(("searchexec \"%s\": no change\n", name));
+ goto success;
}
- if (kill(pid, signo) != 0) {
- ash_msg("(%d) - %m", pid);
- i = 1;
+ while (stat(fullname, &statb) < 0) {
+#ifdef SYSV
+ if (errno == EINTR)
+ continue;
+#endif
+ if (errno != ENOENT && errno != ENOTDIR)
+ e = errno;
+ goto loop;
}
- } while (*++argv);
-
- return i;
-}
+ e = EACCES; /* if we fail, this will be the error */
+ if (!S_ISREG(statb.st_mode))
+ continue;
+ if (pathopt) { /* this is a %func directory */
+ stalloc(strlen(fullname) + 1);
+ readcmdfile(fullname);
+ cmdp = cmdlookup(name, 0);
+ if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION)
+ ash_msg_and_raise_error("%s not defined in %s", name, fullname);
+ stunalloc(fullname);
+ goto success;
+ }
+ TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
+ if (!updatetbl) {
+ entry->cmdtype = CMDNORMAL;
+ entry->u.index = idx;
+ return;
+ }
+ INT_OFF;
+ cmdp = cmdlookup(name, 1);
+ cmdp->cmdtype = CMDNORMAL;
+ cmdp->param.index = idx;
+ INT_ON;
+ goto success;
+ }
-static void
-showpipe(struct job *jp, FILE *out)
-{
- struct procstat *sp;
- struct procstat *spend;
+ /* We failed. If there was an entry for this command, delete it */
+ if (cmdp && updatetbl)
+ delete_cmd_entry();
+ if (act & DO_ERR)
+ ash_msg("%s: %s", name, errmsg(e, "not found"));
+ entry->cmdtype = CMDUNKNOWN;
+ return;
- spend = jp->ps + jp->nprocs;
- for (sp = jp->ps + 1; sp < spend; sp++)
- fprintf(out, " | %s", sp->cmd);
- outcslow('\n', out);
- flush_stdout_stderr();
+ builtin_success:
+ if (!updatetbl) {
+ entry->cmdtype = CMDBUILTIN;
+ entry->u.cmd = bcmd;
+ return;
+ }
+ INT_OFF;
+ cmdp = cmdlookup(name, 1);
+ cmdp->cmdtype = CMDBUILTIN;
+ cmdp->param.cmd = bcmd;
+ INT_ON;
+ success:
+ cmdp->rehash = 0;
+ entry->cmdtype = cmdp->cmdtype;
+ entry->u = cmdp->param;
}
-
+/*
+ * Execute a simple command.
+ */
+static int back_exitstatus; /* exit status of backquoted command */
static int
-restartjob(struct job *jp, int mode)
+isassignment(const char *p)
{
- struct procstat *ps;
- int i;
- int status;
- pid_t pgid;
-
- INT_OFF;
- if (jp->state == JOBDONE)
- goto out;
- jp->state = JOBRUNNING;
- pgid = jp->ps->pid;
- if (mode == FORK_FG)
- xtcsetpgrp(ttyfd, pgid);
- killpg(pgid, SIGCONT);
- ps = jp->ps;
- i = jp->nprocs;
- do {
- if (WIFSTOPPED(ps->status)) {
- ps->status = -1;
- }
- } while (ps++, --i);
- out:
- status = (mode == FORK_FG) ? waitforjob(jp) : 0;
- INT_ON;
- return status;
+ const char *q = endofname(p);
+ if (p == q)
+ return 0;
+ return *q == '=';
}
-
static int
-fg_bgcmd(int argc, char **argv)
+bltincmd(int argc, char **argv)
+{
+ /* Preserve exitstatus of a previous possible redirection
+ * as POSIX mandates */
+ return back_exitstatus;
+}
+static void
+evalcommand(union node *cmd, int flags)
{
+ static const struct builtincmd bltin = {
+ "\0\0", bltincmd
+ };
+ struct stackmark smark;
+ union node *argp;
+ struct arglist arglist;
+ struct arglist varlist;
+ char **argv;
+ int argc;
+ const struct strlist *sp;
+ struct cmdentry cmdentry;
struct job *jp;
- FILE *out;
- int mode;
- int retval;
+ char *lastarg;
+ const char *path;
+ int spclbltin;
+ int cmd_is_exec;
+ int status;
+ char **nargv;
+ struct builtincmd *bcmd;
+ int pseudovarflag = 0;
- mode = (**argv == 'f') ? FORK_FG : FORK_BG;
- nextopt(nullstr);
- argv = argptr;
- out = stdout;
- do {
- jp = getjob(*argv, 1);
- if (mode == FORK_BG) {
- set_curjob(jp, CUR_RUNNING);
- fprintf(out, "[%d] ", jobno(jp));
- }
- outstr(jp->ps->cmd, out);
- showpipe(jp, out);
- retval = restartjob(jp, mode);
- } while (*argv && *++argv);
- return retval;
-}
-#endif
+ /* First expand the arguments. */
+ TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
+ setstackmark(&smark);
+ back_exitstatus = 0;
-static int
-sprint_status(char *s, int status, int sigonly)
-{
- int col;
- int st;
+ cmdentry.cmdtype = CMDBUILTIN;
+ cmdentry.u.cmd = &bltin;
+ varlist.lastp = &varlist.list;
+ *varlist.lastp = NULL;
+ arglist.lastp = &arglist.list;
+ *arglist.lastp = NULL;
- col = 0;
- if (!WIFEXITED(status)) {
-#if JOBS
- if (WIFSTOPPED(status))
- st = WSTOPSIG(status);
- else
-#endif
- st = WTERMSIG(status);
- if (sigonly) {
- if (st == SIGINT || st == SIGPIPE)
- goto out;
-#if JOBS
- if (WIFSTOPPED(status))
- goto out;
-#endif
- }
- st &= 0x7f;
- col = fmtstr(s, 32, strsignal(st));
- if (WCOREDUMP(status)) {
- col += fmtstr(s + col, 16, " (core dumped)");
- }
- } else if (!sigonly) {
- st = WEXITSTATUS(status);
- if (st)
- col = fmtstr(s, 16, "Done(%d)", st);
+ argc = 0;
+ if (cmd->ncmd.args) {
+ bcmd = find_builtin(cmd->ncmd.args->narg.text);
+ pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd);
+ }
+
+ for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) {
+ struct strlist **spp;
+
+ spp = arglist.lastp;
+ if (pseudovarflag && isassignment(argp->narg.text))
+ expandarg(argp, &arglist, EXP_VARTILDE);
else
- col = fmtstr(s, 16, "Done");
+ expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
+
+ for (sp = *spp; sp; sp = sp->next)
+ argc++;
}
- out:
- return col;
-}
-/*
- * Do a wait system call. If job control is compiled in, we accept
- * stopped processes. If block is zero, we return a value of zero
- * rather than blocking.
- *
- * System V doesn't have a non-blocking wait system call. It does
- * have a SIGCLD signal that is sent to a process when one of it's
- * children dies. The obvious way to use SIGCLD would be to install
- * a handler for SIGCLD which simply bumped a counter when a SIGCLD
- * was received, and have waitproc bump another counter when it got
- * the status of a process. Waitproc would then know that a wait
- * system call would not block if the two counters were different.
- * This approach doesn't work because if a process has children that
- * have not been waited for, System V will send it a SIGCLD when it
- * installs a signal handler for SIGCLD. What this means is that when
- * a child exits, the shell will be sent SIGCLD signals continuously
- * until is runs out of stack space, unless it does a wait call before
- * restoring the signal handler. The code below takes advantage of
- * this (mis)feature by installing a signal handler for SIGCLD and
- * then checking to see whether it was called. If there are any
- * children to be waited for, it will be.
- *
- * If neither SYSV nor BSD is defined, we don't implement nonblocking
- * waits at all. In this case, the user will not be informed when
- * a background process until the next time she runs a real program
- * (as opposed to running a builtin command or just typing return),
- * and the jobs command may give out of date information.
- */
-static int
-waitproc(int block, int *status)
-{
- int flags = 0;
+ argv = nargv = stalloc(sizeof(char *) * (argc + 1));
+ for (sp = arglist.list; sp; sp = sp->next) {
+ TRACE(("evalcommand arg: %s\n", sp->text));
+ *nargv++ = sp->text;
+ }
+ *nargv = NULL;
-#if JOBS
- if (jobctl)
- flags |= WUNTRACED;
-#endif
- if (block == 0)
- flags |= WNOHANG;
- return wait3(status, flags, (struct rusage *)NULL);
-}
+ lastarg = NULL;
+ if (iflag && funcnest == 0 && argc > 0)
+ lastarg = nargv[-1];
-/*
- * Wait for a process to terminate.
- */
-static int
-dowait(int block, struct job *job)
-{
- int pid;
- int status;
- struct job *jp;
- struct job *thisjob;
- int state;
+ preverrout_fd = 2;
+ expredir(cmd->ncmd.redirect);
+ status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2);
- TRACE(("dowait(%d) called\n", block));
- pid = waitproc(block, &status);
- TRACE(("wait returns pid %d, status=%d\n", pid, status));
- if (pid <= 0)
- return pid;
- INT_OFF;
- thisjob = NULL;
- for (jp = curjob; jp; jp = jp->prev_job) {
- struct procstat *sp;
- struct procstat *spend;
- if (jp->state == JOBDONE)
- continue;
- state = JOBDONE;
- spend = jp->ps + jp->nprocs;
- sp = jp->ps;
- do {
- if (sp->pid == pid) {
- TRACE(("Job %d: changing status of proc %d "
- "from 0x%x to 0x%x\n",
- jobno(jp), pid, sp->status, status));
- sp->status = status;
- thisjob = jp;
- }
- if (sp->status == -1)
- state = JOBRUNNING;
-#if JOBS
- if (state == JOBRUNNING)
- continue;
- if (WIFSTOPPED(sp->status)) {
- jp->stopstatus = sp->status;
- state = JOBSTOPPED;
+ path = vpath.text;
+ for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
+ struct strlist **spp;
+ char *p;
+
+ spp = varlist.lastp;
+ expandarg(argp, &varlist, EXP_VARTILDE);
+
+ /*
+ * Modify the command lookup path, if a PATH= assignment
+ * is present
+ */
+ p = (*spp)->text;
+ if (varequal(p, path))
+ path = p;
+ }
+
+ /* Print the command if xflag is set. */
+ if (xflag) {
+ int n;
+ const char *p = " %s";
+
+ p++;
+ dprintf(preverrout_fd, p, expandstr(ps4val()));
+
+ sp = varlist.list;
+ for (n = 0; n < 2; n++) {
+ while (sp) {
+ dprintf(preverrout_fd, p, sp->text);
+ sp = sp->next;
+ if (*p == '%') {
+ p--;
+ }
}
-#endif
- } while (++sp < spend);
- if (thisjob)
- goto gotjob;
+ sp = arglist.list;
+ }
+ full_write(preverrout_fd, "\n", 1);
}
-#if JOBS
- if (!WIFSTOPPED(status))
-#endif
- jobless--;
- goto out;
+ cmd_is_exec = 0;
+ spclbltin = -1;
- gotjob:
- if (state != JOBRUNNING) {
- thisjob->changed = 1;
+ /* Now locate the command. */
+ if (argc) {
+ const char *oldpath;
+ int cmd_flag = DO_ERR;
- if (thisjob->state != state) {
- TRACE(("Job %d: changing state from %d to %d\n",
- jobno(thisjob), thisjob->state, state));
- thisjob->state = state;
-#if JOBS
- if (state == JOBSTOPPED) {
- set_curjob(thisjob, CUR_STOPPED);
+ path += 5;
+ oldpath = path;
+ for (;;) {
+ find_command(argv[0], &cmdentry, cmd_flag, path);
+ if (cmdentry.cmdtype == CMDUNKNOWN) {
+ status = 127;
+ flush_stderr();
+ goto bail;
}
+
+ /* implement bltin and command here */
+ if (cmdentry.cmdtype != CMDBUILTIN)
+ break;
+ if (spclbltin < 0)
+ spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd);
+ if (cmdentry.u.cmd == EXECCMD)
+ cmd_is_exec++;
+#if ENABLE_ASH_CMDCMD
+ if (cmdentry.u.cmd == COMMANDCMD) {
+ path = oldpath;
+ nargv = parse_command_args(argv, &path);
+ if (!nargv)
+ break;
+ argc -= nargv - argv;
+ argv = nargv;
+ cmd_flag |= DO_NOFUNC;
+ } else
#endif
+ break;
}
}
- out:
- INT_ON;
+ if (status) {
+ /* We have a redirection error. */
+ if (spclbltin > 0)
+ raise_exception(EXERROR);
+ bail:
+ exitstatus = status;
+ goto out;
+ }
- if (thisjob && thisjob == job) {
- char s[48 + 1];
- int len;
+ /* Execute the command. */
+ switch (cmdentry.cmdtype) {
+ default:
+ /* Fork off a child process if necessary. */
+ if (!(flags & EV_EXIT) || trap[0]) {
+ INT_OFF;
+ jp = makejob(cmd, 1);
+ if (forkshell(jp, cmd, FORK_FG) != 0) {
+ exitstatus = waitforjob(jp);
+ INT_ON;
+ break;
+ }
+ FORCE_INT_ON;
+ }
+ listsetvar(varlist.list, VEXPORT|VSTACK);
+ shellexec(argv, path, cmdentry.u.index);
+ /* NOTREACHED */
- len = sprint_status(s, status, 1);
- if (len) {
- s[len] = '\n';
- s[len + 1] = 0;
- out2str(s);
+ case CMDBUILTIN:
+ cmdenviron = varlist.list;
+ if (cmdenviron) {
+ struct strlist *list = cmdenviron;
+ int i = VNOSET;
+ if (spclbltin > 0 || argc == 0) {
+ i = 0;
+ if (cmd_is_exec && argc > 1)
+ i = VEXPORT;
+ }
+ listsetvar(list, i);
+ }
+ if (evalbltin(cmdentry.u.cmd, argc, argv)) {
+ int exit_status;
+ int i, j;
+
+ i = exception;
+ if (i == EXEXIT)
+ goto raise;
+
+ exit_status = 2;
+ j = 0;
+ if (i == EXINT)
+ j = SIGINT;
+ if (i == EXSIG)
+ j = pendingsigs;
+ if (j)
+ exit_status = j + 128;
+ exitstatus = exit_status;
+
+ if (i == EXINT || spclbltin > 0) {
+ raise:
+ longjmp(exception_handler->loc, 1);
+ }
+ FORCE_INT_ON;
}
+ break;
+
+ case CMDFUNCTION:
+ listsetvar(varlist.list, 0);
+ if (evalfun(cmdentry.u.func, argc, argv, flags))
+ goto raise;
+ break;
}
- return pid;
+
+ out:
+ popredir(cmd_is_exec);
+ if (lastarg)
+ /* dsl: I think this is intended to be used to support
+ * '_' in 'vi' command mode during line editing...
+ * However I implemented that within libedit itself.
+ */
+ setvar("_", lastarg, 0);
+ popstackmark(&smark);
}
-#if JOBS
-static void
-showjob(FILE *out, struct job *jp, int mode)
+static int
+evalbltin(const struct builtincmd *cmd, int argc, char **argv)
{
- struct procstat *ps;
- struct procstat *psend;
- int col;
- int indent;
- char s[80];
+ char *volatile savecmdname;
+ struct jmploc *volatile savehandler;
+ struct jmploc jmploc;
+ int i;
- ps = jp->ps;
+ savecmdname = commandname;
+ i = setjmp(jmploc.loc);
+ if (i)
+ goto cmddone;
+ savehandler = exception_handler;
+ exception_handler = &jmploc;
+ commandname = argv[0];
+ argptr = argv + 1;
+ optptr = NULL; /* initialize nextopt */
+ exitstatus = (*cmd->builtin)(argc, argv);
+ flush_stdout_stderr();
+ cmddone:
+ exitstatus |= ferror(stdout);
+ clearerr(stdout);
+ commandname = savecmdname;
+ exsig = 0;
+ exception_handler = savehandler;
- if (mode & SHOW_PGID) {
- /* just output process (group) id of pipeline */
- fprintf(out, "%d\n", ps->pid);
- return;
- }
+ return i;
+}
- col = fmtstr(s, 16, "[%d] ", jobno(jp));
- indent = col;
+static int
+goodname(const char *p)
+{
+ return !*endofname(p);
+}
- if (jp == curjob)
- s[col - 2] = '+';
- else if (curjob && jp == curjob->prev_job)
- s[col - 2] = '-';
- if (mode & SHOW_PID)
- col += fmtstr(s + col, 16, "%d ", ps->pid);
+/*
+ * Search for a command. This is called before we fork so that the
+ * location of the command will be available in the parent as well as
+ * the child. The check for "goodname" is an overly conservative
+ * check that the name will not be subject to expansion.
+ */
+static void
+prehash(union node *n)
+{
+ struct cmdentry entry;
- psend = ps + jp->nprocs;
+ if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text))
+ find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
+}
- if (jp->state == JOBRUNNING) {
- strcpy(s + col, "Running");
- col += sizeof("Running") - 1;
- } else {
- int status = psend[-1].status;
- if (jp->state == JOBSTOPPED)
- status = jp->stopstatus;
- col += sprint_status(s + col, status, 0);
- }
- goto start;
+/*
+ * Builtin commands. Builtin commands whose functions are closely
+ * tied to evaluation are implemented here.
+ */
- do {
- /* for each process */
- col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3;
- start:
- fprintf(out, "%s%*c%s",
- s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd
- );
- if (!(mode & SHOW_PID)) {
- showpipe(jp, out);
- break;
- }
- if (++ps == psend) {
- outcslow('\n', out);
- break;
- }
- } while (1);
+/*
+ * Handle break and continue commands. Break, continue, and return are
+ * all handled by setting the evalskip flag. The evaluation routines
+ * above all check this flag, and if it is set they start skipping
+ * commands rather than executing them. The variable skipcount is
+ * the number of loops to break/continue, or the number of function
+ * levels to return. (The latter is always 1.) It should probably
+ * be an error to break out of more loops than exist, but it isn't
+ * in the standard shell so we don't make it one here.
+ */
- jp->changed = 0;
+static int
+breakcmd(int argc, char **argv)
+{
+ int n = argc > 1 ? number(argv[1]) : 1;
- if (jp->state == JOBDONE) {
- TRACE(("showjob: freeing job %d\n", jobno(jp)));
- freejob(jp);
+ if (n <= 0)
+ ash_msg_and_raise_error(illnum, argv[1]);
+ if (n > loopnest)
+ n = loopnest;
+ if (n > 0) {
+ evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK;
+ skipcount = n;
}
+ return 0;
}
+/*
+ * The return command.
+ */
static int
-jobscmd(int argc, char **argv)
+returncmd(int argc, char **argv)
{
- int mode, m;
- FILE *out;
+ /*
+ * If called outside a function, do what ksh does;
+ * skip the rest of the file.
+ */
+ evalskip = funcnest ? SKIPFUNC : SKIPFILE;
+ return argv[1] ? number(argv[1]) : exitstatus;
+}
- mode = 0;
- while ((m = nextopt("lp"))) {
- if (m == 'l')
- mode = SHOW_PID;
- else
- mode = SHOW_PGID;
- }
+static int
+falsecmd(int argc, char **argv)
+{
+ return 1;
+}
- out = stdout;
- argv = argptr;
- if (*argv) {
- do
- showjob(out, getjob(*argv,0), mode);
- while (*++argv);
- } else
- showjobs(out, mode);
+static int
+truecmd(int argc, char **argv)
+{
+ return 0;
+}
+static int
+execcmd(int argc, char **argv)
+{
+ if (argc > 1) {
+ iflag = 0; /* exit on error */
+ mflag = 0;
+ optschanged();
+ shellexec(argv + 1, pathval(), 0);
+ }
return 0;
}
+
+/* ============ input.c
+ *
+ * This implements the input routines used by the parser.
+ */
+
+#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */
+
+enum {
+ INPUT_PUSH_FILE = 1,
+ INPUT_NOFILE_OK = 2,
+};
+
/*
- * Print a list of jobs. If "change" is nonzero, only print jobs whose
- * statuses have changed since the last call to showjobs.
+ * NEOF is returned by parsecmd when it encounters an end of file. It
+ * must be distinct from NULL, so we use the address of a variable that
+ * happens to be handy.
*/
-static void
-showjobs(FILE *out, int mode)
-{
- struct job *jp;
+static int plinno = 1; /* input line number */
+/* number of characters left in input buffer */
+static int parsenleft; /* copy of parsefile->nleft */
+static int parselleft; /* copy of parsefile->lleft */
+/* next character in input buffer */
+static char *parsenextc; /* copy of parsefile->nextc */
- TRACE(("showjobs(%x) called\n", mode));
+static int checkkwd;
+/* values of checkkwd variable */
+#define CHKALIAS 0x1
+#define CHKKWD 0x2
+#define CHKNL 0x4
- /* If not even one one job changed, there is nothing to do */
- while (dowait(DOWAIT_NORMAL, NULL) > 0)
- continue;
- for (jp = curjob; jp; jp = jp->prev_job) {
- if (!(mode & SHOW_CHANGED) || jp->changed)
- showjob(out, jp, mode);
+static void
+popstring(void)
+{
+ struct strpush *sp = parsefile->strpush;
+
+ INT_OFF;
+#if ENABLE_ASH_ALIAS
+ if (sp->ap) {
+ if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') {
+ checkkwd |= CHKALIAS;
+ }
+ if (sp->string != sp->ap->val) {
+ free(sp->string);
+ }
+ sp->ap->flag &= ~ALIASINUSE;
+ if (sp->ap->flag & ALIASDEAD) {
+ unalias(sp->ap->name);
+ }
}
+#endif
+ parsenextc = sp->prevstring;
+ parsenleft = sp->prevnleft;
+/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/
+ parsefile->strpush = sp->prev;
+ if (sp != &(parsefile->basestrpush))
+ free(sp);
+ INT_ON;
}
-#endif /* JOBS */
static int
-getstatus(struct job *job)
+preadfd(void)
{
- int status;
- int retval;
+ int nr;
+ char *buf = parsefile->buf;
+ parsenextc = buf;
- status = job->ps[job->nprocs - 1].status;
- retval = WEXITSTATUS(status);
- if (!WIFEXITED(status)) {
-#if JOBS
- retval = WSTOPSIG(status);
- if (!WIFSTOPPED(status))
+ retry:
+#if ENABLE_FEATURE_EDITING
+ if (!iflag || parsefile->fd)
+ nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
+ else {
+#if ENABLE_FEATURE_TAB_COMPLETION
+ line_input_state->path_lookup = pathval();
#endif
- {
- /* XXX: limits number of signals */
- retval = WTERMSIG(status);
-#if JOBS
- if (retval == SIGINT)
- job->sigint = 1;
+ nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state);
+ if (nr == 0) {
+ /* Ctrl+C pressed */
+ if (trap[SIGINT]) {
+ buf[0] = '\n';
+ buf[1] = '\0';
+ raise(SIGINT);
+ return 1;
+ }
+ goto retry;
+ }
+ if (nr < 0 && errno == 0) {
+ /* Ctrl+D presend */
+ nr = 0;
+ }
+ }
+#else
+ nr = safe_read(parsefile->fd, buf, BUFSIZ - 1);
#endif
+
+ if (nr < 0) {
+ if (parsefile->fd == 0 && errno == EWOULDBLOCK) {
+ int flags = fcntl(0, F_GETFL, 0);
+ if (flags >= 0 && flags & O_NONBLOCK) {
+ flags &=~ O_NONBLOCK;
+ if (fcntl(0, F_SETFL, flags) >= 0) {
+ out2str("sh: turning off NDELAY mode\n");
+ goto retry;
+ }
+ }
}
- retval += 128;
}
- TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n",
- jobno(job), job->nprocs, status, retval));
- return retval;
+ return nr;
}
+/*
+ * Refill the input buffer and return the next input character:
+ *
+ * 1) If a string was pushed back on the input, pop it;
+ * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
+ * from a string so we can't refill the buffer, return EOF.
+ * 3) If the is more stuff in this buffer, use it else call read to fill it.
+ * 4) Process input up to the next newline, deleting nul characters.
+ */
static int
-waitcmd(int argc, char **argv)
+preadbuffer(void)
{
- struct job *job;
- int retval;
- struct job *jp;
-
- EXSIGON;
+ char *q;
+ int more;
+ char savec;
- nextopt(nullstr);
- retval = 0;
+ while (parsefile->strpush) {
+#if ENABLE_ASH_ALIAS
+ if (parsenleft == -1 && parsefile->strpush->ap &&
+ parsenextc[-1] != ' ' && parsenextc[-1] != '\t') {
+ return PEOA;
+ }
+#endif
+ popstring();
+ if (--parsenleft >= 0)
+ return SC2INT(*parsenextc++);
+ }
+ if (parsenleft == EOF_NLEFT || parsefile->buf == NULL)
+ return PEOF;
+ flush_stdout_stderr();
- argv = argptr;
- if (!*argv) {
- /* wait for all jobs */
- for (;;) {
- jp = curjob;
- while (1) {
- if (!jp) {
- /* no running procs */
- goto out;
- }
- if (jp->state == JOBRUNNING)
- break;
- jp->waited = 1;
- jp = jp->prev_job;
- }
- dowait(DOWAIT_BLOCK, 0);
+ more = parselleft;
+ if (more <= 0) {
+ again:
+ more = preadfd();
+ if (more <= 0) {
+ parselleft = parsenleft = EOF_NLEFT;
+ return PEOF;
}
}
- retval = 127;
- do {
- if (**argv != '%') {
- pid_t pid = number(*argv);
- job = curjob;
- goto start;
- do {
- if (job->ps[job->nprocs - 1].pid == pid)
- break;
- job = job->prev_job;
- start:
- if (!job)
- goto repeat;
- } while (1);
- } else
- job = getjob(*argv, 0);
- /* loop until process terminated or stopped */
- while (job->state == JOBRUNNING)
- dowait(DOWAIT_BLOCK, 0);
- job->waited = 1;
- retval = getstatus(job);
- repeat:
- ;
- } while (*++argv);
-
- out:
- return retval;
-}
+ q = parsenextc;
-static struct job *
-growjobtab(void)
-{
- size_t len;
- ptrdiff_t offset;
- struct job *jp, *jq;
+ /* delete nul characters */
+ for (;;) {
+ int c;
- len = njobs * sizeof(*jp);
- jq = jobtab;
- jp = ckrealloc(jq, len + 4 * sizeof(*jp));
+ more--;
+ c = *q;
- offset = (char *)jp - (char *)jq;
- if (offset) {
- /* Relocate pointers */
- size_t l = len;
+ if (!c)
+ memmove(q, q + 1, more);
+ else {
+ q++;
+ if (c == '\n') {
+ parsenleft = q - parsenextc - 1;
+ break;
+ }
+ }
- jq = (struct job *)((char *)jq + l);
- while (l) {
- l -= sizeof(*jp);
- jq--;
-#define joff(p) ((struct job *)((char *)(p) + l))
-#define jmove(p) (p) = (void *)((char *)(p) + offset)
- if (joff(jp)->ps == &jq->ps0)
- jmove(joff(jp)->ps);
- if (joff(jp)->prev_job)
- jmove(joff(jp)->prev_job);
+ if (more <= 0) {
+ parsenleft = q - parsenextc - 1;
+ if (parsenleft < 0)
+ goto again;
+ break;
}
- if (curjob)
- jmove(curjob);
-#undef joff
-#undef jmove
}
+ parselleft = more;
- njobs += 4;
- jobtab = jp;
- jp = (struct job *)((char *)jp + len);
- jq = jp + 3;
- do {
- jq->used = 0;
- } while (--jq >= jp);
- return jp;
+ savec = *q;
+ *q = '\0';
+
+ if (vflag) {
+ out2str(parsenextc);
+ }
+
+ *q = savec;
+
+ return SC2INT(*parsenextc++);
}
+#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer())
+
+#if ENABLE_ASH_OPTIMIZE_FOR_SIZE
+#define pgetc_macro() pgetc()
+static int
+pgetc(void)
+{
+ return pgetc_as_macro();
+}
+#else
+#define pgetc_macro() pgetc_as_macro()
+static int
+pgetc(void)
+{
+ return pgetc_macro();
+}
+#endif
+
/*
- * Return a new job structure.
- * Called with interrupts off.
+ * Same as pgetc(), but ignores PEOA.
*/
-static struct job *
-makejob(union node *node, int nprocs)
+#if ENABLE_ASH_ALIAS
+static int
+pgetc2(void)
{
- int i;
- struct job *jp;
+ int c;
- for (i = njobs, jp = jobtab; ; jp++) {
- if (--i < 0) {
- jp = growjobtab();
- break;
- }
- if (jp->used == 0)
- break;
- if (jp->state != JOBDONE || !jp->waited)
- continue;
-#if JOBS
- if (jobctl)
- continue;
-#endif
- freejob(jp);
- break;
- }
- memset(jp, 0, sizeof(*jp));
-#if JOBS
- if (jobctl)
- jp->jobctl = 1;
-#endif
- jp->prev_job = curjob;
- curjob = jp;
- jp->used = 1;
- jp->ps = &jp->ps0;
- if (nprocs > 1) {
- jp->ps = ckmalloc(nprocs * sizeof(struct procstat));
- }
- TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs,
- jobno(jp)));
- return jp;
+ do {
+ c = pgetc_macro();
+ } while (c == PEOA);
+ return c;
}
+#else
+static int
+pgetc2(void)
+{
+ return pgetc_macro();
+}
+#endif
-#if JOBS
/*
- * Return a string identifying a command (to be printed by the
- * jobs command).
+ * Read a line from the script.
*/
-static char *cmdnextc;
-
-static void
-cmdputs(const char *s)
+static char *
+pfgets(char *line, int len)
{
- const char *p, *str;
- char c, cc[2] = " ";
- char *nextc;
- int subtype = 0;
- int quoted = 0;
- static const char vstype[VSTYPE + 1][4] = {
- "", "}", "-", "+", "?", "=",
- "%", "%%", "#", "##"
- };
+ char *p = line;
+ int nleft = len;
+ int c;
- nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
- p = s;
- while ((c = *p++) != 0) {
- str = 0;
- switch (c) {
- case CTLESC:
- c = *p++;
- break;
- case CTLVAR:
- subtype = *p++;
- if ((subtype & VSTYPE) == VSLENGTH)
- str = "${#";
- else
- str = "${";
- if (!(subtype & VSQUOTE) == !(quoted & 1))
- goto dostr;
- quoted ^= 1;
- c = '"';
- break;
- case CTLENDVAR:
- str = "\"}" + !(quoted & 1);
- quoted >>= 1;
- subtype = 0;
- goto dostr;
- case CTLBACKQ:
- str = "$(...)";
- goto dostr;
- case CTLBACKQ+CTLQUOTE:
- str = "\"$(...)\"";
- goto dostr;
-#if ENABLE_ASH_MATH_SUPPORT
- case CTLARI:
- str = "$((";
- goto dostr;
- case CTLENDARI:
- str = "))";
- goto dostr;
-#endif
- case CTLQUOTEMARK:
- quoted ^= 1;
- c = '"';
- break;
- case '=':
- if (subtype == 0)
- break;
- if ((subtype & VSTYPE) != VSNORMAL)
- quoted <<= 1;
- str = vstype[subtype & VSTYPE];
- if (subtype & VSNUL)
- c = ':';
- else
- goto checkstr;
- break;
- case '\'':
- case '\\':
- case '"':
- case '$':
- /* These can only happen inside quotes */
- cc[0] = c;
- str = cc;
- c = '\\';
- break;
- default:
+ while (--nleft > 0) {
+ c = pgetc2();
+ if (c == PEOF) {
+ if (p == line)
+ return NULL;
break;
}
- USTPUTC(c, nextc);
- checkstr:
- if (!str)
- continue;
- dostr:
- while ((c = *str++)) {
- USTPUTC(c, nextc);
- }
- }
- if (quoted & 1) {
- USTPUTC('"', nextc);
+ *p++ = c;
+ if (c == '\n')
+ break;
}
- *nextc = 0;
- cmdnextc = nextc;
+ *p = '\0';
+ return line;
}
-/* cmdtxt() and cmdlist() call each other */
-static void cmdtxt(union node *n);
-
+/*
+ * Undo the last call to pgetc. Only one character may be pushed back.
+ * PEOF may be pushed back.
+ */
static void
-cmdlist(union node *np, int sep)
+pungetc(void)
{
- for (; np; np = np->narg.next) {
- if (!sep)
- cmdputs(spcstr);
- cmdtxt(np);
- if (sep && np->narg.next)
- cmdputs(spcstr);
- }
+ parsenleft++;
+ parsenextc--;
}
+/*
+ * Push a string back onto the input at this current parsefile level.
+ * We handle aliases this way.
+ */
static void
-cmdtxt(union node *n)
+pushstring(char *s, void *ap)
{
- union node *np;
- struct nodelist *lp;
- const char *p;
- char s[2];
+ struct strpush *sp;
+ size_t len;
- if (!n)
- return;
- switch (n->type) {
- default:
-#if DEBUG
- abort();
-#endif
- case NPIPE:
- lp = n->npipe.cmdlist;
- for (;;) {
- cmdtxt(lp->n);
- lp = lp->next;
- if (!lp)
- break;
- cmdputs(" | ");
- }
- break;
- case NSEMI:
- p = "; ";
- goto binop;
- case NAND:
- p = " && ";
- goto binop;
- case NOR:
- p = " || ";
- binop:
- cmdtxt(n->nbinary.ch1);
- cmdputs(p);
- n = n->nbinary.ch2;
- goto donode;
- case NREDIR:
- case NBACKGND:
- n = n->nredir.n;
- goto donode;
- case NNOT:
- cmdputs("!");
- n = n->nnot.com;
- donode:
- cmdtxt(n);
- break;
- case NIF:
- cmdputs("if ");
- cmdtxt(n->nif.test);
- cmdputs("; then ");
- n = n->nif.ifpart;
- if (n->nif.elsepart) {
- cmdtxt(n);
- cmdputs("; else ");
- n = n->nif.elsepart;
- }
- p = "; fi";
- goto dotail;
- case NSUBSHELL:
- cmdputs("(");
- n = n->nredir.n;
- p = ")";
- goto dotail;
- case NWHILE:
- p = "while ";
- goto until;
- case NUNTIL:
- p = "until ";
- until:
- cmdputs(p);
- cmdtxt(n->nbinary.ch1);
- n = n->nbinary.ch2;
- p = "; done";
- dodo:
- cmdputs("; do ");
- dotail:
- cmdtxt(n);
- goto dotail2;
- case NFOR:
- cmdputs("for ");
- cmdputs(n->nfor.var);
- cmdputs(" in ");
- cmdlist(n->nfor.args, 1);
- n = n->nfor.body;
- p = "; done";
- goto dodo;
- case NDEFUN:
- cmdputs(n->narg.text);
- p = "() { ... }";
- goto dotail2;
- case NCMD:
- cmdlist(n->ncmd.args, 1);
- cmdlist(n->ncmd.redirect, 0);
- break;
- case NARG:
- p = n->narg.text;
- dotail2:
- cmdputs(p);
- break;
- case NHERE:
- case NXHERE:
- p = "<<...";
- goto dotail2;
- case NCASE:
- cmdputs("case ");
- cmdputs(n->ncase.expr->narg.text);
- cmdputs(" in ");
- for (np = n->ncase.cases; np; np = np->nclist.next) {
- cmdtxt(np->nclist.pattern);
- cmdputs(") ");
- cmdtxt(np->nclist.body);
- cmdputs(";; ");
- }
- p = "esac";
- goto dotail2;
- case NTO:
- p = ">";
- goto redir;
- case NCLOBBER:
- p = ">|";
- goto redir;
- case NAPPEND:
- p = ">>";
- goto redir;
- case NTOFD:
- p = ">&";
- goto redir;
- case NFROM:
- p = "<";
- goto redir;
- case NFROMFD:
- p = "<&";
- goto redir;
- case NFROMTO:
- p = "<>";
- redir:
- s[0] = n->nfile.fd + '0';
- s[1] = '\0';
- cmdputs(s);
- cmdputs(p);
- if (n->type == NTOFD || n->type == NFROMFD) {
- s[0] = n->ndup.dupfd + '0';
- p = s;
- goto dotail2;
- }
- n = n->nfile.fname;
- goto donode;
+ len = strlen(s);
+ INT_OFF;
+/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
+ if (parsefile->strpush) {
+ sp = ckmalloc(sizeof(struct strpush));
+ sp->prev = parsefile->strpush;
+ parsefile->strpush = sp;
+ } else
+ sp = parsefile->strpush = &(parsefile->basestrpush);
+ sp->prevstring = parsenextc;
+ sp->prevnleft = parsenleft;
+#if ENABLE_ASH_ALIAS
+ sp->ap = (struct alias *)ap;
+ if (ap) {
+ ((struct alias *)ap)->flag |= ALIASINUSE;
+ sp->string = s;
}
+#endif
+ parsenextc = s;
+ parsenleft = len;
+ INT_ON;
}
-static char *
-commandtext(union node *n)
+/*
+ * To handle the "." command, a stack of input files is used. Pushfile
+ * adds a new entry to the stack and popfile restores the previous level.
+ */
+static void
+pushfile(void)
{
- char *name;
+ struct parsefile *pf;
- STARTSTACKSTR(cmdnextc);
- cmdtxt(n);
- name = stackblock();
- TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n",
- name, cmdnextc, cmdnextc));
- return ckstrdup(name);
+ parsefile->nleft = parsenleft;
+ parsefile->lleft = parselleft;
+ parsefile->nextc = parsenextc;
+ parsefile->linno = plinno;
+ pf = ckmalloc(sizeof(*pf));
+ pf->prev = parsefile;
+ pf->fd = -1;
+ pf->strpush = NULL;
+ pf->basestrpush.prev = NULL;
+ parsefile = pf;
}
-#endif /* JOBS */
-/*
- * Fork off a subshell. If we are doing job control, give the subshell its
- * own process group. Jp is a job structure that the job is to be added to.
- * N is the command that will be evaluated by the child. Both jp and n may
- * be NULL. The mode parameter can be one of the following:
- * FORK_FG - Fork off a foreground process.
- * FORK_BG - Fork off a background process.
- * FORK_NOJOB - Like FORK_FG, but don't give the process its own
- * process group even if job control is on.
- *
- * When job control is turned off, background processes have their standard
- * input redirected to /dev/null (except for the second and later processes
- * in a pipeline).
- *
- * Called with interrupts off.
- */
-/*
- * Clear traps on a fork.
- */
static void
-clear_traps(void)
+popfile(void)
{
- char **tp;
+ struct parsefile *pf = parsefile;
- for (tp = trap; tp < &trap[NSIG]; tp++) {
- if (*tp && **tp) { /* trap not NULL or SIG_IGN */
- INT_OFF;
- free(*tp);
- *tp = NULL;
- if (tp != &trap[0])
- setsignal(tp - trap);
- INT_ON;
- }
- }
+ INT_OFF;
+ if (pf->fd >= 0)
+ close(pf->fd);
+ if (pf->buf)
+ free(pf->buf);
+ while (pf->strpush)
+ popstring();
+ parsefile = pf->prev;
+ free(pf);
+ parsenleft = parsefile->nleft;
+ parselleft = parsefile->lleft;
+ parsenextc = parsefile->nextc;
+ plinno = parsefile->linno;
+ INT_ON;
}
+
+/*
+ * Return to top level.
+ */
static void
-forkchild(struct job *jp, union node *n, int mode)
+popallfiles(void)
{
- int oldlvl;
-
- TRACE(("Child shell %d\n", getpid()));
- oldlvl = shlvl;
- shlvl++;
-
- closescript();
- clear_traps();
-#if JOBS
- /* do job control only in root shell */
- jobctl = 0;
- if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
- pid_t pgrp;
-
- if (jp->nprocs == 0)
- pgrp = getpid();
- else
- pgrp = jp->ps[0].pid;
- /* This can fail because we are doing it in the parent also */
- (void)setpgid(0, pgrp);
- if (mode == FORK_FG)
- xtcsetpgrp(ttyfd, pgrp);
- setsignal(SIGTSTP);
- setsignal(SIGTTOU);
- } else
-#endif
- if (mode == FORK_BG) {
- ignoresig(SIGINT);
- ignoresig(SIGQUIT);
- if (jp->nprocs == 0) {
- close(0);
- if (open(bb_dev_null, O_RDONLY) != 0)
- ash_msg_and_raise_error("Can't open %s", bb_dev_null);
- }
- }
- if (!oldlvl && iflag) {
- setsignal(SIGINT);
- setsignal(SIGQUIT);
- setsignal(SIGTERM);
- }
- for (jp = curjob; jp; jp = jp->prev_job)
- freejob(jp);
- jobless = 0;
+ while (parsefile != &basepf)
+ popfile();
}
+/*
+ * Close the file(s) that the shell is reading commands from. Called
+ * after a fork is done.
+ */
static void
-forkparent(struct job *jp, union node *n, int mode, pid_t pid)
+closescript(void)
{
- TRACE(("In parent shell: child = %d\n", pid));
- if (!jp) {
- while (jobless && dowait(DOWAIT_NORMAL, 0) > 0);
- jobless++;
- return;
- }
-#if JOBS
- if (mode != FORK_NOJOB && jp->jobctl) {
- int pgrp;
-
- if (jp->nprocs == 0)
- pgrp = pid;
- else
- pgrp = jp->ps[0].pid;
- /* This can fail because we are doing it in the child also */
- setpgid(pid, pgrp);
- }
-#endif
- if (mode == FORK_BG) {
- backgndpid = pid; /* set $! */
- set_curjob(jp, CUR_RUNNING);
- }
- if (jp) {
- struct procstat *ps = &jp->ps[jp->nprocs++];
- ps->pid = pid;
- ps->status = -1;
- ps->cmd = nullstr;
-#if JOBS
- if (jobctl && n)
- ps->cmd = commandtext(n);
-#endif
+ popallfiles();
+ if (parsefile->fd > 0) {
+ close(parsefile->fd);
+ parsefile->fd = 0;
}
}
-static int
-forkshell(struct job *jp, union node *n, int mode)
+/*
+ * Like setinputfile, but takes an open file descriptor. Call this with
+ * interrupts off.
+ */
+static void
+setinputfd(int fd, int push)
{
- int pid;
-
- TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
- pid = fork();
- if (pid < 0) {
- TRACE(("Fork failed, errno=%d", errno));
- if (jp)
- freejob(jp);
- ash_msg_and_raise_error("Cannot fork");
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+ if (push) {
+ pushfile();
+ parsefile->buf = 0;
}
- if (pid == 0)
- forkchild(jp, n, mode);
- else
- forkparent(jp, n, mode, pid);
- return pid;
+ parsefile->fd = fd;
+ if (parsefile->buf == NULL)
+ parsefile->buf = ckmalloc(IBUFSIZ);
+ parselleft = parsenleft = 0;
+ plinno = 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
- * foreground process to terminate, and then send itself an interrupt
- * signal if the child process was terminated by an interrupt signal.
- * Unfortunately, some programs want to do a bit of cleanup and then
- * exit on interrupt; unless these processes terminate themselves by
- * sending a signal to themselves (instead of calling exit) they will
- * confuse this approach.
- *
- * Called with interrupts off.
+ * Set the input to take input from a file. If push is set, push the
+ * old input onto the stack first.
*/
static int
-waitforjob(struct job *jp)
+setinputfile(const char *fname, int flags)
{
- int st;
+ int fd;
+ int fd2;
- TRACE(("waitforjob(%%%d) called\n", jobno(jp)));
- while (jp->state == JOBRUNNING) {
- dowait(DOWAIT_BLOCK, jp);
+ INT_OFF;
+ fd = open(fname, O_RDONLY);
+ if (fd < 0) {
+ if (flags & INPUT_NOFILE_OK)
+ goto out;
+ ash_msg_and_raise_error("Can't open %s", fname);
}
- st = getstatus(jp);
-#if JOBS
- if (jp->jobctl) {
- xtcsetpgrp(ttyfd, rootpid);
- /*
- * This is truly gross.
- * If we're doing job control, then we did a TIOCSPGRP which
- * caused us (the shell) to no longer be in the controlling
- * session -- so we wouldn't have seen any ^C/SIGINT. So, we
- * intuit from the subprocess exit status whether a SIGINT
- * occurred, and if so interrupt ourselves. Yuck. - mycroft
- */
- if (jp->sigint)
- raise(SIGINT);
+ if (fd < 10) {
+ fd2 = copyfd(fd, 10);
+ close(fd);
+ if (fd2 < 0)
+ ash_msg_and_raise_error("Out of file descriptors");
+ fd = fd2;
}
- if (jp->state == JOBDONE)
-#endif
- freejob(jp);
- return st;
+ setinputfd(fd, flags & INPUT_PUSH_FILE);
+ out:
+ INT_ON;
+ return fd;
}
/*
- * return 1 if there are stopped jobs, otherwise 0
+ * Like setinputfile, but takes input from a string.
*/
-static int
-stoppedjobs(void)
+static void
+setinputstring(char *string)
{
- struct job *jp;
- int retval;
-
- retval = 0;
- if (job_warning)
- goto out;
- jp = curjob;
- if (jp && jp->state == JOBSTOPPED) {
- out2str("You have stopped jobs.\n");
- job_warning = 2;
- retval++;
- }
- out:
- return retval;
+ INT_OFF;
+ pushfile();
+ parsenextc = string;
+ parsenleft = strlen(string);
+ parsefile->buf = NULL;
+ plinno = 1;
+ INT_ON;
}
return 0;
}
-#if ENABLE_LOCALE_SUPPORT
-static void
-change_lc_all(const char *value)
-{
- if (value && *value != '\0')
- setlocale(LC_ALL, value);
-}
-
-static void
-change_lc_ctype(const char *value)
-{
- if (value && *value != '\0')
- setlocale(LC_CTYPE, value);
-}
-#endif
-
#if ENABLE_ASH_RANDOM_SUPPORT
/* Roughly copied from bash.. */
static void