diff options
author | Bryan Drewery <bryan@shatow.net> | 2015-04-19 20:02:22 +0300 |
---|---|---|
committer | Bryan Drewery <bryan@shatow.net> | 2015-04-19 20:26:58 +0300 |
commit | 60396ff4ebc64af9042b61b41523106db5bb1b56 (patch) | |
tree | 091365dbb08bcf76b54af298f310f01fe36634a0 /external | |
parent | 9603fd874795defacbb780d994bbd4c83dd6ad80 (diff) |
Import /bin/sh from FreeBSD r281718
Diffstat (limited to 'external')
59 files changed, 18928 insertions, 0 deletions
diff --git a/external/sh/alias.c b/external/sh/alias.c new file mode 100644 index 00000000..ddaa4884 --- /dev/null +++ b/external/sh/alias.c @@ -0,0 +1,255 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)alias.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/alias.c 263777 2014-03-26 20:43:40Z jilles $"); + +#include <stdlib.h> +#include "shell.h" +#include "output.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "options.h" /* XXX for argptr (should remove?) */ +#include "builtins.h" + +#define ATABSIZE 39 + +static struct alias *atab[ATABSIZE]; +static int aliases; + +static void setalias(const char *, const char *); +static int unalias(const char *); +static struct alias **hashalias(const char *); + +static +void +setalias(const char *name, const char *val) +{ + struct alias *ap, **app; + + app = hashalias(name); + for (ap = *app; ap; ap = ap->next) { + if (equal(name, ap->name)) { + INTOFF; + ckfree(ap->val); + ap->val = savestr(val); + INTON; + return; + } + } + /* not found */ + INTOFF; + ap = ckmalloc(sizeof (struct alias)); + ap->name = savestr(name); + ap->val = savestr(val); + ap->flag = 0; + ap->next = *app; + *app = ap; + aliases++; + INTON; +} + +static int +unalias(const char *name) +{ + struct alias *ap, **app; + + app = hashalias(name); + + for (ap = *app; ap; app = &(ap->next), ap = ap->next) { + if (equal(name, ap->name)) { + /* + * if the alias is currently in use (i.e. its + * buffer is being used by the input routine) we + * just null out the name instead of freeing it. + * We could clear it out later, but this situation + * is so rare that it hardly seems worth it. + */ + if (ap->flag & ALIASINUSE) + *ap->name = '\0'; + else { + INTOFF; + *app = ap->next; + ckfree(ap->name); + ckfree(ap->val); + ckfree(ap); + INTON; + } + aliases--; + return (0); + } + } + + return (1); +} + +static void +rmaliases(void) +{ + struct alias *ap, *tmp; + int i; + + INTOFF; + for (i = 0; i < ATABSIZE; i++) { + ap = atab[i]; + atab[i] = NULL; + while (ap) { + ckfree(ap->name); + ckfree(ap->val); + tmp = ap; + ap = ap->next; + ckfree(tmp); + } + } + aliases = 0; + INTON; +} + +struct alias * +lookupalias(const char *name, int check) +{ + struct alias *ap = *hashalias(name); + + for (; ap; ap = ap->next) { + if (equal(name, ap->name)) { + if (check && (ap->flag & ALIASINUSE)) + return (NULL); + return (ap); + } + } + + return (NULL); +} + +static int +comparealiases(const void *p1, const void *p2) +{ + const struct alias *const *a1 = p1; + const struct alias *const *a2 = p2; + + return strcmp((*a1)->name, (*a2)->name); +} + +static void +printalias(const struct alias *a) +{ + out1fmt("%s=", a->name); + out1qstr(a->val); + out1c('\n'); +} + +static void +printaliases(void) +{ + int i, j; + struct alias **sorted, *ap; + + INTOFF; + sorted = ckmalloc(aliases * sizeof(*sorted)); + j = 0; + for (i = 0; i < ATABSIZE; i++) + for (ap = atab[i]; ap; ap = ap->next) + if (*ap->name != '\0') + sorted[j++] = ap; + qsort(sorted, aliases, sizeof(*sorted), comparealiases); + for (i = 0; i < aliases; i++) { + printalias(sorted[i]); + if (int_pending()) + break; + } + ckfree(sorted); + INTON; +} + +int +aliascmd(int argc __unused, char **argv __unused) +{ + char *n, *v; + int ret = 0; + struct alias *ap; + + nextopt(""); + + if (*argptr == NULL) { + printaliases(); + return (0); + } + while ((n = *argptr++) != NULL) { + if ((v = strchr(n+1, '=')) == NULL) /* n+1: funny ksh stuff */ + if ((ap = lookupalias(n, 0)) == NULL) { + warning("%s: not found", n); + ret = 1; + } else + printalias(ap); + else { + *v++ = '\0'; + setalias(n, v); + } + } + + return (ret); +} + +int +unaliascmd(int argc __unused, char **argv __unused) +{ + int i; + + while ((i = nextopt("a")) != '\0') { + if (i == 'a') { + rmaliases(); + return (0); + } + } + for (i = 0; *argptr; argptr++) + i |= unalias(*argptr); + + return (i); +} + +static struct alias ** +hashalias(const char *p) +{ + unsigned int hashval; + + hashval = *p << 4; + while (*p) + hashval+= *p++; + return &atab[hashval % ATABSIZE]; +} diff --git a/external/sh/alias.h b/external/sh/alias.h new file mode 100644 index 00000000..95a20eae --- /dev/null +++ b/external/sh/alias.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)alias.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/alias.h 223060 2011-06-13 21:03:27Z jilles $ + */ + +#define ALIASINUSE 1 + +struct alias { + struct alias *next; + char *name; + char *val; + int flag; +}; + +struct alias *lookupalias(const char *, int); diff --git a/external/sh/arith.h b/external/sh/arith.h new file mode 100644 index 00000000..3771fe10 --- /dev/null +++ b/external/sh/arith.h @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)arith.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/arith.h 223060 2011-06-13 21:03:27Z jilles $ + */ + +#include "shell.h" + +#define DIGITS(var) (3 + (2 + CHAR_BIT * sizeof((var))) / 3) + +arith_t arith(const char *); +void arith_lex_reset(void); diff --git a/external/sh/arith_yacc.c b/external/sh/arith_yacc.c new file mode 100644 index 00000000..20e1e43e --- /dev/null +++ b/external/sh/arith_yacc.c @@ -0,0 +1,381 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/arith_yacc.c 270246 2014-08-20 20:15:43Z jilles $"); + +#include <limits.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <stdio.h> +#include "arith.h" +#include "arith_yacc.h" +#include "expand.h" +#include "shell.h" +#include "error.h" +#include "memalloc.h" +#include "output.h" +#include "options.h" +#include "var.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +static const char *arith_startbuf; + +const char *arith_buf; +union yystype yylval; + +static int last_token; + +#define ARITH_PRECEDENCE(op, prec) [op - ARITH_BINOP_MIN] = prec + +static const char prec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = { + ARITH_PRECEDENCE(ARITH_MUL, 0), + ARITH_PRECEDENCE(ARITH_DIV, 0), + ARITH_PRECEDENCE(ARITH_REM, 0), + ARITH_PRECEDENCE(ARITH_ADD, 1), + ARITH_PRECEDENCE(ARITH_SUB, 1), + ARITH_PRECEDENCE(ARITH_LSHIFT, 2), + ARITH_PRECEDENCE(ARITH_RSHIFT, 2), + ARITH_PRECEDENCE(ARITH_LT, 3), + ARITH_PRECEDENCE(ARITH_LE, 3), + ARITH_PRECEDENCE(ARITH_GT, 3), + ARITH_PRECEDENCE(ARITH_GE, 3), + ARITH_PRECEDENCE(ARITH_EQ, 4), + ARITH_PRECEDENCE(ARITH_NE, 4), + ARITH_PRECEDENCE(ARITH_BAND, 5), + ARITH_PRECEDENCE(ARITH_BXOR, 6), + ARITH_PRECEDENCE(ARITH_BOR, 7), +}; + +#define ARITH_MAX_PREC 8 + +int letcmd(int, char **); + +static __dead2 void yyerror(const char *s) +{ + error("arithmetic expression: %s: \"%s\"", s, arith_startbuf); + /* NOTREACHED */ +} + +static arith_t arith_lookupvarint(char *varname) +{ + const char *str; + char *p; + arith_t result; + + str = lookupvar(varname); + if (uflag && str == NULL) + yyerror("variable not set"); + if (str == NULL || *str == '\0') + str = "0"; + errno = 0; + result = strtoarith_t(str, &p, 0); + if (errno != 0 || *p != '\0') + yyerror("variable conversion error"); + return result; +} + +static inline int arith_prec(int op) +{ + return prec[op - ARITH_BINOP_MIN]; +} + +static inline int higher_prec(int op1, int op2) +{ + return arith_prec(op1) < arith_prec(op2); +} + +static arith_t do_binop(int op, arith_t a, arith_t b) +{ + + switch (op) { + default: + case ARITH_REM: + case ARITH_DIV: + if (!b) + yyerror("division by zero"); + if (a == ARITH_MIN && b == -1) + yyerror("divide error"); + return op == ARITH_REM ? a % b : a / b; + case ARITH_MUL: + return (uintmax_t)a * (uintmax_t)b; + case ARITH_ADD: + return (uintmax_t)a + (uintmax_t)b; + case ARITH_SUB: + return (uintmax_t)a - (uintmax_t)b; + case ARITH_LSHIFT: + return (uintmax_t)a << (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_RSHIFT: + return a >> (b & (sizeof(uintmax_t) * CHAR_BIT - 1)); + case ARITH_LT: + return a < b; + case ARITH_LE: + return a <= b; + case ARITH_GT: + return a > b; + case ARITH_GE: + return a >= b; + case ARITH_EQ: + return a == b; + case ARITH_NE: + return a != b; + case ARITH_BAND: + return a & b; + case ARITH_BXOR: + return a ^ b; + case ARITH_BOR: + return a | b; + } +} + +static arith_t assignment(int var, int noeval); + +static arith_t primary(int token, union yystype *val, int op, int noeval) +{ + arith_t result; + +again: + switch (token) { + case ARITH_LPAREN: + result = assignment(op, noeval); + if (last_token != ARITH_RPAREN) + yyerror("expecting ')'"); + last_token = yylex(); + return result; + case ARITH_NUM: + last_token = op; + return val->val; + case ARITH_VAR: + last_token = op; + return noeval ? val->val : arith_lookupvarint(val->name); + case ARITH_ADD: + token = op; + *val = yylval; + op = yylex(); + goto again; + case ARITH_SUB: + *val = yylval; + return -primary(op, val, yylex(), noeval); + case ARITH_NOT: + *val = yylval; + return !primary(op, val, yylex(), noeval); + case ARITH_BNOT: + *val = yylval; + return ~primary(op, val, yylex(), noeval); + default: + yyerror("expecting primary"); + } +} + +static arith_t binop2(arith_t a, int op, int precedence, int noeval) +{ + for (;;) { + union yystype val; + arith_t b; + int op2; + int token; + + token = yylex(); + val = yylval; + + b = primary(token, &val, yylex(), noeval); + + op2 = last_token; + if (op2 >= ARITH_BINOP_MIN && op2 < ARITH_BINOP_MAX && + higher_prec(op2, op)) { + b = binop2(b, op2, arith_prec(op), noeval); + op2 = last_token; + } + + a = noeval ? b : do_binop(op, a, b); + + if (op2 < ARITH_BINOP_MIN || op2 >= ARITH_BINOP_MAX || + arith_prec(op2) >= precedence) + return a; + + op = op2; + } +} + +static arith_t binop(int token, union yystype *val, int op, int noeval) +{ + arith_t a = primary(token, val, op, noeval); + + op = last_token; + if (op < ARITH_BINOP_MIN || op >= ARITH_BINOP_MAX) + return a; + + return binop2(a, op, ARITH_MAX_PREC, noeval); +} + +static arith_t and(int token, union yystype *val, int op, int noeval) +{ + arith_t a = binop(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_AND) + return a; + + token = yylex(); + *val = yylval; + + b = and(token, val, yylex(), noeval | !a); + + return a && b; +} + +static arith_t or(int token, union yystype *val, int op, int noeval) +{ + arith_t a = and(token, val, op, noeval); + arith_t b; + + op = last_token; + if (op != ARITH_OR) + return a; + + token = yylex(); + *val = yylval; + + b = or(token, val, yylex(), noeval | !!a); + + return a || b; +} + +static arith_t cond(int token, union yystype *val, int op, int noeval) +{ + arith_t a = or(token, val, op, noeval); + arith_t b; + arith_t c; + + if (last_token != ARITH_QMARK) + return a; + + b = assignment(yylex(), noeval | !a); + + if (last_token != ARITH_COLON) + yyerror("expecting ':'"); + + token = yylex(); + *val = yylval; + + c = cond(token, val, yylex(), noeval | !!a); + + return a ? b : c; +} + +static arith_t assignment(int var, int noeval) +{ + union yystype val = yylval; + int op = yylex(); + arith_t result; + char sresult[DIGITS(result) + 1]; + + if (var != ARITH_VAR) + return cond(var, &val, op, noeval); + + if (op != ARITH_ASS && (op < ARITH_ASS_MIN || op >= ARITH_ASS_MAX)) + return cond(var, &val, op, noeval); + + result = assignment(yylex(), noeval); + if (noeval) + return result; + + if (op != ARITH_ASS) + result = do_binop(op - 11, arith_lookupvarint(val.name), result); + snprintf(sresult, sizeof(sresult), ARITH_FORMAT_STR, result); + setvar(val.name, sresult, 0); + return result; +} + +arith_t arith(const char *s) +{ + struct stackmark smark; + arith_t result; + + setstackmark(&smark); + + arith_buf = arith_startbuf = s; + + result = assignment(yylex(), 0); + + if (last_token) + yyerror("expecting EOF"); + + popstackmark(&smark); + + return result; +} + +/* + * The exp(1) builtin. + */ +int +letcmd(int argc, char **argv) +{ + const char *p; + char *concat; + char **ap; + arith_t i; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + /* + * Concatenate arguments. + */ + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + while (*p) + STPUTC(*p++, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + } else + p = ""; + + i = arith(p); + + out1fmt(ARITH_FORMAT_STR "\n", i); + return !i; +} diff --git a/external/sh/arith_yacc.h b/external/sh/arith_yacc.h new file mode 100644 index 00000000..d386886a --- /dev/null +++ b/external/sh/arith_yacc.h @@ -0,0 +1,93 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2007 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/bin/sh/arith_yacc.h 279503 2015-03-01 21:46:55Z jilles $ + */ + +#define ARITH_ASS 1 + +#define ARITH_OR 2 +#define ARITH_AND 3 +#define ARITH_BAD 4 +#define ARITH_NUM 5 +#define ARITH_VAR 6 +#define ARITH_NOT 7 + +#define ARITH_BINOP_MIN 8 +#define ARITH_LE 8 +#define ARITH_GE 9 +#define ARITH_LT 10 +#define ARITH_GT 11 +#define ARITH_EQ 12 +#define ARITH_REM 13 +#define ARITH_BAND 14 +#define ARITH_LSHIFT 15 +#define ARITH_RSHIFT 16 +#define ARITH_MUL 17 +#define ARITH_ADD 18 +#define ARITH_BOR 19 +#define ARITH_SUB 20 +#define ARITH_BXOR 21 +#define ARITH_DIV 22 +#define ARITH_NE 23 +#define ARITH_BINOP_MAX 24 + +#define ARITH_ASS_MIN 24 +#define ARITH_REMASS 24 +#define ARITH_BANDASS 25 +#define ARITH_LSHIFTASS 26 +#define ARITH_RSHIFTASS 27 +#define ARITH_MULASS 28 +#define ARITH_ADDASS 29 +#define ARITH_BORASS 30 +#define ARITH_SUBASS 31 +#define ARITH_BXORASS 32 +#define ARITH_DIVASS 33 +#define ARITH_ASS_MAX 34 + +#define ARITH_LPAREN 34 +#define ARITH_RPAREN 35 +#define ARITH_BNOT 36 +#define ARITH_QMARK 37 +#define ARITH_COLON 38 + +extern const char *arith_buf; + +union yystype { + arith_t val; + char *name; +}; + +extern union yystype yylval; + +int yylex(void); diff --git a/external/sh/arith_yylex.c b/external/sh/arith_yylex.c new file mode 100644 index 00000000..99368523 --- /dev/null +++ b/external/sh/arith_yylex.c @@ -0,0 +1,248 @@ +/*- + * Copyright (c) 2002 + * Herbert Xu. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/arith_yylex.c 279503 2015-03-01 21:46:55Z jilles $"); + +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include "shell.h" +#include "arith_yacc.h" +#include "expand.h" +#include "error.h" +#include "memalloc.h" +#include "parser.h" +#include "syntax.h" + +#if ARITH_BOR + 11 != ARITH_BORASS || ARITH_ASS + 11 != ARITH_EQ +#error Arithmetic tokens are out of order. +#endif + +int +yylex(void) +{ + int value; + const char *buf = arith_buf; + char *end; + const char *p; + + for (;;) { + value = *buf; + switch (value) { + case ' ': + case '\t': + case '\n': + buf++; + continue; + default: + return ARITH_BAD; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + yylval.val = strtoarith_t(buf, &end, 0); + arith_buf = end; + return ARITH_NUM; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + p = buf; + while (buf++, is_in_name(*buf)) + ; + yylval.name = stalloc(buf - p + 1); + memcpy(yylval.name, p, buf - p); + yylval.name[buf - p] = '\0'; + value = ARITH_VAR; + goto out; + case '=': + value += ARITH_ASS - '='; +checkeq: + buf++; +checkeqcur: + if (*buf != '=') + goto out; + value += 11; + break; + case '>': + switch (*++buf) { + case '=': + value += ARITH_GE - '>'; + break; + case '>': + value += ARITH_RSHIFT - '>'; + goto checkeq; + default: + value += ARITH_GT - '>'; + goto out; + } + break; + case '<': + switch (*++buf) { + case '=': + value += ARITH_LE - '<'; + break; + case '<': + value += ARITH_LSHIFT - '<'; + goto checkeq; + default: + value += ARITH_LT - '<'; + goto out; + } + break; + case '|': + if (*++buf != '|') { + value += ARITH_BOR - '|'; + goto checkeqcur; + } + value += ARITH_OR - '|'; + break; + case '&': + if (*++buf != '&') { + value += ARITH_BAND - '&'; + goto checkeqcur; + } + value += ARITH_AND - '&'; + break; + case '!': + if (*++buf != '=') { + value += ARITH_NOT - '!'; + goto out; + } + value += ARITH_NE - '!'; + break; + case 0: + goto out; + case '(': + value += ARITH_LPAREN - '('; + break; + case ')': + value += ARITH_RPAREN - ')'; + break; + case '*': + value += ARITH_MUL - '*'; + goto checkeq; + case '/': + value += ARITH_DIV - '/'; + goto checkeq; + case '%': + value += ARITH_REM - '%'; + goto checkeq; + case '+': + if (buf[1] == '+') + return ARITH_BAD; + value += ARITH_ADD - '+'; + goto checkeq; + case '-': + if (buf[1] == '-') + return ARITH_BAD; + value += ARITH_SUB - '-'; + goto checkeq; + case '~': + value += ARITH_BNOT - '~'; + break; + case '^': + value += ARITH_BXOR - '^'; + goto checkeq; + case '?': + value += ARITH_QMARK - '?'; + break; + case ':': + value += ARITH_COLON - ':'; + break; + } + break; + } + + buf++; +out: + arith_buf = buf; + return value; +} diff --git a/external/sh/bltin/bltin.h b/external/sh/bltin/bltin.h new file mode 100644 index 00000000..b79cc43a --- /dev/null +++ b/external/sh/bltin/bltin.h @@ -0,0 +1,79 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)bltin.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/bltin/bltin.h 279503 2015-03-01 21:46:55Z jilles $ + */ + +/* + * This file is included by programs which are optionally built into the + * shell. If SHELL is defined, we try to map the standard UNIX library + * routines to ash routines using defines. + */ + +#include "../shell.h" +#include "../mystring.h" +#ifdef SHELL +#include "../error.h" +#include "../output.h" +#include "builtins.h" +#define FILE struct output +#undef stdout +#define stdout out1 +#undef stderr +#define stderr out2 +#define printf out1fmt +#undef putc +#define putc(c, file) outc(c, file) +#undef putchar +#define putchar(c) out1c(c) +#define fprintf outfmt +#define fputs outstr +#define fwrite(ptr, size, nmemb, file) outbin(ptr, (size) * (nmemb), file) +#define fflush flushout +#define INITARGS(argv) +#define warnx warning +#define warn(fmt, ...) warning(fmt ": %s", __VA_ARGS__, strerror(errno)) +#define errx(exitstatus, ...) error(__VA_ARGS__) + +#else +#undef NULL +#include <stdio.h> +#undef main +#define INITARGS(argv) if ((commandname = argv[0]) == NULL) {fputs("Argc is zero\n", stderr); exit(2);} else +#endif + +#include <unistd.h> + +pointer stalloc(int); +int killjob(const char *, int); + +extern char *commandname; diff --git a/external/sh/bltin/echo.c b/external/sh/bltin/echo.c new file mode 100644 index 00000000..3de6589e --- /dev/null +++ b/external/sh/bltin/echo.c @@ -0,0 +1,107 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)echo.c 8.2 (Berkeley) 5/4/95 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/bltin/echo.c 127958 2004-04-06 20:06:54Z markm $"); + +/* + * Echo command. + */ + +#define main echocmd + +#include "bltin.h" + +/* #define eflag 1 */ + +int +main(int argc, char *argv[]) +{ + char **ap; + char *p; + char c; + int count; + int nflag = 0; +#ifndef eflag + int eflag = 0; +#endif + + ap = argv; + if (argc) + ap++; + if ((p = *ap) != NULL) { + if (equal(p, "-n")) { + nflag++; + ap++; + } else if (equal(p, "-e")) { +#ifndef eflag + eflag++; +#endif + ap++; + } + } + while ((p = *ap++) != NULL) { + while ((c = *p++) != '\0') { + if (c == '\\' && eflag) { + switch (*p++) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'c': return 0; /* exit */ + case 'e': c = '\033'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': break; /* c = '\\' */ + case '0': + c = 0; + count = 3; + while (--count >= 0 && (unsigned)(*p - '0') < 8) + c = (c << 3) + (*p++ - '0'); + break; + default: + p--; + break; + } + } + putchar(c); + } + if (*ap) + putchar(' '); + } + if (! nflag) + putchar('\n'); + return 0; +} diff --git a/external/sh/builtins.c b/external/sh/builtins.c new file mode 100644 index 00000000..cabca99a --- /dev/null +++ b/external/sh/builtins.c @@ -0,0 +1,101 @@ +/* + * This file was generated by the mkbuiltins program. + */ + +#include <stdlib.h> +#include "shell.h" +#include "builtins.h" + +int (*const builtinfunc[])(int, char **) = { + bltincmd, + aliascmd, + bgcmd, + bindcmd, + breakcmd, + cdcmd, + commandcmd, + dotcmd, + echocmd, + evalcmd, + execcmd, + exitcmd, + letcmd, + exportcmd, + falsecmd, + fgcmd, + getoptscmd, + hashcmd, + histcmd, + jobidcmd, + jobscmd, + killcmd, + localcmd, + printfcmd, + pwdcmd, + readcmd, + returncmd, + setcmd, + setvarcmd, + shiftcmd, + testcmd, + timescmd, + trapcmd, + truecmd, + typecmd, + ulimitcmd, + umaskcmd, + unaliascmd, + unsetcmd, + waitcmd, + wordexpcmd, +}; + +const struct builtincmd builtincmd[] = { + { "builtin", 0, 0 }, + { "alias", 1, 0 }, + { "bg", 2, 0 }, + { "bind", 3, 0 }, + { "break", 4, 1 }, + { "continue", 4, 1 }, + { "cd", 5, 0 }, + { "chdir", 5, 0 }, + { "command", 6, 0 }, + { ".", 7, 1 }, + { "echo", 8, 0 }, + { "eval", 9, 1 }, + { "exec", 10, 1 }, + { "exit", 11, 1 }, + { "let", 12, 0 }, + { "export", 13, 1 }, + { "readonly", 13, 1 }, + { "false", 14, 0 }, + { "fg", 15, 0 }, + { "getopts", 16, 0 }, + { "hash", 17, 0 }, + { "fc", 18, 0 }, + { "jobid", 19, 0 }, + { "jobs", 20, 0 }, + { "kill", 21, 0 }, + { "local", 22, 0 }, + { "printf", 23, 0 }, + { "pwd", 24, 0 }, + { "read", 25, 0 }, + { "return", 26, 1 }, + { "set", 27, 1 }, + { "setvar", 28, 0 }, + { "shift", 29, 1 }, + { "test", 30, 0 }, + { "[", 30, 0 }, + { "times", 31, 1 }, + { "trap", 32, 1 }, + { ":", 33, 1 }, + { "true", 33, 0 }, + { "type", 34, 0 }, + { "ulimit", 35, 0 }, + { "umask", 36, 0 }, + { "unalias", 37, 0 }, + { "unset", 38, 1 }, + { "wait", 39, 0 }, + { "wordexp", 40, 0 }, + { NULL, 0, 0 } +}; diff --git a/external/sh/builtins.h b/external/sh/builtins.h new file mode 100644 index 00000000..cade05ff --- /dev/null +++ b/external/sh/builtins.h @@ -0,0 +1,97 @@ +/* + * This file was generated by the mkbuiltins program. + */ + +#include <sys/cdefs.h> +#define BLTINCMD 0 +#define ALIASCMD 1 +#define BGCMD 2 +#define BINDCMD 3 +#define BREAKCMD 4 +#define CDCMD 5 +#define COMMANDCMD 6 +#define DOTCMD 7 +#define ECHOCMD 8 +#define EVALCMD 9 +#define EXECCMD 10 +#define EXITCMD 11 +#define LETCMD 12 +#define EXPORTCMD 13 +#define FALSECMD 14 +#define FGCMD 15 +#define GETOPTSCMD 16 +#define HASHCMD 17 +#define HISTCMD 18 +#define JOBIDCMD 19 +#define JOBSCMD 20 +#define KILLCMD 21 +#define LOCALCMD 22 +#define PRINTFCMD 23 +#define PWDCMD 24 +#define READCMD 25 +#define RETURNCMD 26 +#define SETCMD 27 +#define SETVARCMD 28 +#define SHIFTCMD 29 +#define TESTCMD 30 +#define TIMESCMD 31 +#define TRAPCMD 32 +#define TRUECMD 33 +#define TYPECMD 34 +#define ULIMITCMD 35 +#define UMASKCMD 36 +#define UNALIASCMD 37 +#define UNSETCMD 38 +#define WAITCMD 39 +#define WORDEXPCMD 40 + +struct builtincmd { + const char *name; + int code; + int special; +}; + +extern int (*const builtinfunc[])(int, char **); +extern const struct builtincmd builtincmd[]; + +int bltincmd(int, char **); +int aliascmd(int, char **); +int bgcmd(int, char **); +int bindcmd(int, char **); +int breakcmd(int, char **); +int cdcmd(int, char **); +int commandcmd(int, char **); +int dotcmd(int, char **); +int echocmd(int, char **); +int evalcmd(int, char **); +int execcmd(int, char **); +int exitcmd(int, char **); +int letcmd(int, char **); +int exportcmd(int, char **); +int falsecmd(int, char **); +int fgcmd(int, char **); +int getoptscmd(int, char **); +int hashcmd(int, char **); +int histcmd(int, char **); +int jobidcmd(int, char **); +int jobscmd(int, char **); +int killcmd(int, char **); +int localcmd(int, char **); +int printfcmd(int, char **); +int pwdcmd(int, char **); +int readcmd(int, char **); +int returncmd(int, char **); +int setcmd(int, char **); +int setvarcmd(int, char **); +int shiftcmd(int, char **); +int testcmd(int, char **); +int timescmd(int, char **); +int trapcmd(int, char **); +int truecmd(int, char **); +int typecmd(int, char **); +int ulimitcmd(int, char **); +int umaskcmd(int, char **); +int unaliascmd(int, char **); +int unsetcmd(int, char **); +int waitcmd(int, char **); +int wordexpcmd(int, char **); diff --git a/external/sh/cd.c b/external/sh/cd.c new file mode 100644 index 00000000..596d782b --- /dev/null +++ b/external/sh/cd.c @@ -0,0 +1,421 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cd.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/cd.c 278820 2015-02-15 21:47:43Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +/* + * The cd and pwd commands. + */ + +#include "shell.h" +#include "var.h" +#include "nodes.h" /* for jobs.h */ +#include "jobs.h" +#include "options.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "exec.h" +#include "redir.h" +#include "mystring.h" +#include "show.h" +#include "cd.h" +#include "builtins.h" + +static int cdlogical(char *); +static int cdphysical(char *); +static int docd(char *, int, int); +static char *getcomponent(void); +static char *findcwd(char *); +static void updatepwd(char *); +static char *getpwd(void); +static char *getpwd2(void); + +static char *curdir = NULL; /* current working directory */ +static char *prevdir; /* previous working directory */ +static char *cdcomppath; + +int +cdcmd(int argc __unused, char **argv __unused) +{ + const char *dest; + const char *path; + char *p; + struct stat statb; + int ch, phys, print = 0, getcwderr = 0; + int rc; + int errno1 = ENOENT; + + phys = Pflag; + while ((ch = nextopt("eLP")) != '\0') { + switch (ch) { + case 'e': + getcwderr = 1; + break; + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL && argptr[1] != NULL) + error("too many arguments"); + + if ((dest = *argptr) == NULL && (dest = bltinlookup("HOME", 1)) == NULL) + error("HOME not set"); + if (*dest == '\0') + dest = "."; + if (dest[0] == '-' && dest[1] == '\0') { + dest = prevdir ? prevdir : curdir; + if (dest) + print = 1; + else + dest = "."; + } + if (dest[0] == '/' || + (dest[0] == '.' && (dest[1] == '/' || dest[1] == '\0')) || + (dest[0] == '.' && dest[1] == '.' && (dest[2] == '/' || dest[2] == '\0')) || + (path = bltinlookup("CDPATH", 1)) == NULL) + path = ""; + while ((p = padvance(&path, dest)) != NULL) { + if (stat(p, &statb) < 0) { + if (errno != ENOENT) + errno1 = errno; + } else if (!S_ISDIR(statb.st_mode)) + errno1 = ENOTDIR; + else { + if (!print) { + /* + * XXX - rethink + */ + if (p[0] == '.' && p[1] == '/' && p[2] != '\0') + print = strcmp(p + 2, dest); + else + print = strcmp(p, dest); + } + rc = docd(p, print, phys); + if (rc >= 0) + return getcwderr ? rc : 0; + if (errno != ENOENT) + errno1 = errno; + } + } + error("%s: %s", dest, strerror(errno1)); + /*NOTREACHED*/ + return 0; +} + + +/* + * Actually change the directory. In an interactive shell, print the + * directory name if "print" is nonzero. + */ +static int +docd(char *dest, int print, int phys) +{ + int rc; + + TRACE(("docd(\"%s\", %d, %d) called\n", dest, print, phys)); + + /* If logical cd fails, fall back to physical. */ + if ((phys || (rc = cdlogical(dest)) < 0) && (rc = cdphysical(dest)) < 0) + return (-1); + + if (print && iflag && curdir) + out1fmt("%s\n", curdir); + + return (rc); +} + +static int +cdlogical(char *dest) +{ + char *p; + char *q; + char *component; + struct stat statb; + int first; + int badstat; + + /* + * Check each component of the path. If we find a symlink or + * something we can't stat, clear curdir to force a getcwd() + * next time we get the value of the current directory. + */ + badstat = 0; + cdcomppath = stsavestr(dest); + STARTSTACKSTR(p); + if (*dest == '/') { + STPUTC('/', p); + cdcomppath++; + } + first = 1; + while ((q = getcomponent()) != NULL) { + if (q[0] == '\0' || (q[0] == '.' && q[1] == '\0')) + continue; + if (! first) + STPUTC('/', p); + first = 0; + component = q; + STPUTS(q, p); + if (equal(component, "..")) + continue; + STACKSTRNUL(p); + if (lstat(stackblock(), &statb) < 0) { + badstat = 1; + break; + } + } + + INTOFF; + if ((p = findcwd(badstat ? NULL : dest)) == NULL || chdir(p) < 0) { + INTON; + return (-1); + } + updatepwd(p); + INTON; + return (0); +} + +static int +cdphysical(char *dest) +{ + char *p; + int rc = 0; + + INTOFF; + if (chdir(dest) < 0) { + INTON; + return (-1); + } + p = findcwd(NULL); + if (p == NULL) { + warning("warning: failed to get name of current directory"); + rc = 1; + } + updatepwd(p); + INTON; + return (rc); +} + +/* + * Get the next component of the path name pointed to by cdcomppath. + * This routine overwrites the string pointed to by cdcomppath. + */ +static char * +getcomponent(void) +{ + char *p; + char *start; + + if ((p = cdcomppath) == NULL) + return NULL; + start = cdcomppath; + while (*p != '/' && *p != '\0') + p++; + if (*p == '\0') { + cdcomppath = NULL; + } else { + *p++ = '\0'; + cdcomppath = p; + } + return start; +} + + +static char * +findcwd(char *dir) +{ + char *new; + char *p; + + /* + * If our argument is NULL, we don't know the current directory + * any more because we traversed a symbolic link or something + * we couldn't stat(). + */ + if (dir == NULL || curdir == NULL) + return getpwd2(); + cdcomppath = stsavestr(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + STPUTS(curdir, new); + if (STTOPC(new) == '/') + STUNPUTC(new); + } + while ((p = getcomponent()) != NULL) { + if (equal(p, "..")) { + while (new > stackblock() && (STUNPUTC(new), *new) != '/'); + } else if (*p != '\0' && ! equal(p, ".")) { + STPUTC('/', new); + STPUTS(p, new); + } + } + if (new == stackblock()) + STPUTC('/', new); + STACKSTRNUL(new); + return stackblock(); +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. We also call hashcd to let the routines in exec.c know + * that the current directory has changed. + */ +static void +updatepwd(char *dir) +{ + hashcd(); /* update command hash table */ + + if (prevdir) + ckfree(prevdir); + prevdir = curdir; + curdir = dir ? savestr(dir) : NULL; + setvar("PWD", curdir, VEXPORT); + setvar("OLDPWD", prevdir, VEXPORT); +} + +int +pwdcmd(int argc __unused, char **argv __unused) +{ + char *p; + int ch, phys; + + phys = Pflag; + while ((ch = nextopt("LP")) != '\0') { + switch (ch) { + case 'L': + phys = 0; + break; + case 'P': + phys = 1; + break; + } + } + + if (*argptr != NULL) + error("too many arguments"); + + if (!phys && getpwd()) { + out1str(curdir); + out1c('\n'); + } else { + if ((p = getpwd2()) == NULL) + error(".: %s", strerror(errno)); + out1str(p); + out1c('\n'); + } + + return 0; +} + +/* + * Get the current directory and cache the result in curdir. + */ +static char * +getpwd(void) +{ + char *p; + + if (curdir) + return curdir; + + p = getpwd2(); + if (p != NULL) + curdir = savestr(p); + + return curdir; +} + +#define MAXPWD 256 + +/* + * Return the current directory. + */ +static char * +getpwd2(void) +{ + char *pwd; + int i; + + for (i = MAXPWD;; i *= 2) { + pwd = stalloc(i); + if (getcwd(pwd, i) != NULL) + return pwd; + stunalloc(pwd); + if (errno != ERANGE) + break; + } + + return NULL; +} + +/* + * Initialize PWD in a new shell. + * If the shell is interactive, we need to warn if this fails. + */ +void +pwd_init(int warn) +{ + char *pwd; + struct stat stdot, stpwd; + + pwd = lookupvar("PWD"); + if (pwd && *pwd == '/' && stat(".", &stdot) != -1 && + stat(pwd, &stpwd) != -1 && + stdot.st_dev == stpwd.st_dev && + stdot.st_ino == stpwd.st_ino) { + if (curdir) + ckfree(curdir); + curdir = savestr(pwd); + } + if (getpwd() == NULL && warn) + out2fmt_flush("sh: cannot determine working directory\n"); + setvar("PWD", curdir, VEXPORT); +} diff --git a/external/sh/cd.h b/external/sh/cd.h new file mode 100644 index 00000000..6a2c5d3b --- /dev/null +++ b/external/sh/cd.h @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD: head/bin/sh/cd.h 223060 2011-06-13 21:03:27Z jilles $ + */ + +void pwd_init(int); diff --git a/external/sh/error.c b/external/sh/error.c new file mode 100644 index 00000000..65198dd2 --- /dev/null +++ b/external/sh/error.c @@ -0,0 +1,199 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)error.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/error.c 279569 2015-03-03 21:21:43Z jilles $"); + +/* + * Errors and exceptions. + */ + +#include "shell.h" +#include "eval.h" +#include "main.h" +#include "options.h" +#include "output.h" +#include "error.h" +#include "nodes.h" /* show.h needs nodes.h */ +#include "show.h" +#include "trap.h" +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> + + +/* + * Code to handle exceptions in C. + */ + +struct jmploc *handler; +volatile sig_atomic_t exception; +volatile sig_atomic_t suppressint; +volatile sig_atomic_t intpending; + + +static void exverror(int, const char *, va_list) __printf0like(2, 0) __dead2; + +/* + * Called to raise an exception. Since C doesn't include exceptions, we + * just do a longjmp to the exception handler. The type of exception is + * stored in the global variable "exception". + * + * Interrupts are disabled; they should be reenabled when the exception is + * caught. + */ + +void +exraise(int e) +{ + INTOFF; + if (handler == NULL) + abort(); + exception = e; + longjmp(handler->loc, 1); +} + + +/* + * Called from trap.c when a SIGINT is received and not suppressed, or when + * an interrupt is pending and interrupts are re-enabled using INTON. + * (If the user specifies that SIGINT is to be trapped or ignored using the + * trap builtin, then this routine is not called.) Suppressint is nonzero + * when interrupts are held using the INTOFF macro. If SIGINTs are not + * suppressed and the shell is not a root shell, then we want to be + * terminated if we get here, as if we were terminated directly by a SIGINT. + * Arrange for this here. + */ + +void +onint(void) +{ + sigset_t sigs; + + intpending = 0; + sigemptyset(&sigs); + sigprocmask(SIG_SETMASK, &sigs, NULL); + + /* + * This doesn't seem to be needed, since main() emits a newline. + */ +#if 0 + if (tcgetpgrp(0) == getpid()) + write(STDERR_FILENO, "\n", 1); +#endif + if (rootshell && iflag) + exraise(EXINT); + else { + signal(SIGINT, SIG_DFL); + kill(getpid(), SIGINT); + _exit(128 + SIGINT); + } +} + + +static void +vwarning(const char *msg, va_list ap) +{ + if (commandname) + outfmt(out2, "%s: ", commandname); + else if (arg0) + outfmt(out2, "%s: ", arg0); + doformat(out2, msg, ap); + out2fmt_flush("\n"); +} + + +void +warning(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vwarning(msg, ap); + va_end(ap); +} + + +/* + * Exverror is called to raise the error exception. If the first argument + * is not NULL then error prints an error message using printf style + * formatting. It then raises the error exception. + */ +static void +exverror(int cond, const char *msg, va_list ap) +{ + /* + * An interrupt trumps an error. Certain places catch error + * exceptions or transform them to a plain nonzero exit code + * in child processes, and if an error exception can be handled, + * an interrupt can be handled as well. + * + * exraise() will disable interrupts for the exception handler. + */ + FORCEINTON; + +#ifdef DEBUG + if (msg) + TRACE(("exverror(%d, \"%s\") pid=%d\n", cond, msg, getpid())); + else + TRACE(("exverror(%d, NULL) pid=%d\n", cond, getpid())); +#endif + if (msg) + vwarning(msg, ap); + flushall(); + exraise(cond); +} + + +void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(EXERROR, msg, ap); + va_end(ap); +} + + +void +exerror(int cond, const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + exverror(cond, msg, ap); + va_end(ap); +} diff --git a/external/sh/error.h b/external/sh/error.h new file mode 100644 index 00000000..4aa7f370 --- /dev/null +++ b/external/sh/error.h @@ -0,0 +1,95 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)error.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/error.h 276038 2014-12-21 23:09:59Z jilles $ + */ + +/* + * We enclose jmp_buf in a structure so that we can declare pointers to + * jump locations. The global variable handler contains the location to + * jump to when an exception occurs, and the global variable exception + * contains a code identifying the exception. To implement nested + * exception handlers, the user should save the value of handler on entry + * to an inner scope, set handler to point to a jmploc structure for the + * inner scope, and restore handler on exit from the scope. + */ + +#include <setjmp.h> +#include <signal.h> + +struct jmploc { + jmp_buf loc; +}; + +extern struct jmploc *handler; +extern volatile sig_atomic_t exception; + +/* exceptions */ +#define EXINT 0 /* SIGINT received */ +#define EXERROR 1 /* a generic error */ +#define EXEXEC 2 /* command execution failed */ +#define EXEXIT 3 /* call exitshell(exitstatus) */ + + +/* + * These macros allow the user to suspend the handling of interrupt signals + * over a period of time. This is similar to SIGHOLD to or sigblock, but + * much more efficient and portable. (But hacking the kernel is so much + * more fun than worrying about efficiency and portability. :-)) + */ + +extern volatile sig_atomic_t suppressint; +extern volatile sig_atomic_t intpending; + +#define INTOFF suppressint++ +#define INTON { if (--suppressint == 0 && intpending) onint(); } +#define is_int_on() suppressint +#define SETINTON(s) suppressint = (s) +#define FORCEINTON {suppressint = 0; if (intpending) onint();} +#define SET_PENDING_INT intpending = 1 +#define CLEAR_PENDING_INT intpending = 0 +#define int_pending() intpending + +void exraise(int) __dead2; +void onint(void) __dead2; +void warning(const char *, ...) __printflike(1, 2); +void error(const char *, ...) __printf0like(1, 2) __dead2; +void exerror(int, const char *, ...) __printf0like(2, 3) __dead2; + + +/* + * BSD setjmp saves the signal mask, which violates ANSI C and takes time, + * so we use _setjmp instead. + */ + +#define setjmp(jmploc) _setjmp(jmploc) +#define longjmp(jmploc, val) _longjmp(jmploc, val) diff --git a/external/sh/eval.c b/external/sh/eval.c new file mode 100644 index 00000000..d66cdb58 --- /dev/null +++ b/external/sh/eval.c @@ -0,0 +1,1382 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)eval.c 8.9 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/eval.c 279508 2015-03-01 22:32:23Z jilles $"); + +#include <paths.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/resource.h> +#include <sys/wait.h> /* For WIFSIGNALED(status) */ +#include <errno.h> + +/* + * Evaluate a command. + */ + +#include "shell.h" +#include "nodes.h" +#include "syntax.h" +#include "expand.h" +#include "parser.h" +#include "jobs.h" +#include "eval.h" +#include "builtins.h" +#include "options.h" +#include "exec.h" +#include "redir.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "show.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +int evalskip; /* set if we are skipping commands */ +int skipcount; /* number of levels to skip */ +static int loopnest; /* current loop nesting level */ +int funcnest; /* depth of function calls */ +static int builtin_flags; /* evalcommand flags for builtins */ + + +char *commandname; +struct strlist *cmdenviron; +int exitstatus; /* exit status of last command */ +int oexitstatus; /* saved exit status */ + + +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static union node *evalcase(union node *); +static void evalsubshell(union node *, int); +static void evalredir(union node *, int); +static void exphere(union node *, struct arglist *); +static void expredir(union node *); +static void evalpipe(union node *); +static int is_valid_fast_cmdsubst(union node *n); +static void evalcommand(union node *, int, struct backcmd *); +static void prehash(union node *); + + +/* + * Called to reset things after an exception. + */ + +void +reseteval(void) +{ + evalskip = 0; + loopnest = 0; +} + + +/* + * The eval command. + */ + +int +evalcmd(int argc, char **argv) +{ + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + STPUTS(p, concat); + if ((p = *ap++) == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, builtin_flags); + } else + exitstatus = 0; + return exitstatus; +} + + +/* + * Execute a command or commands contained in a string. + */ + +void +evalstring(const char *s, int flags) +{ + union node *n; + struct stackmark smark; + int flags_exit; + int any; + + flags_exit = flags & EV_EXIT; + flags &= ~EV_EXIT; + any = 0; + setstackmark(&smark); + setinputstring(s, 1); + while ((n = parsecmd(0)) != NEOF) { + if (n != NULL && !nflag) { + if (flags_exit && preadateof()) + evaltree(n, flags | EV_EXIT); + else + evaltree(n, flags); + any = 1; + if (evalskip) + break; + } + popstackmark(&smark); + setstackmark(&smark); + } + popfile(); + popstackmark(&smark); + if (!any) + exitstatus = 0; + if (flags_exit) + exraise(EXEXIT); +} + + +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ + +void +evaltree(union node *n, int flags) +{ + int do_etest; + union node *next; + struct stackmark smark; + + setstackmark(&smark); + do_etest = 0; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + exitstatus = 0; + goto out; + } + do { + next = NULL; +#ifndef NO_HISTORY + displayhist = 1; /* show history substitutions done with fc */ +#endif + TRACE(("evaltree(%p: %d) called\n", (void *)n, n->type)); + switch (n->type) { + case NSEMI: + evaltree(n->nbinary.ch1, flags & ~EV_EXIT); + if (evalskip) + goto out; + next = n->nbinary.ch2; + break; + case NAND: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus != 0) { + goto out; + } + next = n->nbinary.ch2; + break; + case NOR: + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip || exitstatus == 0) + goto out; + next = n->nbinary.ch2; + break; + case NREDIR: + evalredir(n, flags); + break; + case NSUBSHELL: + evalsubshell(n, flags); + do_etest = !(flags & EV_TESTED); + break; + case NBACKGND: + evalsubshell(n, flags); + break; + case NIF: { + evaltree(n->nif.test, EV_TESTED); + if (evalskip) + goto out; + if (exitstatus == 0) + next = n->nif.ifpart; + else if (n->nif.elsepart) + next = n->nif.elsepart; + else + exitstatus = 0; + break; + } + case NWHILE: + case NUNTIL: + evalloop(n, flags & ~EV_EXIT); + break; + case NFOR: + evalfor(n, flags & ~EV_EXIT); + break; + case NCASE: + next = evalcase(n); + break; + case NCLIST: + next = n->nclist.body; + break; + case NCLISTFALLTHRU: + if (n->nclist.body) { + evaltree(n->nclist.body, flags & ~EV_EXIT); + if (evalskip) + goto out; + } + next = n->nclist.next; + break; + case NDEFUN: + defun(n->narg.text, n->narg.next); + exitstatus = 0; + break; + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + if (evalskip) + goto out; + exitstatus = !exitstatus; + break; + + case NPIPE: + evalpipe(n); + do_etest = !(flags & EV_TESTED); + break; + case NCMD: + evalcommand(n, flags, (struct backcmd *)NULL); + do_etest = !(flags & EV_TESTED); + break; + default: + out1fmt("Node type = %d\n", n->type); + flushout(&output); + break; + } + n = next; + popstackmark(&smark); + setstackmark(&smark); + } while (n != NULL); +out: + popstackmark(&smark); + if (pendingsig) + dotrap(); + if (eflag && exitstatus != 0 && do_etest) + exitshell(exitstatus); + if (flags & EV_EXIT) + exraise(EXEXIT); +} + + +static void +evalloop(union node *n, int flags) +{ + int status; + + loopnest++; + status = 0; + for (;;) { + if (!evalskip) + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + if (evalskip == SKIPRETURN) + status = exitstatus; + break; + } + if (n->type == NWHILE) { + if (exitstatus != 0) + break; + } else { + if (exitstatus == 0) + break; + } + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + } + loopnest--; + exitstatus = status; +} + + + +static void +evalfor(union node *n, int flags) +{ + struct arglist arglist; + union node *argp; + struct strlist *sp; + int status; + + arglist.lastp = &arglist.list; + for (argp = n->nfor.args ; argp ; argp = argp->narg.next) { + oexitstatus = exitstatus; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + *arglist.lastp = NULL; + + loopnest++; + status = 0; + for (sp = arglist.list ; sp ; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + status = exitstatus; + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + exitstatus = status; +} + + +/* + * Evaluate a case statement, returning the selected tree. + * + * The exit status needs care to get right. + */ + +static union node * +evalcase(union node *n) +{ + union node *cp; + union node *patp; + struct arglist arglist; + + arglist.lastp = &arglist.list; + oexitstatus = exitstatus; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + for (cp = n->ncase.cases ; cp ; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern ; patp ; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + while (cp->nclist.next && + cp->type == NCLISTFALLTHRU && + cp->nclist.body == NULL) + cp = cp->nclist.next; + if (cp->nclist.next && + cp->type == NCLISTFALLTHRU) + return (cp); + if (cp->nclist.body == NULL) + exitstatus = 0; + return (cp->nclist.body); + } + } + } + exitstatus = 0; + return (NULL); +} + + + +/* + * Kick off a subshell to evaluate a tree. + */ + +static void +evalsubshell(union node *n, int flags) +{ + struct job *jp; + int backgnd = (n->type == NBACKGND); + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + if ((!backgnd && flags & EV_EXIT && !have_traps()) || + forkshell(jp = makejob(n, 1), n, backgnd) == 0) { + if (backgnd) + flags &=~ EV_TESTED; + redirect(n->nredir.redirect, 0); + evaltree(n->nredir.n, flags | EV_EXIT); /* never returns */ + } else if (! backgnd) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + INTON; + } else + exitstatus = 0; +} + + +/* + * Evaluate a redirected compound command. + */ + +static void +evalredir(union node *n, int flags) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + volatile int in_redirect = 1; + + oexitstatus = exitstatus; + expredir(n->nredir.redirect); + savehandler = handler; + if (setjmp(jmploc.loc)) { + int e; + + handler = savehandler; + e = exception; + popredir(); + if (e == EXERROR || e == EXEXEC) { + if (in_redirect) { + exitstatus = 2; + return; + } + } + longjmp(handler->loc, 1); + } else { + INTOFF; + handler = &jmploc; + redirect(n->nredir.redirect, REDIR_PUSH); + in_redirect = 0; + INTON; + evaltree(n->nredir.n, flags); + } + INTOFF; + handler = savehandler; + popredir(); + INTON; +} + + +static void +exphere(union node *redir, struct arglist *fn) +{ + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + int need_longjmp = 0; + + redir->nhere.expdoc = ""; + savelocalvars = localvars; + localvars = NULL; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) + need_longjmp = exception != EXERROR && exception != EXEXEC; + else { + handler = &jmploc; + expandarg(redir->nhere.doc, fn, 0); + redir->nhere.expdoc = fn->list->text; + INTOFF; + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + if (need_longjmp) + longjmp(handler->loc, 1); + INTON; +} + + +/* + * Compute the names of the files in a redirection list. + */ + +static void +expredir(union node *n) +{ + union node *redir; + + for (redir = n ; redir ; redir = redir->nfile.next) { + struct arglist fn; + fn.lastp = &fn.list; + switch (redir->type) { + case NFROM: + case NTO: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + expandarg(redir->nfile.fname, &fn, EXP_TILDE); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_TILDE); + fixredir(redir, fn.list->text, 1); + } + break; + case NXHERE: + exphere(redir, &fn); + break; + } + } +} + + + +/* + * 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) +{ + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; + + TRACE(("evalpipe(%p) called\n", (void *)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) + pipelen++; + INTOFF; + 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) { + if (prevfd >= 0) + close(prevfd); + error("Pipe call failed: %s", strerror(errno)); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INTON; + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] >= 0) { + if (!(prevfd >= 0 && pip[0] == 0)) + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + evaltree(lp->n, EV_EXIT); + } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + if (pip[1] != -1) + close(pip[1]); + } + INTON; + if (n->npipe.backgnd == 0) { + INTOFF; + exitstatus = waitforjob(jp, (int *)NULL); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); + INTON; + } else + exitstatus = 0; +} + + + +static int +is_valid_fast_cmdsubst(union node *n) +{ + + return (n->type == NCMD); +} + +/* + * 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. + */ + +void +evalbackcmd(union node *n, struct backcmd *result) +{ + int pip[2]; + struct job *jp; + struct stackmark smark; + struct jmploc jmploc; + struct jmploc *savehandler; + struct localvar *savelocalvars; + + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { + exitstatus = 0; + return; + } + setstackmark(&smark); + exitstatus = oexitstatus; + if (is_valid_fast_cmdsubst(n)) { + savelocalvars = localvars; + localvars = NULL; + forcelocal++; + savehandler = handler; + if (setjmp(jmploc.loc)) { + if (exception == EXERROR || exception == EXEXEC) + exitstatus = 2; + else if (exception != 0) { + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + longjmp(handler->loc, 1); + } + } else { + handler = &jmploc; + evalcommand(n, EV_BACKCMD, result); + } + handler = savehandler; + forcelocal--; + poplocalvars(); + localvars = savelocalvars; + } else { + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltree(n, EV_EXIT); + } + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; + } + popstackmark(&smark); + TRACE(("evalbackcmd done: fd=%d buf=%p nleft=%d jp=%p\n", + result->fd, result->buf, result->nleft, result->jp)); +} + +static int +mustexpandto(const char *argtext, const char *mask) +{ + for (;;) { + if (*argtext == CTLQUOTEMARK || *argtext == CTLQUOTEEND) { + argtext++; + continue; + } + if (*argtext == CTLESC) + argtext++; + else if (BASESYNTAX[(int)*argtext] == CCTL) + return (0); + if (*argtext != *mask) + return (0); + if (*argtext == '\0') + return (1); + argtext++; + mask++; + } +} + +static int +isdeclarationcmd(struct narg *arg) +{ + int have_command = 0; + + if (arg == NULL) + return (0); + while (mustexpandto(arg->text, "command")) { + have_command = 1; + arg = &arg->next->narg; + if (arg == NULL) + return (0); + /* + * To also allow "command -p" and "command --" as part of + * a declaration command, add code here. + * We do not do this, as ksh does not do it either and it + * is not required by POSIX. + */ + } + return (mustexpandto(arg->text, "export") || + mustexpandto(arg->text, "readonly") || + (mustexpandto(arg->text, "local") && + (have_command || !isfunc("local")))); +} + +static void +xtracecommand(struct arglist *varlist, struct arglist *arglist) +{ + struct strlist *sp; + char sep = 0; + const char *p, *ps4; + + ps4 = expandstr(ps4val()); + out2str(ps4 != NULL ? ps4 : ps4val()); + for (sp = varlist->list ; sp ; sp = sp->next) { + if (sep != 0) + out2c(' '); + p = strchr(sp->text, '='); + if (p != NULL) { + p++; + outbin(sp->text, p - sp->text, out2); + out2qstr(p); + } else + out2qstr(sp->text); + sep = ' '; + } + for (sp = arglist->list ; sp ; sp = sp->next) { + if (sep != 0) + out2c(' '); + out2qstr(sp->text); + sep = ' '; + } + out2c('\n'); + flushout(&errout); +} + +/* + * Check if a builtin can safely be executed in the same process, + * even though it should be in a subshell (command substitution). + * Note that jobid, jobs, times and trap can show information not + * available in a child process; this is deliberate. + * The arguments should already have been expanded. + */ +static int +safe_builtin(int idx, int argc, char **argv) +{ + if (idx == BLTINCMD || idx == COMMANDCMD || idx == ECHOCMD || + idx == FALSECMD || idx == JOBIDCMD || idx == JOBSCMD || + idx == KILLCMD || idx == PRINTFCMD || idx == PWDCMD || + idx == TESTCMD || idx == TIMESCMD || idx == TRUECMD || + idx == TYPECMD) + return (1); + if (idx == EXPORTCMD || idx == TRAPCMD || idx == ULIMITCMD || + idx == UMASKCMD) + return (argc <= 1 || (argc == 2 && argv[1][0] == '-')); + if (idx == SETCMD) + return (argc <= 1 || (argc == 2 && (argv[1][0] == '-' || + argv[1][0] == '+') && argv[1][1] == 'o' && + argv[1][2] == '\0')); + return (0); +} + +/* + * Execute a simple command. + * Note: This may or may not return if (flags & EV_EXIT). + */ + +static void +evalcommand(union node *cmd, int flags, struct backcmd *backcmd) +{ + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + char **envp; + int varflag; + struct strlist *sp; + int mode; + int pip[2]; + struct cmdentry cmdentry; + struct job *jp; + struct jmploc jmploc; + struct jmploc *savehandler; + char *savecmdname; + struct shparam saveparam; + struct localvar *savelocalvars; + struct parsefile *savetopfile; + volatile int e; + char *lastarg; + int realstatus; + int do_clearcmdentry; + const char *path = pathval(); + + /* First expand the arguments. */ + TRACE(("evalcommand(%p, %d) called\n", (void *)cmd, flags)); + arglist.lastp = &arglist.list; + varlist.lastp = &varlist.list; + varflag = 1; + jp = NULL; + do_clearcmdentry = 0; + oexitstatus = exitstatus; + exitstatus = 0; + for (argp = cmd->ncmd.args ; argp ; argp = argp->narg.next) { + if (varflag && isassignment(argp->narg.text)) { + expandarg(argp, varflag == 1 ? &varlist : &arglist, + EXP_VARTILDE); + continue; + } else if (varflag == 1) + varflag = isdeclarationcmd(&argp->narg) ? 2 : 0; + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + } + *arglist.lastp = NULL; + *varlist.lastp = NULL; + expredir(cmd->ncmd.redirect); + argc = 0; + for (sp = arglist.list ; sp ; sp = sp->next) + argc++; + /* Add one slot at the beginning for tryexec(). */ + argv = stalloc(sizeof (char *) * (argc + 2)); + argv++; + + for (sp = arglist.list ; sp ; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *argv++ = sp->text; + } + *argv = NULL; + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = argv[-1]; + argv -= argc; + + /* Print the command if xflag is set. */ + if (xflag) + xtracecommand(&varlist, &arglist); + + /* Now locate the command. */ + if (argc == 0) { + /* Variable assignment(s) without command */ + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.index = BLTINCMD; + cmdentry.special = 0; + } else { + static const char PATH[] = "PATH="; + int cmd_flags = 0, bltinonly = 0; + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + for (sp = varlist.list ; sp ; sp = sp->next) + if (strncmp(sp->text, PATH, sizeof(PATH) - 1) == 0) { + path = sp->text + sizeof(PATH) - 1; + /* + * On `PATH=... command`, we need to make + * sure that the command isn't using the + * non-updated hash table of the outer PATH + * setting and we need to make sure that + * the hash table isn't filled with items + * from the temporary setting. + * + * It would be better to forbit using and + * updating the table while this command + * runs, by the command finding mechanism + * is heavily integrated with hash handling, + * so we just delete the hash before and after + * the command runs. Partly deleting like + * changepatch() does doesn't seem worth the + * bookinging effort, since most such runs add + * directories in front of the new PATH. + */ + clearcmdentry(); + do_clearcmdentry = 1; + } + + for (;;) { + if (bltinonly) { + cmdentry.u.index = find_builtin(*argv, &cmdentry.special); + if (cmdentry.u.index < 0) { + cmdentry.u.index = BLTINCMD; + argv--; + argc++; + break; + } + } else + find_command(argv[0], &cmdentry, cmd_flags, path); + /* implement the bltin and command builtins here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (cmdentry.u.index == BLTINCMD) { + if (argc == 1) + break; + argv++; + argc--; + bltinonly = 1; + } else if (cmdentry.u.index == COMMANDCMD) { + if (argc == 1) + break; + if (!strcmp(argv[1], "-p")) { + if (argc == 2) + break; + if (argv[2][0] == '-') { + if (strcmp(argv[2], "--")) + break; + if (argc == 3) + break; + argv += 3; + argc -= 3; + } else { + argv += 2; + argc -= 2; + } + path = _PATH_STDPATH; + clearcmdentry(); + do_clearcmdentry = 1; + } else if (!strcmp(argv[1], "--")) { + if (argc == 2) + break; + argv += 2; + argc -= 2; + } else if (argv[1][0] == '-') + break; + else { + argv++; + argc--; + } + cmd_flags |= DO_NOFUNC; + bltinonly = 0; + } else + break; + } + /* + * Special builtins lose their special properties when + * called via 'command'. + */ + if (cmd_flags & DO_NOFUNC) + cmdentry.special = 0; + } + + /* Fork off a child process if necessary. */ + if (((cmdentry.cmdtype == CMDNORMAL || cmdentry.cmdtype == CMDUNKNOWN) + && ((flags & EV_EXIT) == 0 || have_traps())) + || ((flags & EV_BACKCMD) != 0 + && (cmdentry.cmdtype != CMDBUILTIN || + !safe_builtin(cmdentry.u.index, argc, argv)))) { + jp = makejob(cmd, 1); + mode = FORK_FG; + if (flags & EV_BACKCMD) { + mode = FORK_NOJOB; + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + } + if (cmdentry.cmdtype == CMDNORMAL && + cmd->ncmd.redirect == NULL && + varlist.list == NULL && + (mode == FORK_FG || mode == FORK_NOJOB) && + !disvforkset() && !iflag && !mflag) { + vforkexecshell(jp, argv, environment(), path, + cmdentry.u.index, flags & EV_BACKCMD ? pip : NULL); + goto parent; + } + if (forkshell(jp, cmd, mode) != 0) + goto parent; /* at end of routine */ + if (flags & EV_BACKCMD) { + FORCEINTON; + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + flags &= ~EV_BACKCMD; + } + flags |= EV_EXIT; + } + + /* This is the child process if a fork occurred. */ + /* Execute the command. */ + if (cmdentry.cmdtype == CMDFUNCTION) { +#ifdef DEBUG + trputs("Shell function: "); trargs(argv); +#endif + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.reset = 1; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; + shellparam.optp = NULL; + shellparam.optnext = NULL; + INTOFF; + savelocalvars = localvars; + localvars = NULL; + reffunc(cmdentry.u.func); + savehandler = handler; + if (setjmp(jmploc.loc)) { + freeparam(&shellparam); + shellparam = saveparam; + popredir(); + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + funcnest--; + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + funcnest++; + redirect(cmd->ncmd.redirect, REDIR_PUSH); + INTON; + for (sp = varlist.list ; sp ; sp = sp->next) + mklocal(sp->text); + exitstatus = oexitstatus; + evaltree(getfuncnode(cmdentry.u.func), + flags & (EV_TESTED | EV_EXIT)); + INTOFF; + unreffunc(cmdentry.u.func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + handler = savehandler; + funcnest--; + popredir(); + INTON; + if (evalskip == SKIPRETURN) { + evalskip = 0; + skipcount = 0; + } + if (jp) + exitshell(exitstatus); + } else if (cmdentry.cmdtype == CMDBUILTIN) { +#ifdef DEBUG + trputs("builtin command: "); trargs(argv); +#endif + mode = (cmdentry.u.index == EXECCMD)? 0 : REDIR_PUSH; + if (flags == EV_BACKCMD) { + memout.nleft = 0; + memout.nextc = memout.buf; + memout.bufsize = 64; + mode |= REDIR_BACKQ; + } + savecmdname = commandname; + savetopfile = getcurrentfile(); + cmdenviron = varlist.list; + e = -1; + savehandler = handler; + if (setjmp(jmploc.loc)) { + e = exception; + if (e == EXINT) + exitstatus = SIGINT+128; + else if (e != EXEXIT) + exitstatus = 2; + goto cmddone; + } + handler = &jmploc; + redirect(cmd->ncmd.redirect, mode); + outclearerror(out1); + /* + * If there is no command word, redirection errors should + * not be fatal but assignment errors should. + */ + if (argc == 0) + cmdentry.special = 1; + listsetvar(cmdenviron, cmdentry.special ? 0 : VNOSET); + if (argc > 0) + bltinsetlocale(); + commandname = argv[0]; + argptr = argv + 1; + nextopt_optptr = NULL; /* initialize nextopt */ + builtin_flags = flags; + exitstatus = (*builtinfunc[cmdentry.u.index])(argc, argv); + flushall(); + if (outiserror(out1)) { + warning("write error on stdout"); + if (exitstatus == 0 || exitstatus == 1) + exitstatus = 2; + } +cmddone: + if (argc > 0) + bltinunsetlocale(); + cmdenviron = NULL; + out1 = &output; + out2 = &errout; + freestdout(); + handler = savehandler; + commandname = savecmdname; + if (jp) + exitshell(exitstatus); + if (flags == EV_BACKCMD) { + backcmd->buf = memout.buf; + backcmd->nleft = memout.nextc - memout.buf; + memout.buf = NULL; + } + if (cmdentry.u.index != EXECCMD) + popredir(); + if (e != -1) { + if ((e != EXERROR && e != EXEXEC) + || cmdentry.special) + exraise(e); + popfilesupto(savetopfile); + if (flags != EV_BACKCMD) + FORCEINTON; + } + } else { +#ifdef DEBUG + trputs("normal command: "); trargs(argv); +#endif + redirect(cmd->ncmd.redirect, 0); + for (sp = varlist.list ; sp ; sp = sp->next) + setvareq(sp->text, VEXPORT|VSTACK); + envp = environment(); + shellexec(argv, envp, path, cmdentry.u.index); + /*NOTREACHED*/ + } + goto out; + +parent: /* parent process gets here (if we forked) */ + if (mode == FORK_FG) { /* argument to fork */ + INTOFF; + exitstatus = waitforjob(jp, &realstatus); + INTON; + if (iflag && loopnest > 0 && WIFSIGNALED(realstatus)) { + evalskip = SKIPBREAK; + skipcount = loopnest; + } + } else if (mode == FORK_NOJOB) { + backcmd->fd = pip[0]; + close(pip[1]); + backcmd->jp = jp; + } + +out: + if (lastarg) + setvar("_", lastarg, 0); + if (do_clearcmdentry) + clearcmdentry(); +} + + + +/* + * 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; + + if (n && n->type == NCMD && n->ncmd.args) + if (goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, + pathval()); +} + + + +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ + +/* + * No command given, a bltin command with no arguments, or a bltin command + * with an invalid name. + */ + +int +bltincmd(int argc, char **argv) +{ + if (argc > 1) { + out2fmt_flush("%s: not found\n", argv[1]); + return 127; + } + /* + * Preserve exitstatus of a previous possible redirection + * as POSIX mandates + */ + return exitstatus; +} + + +/* + * 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. + */ + +int +breakcmd(int argc, char **argv) +{ + long n; + char *end; + + if (argc > 1) { + /* Allow arbitrarily large numbers. */ + n = strtol(argv[1], &end, 10); + if (!is_digit(argv[1][0]) || *end != '\0') + error("Illegal number: %s", argv[1]); + } else + n = 1; + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; + } + return 0; +} + +/* + * The `command' command. + */ +int +commandcmd(int argc __unused, char **argv __unused) +{ + const char *path; + int ch; + int cmd = -1; + + path = bltinlookup("PATH", 1); + + while ((ch = nextopt("pvV")) != '\0') { + switch (ch) { + case 'p': + path = _PATH_STDPATH; + break; + case 'v': + cmd = TYPECMD_SMALLV; + break; + case 'V': + cmd = TYPECMD_BIGV; + break; + } + } + + if (cmd != -1) { + if (*argptr == NULL || argptr[1] != NULL) + error("wrong number of arguments"); + return typecmd_impl(2, argptr - 1, cmd, path); + } + if (*argptr != NULL) + error("commandcmd bad call"); + + /* + * Do nothing successfully if no command was specified; + * ksh also does this. + */ + return 0; +} + + +/* + * The return command. + */ + +int +returncmd(int argc, char **argv) +{ + int ret = argc > 1 ? number(argv[1]) : oexitstatus; + + evalskip = SKIPRETURN; + skipcount = 1; + return ret; +} + + +int +falsecmd(int argc __unused, char **argv __unused) +{ + return 1; +} + + +int +truecmd(int argc __unused, char **argv __unused) +{ + return 0; +} + + +int +execcmd(int argc, char **argv) +{ + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + if (argc > 1 && strcmp(argv[1], "--") == 0) + argc--, argv++; + if (argc > 1) { + struct strlist *sp; + + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + for (sp = cmdenviron; sp ; sp = sp->next) + setvareq(sp->text, VEXPORT|VSTACK); + shellexec(argv + 1, environment(), pathval(), 0); + + } + return 0; +} + + +int +timescmd(int argc __unused, char **argv __unused) +{ + struct rusage ru; + long shumins, shsmins, chumins, chsmins; + double shusecs, shssecs, chusecs, chssecs; + + if (getrusage(RUSAGE_SELF, &ru) < 0) + return 1; + shumins = ru.ru_utime.tv_sec / 60; + shusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + shsmins = ru.ru_stime.tv_sec / 60; + shssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + if (getrusage(RUSAGE_CHILDREN, &ru) < 0) + return 1; + chumins = ru.ru_utime.tv_sec / 60; + chusecs = ru.ru_utime.tv_sec % 60 + ru.ru_utime.tv_usec / 1000000.; + chsmins = ru.ru_stime.tv_sec / 60; + chssecs = ru.ru_stime.tv_sec % 60 + ru.ru_stime.tv_usec / 1000000.; + out1fmt("%ldm%.3fs %ldm%.3fs\n%ldm%.3fs %ldm%.3fs\n", shumins, + shusecs, shsmins, shssecs, chumins, chusecs, chsmins, chssecs); + return 0; +} diff --git a/external/sh/eval.h b/external/sh/eval.h new file mode 100644 index 00000000..9484b565 --- /dev/null +++ b/external/sh/eval.h @@ -0,0 +1,70 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)eval.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/eval.h 279508 2015-03-01 22:32:23Z jilles $ + */ + +extern char *commandname; /* currently executing command */ +extern int exitstatus; /* exit status of last command */ +extern int oexitstatus; /* saved exit status */ +extern struct strlist *cmdenviron; /* environment for builtin command */ + + +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 */ +}; + +void reseteval(void); + +/* flags in argument to evaltree/evalstring */ +#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 */ + +void evalstring(const char *, int); +union node; /* BLETCH for ansi C */ +void evaltree(union node *, int); +void evalbackcmd(union node *, struct backcmd *); + +/* in_function returns nonzero if we are currently evaluating a function */ +#define in_function() funcnest +extern int funcnest; +extern int evalskip; +extern int skipcount; + +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK 1 +#define SKIPCONT 2 +#define SKIPRETURN 3 diff --git a/external/sh/exec.c b/external/sh/exec.c new file mode 100644 index 00000000..60e1e545 --- /dev/null +++ b/external/sh/exec.c @@ -0,0 +1,771 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)exec.c 8.4 (Berkeley) 6/8/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/exec.c 268920 2014-07-20 12:06:52Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <paths.h> +#include <stdlib.h> + +/* + * 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. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "parser.h" +#include "redir.h" +#include "eval.h" +#include "exec.h" +#include "builtins.h" +#include "var.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "syntax.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "show.h" +#include "jobs.h" +#include "alias.h" + + +#define CMDTABLESIZE 31 /* should be prime */ + + + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + int special; /* flag for special builtin commands */ + signed char cmdtype; /* index identifying command */ + char cmdname[]; /* name of command */ +}; + + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int cmdtable_cd = 0; /* cmdtable contains cd-dependent entries */ +int exerrno = 0; /* Last exec error */ + + +static void tryexec(char *, char **, char **); +static void printentry(struct tblentry *, int); +static struct tblentry *cmdlookup(const char *, int); +static void delete_cmd_entry(void); +static void addcmdentry(const char *, struct cmdentry *); + + + +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + * + * The argv array may be changed and element argv[-1] should be writable. + */ + +void +shellexec(char **argv, char **envp, const char *path, int idx) +{ + char *cmdname; + int e; + + if (strchr(argv[0], '/') != NULL) { + 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; + if (e == ENOEXEC) + break; + } + stunalloc(cmdname); + } + } + + /* Map to POSIX errors */ + if (e == ENOENT || e == ENOTDIR) { + exerrno = 127; + exerror(EXEXEC, "%s: not found", argv[0]); + } else { + exerrno = 126; + exerror(EXEXEC, "%s: %s", argv[0], strerror(e)); + } +} + + +static void +tryexec(char *cmd, char **argv, char **envp) +{ + int e, in; + ssize_t n; + char buf[256]; + + execve(cmd, argv, envp); + e = errno; + if (e == ENOEXEC) { + INTOFF; + in = open(cmd, O_RDONLY | O_NONBLOCK); + if (in != -1) { + n = pread(in, buf, sizeof buf, 0); + close(in); + if (n > 0 && memchr(buf, '\0', n) != NULL) { + errno = ENOEXEC; + return; + } + } + *argv = cmd; + *--argv = __DECONST(char *, _PATH_BSHELL); + execve(_PATH_BSHELL, argv, envp); + } + errno = e; +} + +/* + * Do a path search. The variable path (passed by reference) should be + * set to the start of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ + +const char *pathopt; + +char * +padvance(const char **path, const char *name) +{ + const char *p, *start; + char *q; + size_t len, namelen; + + if (*path == NULL) + return NULL; + start = *path; + for (p = start; *p && *p != ':' && *p != '%'; p++) + ; /* nothing */ + namelen = strlen(name); + len = p - start + namelen + 2; /* "2" is for '/' and '\0' */ + STARTSTACKSTR(q); + CHECKSTRSPACE(len, q); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + memcpy(q, name, namelen + 1); + pathopt = NULL; + if (*p == '%') { + pathopt = ++p; + while (*p && *p != ':') p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + + +/*** Command hashing code ***/ + + +int +hashcmd(int argc __unused, char **argv __unused) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + int verbose; + struct cmdentry entry; + char *name; + int errors; + + errors = 0; + verbose = 0; + while ((c = nextopt("rv")) != '\0') { + if (c == 'r') { + clearcmdentry(); + } else if (c == 'v') { + verbose++; + } + } + if (*argptr == NULL) { + for (pp = cmdtable ; pp < &cmdtable[CMDTABLESIZE] ; pp++) { + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp, verbose); + } + } + return 0; + } + while ((name = *argptr) != NULL) { + if ((cmdp = cmdlookup(name, 0)) != NULL + && cmdp->cmdtype == CMDNORMAL) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + errors = 1; + else if (verbose) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) + printentry(cmdp, verbose); + else { + outfmt(out2, "%s: not found\n", name); + errors = 1; + } + flushall(); + } + argptr++; + } + return errors; +} + + +static void +printentry(struct tblentry *cmdp, int verbose) +{ + int idx; + const char *path; + char *name; + + if (cmdp->cmdtype == CMDNORMAL) { + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1str(name); + } else if (cmdp->cmdtype == CMDBUILTIN) { + out1fmt("builtin %s", cmdp->cmdname); + } else if (cmdp->cmdtype == CMDFUNCTION) { + out1fmt("function %s", cmdp->cmdname); + if (verbose) { + INTOFF; + name = commandtext(getfuncnode(cmdp->param.func)); + out1c(' '); + out1str(name); + ckfree(name); + INTON; + } +#ifdef DEBUG + } else { + error("internal error: cmdtype %d", cmdp->cmdtype); +#endif + } + out1c('\n'); +} + + + +/* + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. + */ + +void +find_command(const char *name, struct cmdentry *entry, int act, + const char *path) +{ + struct tblentry *cmdp, loc_cmd; + int idx; + char *fullname; + struct stat statb; + int e; + int i; + int spec; + int cd; + + /* If name contains a slash, don't use the hash table */ + if (strchr(name, '/') != NULL) { + entry->cmdtype = CMDNORMAL; + entry->u.index = 0; + return; + } + + cd = 0; + + /* If name is in the table, and not invalidated by cd, we're done */ + if ((cmdp = cmdlookup(name, 0)) != NULL) { + if (cmdp->cmdtype == CMDFUNCTION && act & DO_NOFUNC) + cmdp = NULL; + else + goto success; + } + + /* Check for builtin next */ + if ((i = find_builtin(name, &spec)) >= 0) { + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.index = i; + cmdp->special = spec; + INTON; + goto success; + } + + /* We have to search path. */ + + e = ENOENT; + idx = -1; + for (;(fullname = padvance(&path, name)) != NULL; stunalloc(fullname)) { + idx++; + if (pathopt) { + if (strncmp(pathopt, "func", 4) == 0) { + /* handled below */ + } else { + continue; /* ignore unimplemented options */ + } + } + if (fullname[0] != '/') + cd = 1; + if (stat(fullname, &statb) < 0) { + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + continue; + } + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (pathopt) { /* this is a %func directory */ + readcmdfile(fullname); + if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) + error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } +#ifdef notdef + if (statb.st_uid == geteuid()) { + if ((statb.st_mode & 0100) == 0) + goto loop; + } else if (statb.st_gid == getegid()) { + if ((statb.st_mode & 010) == 0) + goto loop; + } else { + if ((statb.st_mode & 01) == 0) + goto loop; + } +#endif + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + INTOFF; + stunalloc(fullname); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) + cmdp = &loc_cmd; + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INTON; + goto success; + } + + if (act & DO_ERR) { + if (e == ENOENT || e == ENOTDIR) + outfmt(out2, "%s: not found\n", name); + else + outfmt(out2, "%s: %s\n", name, strerror(e)); + } + entry->cmdtype = CMDUNKNOWN; + entry->u.index = 0; + return; + +success: + if (cd) + cmdtable_cd = 1; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; + entry->special = cmdp->special; +} + + + +/* + * Search the table of builtin commands. + */ + +int +find_builtin(const char *name, int *special) +{ + const struct builtincmd *bp; + + for (bp = builtincmd ; bp->name ; bp++) { + if (*bp->name == *name && equal(bp->name, name)) { + *special = bp->special; + return bp->code; + } + } + return -1; +} + + + +/* + * Called when a cd is done. If any entry in cmdtable depends on the current + * directory, simply clear cmdtable completely. + */ + +void +hashcd(void) +{ + if (cmdtable_cd) + clearcmdentry(); +} + + + +/* + * 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. + */ + +void +changepath(const char *newval __unused) +{ + clearcmdentry(); +} + + +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ + +void +clearcmdentry(void) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; + + INTOFF; + for (tblp = cmdtable ; tblp < &cmdtable[CMDTABLESIZE] ; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if (cmdp->cmdtype == CMDNORMAL) { + *pp = cmdp->next; + ckfree(cmdp); + } else { + pp = &cmdp->next; + } + } + } + cmdtable_cd = 0; + INTON; +} + + +/* + * 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. + */ + +static struct tblentry **lastcmdentry; + + +static struct tblentry * +cmdlookup(const char *name, int add) +{ + int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + size_t len; + + p = name; + hashval = *p << 4; + while (*p) + hashval += *p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp ; cmdp ; cmdp = cmdp->next) { + if (equal(cmdp->cmdname, name)) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + INTOFF; + len = strlen(name); + cmdp = *pp = ckmalloc(sizeof (struct tblentry) + len + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + memcpy(cmdp->cmdname, name, len + 1); + INTON; + } + lastcmdentry = pp; + return cmdp; +} + +/* + * Delete the command entry returned on the last lookup. + */ + +static void +delete_cmd_entry(void) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + ckfree(cmdp); + INTON; +} + + + +/* + * Add a new command entry, replacing any existing command entry for + * the same name. + */ + +static void +addcmdentry(const char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; + + INTOFF; + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + INTON; +} + + +/* + * Define a shell function. + */ + +void +defun(const char *name, union node *func) +{ + struct cmdentry entry; + + INTOFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); + INTON; +} + + +/* + * Delete a function if it exists. + * Called with interrupts off. + */ + +int +unsetfunc(const char *name) +{ + struct tblentry *cmdp; + + if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) { + unreffunc(cmdp->param.func); + delete_cmd_entry(); + return (0); + } + return (0); +} + + +/* + * Check if a function by a certain name exists. + */ +int +isfunc(const char *name) +{ + struct tblentry *cmdp; + cmdp = cmdlookup(name, 0); + return (cmdp != NULL && cmdp->cmdtype == CMDFUNCTION); +} + + +/* + * Shared code for the following builtin commands: + * type, command -v, command -V + */ + +int +typecmd_impl(int argc, char **argv, int cmd, const char *path) +{ + struct cmdentry entry; + struct tblentry *cmdp; + const char *const *pp; + struct alias *ap; + int i; + int error1 = 0; + + if (path != pathval()) + clearcmdentry(); + + for (i = 1; i < argc; i++) { + /* First look at the keywords */ + for (pp = parsekwd; *pp; pp++) + if (**pp == *argv[i] && equal(*pp, argv[i])) + break; + + if (*pp) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell keyword\n", argv[i]); + continue; + } + + /* Then look at the aliases */ + if ((ap = lookupalias(argv[i], 1)) != NULL) { + if (cmd == TYPECMD_SMALLV) { + out1fmt("alias %s=", argv[i]); + out1qstr(ap->val); + outcslow('\n', out1); + } else + out1fmt("%s is an alias for %s\n", argv[i], + ap->val); + continue; + } + + /* Then check if it is a tracked alias */ + if ((cmdp = cmdlookup(argv[i], 0)) != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + entry.special = cmdp->special; + } + else { + /* Finally use brute force */ + find_command(argv[i], &entry, 0, path); + } + + switch (entry.cmdtype) { + case CMDNORMAL: { + if (strchr(argv[i], '/') == NULL) { + const char *path2 = path; + char *name; + int j = entry.u.index; + do { + name = padvance(&path2, argv[i]); + stunalloc(name); + } while (--j >= 0); + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", name); + else + out1fmt("%s is%s %s\n", argv[i], + (cmdp && cmd == TYPECMD_TYPE) ? + " a tracked alias for" : "", + name); + } else { + if (eaccess(argv[i], X_OK) == 0) { + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is %s\n", argv[i], + argv[i]); + } else { + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: %s\n", + argv[i], strerror(errno)); + error1 |= 127; + } + } + break; + } + case CMDFUNCTION: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else + out1fmt("%s is a shell function\n", argv[i]); + break; + + case CMDBUILTIN: + if (cmd == TYPECMD_SMALLV) + out1fmt("%s\n", argv[i]); + else if (entry.special) + out1fmt("%s is a special shell builtin\n", + argv[i]); + else + out1fmt("%s is a shell builtin\n", argv[i]); + break; + + default: + if (cmd != TYPECMD_SMALLV) + outfmt(out2, "%s: not found\n", argv[i]); + error1 |= 127; + break; + } + } + + if (path != pathval()) + clearcmdentry(); + + return error1; +} + +/* + * Locate and print what a word is... + */ + +int +typecmd(int argc, char **argv) +{ + if (argc > 2 && strcmp(argv[1], "--") == 0) + argc--, argv++; + return typecmd_impl(argc, argv, TYPECMD_TYPE, bltinlookup("PATH", 1)); +} diff --git a/external/sh/exec.h b/external/sh/exec.h new file mode 100644 index 00000000..0bd6df32 --- /dev/null +++ b/external/sh/exec.h @@ -0,0 +1,77 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)exec.h 8.3 (Berkeley) 6/8/95 + * $FreeBSD: head/bin/sh/exec.h 238468 2012-07-15 10:19:43Z jilles $ + */ + +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDBUILTIN 1 /* command is a shell builtin */ +#define CMDFUNCTION 2 /* command is a shell function */ + +/* values for typecmd_impl's third parameter */ +enum { + TYPECMD_SMALLV, /* command -v */ + TYPECMD_BIGV, /* command -V */ + TYPECMD_TYPE /* type */ +}; + +union node; +struct cmdentry { + int cmdtype; + union param { + int index; + struct funcdef *func; + } u; + int special; +}; + + +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_NOFUNC 0x02 /* don't return shell functions, for command */ + +extern const char *pathopt; /* set by padvance */ +extern int exerrno; /* last exec error */ + +void shellexec(char **, char **, const char *, int) __dead2; +char *padvance(const char **, const char *); +void find_command(const char *, struct cmdentry *, int, const char *); +int find_builtin(const char *, int *); +void hashcd(void); +void changepath(const char *); +void defun(const char *, union node *); +int unsetfunc(const char *); +int isfunc(const char *); +int typecmd_impl(int, char **, int, const char *); +void clearcmdentry(void); diff --git a/external/sh/expand.c b/external/sh/expand.c new file mode 100644 index 00000000..fd6385e2 --- /dev/null +++ b/external/sh/expand.c @@ -0,0 +1,1663 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 1997-2005 + * Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/expand.c 279330 2015-02-26 20:59:18Z nwhitehorn $"); + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> +#include <wctype.h> + +/* + * Routines to expand arguments to commands. We have to deal with + * backquotes, shell variables, and file metacharacters. + */ + +#include "shell.h" +#include "main.h" +#include "nodes.h" +#include "eval.h" +#include "expand.h" +#include "syntax.h" +#include "parser.h" +#include "jobs.h" +#include "options.h" +#include "var.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "arith.h" +#include "show.h" +#include "builtins.h" + +/* + * 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 inquotes; /* search for nul bytes only */ +}; + + +static char *expdest; /* output of current string */ +static struct nodelist *argbackq; /* list of back quote expressions */ +static struct ifsregion ifsfirst; /* first struct in list of ifs regions */ +static struct ifsregion *ifslastp; /* last struct in list */ +static struct arglist exparg; /* holds expanded arg list */ + +static char *argstr(char *, int); +static char *exptilde(char *, int); +static char *expari(char *); +static void expbackq(union node *, int, int); +static int subevalvar(char *, char *, int, int, int, int, int); +static char *evalvar(char *, int); +static int varisset(const char *, int); +static void strtodest(const char *, int, int, int); +static void varvalue(const char *, int, int, int); +static void recordregion(int, int, int); +static void removerecordregions(int); +static void ifsbreakup(char *, struct arglist *); +static void expandmeta(struct strlist *); +static void expmeta(char *, char *); +static void addfname(char *); +static struct strlist *expsort(struct strlist *); +static struct strlist *msort(struct strlist *, int); +static int patmatch(const char *, const char *, int); +static char *cvtnum(int, char *); +static int collate_range_cmp(wchar_t, wchar_t); + +static int +collate_range_cmp(wchar_t c1, wchar_t c2) +{ + static wchar_t s1[2], s2[2]; + + s1[0] = c1; + s2[0] = c2; + return (wcscoll(s1, s2)); +} + +static char * +stputs_quotes(const char *data, const char *syntax, char *p) +{ + while (*data) { + CHECKSTRSPACE(2, p); + if (syntax[(int)*data] == CCTL) + USTPUTC(CTLESC, p); + USTPUTC(*data++, p); + } + return (p); +} +#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) + +/* + * Perform expansions on an argument, placing the resulting list of arguments + * in arglist. Parameter expansion, command substitution and arithmetic + * expansion are always performed; additional expansions can be requested + * via flag (EXP_*). + * The result is left in the stack string. + * When arglist is NULL, perform here document expansion. + * + * Caution: this function uses global state and is not reentrant. + * However, a new invocation after an interrupted invocation is safe + * and will reset the global state for the new call. + */ +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); + if (arglist == NULL) { + STACKSTRNUL(expdest); + return; /* here document expanded */ + } + STPUTC('\0', expdest); + p = grabstackstr(expdest); + exparg.lastp = &exparg.list; + if (flag & EXP_FULL) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + expandmeta(exparg.list); + } else { + sp = (struct strlist *)stalloc(sizeof (struct strlist)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + + +/* + * Perform parameter expansion, command substitution and arithmetic + * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. + * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. + * This is used to expand word in ${var+word} etc. + * If EXP_FULL or EXP_CASE are set, keep and/or generate CTLESC + * characters to allow for further processing. + * If EXP_FULL is set, also preserve CTLQUOTEMARK characters. + */ +static char * +argstr(char *p, int flag) +{ + char c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + int split_lit; + int lit_quoted; + + split_lit = flag & EXP_SPLIT_LIT; + lit_quoted = flag & EXP_LIT_QUOTED; + flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(p, flag); + for (;;) { + CHECKSTRSPACE(2, expdest); + switch (c = *p++) { + case '\0': + return (p - 1); + case CTLENDVAR: + case CTLENDARI: + return (p); + case CTLQUOTEMARK: + lit_quoted = 1; + /* "$@" syntax adherence hack */ + if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=') + break; + if ((flag & EXP_FULL) != 0) + USTPUTC(c, expdest); + break; + case CTLQUOTEEND: + lit_quoted = 0; + break; + case CTLESC: + if (quotes) + USTPUTC(c, expdest); + c = *p++; + USTPUTC(c, expdest); + if (split_lit && !lit_quoted) + recordregion(expdest - stackblock() - + (quotes ? 2 : 1), + expdest - stackblock(), 0); + break; + case CTLVAR: + p = evalvar(p, flag); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c & CTLQUOTE, flag); + argbackq = argbackq->next; + break; + case CTLARI: + p = expari(p); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + USTPUTC(c, expdest); + if (split_lit && !lit_quoted) + recordregion(expdest - stackblock() - 1, + expdest - stackblock(), 0); + if (flag & EXP_VARTILDE && *p == '~' && + (c != '=' || firsteq)) { + if (c == '=') + firsteq = 0; + p = exptilde(p, flag); + } + break; + default: + USTPUTC(c, expdest); + if (split_lit && !lit_quoted) + recordregion(expdest - stackblock() - 1, + expdest - stackblock(), 0); + } + } +} + +/* + * Perform tilde expansion, placing the result in the stack string and + * returning the next position in the input string to process. + */ +static char * +exptilde(char *p, int flag) +{ + char c, *startp = p; + struct passwd *pw; + char *home; + + for (;;) { + c = *p; + switch(c) { + case CTLESC: /* This means CTL* are always considered quoted. */ + case CTLVAR: + case CTLBACKQ: + case CTLBACKQ | CTLQUOTE: + case CTLARI: + case CTLENDARI: + case CTLQUOTEMARK: + return (startp); + case ':': + if ((flag & EXP_VARTILDE) == 0) + break; + /* FALLTHROUGH */ + case '\0': + case '/': + case CTLENDVAR: + *p = '\0'; + if (*(startp+1) == '\0') { + home = lookupvar("HOME"); + } else { + pw = getpwnam(startp+1); + home = pw != NULL ? pw->pw_dir : NULL; + } + *p = c; + if (home == NULL || *home == '\0') + return (startp); + strtodest(home, flag, VSNORMAL, 1); + return (p); + } + p++; + } +} + + +static void +removerecordregions(int endoff) +{ + if (ifslastp == NULL) + return; + + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifsfirst.next->next; + ckfree(ifsfirst.next); + ifsfirst.next = ifsp; + INTON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + return; + } + + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = ifslastp->next->next; + ckfree(ifslastp->next); + ifslastp->next = ifsp; + INTON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; +} + +/* + * Expand arithmetic expression. + * Note that flag is not required as digits never require CTLESC characters. + */ +static char * +expari(char *p) +{ + char *q, *start; + arith_t result; + int begoff; + int quoted; + int adj; + + quoted = *p++ == '"'; + begoff = expdest - stackblock(); + p = argstr(p, 0); + removerecordregions(begoff); + STPUTC('\0', expdest); + start = stackblock() + begoff; + + q = grabstackstr(expdest); + result = arith(start); + ungrabstackstr(q, expdest); + + start = stackblock() + begoff; + adj = start - expdest; + STADJUST(adj, expdest); + + CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); + fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); + adj = strlen(expdest); + STADJUST(adj, expdest); + if (!quoted) + recordregion(begoff, expdest - stackblock(), 0); + return p; +} + + +/* + * Perform command substitution. + */ +static void +expbackq(union node *cmd, int quoted, int flag) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = expdest; + struct ifsregion saveifs, *savelastp; + struct nodelist *saveargbackq; + char lastc; + int startloc = dest - stackblock(); + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int quotes = flag & (EXP_FULL | EXP_CASE); + size_t nnl; + + INTOFF; + saveifs = ifsfirst; + savelastp = ifslastp; + saveargbackq = argbackq; + p = grabstackstr(dest); + evalbackcmd(cmd, &in); + ungrabstackstr(p, dest); + ifsfirst = saveifs; + ifslastp = savelastp; + argbackq = saveargbackq; + + p = in.buf; + lastc = '\0'; + nnl = 0; + /* Don't copy trailing newlines */ + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR); + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; + if (lastc != '\0') { + if (lastc == '\n') { + nnl++; + } else { + CHECKSTRSPACE(nnl + 2, dest); + while (nnl > 0) { + nnl--; + USTPUTC('\n', dest); + } + if (quotes && syntax[(int)lastc] == CCTL) + USTPUTC(CTLESC, dest); + USTPUTC(lastc, dest); + } + } + } + + if (in.fd >= 0) + close(in.fd); + if (in.buf) + ckfree(in.buf); + if (in.jp) + exitstatus = waitforjob(in.jp, (int *)NULL); + if (quoted == 0) + recordregion(startloc, dest - stackblock(), 0); + TRACE(("expbackq: size=%td: \"%.*s\"\n", + ((dest - stackblock()) - startloc), + (int)((dest - stackblock()) - startloc), + stackblock() + startloc)); + expdest = dest; + INTON; +} + + + +static void +recordleft(const char *str, const char *loc, char *startp) +{ + int amount; + + amount = ((str - 1) - (loc - startp)) - expdest; + STADJUST(amount, expdest); + while (loc != str - 1) + *startp++ = *loc++; +} + +static int +subevalvar(char *p, char *str, int strloc, int subtype, int startloc, + int varflags, int quotes) +{ + char *startp; + char *loc = NULL; + char *q; + int c = 0; + struct nodelist *saveargbackq = argbackq; + int amount; + + argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || + subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ? + EXP_CASE : 0) | EXP_TILDE); + STACKSTRNUL(expdest); + argbackq = saveargbackq; + startp = stackblock() + startloc; + if (str == NULL) + str = stackblock() + strloc; + + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + varflags &= ~VSNUL; + return 1; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(out2, "%s\n", startp); + error((char *)NULL); + } + error("%.*s: parameter %snot set", (int)(p - str - 1), + str, (varflags & VSNUL) ? "null or " : ""); + return 0; + + case VSTRIMLEFT: + for (loc = startp; loc < str; loc++) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) { + *loc = c; + recordleft(str, loc, startp); + return 1; + } + *loc = c; + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + case VSTRIMLEFTMAX: + for (loc = str - 1; loc >= startp;) { + c = *loc; + *loc = '\0'; + if (patmatch(str, startp, quotes)) { + *loc = c; + recordleft(str, loc, startp); + return 1; + } + *loc = c; + loc--; + if (quotes && loc > startp && *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHT: + for (loc = str - 1; loc >= startp;) { + if (patmatch(str, loc, quotes)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return 1; + } + loc--; + if (quotes && loc > startp && *(loc - 1) == CTLESC) { + for (q = startp; q < loc; q++) + if (*q == CTLESC) + q++; + if (q > loc) + loc--; + } + } + return 0; + + case VSTRIMRIGHTMAX: + for (loc = startp; loc < str - 1; loc++) { + if (patmatch(str, loc, quotes)) { + amount = loc - expdest; + STADJUST(amount, expdest); + return 1; + } + if (quotes && *loc == CTLESC) + loc++; + } + return 0; + + + default: + abort(); + } +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +static char * +evalvar(char *p, int flag) +{ + int subtype; + int varflags; + char *var; + const char *val; + int patloc; + int c; + int set; + int special; + int startloc; + int varlen; + int varlenb; + int easy; + int quotes = flag & (EXP_FULL | EXP_CASE); + int record = 0; + + varflags = (unsigned char)*p++; + subtype = varflags & VSTYPE; + var = p; + special = 0; + if (! is_name(*p)) + special = 1; + p = strchr(p, '=') + 1; +again: /* jump here after setting a variable with ${var=text} */ + if (varflags & VSLINENO) { + set = 1; + special = 1; + val = NULL; + } else if (special) { + set = varisset(var, varflags & VSNUL); + val = NULL; + } else { + val = bltinlookup(var, 1); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + varlen = 0; + startloc = expdest - stackblock(); + if (!set && uflag && *var != '@' && *var != '*') { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error("%.*s: parameter not set", (int)(p - var - 1), + var); + } + } + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + if (varflags & VSLINENO) + STPUTBIN(var, p - var - 1, expdest); + else + varvalue(var, varflags & VSQUOTE, subtype, flag); + if (subtype == VSLENGTH) { + varlenb = expdest - stackblock() - startloc; + varlen = varlenb; + if (localeisutf8) { + val = stackblock() + startloc; + for (;val != expdest; val++) + if ((*val & 0xC0) == 0x80) + varlen--; + } + STADJUST(-varlenb, expdest); + } + } else { + if (subtype == VSLENGTH) { + for (;*val; val++) + if (!localeisutf8 || + (*val & 0xC0) != 0x80) + varlen++; + } + else + strtodest(val, flag, subtype, + varflags & VSQUOTE); + } + } + + if (subtype == VSPLUS) + set = ! set; + + easy = ((varflags & VSQUOTE) == 0 || + (*var == '@' && shellparam.nparam != 1)); + + + switch (subtype) { + case VSLENGTH: + expdest = cvtnum(varlen, expdest); + record = 1; + break; + + case VSNORMAL: + record = easy; + break; + + case VSPLUS: + case VSMINUS: + if (!set) { + argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) | + (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0)); + break; + } + record = easy; + break; + + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + if (!set) + break; + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - stackblock(); + if (subevalvar(p, NULL, patloc, subtype, + startloc, varflags, quotes) == 0) { + int amount = (expdest - stackblock() - patloc) + 1; + STADJUST(-amount, expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + record = 1; + break; + + case VSASSIGN: + case VSQUESTION: + if (!set) { + if (subevalvar(p, var, 0, subtype, startloc, varflags, + quotes)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + break; + } + record = easy; + break; + + case VSERROR: + c = p - var - 1; + error("${%.*s%s}: Bad substitution", c, var, + (c > 0 && *p != CTLENDVAR) ? "..." : ""); + + default: + abort(); + } + + if (record) + recordregion(startloc, expdest - stackblock(), + varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' && + (*var == '@' || *var == '*'))); + + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + if ((c = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (set) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } + } + return p; +} + + + +/* + * Test whether a specialized variable is set. + */ + +static int +varisset(const char *name, int nulok) +{ + + if (*name == '!') + return backgndpidset(); + else if (*name == '@' || *name == '*') { + if (*shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + long num; + + errno = 0; + num = strtol(name, NULL, 10); + if (errno != 0 || num > shellparam.nparam) + return 0; + + if (num == 0) + ap = arg0; + else + ap = shellparam.p[num - 1]; + + if (nulok && (ap == NULL || *ap == '\0')) + return 0; + } + return 1; +} + +static void +strtodest(const char *p, int flag, int subtype, int quoted) +{ + if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) + STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); + else + STPUTS(p, expdest); +} + +/* + * Add the value of a specialized variable to the stack string. + */ + +static void +varvalue(const char *name, int quoted, int subtype, int flag) +{ + int num; + char *p; + int i; + char sep[2]; + char **ap; + + switch (*name) { + case '$': + num = rootpid; + break; + case '?': + num = oexitstatus; + break; + case '#': + num = shellparam.nparam; + break; + case '!': + num = backgndpidval(); + break; + case '-': + for (i = 0 ; i < NOPTS ; i++) { + if (optlist[i].val) + STPUTC(optlist[i].letter, expdest); + } + return; + case '@': + if (flag & EXP_FULL && quoted) { + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted); + if (*ap) + STPUTC('\0', expdest); + } + return; + } + /* FALLTHROUGH */ + case '*': + if (ifsset()) + sep[0] = ifsval()[0]; + else + sep[0] = ' '; + sep[1] = '\0'; + for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { + strtodest(p, flag, subtype, quoted); + if (!*ap) + break; + if (sep[0]) + strtodest(sep, flag, subtype, quoted); + else if (flag & EXP_FULL && !quoted && **ap != '\0') + STPUTC('\0', expdest); + } + return; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num == 0) + p = arg0; + else if (num > 0 && num <= shellparam.nparam) + p = shellparam.p[num - 1]; + else + return; + strtodest(p, flag, subtype, quoted); + } + return; + } + expdest = cvtnum(num, expdest); +} + + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +static void +recordregion(int start, int end, int inquotes) +{ + struct ifsregion *ifsp; + + INTOFF; + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + if (ifslastp->endoff == start + && ifslastp->inquotes == inquotes) { + /* extend previous area */ + ifslastp->endoff = end; + INTON; + return; + } + ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); + ifslastp->next = ifsp; + } + ifslastp = ifsp; + ifslastp->next = NULL; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->inquotes = inquotes; + INTON; +} + + + +/* + * 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. + * CTLESC characters are preserved but have little effect in this pass + * other than escaping CTL* characters. In particular, they do not escape + * IFS characters: that should be done with the ifsregion mechanism. + * CTLQUOTEMARK characters are used to preserve empty quoted strings. + * This pass treats them as a regular character, making the string non-empty. + * Later, they are removed along with the other CTL* characters. + */ +static void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs; + const char *ifsspc; + int had_param_ch = 0; + + start = string; + + if (ifslastp == NULL) { + /* Return entire argument, IFS doesn't apply to any of it */ + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + return; + } + + ifs = ifsset() ? ifsval() : " \t\n"; + + for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { + p = string + ifsp->begoff; + while (p < string + ifsp->endoff) { + q = p; + if (*p == CTLESC) + p++; + if (ifsp->inquotes) { + /* Only NULs (should be from "$@") end args */ + had_param_ch = 1; + if (*p != 0) { + p++; + continue; + } + ifsspc = NULL; + } else { + if (!strchr(ifs, *p)) { + had_param_ch = 1; + p++; + continue; + } + ifsspc = strchr(" \t\n", *p); + + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc != NULL) { + p++; + start = p; + continue; + } + had_param_ch = 0; + } + + /* Save this argument... */ + *q = '\0'; + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + + if (ifsspc != NULL) { + /* Ignore further trailing IFS whitespace */ + for (; p < string + ifsp->endoff; p++) { + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL) { + p = q; + break; + } + if (strchr(" \t\n", *p) == NULL) { + p++; + break; + } + } + } + start = p; + } + } + + /* + * Save anything left as an argument. + * Traditionally we have treated 'IFS=':'; set -- x$IFS' as + * generating 2 arguments, the second of which is empty. + * Some recent clarification of the Posix spec say that it + * should only generate one.... + */ + if (had_param_ch || *start != 0) { + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + } +} + + +static char expdir[PATH_MAX]; +#define expdir_end (expdir + sizeof(expdir)) + +/* + * Perform pathname generation and remove control characters. + * At this point, the only control characters should be CTLESC and CTLQUOTEMARK. + * The results are stored in the list exparg. + */ +static void +expandmeta(struct strlist *str) +{ + char *p; + struct strlist **savelastp; + struct strlist *sp; + char c; + + while (str) { + savelastp = exparg.lastp; + if (!fflag) { + p = str->text; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, str->text); + INTON; + break; + } + } + } + if (exparg.lastp == savelastp) { + /* + * no matches + */ + *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; + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +static void +expmeta(char *enddir, char *name) +{ + const char *p; + const char *q; + const char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; + int esc; + int namlen; + + metaflag = 0; + start = name; + for (p = name; esc = 0, *p; p += esc + 1) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + q = p + 1; + if (*q == '!' || *q == '^') + q++; + for (;;) { + while (*q == CTLQUOTEMARK) + q++; + if (*q == CTLESC) + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\0') + break; + else if (*p == CTLQUOTEMARK) + continue; + else { + if (*p == CTLESC) + esc++; + if (p[esc] == '/') { + if (metaflag) + break; + start = p + esc + 1; + } + } + } + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLQUOTEMARK) + continue; + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + if (enddir == expdir_end) + return; + } + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = name + (p - name); + if (start != name) { + p = name; + while (p < start) { + while (*p == CTLQUOTEMARK) + p++; + if (*p == CTLESC) + p++; + *enddir++ = *p++; + if (enddir == expdir_end) + return; + } + } + if (enddir == expdir) { + p = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + p = "/"; + } else { + p = expdir; + enddir[-1] = '\0'; + } + if ((dirp = opendir(p)) == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname = '\0'; + endname += esc + 1; + } + matchdot = 0; + p = start; + while (*p == CTLQUOTEMARK) + p++; + if (*p == CTLESC) + p++; + if (*p == '.') + matchdot++; + while (! int_pending() && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (patmatch(start, dp->d_name, 0)) { + namlen = dp->d_namlen; + if (enddir + namlen + 1 > expdir_end) + continue; + memcpy(enddir, dp->d_name, namlen + 1); + if (atend) + addfname(expdir); + else { + if (dp->d_type != DT_UNKNOWN && + dp->d_type != DT_DIR && + dp->d_type != DT_LNK) + continue; + if (enddir + namlen + 2 > expdir_end) + continue; + enddir[namlen] = '/'; + enddir[namlen + 1] = '\0'; + expmeta(enddir + namlen + 1, endname); + } + } + } + closedir(dirp); + if (! atend) + endname[-esc - 1] = esc ? CTLESC : '/'; +} + + +/* + * Add a file name to the list. + */ + +static void +addfname(char *name) +{ + char *p; + struct strlist *sp; + + p = stsavestr(name); + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; +} + + +/* + * 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 struct strlist * +msort(struct strlist *list, int len) +{ + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; + + 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 (strcmp(p->text, q->text) < 0) { + *lpp = p; + lpp = &p->next; + if ((p = *lpp) == NULL) { + *lpp = q; + break; + } + } else { + *lpp = q; + lpp = &q->next; + if ((q = *lpp) == NULL) { + *lpp = p; + break; + } + } + } + return list; +} + + + +static wchar_t +get_wc(const char **p) +{ + wchar_t c; + int chrlen; + + chrlen = mbtowc(&c, *p, 4); + if (chrlen == 0) + return 0; + else if (chrlen == -1) + c = 0; + else + *p += chrlen; + return c; +} + + +/* + * See if a character matches a character class, starting at the first colon + * of "[:class:]". + * If a valid character class is recognized, a pointer to the next character + * after the final closing bracket is stored into *end, otherwise a null + * pointer is stored into *end. + */ +static int +match_charclass(const char *p, wchar_t chr, const char **end) +{ + char name[20]; + const char *nameend; + wctype_t cclass; + + *end = NULL; + p++; + nameend = strstr(p, ":]"); + if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || + nameend == p) + return 0; + memcpy(name, p, nameend - p); + name[nameend - p] = '\0'; + *end = nameend + 2; + cclass = wctype(name); + /* An unknown class matches nothing but is valid nevertheless. */ + if (cclass == 0) + return 0; + return iswctype(chr, cclass); +} + + +/* + * Returns true if the pattern matches the string. + */ + +static int +patmatch(const char *pattern, const char *string, int squoted) +{ + const char *p, *q, *end; + const char *bt_p, *bt_q; + char c; + wchar_t wc, wc2; + + p = pattern; + q = string; + bt_p = NULL; + bt_q = NULL; + for (;;) { + switch (c = *p++) { + case '\0': + if (*q != '\0') + goto backtrack; + return 1; + case CTLESC: + if (squoted && *q == CTLESC) + q++; + if (*q++ != *p++) + goto backtrack; + break; + case CTLQUOTEMARK: + continue; + case '?': + if (squoted && *q == CTLESC) + q++; + if (*q == '\0') + return 0; + if (localeisutf8) { + wc = get_wc(&q); + /* + * A '?' does not match invalid UTF-8 but a + * '*' does, so backtrack. + */ + if (wc == 0) + goto backtrack; + } else + wc = (unsigned char)*q++; + break; + case '*': + c = *p; + while (c == CTLQUOTEMARK || c == '*') + c = *++p; + /* + * If the pattern ends here, we know the string + * matches without needing to look at the rest of it. + */ + if (c == '\0') + return 1; + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_p = p; + bt_q = q; + break; + case '[': { + const char *endp; + int invert, found; + wchar_t chr; + + endp = p; + if (*endp == '!' || *endp == '^') + endp++; + do { + while (*endp == CTLQUOTEMARK) + endp++; + if (*endp == 0) + goto dft; /* no matching ] */ + if (*endp == CTLESC) + endp++; + } while (*++endp != ']'); + invert = 0; + if (*p == '!' || *p == '^') { + invert++; + p++; + } + found = 0; + if (squoted && *q == CTLESC) + q++; + if (*q == '\0') + return 0; + if (localeisutf8) { + chr = get_wc(&q); + if (chr == 0) + goto backtrack; + } else + chr = (unsigned char)*q++; + c = *p++; + do { + if (c == CTLQUOTEMARK) + continue; + if (c == '[' && *p == ':') { + found |= match_charclass(p, chr, &end); + if (end != NULL) + p = end; + } + if (c == CTLESC) + c = *p++; + if (localeisutf8 && c & 0x80) { + p--; + wc = get_wc(&p); + if (wc == 0) /* bad utf-8 */ + return 0; + } else + wc = (unsigned char)c; + if (*p == '-' && p[1] != ']') { + p++; + while (*p == CTLQUOTEMARK) + p++; + if (*p == CTLESC) + p++; + if (localeisutf8) { + wc2 = get_wc(&p); + if (wc2 == 0) /* bad utf-8 */ + return 0; + } else + wc2 = (unsigned char)*p++; + if ( collate_range_cmp(chr, wc) >= 0 + && collate_range_cmp(chr, wc2) <= 0 + ) + found = 1; + } else { + if (chr == wc) + found = 1; + } + } while ((c = *p++) != ']'); + if (found == invert) + goto backtrack; + break; + } +dft: default: + if (squoted && *q == CTLESC) + q++; + if (*q == '\0') + return 0; + if (*q++ == c) + break; +backtrack: + /* + * If we have a mismatch (other than hitting the end + * of the string), go back to the last '*' seen and + * have it match one additional character. + */ + if (bt_p == NULL) + return 0; + if (squoted && *bt_q == CTLESC) + bt_q++; + if (*bt_q == '\0') + return 0; + bt_q++; + p = bt_p; + q = bt_q; + break; + } + } +} + + + +/* + * Remove any CTLESC and CTLQUOTEMARK characters from a string. + */ + +void +rmescapes(char *str) +{ + char *p, *q; + + p = str; + while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { + if (*p++ == '\0') + return; + } + q = p; + while (*p) { + if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { + p++; + continue; + } + if (*p == CTLESC) + p++; + *q++ = *p++; + } + *q = '\0'; +} + + + +/* + * See if a pattern matches in a case statement. + */ + +int +casematch(union node *pattern, const char *val) +{ + struct stackmark smark; + int result; + char *p; + + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STPUTC('\0', expdest); + p = grabstackstr(expdest); + result = patmatch(p, val, 0); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +static char * +cvtnum(int num, char *buf) +{ + char temp[32]; + int neg = num < 0; + char *p = temp + 31; + + temp[31] = '\0'; + + do { + *--p = num % 10 + '0'; + } while ((num /= 10) != 0); + + if (neg) + *--p = '-'; + + STPUTS(p, buf); + return buf; +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(int argc, char **argv) +{ + size_t len; + int i; + + out1fmt("%08x", argc - 1); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt("%08x", (int)len); + for (i = 1; i < argc; i++) + outbin(argv[i], strlen(argv[i]) + 1, out1); + return (0); +} diff --git a/external/sh/expand.h b/external/sh/expand.h new file mode 100644 index 00000000..2da855d2 --- /dev/null +++ b/external/sh/expand.h @@ -0,0 +1,61 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)expand.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/expand.h 276037 2014-12-21 22:18:30Z jilles $ + */ + +struct strlist { + struct strlist *next; + char *text; +}; + + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * 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_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_SPLIT_LIT 0x20 /* IFS split literal text ${v+-a b c} */ +#define EXP_LIT_QUOTED 0x40 /* for EXP_SPLIT_LIT, start off quoted */ + + +union node; +void expandarg(union node *, struct arglist *, int); +void rmescapes(char *); +int casematch(union node *, const char *); diff --git a/external/sh/histedit.c b/external/sh/histedit.c new file mode 100644 index 00000000..dad46722 --- /dev/null +++ b/external/sh/histedit.c @@ -0,0 +1,502 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)histedit.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/histedit.c 279508 2015-03-01 22:32:23Z jilles $"); + +#include <sys/param.h> +#include <limits.h> +#include <paths.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +/* + * Editline and history functions (and glue). + */ +#include "shell.h" +#include "parser.h" +#include "var.h" +#include "options.h" +#include "main.h" +#include "output.h" +#include "mystring.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#include "error.h" +#include "eval.h" +#include "memalloc.h" +#include "builtins.h" + +#define MAXHISTLOOPS 4 /* max recursions through fc */ +#define DEFEDITOR "ed" /* default editor *should* be $EDITOR */ + +History *hist; /* history cookie */ +EditLine *el; /* editline cookie */ +int displayhist; +static FILE *el_in, *el_out, *el_err; + +static char *fc_replace(const char *, char *, char *); +static int not_fcnumber(const char *); +static int str_to_event(const char *, int); + +/* + * Set history and editing status. Called whenever the status may + * have changed (figures out what to do). + */ +void +histedit(void) +{ + +#define editing (Eflag || Vflag) + + if (iflag) { + if (!hist) { + /* + * turn history on + */ + INTOFF; + hist = history_init(); + INTON; + + if (hist != NULL) + sethistsize(histsizeval()); + else + out2fmt_flush("sh: can't initialize history\n"); + } + if (editing && !el && isatty(0)) { /* && isatty(2) ??? */ + /* + * turn editing on + */ + char *term; + + INTOFF; + if (el_in == NULL) + el_in = fdopen(0, "r"); + if (el_err == NULL) + el_err = fdopen(1, "w"); + if (el_out == NULL) + el_out = fdopen(2, "w"); + if (el_in == NULL || el_err == NULL || el_out == NULL) + goto bad; + term = lookupvar("TERM"); + if (term) + setenv("TERM", term, 1); + else + unsetenv("TERM"); + el = el_init(arg0, el_in, el_out, el_err); + if (el != NULL) { + if (hist) + el_set(el, EL_HIST, history, hist); + el_set(el, EL_PROMPT, getprompt); + el_set(el, EL_ADDFN, "sh-complete", + "Filename completion", + _el_fn_sh_complete); + } else { +bad: + out2fmt_flush("sh: can't initialize editing\n"); + } + INTON; + } else if (!editing && el) { + INTOFF; + el_end(el); + el = NULL; + INTON; + } + if (el) { + if (Vflag) + el_set(el, EL_EDITOR, "vi"); + else if (Eflag) + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_BIND, "^I", "sh-complete", NULL); + el_source(el, NULL); + } + } else { + INTOFF; + if (el) { /* no editing if not interactive */ + el_end(el); + el = NULL; + } + if (hist) { + history_end(hist); + hist = NULL; + } + INTON; + } +} + + +void +sethistsize(const char *hs) +{ + int histsize; + HistEvent he; + + if (hist != NULL) { + if (hs == NULL || !is_number(hs)) + histsize = 100; + else + histsize = atoi(hs); + history(hist, &he, H_SETSIZE, histsize); + history(hist, &he, H_SETUNIQUE, 1); + } +} + +void +setterm(const char *term) +{ + if (rootshell && el != NULL && term != NULL) + el_set(el, EL_TERMINAL, term); +} + +int +histcmd(int argc, char **argv __unused) +{ + int ch; + const char *editor = NULL; + HistEvent he; + int lflg = 0, nflg = 0, rflg = 0, sflg = 0; + int i, retval; + const char *firststr, *laststr; + int first, last, direction; + char *pat = NULL, *repl = NULL; + static int active = 0; + struct jmploc jmploc; + struct jmploc *savehandler; + char editfilestr[PATH_MAX]; + char *volatile editfile; + FILE *efp = NULL; + int oldhistnum; + + if (hist == NULL) + error("history not active"); + + if (argc == 1) + error("missing history argument"); + + while (not_fcnumber(*argptr) && (ch = nextopt("e:lnrs")) != '\0') + switch ((char)ch) { + case 'e': + editor = shoptarg; + break; + case 'l': + lflg = 1; + break; + case 'n': + nflg = 1; + break; + case 'r': + rflg = 1; + break; + case 's': + sflg = 1; + break; + } + + savehandler = handler; + /* + * If executing... + */ + if (lflg == 0 || editor || sflg) { + lflg = 0; /* ignore */ + editfile = NULL; + /* + * Catch interrupts to reset active counter and + * cleanup temp files. + */ + if (setjmp(jmploc.loc)) { + active = 0; + if (editfile) + unlink(editfile); + handler = savehandler; + longjmp(handler->loc, 1); + } + handler = &jmploc; + if (++active > MAXHISTLOOPS) { + active = 0; + displayhist = 0; + error("called recursively too many times"); + } + /* + * Set editor. + */ + if (sflg == 0) { + if (editor == NULL && + (editor = bltinlookup("FCEDIT", 1)) == NULL && + (editor = bltinlookup("EDITOR", 1)) == NULL) + editor = DEFEDITOR; + if (editor[0] == '-' && editor[1] == '\0') { + sflg = 1; /* no edit */ + editor = NULL; + } + } + } + + /* + * If executing, parse [old=new] now + */ + if (lflg == 0 && *argptr != NULL && + ((repl = strchr(*argptr, '=')) != NULL)) { + pat = *argptr; + *repl++ = '\0'; + argptr++; + } + /* + * determine [first] and [last] + */ + if (*argptr == NULL) { + firststr = lflg ? "-16" : "-1"; + laststr = "-1"; + } else if (argptr[1] == NULL) { + firststr = argptr[0]; + laststr = lflg ? "-1" : argptr[0]; + } else if (argptr[2] == NULL) { + firststr = argptr[0]; + laststr = argptr[1]; + } else + error("too many arguments"); + /* + * Turn into event numbers. + */ + first = str_to_event(firststr, 0); + last = str_to_event(laststr, 1); + + if (rflg) { + i = last; + last = first; + first = i; + } + /* + * XXX - this should not depend on the event numbers + * always increasing. Add sequence numbers or offset + * to the history element in next (diskbased) release. + */ + direction = first < last ? H_PREV : H_NEXT; + + /* + * If editing, grab a temp file. + */ + if (editor) { + int fd; + INTOFF; /* easier */ + sprintf(editfilestr, "%s/_shXXXXXX", _PATH_TMP); + if ((fd = mkstemp(editfilestr)) < 0) + error("can't create temporary file %s", editfile); + editfile = editfilestr; + if ((efp = fdopen(fd, "w")) == NULL) { + close(fd); + error("Out of space"); + } + } + + /* + * Loop through selected history events. If listing or executing, + * do it now. Otherwise, put into temp file and call the editor + * after. + * + * The history interface needs rethinking, as the following + * convolutions will demonstrate. + */ + history(hist, &he, H_FIRST); + retval = history(hist, &he, H_NEXT_EVENT, first); + for (;retval != -1; retval = history(hist, &he, direction)) { + if (lflg) { + if (!nflg) + out1fmt("%5d ", he.num); + out1str(he.str); + } else { + const char *s = pat ? + fc_replace(he.str, pat, repl) : he.str; + + if (sflg) { + if (displayhist) { + out2str(s); + flushout(out2); + } + evalstring(s, 0); + if (displayhist && hist) { + /* + * XXX what about recursive and + * relative histnums. + */ + oldhistnum = he.num; + history(hist, &he, H_ENTER, s); + /* + * XXX H_ENTER moves the internal + * cursor, set it back to the current + * entry. + */ + retval = history(hist, &he, + H_NEXT_EVENT, oldhistnum); + } + } else + fputs(s, efp); + } + /* + * At end? (if we were to lose last, we'd sure be + * messed up). + */ + if (he.num == last) + break; + } + if (editor) { + char *editcmd; + + fclose(efp); + editcmd = stalloc(strlen(editor) + strlen(editfile) + 2); + sprintf(editcmd, "%s %s", editor, editfile); + evalstring(editcmd, 0); /* XXX - should use no JC command */ + INTON; + readcmdfile(editfile); /* XXX - should read back - quick tst */ + unlink(editfile); + } + + if (lflg == 0 && active > 0) + --active; + if (displayhist) + displayhist = 0; + handler = savehandler; + return 0; +} + +static char * +fc_replace(const char *s, char *p, char *r) +{ + char *dest; + int plen = strlen(p); + + STARTSTACKSTR(dest); + while (*s) { + if (*s == *p && strncmp(s, p, plen) == 0) { + STPUTS(r, dest); + s += plen; + *p = '\0'; /* so no more matches */ + } else + STPUTC(*s++, dest); + } + STPUTC('\0', dest); + dest = grabstackstr(dest); + + return (dest); +} + +static int +not_fcnumber(const char *s) +{ + if (s == NULL) + return (0); + if (*s == '-') + s++; + return (!is_number(s)); +} + +static int +str_to_event(const char *str, int last) +{ + HistEvent he; + const char *s = str; + int relative = 0; + int i, retval; + + retval = history(hist, &he, H_FIRST); + switch (*s) { + case '-': + relative = 1; + /*FALLTHROUGH*/ + case '+': + s++; + } + if (is_number(s)) { + i = atoi(s); + if (relative) { + while (retval != -1 && i--) { + retval = history(hist, &he, H_NEXT); + } + if (retval == -1) + retval = history(hist, &he, H_LAST); + } else { + retval = history(hist, &he, H_NEXT_EVENT, i); + if (retval == -1) { + /* + * the notion of first and last is + * backwards to that of the history package + */ + retval = history(hist, &he, last ? H_FIRST : H_LAST); + } + } + if (retval == -1) + error("history number %s not found (internal error)", + str); + } else { + /* + * pattern + */ + retval = history(hist, &he, H_PREV_STR, str); + if (retval == -1) + error("history pattern not found: %s", str); + } + return (he.num); +} + +int +bindcmd(int argc, char **argv) +{ + + if (el == NULL) + error("line editing is disabled"); + return (el_parse(el, argc, __DECONST(const char **, argv))); +} + +#else +#include "error.h" + +int +histcmd(int argc, char **argv) +{ + + error("not compiled with history support"); + /*NOTREACHED*/ + return (0); +} + +int +bindcmd(int argc, char **argv) +{ + + error("not compiled with line editing support"); + return (0); +} +#endif diff --git a/external/sh/input.c b/external/sh/input.c new file mode 100644 index 00000000..aeb3590e --- /dev/null +++ b/external/sh/input.c @@ -0,0 +1,523 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)input.c 8.3 (Berkeley) 6/9/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/input.c 271593 2014-09-14 16:46:30Z jilles $"); + +#include <stdio.h> /* defines BUFSIZ */ +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +/* + * This file implements the input routines used by the parser. + */ + +#include "shell.h" +#include "redir.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "options.h" +#include "memalloc.h" +#include "error.h" +#include "alias.h" +#include "parser.h" +#include "myhistedit.h" +#include "trap.h" + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +struct strpush { + struct strpush *prev; /* preceding string on stack */ + const char *prevstring; + int prevnleft; + int prevlleft; + struct alias *ap; /* if push was associated with an alias */ +}; + +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ + +struct parsefile { + struct parsefile *prev; /* preceding file on stack */ + int linno; /* current line */ + int fd; /* file descriptor (or -1 if string) */ + int nleft; /* number of chars left in this line */ + int lleft; /* number of lines left in this buffer */ + const char *nextc; /* next char in buffer */ + char *buf; /* input buffer */ + struct strpush *strpush; /* for pushing strings at this level */ + struct strpush basestrpush; /* so pushing one is fast */ +}; + + +int plinno = 1; /* input line number */ +int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +const char *parsenextc; /* copy of parsefile->nextc */ +static char basebuf[BUFSIZ + 1];/* buffer for top level input file */ +static struct parsefile basepf = { /* top level input file */ + .nextc = basebuf, + .buf = basebuf +}; +static struct parsefile *parsefile = &basepf; /* current input file */ +int whichprompt; /* 1 == PS1, 2 == PS2 */ + +EditLine *el; /* cookie for editline package */ + +static void pushfile(void); +static int preadfd(void); +static void popstring(void); + +void +resetinput(void) +{ + popallfiles(); + parselleft = parsenleft = 0; /* clear input buffer */ +} + + + +/* + * Read a character from the script, returning PEOF on end of file. + * Nul characters in the input are silently discarded. + */ + +int +pgetc(void) +{ + return pgetc_macro(); +} + + +static int +preadfd(void) +{ + int nr; + parsenextc = parsefile->buf; + +retry: +#ifndef NO_HISTORY + if (parsefile->fd == 0 && el) { + static const char *rl_cp; + static int el_len; + + if (rl_cp == NULL) { + el_resize(el); + rl_cp = el_gets(el, &el_len); + } + if (rl_cp == NULL) + nr = el_len == 0 ? 0 : -1; + else { + nr = el_len; + if (nr > BUFSIZ) + nr = BUFSIZ; + memcpy(parsefile->buf, rl_cp, nr); + if (nr != el_len) { + el_len -= nr; + rl_cp += nr; + } else + rl_cp = NULL; + } + } else +#endif + nr = read(parsefile->fd, parsefile->buf, BUFSIZ); + + if (nr <= 0) { + if (nr < 0) { + if (errno == EINTR) + goto retry; + 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) { + out2fmt_flush("sh: turning off NDELAY mode\n"); + goto retry; + } + } + } + } + nr = -1; + } + 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 there is more in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ + +int +preadbuffer(void) +{ + char *p, *q; + int more; + char savec; + + while (parsefile->strpush) { + /* + * Add a space to the end of an alias to ensure that the + * alias remains in use while parsing its last word. + * This avoids alias recursions. + */ + if (parsenleft == -1 && parsefile->strpush->ap != NULL) + return ' '; + popstring(); + if (--parsenleft >= 0) + return (*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flushout(&output); + flushout(&errout); + +again: + if (parselleft <= 0) { + if ((parselleft = preadfd()) == -1) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; + } + } + + q = p = parsefile->buf + (parsenextc - parsefile->buf); + + /* delete nul characters */ + for (more = 1; more;) { + switch (*p) { + case '\0': + p++; /* Skip nul */ + goto check; + + case '\n': + parsenleft = q - parsenextc; + more = 0; /* Stop processing here */ + break; + + default: + break; + } + + *q++ = *p++; +check: + if (--parselleft <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + *q = '\0'; + more = 0; + } + } + + savec = *q; + *q = '\0'; + +#ifndef NO_HISTORY + if (parsefile->fd == 0 && hist && + parsenextc[strspn(parsenextc, " \t\n")] != '\0') { + HistEvent he; + INTOFF; + history(hist, &he, whichprompt == 1 ? H_ENTER : H_ADD, + parsenextc); + INTON; + } +#endif + + if (vflag) { + out2str(parsenextc); + flushout(out2); + } + + *q = savec; + + return *parsenextc++; +} + +/* + * Returns if we are certain we are at EOF. Does not cause any more input + * to be read from the outside world. + */ + +int +preadateof(void) +{ + if (parsenleft > 0) + return 0; + if (parsefile->strpush) + return 0; + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return 1; + return 0; +} + +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ + +void +pungetc(void) +{ + parsenleft++; + parsenextc--; +} + +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ +void +pushstring(const char *s, int len, struct alias *ap) +{ + struct strpush *sp; + + INTOFF; +/*out2fmt_flush("*** 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; + sp->prevlleft = parselleft; + sp->ap = ap; + if (ap) + ap->flag |= ALIASINUSE; + parsenextc = s; + parsenleft = len; + INTON; +} + +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INTOFF; + if (sp->ap) { + if (parsenextc != sp->ap->val && + (parsenextc[-1] == ' ' || parsenextc[-1] == '\t')) + forcealias(); + sp->ap->flag &= ~ALIASINUSE; + } + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; + parselleft = sp->prevlleft; +/*out2fmt_flush("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + ckfree(sp); + INTON; +} + +/* + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. + */ + +void +setinputfile(const char *fname, int push) +{ + int fd; + int fd2; + + INTOFF; + if ((fd = open(fname, O_RDONLY | O_CLOEXEC)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + if (fd < 10) { + fd2 = fcntl(fd, F_DUPFD_CLOEXEC, 10); + close(fd); + if (fd2 < 0) + error("Out of file descriptors"); + fd = fd2; + } + setinputfd(fd, push); + INTON; +} + + +/* + * Like setinputfile, but takes an open file descriptor (which should have + * its FD_CLOEXEC flag already set). Call this with interrupts off. + */ + +void +setinputfd(int fd, int push) +{ + if (push) { + pushfile(); + parsefile->buf = ckmalloc(BUFSIZ + 1); + } + if (parsefile->fd > 0) + close(parsefile->fd); + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(BUFSIZ + 1); + parselleft = parsenleft = 0; + plinno = 1; +} + + +/* + * Like setinputfile, but takes input from a string. + */ + +void +setinputstring(const char *string, int push) +{ + INTOFF; + if (push) + pushfile(); + parsenextc = string; + parselleft = parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INTON; +} + + + +/* + * 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 = (struct parsefile *)ckmalloc(sizeof (struct parsefile)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; +} + + +void +popfile(void) +{ + struct parsefile *pf = parsefile; + + INTOFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + ckfree(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + ckfree(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INTON; +} + + +/* + * Return current file (to go back to it later using popfilesupto()). + */ + +struct parsefile * +getcurrentfile(void) +{ + return parsefile; +} + + +/* + * Pop files until the given file is on top again. Useful for regular + * builtins that read shell commands from files or strings. + * If the given file is not an active file, an error is raised. + */ + +void +popfilesupto(struct parsefile *file) +{ + while (parsefile != file && parsefile != &basepf) + popfile(); + if (parsefile != file) + error("popfilesupto() misused"); +} + +/* + * Return to top level. + */ + +void +popallfiles(void) +{ + while (parsefile != &basepf) + popfile(); +} + + + +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ + +void +closescript(void) +{ + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; + } +} diff --git a/external/sh/input.h b/external/sh/input.h new file mode 100644 index 00000000..1c3ece96 --- /dev/null +++ b/external/sh/input.h @@ -0,0 +1,65 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)input.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/input.h 271593 2014-09-14 16:46:30Z jilles $ + */ + +/* PEOF (the end of file marker) is defined in syntax.h */ + +/* + * The input line number. Input.c just defines this variable, and saves + * and restores it when files are pushed and popped. The user of this + * package must set its value. + */ +extern int plinno; +extern int parsenleft; /* number of characters left in input buffer */ +extern const char *parsenextc; /* next character in input buffer */ + +struct alias; +struct parsefile; + +void resetinput(void); +int pgetc(void); +int preadbuffer(void); +int preadateof(void); +void pungetc(void); +void pushstring(const char *, int, struct alias *); +void setinputfile(const char *, int); +void setinputfd(int, int); +void setinputstring(const char *, int); +void popfile(void); +struct parsefile *getcurrentfile(void); +void popfilesupto(struct parsefile *); +void popallfiles(void); +void closescript(void); + +#define pgetc_macro() (--parsenleft >= 0? *parsenextc++ : preadbuffer()) diff --git a/external/sh/jobs.c b/external/sh/jobs.c new file mode 100644 index 00000000..fa796fad --- /dev/null +++ b/external/sh/jobs.c @@ -0,0 +1,1515 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/jobs.c 279508 2015-03-01 22:32:23Z jilles $"); + +#include <sys/ioctl.h> +#include <sys/param.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <errno.h> +#include <fcntl.h> +#include <paths.h> +#include <signal.h> +#include <stddef.h> +#include <stdlib.h> +#include <unistd.h> + +#include "shell.h" +#if JOBS +#include <termios.h> +#undef CEOF /* syntax.h redefines this */ +#endif +#include "redir.h" +#include "exec.h" +#include "show.h" +#include "main.h" +#include "parser.h" +#include "nodes.h" +#include "jobs.h" +#include "options.h" +#include "trap.h" +#include "syntax.h" +#include "input.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "var.h" +#include "builtins.h" + + +static struct job *jobtab; /* array of jobs */ +static int njobs; /* size of array */ +static pid_t backgndpid = -1; /* pid of last background process */ +static struct job *bgjob = NULL; /* last background process */ +#if JOBS +static struct job *jobmru; /* most recently used job list */ +static pid_t initialpgrp; /* pgrp of shell on invocation */ +#endif +static int ttyfd = -1; + +/* mode flags for dowait */ +#define DOWAIT_BLOCK 0x1 /* wait until a child exits */ +#define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on signal */ +#define DOWAIT_SIG_TRAP 0x4 /* if DOWAIT_SIG, abort on trapped signal only */ + +#if JOBS +static void restartjob(struct job *); +#endif +static void freejob(struct job *); +static int waitcmdloop(struct job *); +static struct job *getjob_nonotfound(const char *); +static struct job *getjob(const char *); +pid_t killjob(const char *, int); +static pid_t dowait(int, struct job *); +static void checkzombies(void); +static void cmdtxt(union node *); +static void cmdputs(const char *); +#if JOBS +static void setcurjob(struct job *); +static void deljob(struct job *); +static struct job *getcurjob(struct job *); +#endif +static void printjobcmd(struct job *); +static void showjob(struct job *, int); + + +/* + * Turn job control on and off. + */ + +static int jobctl; + +#if JOBS +static void +jobctl_notty(void) +{ + if (ttyfd >= 0) { + close(ttyfd); + ttyfd = -1; + } + if (!iflag) { + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + jobctl = 1; + return; + } + out2fmt_flush("sh: can't access tty; job control turned off\n"); + mflag = 0; +} + +void +setjobctl(int on) +{ + int i; + + if (on == jobctl || rootshell == 0) + return; + if (on) { + if (ttyfd != -1) + close(ttyfd); + if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { + i = 0; + while (i <= 2 && !isatty(i)) + i++; + if (i > 2 || + (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + } + if (ttyfd < 10) { + /* + * Keep our TTY file descriptor out of the way of + * the user's redirections. + */ + if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { + jobctl_notty(); + return; + } + close(ttyfd); + ttyfd = i; + } + do { /* while we are in the background */ + initialpgrp = tcgetpgrp(ttyfd); + if (initialpgrp < 0) { + jobctl_notty(); + return; + } + if (initialpgrp != getpgrp()) { + if (!iflag) { + initialpgrp = -1; + jobctl_notty(); + return; + } + kill(0, SIGTTIN); + continue; + } + } while (0); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + setpgid(0, rootpid); + tcsetpgrp(ttyfd, rootpid); + } else { /* turning job control off */ + setpgid(0, initialpgrp); + if (ttyfd >= 0) { + tcsetpgrp(ttyfd, initialpgrp); + close(ttyfd); + ttyfd = -1; + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + } + jobctl = on; +} +#endif + + +#if JOBS +int +fgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + pid_t pgrp; + int status; + + nextopt(""); + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + printjobcmd(jp); + flushout(&output); + pgrp = jp->ps[0].pid; + if (ttyfd >= 0) + tcsetpgrp(ttyfd, pgrp); + restartjob(jp); + jp->foreground = 1; + INTOFF; + status = waitforjob(jp, (int *)NULL); + INTON; + return status; +} + + +int +bgcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + + nextopt(""); + do { + jp = getjob(*argptr); + if (jp->jobctl == 0) + error("job not created under job control"); + if (jp->state == JOBDONE) + continue; + restartjob(jp); + jp->foreground = 0; + out1fmt("[%td] ", jp - jobtab + 1); + printjobcmd(jp); + } while (*argptr != NULL && *++argptr != NULL); + return 0; +} + + +static void +restartjob(struct job *jp) +{ + struct procstat *ps; + int i; + + if (jp->state == JOBDONE) + return; + setcurjob(jp); + INTOFF; + kill(-jp->ps[0].pid, SIGCONT); + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; + jp->state = 0; + } + } + INTON; +} +#endif + + +int +jobscmd(int argc __unused, char *argv[] __unused) +{ + char *id; + int ch, mode; + + mode = SHOWJOBS_DEFAULT; + while ((ch = nextopt("lps")) != '\0') { + switch (ch) { + case 'l': + mode = SHOWJOBS_VERBOSE; + break; + case 'p': + mode = SHOWJOBS_PGIDS; + break; + case 's': + mode = SHOWJOBS_PIDS; + break; + } + } + + if (*argptr == NULL) + showjobs(0, mode); + else + while ((id = *argptr++) != NULL) + showjob(getjob(id), mode); + + return (0); +} + +static void +printjobcmd(struct job *jp) +{ + struct procstat *ps; + int i; + + for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { + out1str(ps->cmd); + if (i > 0) + out1str(" | "); + } + out1c('\n'); +} + +static void +showjob(struct job *jp, int mode) +{ + char s[64]; + char statestr[64]; + const char *sigstr; + struct procstat *ps; + struct job *j; + int col, curr, i, jobno, prev, procno; + char c; + + procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; + jobno = jp - jobtab + 1; + curr = prev = 0; +#if JOBS + if ((j = getcurjob(NULL)) != NULL) { + curr = j - jobtab + 1; + if ((j = getcurjob(j)) != NULL) + prev = j - jobtab + 1; + } +#endif + ps = jp->ps + jp->nprocs - 1; + if (jp->state == 0) { + strcpy(statestr, "Running"); +#if JOBS + } else if (jp->state == JOBSTOPPED) { + while (!WIFSTOPPED(ps->status) && ps > jp->ps) + ps--; + if (WIFSTOPPED(ps->status)) + i = WSTOPSIG(ps->status); + else + i = -1; + sigstr = strsignal(i); + if (sigstr != NULL) + strcpy(statestr, sigstr); + else + strcpy(statestr, "Suspended"); +#endif + } else if (WIFEXITED(ps->status)) { + if (WEXITSTATUS(ps->status) == 0) + strcpy(statestr, "Done"); + else + fmtstr(statestr, 64, "Done(%d)", + WEXITSTATUS(ps->status)); + } else { + i = WTERMSIG(ps->status); + sigstr = strsignal(i); + if (sigstr != NULL) + strcpy(statestr, sigstr); + else + strcpy(statestr, "Unknown signal"); + if (WCOREDUMP(ps->status)) + strcat(statestr, " (core dumped)"); + } + + for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ + if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { + out1fmt("%d\n", (int)ps->pid); + continue; + } + if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) + continue; + if (jobno == curr && ps == jp->ps) + c = '+'; + else if (jobno == prev && ps == jp->ps) + c = '-'; + else + c = ' '; + if (ps == jp->ps) + fmtstr(s, 64, "[%d] %c ", jobno, c); + else + fmtstr(s, 64, " %c ", c); + out1str(s); + col = strlen(s); + if (mode == SHOWJOBS_VERBOSE) { + fmtstr(s, 64, "%d ", (int)ps->pid); + out1str(s); + col += strlen(s); + } + if (ps == jp->ps) { + out1str(statestr); + col += strlen(statestr); + } + do { + out1c(' '); + col++; + } while (col < 30); + if (mode == SHOWJOBS_VERBOSE) { + out1str(ps->cmd); + out1c('\n'); + } else + printjobcmd(jp); + } +} + +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + * + * If the shell is interrupted in the process of creating a job, the + * result may be a job structure containing zero processes. Such structures + * will be freed here. + */ + +void +showjobs(int change, int mode) +{ + int jobno; + struct job *jp; + + TRACE(("showjobs(%d) called\n", change)); + checkzombies(); + for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { + if (! jp->used) + continue; + if (jp->nprocs == 0) { + freejob(jp); + continue; + } + if (change && ! jp->changed) + continue; + showjob(jp, mode); + if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { + jp->changed = 0; + /* Hack: discard jobs for which $! has not been + * referenced in interactive mode when they terminate. + */ + if (jp->state == JOBDONE && !jp->remembered && + (iflag || jp != bgjob)) { + freejob(jp); + } + } + } +} + + +/* + * Mark a job structure as unused. + */ + +static void +freejob(struct job *jp) +{ + struct procstat *ps; + int i; + + INTOFF; + if (bgjob == jp) + bgjob = NULL; + for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { + if (ps->cmd != nullstr) + ckfree(ps->cmd); + } + if (jp->ps != &jp->ps0) + ckfree(jp->ps); + jp->used = 0; +#if JOBS + deljob(jp); +#endif + INTON; +} + + + +int +waitcmd(int argc __unused, char **argv __unused) +{ + struct job *job; + int retval; + + nextopt(""); + if (*argptr == NULL) + return (waitcmdloop(NULL)); + + do { + job = getjob_nonotfound(*argptr); + if (job == NULL) + retval = 127; + else + retval = waitcmdloop(job); + argptr++; + } while (*argptr != NULL); + + return (retval); +} + +static int +waitcmdloop(struct job *job) +{ + int status, retval, sig; + struct job *jp; + + /* + * Loop until a process is terminated or stopped, or a SIGINT is + * received. + */ + + do { + if (job != NULL) { + if (job->state == JOBDONE) { + status = job->ps[job->nprocs - 1].status; + if (WIFEXITED(status)) + retval = WEXITSTATUS(status); + else + retval = WTERMSIG(status) + 128; + if (! iflag || ! job->changed) + freejob(job); + else { + job->remembered = 0; + if (job == bgjob) + bgjob = NULL; + } + return retval; + } + } else { + for (jp = jobtab ; jp < jobtab + njobs; jp++) + if (jp->used && jp->state == JOBDONE) { + if (! iflag || ! jp->changed) + freejob(jp); + else { + jp->remembered = 0; + if (jp == bgjob) + bgjob = NULL; + } + } + for (jp = jobtab ; ; jp++) { + if (jp >= jobtab + njobs) { /* no running procs */ + return 0; + } + if (jp->used && jp->state == 0) + break; + } + } + } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); + + sig = pendingsig_waitcmd; + pendingsig_waitcmd = 0; + return sig + 128; +} + + + +int +jobidcmd(int argc __unused, char **argv __unused) +{ + struct job *jp; + int i; + + nextopt(""); + jp = getjob(*argptr); + for (i = 0 ; i < jp->nprocs ; ) { + out1fmt("%d", (int)jp->ps[i].pid); + out1c(++i < jp->nprocs? ' ' : '\n'); + } + return 0; +} + + + +/* + * Convert a job name to a job structure. + */ + +static struct job * +getjob_nonotfound(const char *name) +{ + int jobno; + struct job *found, *jp; + size_t namelen; + pid_t pid; + int i; + + if (name == NULL) { +#if JOBS + name = "%+"; +#else + error("No current job"); +#endif + } + if (name[0] == '%') { + if (is_digit(name[1])) { + jobno = number(name + 1); + if (jobno > 0 && jobno <= njobs + && jobtab[jobno - 1].used != 0) + return &jobtab[jobno - 1]; +#if JOBS + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); + } else if (name[1] == '-' && name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL || + (jp = getcurjob(jp)) == NULL) + error("No previous job"); + return (jp); +#endif + } else if (name[1] == '?') { + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strstr(jp->ps[0].cmd, name + 2) != NULL) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found != NULL) + return (found); + } else { + namelen = strlen(name); + found = NULL; + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && strncmp(jp->ps[0].cmd, name + 1, + namelen - 1) == 0) { + if (found) + error("%s: ambiguous", name); + found = jp; + } + } + if (found) + return found; + } + } else if (is_number(name)) { + pid = (pid_t)number(name); + for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { + if (jp->used && jp->nprocs > 0 + && jp->ps[jp->nprocs - 1].pid == pid) + return jp; + } + } + return NULL; +} + + +static struct job * +getjob(const char *name) +{ + struct job *jp; + + jp = getjob_nonotfound(name); + if (jp == NULL) + error("No such job: %s", name); + return (jp); +} + + +int +killjob(const char *name, int sig) +{ + struct job *jp; + int i, ret; + + jp = getjob(name); + if (jp->state == JOBDONE) + return 0; + if (jp->jobctl) + return kill(-jp->ps[0].pid, sig); + ret = -1; + errno = ESRCH; + for (i = 0; i < jp->nprocs; i++) + if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { + if (kill(jp->ps[i].pid, sig) == 0) + ret = 0; + } else + ret = 0; + return ret; +} + +/* + * Return a new job structure, + */ + +struct job * +makejob(union node *node __unused, int nprocs) +{ + int i; + struct job *jp; + + for (i = njobs, jp = jobtab ; ; jp++) { + if (--i < 0) { + INTOFF; + if (njobs == 0) { + jobtab = ckmalloc(4 * sizeof jobtab[0]); +#if JOBS + jobmru = NULL; +#endif + } else { + jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); + memcpy(jp, jobtab, njobs * sizeof jp[0]); +#if JOBS + /* Relocate `next' pointers and list head */ + if (jobmru != NULL) + jobmru = &jp[jobmru - jobtab]; + for (i = 0; i < njobs; i++) + if (jp[i].next != NULL) + jp[i].next = &jp[jp[i].next - + jobtab]; +#endif + if (bgjob != NULL) + bgjob = &jp[bgjob - jobtab]; + /* Relocate `ps' pointers */ + for (i = 0; i < njobs; i++) + if (jp[i].ps == &jobtab[i].ps0) + jp[i].ps = &jp[i].ps0; + ckfree(jobtab); + jobtab = jp; + } + jp = jobtab + njobs; + for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) + ; + INTON; + break; + } + if (jp->used == 0) + break; + } + INTOFF; + jp->state = 0; + jp->used = 1; + jp->changed = 0; + jp->nprocs = 0; + jp->foreground = 0; + jp->remembered = 0; +#if JOBS + jp->jobctl = jobctl; + jp->next = NULL; +#endif + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); + } else { + jp->ps = &jp->ps0; + } + INTON; + TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, + jp - jobtab + 1)); + return jp; +} + +#if JOBS +static void +setcurjob(struct job *cj) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == cj) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + jp->next = jobmru; + jobmru = cj; + return; + } + } + cj->next = jobmru; + jobmru = cj; +} + +static void +deljob(struct job *j) +{ + struct job *jp, *prev; + + for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { + if (jp == j) { + if (prev != NULL) + prev->next = jp->next; + else + jobmru = jp->next; + return; + } + } +} + +/* + * Return the most recently used job that isn't `nj', and preferably one + * that is stopped. + */ +static struct job * +getcurjob(struct job *nj) +{ + struct job *jp; + + /* Try to find a stopped one.. */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj && jp->state == JOBSTOPPED) + return (jp); + /* Otherwise the most recently used job that isn't `nj' */ + for (jp = jobmru; jp != NULL; jp = jp->next) + if (jp->used && jp != nj) + return (jp); + + return (NULL); +} + +#endif + +/* + * Fork of 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). + */ + +pid_t +forkshell(struct job *jp, union node *n, int mode) +{ + pid_t pid; + pid_t pgrp; + + TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, + mode)); + INTOFF; + if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) + checkzombies(); + flushall(); + pid = fork(); + if (pid == -1) { + TRACE(("Fork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + struct job *p; + int wasroot; + int i; + + TRACE(("Child shell %d\n", (int)getpid())); + wasroot = rootshell; + rootshell = 0; + handler = &main_handler; + closescript(); + INTON; + forcelocal = 0; + clear_traps(); +#if JOBS + jobctl = 0; /* do job control only in root shell */ + if (wasroot && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + if (setpgid(0, pgrp) == 0 && mode == FORK_FG && + ttyfd >= 0) { + /*** this causes superfluous TIOCSPGRPS ***/ + if (tcsetpgrp(ttyfd, pgrp) < 0) + error("tcsetpgrp failed, errno=%d", errno); + } + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#else + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if ((jp == NULL || jp->nprocs == 0) && + ! fd0_redirected_p ()) { + close(0); + if (open(_PATH_DEVNULL, O_RDONLY) != 0) + error("cannot open %s: %s", + _PATH_DEVNULL, strerror(errno)); + } + } +#endif + INTOFF; + for (i = njobs, p = jobtab ; --i >= 0 ; p++) + if (p->used) + freejob(p); + INTON; + if (wasroot && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + } + return pid; + } + if (rootshell && mode != FORK_NOJOB && mflag) { + if (jp == NULL || jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + setpgid(pid, pgrp); + } + if (mode == FORK_BG) { + if (bgjob != NULL && bgjob->state == JOBDONE && + !bgjob->remembered && !iflag) + freejob(bgjob); + backgndpid = pid; /* set $! */ + bgjob = jp; + } + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + if (iflag && rootshell && n) + ps->cmd = commandtext(n); + jp->foreground = mode == FORK_FG; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +pid_t +vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) +{ + pid_t pid; + struct jmploc jmploc; + struct jmploc *savehandler; + + TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], + (void *)pip)); + INTOFF; + flushall(); + savehandler = handler; + pid = vfork(); + if (pid == -1) { + TRACE(("Vfork failed, errno=%d\n", errno)); + INTON; + error("Cannot fork: %s", strerror(errno)); + } + if (pid == 0) { + TRACE(("Child shell %d\n", (int)getpid())); + if (setjmp(jmploc.loc)) + _exit(exception == EXEXEC ? exerrno : 2); + if (pip != NULL) { + close(pip[0]); + if (pip[1] != 1) { + dup2(pip[1], 1); + close(pip[1]); + } + } + handler = &jmploc; + shellexec(argv, envp, path, idx); + } + handler = savehandler; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; + jp->foreground = 1; +#if JOBS + setcurjob(jp); +#endif + } + INTON; + TRACE(("In parent shell: child = %d\n", (int)pid)); + return pid; +} + + +/* + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * 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. + */ + +int +waitforjob(struct job *jp, int *origstatus) +{ +#if JOBS + int propagate_int = jp->jobctl && jp->foreground; +#endif + int status; + int st; + + INTOFF; + TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); + while (jp->state == 0) + if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | + DOWAIT_SIG_TRAP : 0), jp) == -1) + dotrap(); +#if JOBS + if (jp->jobctl) { + if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) + error("tcsetpgrp failed, errno=%d\n", errno); + } + if (jp->state == JOBSTOPPED) + setcurjob(jp); +#endif + status = jp->ps[jp->nprocs - 1].status; + if (origstatus != NULL) + *origstatus = status; + /* convert to 8 bits */ + if (WIFEXITED(status)) + st = WEXITSTATUS(status); +#if JOBS + else if (WIFSTOPPED(status)) + st = WSTOPSIG(status) + 128; +#endif + else + st = WTERMSIG(status) + 128; + if (! JOBS || jp->state == JOBDONE) + freejob(jp); + if (int_pending()) { + if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) + CLEAR_PENDING_INT; + } +#if JOBS + else if (rootshell && iflag && propagate_int && + WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) + kill(getpid(), SIGINT); +#endif + INTON; + return st; +} + + +static void +dummy_handler(int sig __unused) +{ +} + +/* + * Wait for a process to terminate. + */ + +static pid_t +dowait(int mode, struct job *job) +{ + struct sigaction sa, osa; + sigset_t mask, omask; + pid_t pid; + int status; + struct procstat *sp; + struct job *jp; + struct job *thisjob; + const char *sigstr; + int done; + int stopped; + int sig; + int coredump; + int wflags; + int restore_sigchld; + + TRACE(("dowait(%d, %p) called\n", mode, job)); + restore_sigchld = 0; + if ((mode & DOWAIT_SIG) != 0) { + sigfillset(&mask); + sigprocmask(SIG_BLOCK, &mask, &omask); + INTOFF; + if (!issigchldtrapped()) { + restore_sigchld = 1; + sa.sa_handler = dummy_handler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGCHLD, &sa, &osa); + } + } + do { +#if JOBS + if (iflag) + wflags = WUNTRACED | WCONTINUED; + else +#endif + wflags = 0; + if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) + wflags |= WNOHANG; + pid = wait3(&status, wflags, (struct rusage *)NULL); + TRACE(("wait returns %d, status=%d\n", (int)pid, status)); + if (pid == 0 && (mode & DOWAIT_SIG) != 0) { + pid = -1; + if (((mode & DOWAIT_SIG_TRAP) != 0 ? + pendingsig : pendingsig_waitcmd) != 0) { + errno = EINTR; + break; + } + sigsuspend(&omask); + if (int_pending()) + break; + } + } while (pid == -1 && errno == EINTR); + if (pid == -1 && errno == ECHILD && job != NULL) + job->state = JOBDONE; + if ((mode & DOWAIT_SIG) != 0) { + if (restore_sigchld) + sigaction(SIGCHLD, &osa, NULL); + sigprocmask(SIG_SETMASK, &omask, NULL); + INTON; + } + if (pid <= 0) + return pid; + INTOFF; + thisjob = NULL; + for (jp = jobtab ; jp < jobtab + njobs ; jp++) { + if (jp->used && jp->nprocs > 0) { + done = 1; + stopped = 1; + for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { + if (sp->pid == -1) + continue; + if (sp->pid == pid && (sp->status == -1 || + WIFSTOPPED(sp->status))) { + TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", + (int)pid, sp->status, + status)); + if (WIFCONTINUED(status)) { + sp->status = -1; + jp->state = 0; + } else + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + stopped = 0; + else if (WIFSTOPPED(sp->status)) + done = 0; + } + if (stopped) { /* stopped or done */ + int state = done? JOBDONE : JOBSTOPPED; + if (jp->state != state) { + TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); + jp->state = state; + if (jp != job) { + if (done && !jp->remembered && + !iflag && jp != bgjob) + freejob(jp); +#if JOBS + else if (done) + deljob(jp); +#endif + } + } + } + } + } + INTON; + if (!thisjob || thisjob->state == 0) + ; + else if ((!rootshell || !iflag || thisjob == job) && + thisjob->foreground && thisjob->state != JOBSTOPPED) { + sig = 0; + coredump = 0; + for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) + if (WIFSIGNALED(sp->status)) { + sig = WTERMSIG(sp->status); + coredump = WCOREDUMP(sp->status); + } + if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { + sigstr = strsignal(sig); + if (sigstr != NULL) + out2str(sigstr); + else + out2str("Unknown signal"); + if (coredump) + out2str(" (core dumped)"); + out2c('\n'); + flushout(out2); + } + } else { + TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); + thisjob->changed = 1; + } + return pid; +} + + + +/* + * return 1 if there are stopped jobs, otherwise 0 + */ +int job_warning = 0; +int +stoppedjobs(void) +{ + int jobno; + struct job *jp; + + if (job_warning) + return (0); + for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { + if (jp->used == 0) + continue; + if (jp->state == JOBSTOPPED) { + out2fmt_flush("You have stopped jobs.\n"); + job_warning = 2; + return (1); + } + } + + return (0); +} + + +static void +checkzombies(void) +{ + while (njobs > 0 && dowait(0, NULL) > 0) + ; +} + + +int +backgndpidset(void) +{ + return backgndpid != -1; +} + + +pid_t +backgndpidval(void) +{ + if (bgjob != NULL && !forcelocal) + bgjob->remembered = 1; + return backgndpid; +} + +/* + * Return a string identifying a command (to be printed by the + * jobs command. + */ + +static char *cmdnextc; +static int cmdnleft; +#define MAXCMDTEXT 200 + +char * +commandtext(union node *n) +{ + char *name; + + cmdnextc = name = ckmalloc(MAXCMDTEXT); + cmdnleft = MAXCMDTEXT - 4; + cmdtxt(n); + *cmdnextc = '\0'; + return name; +} + + +static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void +cmdtxt(union node *n) +{ + union node *np; + struct nodelist *lp; + + if (n == NULL) + return; + switch (n->type) { + case NSEMI: + cmdtxt(n->nbinary.ch1); + cmdputs("; "); + cmdtxt(n->nbinary.ch2); + break; + case NAND: + cmdtxt(n->nbinary.ch1); + cmdputs(" && "); + cmdtxt(n->nbinary.ch2); + break; + case NOR: + cmdtxt(n->nbinary.ch1); + cmdputs(" || "); + cmdtxt(n->nbinary.ch2); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + cmdtxt(lp->n); + if (lp->next) + cmdputs(" | "); + } + break; + case NSUBSHELL: + cmdputs("("); + cmdtxt(n->nredir.n); + cmdputs(")"); + break; + case NREDIR: + case NBACKGND: + cmdtxt(n->nredir.n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + cmdtxt(n->nif.ifpart); + cmdputs("..."); + break; + case NWHILE: + cmdputs("while "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NUNTIL: + cmdputs("until "); + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in ..."); + break; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in ..."); + break; + case NDEFUN: + cmdputs(n->narg.text); + cmdputs("() ..."); + break; + case NNOT: + cmdputs("! "); + cmdtxt(n->nnot.com); + break; + case NCMD: + for (np = n->ncmd.args ; np ; np = np->narg.next) { + cmdtxt(np); + if (np->narg.next) + cmdputs(" "); + } + for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { + cmdputs(" "); + cmdtxt(np); + } + break; + case NARG: + cmdputs(n->narg.text); + break; + case NTO: + cmdtxtredir(n, ">", 1); + break; + case NAPPEND: + cmdtxtredir(n, ">>", 1); + break; + case NTOFD: + cmdtxtredir(n, ">&", 1); + break; + case NCLOBBER: + cmdtxtredir(n, ">|", 1); + break; + case NFROM: + cmdtxtredir(n, "<", 0); + break; + case NFROMTO: + cmdtxtredir(n, "<>", 0); + break; + case NFROMFD: + cmdtxtredir(n, "<&", 0); + break; + case NHERE: + case NXHERE: + cmdputs("<<..."); + break; + default: + cmdputs("???"); + break; + } +} + + + +static void +cmdputs(const char *s) +{ + const char *p; + char *q; + char c; + int subtype = 0; + + if (cmdnleft <= 0) + return; + p = s; + q = cmdnextc; + while ((c = *p++) != '\0') { + if (c == CTLESC) + *q++ = *p++; + else if (c == CTLVAR) { + *q++ = '$'; + if (--cmdnleft > 0) + *q++ = '{'; + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) + *q++ = '#'; + } else if (c == '=' && subtype != 0) { + *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; + if (*q) + q++; + else + cmdnleft++; + if (((subtype & VSTYPE) == VSTRIMLEFTMAX || + (subtype & VSTYPE) == VSTRIMRIGHTMAX) && + --cmdnleft > 0) + *q = q[-1], q++; + subtype = 0; + } else if (c == CTLENDVAR) { + *q++ = '}'; + } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { + cmdnleft -= 5; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + *q++ = ')'; + } + } else if (c == CTLARI) { + cmdnleft -= 2; + if (cmdnleft > 0) { + *q++ = '$'; + *q++ = '('; + *q++ = '('; + } + p++; + } else if (c == CTLENDARI) { + if (--cmdnleft > 0) { + *q++ = ')'; + *q++ = ')'; + } + } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) + cmdnleft++; /* ignore */ + else + *q++ = c; + if (--cmdnleft <= 0) { + *q++ = '.'; + *q++ = '.'; + *q++ = '.'; + break; + } + } + cmdnextc = q; +} diff --git a/external/sh/jobs.h b/external/sh/jobs.h new file mode 100644 index 00000000..f9978ded --- /dev/null +++ b/external/sh/jobs.h @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)jobs.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/jobs.h 255157 2013-09-02 21:57:46Z jilles $ + */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +#include <signal.h> /* for sig_atomic_t */ + +/* + * 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; /* status flags (defined above) */ + char *cmd; /* text of command being run */ +}; + + +/* states */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ + + +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ + short nprocs; /* number of processes */ + pid_t pgrp; /* process group of this job */ + char state; /* true if job is finished */ + char used; /* true if this entry is in used */ + char changed; /* true if status has changed */ + char foreground; /* true if running in the foreground */ + char remembered; /* true if $! referenced */ +#if JOBS + char jobctl; /* job running under job control */ + struct job *next; /* job used after this one */ +#endif +}; + +enum { + SHOWJOBS_DEFAULT, /* job number, status, command */ + SHOWJOBS_VERBOSE, /* job number, PID, status, command */ + SHOWJOBS_PIDS, /* PID only */ + SHOWJOBS_PGIDS /* PID of the group leader only */ +}; + +extern int job_warning; /* user was warned about stopped jobs */ + +void setjobctl(int); +void showjobs(int, int); +struct job *makejob(union node *, int); +pid_t forkshell(struct job *, union node *, int); +pid_t vforkexecshell(struct job *, char **, char **, const char *, int, int []); +int waitforjob(struct job *, int *); +int stoppedjobs(void); +int backgndpidset(void); +pid_t backgndpidval(void); +char *commandtext(union node *); + +#if ! JOBS +#define setjobctl(on) /* do nothing */ +#endif diff --git a/external/sh/kill.c b/external/sh/kill.c new file mode 100644 index 00000000..9f2470f1 --- /dev/null +++ b/external/sh/kill.c @@ -0,0 +1,209 @@ +/*- + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /bin/kill and + * as a builtin for /bin/sh (#define SHELL). + */ + +#if 0 +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1988, 1993, 1994\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +static char sccsid[] = "@(#)kill.c 8.4 (Berkeley) 4/28/95"; +#endif /* not lint */ +#endif +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/kill/kill.c 279503 2015-03-01 21:46:55Z jilles $"); + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef SHELL +#define main killcmd +#include "bltin/bltin.h" +#endif + +static void nosig(const char *); +static void printsignals(FILE *); +static int signame_to_signum(const char *); +static void usage(void); + +int +main(int argc, char *argv[]) +{ + int errors, numsig, pid, ret; + char *ep; + + if (argc < 2) + usage(); + + numsig = SIGTERM; + + argc--, argv++; + if (!strcmp(*argv, "-l")) { + argc--, argv++; + if (argc > 1) + usage(); + if (argc == 1) { + if (!isdigit(**argv)) + usage(); + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig >= 128) + numsig -= 128; + if (numsig <= 0 || numsig >= sys_nsig) + nosig(*argv); + printf("%s\n", sys_signame[numsig]); + return (0); + } + printsignals(stdout); + return (0); + } + + if (!strcmp(*argv, "-s")) { + argc--, argv++; + if (argc < 1) { + warnx("option requires an argument -- s"); + usage(); + } + if (strcmp(*argv, "0")) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else + numsig = 0; + argc--, argv++; + } else if (**argv == '-' && *(*argv + 1) != '-') { + ++*argv; + if (isalpha(**argv)) { + if ((numsig = signame_to_signum(*argv)) < 0) + nosig(*argv); + } else if (isdigit(**argv)) { + numsig = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal signal number: %s", *argv); + if (numsig < 0) + nosig(*argv); + } else + nosig(*argv); + argc--, argv++; + } + + if (argc > 0 && strncmp(*argv, "--", 2) == 0) + argc--, argv++; + + if (argc == 0) + usage(); + + for (errors = 0; argc; argc--, argv++) { +#ifdef SHELL + if (**argv == '%') + ret = killjob(*argv, numsig); + else +#endif + { + pid = strtol(*argv, &ep, 10); + if (!**argv || *ep) + errx(2, "illegal process id: %s", *argv); + ret = kill(pid, numsig); + } + if (ret == -1) { + warn("%s", *argv); + errors = 1; + } + } + + return (errors); +} + +static int +signame_to_signum(const char *sig) +{ + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < sys_nsig; n++) { + if (!strcasecmp(sys_signame[n], sig)) + return (n); + } + return (-1); +} + +static void +nosig(const char *name) +{ + + warnx("unknown signal %s; valid signals:", name); + printsignals(stderr); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} + +static void +printsignals(FILE *fp) +{ + int n; + + for (n = 1; n < sys_nsig; n++) { + (void)fprintf(fp, "%s", sys_signame[n]); + if (n == (sys_nsig / 2) || n == (sys_nsig - 1)) + (void)fprintf(fp, "\n"); + else + (void)fprintf(fp, " "); + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "%s\n%s\n%s\n%s\n", + "usage: kill [-s signal_name] pid ...", + " kill -l [exit_status]", + " kill -signal_name pid ...", + " kill -signal_number pid ..."); +#ifdef SHELL + error(NULL); +#else + exit(2); +#endif +} diff --git a/external/sh/mail.c b/external/sh/mail.c new file mode 100644 index 00000000..b6de32b6 --- /dev/null +++ b/external/sh/mail.c @@ -0,0 +1,117 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mail.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/mail.c 278820 2015-02-15 21:47:43Z jilles $"); + +/* + * Routines to check for mail. (Perhaps make part of main.c?) + */ + +#include "shell.h" +#include "exec.h" /* defines padvance() */ +#include "mail.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include <sys/types.h> +#include <sys/stat.h> +#include <stdlib.h> + + +#define MAXMBOXES 10 + + +static int nmboxes; /* number of mailboxes */ +static time_t mailtime[MAXMBOXES]; /* times of mailboxes */ + + + +/* + * Print appropriate message(s) if mail has arrived. If the argument is + * nozero, then the value of MAIL has changed, so we just update the + * values. + */ + +void +chkmail(int silent) +{ + int i; + const char *mpath; + char *p; + char *q; + struct stackmark smark; + struct stat statb; + + if (silent) + nmboxes = 10; + if (nmboxes == 0) + return; + setstackmark(&smark); + mpath = mpathset()? mpathval() : mailval(); + for (i = 0 ; i < nmboxes ; i++) { + p = padvance(&mpath, ""); + if (p == NULL) + break; + if (*p == '\0') + continue; + for (q = p ; *q ; q++); + if (q[-1] != '/') + abort(); + q[-1] = '\0'; /* delete trailing '/' */ +#ifdef notdef /* this is what the System V shell claims to do (it lies) */ + if (stat(p, &statb) < 0) + statb.st_mtime = 0; + if (statb.st_mtime > mailtime[i] && ! silent) { + out2str(pathopt? pathopt : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_mtime; +#else /* this is what it should do */ + if (stat(p, &statb) < 0) + statb.st_size = 0; + if (statb.st_size > mailtime[i] && ! silent) { + out2str(pathopt? pathopt : "you have mail"); + out2c('\n'); + } + mailtime[i] = statb.st_size; +#endif + } + nmboxes = i; + popstackmark(&smark); +} diff --git a/external/sh/mail.h b/external/sh/mail.h new file mode 100644 index 00000000..fb5f9088 --- /dev/null +++ b/external/sh/mail.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mail.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/mail.h 127958 2004-04-06 20:06:54Z markm $ + */ + +void chkmail(int); diff --git a/external/sh/main.c b/external/sh/main.c new file mode 100644 index 00000000..452ed355 --- /dev/null +++ b/external/sh/main.c @@ -0,0 +1,349 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1991, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.6 (Berkeley) 5/28/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/main.c 265772 2014-05-09 13:27:30Z jilles $"); + +#include <stdio.h> +#include <signal.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <locale.h> +#include <errno.h> + +#include "shell.h" +#include "main.h" +#include "mail.h" +#include "options.h" +#include "output.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "trap.h" +#include "var.h" +#include "show.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "exec.h" +#include "cd.h" +#include "redir.h" +#include "builtins.h" + +int rootpid; +int rootshell; +struct jmploc main_handler; +int localeisutf8, initial_localeisutf8; + +static void reset(void); +static void cmdloop(int); +static void read_profile(const char *); +static char *find_dot_file(char *); + +/* + * Main routine. We initialize things, parse the arguments, execute + * profiles if we're a login shell, and then call cmdloop to execute + * commands. The setjmp call sets up the location to jump to when an + * exception occurs. When an exception occurs the variable "state" + * is used to figure out how far we had gotten. + */ + +int +main(int argc, char *argv[]) +{ + struct stackmark smark, smark2; + volatile int state; + char *shinit; + + (void) setlocale(LC_ALL, ""); + initcharset(); + state = 0; + if (setjmp(main_handler.loc)) { + switch (exception) { + case EXEXEC: + exitstatus = exerrno; + break; + + case EXERROR: + exitstatus = 2; + break; + + default: + break; + } + + if (state == 0 || iflag == 0 || ! rootshell || + exception == EXEXIT) + exitshell(exitstatus); + reset(); + if (exception == EXINT) + out2fmt_flush("\n"); + popstackmark(&smark); + FORCEINTON; /* enable interrupts */ + if (state == 1) + goto state1; + else if (state == 2) + goto state2; + else if (state == 3) + goto state3; + else + goto state4; + } + handler = &main_handler; +#ifdef DEBUG + opentrace(); + trputs("Shell args: "); trargs(argv); +#endif + rootpid = getpid(); + rootshell = 1; + INTOFF; + initvar(); + setstackmark(&smark); + setstackmark(&smark2); + procargs(argc, argv); + pwd_init(iflag); + INTON; + if (iflag) + chkmail(1); + if (argv[0] && argv[0][0] == '-') { + state = 1; + read_profile("/etc/profile"); +state1: + state = 2; + if (privileged == 0) + read_profile("${HOME-}/.profile"); + else + read_profile("/etc/suid_profile"); + } +state2: + state = 3; + if (!privileged && iflag) { + if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') { + state = 3; + read_profile(shinit); + } + } +state3: + state = 4; + popstackmark(&smark2); + if (minusc) { + evalstring(minusc, sflag ? 0 : EV_EXIT); + } +state4: + if (sflag || minusc == NULL) { + cmdloop(1); + } + exitshell(exitstatus); + /*NOTREACHED*/ + return 0; +} + +static void +reset(void) +{ + reseteval(); + resetinput(); +} + +/* + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. + */ + +static void +cmdloop(int top) +{ + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + setstackmark(&smark); + for (;;) { + if (pendingsig) + dotrap(); + inter = 0; + if (iflag && top) { + inter++; + showjobs(1, SHOWJOBS_DEFAULT); + chkmail(0); + flushout(&output); + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2fmt_flush("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (n != NULL && nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + setstackmark(&smark); + if (evalskip != 0) { + if (evalskip == SKIPRETURN) + evalskip = 0; + break; + } + } + popstackmark(&smark); +} + + + +/* + * Read /etc/profile or .profile. Return on error. + */ + +static void +read_profile(const char *name) +{ + int fd; + const char *expandedname; + + expandedname = expandstr(name); + if (expandedname == NULL) + return; + INTOFF; + if ((fd = open(expandedname, O_RDONLY | O_CLOEXEC)) >= 0) + setinputfd(fd, 1); + INTON; + if (fd < 0) + return; + cmdloop(0); + popfile(); +} + + + +/* + * Read a file containing shell functions. + */ + +void +readcmdfile(const char *name) +{ + setinputfile(name, 1); + cmdloop(0); + popfile(); +} + + + +/* + * Take commands from a file. To be compatible we should do a path + * search for the file, which is necessary to find sub-commands. + */ + + +static char * +find_dot_file(char *basename) +{ + char *fullname; + const char *path = pathval(); + struct stat statb; + + /* don't try this for absolute or relative paths */ + if( strchr(basename, '/')) + return basename; + + while ((fullname = padvance(&path, basename)) != NULL) { + if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { + /* + * Don't bother freeing here, since it will + * be freed by the caller. + */ + return fullname; + } + stunalloc(fullname); + } + return basename; +} + +int +dotcmd(int argc, char **argv) +{ + char *filename, *fullname; + + if (argc < 2) + error("missing filename"); + + exitstatus = 0; + + /* + * Because we have historically not supported any options, + * only treat "--" specially. + */ + filename = argc > 2 && strcmp(argv[1], "--") == 0 ? argv[2] : argv[1]; + + fullname = find_dot_file(filename); + setinputfile(fullname, 1); + commandname = fullname; + cmdloop(0); + popfile(); + return exitstatus; +} + + +int +exitcmd(int argc, char **argv) +{ + if (stoppedjobs()) + return 0; + if (argc > 1) + exitshell(number(argv[1])); + else + exitshell_savedstatus(); +} diff --git a/external/sh/main.h b/external/sh/main.h new file mode 100644 index 00000000..1e1b4996 --- /dev/null +++ b/external/sh/main.h @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)main.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/main.h 229220 2012-01-01 22:17:12Z jilles $ + */ + +extern int rootpid; /* pid of main shell */ +extern int rootshell; /* true if we aren't a child of the main shell */ +extern struct jmploc main_handler; /* top level exception handler */ + +void readcmdfile(const char *); diff --git a/external/sh/memalloc.c b/external/sh/memalloc.c new file mode 100644 index 00000000..32e3838e --- /dev/null +++ b/external/sh/memalloc.c @@ -0,0 +1,342 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)memalloc.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/memalloc.c 278818 2015-02-15 21:41:29Z jilles $"); + +#include <sys/param.h> +#include "shell.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "expand.h" +#include <stdlib.h> +#include <unistd.h> + +/* + * Like malloc, but returns an error when out of space. + */ + +pointer +ckmalloc(size_t nbytes) +{ + pointer p; + + INTOFF; + p = malloc(nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + + +/* + * Same for realloc. + */ + +pointer +ckrealloc(pointer p, int nbytes) +{ + INTOFF; + p = realloc(p, nbytes); + INTON; + if (p == NULL) + error("Out of space"); + return p; +} + +void +ckfree(pointer p) +{ + INTOFF; + free(p); + INTON; +} + + +/* + * Make a copy of a string in safe storage. + */ + +char * +savestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = ckmalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +/* + * Parse trees for commands are allocated in lifo order, so we use a stack + * to make this more efficient, and also to avoid all sorts of exception + * handling code to handle interrupts in the middle of a parse. + * + * The size 496 was chosen because with 16-byte alignment the total size + * for the allocated block is 512. + */ + +#define MINSIZE 496 /* minimum size of a block. */ + + +struct stack_block { + struct stack_block *prev; + /* Data follows */ +}; +#define SPACE(sp) ((char*)(sp) + ALIGN(sizeof(struct stack_block))) + +static struct stack_block *stackp; +char *stacknxt; +int stacknleft; +char *sstrend; + + +static void +stnewblock(int nbytes) +{ + struct stack_block *sp; + int allocsize; + + if (nbytes < MINSIZE) + nbytes = MINSIZE; + + allocsize = ALIGN(sizeof(struct stack_block)) + ALIGN(nbytes); + + INTOFF; + sp = ckmalloc(allocsize); + sp->prev = stackp; + stacknxt = SPACE(sp); + stacknleft = allocsize - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + stackp = sp; + INTON; +} + + +pointer +stalloc(int nbytes) +{ + char *p; + + nbytes = ALIGN(nbytes); + if (nbytes > stacknleft) + stnewblock(nbytes); + p = stacknxt; + stacknxt += nbytes; + stacknleft -= nbytes; + return p; +} + + +void +stunalloc(pointer p) +{ + if (p == NULL) { /*DEBUG */ + write(STDERR_FILENO, "stunalloc\n", 10); + abort(); + } + stacknleft += stacknxt - (char *)p; + stacknxt = p; +} + + +char * +stsavestr(const char *s) +{ + char *p; + size_t len; + + len = strlen(s); + p = stalloc(len + 1); + memcpy(p, s, len + 1); + return p; +} + + +void +setstackmark(struct stackmark *mark) +{ + mark->stackp = stackp; + mark->stacknxt = stacknxt; + mark->stacknleft = stacknleft; + /* Ensure this block stays in place. */ + if (stackp != NULL && stacknxt == SPACE(stackp)) + stalloc(1); +} + + +void +popstackmark(struct stackmark *mark) +{ + struct stack_block *sp; + + INTOFF; + while (stackp != mark->stackp) { + sp = stackp; + stackp = sp->prev; + ckfree(sp); + } + stacknxt = mark->stacknxt; + stacknleft = mark->stacknleft; + sstrend = stacknxt + stacknleft; + INTON; +} + + +/* + * When the parser reads in a string, it wants to stick the string on the + * stack and only adjust the stack pointer when it knows how big the + * string is. Stackblock (defined in stack.h) returns a pointer to a block + * of space on top of the stack and stackblocklen returns the length of + * this block. Growstackblock will grow this space by at least one byte, + * possibly moving it (like realloc). Grabstackblock actually allocates the + * part of the block that has been used. + */ + +static void +growstackblock(int min) +{ + char *p; + int newlen; + char *oldspace; + int oldlen; + struct stack_block *sp; + struct stack_block *oldstackp; + + if (min < stacknleft) + min = stacknleft; + if ((unsigned int)min >= + INT_MAX / 2 - ALIGN(sizeof(struct stack_block))) + error("Out of space"); + min += stacknleft; + min += ALIGN(sizeof(struct stack_block)); + newlen = 512; + while (newlen < min) + newlen <<= 1; + oldspace = stacknxt; + oldlen = stacknleft; + + if (stackp != NULL && stacknxt == SPACE(stackp)) { + INTOFF; + oldstackp = stackp; + stackp = oldstackp->prev; + sp = ckrealloc((pointer)oldstackp, newlen); + sp->prev = stackp; + stackp = sp; + stacknxt = SPACE(sp); + stacknleft = newlen - (stacknxt - (char*)sp); + sstrend = stacknxt + stacknleft; + INTON; + } else { + newlen -= ALIGN(sizeof(struct stack_block)); + p = stalloc(newlen); + if (oldlen != 0) + memcpy(p, oldspace, oldlen); + stunalloc(p); + } +} + + + +/* + * The following routines are somewhat easier to use that the above. + * The user declares a variable of type STACKSTR, which may be declared + * to be a register. The macro STARTSTACKSTR initializes things. Then + * the user uses the macro STPUTC to add characters to the string. In + * effect, STPUTC(c, p) is the same as *p++ = c except that the stack is + * grown as necessary. When the user is done, she can just leave the + * string there and refer to it using stackblock(). Or she can allocate + * the space for it using grabstackstr(). If it is necessary to allow + * someone else to use the stack temporarily and then continue to grow + * the string, the user should use grabstack to allocate the space, and + * then call ungrabstr(p) to return to the previous mode of operation. + * + * USTPUTC is like STPUTC except that it doesn't check for overflow. + * CHECKSTACKSPACE can be called before USTPUTC to ensure that there + * is space for at least one character. + */ + +static char * +growstrstackblock(int n, int min) +{ + growstackblock(min); + return stackblock() + n; +} + +char * +growstackstr(void) +{ + int len; + + len = stackblocksize(); + return (growstrstackblock(len, 0)); +} + + +/* + * Called from CHECKSTRSPACE. + */ + +char * +makestrspace(int min, char *p) +{ + int len; + + len = p - stackblock(); + return (growstrstackblock(len, min)); +} + + +char * +stputbin(const char *data, size_t len, char *p) +{ + CHECKSTRSPACE(len, p); + memcpy(p, data, len); + return (p + len); +} + +char * +stputs(const char *data, char *p) +{ + return (stputbin(data, strlen(data), p)); +} diff --git a/external/sh/memalloc.h b/external/sh/memalloc.h new file mode 100644 index 00000000..ee86a922 --- /dev/null +++ b/external/sh/memalloc.h @@ -0,0 +1,86 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)memalloc.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/memalloc.h 278818 2015-02-15 21:41:29Z jilles $ + */ + +#include <string.h> + +struct stackmark { + struct stack_block *stackp; + char *stacknxt; + int stacknleft; +}; + + +extern char *stacknxt; +extern int stacknleft; +extern char *sstrend; + +pointer ckmalloc(size_t); +pointer ckrealloc(pointer, int); +void ckfree(pointer); +char *savestr(const char *); +pointer stalloc(int); +void stunalloc(pointer); +char *stsavestr(const char *); +void setstackmark(struct stackmark *); +void popstackmark(struct stackmark *); +char *growstackstr(void); +char *makestrspace(int, char *); +char *stputbin(const char *data, size_t len, char *p); +char *stputs(const char *data, char *p); + + + +#define stackblock() stacknxt +#define stackblocksize() stacknleft +#define grabstackblock(n) stalloc(n) +#define STARTSTACKSTR(p) p = stackblock() +#define STPUTC(c, p) do { if (p == sstrend) p = growstackstr(); *p++ = (c); } while(0) +#define CHECKSTRSPACE(n, p) { if ((size_t)(sstrend - p) < n) p = makestrspace(n, p); } +#define USTPUTC(c, p) (*p++ = (c)) +/* + * STACKSTRNUL's use is where we want to be able to turn a stack + * (non-sentinel, character counting string) into a C string, + * and later pretend the NUL is not there. + * Note: Because of STACKSTRNUL's semantics, STACKSTRNUL cannot be used + * on a stack that will grabstackstr()ed. + */ +#define STACKSTRNUL(p) (p == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) +#define grabstackstr(p) stalloc((char *)p - stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define STPUTBIN(s, len, p) p = stputbin((s), (len), p) +#define STPUTS(s, p) p = stputs((s), p) diff --git a/external/sh/miscbltin.c b/external/sh/miscbltin.c new file mode 100644 index 00000000..08121349 --- /dev/null +++ b/external/sh/miscbltin.c @@ -0,0 +1,523 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)miscbltin.c 8.4 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/miscbltin.c 278820 2015-02-15 21:47:43Z jilles $"); + +/* + * Miscellaneous builtins. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <unistd.h> +#include <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +#include "shell.h" +#include "options.h" +#include "var.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "syntax.h" +#include "trap.h" + +#undef eflag + +int readcmd(int, char **); +int umaskcmd(int, char **); +int ulimitcmd(int, char **); + +/* + * The read builtin. The -r option causes backslashes to be treated like + * ordinary characters. + * + * This uses unbuffered input, which may be avoidable in some cases. + * + * Note that if IFS=' :' then read x y should work so that: + * 'a b' x='a', y='b' + * ' a b ' x='a', y='b' + * ':b' x='', y='b' + * ':' x='', y='' + * '::' x='', y='' + * ': :' x='', y='' + * ':::' x='', y='::' + * ':b c:' x='', y='b c:' + */ + +int +readcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int backslash; + char c; + int rflag; + char *prompt; + const char *ifs; + char *p; + int startword; + int status; + int i; + int is_ifs; + int saveall = 0; + struct timeval tv; + char *tvptr; + fd_set ifds; + ssize_t nread; + int sig; + + rflag = 0; + prompt = NULL; + tv.tv_sec = -1; + tv.tv_usec = 0; + while ((i = nextopt("erp:t:")) != '\0') { + switch(i) { + case 'p': + prompt = shoptarg; + break; + case 'e': + break; + case 'r': + rflag = 1; + break; + case 't': + tv.tv_sec = strtol(shoptarg, &tvptr, 0); + if (tvptr == shoptarg) + error("timeout value"); + switch(*tvptr) { + case 0: + case 's': + break; + case 'h': + tv.tv_sec *= 60; + /* FALLTHROUGH */ + case 'm': + tv.tv_sec *= 60; + break; + default: + error("timeout unit"); + } + break; + } + } + if (prompt && isatty(0)) { + out2str(prompt); + flushall(); + } + if (*(ap = argptr) == NULL) + error("arg count"); + if ((ifs = bltinlookup("IFS", 1)) == NULL) + ifs = " \t\n"; + + if (tv.tv_sec >= 0) { + /* + * Wait for something to become available. + */ + FD_ZERO(&ifds); + FD_SET(0, &ifds); + status = select(1, &ifds, NULL, NULL, &tv); + /* + * If there's nothing ready, return an error. + */ + if (status <= 0) { + sig = pendingsig; + return (128 + (sig != 0 ? sig : SIGALRM)); + } + } + + status = 0; + startword = 2; + backslash = 0; + STARTSTACKSTR(p); + for (;;) { + nread = read(STDIN_FILENO, &c, 1); + if (nread == -1) { + if (errno == EINTR) { + sig = pendingsig; + if (sig == 0) + continue; + status = 128 + sig; + break; + } + warning("read error: %s", strerror(errno)); + status = 2; + break; + } else if (nread != 1) { + status = 1; + break; + } + if (c == '\0') + continue; + CHECKSTRSPACE(1, p); + if (backslash) { + backslash = 0; + startword = 0; + if (c != '\n') + USTPUTC(c, p); + continue; + } + if (!rflag && c == '\\') { + backslash++; + continue; + } + if (c == '\n') + break; + if (strchr(ifs, c)) + is_ifs = strchr(" \t\n", c) ? 1 : 2; + else + is_ifs = 0; + + if (startword != 0) { + if (is_ifs == 1) { + /* Ignore leading IFS whitespace */ + if (saveall) + USTPUTC(c, p); + continue; + } + if (is_ifs == 2 && startword == 1) { + /* Only one non-whitespace IFS per word */ + startword = 2; + if (saveall) + USTPUTC(c, p); + continue; + } + } + + if (is_ifs == 0) { + /* append this character to the current variable */ + startword = 0; + if (saveall) + /* Not just a spare terminator */ + saveall++; + USTPUTC(c, p); + continue; + } + + /* end of variable... */ + startword = is_ifs; + + if (ap[1] == NULL) { + /* Last variable needs all IFS chars */ + saveall++; + USTPUTC(c, p); + continue; + } + + STACKSTRNUL(p); + setvar(*ap, stackblock(), 0); + ap++; + STARTSTACKSTR(p); + } + STACKSTRNUL(p); + + /* Remove trailing IFS chars */ + for (; stackblock() <= --p; *p = 0) { + if (!strchr(ifs, *p)) + break; + if (strchr(" \t\n", *p)) + /* Always remove whitespace */ + continue; + if (saveall > 1) + /* Don't remove non-whitespace unless it was naked */ + break; + } + setvar(*ap, stackblock(), 0); + + /* Set any remaining args to "" */ + while (*++ap != NULL) + setvar(*ap, "", 0); + return status; +} + + + +int +umaskcmd(int argc __unused, char **argv __unused) +{ + char *ap; + int mask; + int i; + int symbolic_mode = 0; + + while ((i = nextopt("S")) != '\0') { + symbolic_mode = 1; + } + + INTOFF; + mask = umask(0); + umask(mask); + INTON; + + if ((ap = *argptr) == NULL) { + if (symbolic_mode) { + char u[4], g[4], o[4]; + + i = 0; + if ((mask & S_IRUSR) == 0) + u[i++] = 'r'; + if ((mask & S_IWUSR) == 0) + u[i++] = 'w'; + if ((mask & S_IXUSR) == 0) + u[i++] = 'x'; + u[i] = '\0'; + + i = 0; + if ((mask & S_IRGRP) == 0) + g[i++] = 'r'; + if ((mask & S_IWGRP) == 0) + g[i++] = 'w'; + if ((mask & S_IXGRP) == 0) + g[i++] = 'x'; + g[i] = '\0'; + + i = 0; + if ((mask & S_IROTH) == 0) + o[i++] = 'r'; + if ((mask & S_IWOTH) == 0) + o[i++] = 'w'; + if ((mask & S_IXOTH) == 0) + o[i++] = 'x'; + o[i] = '\0'; + + out1fmt("u=%s,g=%s,o=%s\n", u, g, o); + } else { + out1fmt("%.4o\n", mask); + } + } else { + if (is_digit(*ap)) { + mask = 0; + do { + if (*ap >= '8' || *ap < '0') + error("Illegal number: %s", *argptr); + mask = (mask << 3) + (*ap - '0'); + } while (*++ap != '\0'); + umask(mask); + } else { + void *set; + INTOFF; + if ((set = setmode (ap)) == 0) + error("Illegal number: %s", ap); + + mask = getmode (set, ~mask & 0777); + umask(~mask & 0777); + free(set); + INTON; + } + } + return 0; +} + +/* + * ulimit builtin + * + * This code, originally by Doug Gwyn, Doug Kingston, Eric Gisin, and + * Michael Rendell was ripped from pdksh 5.0.8 and hacked for use with + * ash by J.T. Conklin. + * + * Public domain. + */ + +struct limits { + const char *name; + const char *units; + int cmd; + int factor; /* multiply by to get rlim_{cur,max} values */ + char option; +}; + +static const struct limits limits[] = { +#ifdef RLIMIT_CPU + { "cpu time", "seconds", RLIMIT_CPU, 1, 't' }, +#endif +#ifdef RLIMIT_FSIZE + { "file size", "512-blocks", RLIMIT_FSIZE, 512, 'f' }, +#endif +#ifdef RLIMIT_DATA + { "data seg size", "kbytes", RLIMIT_DATA, 1024, 'd' }, +#endif +#ifdef RLIMIT_STACK + { "stack size", "kbytes", RLIMIT_STACK, 1024, 's' }, +#endif +#ifdef RLIMIT_CORE + { "core file size", "512-blocks", RLIMIT_CORE, 512, 'c' }, +#endif +#ifdef RLIMIT_RSS + { "max memory size", "kbytes", RLIMIT_RSS, 1024, 'm' }, +#endif +#ifdef RLIMIT_MEMLOCK + { "locked memory", "kbytes", RLIMIT_MEMLOCK, 1024, 'l' }, +#endif +#ifdef RLIMIT_NPROC + { "max user processes", (char *)0, RLIMIT_NPROC, 1, 'u' }, +#endif +#ifdef RLIMIT_NOFILE + { "open files", (char *)0, RLIMIT_NOFILE, 1, 'n' }, +#endif +#ifdef RLIMIT_VMEM + { "virtual mem size", "kbytes", RLIMIT_VMEM, 1024, 'v' }, +#endif +#ifdef RLIMIT_SWAP + { "swap limit", "kbytes", RLIMIT_SWAP, 1024, 'w' }, +#endif +#ifdef RLIMIT_SBSIZE + { "sbsize", "bytes", RLIMIT_SBSIZE, 1, 'b' }, +#endif +#ifdef RLIMIT_NPTS + { "pseudo-terminals", (char *)0, RLIMIT_NPTS, 1, 'p' }, +#endif +#ifdef RLIMIT_KQUEUES + { "kqueues", (char *)0, RLIMIT_KQUEUES, 1, 'k' }, +#endif + { (char *) 0, (char *)0, 0, 0, '\0' } +}; + +enum limithow { SOFT = 0x1, HARD = 0x2 }; + +static void +printlimit(enum limithow how, const struct rlimit *limit, + const struct limits *l) +{ + rlim_t val = 0; + + if (how & SOFT) + val = limit->rlim_cur; + else if (how & HARD) + val = limit->rlim_max; + if (val == RLIM_INFINITY) + out1str("unlimited\n"); + else + { + val /= l->factor; + out1fmt("%jd\n", (intmax_t)val); + } +} + +int +ulimitcmd(int argc __unused, char **argv __unused) +{ + rlim_t val = 0; + enum limithow how = SOFT | HARD; + const struct limits *l; + int set, all = 0; + int optc, what; + struct rlimit limit; + + what = 'f'; + while ((optc = nextopt("HSatfdsmcnuvlbpwk")) != '\0') + switch (optc) { + case 'H': + how = HARD; + break; + case 'S': + how = SOFT; + break; + case 'a': + all = 1; + break; + default: + what = optc; + } + + for (l = limits; l->name && l->option != what; l++) + ; + if (!l->name) + error("internal error (%c)", what); + + set = *argptr ? 1 : 0; + if (set) { + char *p = *argptr; + + if (all || argptr[1]) + error("too many arguments"); + if (strcmp(p, "unlimited") == 0) + val = RLIM_INFINITY; + else { + char *end; + uintmax_t uval; + + if (*p < '0' || *p > '9') + error("bad number"); + errno = 0; + uval = strtoumax(p, &end, 10); + if (errno != 0 || *end != '\0') + error("bad number"); + if (uval > UINTMAX_MAX / l->factor) + error("bad number"); + uval *= l->factor; + val = (rlim_t)uval; + if (val < 0 || (uintmax_t)val != uval || + val == RLIM_INFINITY) + error("bad number"); + } + } + if (all) { + for (l = limits; l->name; l++) { + char optbuf[40]; + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + + if (l->units) + snprintf(optbuf, sizeof(optbuf), + "(%s, -%c) ", l->units, l->option); + else + snprintf(optbuf, sizeof(optbuf), + "(-%c) ", l->option); + out1fmt("%-18s %18s ", l->name, optbuf); + printlimit(how, &limit, l); + } + return 0; + } + + if (getrlimit(l->cmd, &limit) < 0) + error("can't get limit: %s", strerror(errno)); + if (set) { + if (how & SOFT) + limit.rlim_cur = val; + if (how & HARD) + limit.rlim_max = val; + if (setrlimit(l->cmd, &limit) < 0) + error("bad limit: %s", strerror(errno)); + } else + printlimit(how, &limit, l); + return 0; +} diff --git a/external/sh/myhistedit.h b/external/sh/myhistedit.h new file mode 100644 index 00000000..1d21f467 --- /dev/null +++ b/external/sh/myhistedit.h @@ -0,0 +1,42 @@ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)myhistedit.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/myhistedit.h 229220 2012-01-01 22:17:12Z jilles $ + */ + +#include <histedit.h> + +extern History *hist; +extern EditLine *el; +extern int displayhist; + +void histedit(void); +void sethistsize(const char *); +void setterm(const char *); + diff --git a/external/sh/mystring.c b/external/sh/mystring.c new file mode 100644 index 00000000..60349d7e --- /dev/null +++ b/external/sh/mystring.c @@ -0,0 +1,98 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mystring.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/mystring.c 270102 2014-08-17 16:40:29Z jilles $"); + +/* + * String functions. + * + * equal(s1, s2) Return true if strings are equal. + * number(s) Convert a string of digits to an integer. + * is_number(s) Return true if s is a string of digits. + */ + +#include <stdlib.h> +#include "shell.h" +#include "syntax.h" +#include "error.h" +#include "mystring.h" + + +char nullstr[1]; /* zero length string */ + +/* + * equal - #defined in mystring.h + */ + + +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ + +int +number(const char *s) +{ + if (! is_number(s)) + error("Illegal number: %s", s); + return atoi(s); +} + + + +/* + * Check for a valid number. This should be elsewhere. + */ + +int +is_number(const char *p) +{ + const char *q; + + if (*p == '\0') + return 0; + while (*p == '0') + p++; + for (q = p; *q != '\0'; q++) + if (! is_digit(*q)) + return 0; + if (q - p > 10 || + (q - p == 10 && memcmp(p, "2147483647", 10) > 0)) + return 0; + return 1; +} diff --git a/external/sh/mystring.h b/external/sh/mystring.h new file mode 100644 index 00000000..3876bab5 --- /dev/null +++ b/external/sh/mystring.h @@ -0,0 +1,41 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mystring.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/mystring.h 268920 2014-07-20 12:06:52Z jilles $ + */ + +#include <string.h> + +int number(const char *); +int is_number(const char *); + +#define equal(s1, s2) (strcmp(s1, s2) == 0) diff --git a/external/sh/nodes.c b/external/sh/nodes.c new file mode 100644 index 00000000..956a4ded --- /dev/null +++ b/external/sh/nodes.c @@ -0,0 +1,368 @@ +/* + * This file was generated by the mknodes program. + */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)nodes.c.pat 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/nodes.c.pat 249235 2013-04-07 16:28:36Z jilles $ + */ + +#include <sys/param.h> +#include <stdlib.h> +#include <stddef.h> +/* + * Routine for dealing with parsed shell commands. + */ + +#include "shell.h" +#include "nodes.h" +#include "memalloc.h" +#include "mystring.h" + + +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static pointer funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ + +static const short nodesize[27] = { + ALIGN(sizeof (struct nbinary)), + ALIGN(sizeof (struct ncmd)), + ALIGN(sizeof (struct npipe)), + ALIGN(sizeof (struct nredir)), + ALIGN(sizeof (struct nredir)), + ALIGN(sizeof (struct nredir)), + ALIGN(sizeof (struct nbinary)), + ALIGN(sizeof (struct nbinary)), + ALIGN(sizeof (struct nif)), + ALIGN(sizeof (struct nbinary)), + ALIGN(sizeof (struct nbinary)), + ALIGN(sizeof (struct nfor)), + ALIGN(sizeof (struct ncase)), + ALIGN(sizeof (struct nclist)), + ALIGN(sizeof (struct nclist)), + ALIGN(sizeof (struct narg)), + ALIGN(sizeof (struct narg)), + ALIGN(sizeof (struct nfile)), + ALIGN(sizeof (struct nfile)), + ALIGN(sizeof (struct nfile)), + ALIGN(sizeof (struct nfile)), + ALIGN(sizeof (struct nfile)), + ALIGN(sizeof (struct ndup)), + ALIGN(sizeof (struct ndup)), + ALIGN(sizeof (struct nhere)), + ALIGN(sizeof (struct nhere)), + ALIGN(sizeof (struct nnot)), +}; + + +static void calcsize(union node *); +static void sizenodelist(struct nodelist *); +static union node *copynode(union node *); +static struct nodelist *copynodelist(struct nodelist *); +static char *nodesavestr(const char *); + + +struct funcdef { + unsigned int refcount; + union node n; +}; + +/* + * Make a copy of a parse tree. + */ + +struct funcdef * +copyfunc(union node *n) +{ + struct funcdef *fn; + + if (n == NULL) + return NULL; + funcblocksize = offsetof(struct funcdef, n); + funcstringsize = 0; + calcsize(n); + fn = ckmalloc(funcblocksize + funcstringsize); + fn->refcount = 1; + funcblock = (char *)fn + offsetof(struct funcdef, n); + funcstring = (char *)fn + funcblocksize; + copynode(n); + return fn; +} + + +union node * +getfuncnode(struct funcdef *fn) +{ + return fn == NULL ? NULL : &fn->n; +} + + +static void +calcsize(union node *n) +{ + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NSEMI: + case NAND: + case NOR: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + 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: + case NCLISTFALLTHRU: + 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 NFROM: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + 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 void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; + } +} + + + +static union node * +copynode(union node *n) +{ + union node *new; + + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *)funcblock + nodesize[n->type]; + switch (n->type) { + case NSEMI: + case NAND: + case NOR: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + 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 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 = nodesavestr(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: + case NCLISTFALLTHRU: + 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 = nodesavestr(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: + case NFROM: + case NFROMTO: + case NAPPEND: + case NCLOBBER: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.next = copynode(n->nfile.next); + new->nfile.fd = n->nfile.fd; + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.next = copynode(n->ndup.next); + new->ndup.fd = n->ndup.fd; + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.next = copynode(n->nhere.next); + new->nhere.fd = n->nhere.fd; + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; +} + + +static struct nodelist * +copynodelist(struct nodelist *lp) +{ + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *)funcblock + ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; + } + *lpp = NULL; + return start; +} + + + +static char * +nodesavestr(const char *s) +{ + const char *p = s; + char *q = funcstring; + char *rtn = funcstring; + + while ((*q++ = *p++) != '\0') + continue; + funcstring = q; + return rtn; +} + + +void +reffunc(struct funcdef *fn) +{ + if (fn) + fn->refcount++; +} + + +/* + * Decrement the reference count of a function definition, freeing it + * if it falls to 0. + */ + +void +unreffunc(struct funcdef *fn) +{ + if (fn) { + fn->refcount--; + if (fn->refcount > 0) + return; + ckfree(fn); + } +} diff --git a/external/sh/nodes.h b/external/sh/nodes.h new file mode 100644 index 00000000..dddbf609 --- /dev/null +++ b/external/sh/nodes.h @@ -0,0 +1,163 @@ +/* + * This file was generated by the mknodes program. + */ + +#define NSEMI 0 +#define NCMD 1 +#define NPIPE 2 +#define NREDIR 3 +#define NBACKGND 4 +#define NSUBSHELL 5 +#define NAND 6 +#define NOR 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NCLISTFALLTHRU 14 +#define NDEFUN 15 +#define NARG 16 +#define NTO 17 +#define NFROM 18 +#define NFROMTO 19 +#define NAPPEND 20 +#define NCLOBBER 21 +#define NTOFD 22 +#define NFROMFD 23 +#define NHERE 24 +#define NXHERE 25 +#define NNOT 26 + + + +struct nbinary { + int type; + union node *ch1; + union node *ch2; +}; + + +struct ncmd { + int type; + union node *args; + union node *redirect; +}; + + +struct npipe { + int type; + int backgnd; + struct nodelist *cmdlist; +}; + + +struct nredir { + int type; + union node *n; + union node *redirect; +}; + + +struct nif { + int type; + union node *test; + union node *ifpart; + union node *elsepart; +}; + + +struct nfor { + int type; + union node *args; + union node *body; + char *var; +}; + + +struct ncase { + int type; + union node *expr; + union node *cases; +}; + + +struct nclist { + int type; + union node *next; + union node *pattern; + union node *body; +}; + + +struct narg { + int type; + union node *next; + char *text; + struct nodelist *backquote; +}; + + +struct nfile { + int type; + int fd; + union node *next; + union node *fname; + char *expfname; +}; + + +struct ndup { + int type; + int fd; + union node *next; + int dupfd; + union node *vname; +}; + + +struct nhere { + int type; + int fd; + union node *next; + union node *doc; + const char *expdoc; +}; + + +struct nnot { + int type; + union node *com; +}; + + +union node { + int type; + struct nbinary nbinary; + struct ncmd ncmd; + struct npipe npipe; + struct nredir nredir; + struct nif nif; + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; + struct narg narg; + struct nfile nfile; + struct ndup ndup; + struct nhere nhere; + struct nnot nnot; +}; + + +struct nodelist { + struct nodelist *next; + union node *n; +}; + + +struct funcdef; +struct funcdef *copyfunc(union node *); +union node *getfuncnode(struct funcdef *); +void reffunc(struct funcdef *); +void unreffunc(struct funcdef *); diff --git a/external/sh/options.c b/external/sh/options.c new file mode 100644 index 00000000..f12ec40f --- /dev/null +++ b/external/sh/options.c @@ -0,0 +1,588 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)options.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/options.c 279508 2015-03-01 22:32:23Z jilles $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#define DEFINE_OPTIONS +#include "options.h" +#undef DEFINE_OPTIONS +#include "nodes.h" /* for other header files */ +#include "eval.h" +#include "jobs.h" +#include "input.h" +#include "output.h" +#include "trap.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +char *arg0; /* value of $0 */ +struct shparam shellparam; /* current positional parameters */ +char **argptr; /* argument list for builtin commands */ +char *shoptarg; /* set by nextopt (like getopt) */ +char *nextopt_optptr; /* used by nextopt */ + +char *minusc; /* argument to -c option */ + + +static void options(int); +static void minus_o(char *, int); +static void setoption(int, int); +static int getopts(char *, char *, char **, char ***, char **); + + +/* + * Process the shell command line arguments. + */ + +void +procargs(int argc, char **argv) +{ + int i; + char *scriptname; + + argptr = argv; + if (argc > 0) + argptr++; + for (i = 0; i < NOPTS; i++) + optlist[i].val = 2; + privileged = (getuid() != geteuid() || getgid() != getegid()); + options(1); + if (*argptr == NULL && minusc == NULL) + sflag = 1; + if (iflag != 0 && sflag == 1 && isatty(0) && isatty(1)) { + iflag = 1; + if (Eflag == 2) + Eflag = 1; + } + if (mflag == 2) + mflag = iflag; + for (i = 0; i < NOPTS; i++) + if (optlist[i].val == 2) + optlist[i].val = 0; + arg0 = argv[0]; + if (sflag == 0 && minusc == NULL) { + scriptname = *argptr++; + setinputfile(scriptname, 0); + commandname = arg0 = scriptname; + } + /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */ + if (argptr && minusc && *argptr) + arg0 = *argptr++; + + shellparam.p = argptr; + shellparam.reset = 1; + /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */ + while (*argptr) { + shellparam.nparam++; + argptr++; + } + optschanged(); +} + + +void +optschanged(void) +{ + setinteractive(iflag); +#ifndef NO_HISTORY + histedit(); +#endif + setjobctl(mflag); +} + +/* + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. + */ + +static void +options(int cmdline) +{ + char *kp, *p; + int val; + int c; + + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + if ((c = *p++) == '-') { + val = 1; + /* A "-" or "--" terminates options */ + if (p[0] == '\0') + goto end_options1; + if (p[0] == '-' && p[1] == '\0') + goto end_options2; + /** + * For the benefit of `#!' lines in shell scripts, + * treat a string of '-- *#.*' the same as '--'. + * This is needed so that a script starting with: + * #!/bin/sh -- # -*- perl -*- + * will continue to work after a change is made to + * kern/imgact_shell.c to NOT token-ize the options + * specified on a '#!' line. A bit of a kludge, + * but that trick is recommended in documentation + * for some scripting languages, and we might as + * well continue to support it. + */ + if (p[0] == '-') { + kp = p + 1; + while (*kp == ' ' || *kp == '\t') + kp++; + if (*kp == '#' || *kp == '\0') + goto end_options2; + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; + } + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + char *q; +#ifdef NOHACK /* removing this code allows sh -ce 'foo' for compat */ + if (*p == '\0') +#endif + q = *argptr++; + if (q == NULL || minusc != NULL) + error("Bad -c option"); + minusc = q; +#ifdef NOHACK + break; +#endif + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else + setoption(c, val); + } + } + return; + + /* When processing `set', a single "-" means turn off -x and -v */ +end_options1: + if (!cmdline) { + xflag = vflag = 0; + return; + } + + /* + * When processing `set', a "--" means the remaining arguments + * replace the positional parameters in the active shell. If + * there are no remaining options, then all the positional + * parameters are cleared (equivalent to doing ``shift $#''). + */ +end_options2: + if (!cmdline) { + if (*argptr == NULL) + setparam(argptr); + return; + } + + /* + * At this point we are processing options given to 'sh' on a command + * line. If an end-of-options marker ("-" or "--") is followed by an + * arg of "#", then skip over all remaining arguments. Some scripting + * languages (e.g.: perl) document that /bin/sh will implement this + * behavior, and they recommend that users take advantage of it to + * solve certain issues that can come up when writing a perl script. + * Yes, this feature is in /bin/sh to help users write perl scripts. + */ + p = *argptr; + if (p != NULL && p[0] == '#' && p[1] == '\0') { + while (*argptr != NULL) + argptr++; + /* We need to keep the final argument */ + argptr--; + } +} + +static void +minus_o(char *name, int val) +{ + int i; + + if (name == NULL) { + if (val) { + /* "Pretty" output. */ + out1str("Current option settings\n"); + for (i = 0; i < NOPTS; i++) + out1fmt("%-16s%s\n", optlist[i].name, + optlist[i].val ? "on" : "off"); + } else { + /* Output suitable for re-input to shell. */ + for (i = 0; i < NOPTS; i++) + out1fmt("%s %co %s%s", + i % 6 == 0 ? "set" : "", + optlist[i].val ? '-' : '+', + optlist[i].name, + i % 6 == 5 || i == NOPTS - 1 ? "\n" : ""); + } + } else { + for (i = 0; i < NOPTS; i++) + if (equal(name, optlist[i].name)) { + setoption(optlist[i].letter, val); + return; + } + error("Illegal option -o %s", name); + } +} + + +static void +setoption(int flag, int val) +{ + int i; + + if (flag == 'p' && !val && privileged) { + if (setgid(getgid()) == -1) + error("setgid"); + if (setuid(getuid()) == -1) + error("setuid"); + } + for (i = 0; i < NOPTS; i++) + if (optlist[i].letter == flag) { + optlist[i].val = val; + if (val) { + /* #%$ hack for ksh semantics */ + if (flag == 'V') + Eflag = 0; + else if (flag == 'E') + Vflag = 0; + } + return; + } + error("Illegal option -%c", flag); +} + + +/* + * Set the shell parameters. + */ + +void +setparam(char **argv) +{ + char **newparam; + char **ap; + int nparam; + + for (nparam = 0 ; argv[nparam] ; nparam++); + ap = newparam = ckmalloc((nparam + 1) * sizeof *ap); + while (*argv) { + *ap++ = savestr(*argv++); + } + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; + shellparam.optp = NULL; + shellparam.reset = 1; + shellparam.optnext = NULL; +} + + +/* + * Free the list of positional parameters. + */ + +void +freeparam(struct shparam *param) +{ + char **ap; + + if (param->malloc) { + for (ap = param->p ; *ap ; ap++) + ckfree(*ap); + ckfree(param->p); + } + if (param->optp) { + for (ap = param->optp ; *ap ; ap++) + ckfree(*ap); + ckfree(param->optp); + } +} + + + +/* + * The shift builtin command. + */ + +int +shiftcmd(int argc, char **argv) +{ + int n; + char **ap1, **ap2; + + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + return 1; + INTOFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p ; --n >= 0 ; ap1++) { + if (shellparam.malloc) + ckfree(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL); + shellparam.reset = 1; + INTON; + return 0; +} + + + +/* + * The set command builtin. + */ + +int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvarscmd(argc, argv); + INTOFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); + } + INTON; + return 0; +} + + +void +getoptsreset(const char *value) +{ + while (*value == '0') + value++; + if (strcmp(value, "1") == 0) + shellparam.reset = 1; +} + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ + +int +getoptscmd(int argc, char **argv) +{ + char **optbase = NULL, **ap; + int i; + + if (argc < 3) + error("usage: getopts optstring var [arg]"); + + if (shellparam.reset == 1) { + INTOFF; + if (shellparam.optp) { + for (ap = shellparam.optp ; *ap ; ap++) + ckfree(*ap); + ckfree(shellparam.optp); + shellparam.optp = NULL; + } + if (argc > 3) { + shellparam.optp = ckmalloc((argc - 2) * sizeof *ap); + memset(shellparam.optp, '\0', (argc - 2) * sizeof *ap); + for (i = 0; i < argc - 3; i++) + shellparam.optp[i] = savestr(argv[i + 3]); + } + INTON; + optbase = argc == 3 ? shellparam.p : shellparam.optp; + shellparam.optnext = optbase; + shellparam.optptr = NULL; + shellparam.reset = 0; + } else + optbase = shellparam.optp ? shellparam.optp : shellparam.p; + + return getopts(argv[1], argv[2], optbase, &shellparam.optnext, + &shellparam.optptr); +} + +static int +getopts(char *optstr, char *optvar, char **optfirst, char ***optnext, + char **optptr) +{ + char *p, *q; + char c = '?'; + int done = 0; + int ind = 0; + int err = 0; + char s[10]; + const char *newoptarg = NULL; + + if ((p = *optptr) == NULL || *p == '\0') { + /* Current word is done, advance */ + if (*optnext == NULL) + return 1; + p = **optnext; + if (p == NULL || *p != '-' || *++p == '\0') { +atend: + ind = *optnext - optfirst + 1; + *optnext = NULL; + p = NULL; + done = 1; + goto out; + } + (*optnext)++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + goto atend; + } + + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + } + else + out2fmt_flush("Illegal option -%c\n", c); + c = '?'; + goto out; + } + if (*++q == ':') + q++; + } + + if (*++q == ':') { + if (*p == '\0' && (p = **optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + newoptarg = s; + c = ':'; + } + else { + out2fmt_flush("No arg for -%c option\n", c); + c = '?'; + } + goto out; + } + + if (p == **optnext) + (*optnext)++; + newoptarg = p; + p = NULL; + } + +out: + if (*optnext != NULL) + ind = *optnext - optfirst + 1; + *optptr = p; + if (newoptarg != NULL) + err |= setvarsafe("OPTARG", newoptarg, 0); + else { + INTOFF; + err |= unsetvar("OPTARG"); + INTON; + } + fmtstr(s, sizeof(s), "%d", ind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *optnext = NULL; + *optptr = NULL; + flushall(); + exraise(EXERROR); + } + return done; +} + +/* + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It return the character, or '\0' on + * end of input. + */ + +int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; + + if ((p = nextopt_optptr) == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (p[0] == '-' && p[1] == '\0') /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring ; *q != c ; ) { + if (*q == '\0') + error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + error("No arg for -%c option", c); + shoptarg = p; + p = NULL; + } + nextopt_optptr = p; + return c; +} diff --git a/external/sh/options.h b/external/sh/options.h new file mode 100644 index 00000000..4e06bf02 --- /dev/null +++ b/external/sh/options.h @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)options.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/options.h 279503 2015-03-01 21:46:55Z jilles $ + */ + +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + unsigned char reset; /* if getopts has been reset */ + char **p; /* parameter list */ + char **optp; /* parameter list for getopts */ + char **optnext; /* next parameter to be processed by getopts */ + char *optptr; /* used by getopts */ +}; + + + +#define eflag optlist[0].val +#define fflag optlist[1].val +#define Iflag optlist[2].val +#define iflag optlist[3].val +#define mflag optlist[4].val +#define nflag optlist[5].val +#define sflag optlist[6].val +#define xflag optlist[7].val +#define vflag optlist[8].val +#define Vflag optlist[9].val +#define Eflag optlist[10].val +#define Cflag optlist[11].val +#define aflag optlist[12].val +#define bflag optlist[13].val +#define uflag optlist[14].val +#define privileged optlist[15].val +#define Tflag optlist[16].val +#define Pflag optlist[17].val +#define hflag optlist[18].val + +#define NOPTS 19 + +struct optent { + const char *name; + const char letter; + char val; +}; + +extern struct optent optlist[NOPTS]; +#ifdef DEFINE_OPTIONS +struct optent optlist[NOPTS] = { + { "errexit", 'e', 0 }, + { "noglob", 'f', 0 }, + { "ignoreeof", 'I', 0 }, + { "interactive",'i', 0 }, + { "monitor", 'm', 0 }, + { "noexec", 'n', 0 }, + { "stdin", 's', 0 }, + { "xtrace", 'x', 0 }, + { "verbose", 'v', 0 }, + { "vi", 'V', 0 }, + { "emacs", 'E', 0 }, + { "noclobber", 'C', 0 }, + { "allexport", 'a', 0 }, + { "notify", 'b', 0 }, + { "nounset", 'u', 0 }, + { "privileged", 'p', 0 }, + { "trapsasync", 'T', 0 }, + { "physical", 'P', 0 }, + { "trackall", 'h', 0 }, +}; +#endif + + +extern char *minusc; /* argument to -c option */ +extern char *arg0; /* $0 */ +extern struct shparam shellparam; /* $@ */ +extern char **argptr; /* argument list for builtin commands */ +extern char *shoptarg; /* set by nextopt */ +extern char *nextopt_optptr; /* used by nextopt */ + +void procargs(int, char **); +void optschanged(void); +void setparam(char **); +void freeparam(struct shparam *); +int nextopt(const char *); +void getoptsreset(const char *); diff --git a/external/sh/output.c b/external/sh/output.c new file mode 100644 index 00000000..77186035 --- /dev/null +++ b/external/sh/output.c @@ -0,0 +1,375 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)output.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/output.c 275766 2014-12-14 16:26:19Z jilles $"); + +/* + * Shell output routines. We use our own output routines because: + * When a builtin command is interrupted we have to discard + * any pending output. + * When a builtin command appears in back quotes, we want to + * save the output of the command in a region obtained + * via malloc, rather than doing a fork and reading the + * output of the command via a pipe. + */ + +#include <stdio.h> /* defines BUFSIZ */ +#include <string.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#include "shell.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "var.h" + + +#define OUTBUFSIZ BUFSIZ +#define MEM_OUT -2 /* output to dynamically allocated memory */ +#define OUTPUT_ERR 01 /* error occurred on output */ + +static int doformat_wr(void *, const char *, int); + +struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0}; +struct output errout = {NULL, 0, NULL, 256, 2, 0}; +struct output memout = {NULL, 0, NULL, 0, MEM_OUT, 0}; +struct output *out1 = &output; +struct output *out2 = &errout; + +void +outcslow(int c, struct output *file) +{ + outc(c, file); +} + +void +out1str(const char *p) +{ + outstr(p, out1); +} + +void +out1qstr(const char *p) +{ + outqstr(p, out1); +} + +void +out2str(const char *p) +{ + outstr(p, out2); +} + +void +out2qstr(const char *p) +{ + outqstr(p, out2); +} + +void +outstr(const char *p, struct output *file) +{ + outbin(p, strlen(p), file); +} + +static void +byteseq(int ch, struct output *file) +{ + char seq[4]; + + seq[0] = '\\'; + seq[1] = (ch >> 6 & 0x3) + '0'; + seq[2] = (ch >> 3 & 0x7) + '0'; + seq[3] = (ch & 0x7) + '0'; + outbin(seq, 4, file); +} + +static void +outdqstr(const char *p, struct output *file) +{ + const char *end; + mbstate_t mbs; + size_t clen; + wchar_t wc; + + memset(&mbs, '\0', sizeof(mbs)); + end = p + strlen(p); + outstr("$'", file); + while ((clen = mbrtowc(&wc, p, end - p + 1, &mbs)) != 0) { + if (clen == (size_t)-2) { + while (p < end) + byteseq(*p++, file); + break; + } + if (clen == (size_t)-1) { + memset(&mbs, '\0', sizeof(mbs)); + byteseq(*p++, file); + continue; + } + if (wc == L'\n') + outcslow('\n', file), p++; + else if (wc == L'\r') + outstr("\\r", file), p++; + else if (wc == L'\t') + outstr("\\t", file), p++; + else if (!iswprint(wc)) { + for (; clen > 0; clen--) + byteseq(*p++, file); + } else { + if (wc == L'\'' || wc == L'\\') + outcslow('\\', file); + outbin(p, clen, file); + p += clen; + } + } + outcslow('\'', file); +} + +/* Like outstr(), but quote for re-input into the shell. */ +void +outqstr(const char *p, struct output *file) +{ + int i; + + if (p[0] == '\0') { + outstr("''", file); + return; + } + for (i = 0; p[i] != '\0'; i++) { + if ((p[i] > '\0' && p[i] < ' ' && p[i] != '\n') || + (p[i] & 0x80) != 0 || p[i] == '\'') { + outdqstr(p, file); + return; + } + } + + if (p[strcspn(p, "|&;<>()$`\\\" \n*?[~#=")] == '\0' || + strcmp(p, "[") == 0) { + outstr(p, file); + return; + } + + outcslow('\'', file); + outstr(p, file); + outcslow('\'', file); +} + +void +outbin(const void *data, size_t len, struct output *file) +{ + const char *p; + + p = data; + while (len-- > 0) + outc(*p++, file); +} + +void +emptyoutbuf(struct output *dest) +{ + int offset; + + if (dest->buf == NULL) { + INTOFF; + dest->buf = ckmalloc(dest->bufsize); + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; + INTON; + } else if (dest->fd == MEM_OUT) { + offset = dest->bufsize; + INTOFF; + dest->bufsize <<= 1; + dest->buf = ckrealloc(dest->buf, dest->bufsize); + dest->nleft = dest->bufsize - offset; + dest->nextc = dest->buf + offset; + INTON; + } else { + flushout(dest); + } + dest->nleft--; +} + + +void +flushall(void) +{ + flushout(&output); + flushout(&errout); +} + + +void +flushout(struct output *dest) +{ + + if (dest->buf == NULL || dest->nextc == dest->buf || dest->fd < 0) + return; + if (xwrite(dest->fd, dest->buf, dest->nextc - dest->buf) < 0) + dest->flags |= OUTPUT_ERR; + dest->nextc = dest->buf; + dest->nleft = dest->bufsize; +} + + +void +freestdout(void) +{ + INTOFF; + if (output.buf) { + ckfree(output.buf); + output.buf = NULL; + output.nleft = 0; + } + INTON; +} + + +int +outiserror(struct output *file) +{ + return (file->flags & OUTPUT_ERR); +} + + +void +outclearerror(struct output *file) +{ + file->flags &= ~OUTPUT_ERR; +} + + +void +outfmt(struct output *file, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(file, fmt, ap); + va_end(ap); +} + + +void +out1fmt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out1, fmt, ap); + va_end(ap); +} + +void +out2fmt_flush(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + doformat(out2, fmt, ap); + va_end(ap); + flushout(out2); +} + +void +fmtstr(char *outbuf, int length, const char *fmt, ...) +{ + va_list ap; + + INTOFF; + va_start(ap, fmt); + vsnprintf(outbuf, length, fmt, ap); + va_end(ap); + INTON; +} + +static int +doformat_wr(void *cookie, const char *buf, int len) +{ + struct output *o; + + o = (struct output *)cookie; + outbin(buf, len, o); + + return (len); +} + +void +doformat(struct output *dest, const char *f, va_list ap) +{ + FILE *fp; + + if ((fp = fwopen(dest, doformat_wr)) != NULL) { + vfprintf(fp, f, ap); + fclose(fp); + } +} + +/* + * Version of write which resumes after a signal is caught. + */ + +int +xwrite(int fd, const char *buf, int nbytes) +{ + int ntry; + int i; + int n; + + n = nbytes; + ntry = 0; + for (;;) { + i = write(fd, buf, n); + if (i > 0) { + if ((n -= i) <= 0) + return nbytes; + buf += i; + ntry = 0; + } else if (i == 0) { + if (++ntry > 10) + return nbytes - n; + } else if (errno != EINTR) { + return -1; + } + } +} diff --git a/external/sh/output.h b/external/sh/output.h new file mode 100644 index 00000000..227db2dc --- /dev/null +++ b/external/sh/output.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)output.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/output.h 244162 2012-12-12 22:01:10Z jilles $ + */ + +#ifndef OUTPUT_INCL + +#include <stdarg.h> +#include <stddef.h> + +struct output { + char *nextc; + int nleft; + char *buf; + int bufsize; + short fd; + short flags; +}; + +extern struct output output; /* to fd 1 */ +extern struct output errout; /* to fd 2 */ +extern struct output memout; +extern struct output *out1; /* &memout if backquote, otherwise &output */ +extern struct output *out2; /* &memout if backquote with 2>&1, otherwise + &errout */ + +void outcslow(int, struct output *); +void out1str(const char *); +void out1qstr(const char *); +void out2str(const char *); +void out2qstr(const char *); +void outstr(const char *, struct output *); +void outqstr(const char *, struct output *); +void outbin(const void *, size_t, struct output *); +void emptyoutbuf(struct output *); +void flushall(void); +void flushout(struct output *); +void freestdout(void); +int outiserror(struct output *); +void outclearerror(struct output *); +void outfmt(struct output *, const char *, ...) __printflike(2, 3); +void out1fmt(const char *, ...) __printflike(1, 2); +void out2fmt_flush(const char *, ...) __printflike(1, 2); +void fmtstr(char *, int, const char *, ...) __printflike(3, 4); +void doformat(struct output *, const char *, va_list) __printflike(2, 0); +int xwrite(int, const char *, int); + +#define outc(c, file) (--(file)->nleft < 0? (emptyoutbuf(file), *(file)->nextc++ = (c)) : (*(file)->nextc++ = (c))) +#define out1c(c) outc(c, out1); +#define out2c(c) outcslow(c, out2); + +#define OUTPUT_INCL +#endif diff --git a/external/sh/parser.c b/external/sh/parser.c new file mode 100644 index 00000000..657637cd --- /dev/null +++ b/external/sh/parser.c @@ -0,0 +1,2072 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/parser.c 278820 2015-02-15 21:47:43Z jilles $"); + +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "expand.h" /* defines rmescapes() */ +#include "syntax.h" +#include "options.h" +#include "input.h" +#include "output.h" +#include "var.h" +#include "error.h" +#include "memalloc.h" +#include "mystring.h" +#include "alias.h" +#include "show.h" +#include "eval.h" +#include "exec.h" /* to check for special builtins */ +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + +/* + * Shell command parser. + */ + +#define PROMPTLEN 128 + +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 + +/* values returned by readtoken */ +#include "token.h" + + + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + +struct parser_temp { + struct parser_temp *next; + void *data; +}; + + +static struct heredoc *heredoclist; /* list of here documents to read */ +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ +static int lasttoken; /* last token read */ +static int tokpushback; /* last token pushed back */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ +static int startlinno; /* line # where last token started */ +static int funclinno; /* line # where the current function started */ +static struct parser_temp *parser_temp; + + +static union node *list(int); +static union node *andor(void); +static union node *pipeline(void); +static union node *command(void); +static union node *simplecmd(union node **, union node *); +static union node *makename(void); +static union node *makebinary(int type, union node *n1, union node *n2); +static void parsefname(void); +static void parseheredoc(void); +static int peektoken(void); +static int readtoken(void); +static int xxreadtoken(void); +static int readtoken1(int, const char *, const char *, int); +static int noexpand(char *); +static void consumetoken(int); +static void synexpect(int) __dead2; +static void synerror(const char *) __dead2; +static void setprompt(int); +static int pgetc_linecont(void); + + +static void * +parser_temp_alloc(size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = ckmalloc(sizeof(*t)); + t->data = NULL; + t->next = parser_temp; + parser_temp = t; + t->data = ckmalloc(len); + INTON; + return t->data; +} + + +static void * +parser_temp_realloc(void *ptr, size_t len) +{ + struct parser_temp *t; + + INTOFF; + t = parser_temp; + if (ptr != t->data) + error("bug: parser_temp_realloc misused"); + t->data = ckrealloc(t->data, len); + INTON; + return t->data; +} + + +static void +parser_temp_free_upto(void *ptr) +{ + struct parser_temp *t; + int done = 0; + + INTOFF; + while (parser_temp != NULL && !done) { + t = parser_temp; + parser_temp = t->next; + done = t->data == ptr; + ckfree(t->data); + ckfree(t); + } + INTON; + if (!done) + error("bug: parser_temp_free_upto misused"); +} + + +static void +parser_temp_free_all(void) +{ + struct parser_temp *t; + + INTOFF; + while (parser_temp != NULL) { + t = parser_temp; + parser_temp = t->next; + ckfree(t->data); + ckfree(t); + } + INTON; +} + + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ + +union node * +parsecmd(int interact) +{ + int t; + + /* This assumes the parser is not re-entered, + * which could happen if we add command substitution on PS1/PS2. + */ + parser_temp_free_all(); + heredoclist = NULL; + + tokpushback = 0; + checkkwd = 0; + doprompt = interact; + if (doprompt) + setprompt(1); + else + setprompt(0); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); +} + + +static union node * +list(int nlflag) +{ + union node *ntop, *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return NULL; + ntop = n1 = NULL; + for (;;) { + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2 != NULL && n2->type == NPIPE) { + n2->npipe.backgnd = 1; + } else if (n2 != NULL && n2->type == NREDIR) { + n2->type = NBACKGND; + } else { + n3 = (union node *)stalloc(sizeof (struct nredir)); + n3->type = NBACKGND; + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; + } + } + if (ntop == NULL) + ntop = n2; + else if (n1 == NULL) { + n1 = makebinary(NSEMI, ntop, n2); + ntop = n1; + } + else { + n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); + n1->nbinary.ch2 = n3; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* FALLTHROUGH */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag) + return ntop; + } else if (tok == TEOF && nlflag) { + parseheredoc(); + return ntop; + } else { + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (!nlflag && tokendlist[peektoken()]) + return ntop; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return ntop; + default: + if (nlflag) + synexpect(-1); + tokpushback++; + return ntop; + } + } +} + + + +static union node * +andor(void) +{ + union node *n; + int t; + + n = pipeline(); + for (;;) { + if ((t = readtoken()) == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n; + } + n = makebinary(t, n, pipeline()); + } +} + + + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate, t; + + negate = 0; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + TRACE(("pipeline: entered\n")); + while (readtoken() == TNOT) + negate = !negate; + tokpushback++; + n1 = command(); + if (readtoken() == TPIPE) { + pipenode = (union node *)stalloc(sizeof (struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + t = readtoken(); + tokpushback++; + if (t == TNOT) + lp->n = pipeline(); + else + lp->n = command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = (union node *)stalloc(sizeof (struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } else + return n1; +} + + + +static union node * +command(void) +{ + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + int t; + int is_subshell; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + is_subshell = 0; + redir = NULL; + n1 = NULL; + rpp = &redir; + + /* Check for redirection which may precede command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + + switch (readtoken()) { + case TIF: + n1 = (union node *)stalloc(sizeof (struct nif)); + n1->type = NIF; + if ((n1->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + if ((n2->nif.test = list(0)) == NULL) + synexpect(-1); + consumetoken(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + consumetoken(TFI); + checkkwd = CHKKWD | CHKALIAS; + break; + case TWHILE: + case TUNTIL: + t = lasttoken; + if ((n1 = list(0)) == NULL) + synexpect(-1); + consumetoken(TDO); + n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); + consumetoken(TDONE); + checkkwd = CHKKWD | CHKALIAS; + break; + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + synerror("Bad for loop variable"); + n1 = (union node *)stalloc(sizeof (struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + while (readtoken() == TNL) + ; + if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { + app = ≈ + while (readtoken() == TWORD) { + n2 = makename(); + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + synexpect(-1); + } else { + static char argvars[5] = { + CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' + }; + n2 = (union node *)stalloc(sizeof (struct narg)); + n2->type = NARG; + n2->narg.text = argvars; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) == TDO) + t = TDONE; + else if (t == TBEGIN) + t = TEND; + else + synexpect(-1); + n1->nfor.body = list(0); + consumetoken(t); + checkkwd = CHKKWD | CHKALIAS; + break; + case TCASE: + n1 = (union node *)stalloc(sizeof (struct ncase)); + n1->type = NCASE; + consumetoken(TWORD); + n1->ncase.expr = makename(); + while (readtoken() == TNL); + if (lasttoken != TWORD || ! equal(wordtext, "in")) + synerror("expecting \"in\""); + cpp = &n1->ncase.cases; + checkkwd = CHKNL | CHKKWD, readtoken(); + while (lasttoken != TESAC) { + *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + if (lasttoken == TLP) + readtoken(); + for (;;) { + *app = ap = makename(); + checkkwd = CHKNL | CHKKWD; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + synexpect(TRP); + cp->nclist.body = list(0); + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if ((t = readtoken()) != TESAC) { + if (t == TENDCASE) + ; + else if (t == TFALLTHRU) + cp->type = NCLISTFALLTHRU; + else + synexpect(TENDCASE); + checkkwd = CHKNL | CHKKWD, readtoken(); + } + cpp = &cp->nclist.next; + } + *cpp = NULL; + checkkwd = CHKKWD | CHKALIAS; + break; + case TLP: + n1 = (union node *)stalloc(sizeof (struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + consumetoken(TRP); + checkkwd = CHKKWD | CHKALIAS; + is_subshell = 1; + break; + case TBEGIN: + n1 = list(0); + consumetoken(TEND); + checkkwd = CHKKWD | CHKALIAS; + break; + /* A simple command must have at least one redirection or word. */ + case TBACKGND: + case TSEMI: + case TAND: + case TOR: + case TPIPE: + case TENDCASE: + case TFALLTHRU: + case TEOF: + case TNL: + case TRP: + if (!redir) + synexpect(-1); + case TWORD: + tokpushback++; + n1 = simplecmd(rpp, redir); + return n1; + default: + synexpect(-1); + } + + /* Now check for redirection which may follow command */ + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); + } + tokpushback++; + *rpp = NULL; + if (redir) { + if (!is_subshell) { + n2 = (union node *)stalloc(sizeof (struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; + } + n1->nredir.redirect = redir; + } + + return n1; +} + + +static union node * +simplecmd(union node **rpp, union node *redir) +{ + union node *args, **app; + union node **orig_rpp = rpp; + union node *n = NULL; + int special; + int savecheckkwd; + + /* If we don't have any redirections already, then we must reset */ + /* rpp to be the address of the local redir variable. */ + if (redir == 0) + rpp = &redir; + + args = NULL; + app = &args; + /* + * We save the incoming value, because we need this for shell + * functions. There can not be a redirect or an argument between + * the function name and the open parenthesis. + */ + orig_rpp = rpp; + + savecheckkwd = CHKALIAS; + + for (;;) { + checkkwd = savecheckkwd; + if (readtoken() == TWORD) { + n = makename(); + *app = n; + app = &n->narg.next; + if (savecheckkwd != 0 && !isassignment(wordtext)) + savecheckkwd = 0; + } else if (lasttoken == TREDIR) { + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + } else if (lasttoken == TLP && app == &args->narg.next + && rpp == orig_rpp) { + /* We have a function */ + consumetoken(TRP); + funclinno = plinno; + /* + * - Require plain text. + * - Functions with '/' cannot be called. + * - Reject name=(). + * - Reject ksh extended glob patterns. + */ + if (!noexpand(n->narg.text) || quoteflag || + strchr(n->narg.text, '/') || + strchr("!%*+-=?@}~", + n->narg.text[strlen(n->narg.text) - 1])) + synerror("Bad function name"); + rmescapes(n->narg.text); + if (find_builtin(n->narg.text, &special) >= 0 && + special) + synerror("Cannot override a special builtin with a function"); + n->type = NDEFUN; + n->narg.next = command(); + funclinno = 0; + return n; + } else { + tokpushback++; + break; + } + } + *app = NULL; + *rpp = NULL; + n = (union node *)stalloc(sizeof (struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.redirect = redir; + return n; +} + +static union node * +makename(void) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static union node * +makebinary(int type, union node *n1, union node *n2) +{ + union node *n; + + n = (union node *)stalloc(sizeof (struct nbinary)); + n->type = type; + n->nbinary.ch1 = n1; + n->nbinary.ch2 = n2; + return (n); +} + +void +forcealias(void) +{ + checkkwd |= CHKALIAS; +} + +void +fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (is_digit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (text[0] == '-' && text[1] == '\0') + n->ndup.dupfd = -1; + else { + + if (err) + synerror("Bad fd number"); + else + n->ndup.vname = makename(); + } +} + + +static void +parsefname(void) +{ + union node *n = redirnode; + + consumetoken(TWORD); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (here->striptabs) { + while (*wordtext == '\t') + wordtext++; + } + if (! noexpand(wordtext)) + synerror("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist ; p->next ; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + + +/* + * Input any here documents. + */ + +static void +parseheredoc(void) +{ + struct heredoc *here; + union node *n; + + while (heredoclist) { + here = heredoclist; + heredoclist = here->next; + if (needprompt) { + setprompt(2); + needprompt = 0; + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = makename(); + here->here->nhere.doc = n; + } +} + +static int +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return (t); +} + +static int +readtoken(void) +{ + int t; + struct alias *ap; +#ifdef DEBUG + int alreadyseen = tokpushback; +#endif + + top: + t = xxreadtoken(); + + /* + * eat newlines + */ + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } + } + + /* + * check for keywords and aliases + */ + if (t == TWORD && !quoteflag) + { + const char * const *pp; + + if (checkkwd & CHKKWD) + for (pp = parsekwd; *pp; pp++) { + if (**pp == *wordtext && equal(*pp, wordtext)) + { + lasttoken = t = pp - parsekwd + KWDOFFSET; + TRACE(("keyword %s recognized\n", tokname[t])); + goto out; + } + } + if (checkkwd & CHKALIAS && + (ap = lookupalias(wordtext, 1)) != NULL) { + pushstring(ap->val, strlen(ap->val), ap); + goto top; + } + } +out: + if (t != TNOT) + checkkwd = 0; + +#ifdef DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname[t], t == TWORD ? wordtext : "")); +#endif + return (t); +} + + +/* + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] + */ + +#define RETURN(token) return lasttoken = token + +static int +xxreadtoken(void) +{ + int c; + + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + needprompt = 0; + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + else + setprompt(0); + continue; + } + pungetc(); + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc_linecont() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc_linecont() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + c = pgetc_linecont(); + if (c == ';') + RETURN(TENDCASE); + else if (c == '&') + RETURN(TFALLTHRU); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + } + } +#undef RETURN +} + + +#define MAXNEST_static 8 +struct tokenstate +{ + const char *syntax; /* *SYNTAX */ + int parenlevel; /* levels of parentheses in arithmetic */ + enum tokenstate_category + { + TSTATE_TOP, + TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ + TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ + TSTATE_ARITH + } category; +}; + + +/* + * Check to see whether we are at the end of the here document. When this + * is called, c is set to the first character of the next input line. If + * we are at the end of the here document, this routine sets the c to PEOF. + * The new value of c is returned. + */ + +static int +checkend(int c, const char *eofmark, int striptabs) +{ + if (striptabs) { + while (c == '\t') + c = pgetc(); + } + if (c == *eofmark) { + int c2; + const char *q; + + for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) + ; + if ((c2 == PEOF || c2 == '\n') && *q == '\0') { + c = PEOF; + if (c2 == '\n') { + plinno++; + needprompt = doprompt; + } + } else { + pungetc(); + pushstring(eofmark + 1, q - (eofmark + 1), NULL); + } + } + return (c); +} + + +/* + * Parse a redirection operator. The variable "out" points to a string + * specifying the fd to be redirected. The variable "c" contains the + * first character of the redirection operator. + */ + +static void +parseredir(char *out, int c) +{ + char fd = *out; + union node *np; + + np = (union node *)stalloc(sizeof (struct nfile)); + if (c == '>') { + np->nfile.fd = 1; + c = pgetc_linecont(); + if (c == '>') + np->type = NAPPEND; + else if (c == '&') + np->type = NTOFD; + else if (c == '|') + np->type = NCLOBBER; + else { + np->type = NTO; + pungetc(); + } + } else { /* c == '<' */ + np->nfile.fd = 0; + c = pgetc_linecont(); + if (c == '<') { + if (sizeof (struct nfile) != sizeof (struct nhere)) { + np = (union node *)stalloc(sizeof (struct nhere)); + np->nfile.fd = 0; + } + np->type = NHERE; + heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); + heredoc->here = np; + if ((c = pgetc_linecont()) == '-') { + heredoc->striptabs = 1; + } else { + heredoc->striptabs = 0; + pungetc(); + } + } else if (c == '&') + np->type = NFROMFD; + else if (c == '>') + np->type = NFROMTO; + else { + np->type = NFROM; + pungetc(); + } + } + if (fd != '\0') + np->nfile.fd = digit_val(fd); + redirnode = np; +} + +/* + * Called to parse command substitutions. + */ + +static char * +parsebackq(char *out, struct nodelist **pbqlist, + int oldstyle, int dblquote, int quoted) +{ + struct nodelist **nlpp; + union node *n; + char *volatile str; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + size_t savelen; + int saveprompt; + const int bq_startlinno = plinno; + char *volatile ostr = NULL; + struct parsefile *const savetopfile = getcurrentfile(); + struct heredoc *const saveheredoclist = heredoclist; + struct heredoc *here; + + str = NULL; + if (setjmp(jmploc.loc)) { + popfilesupto(savetopfile); + if (str) + ckfree(str); + if (ostr) + ckfree(ostr); + heredoclist = saveheredoclist; + handler = savehandler; + if (exception == EXERROR) { + startlinno = bq_startlinno; + synerror("Error in command substitution"); + } + longjmp(handler->loc, 1); + } + INTOFF; + savelen = out - stackblock(); + if (savelen > 0) { + str = ckmalloc(savelen); + memcpy(str, stackblock(), savelen); + } + handler = &jmploc; + heredoclist = NULL; + INTON; + if (oldstyle) { + /* We must read until the closing backquote, giving special + treatment to some slashes, and then push the string and + reread it as input, interpreting it normally. */ + char *oout; + int c; + int olen; + + + STARTSTACKSTR(oout); + for (;;) { + if (needprompt) { + setprompt(2); + needprompt = 0; + } + CHECKSTRSPACE(2, oout); + c = pgetc_linecont(); + if (c == '`') + break; + switch (c) { + case '\\': + c = pgetc(); + if (c != '\\' && c != '`' && c != '$' + && (!dblquote || c != '"')) + USTPUTC('\\', oout); + break; + + case '\n': + plinno++; + needprompt = doprompt; + break; + + case PEOF: + startlinno = plinno; + synerror("EOF in backquote substitution"); + break; + + default: + break; + } + USTPUTC(c, oout); + } + USTPUTC('\0', oout); + olen = oout - stackblock(); + INTOFF; + ostr = ckmalloc(olen); + memcpy(ostr, stackblock(), olen); + setinputstring(ostr, 1); + INTON; + } + nlpp = pbqlist; + while (*nlpp) + nlpp = &(*nlpp)->next; + *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); + (*nlpp)->next = NULL; + + if (oldstyle) { + saveprompt = doprompt; + doprompt = 0; + } + + n = list(0); + + if (oldstyle) { + if (peektoken() != TEOF) + synexpect(-1); + doprompt = saveprompt; + } else + consumetoken(TRP); + + (*nlpp)->n = n; + if (oldstyle) { + /* + * Start reading from old file again, ignoring any pushed back + * tokens left from the backquote parsing + */ + popfile(); + tokpushback = 0; + } + STARTSTACKSTR(out); + CHECKSTRSPACE(savelen + 1, out); + INTOFF; + if (str) { + memcpy(out, str, savelen); + STADJUST(savelen, out); + ckfree(str); + str = NULL; + } + if (ostr) { + ckfree(ostr); + ostr = NULL; + } + here = saveheredoclist; + if (here != NULL) { + while (here->next != NULL) + here = here->next; + here->next = heredoclist; + heredoclist = saveheredoclist; + } + handler = savehandler; + INTON; + if (quoted) + USTPUTC(CTLBACKQ | CTLQUOTE, out); + else + USTPUTC(CTLBACKQ, out); + return out; +} + + +/* + * Called to parse a backslash escape sequence inside $'...'. + * The backslash has already been read. + */ +static char * +readcstyleesc(char *out) +{ + int c, v, i, n; + + c = pgetc(); + switch (c) { + case '\0': + synerror("Unterminated quoted string"); + case '\n': + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + return out; + case '\\': + case '\'': + case '"': + v = c; + break; + case 'a': v = '\a'; break; + case 'b': v = '\b'; break; + case 'e': v = '\033'; break; + case 'f': v = '\f'; break; + case 'n': v = '\n'; break; + case 'r': v = '\r'; break; + case 't': v = '\t'; break; + case 'v': v = '\v'; break; + case 'x': + v = 0; + for (;;) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + break; + } + pungetc(); + break; + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + v = c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + c = pgetc(); + if (c >= '0' && c <= '7') { + v <<= 3; + v += c - '0'; + } else + pungetc(); + } else + pungetc(); + break; + case 'c': + c = pgetc(); + if (c < 0x3f || c > 0x7a || c == 0x60) + synerror("Bad escape sequence"); + if (c == '\\' && pgetc() != '\\') + synerror("Bad escape sequence"); + if (c == '?') + v = 127; + else + v = c & 0x1f; + break; + case 'u': + case 'U': + n = c == 'U' ? 8 : 4; + v = 0; + for (i = 0; i < n; i++) { + c = pgetc(); + if (c >= '0' && c <= '9') + v = (v << 4) + c - '0'; + else if (c >= 'A' && c <= 'F') + v = (v << 4) + c - 'A' + 10; + else if (c >= 'a' && c <= 'f') + v = (v << 4) + c - 'a' + 10; + else + synerror("Bad escape sequence"); + } + if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) + synerror("Bad escape sequence"); + /* We really need iconv here. */ + if (initial_localeisutf8 && v > 127) { + CHECKSTRSPACE(4, out); + /* + * We cannot use wctomb() as the locale may have + * changed. + */ + if (v <= 0x7ff) { + USTPUTC(0xc0 | v >> 6, out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0xffff) { + USTPUTC(0xe0 | v >> 12, out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } else if (v <= 0x10ffff) { + USTPUTC(0xf0 | v >> 18, out); + USTPUTC(0x80 | ((v >> 12) & 0x3f), out); + USTPUTC(0x80 | ((v >> 6) & 0x3f), out); + USTPUTC(0x80 | (v & 0x3f), out); + return out; + } + } + if (v > 127) + v = '?'; + break; + default: + synerror("Bad escape sequence"); + } + v = (char)v; + /* + * We can't handle NUL bytes. + * POSIX says we should skip till the closing quote. + */ + if (v == '\0') { + while ((c = pgetc()) != '\'') { + if (c == '\\') + c = pgetc(); + if (c == PEOF) + synerror("Unterminated quoted string"); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } + } + pungetc(); + return out; + } + if (SQSYNTAX[v] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(v, out); + return out; +} + + +/* + * If eofmark is NULL, read a word or a redirection symbol. If eofmark + * is not NULL, read a here document. In the latter case, eofmark is the + * word which marks the end of the document and striptabs is true if + * leading tabs should be stripped from the document. The argument firstc + * is the first character of the input token or document. + * + * Because C does not have internal subroutines, I have simulated them + * using goto's to implement the subroutine linkage. The following macros + * will run code that appears at the end of readtoken1. + */ + +#define PARSESUB() {goto parsesub; parsesub_return:;} +#define PARSEARITH() {goto parsearith; parsearith_return:;} + +static int +readtoken1(int firstc, char const *initialsyntax, const char *eofmark, + int striptabs) +{ + int c = firstc; + char *out; + int len; + struct nodelist *bqlist; + int quotef; + int newvarnest; + int level; + int synentry; + struct tokenstate state_static[MAXNEST_static]; + int maxnest = MAXNEST_static; + struct tokenstate *state = state_static; + int sqiscstyle = 0; + + startlinno = plinno; + quotef = 0; + bqlist = NULL; + newvarnest = 0; + level = 0; + state[level].syntax = initialsyntax; + state[level].parenlevel = 0; + state[level].category = TSTATE_TOP; + + STARTSTACKSTR(out); + loop: { /* for each line, until end of word */ + if (eofmark) + /* set c to PEOF if at end of here document */ + c = checkend(c, eofmark, striptabs); + for (;;) { /* until end of line or end of word */ + CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ + + synentry = state[level].syntax[c]; + + switch(synentry) { + case CNL: /* '\n' */ + if (state[level].syntax == BASESYNTAX) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + c = pgetc(); + goto loop; /* continue outer loop */ + case CSBACK: + if (sqiscstyle) { + out = readcstyleesc(out); + break; + } + /* FALLTHROUGH */ + case CWORD: + USTPUTC(c, out); + break; + case CCTL: + if (eofmark == NULL || initialsyntax != SQSYNTAX) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + break; + case CBACK: /* backslash */ + c = pgetc(); + if (c == PEOF) { + USTPUTC('\\', out); + pungetc(); + } else if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + if (state[level].syntax == DQSYNTAX && + c != '\\' && c != '`' && c != '$' && + (c != '"' || (eofmark != NULL && + newvarnest == 0)) && + (c != '}' || state[level].category != TSTATE_VAR_OLD)) + USTPUTC('\\', out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX) + USTPUTC(CTLQUOTEMARK, out); + if (SQSYNTAX[c] == CCTL) + USTPUTC(CTLESC, out); + USTPUTC(c, out); + if ((eofmark == NULL || + newvarnest > 0) && + state[level].syntax == BASESYNTAX && + state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + quotef++; + } + break; + case CSQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 0; + break; + case CDQUOTE: + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = DQSYNTAX; + break; + case CENDQUOTE: + if (eofmark != NULL && newvarnest == 0) + USTPUTC(c, out); + else { + if (state[level].category == TSTATE_VAR_OLD) + USTPUTC(CTLQUOTEEND, out); + state[level].syntax = BASESYNTAX; + quotef++; + } + break; + case CVAR: /* '$' */ + PARSESUB(); /* parse substitution */ + break; + case CENDVAR: /* '}' */ + if (level > 0 && + ((state[level].category == TSTATE_VAR_OLD && + state[level].syntax == + state[level - 1].syntax) || + (state[level].category == TSTATE_VAR_NEW && + state[level].syntax == BASESYNTAX))) { + if (state[level].category == TSTATE_VAR_NEW) + newvarnest--; + level--; + USTPUTC(CTLENDVAR, out); + } else { + USTPUTC(c, out); + } + break; + case CLP: /* '(' in arithmetic */ + state[level].parenlevel++; + USTPUTC(c, out); + break; + case CRP: /* ')' in arithmetic */ + if (state[level].parenlevel > 0) { + USTPUTC(c, out); + --state[level].parenlevel; + } else { + if (pgetc_linecont() == ')') { + if (level > 0 && + state[level].category == TSTATE_ARITH) { + level--; + USTPUTC(CTLENDARI, out); + } else + USTPUTC(')', out); + } else { + /* + * unbalanced parens + * (don't 2nd guess - no error) + */ + pungetc(); + USTPUTC(')', out); + } + } + break; + case CBQUOTE: /* '`' */ + out = parsebackq(out, &bqlist, 1, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); + break; + case CEOF: + goto endword; /* exit outer loop */ + case CIGN: + break; + default: + if (level == 0) + goto endword; /* exit outer loop */ + USTPUTC(c, out); + } + c = pgetc_macro(); + } + } +endword: + if (state[level].syntax == ARISYNTAX) + synerror("Missing '))'"); + if (state[level].syntax != BASESYNTAX && eofmark == NULL) + synerror("Unterminated quoted string"); + if (state[level].category == TSTATE_VAR_OLD || + state[level].category == TSTATE_VAR_NEW) { + startlinno = plinno; + synerror("Missing '}'"); + } + if (state != state_static) + parser_temp_free_upto(state); + USTPUTC('\0', out); + len = out - stackblock(); + out = stackblock(); + if (eofmark == NULL) { + if ((c == '>' || c == '<') + && quotef == 0 + && len <= 2 + && (*out == '\0' || is_digit(*out))) { + parseredir(out, c); + return lasttoken = TREDIR; + } else { + pungetc(); + } + } + quoteflag = quotef; + backquotelist = bqlist; + grabstackblock(len); + wordtext = out; + return lasttoken = TWORD; +/* end of readtoken routine */ + + +/* + * Parse a substitution. At this point, we have read the dollar sign + * and nothing else. + */ + +parsesub: { + char buf[10]; + int subtype; + int typeloc; + int flags; + char *p; + static const char types[] = "}-+?="; + int bracketed_name = 0; /* used to handle ${[0-9]*} variables */ + int linno; + int length; + int c1; + + c = pgetc_linecont(); + if (c == '(') { /* $(command) or $((arith)) */ + if (pgetc_linecont() == '(') { + PARSEARITH(); + } else { + pungetc(); + out = parsebackq(out, &bqlist, 0, + state[level].syntax == DQSYNTAX && + (eofmark == NULL || newvarnest > 0), + state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX); + } + } else if (c == '{' || is_name(c) || is_special(c)) { + USTPUTC(CTLVAR, out); + typeloc = out - stackblock(); + USTPUTC(VSNORMAL, out); + subtype = VSNORMAL; + flags = 0; + if (c == '{') { + bracketed_name = 1; + c = pgetc_linecont(); + subtype = 0; + } +varname: + if (!is_eof(c) && is_name(c)) { + length = 0; + do { + STPUTC(c, out); + c = pgetc_linecont(); + length++; + } while (!is_eof(c) && is_in_name(c)); + if (length == 6 && + strncmp(out - length, "LINENO", length) == 0) { + /* Replace the variable name with the + * current line number. */ + linno = plinno; + if (funclinno != 0) + linno -= funclinno - 1; + snprintf(buf, sizeof(buf), "%d", linno); + STADJUST(-6, out); + STPUTS(buf, out); + flags |= VSLINENO; + } + } else if (is_digit(c)) { + if (bracketed_name) { + do { + STPUTC(c, out); + c = pgetc_linecont(); + } while (is_digit(c)); + } else { + STPUTC(c, out); + c = pgetc_linecont(); + } + } else if (is_special(c)) { + c1 = c; + c = pgetc_linecont(); + if (subtype == 0 && c1 == '#') { + subtype = VSLENGTH; + if (strchr(types, c) == NULL && c != ':' && + c != '#' && c != '%') + goto varname; + c1 = c; + c = pgetc_linecont(); + if (c1 != '}' && c == '}') { + pungetc(); + c = c1; + goto varname; + } + pungetc(); + c = c1; + c1 = '#'; + subtype = 0; + } + USTPUTC(c1, out); + } else { + subtype = VSERROR; + if (c == '}') + pungetc(); + else if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + else + USTPUTC(c, out); + } + if (subtype == 0) { + switch (c) { + case ':': + flags |= VSNUL; + c = pgetc_linecont(); + /*FALLTHROUGH*/ + default: + p = strchr(types, c); + if (p == NULL) { + if (c == '\n' || c == PEOF) + synerror("Unexpected end of line in substitution"); + if (flags == VSNUL) + STPUTC(':', out); + STPUTC(c, out); + subtype = VSERROR; + } else + subtype = p - types + VSNORMAL; + break; + case '%': + case '#': + { + int cc = c; + subtype = c == '#' ? VSTRIMLEFT : + VSTRIMRIGHT; + c = pgetc_linecont(); + if (c == cc) + subtype++; + else + pungetc(); + break; + } + } + } else if (subtype != VSERROR) { + if (subtype == VSLENGTH && c != '}') + subtype = VSERROR; + pungetc(); + } + STPUTC('=', out); + if (state[level].syntax == DQSYNTAX || + state[level].syntax == ARISYNTAX) + flags |= VSQUOTE; + *(stackblock() + typeloc) = subtype | flags; + if (subtype != VSNORMAL) { + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].parenlevel = 0; + if (subtype == VSMINUS || subtype == VSPLUS || + subtype == VSQUESTION || subtype == VSASSIGN) { + /* + * For operators that were in the Bourne shell, + * inherit the double-quote state. + */ + state[level].syntax = state[level - 1].syntax; + state[level].category = TSTATE_VAR_OLD; + } else { + /* + * The other operators take a pattern, + * so go to BASESYNTAX. + * Also, ' and " are now special, even + * in here documents. + */ + state[level].syntax = BASESYNTAX; + state[level].category = TSTATE_VAR_NEW; + newvarnest++; + } + } + } else if (c == '\'' && state[level].syntax == BASESYNTAX) { + /* $'cstylequotes' */ + USTPUTC(CTLQUOTEMARK, out); + state[level].syntax = SQSYNTAX; + sqiscstyle = 1; + } else { + USTPUTC('$', out); + pungetc(); + } + goto parsesub_return; +} + + +/* + * Parse an arithmetic expansion (indicate start of one and set state) + */ +parsearith: { + + if (level + 1 >= maxnest) { + maxnest *= 2; + if (state == state_static) { + state = parser_temp_alloc( + maxnest * sizeof(*state)); + memcpy(state, state_static, + MAXNEST_static * sizeof(*state)); + } else + state = parser_temp_realloc(state, + maxnest * sizeof(*state)); + } + level++; + state[level].syntax = ARISYNTAX; + state[level].parenlevel = 0; + state[level].category = TSTATE_ARITH; + USTPUTC(CTLARI, out); + if (state[level - 1].syntax == DQSYNTAX) + USTPUTC('"',out); + else + USTPUTC(' ',out); + goto parsearith_return; +} + +} /* end of readtoken */ + + +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ + +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if ( c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (BASESYNTAX[(int)c] == CCTL) + return 0; + } + return 1; +} + + +/* + * Return true if the argument is a legal variable name (a letter or + * underscore followed by zero or more letters, underscores, and digits). + */ + +int +goodname(const char *name) +{ + const char *p; + + p = name; + if (! is_name(*p)) + return 0; + while (*++p) { + if (! is_in_name(*p)) + return 0; + } + return 1; +} + + +int +isassignment(const char *p) +{ + if (!is_name(*p)) + return 0; + p++; + for (;;) { + if (*p == '=') + return 1; + else if (!is_in_name(*p)) + return 0; + p++; + } +} + + +static void +consumetoken(int token) +{ + if (readtoken() != token) + synexpect(token); +} + + +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ + +static void +synexpect(int token) +{ + char msg[64]; + + if (token >= 0) { + fmtstr(msg, 64, "%s unexpected (expecting %s)", + tokname[lasttoken], tokname[token]); + } else { + fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]); + } + synerror(msg); +} + + +static void +synerror(const char *msg) +{ + if (commandname) + outfmt(out2, "%s: %d: ", commandname, startlinno); + else if (arg0) + outfmt(out2, "%s: ", arg0); + outfmt(out2, "Syntax error: %s\n", msg); + error((char *)NULL); +} + +static void +setprompt(int which) +{ + whichprompt = which; + +#ifndef NO_HISTORY + if (!el) +#endif + { + out2str(getprompt(NULL)); + flushout(out2); + } +} + +static int +pgetc_linecont(void) +{ + int c; + + while ((c = pgetc_macro()) == '\\') { + c = pgetc(); + if (c == '\n') { + plinno++; + if (doprompt) + setprompt(2); + else + setprompt(0); + } else { + pungetc(); + /* Allow the backslash to be pushed back. */ + pushstring("\\", 1, NULL); + return (pgetc()); + } + } + return (c); +} + +/* + * called by editline -- any expansions to the prompt + * should be added here. + */ +char * +getprompt(void *unused __unused) +{ + static char ps[PROMPTLEN]; + const char *fmt; + const char *pwd; + int i, trim; + static char internal_error[] = "??"; + + /* + * Select prompt format. + */ + switch (whichprompt) { + case 0: + fmt = ""; + break; + case 1: + fmt = ps1val(); + break; + case 2: + fmt = ps2val(); + break; + default: + return internal_error; + } + + /* + * Format prompt string. + */ + for (i = 0; (i < 127) && (*fmt != '\0'); i++, fmt++) + if (*fmt == '\\') + switch (*++fmt) { + + /* + * Hostname. + * + * \h specifies just the local hostname, + * \H specifies fully-qualified hostname. + */ + case 'h': + case 'H': + ps[i] = '\0'; + gethostname(&ps[i], PROMPTLEN - i); + /* Skip to end of hostname. */ + trim = (*fmt == 'h') ? '.' : '\0'; + while ((ps[i+1] != '\0') && (ps[i+1] != trim)) + i++; + break; + + /* + * Working directory. + * + * \W specifies just the final component, + * \w specifies the entire path. + */ + case 'W': + case 'w': + pwd = lookupvar("PWD"); + if (pwd == NULL) + pwd = "?"; + if (*fmt == 'W' && + *pwd == '/' && pwd[1] != '\0') + strlcpy(&ps[i], strrchr(pwd, '/') + 1, + PROMPTLEN - i); + else + strlcpy(&ps[i], pwd, PROMPTLEN - i); + /* Skip to end of path. */ + while (ps[i + 1] != '\0') + i++; + break; + + /* + * Superuser status. + * + * '$' for normal users, '#' for root. + */ + case '$': + ps[i] = (geteuid() != 0) ? '$' : '#'; + break; + + /* + * A literal \. + */ + case '\\': + ps[i] = '\\'; + break; + + /* + * Emit unrecognized formats verbatim. + */ + default: + ps[i++] = '\\'; + ps[i] = *fmt; + break; + } + else + ps[i] = *fmt; + ps[i] = '\0'; + return (ps); +} + + +const char * +expandstr(const char *ps) +{ + union node n; + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + const int saveprompt = doprompt; + struct parsefile *const savetopfile = getcurrentfile(); + struct parser_temp *const saveparser_temp = parser_temp; + const char *result = NULL; + + if (!setjmp(jmploc.loc)) { + handler = &jmploc; + parser_temp = NULL; + setinputstring(ps, 1); + doprompt = 0; + readtoken1(pgetc(), DQSYNTAX, "", 0); + if (backquotelist != NULL) + error("Command substitution not allowed here"); + + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + result = stackblock(); + INTOFF; + } + handler = savehandler; + doprompt = saveprompt; + popfilesupto(savetopfile); + if (parser_temp != saveparser_temp) { + parser_temp_free_all(); + parser_temp = saveparser_temp; + } + if (result != NULL) { + INTON; + } else if (exception == EXINT) + raise(SIGINT); + return result; +} diff --git a/external/sh/parser.h b/external/sh/parser.h new file mode 100644 index 00000000..241de010 --- /dev/null +++ b/external/sh/parser.h @@ -0,0 +1,84 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)parser.h 8.3 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/parser.h 261192 2014-01-26 21:19:33Z jilles $ + */ + +/* control characters in argument strings */ +#define CTLESC '\300' +#define CTLVAR '\301' +#define CTLENDVAR '\371' +#define CTLBACKQ '\372' +#define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ +/* CTLBACKQ | CTLQUOTE == '\373' */ +#define CTLARI '\374' +#define CTLENDARI '\375' +#define CTLQUOTEMARK '\376' +#define CTLQUOTEEND '\377' /* only for ${v+-...} */ + +/* variable substitution byte (follows CTLVAR) */ +#define VSTYPE 0x0f /* type of variable substitution */ +#define VSNUL 0x10 /* colon--treat the empty string as unset */ +#define VSLINENO 0x20 /* expansion of $LINENO, the line number \ + follows immediately */ +#define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ + +/* values of VSTYPE field */ +#define VSNORMAL 0x1 /* normal variable: $var or ${var} */ +#define VSMINUS 0x2 /* ${var-text} */ +#define VSPLUS 0x3 /* ${var+text} */ +#define VSQUESTION 0x4 /* ${var?message} */ +#define VSASSIGN 0x5 /* ${var=text} */ +#define VSTRIMLEFT 0x6 /* ${var#pattern} */ +#define VSTRIMLEFTMAX 0x7 /* ${var##pattern} */ +#define VSTRIMRIGHT 0x8 /* ${var%pattern} */ +#define VSTRIMRIGHTMAX 0x9 /* ${var%%pattern} */ +#define VSLENGTH 0xa /* ${#var} */ +#define VSERROR 0xb /* Syntax error, issue when expanded */ + + +/* + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL. + */ +#define NEOF ((union node *)-1) +extern int whichprompt; /* 1 == PS1, 2 == PS2 */ +extern const char *const parsekwd[]; + + +union node *parsecmd(int); +void forcealias(void); +void fixredir(union node *, const char *, int); +int goodname(const char *); +int isassignment(const char *); +char *getprompt(void *); +const char *expandstr(const char *); diff --git a/external/sh/printf.c b/external/sh/printf.c new file mode 100644 index 00000000..438b85ec --- /dev/null +++ b/external/sh/printf.c @@ -0,0 +1,686 @@ +/*- + * Copyright 2014 Garrett D'Amore <garrett@damore.org> + * Copyright 2010 Nexenta Systems, Inc. All rights reserved. + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * Important: This file is used both as a standalone program /usr/bin/printf + * and as a builtin for /bin/sh (#define SHELL). + */ + +#ifndef SHELL +#ifndef lint +static char const copyright[] = +"@(#) Copyright (c) 1989, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif /* not lint */ +#endif + +#ifndef lint +#if 0 +static char const sccsid[] = "@(#)printf.c 8.1 (Berkeley) 7/20/93"; +#endif +static const char rcsid[] = + "$FreeBSD: head/usr.bin/printf/printf.c 279503 2015-03-01 21:46:55Z jilles $"; +#endif /* not lint */ + +#include <sys/types.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#ifdef SHELL +#define main printfcmd +#include "bltin/bltin.h" +#include "options.h" +#endif + +#define PF(f, func) do { \ + char *b = NULL; \ + if (havewidth) \ + if (haveprec) \ + (void)asprintf(&b, f, fieldwidth, precision, func); \ + else \ + (void)asprintf(&b, f, fieldwidth, func); \ + else if (haveprec) \ + (void)asprintf(&b, f, precision, func); \ + else \ + (void)asprintf(&b, f, func); \ + if (b) { \ + (void)fputs(b, stdout); \ + free(b); \ + } \ +} while (0) + +static int asciicode(void); +static char *printf_doformat(char *, int *); +static int escape(char *, int, size_t *); +static int getchr(void); +static int getfloating(long double *, int); +static int getint(int *); +static int getnum(intmax_t *, uintmax_t *, int); +static const char + *getstr(void); +static char *mknum(char *, char); +static void usage(void); + +static const char digits[] = "0123456789"; + +static char end_fmt[1]; + +static int myargc; +static char **myargv; +static char **gargv; +static char **maxargv; + +int +main(int argc, char *argv[]) +{ + size_t len; + int end, rval; + char *format, *fmt, *start; +#ifndef SHELL + int ch; + + (void) setlocale(LC_ALL, ""); +#endif + +#ifdef SHELL + nextopt(""); + argc -= argptr - argv; + argv = argptr; +#else + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + return (1); + } + argc -= optind; + argv += optind; +#endif + + if (argc < 1) { + usage(); + return (1); + } + +#ifdef SHELL + INTOFF; +#endif + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. Note, + * format strings are reused as necessary to use up the provided + * arguments, arguments of zero/null string are provided to use + * up the format string. + */ + fmt = format = *argv; + escape(fmt, 1, &len); /* backslash interpretation */ + rval = end = 0; + gargv = ++argv; + + for (;;) { + maxargv = gargv; + + myargv = gargv; + for (myargc = 0; gargv[myargc]; myargc++) + /* nop */; + start = fmt; + while (fmt < format + len) { + if (fmt[0] == '%') { + fwrite(start, 1, fmt - start, stdout); + if (fmt[1] == '%') { + /* %% prints a % */ + putchar('%'); + fmt += 2; + } else { + fmt = printf_doformat(fmt, &rval); + if (fmt == NULL || fmt == end_fmt) { +#ifdef SHELL + INTON; +#endif + return (fmt == NULL ? 1 : rval); + } + end = 0; + } + start = fmt; + } else + fmt++; + if (gargv > maxargv) + maxargv = gargv; + } + gargv = maxargv; + + if (end == 1) { + warnx("missing format character"); +#ifdef SHELL + INTON; +#endif + return (1); + } + fwrite(start, 1, fmt - start, stdout); + if (!*gargv) { +#ifdef SHELL + INTON; +#endif + return (rval); + } + /* Restart at the beginning of the format string. */ + fmt = format; + end = 1; + } + /* NOTREACHED */ +} + + +static char * +printf_doformat(char *fmt, int *rval) +{ + static const char skip1[] = "#'-+ 0"; + int fieldwidth, haveprec, havewidth, mod_ldbl, precision; + char convch, nextch; + char start[strlen(fmt) + 1]; + char **fargv; + char *dptr; + int l; + + dptr = start; + *dptr++ = '%'; + *dptr = 0; + + fmt++; + + /* look for "n$" field index specifier */ + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + if (gargv > maxargv) + maxargv = gargv; + fmt += l + 1; + + /* save format argument */ + fargv = gargv; + } else { + fargv = NULL; + } + + /* skip to field width */ + while (*fmt && strchr(skip1, *fmt) != NULL) { + *dptr++ = *fmt++; + *dptr = 0; + } + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&fieldwidth)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + havewidth = 1; + + *dptr++ = '*'; + *dptr = 0; + } else { + havewidth = 0; + + /* skip to possible '.', get following precision */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + + if (*fmt == '.') { + /* precision present? */ + fmt++; + *dptr++ = '.'; + + if (*fmt == '*') { + + fmt++; + l = strspn(fmt, digits); + if ((l > 0) && (fmt[l] == '$')) { + int idx = atoi(fmt); + if (fargv == NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + if (idx <= myargc) { + gargv = &myargv[idx - 1]; + } else { + gargv = &myargv[myargc]; + } + fmt += l + 1; + } else if (fargv != NULL) { + warnx("incomplete use of n$"); + return (NULL); + } + + if (getint(&precision)) + return (NULL); + if (gargv > maxargv) + maxargv = gargv; + haveprec = 1; + *dptr++ = '*'; + *dptr = 0; + } else { + haveprec = 0; + + /* skip to conversion char */ + while (isdigit(*fmt)) { + *dptr++ = *fmt++; + *dptr = 0; + } + } + } else + haveprec = 0; + if (!*fmt) { + warnx("missing format character"); + return (NULL); + } + *dptr++ = *fmt; + *dptr = 0; + + /* + * Look for a length modifier. POSIX doesn't have these, so + * we only support them for floating-point conversions, which + * are extensions. This is useful because the L modifier can + * be used to gain extra range and precision, while omitting + * it is more likely to produce consistent results on different + * architectures. This is not so important for integers + * because overflow is the only bad thing that can happen to + * them, but consider the command printf %a 1.1 + */ + if (*fmt == 'L') { + mod_ldbl = 1; + fmt++; + if (!strchr("aAeEfFgG", *fmt)) { + warnx("bad modifier L for %%%c", *fmt); + return (NULL); + } + } else { + mod_ldbl = 0; + } + + /* save the current arg offset, and set to the format arg */ + if (fargv != NULL) { + gargv = fargv; + } + + convch = *fmt; + nextch = *++fmt; + + *fmt = '\0'; + switch (convch) { + case 'b': { + size_t len; + char *p; + int getout; + + p = strdup(getstr()); + if (p == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + getout = escape(p, 0, &len); + fputs(p, stdout); + free(p); + if (getout) + return (end_fmt); + break; + } + case 'c': { + char p; + + p = getchr(); + PF(start, p); + break; + } + case 's': { + const char *p; + + p = getstr(); + PF(start, p); + break; + } + case 'd': case 'i': case 'o': case 'u': case 'x': case 'X': { + char *f; + intmax_t val; + uintmax_t uval; + int signedconv; + + signedconv = (convch == 'd' || convch == 'i'); + if ((f = mknum(start, convch)) == NULL) + return (NULL); + if (getnum(&val, &uval, signedconv)) + *rval = 1; + if (signedconv) + PF(f, val); + else + PF(f, uval); + break; + } + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'a': case 'A': { + long double p; + + if (getfloating(&p, mod_ldbl)) + *rval = 1; + if (mod_ldbl) + PF(start, p); + else + PF(start, (double)p); + break; + } + default: + warnx("illegal format character %c", convch); + return (NULL); + } + *fmt = nextch; + /* return the gargv to the next element */ + return (fmt); +} + +static char * +mknum(char *str, char ch) +{ + static char *copy; + static size_t copy_size; + char *newcopy; + size_t len, newlen; + + len = strlen(str) + 2; + if (len > copy_size) { + newlen = ((len + 1023) >> 10) << 10; + if ((newcopy = realloc(copy, newlen)) == NULL) { + warnx("%s", strerror(ENOMEM)); + return (NULL); + } + copy = newcopy; + copy_size = newlen; + } + + memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return (copy); +} + +static int +escape(char *fmt, int percent, size_t *len) +{ + char *save, *store, c; + int value; + + for (save = store = fmt; ((c = *fmt) != 0); ++fmt, ++store) { + if (c != '\\') { + *store = c; + continue; + } + switch (*++fmt) { + case '\0': /* EOS, user error */ + *store = '\\'; + *++store = '\0'; + *len = store - save; + return (0); + case '\\': /* backslash */ + case '\'': /* single quote */ + *store = *fmt; + break; + case 'a': /* bell/alert */ + *store = '\a'; + break; + case 'b': /* backspace */ + *store = '\b'; + break; + case 'c': + if (!percent) { + *store = '\0'; + *len = store - save; + return (1); + } + *store = 'c'; + break; + case 'f': /* form-feed */ + *store = '\f'; + break; + case 'n': /* newline */ + *store = '\n'; + break; + case 'r': /* carriage-return */ + *store = '\r'; + break; + case 't': /* horizontal tab */ + *store = '\t'; + break; + case 'v': /* vertical tab */ + *store = '\v'; + break; + /* octal constant */ + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + c = (!percent && *fmt == '0') ? 4 : 3; + for (value = 0; + c-- && *fmt >= '0' && *fmt <= '7'; ++fmt) { + value <<= 3; + value += *fmt - '0'; + } + --fmt; + if (percent && value == '%') { + *store++ = '%'; + *store = '%'; + } else + *store = (char)value; + break; + default: + *store = *fmt; + break; + } + } + *store = '\0'; + *len = store - save; + return (0); +} + +static int +getchr(void) +{ + if (!*gargv) + return ('\0'); + return ((int)**gargv++); +} + +static const char * +getstr(void) +{ + if (!*gargv) + return (""); + return (*gargv++); +} + +static int +getint(int *ip) +{ + intmax_t val; + uintmax_t uval; + int rval; + + if (getnum(&val, &uval, 1)) + return (1); + rval = 0; + if (val < INT_MIN || val > INT_MAX) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + *ip = (int)val; + return (rval); +} + +static int +getnum(intmax_t *ip, uintmax_t *uip, int signedconv) +{ + char *ep; + int rval; + + if (!*gargv) { + *ip = *uip = 0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + if (signedconv) + *ip = asciicode(); + else + *uip = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (signedconv) + *ip = strtoimax(*gargv, &ep, 0); + else + *uip = strtoumax(*gargv, &ep, 0); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } + else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +getfloating(long double *dp, int mod_ldbl) +{ + char *ep; + int rval; + + if (!*gargv) { + *dp = 0.0; + return (0); + } + if (**gargv == '"' || **gargv == '\'') { + *dp = asciicode(); + return (0); + } + rval = 0; + errno = 0; + if (mod_ldbl) + *dp = strtold(*gargv, &ep); + else + *dp = strtod(*gargv, &ep); + if (ep == *gargv) { + warnx("%s: expected numeric value", *gargv); + rval = 1; + } else if (*ep != '\0') { + warnx("%s: not completely converted", *gargv); + rval = 1; + } + if (errno == ERANGE) { + warnx("%s: %s", *gargv, strerror(ERANGE)); + rval = 1; + } + ++gargv; + return (rval); +} + +static int +asciicode(void) +{ + int ch; + wchar_t wch; + mbstate_t mbs; + + ch = (unsigned char)**gargv; + if (ch == '\'' || ch == '"') { + memset(&mbs, 0, sizeof(mbs)); + switch (mbrtowc(&wch, *gargv + 1, MB_LEN_MAX, &mbs)) { + case (size_t)-2: + case (size_t)-1: + wch = (unsigned char)gargv[0][1]; + break; + case 0: + wch = 0; + break; + } + ch = wch; + } + ++gargv; + return (ch); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: printf format [arguments ...]\n"); +} diff --git a/external/sh/redir.c b/external/sh/redir.c new file mode 100644 index 00000000..0c3653e5 --- /dev/null +++ b/external/sh/redir.c @@ -0,0 +1,348 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)redir.c 8.2 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/redir.c 272575 2014-10-05 21:51:36Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> +#include <signal.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> + +/* + * Code for dealing with input/output redirection. + */ + +#include "shell.h" +#include "nodes.h" +#include "jobs.h" +#include "expand.h" +#include "redir.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "options.h" + + +#define EMPTY -2 /* marks an unused slot in redirtab */ +#define CLOSED -1 /* fd was not open before redir */ + + +struct redirtab { + struct redirtab *next; + int renamed[10]; + int fd0_redirected; +}; + + +static struct redirtab *redirlist; + +/* + * We keep track of whether or not fd0 has been redirected. This is for + * background commands, where we want to redirect fd0 to /dev/null only + * if it hasn't already been redirected. +*/ +static int fd0_redirected = 0; + +static void openredirect(union node *, char[10 ]); +static int openhere(union node *); + + +/* + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. +* + * We suppress interrupts so that we won't leave open file + * descriptors around. Because the signal handler remains + * installed and we do not use system call restart, interrupts + * will still abort blocking opens such as fifos (they will fail + * with EINTR). There is, however, a race condition if an interrupt + * arrives after INTOFF and before open blocks. + */ + +void +redirect(union node *redir, int flags) +{ + union node *n; + struct redirtab *sv = NULL; + int i; + int fd; + char memory[10]; /* file descriptors to write to memory */ + + INTOFF; + for (i = 10 ; --i >= 0 ; ) + memory[i] = 0; + memory[1] = flags & REDIR_BACKQ; + if (flags & REDIR_PUSH) { + sv = ckmalloc(sizeof (struct redirtab)); + for (i = 0 ; i < 10 ; i++) + sv->renamed[i] = EMPTY; + sv->fd0_redirected = fd0_redirected; + sv->next = redirlist; + redirlist = sv; + } + for (n = redir ; n ; n = n->nfile.next) { + fd = n->nfile.fd; + if (fd == 0) + fd0_redirected = 1; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) && + n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ + + if ((flags & REDIR_PUSH) && sv->renamed[fd] == EMPTY) { + INTOFF; + if ((i = fcntl(fd, F_DUPFD_CLOEXEC, 10)) == -1) { + switch (errno) { + case EBADF: + i = CLOSED; + break; + default: + INTON; + error("%d: %s", fd, strerror(errno)); + break; + } + } + sv->renamed[fd] = i; + INTON; + } + openredirect(n, memory); + INTON; + INTOFF; + } + if (memory[1]) + out1 = &memout; + if (memory[2]) + out2 = &memout; + INTON; +} + + +static void +openredirect(union node *redir, char memory[10]) +{ + struct stat sb; + int fd = redir->nfile.fd; + const char *fname; + int f; + int e; + + memory[fd] = 0; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDONLY)) < 0) + error("cannot open %s: %s", fname, strerror(errno)); + break; + case NFROMTO: + fname = redir->nfile.expfname; + if ((f = open(fname, O_RDWR|O_CREAT, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTO: + if (Cflag) { + fname = redir->nfile.expfname; + if (stat(fname, &sb) == -1) { + if ((f = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + } else if (!S_ISREG(sb.st_mode)) { + if ((f = open(fname, O_WRONLY, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + if (fstat(f, &sb) != -1 && S_ISREG(sb.st_mode)) { + close(f); + error("cannot create %s: %s", fname, + strerror(EEXIST)); + } + } else + error("cannot create %s: %s", fname, + strerror(EEXIST)); + break; + } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NAPPEND: + fname = redir->nfile.expfname; + if ((f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666)) < 0) + error("cannot create %s: %s", fname, strerror(errno)); + break; + case NTOFD: + case NFROMFD: + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + if (memory[redir->ndup.dupfd]) + memory[fd] = 1; + else { + if (dup2(redir->ndup.dupfd, fd) < 0) + error("%d: %s", redir->ndup.dupfd, + strerror(errno)); + } + } else { + close(fd); + } + return; + case NHERE: + case NXHERE: + f = openhere(redir); + break; + default: + abort(); + } + if (f != fd) { + if (dup2(f, fd) == -1) { + e = errno; + close(f); + error("%d: %s", fd, strerror(e)); + } + close(f); + } +} + + +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ + +static int +openhere(union node *redir) +{ + const char *p; + int pip[2]; + size_t len = 0; + int flags; + ssize_t written = 0; + + if (pipe(pip) < 0) + error("Pipe call failed: %s", strerror(errno)); + + if (redir->type == NXHERE) + p = redir->nhere.expdoc; + else + p = redir->nhere.doc->narg.text; + len = strlen(p); + if (len == 0) + goto out; + flags = fcntl(pip[1], F_GETFL, 0); + if (flags != -1 && fcntl(pip[1], F_SETFL, flags | O_NONBLOCK) != -1) { + written = write(pip[1], p, len); + if (written < 0) + written = 0; + if ((size_t)written == len) + goto out; + fcntl(pip[1], F_SETFL, flags); + } + + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGTSTP, SIG_IGN); + signal(SIGPIPE, SIG_DFL); + xwrite(pip[1], p + written, len - written); + _exit(0); + } +out: + close(pip[1]); + return pip[0]; +} + + + +/* + * Undo the effects of the last redirection. + */ + +void +popredir(void) +{ + struct redirtab *rp = redirlist; + int i; + + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] != EMPTY) { + if (rp->renamed[i] >= 0) { + dup2(rp->renamed[i], i); + close(rp->renamed[i]); + } else { + close(i); + } + } + } + INTOFF; + fd0_redirected = rp->fd0_redirected; + redirlist = rp->next; + ckfree(rp); + INTON; +} + +/* Return true if fd 0 has already been redirected at least once. */ +int +fd0_redirected_p(void) +{ + return fd0_redirected != 0; +} + +/* + * Discard all saved file descriptors. + */ + +void +clearredir(void) +{ + struct redirtab *rp; + int i; + + for (rp = redirlist ; rp ; rp = rp->next) { + for (i = 0 ; i < 10 ; i++) { + if (rp->renamed[i] >= 0) { + close(rp->renamed[i]); + } + rp->renamed[i] = EMPTY; + } + } +} diff --git a/external/sh/redir.h b/external/sh/redir.h new file mode 100644 index 00000000..7146dd1f --- /dev/null +++ b/external/sh/redir.h @@ -0,0 +1,45 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)redir.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/redir.h 254426 2013-08-16 20:24:41Z jilles $ + */ + +/* flags passed to redirect */ +#define REDIR_PUSH 01 /* save previous values of file descriptors */ +#define REDIR_BACKQ 02 /* save the command output in memory */ + +union node; +void redirect(union node *, int); +void popredir(void); +int fd0_redirected_p(void); +void clearredir(void); + diff --git a/external/sh/shell.h b/external/sh/shell.h new file mode 100644 index 00000000..add2bd87 --- /dev/null +++ b/external/sh/shell.h @@ -0,0 +1,77 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)shell.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/shell.h 253658 2013-07-25 19:48:15Z jilles $ + */ + +#ifndef SHELL_H_ +#define SHELL_H_ + +#include <inttypes.h> + +/* + * The follow should be set to reflect the type of system you have: + * JOBS -> 1 if you have Berkeley job control, 0 otherwise. + * define DEBUG=1 to compile in debugging (set global "debug" to turn on) + * define DEBUG=2 to compile in and turn on debugging. + * + * When debugging is on, debugging info will be written to ./trace and + * a quit signal will generate a core dump. + */ + + +#define JOBS 1 +/* #define DEBUG 1 */ + +/* + * Type of used arithmetics. SUSv3 requires us to have at least signed long. + */ +typedef intmax_t arith_t; +#define ARITH_FORMAT_STR "%" PRIdMAX +#define atoarith_t(arg) strtoimax(arg, NULL, 0) +#define strtoarith_t(nptr, endptr, base) strtoimax(nptr, endptr, base) +#define ARITH_MIN INTMAX_MIN +#define ARITH_MAX INTMAX_MAX + +typedef void *pointer; + +#include <sys/cdefs.h> + +extern char nullstr[1]; /* null string */ + +#ifdef DEBUG +#define TRACE(param) sh_trace param +#else +#define TRACE(param) +#endif + +#endif /* !SHELL_H_ */ diff --git a/external/sh/show.c b/external/sh/show.c new file mode 100644 index 00000000..0e8cf433 --- /dev/null +++ b/external/sh/show.c @@ -0,0 +1,408 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)show.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/show.c 258776 2013-11-30 21:27:11Z jilles $"); + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> + +#include "shell.h" +#include "parser.h" +#include "nodes.h" +#include "mystring.h" +#include "show.h" + + +#ifdef DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + + +void +showtree(union node *n) +{ + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} + + +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch(n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; +binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "<node type %d>", n->type); + if (ind >= 0) + putc('\n', fp); + break; + } +} + + + +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args ; np ; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect ; np ; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NHERE: s = "<<"; dftfd = 0; break; + case NXHERE: s = "<<"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + if (np->ndup.dupfd >= 0) + fprintf(fp, "%d", np->ndup.dupfd); + else + fprintf(fp, "-"); + } else if (np->nfile.type == NHERE) { + fprintf(fp, "HERE"); + } else if (np->nfile.type == NXHERE) { + fprintf(fp, "XHERE"); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} + + + +static void +sharg(union node *arg, FILE *fp) +{ + char *p; + struct nodelist *bqlist; + int subtype; + + if (arg->type != NARG) { + printf("<node type %d>\n", arg->type); + fflush(stdout); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text ; *p ; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); + + while (*p != '=') + putc(*p++, fp); + + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + printf("<subtype %d>", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); + break; + } + } +} + + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0 ; i < amount ; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } +} + + +/* + * Debugging stuff. + */ + + +FILE *tracefile; + +#if DEBUG >= 2 +int debug = 1; +#else +int debug = 0; +#endif + + +void +trputc(int c) +{ + if (tracefile == NULL) + return; + putc(c, tracefile); + if (c == '\n') + fflush(tracefile); +} + + +void +sh_trace(const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + if (tracefile != NULL) { + (void) vfprintf(tracefile, fmt, va); + if (strchr(fmt, '\n')) + (void) fflush(tracefile); + } + va_end(va); +} + + +void +trputs(const char *s) +{ + if (tracefile == NULL) + return; + fputs(s, tracefile); + if (strchr(s, '\n')) + fflush(tracefile); +} + + +static void +trstring(char *s) +{ + char *p; + char c; + + if (tracefile == NULL) + return; + putc('"', tracefile); + for (p = s ; *p ; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; +backslash: putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; + } + } + putc('"', tracefile); +} + + +void +trargs(char **ap) +{ + if (tracefile == NULL) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); + } + fflush(tracefile); +} + + +void +opentrace(void) +{ + char s[100]; + int flags; + + if (!debug) + return; +#ifdef not_this_way + { + char *p; + if ((p = getenv("HOME")) == NULL) { + if (geteuid() == 0) + p = "/"; + else + p = "/tmp"; + } + strcpy(s, p); + strcat(s, "/trace"); + } +#else + strcpy(s, "./trace"); +#endif /* not_this_way */ + if ((tracefile = fopen(s, "a")) == NULL) { + fprintf(stderr, "Can't open %s: %s\n", s, strerror(errno)); + return; + } + if ((flags = fcntl(fileno(tracefile), F_GETFL, 0)) >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); + fputs("\nTracing started.\n", tracefile); + fflush(tracefile); +} +#endif /* DEBUG */ diff --git a/external/sh/show.h b/external/sh/show.h new file mode 100644 index 00000000..d9b16669 --- /dev/null +++ b/external/sh/show.h @@ -0,0 +1,40 @@ +/*- + * Copyright (c) 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)show.h 1.1 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/show.h 200956 2009-12-24 18:41:14Z jilles $ + */ + +void showtree(union node *); +#ifdef DEBUG +void sh_trace(const char *, ...) __printflike(1, 2); +void trargs(char **); +void trputc(int); +void trputs(const char *); +void opentrace(void); +#endif diff --git a/external/sh/syntax.c b/external/sh/syntax.c new file mode 100644 index 00000000..28f9b62d --- /dev/null +++ b/external/sh/syntax.c @@ -0,0 +1,192 @@ +/* + * This file was generated by the mksyntax program. + */ + +#include "parser.h" +#include "shell.h" +#include "syntax.h" + +/* syntax table used when not in quotes */ +const char basesyntax[SYNBASE + CHAR_MAX + 1] = { + [SYNBASE + PEOF] = CEOF, + [SYNBASE + CTLESC] = CCTL, + [SYNBASE + CTLVAR] = CCTL, + [SYNBASE + CTLENDVAR] = CCTL, + [SYNBASE + CTLBACKQ] = CCTL, + [SYNBASE + CTLBACKQ + CTLQUOTE] = CCTL, + [SYNBASE + CTLARI] = CCTL, + [SYNBASE + CTLENDARI] = CCTL, + [SYNBASE + CTLQUOTEMARK] = CCTL, + [SYNBASE + CTLQUOTEEND] = CCTL, + [SYNBASE + '\n'] = CNL, + [SYNBASE + '\\'] = CBACK, + [SYNBASE + '\''] = CSQUOTE, + [SYNBASE + '"'] = CDQUOTE, + [SYNBASE + '`'] = CBQUOTE, + [SYNBASE + '$'] = CVAR, + [SYNBASE + '}'] = CENDVAR, + [SYNBASE + '<'] = CSPCL, + [SYNBASE + '>'] = CSPCL, + [SYNBASE + '('] = CSPCL, + [SYNBASE + ')'] = CSPCL, + [SYNBASE + ';'] = CSPCL, + [SYNBASE + '&'] = CSPCL, + [SYNBASE + '|'] = CSPCL, + [SYNBASE + ' '] = CSPCL, + [SYNBASE + '\t'] = CSPCL, +}; + +/* syntax table used when in double quotes */ +const char dqsyntax[SYNBASE + CHAR_MAX + 1] = { + [SYNBASE + PEOF] = CEOF, + [SYNBASE + CTLESC] = CCTL, + [SYNBASE + CTLVAR] = CCTL, + [SYNBASE + CTLENDVAR] = CCTL, + [SYNBASE + CTLBACKQ] = CCTL, + [SYNBASE + CTLBACKQ + CTLQUOTE] = CCTL, + [SYNBASE + CTLARI] = CCTL, + [SYNBASE + CTLENDARI] = CCTL, + [SYNBASE + CTLQUOTEMARK] = CCTL, + [SYNBASE + CTLQUOTEEND] = CCTL, + [SYNBASE + '\n'] = CNL, + [SYNBASE + '\\'] = CBACK, + [SYNBASE + '"'] = CENDQUOTE, + [SYNBASE + '`'] = CBQUOTE, + [SYNBASE + '$'] = CVAR, + [SYNBASE + '}'] = CENDVAR, + [SYNBASE + '!'] = CCTL, + [SYNBASE + '*'] = CCTL, + [SYNBASE + '?'] = CCTL, + [SYNBASE + '['] = CCTL, + [SYNBASE + ']'] = CCTL, + [SYNBASE + '='] = CCTL, + [SYNBASE + '~'] = CCTL, + [SYNBASE + ':'] = CCTL, + [SYNBASE + '/'] = CCTL, + [SYNBASE + '-'] = CCTL, + [SYNBASE + '^'] = CCTL, +}; + +/* syntax table used when in single quotes */ +const char sqsyntax[SYNBASE + CHAR_MAX + 1] = { + [SYNBASE + PEOF] = CEOF, + [SYNBASE + CTLESC] = CCTL, + [SYNBASE + CTLVAR] = CCTL, + [SYNBASE + CTLENDVAR] = CCTL, + [SYNBASE + CTLBACKQ] = CCTL, + [SYNBASE + CTLBACKQ + CTLQUOTE] = CCTL, + [SYNBASE + CTLARI] = CCTL, + [SYNBASE + CTLENDARI] = CCTL, + [SYNBASE + CTLQUOTEMARK] = CCTL, + [SYNBASE + CTLQUOTEEND] = CCTL, + [SYNBASE + '\n'] = CNL, + [SYNBASE + '\\'] = CSBACK, + [SYNBASE + '\''] = CENDQUOTE, + [SYNBASE + '!'] = CCTL, + [SYNBASE + '*'] = CCTL, + [SYNBASE + '?'] = CCTL, + [SYNBASE + '['] = CCTL, + [SYNBASE + ']'] = CCTL, + [SYNBASE + '='] = CCTL, + [SYNBASE + '~'] = CCTL, + [SYNBASE + ':'] = CCTL, + [SYNBASE + '/'] = CCTL, + [SYNBASE + '-'] = CCTL, + [SYNBASE + '^'] = CCTL, +}; + +/* syntax table used when in arithmetic */ +const char arisyntax[SYNBASE + CHAR_MAX + 1] = { + [SYNBASE + PEOF] = CEOF, + [SYNBASE + CTLESC] = CCTL, + [SYNBASE + CTLVAR] = CCTL, + [SYNBASE + CTLENDVAR] = CCTL, + [SYNBASE + CTLBACKQ] = CCTL, + [SYNBASE + CTLBACKQ + CTLQUOTE] = CCTL, + [SYNBASE + CTLARI] = CCTL, + [SYNBASE + CTLENDARI] = CCTL, + [SYNBASE + CTLQUOTEMARK] = CCTL, + [SYNBASE + CTLQUOTEEND] = CCTL, + [SYNBASE + '\n'] = CNL, + [SYNBASE + '\\'] = CBACK, + [SYNBASE + '`'] = CBQUOTE, + [SYNBASE + '"'] = CIGN, + [SYNBASE + '$'] = CVAR, + [SYNBASE + '}'] = CENDVAR, + [SYNBASE + '('] = CLP, + [SYNBASE + ')'] = CRP, +}; + +/* character classification table */ +const char is_type[SYNBASE + CHAR_MAX + 1] = { + [SYNBASE + '0'] = ISDIGIT, + [SYNBASE + '1'] = ISDIGIT, + [SYNBASE + '2'] = ISDIGIT, + [SYNBASE + '3'] = ISDIGIT, + [SYNBASE + '4'] = ISDIGIT, + [SYNBASE + '5'] = ISDIGIT, + [SYNBASE + '6'] = ISDIGIT, + [SYNBASE + '7'] = ISDIGIT, + [SYNBASE + '8'] = ISDIGIT, + [SYNBASE + '9'] = ISDIGIT, + [SYNBASE + 'a'] = ISLOWER, + [SYNBASE + 'b'] = ISLOWER, + [SYNBASE + 'c'] = ISLOWER, + [SYNBASE + 'd'] = ISLOWER, + [SYNBASE + 'e'] = ISLOWER, + [SYNBASE + 'f'] = ISLOWER, + [SYNBASE + 'g'] = ISLOWER, + [SYNBASE + 'h'] = ISLOWER, + [SYNBASE + 'i'] = ISLOWER, + [SYNBASE + 'j'] = ISLOWER, + [SYNBASE + 'k'] = ISLOWER, + [SYNBASE + 'l'] = ISLOWER, + [SYNBASE + 'm'] = ISLOWER, + [SYNBASE + 'n'] = ISLOWER, + [SYNBASE + 'o'] = ISLOWER, + [SYNBASE + 'p'] = ISLOWER, + [SYNBASE + 'q'] = ISLOWER, + [SYNBASE + 'r'] = ISLOWER, + [SYNBASE + 's'] = ISLOWER, + [SYNBASE + 't'] = ISLOWER, + [SYNBASE + 'u'] = ISLOWER, + [SYNBASE + 'v'] = ISLOWER, + [SYNBASE + 'w'] = ISLOWER, + [SYNBASE + 'x'] = ISLOWER, + [SYNBASE + 'y'] = ISLOWER, + [SYNBASE + 'z'] = ISLOWER, + [SYNBASE + 'A'] = ISUPPER, + [SYNBASE + 'B'] = ISUPPER, + [SYNBASE + 'C'] = ISUPPER, + [SYNBASE + 'D'] = ISUPPER, + [SYNBASE + 'E'] = ISUPPER, + [SYNBASE + 'F'] = ISUPPER, + [SYNBASE + 'G'] = ISUPPER, + [SYNBASE + 'H'] = ISUPPER, + [SYNBASE + 'I'] = ISUPPER, + [SYNBASE + 'J'] = ISUPPER, + [SYNBASE + 'K'] = ISUPPER, + [SYNBASE + 'L'] = ISUPPER, + [SYNBASE + 'M'] = ISUPPER, + [SYNBASE + 'N'] = ISUPPER, + [SYNBASE + 'O'] = ISUPPER, + [SYNBASE + 'P'] = ISUPPER, + [SYNBASE + 'Q'] = ISUPPER, + [SYNBASE + 'R'] = ISUPPER, + [SYNBASE + 'S'] = ISUPPER, + [SYNBASE + 'T'] = ISUPPER, + [SYNBASE + 'U'] = ISUPPER, + [SYNBASE + 'V'] = ISUPPER, + [SYNBASE + 'W'] = ISUPPER, + [SYNBASE + 'X'] = ISUPPER, + [SYNBASE + 'Y'] = ISUPPER, + [SYNBASE + 'Z'] = ISUPPER, + [SYNBASE + '_'] = ISUNDER, + [SYNBASE + '#'] = ISSPECL, + [SYNBASE + '?'] = ISSPECL, + [SYNBASE + '$'] = ISSPECL, + [SYNBASE + '!'] = ISSPECL, + [SYNBASE + '-'] = ISSPECL, + [SYNBASE + '*'] = ISSPECL, + [SYNBASE + '@'] = ISSPECL, +}; diff --git a/external/sh/syntax.h b/external/sh/syntax.h new file mode 100644 index 00000000..e9cf2e90 --- /dev/null +++ b/external/sh/syntax.h @@ -0,0 +1,54 @@ +/* + * This file was generated by the mksyntax program. + */ + +#include <sys/cdefs.h> +#include <limits.h> + +/* Syntax classes */ +#define CWORD 0 /* character is nothing special */ +#define CNL 1 /* newline character */ +#define CBACK 2 /* a backslash character */ +#define CSBACK 3 /* a backslash character in single quotes */ +#define CSQUOTE 4 /* single quote */ +#define CDQUOTE 5 /* double quote */ +#define CENDQUOTE 6 /* a terminating quote */ +#define CBQUOTE 7 /* backwards single quote */ +#define CVAR 8 /* a dollar sign */ +#define CENDVAR 9 /* a '}' character */ +#define CLP 10 /* a left paren in arithmetic */ +#define CRP 11 /* a right paren in arithmetic */ +#define CEOF 12 /* end of file */ +#define CCTL 13 /* like CWORD, except it must be escaped */ +#define CSPCL 14 /* these terminate a word */ +#define CIGN 15 /* character should be ignored */ + +/* Syntax classes for is_ functions */ +#define ISDIGIT 01 /* a digit */ +#define ISUPPER 02 /* an upper case letter */ +#define ISLOWER 04 /* a lower case letter */ +#define ISUNDER 010 /* an underscore */ +#define ISSPECL 020 /* the name of a special parameter */ + +#define SYNBASE (1 - CHAR_MIN) +#define PEOF -SYNBASE + + +#define BASESYNTAX (basesyntax + SYNBASE) +#define DQSYNTAX (dqsyntax + SYNBASE) +#define SQSYNTAX (sqsyntax + SYNBASE) +#define ARISYNTAX (arisyntax + SYNBASE) + +#define is_digit(c) ((unsigned int)((c) - '0') <= 9) +#define is_eof(c) ((c) == PEOF) +#define is_alpha(c) ((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER)) +#define is_name(c) ((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER)) +#define is_in_name(c) ((is_type+SYNBASE)[(int)c] & (ISUPPER|ISLOWER|ISUNDER|ISDIGIT)) +#define is_special(c) ((is_type+SYNBASE)[(int)c] & (ISSPECL|ISDIGIT)) +#define digit_val(c) ((c) - '0') + +extern const char basesyntax[]; +extern const char dqsyntax[]; +extern const char sqsyntax[]; +extern const char arisyntax[]; +extern const char is_type[]; diff --git a/external/sh/test.c b/external/sh/test.c new file mode 100644 index 00000000..446c0a93 --- /dev/null +++ b/external/sh/test.c @@ -0,0 +1,599 @@ +/* $NetBSD: test.c,v 1.21 1999/04/05 09:48:38 kleink Exp $ */ + +/*- + * test(1); version 7-like -- author Erik Baalbergen + * modified by Eric Gisin to be used as built-in. + * modified by Arnold Robbins to add SVR3 compatibility + * (-x -c -b -p -u -g -k) plus Korn's -L -nt -ot -ef and new -S (socket). + * modified by J.T. Conklin for NetBSD. + * + * This program is in the Public Domain. + */ +/* + * Important: This file is used both as a standalone program /bin/test and + * as a builtin for /bin/sh (#define SHELL). + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/test/test.c 259017 2013-12-05 22:53:32Z jilles $"); + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#ifdef SHELL +#define main testcmd +#include "bltin/bltin.h" +#else +#include <locale.h> + +static void error(const char *, ...) __dead2 __printf0like(1, 2); + +static void +error(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} +#endif + +/* test(1) accepts the following grammar: + oexpr ::= aexpr | aexpr "-o" oexpr ; + aexpr ::= nexpr | nexpr "-a" aexpr ; + nexpr ::= primary | "!" primary + primary ::= unary-operator operand + | operand binary-operator operand + | operand + | "(" oexpr ")" + ; + unary-operator ::= "-r"|"-w"|"-x"|"-f"|"-d"|"-c"|"-b"|"-p"| + "-u"|"-g"|"-k"|"-s"|"-t"|"-z"|"-n"|"-o"|"-O"|"-G"|"-L"|"-S"; + + binary-operator ::= "="|"!="|"-eq"|"-ne"|"-ge"|"-gt"|"-le"|"-lt"| + "-nt"|"-ot"|"-ef"; + operand ::= <any legal UNIX file name> +*/ + +enum token_types { + UNOP = 0x100, + BINOP = 0x200, + BUNOP = 0x300, + BBINOP = 0x400, + PAREN = 0x500 +}; + +enum token { + EOI, + OPERAND, + FILRD = UNOP + 1, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + STREZ, + STRNZ, + FILUID, + FILGID, + FILNT = BINOP + 1, + FILOT, + FILEQ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT = BUNOP + 1, + BAND = BBINOP + 1, + BOR, + LPAREN = PAREN + 1, + RPAREN +}; + +#define TOKEN_TYPE(token) ((token) & 0xff00) + +static struct t_op { + char op_text[4]; + short op_num; +} const ops [] = { + {"-r", FILRD}, + {"-w", FILWR}, + {"-x", FILEX}, + {"-e", FILEXIST}, + {"-f", FILREG}, + {"-d", FILDIR}, + {"-c", FILCDEV}, + {"-b", FILBDEV}, + {"-p", FILFIFO}, + {"-u", FILSUID}, + {"-g", FILSGID}, + {"-k", FILSTCK}, + {"-s", FILGZ}, + {"-t", FILTT}, + {"-z", STREZ}, + {"-n", STRNZ}, + {"-h", FILSYM}, /* for backwards compat */ + {"-O", FILUID}, + {"-G", FILGID}, + {"-L", FILSYM}, + {"-S", FILSOCK}, + {"=", STREQ}, + {"==", STREQ}, + {"!=", STRNE}, + {"<", STRLT}, + {">", STRGT}, + {"-eq", INTEQ}, + {"-ne", INTNE}, + {"-ge", INTGE}, + {"-gt", INTGT}, + {"-le", INTLE}, + {"-lt", INTLT}, + {"-nt", FILNT}, + {"-ot", FILOT}, + {"-ef", FILEQ}, + {"!", UNOT}, + {"-a", BAND}, + {"-o", BOR}, + {"(", LPAREN}, + {")", RPAREN}, + {"", 0} +}; + +static int nargc; +static char **t_wp; +static int parenlevel; + +static int aexpr(enum token); +static int binop(enum token); +static int equalf(const char *, const char *); +static int filstat(char *, enum token); +static int getn(const char *); +static intmax_t getq(const char *); +static int intcmp(const char *, const char *); +static int isunopoperand(void); +static int islparenoperand(void); +static int isrparenoperand(void); +static int newerf(const char *, const char *); +static int nexpr(enum token); +static int oexpr(enum token); +static int olderf(const char *, const char *); +static int primary(enum token); +static void syntax(const char *, const char *); +static enum token t_lex(char *); + +int +main(int argc, char **argv) +{ + int res; + char *p; + + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + p++; + if (strcmp(p, "[") == 0) { + if (strcmp(argv[--argc], "]") != 0) + error("missing ]"); + argv[argc] = NULL; + } + + /* no expression => false */ + if (--argc <= 0) + return 1; + +#ifndef SHELL + (void)setlocale(LC_CTYPE, ""); +#endif + nargc = argc; + t_wp = &argv[1]; + parenlevel = 0; + if (nargc == 4 && strcmp(*t_wp, "!") == 0) { + /* Things like ! "" -o x do not fit in the normal grammar. */ + --nargc; + ++t_wp; + res = oexpr(t_lex(*t_wp)); + } else + res = !oexpr(t_lex(*t_wp)); + + if (--nargc > 0) + syntax(*t_wp, "unexpected operator"); + + return res; +} + +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} + +static int +oexpr(enum token n) +{ + int res; + + res = aexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BOR) + return oexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) || + res; + t_wp--; + nargc++; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) == BAND) + return aexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) && + res; + t_wp--; + nargc++; + return res; +} + +static int +nexpr(enum token n) +{ + if (n == UNOT) + return !nexpr(t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + parenlevel++; + if ((nn = t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL)) == + RPAREN) { + parenlevel--; + return 0; /* missing expression */ + } + res = oexpr(nn); + if (t_lex(nargc > 0 ? (--nargc, *++t_wp) : NULL) != RPAREN) + syntax(NULL, "closing paren expected"); + parenlevel--; + return res; + } + if (TOKEN_TYPE(n) == UNOP) { + /* unary expression */ + if (--nargc == 0) + syntax(NULL, "argument expected"); /* impossible */ + switch (n) { + case STREZ: + return strlen(*++t_wp) == 0; + case STRNZ: + return strlen(*++t_wp) != 0; + case FILTT: + return isatty(getn(*++t_wp)); + default: + return filstat(*++t_wp, n); + } + } + + nn = t_lex(nargc > 0 ? t_wp[1] : NULL); + if (TOKEN_TYPE(nn) == BINOP) + return binop(nn); + + return strlen(*t_wp) > 0; +} + +static int +binop(enum token n) +{ + const char *opnd1, *op, *opnd2; + + opnd1 = *t_wp; + op = nargc > 0 ? (--nargc, *++t_wp) : NULL; + + if ((opnd2 = nargc > 0 ? (--nargc, *++t_wp) : NULL) == NULL) + syntax(op, "argument expected"); + + switch (n) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return intcmp(opnd1, opnd2) == 0; + case INTNE: + return intcmp(opnd1, opnd2) != 0; + case INTGE: + return intcmp(opnd1, opnd2) >= 0; + case INTGT: + return intcmp(opnd1, opnd2) > 0; + case INTLE: + return intcmp(opnd1, opnd2) <= 0; + case INTLT: + return intcmp(opnd1, opnd2) < 0; + case FILNT: + return newerf (opnd1, opnd2); + case FILOT: + return olderf (opnd1, opnd2); + case FILEQ: + return equalf (opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +static int +filstat(char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return (eaccess(nm, R_OK) == 0); + case FILWR: + return (eaccess(nm, W_OK) == 0); + case FILEX: + /* XXX work around eaccess(2) false positives for superuser */ + if (eaccess(nm, X_OK) != 0) + return 0; + if (S_ISDIR(s.st_mode) || geteuid() != 0) + return 1; + return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0; + case FILEXIST: + return (eaccess(nm, F_OK) == 0); + case FILREG: + return S_ISREG(s.st_mode); + case FILDIR: + return S_ISDIR(s.st_mode); + case FILCDEV: + return S_ISCHR(s.st_mode); + case FILBDEV: + return S_ISBLK(s.st_mode); + case FILFIFO: + return S_ISFIFO(s.st_mode); + case FILSOCK: + return S_ISSOCK(s.st_mode); + case FILSYM: + return S_ISLNK(s.st_mode); + case FILSUID: + return (s.st_mode & S_ISUID) != 0; + case FILSGID: + return (s.st_mode & S_ISGID) != 0; + case FILSTCK: + return (s.st_mode & S_ISVTX) != 0; + case FILGZ: + return s.st_size > (off_t)0; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } +} + +static enum token +t_lex(char *s) +{ + struct t_op const *op = ops; + + if (s == 0) { + return EOI; + } + while (*op->op_text) { + if (strcmp(s, op->op_text) == 0) { + if (((TOKEN_TYPE(op->op_num) == UNOP || + TOKEN_TYPE(op->op_num) == BUNOP) + && isunopoperand()) || + (op->op_num == LPAREN && islparenoperand()) || + (op->op_num == RPAREN && isrparenoperand())) + break; + return op->op_num; + } + op++; + } + return OPERAND; +} + +static int +isunopoperand(void) +{ + struct t_op const *op = ops; + char *s; + char *t; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + t = *(t_wp + 2); + while (*op->op_text) { + if (strcmp(s, op->op_text) == 0) + return TOKEN_TYPE(op->op_num) == BINOP && + (parenlevel == 0 || t[0] != ')' || t[1] != '\0'); + op++; + } + return 0; +} + +static int +islparenoperand(void) +{ + struct t_op const *op = ops; + char *s; + + if (nargc == 1) + return 1; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + if (nargc != 3) + return 0; + while (*op->op_text) { + if (strcmp(s, op->op_text) == 0) + return TOKEN_TYPE(op->op_num) == BINOP; + op++; + } + return 0; +} + +static int +isrparenoperand(void) +{ + char *s; + + if (nargc == 1) + return 0; + s = *(t_wp + 1); + if (nargc == 2) + return parenlevel == 1 && strcmp(s, ")") == 0; + return 0; +} + +/* atoi with error detection */ +static int +getn(const char *s) +{ + char *p; + long r; + + errno = 0; + r = strtol(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return (int) r; +} + +/* atoi with error detection and 64 bit range */ +static intmax_t +getq(const char *s) +{ + char *p; + intmax_t r; + + errno = 0; + r = strtoimax(s, &p, 10); + + if (s == p) + error("%s: bad number", s); + + if (errno != 0) + error((errno == EINVAL) ? "%s: bad number" : + "%s: out of range", s); + + while (isspace((unsigned char)*p)) + p++; + + if (*p) + error("%s: bad number", s); + + return r; +} + +static int +intcmp (const char *s1, const char *s2) +{ + intmax_t q1, q2; + + + q1 = getq(s1); + q2 = getq(s2); + + if (q1 > q2) + return 1; + + if (q1 < q2) + return -1; + + return 0; +} + +static int +newerf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + if (stat(f1, &b1) != 0 || stat(f2, &b2) != 0) + return 0; + + if (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec) + return 1; + if (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec) + return 0; + + return (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec); +} + +static int +olderf (const char *f1, const char *f2) +{ + return (newerf(f2, f1)); +} + +static int +equalf (const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat (f1, &b1) == 0 && + stat (f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} diff --git a/external/sh/token.h b/external/sh/token.h new file mode 100644 index 00000000..205dfe8d --- /dev/null +++ b/external/sh/token.h @@ -0,0 +1,112 @@ +#define TEOF 0 +#define TNL 1 +#define TSEMI 2 +#define TBACKGND 3 +#define TAND 4 +#define TOR 5 +#define TPIPE 6 +#define TLP 7 +#define TRP 8 +#define TENDCASE 9 +#define TFALLTHRU 10 +#define TREDIR 11 +#define TWORD 12 +#define TIF 13 +#define TTHEN 14 +#define TELSE 15 +#define TELIF 16 +#define TFI 17 +#define TWHILE 18 +#define TUNTIL 19 +#define TFOR 20 +#define TDO 21 +#define TDONE 22 +#define TBEGIN 23 +#define TEND 24 +#define TCASE 25 +#define TESAC 26 +#define TNOT 27 + +/* Array indicating which tokens mark the end of a list */ +static const char tokendlist[] = { + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 1, + 1, + 0, + 1, + 0, + 1, + 0, +}; + +static const char *const tokname[] = { + "end of file", + "newline", + "\";\"", + "\"&\"", + "\"&&\"", + "\"||\"", + "\"|\"", + "\"(\"", + "\")\"", + "\";;\"", + "\";&\"", + "redirection", + "word", + "\"if\"", + "\"then\"", + "\"else\"", + "\"elif\"", + "\"fi\"", + "\"while\"", + "\"until\"", + "\"for\"", + "\"do\"", + "\"done\"", + "\"{\"", + "\"}\"", + "\"case\"", + "\"esac\"", + "\"!\"", +}; + +#define KWDOFFSET 13 + +const char *const parsekwd[] = { + "if", + "then", + "else", + "elif", + "fi", + "while", + "until", + "for", + "do", + "done", + "{", + "}", + "case", + "esac", + "!", + 0 +}; diff --git a/external/sh/trap.c b/external/sh/trap.c new file mode 100644 index 00000000..a5a9c6b2 --- /dev/null +++ b/external/sh/trap.c @@ -0,0 +1,551 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)trap.c 8.5 (Berkeley) 6/5/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/trap.c 281718 2015-04-18 23:49:57Z bdrewery $"); + +#include <signal.h> +#include <unistd.h> +#include <stdlib.h> + +#include "shell.h" +#include "main.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" +#include "jobs.h" +#include "show.h" +#include "options.h" +#include "syntax.h" +#include "output.h" +#include "memalloc.h" +#include "error.h" +#include "trap.h" +#include "mystring.h" +#include "builtins.h" +#include "myhistedit.h" + + +/* + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, + */ + +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permanently */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + + +static char sigmode[NSIG]; /* current value of signal */ +volatile sig_atomic_t pendingsig; /* indicates some signal received */ +volatile sig_atomic_t pendingsig_waitcmd; /* indicates wait builtin should be interrupted */ +static int in_dotrap; /* do we execute in a trap handler? */ +static char *volatile trap[NSIG]; /* trap handler commands */ +static volatile sig_atomic_t gotsig[NSIG]; + /* indicates specified signal received */ +static int ignore_sigchld; /* Used while handling SIGCHLD traps. */ +static int last_trapsig; + +static int exiting; /* exitshell() has been called */ +static int exiting_exitstatus; /* value passed to exitshell() */ + +static int getsigaction(int, sig_t *); + + +/* + * Map a string to a signal number. + * + * Note: the signal number may exceed NSIG. + */ +static int +sigstring_to_signum(char *sig) +{ + + if (is_number(sig)) { + int signo; + + signo = atoi(sig); + return ((signo >= 0 && signo < NSIG) ? signo : (-1)); + } else if (strcasecmp(sig, "EXIT") == 0) { + return (0); + } else { + int n; + + if (strncasecmp(sig, "SIG", 3) == 0) + sig += 3; + for (n = 1; n < sys_nsig; n++) + if (sys_signame[n] && + strcasecmp(sys_signame[n], sig) == 0) + return (n); + } + return (-1); +} + + +/* + * Print a list of valid signal names. + */ +static void +printsignals(void) +{ + int n, outlen; + + outlen = 0; + for (n = 1; n < sys_nsig; n++) { + if (sys_signame[n]) { + out1fmt("%s", sys_signame[n]); + outlen += strlen(sys_signame[n]); + } else { + out1fmt("%d", n); + outlen += 3; /* good enough */ + } + ++outlen; + if (outlen > 71 || n == sys_nsig - 1) { + out1str("\n"); + outlen = 0; + } else { + out1c(' '); + } + } +} + + +/* + * The trap builtin. + */ +int +trapcmd(int argc __unused, char **argv) +{ + char *action; + int signo; + int errors = 0; + int i; + + while ((i = nextopt("l")) != '\0') { + switch (i) { + case 'l': + printsignals(); + return (0); + } + } + argv = argptr; + + if (*argv == NULL) { + for (signo = 0 ; signo < sys_nsig ; signo++) { + if (signo < NSIG && trap[signo] != NULL) { + out1str("trap -- "); + out1qstr(trap[signo]); + if (signo == 0) { + out1str(" EXIT\n"); + } else if (sys_signame[signo]) { + out1fmt(" %s\n", sys_signame[signo]); + } else { + out1fmt(" %d\n", signo); + } + } + } + return 0; + } + action = NULL; + if (*argv && !is_number(*argv)) { + if (strcmp(*argv, "-") == 0) + argv++; + else { + action = *argv; + argv++; + } + } + for (; *argv; argv++) { + if ((signo = sigstring_to_signum(*argv)) == -1) { + warning("bad signal %s", *argv); + errors = 1; + continue; + } + INTOFF; + if (action) + action = savestr(action); + if (trap[signo]) + ckfree(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INTON; + } + return errors; +} + + +/* + * Clear traps on a fork. + */ +void +clear_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INTOFF; + ckfree(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INTON; + } + } +} + + +/* + * Check if we have any traps enabled. + */ +int +have_traps(void) +{ + char *volatile *tp; + + for (tp = trap ; tp <= &trap[NSIG - 1] ; tp++) { + if (*tp && **tp) /* trap not NULL or SIG_IGN */ + return 1; + } + return 0; +} + +/* + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. + */ +void +setsignal(int signo) +{ + int action; + sig_t sigact = SIG_DFL; + struct sigaction sa; + char *t; + + if ((t = trap[signo]) == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (action == S_DFL) { + switch (signo) { + case SIGINT: + action = S_CATCH; + break; + case SIGQUIT: +#ifdef DEBUG + { + extern int debug; + + if (debug) + break; + } +#endif + action = S_CATCH; + break; + case SIGTERM: + if (rootshell && iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (rootshell && mflag) + action = S_IGN; + break; +#endif + } + } + + t = &sigmode[signo]; + if (*t == 0) { + /* + * current setting unknown + */ + if (!getsigaction(signo, &sigact)) { + /* + * 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 (sigact == SIG_IGN) { + if (mflag && (signo == SIGTSTP || + signo == SIGTTIN || signo == SIGTTOU)) { + *t = S_IGN; /* don't hard ignore these */ + } else + *t = S_HARD_IGN; + } else { + *t = S_RESET; /* force to be set */ + } + } + if (*t == S_HARD_IGN || *t == action) + return; + switch (action) { + case S_DFL: sigact = SIG_DFL; break; + case S_CATCH: sigact = onsig; break; + case S_IGN: sigact = SIG_IGN; break; + } + *t = action; + sa.sa_handler = sigact; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(signo, &sa, NULL); +} + + +/* + * Return the current setting for sig w/o changing it. + */ +static int +getsigaction(int signo, sig_t *sigact) +{ + struct sigaction sa; + + if (sigaction(signo, (struct sigaction *)0, &sa) == -1) + return 0; + *sigact = (sig_t) sa.sa_handler; + return 1; +} + + +/* + * Ignore a signal. + */ +void +ignoresig(int signo) +{ + + if (sigmode[signo] == 0) + setsignal(signo); + if (sigmode[signo] != S_IGN && sigmode[signo] != S_HARD_IGN) { + signal(signo, SIG_IGN); + sigmode[signo] = S_IGN; + } +} + + +int +issigchldtrapped(void) +{ + + return (trap[SIGCHLD] != NULL && *trap[SIGCHLD] != '\0'); +} + + +/* + * Signal handler. + */ +void +onsig(int signo) +{ + + if (signo == SIGINT && trap[SIGINT] == NULL) { + /* + * The !in_dotrap here is safe. The only way we can arrive + * here with in_dotrap set is that a trap handler set SIGINT to + * SIG_DFL and killed itself. + */ + if (suppressint && !in_dotrap) + SET_PENDING_INT; + else + onint(); + return; + } + + /* If we are currently in a wait builtin, prepare to break it */ + if (signo == SIGINT || signo == SIGQUIT) + pendingsig_waitcmd = signo; + + if (trap[signo] != NULL && trap[signo][0] != '\0' && + (signo != SIGCHLD || !ignore_sigchld)) { + gotsig[signo] = 1; + pendingsig = signo; + pendingsig_waitcmd = signo; + } +} + + +/* + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. + */ +void +dotrap(void) +{ + int i; + int savestatus, prev_evalskip, prev_skipcount; + + in_dotrap++; + for (;;) { + pendingsig = 0; + pendingsig_waitcmd = 0; + for (i = 1; i < NSIG; i++) { + if (gotsig[i]) { + gotsig[i] = 0; + if (trap[i]) { + /* + * Ignore SIGCHLD to avoid infinite + * recursion if the trap action does + * a fork. + */ + if (i == SIGCHLD) + ignore_sigchld++; + + /* + * Backup current evalskip + * state and reset it before + * executing a trap, so that the + * trap is not disturbed by an + * ongoing break/continue/return + * statement. + */ + prev_evalskip = evalskip; + prev_skipcount = skipcount; + evalskip = 0; + + last_trapsig = i; + savestatus = exitstatus; + evalstring(trap[i], 0); + + /* + * If such a command was not + * already in progress, allow a + * break/continue/return in the + * trap action to have an effect + * outside of it. + */ + if (evalskip == 0 || + prev_evalskip != 0) { + evalskip = prev_evalskip; + skipcount = prev_skipcount; + exitstatus = savestatus; + } + + if (i == SIGCHLD) + ignore_sigchld--; + } + break; + } + } + if (i >= NSIG) + break; + } + in_dotrap--; +} + + +/* + * Controls whether the shell is interactive or not. + */ +void +setinteractive(int on) +{ + static int is_interactive = -1; + + if (on == is_interactive) + return; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); + is_interactive = on; +} + + +/* + * Called to exit the shell. + */ +void +exitshell(int status) +{ + TRACE(("exitshell(%d) pid=%d\n", status, getpid())); + exiting = 1; + exiting_exitstatus = status; + exitshell_savedstatus(); +} + +void +exitshell_savedstatus(void) +{ + struct jmploc loc1, loc2; + char *p; + int sig = 0; + sigset_t sigs; + + if (!exiting) { + if (in_dotrap && last_trapsig) { + sig = last_trapsig; + exiting_exitstatus = sig + 128; + } else + exiting_exitstatus = oexitstatus; + } + exitstatus = oexitstatus = exiting_exitstatus; + if (!setjmp(loc1.loc)) { + handler = &loc1; + if ((p = trap[0]) != NULL && *p != '\0') { + /* + * Reset evalskip, or the trap on EXIT could be + * interrupted if the last command was a "return". + */ + evalskip = 0; + trap[0] = NULL; + evalstring(p, 0); + } + } + if (!setjmp(loc2.loc)) { + handler = &loc2; /* probably unnecessary */ + flushall(); +#if JOBS + setjobctl(0); +#endif + } + if (sig != 0 && sig != SIGSTOP && sig != SIGTSTP && sig != SIGTTIN && + sig != SIGTTOU) { + signal(sig, SIG_DFL); + sigemptyset(&sigs); + sigaddset(&sigs, sig); + sigprocmask(SIG_UNBLOCK, &sigs, NULL); + kill(getpid(), sig); + /* If the default action is to ignore, fall back to _exit(). */ + } + _exit(exiting_exitstatus); +} diff --git a/external/sh/trap.h b/external/sh/trap.h new file mode 100644 index 00000000..a0006471 --- /dev/null +++ b/external/sh/trap.h @@ -0,0 +1,48 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)trap.h 8.3 (Berkeley) 6/5/95 + * $FreeBSD: head/bin/sh/trap.h 276038 2014-12-21 23:09:59Z jilles $ + */ + +extern volatile sig_atomic_t pendingsig; +extern volatile sig_atomic_t pendingsig_waitcmd; + +void clear_traps(void); +int have_traps(void); +void setsignal(int); +void ignoresig(int); +int issigchldtrapped(void); +void onsig(int); +void dotrap(void); +void setinteractive(int); +void exitshell(int) __dead2; +void exitshell_savedstatus(void) __dead2; diff --git a/external/sh/var.c b/external/sh/var.c new file mode 100644 index 00000000..53933df8 --- /dev/null +++ b/external/sh/var.c @@ -0,0 +1,956 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 5/4/95"; +#endif +#endif /* not lint */ +#include <sys/cdefs.h> +__FBSDID("$FreeBSD: head/bin/sh/var.c 279569 2015-03-03 21:21:43Z jilles $"); + +#include <unistd.h> +#include <stdlib.h> +#include <paths.h> + +/* + * Shell variables. + */ + +#include <locale.h> +#include <langinfo.h> + +#include "shell.h" +#include "output.h" +#include "expand.h" +#include "nodes.h" /* for other headers */ +#include "eval.h" /* defines cmdenviron */ +#include "exec.h" +#include "syntax.h" +#include "options.h" +#include "mail.h" +#include "var.h" +#include "memalloc.h" +#include "error.h" +#include "mystring.h" +#include "parser.h" +#include "builtins.h" +#ifndef NO_HISTORY +#include "myhistedit.h" +#endif + + +#define VTABSIZE 39 + + +struct varinit { + struct var *var; + int flags; + const char *text; + void (*func)(const char *); +}; + + +#ifndef NO_HISTORY +struct var vhistsize; +struct var vterm; +#endif +struct var vifs; +struct var vmail; +struct var vmpath; +struct var vpath; +struct var vps1; +struct var vps2; +struct var vps4; +static struct var voptind; +struct var vdisvfork; + +struct localvar *localvars; +int forcelocal; + +static const struct varinit varinit[] = { +#ifndef NO_HISTORY + { &vhistsize, VUNSET, "HISTSIZE=", + sethistsize }, +#endif + { &vifs, 0, "IFS= \t\n", + NULL }, + { &vmail, VUNSET, "MAIL=", + NULL }, + { &vmpath, VUNSET, "MAILPATH=", + NULL }, + { &vpath, 0, "PATH=" _PATH_DEFPATH, + changepath }, + /* + * vps1 depends on uid + */ + { &vps2, 0, "PS2=> ", + NULL }, + { &vps4, 0, "PS4=+ ", + NULL }, +#ifndef NO_HISTORY + { &vterm, VUNSET, "TERM=", + setterm }, +#endif + { &voptind, 0, "OPTIND=1", + getoptsreset }, + { &vdisvfork, VUNSET, "SH_DISABLE_VFORK=", + NULL }, + { NULL, 0, NULL, + NULL } +}; + +static struct var *vartab[VTABSIZE]; + +static const char *const locale_names[7] = { + "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", + "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", NULL +}; +static const int locale_categories[7] = { + LC_COLLATE, LC_CTYPE, LC_MONETARY, LC_NUMERIC, LC_TIME, LC_MESSAGES, 0 +}; + +static int varequal(const char *, const char *); +static struct var *find_var(const char *, struct var ***, int *); +static int localevar(const char *); +static void setvareq_const(const char *s, int flags); + +extern char **environ; + +/* + * This routine initializes the builtin variables and imports the environment. + * It is called when the shell is initialized. + */ + +void +initvar(void) +{ + char ppid[20]; + const struct varinit *ip; + struct var *vp; + struct var **vpp; + char **envp; + + for (ip = varinit ; (vp = ip->var) != NULL ; ip++) { + if (find_var(ip->text, &vpp, &vp->name_len) != NULL) + continue; + vp->next = *vpp; + *vpp = vp; + vp->text = __DECONST(char *, ip->text); + vp->flags = ip->flags | VSTRFIXED | VTEXTFIXED; + vp->func = ip->func; + } + /* + * PS1 depends on uid + */ + if (find_var("PS1", &vpp, &vps1.name_len) == NULL) { + vps1.next = *vpp; + *vpp = &vps1; + vps1.text = __DECONST(char *, geteuid() ? "PS1=$ " : "PS1=# "); + vps1.flags = VSTRFIXED|VTEXTFIXED; + } + fmtstr(ppid, sizeof(ppid), "%d", (int)getppid()); + setvarsafe("PPID", ppid, 0); + for (envp = environ ; *envp ; envp++) { + if (strchr(*envp, '=')) { + setvareq(*envp, VEXPORT|VTEXTFIXED); + } + } + setvareq_const("OPTIND=1", 0); +} + +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ + +int +setvarsafe(const char *name, const char *val, int flags) +{ + struct jmploc jmploc; + struct jmploc *const savehandler = handler; + int err = 0; + int inton; + + inton = is_int_on(); + if (setjmp(jmploc.loc)) + err = 1; + else { + handler = &jmploc; + setvar(name, val, flags); + } + handler = savehandler; + SETINTON(inton); + return err; +} + +/* + * Set the value of a variable. The flags argument is stored with the + * flags of the variable. If val is NULL, the variable is unset. + */ + +void +setvar(const char *name, const char *val, int flags) +{ + const char *p; + size_t len; + size_t namelen; + size_t vallen; + char *nameeq; + int isbad; + + isbad = 0; + p = name; + if (! is_name(*p)) + isbad = 1; + p++; + for (;;) { + if (! is_in_name(*p)) { + if (*p == '\0' || *p == '=') + break; + isbad = 1; + } + p++; + } + namelen = p - name; + if (isbad) + error("%.*s: bad variable name", (int)namelen, name); + len = namelen + 2; /* 2 is space for '=' and '\0' */ + if (val == NULL) { + flags |= VUNSET; + vallen = 0; + } else { + vallen = strlen(val); + len += vallen; + } + INTOFF; + nameeq = ckmalloc(len); + memcpy(nameeq, name, namelen); + nameeq[namelen] = '='; + if (val) + memcpy(nameeq + namelen + 1, val, vallen + 1); + else + nameeq[namelen + 1] = '\0'; + setvareq(nameeq, flags); + INTON; +} + +static int +localevar(const char *s) +{ + const char *const *ss; + + if (*s != 'L') + return 0; + if (varequal(s + 1, "ANG")) + return 1; + if (strncmp(s + 1, "C_", 2) != 0) + return 0; + if (varequal(s + 3, "ALL")) + return 1; + for (ss = locale_names; *ss ; ss++) + if (varequal(s + 3, *ss + 3)) + return 1; + return 0; +} + + +/* + * Sets/unsets an environment variable from a pointer that may actually be a + * pointer into environ where the string should not be manipulated. + */ +static void +change_env(const char *s, int set) +{ + char *eqp; + char *ss; + + INTOFF; + ss = savestr(s); + if ((eqp = strchr(ss, '=')) != NULL) + *eqp = '\0'; + if (set && eqp != NULL) + (void) setenv(ss, eqp + 1, 1); + else + (void) unsetenv(ss); + ckfree(ss); + INTON; + + return; +} + + +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + */ + +void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + int nlen; + + if (aflag) + flags |= VEXPORT; + if (forcelocal && !(flags & (VNOSET | VNOLOCAL))) + mklocal(s); + vp = find_var(s, &vpp, &nlen); + if (vp != NULL) { + if (vp->flags & VREADONLY) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + error("%.*s: is read only", vp->name_len, s); + } + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(s + vp->name_len + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(vp->text); + + vp->flags &= ~(VTEXTFIXED|VSTACK|VUNSET); + vp->flags |= flags; + vp->text = s; + + /* + * We could roll this to a function, to handle it as + * a regular variable function callback, but why bother? + * + * Note: this assumes iflag is not set to 1 initially. + * As part of initvar(), this is called before arguments + * are looked at. + */ + if ((vp == &vmpath || (vp == &vmail && ! mpathset())) && + iflag == 1) + chkmail(1); + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; + return; + } + /* not found */ + if (flags & VNOSET) { + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + ckfree(s); + return; + } + INTOFF; + vp = ckmalloc(sizeof (*vp)); + vp->flags = flags; + vp->text = s; + vp->name_len = nlen; + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + if ((vp->flags & VEXPORT) && localevar(s)) { + change_env(s, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + INTON; +} + + +static void +setvareq_const(const char *s, int flags) +{ + setvareq(__DECONST(char *, s), flags | VTEXTFIXED); +} + + +/* + * Process a linked list of variable assignments. + */ + +void +listsetvar(struct strlist *list, int flags) +{ + struct strlist *lp; + + INTOFF; + for (lp = list ; lp ; lp = lp->next) { + setvareq(savestr(lp->text), flags); + } + INTON; +} + + + +/* + * Find the value of a variable. Returns NULL if not set. + */ + +char * +lookupvar(const char *name) +{ + struct var *v; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET) + return NULL; + return v->text + v->name_len + 1; +} + + + +/* + * Search the environment of a builtin command. If the second argument + * is nonzero, return the value of a variable even if it hasn't been + * exported. + */ + +char * +bltinlookup(const char *name, int doall) +{ + struct strlist *sp; + struct var *v; + char *result; + + result = NULL; + for (sp = cmdenviron ; sp ; sp = sp->next) { + if (varequal(sp->text, name)) + result = strchr(sp->text, '=') + 1; + } + if (result != NULL) + return result; + + v = find_var(name, NULL, NULL); + if (v == NULL || v->flags & VUNSET || + (!doall && (v->flags & VEXPORT) == 0)) + return NULL; + return v->text + v->name_len + 1; +} + + +/* + * Set up locale for a builtin (LANG/LC_* assignments). + */ +void +bltinsetlocale(void) +{ + struct strlist *lp; + int act = 0; + char *loc, *locdef; + int i; + + for (lp = cmdenviron ; lp ; lp = lp->next) { + if (localevar(lp->text)) { + act = 1; + break; + } + } + if (!act) + return; + loc = bltinlookup("LC_ALL", 0); + INTOFF; + if (loc != NULL) { + setlocale(LC_ALL, loc); + INTON; + updatecharset(); + return; + } + locdef = bltinlookup("LANG", 0); + for (i = 0; locale_names[i] != NULL; i++) { + loc = bltinlookup(locale_names[i], 0); + if (loc == NULL) + loc = locdef; + if (loc != NULL) + setlocale(locale_categories[i], loc); + } + INTON; + updatecharset(); +} + +/* + * Undo the effect of bltinlocaleset(). + */ +void +bltinunsetlocale(void) +{ + struct strlist *lp; + + INTOFF; + for (lp = cmdenviron ; lp ; lp = lp->next) { + if (localevar(lp->text)) { + setlocale(LC_ALL, ""); + updatecharset(); + return; + } + } + INTON; +} + +/* + * Update the localeisutf8 flag. + */ +void +updatecharset(void) +{ + char *charset; + + charset = nl_langinfo(CODESET); + localeisutf8 = !strcmp(charset, "UTF-8"); +} + +void +initcharset(void) +{ + updatecharset(); + initial_localeisutf8 = localeisutf8; +} + +/* + * Generate a list of exported variables. This routine is used to construct + * the third argument to execve when executing a program. + */ + +char ** +environment(void) +{ + int nenv; + struct var **vpp; + struct var *vp; + char **env, **ep; + + nenv = 0; + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + nenv++; + } + ep = env = stalloc((nenv + 1) * sizeof *env); + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) + if (vp->flags & VEXPORT) + *ep++ = vp->text; + } + *ep = NULL; + return env; +} + + +static int +var_compare(const void *a, const void *b) +{ + const char *const *sa, *const *sb; + + sa = a; + sb = b; + /* + * This compares two var=value strings which creates a different + * order from what you would probably expect. POSIX is somewhat + * ambiguous on what should be sorted exactly. + */ + return strcoll(*sa, *sb); +} + + +/* + * Command to list all variables which are set. This is invoked from the + * set command when it is called without any options or operands. + */ + +int +showvarscmd(int argc __unused, char **argv __unused) +{ + struct var **vpp; + struct var *vp; + const char *s; + const char **vars; + int i, n; + + /* + * POSIX requires us to sort the variables. + */ + n = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + n++; + } + } + + INTOFF; + vars = ckmalloc(n * sizeof(*vars)); + i = 0; + for (vpp = vartab; vpp < vartab + VTABSIZE; vpp++) { + for (vp = *vpp; vp; vp = vp->next) { + if (!(vp->flags & VUNSET)) + vars[i++] = vp->text; + } + } + + qsort(vars, n, sizeof(*vars), var_compare); + for (i = 0; i < n; i++) { + /* + * Skip improper variable names so the output remains usable as + * shell input. + */ + if (!isassignment(vars[i])) + continue; + s = strchr(vars[i], '='); + s++; + outbin(vars[i], s - vars[i], out1); + out1qstr(s); + out1c('\n'); + } + ckfree(vars); + INTON; + + return 0; +} + + + +/* + * The export and readonly commands. + */ + +int +exportcmd(int argc __unused, char **argv) +{ + struct var **vpp; + struct var *vp; + char **ap; + char *name; + char *p; + char *cmdname; + int ch, values; + int flag = argv[0][0] == 'r'? VREADONLY : VEXPORT; + + cmdname = argv[0]; + values = 0; + while ((ch = nextopt("p")) != '\0') { + switch (ch) { + case 'p': + values = 1; + break; + } + } + + if (values && *argptr != NULL) + error("-p requires no arguments"); + if (*argptr != NULL) { + for (ap = argptr; (name = *ap) != NULL; ap++) { + if ((p = strchr(name, '=')) != NULL) { + p++; + } else { + vp = find_var(name, NULL, NULL); + if (vp != NULL) { + vp->flags |= flag; + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(vp->text, 1); + (void) setlocale(LC_ALL, ""); + updatecharset(); + } + continue; + } + } + setvar(name, p, flag); + } + } else { + for (vpp = vartab ; vpp < vartab + VTABSIZE ; vpp++) { + for (vp = *vpp ; vp ; vp = vp->next) { + if (vp->flags & flag) { + if (values) { + /* + * Skip improper variable names + * so the output remains usable + * as shell input. + */ + if (!isassignment(vp->text)) + continue; + out1str(cmdname); + out1c(' '); + } + if (values && !(vp->flags & VUNSET)) { + outbin(vp->text, + vp->name_len + 1, out1); + out1qstr(vp->text + + vp->name_len + 1); + } else + outbin(vp->text, vp->name_len, + out1); + out1c('\n'); + } + } + } + } + return 0; +} + + +/* + * The "local" command. + */ + +int +localcmd(int argc __unused, char **argv __unused) +{ + char *name; + + nextopt(""); + if (! in_function()) + error("Not in a function"); + while ((name = *argptr++) != NULL) { + mklocal(name); + } + return 0; +} + + +/* + * 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. + */ + +void +mklocal(char *name) +{ + struct localvar *lvp; + struct var **vpp; + struct var *vp; + + INTOFF; + lvp = ckmalloc(sizeof (struct localvar)); + if (name[0] == '-' && name[1] == '\0') { + lvp->text = ckmalloc(sizeof optlist); + memcpy(lvp->text, optlist, sizeof optlist); + vp = NULL; + } else { + vp = find_var(name, &vpp, NULL); + if (vp == NULL) { + if (strchr(name, '=')) + setvareq(savestr(name), VSTRFIXED | VNOLOCAL); + else + setvar(name, NULL, VSTRFIXED | VNOLOCAL); + vp = *vpp; /* the new variable */ + lvp->text = NULL; + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (name[vp->name_len] == '=') + setvareq(savestr(name), VNOLOCAL); + } + } + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INTON; +} + + +/* + * Called after a function returns. + */ + +void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + INTOFF; + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof optlist); + ckfree(lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + (void)unsetvar(vp->text); + } else { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; + } + ckfree(lvp); + } + INTON; +} + + +int +setvarcmd(int argc, char **argv) +{ + if (argc <= 2) + return unsetcmd(argc, argv); + else if (argc == 3) + setvar(argv[1], argv[2], 0); + else + error("too many arguments"); + return 0; +} + + +/* + * The unset builtin command. + */ + +int +unsetcmd(int argc __unused, char **argv __unused) +{ + char **ap; + int i; + int flg_func = 0; + int flg_var = 0; + int ret = 0; + + while ((i = nextopt("vf")) != '\0') { + if (i == 'f') + flg_func = 1; + else + flg_var = 1; + } + if (flg_func == 0 && flg_var == 0) + flg_var = 1; + + INTOFF; + for (ap = argptr; *ap ; ap++) { + if (flg_func) + ret |= unsetfunc(*ap); + if (flg_var) + ret |= unsetvar(*ap); + } + INTON; + return ret; +} + + +/* + * Unset the specified variable. + * Called with interrupts off. + */ + +int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + + vp = find_var(s, &vpp, NULL); + if (vp == NULL) + return (0); + if (vp->flags & VREADONLY) + return (1); + if (vp->text[vp->name_len + 1] != '\0') + setvar(s, "", 0); + if ((vp->flags & VEXPORT) && localevar(vp->text)) { + change_env(s, 0); + setlocale(LC_ALL, ""); + updatecharset(); + } + vp->flags &= ~VEXPORT; + vp->flags |= VUNSET; + if ((vp->flags & VSTRFIXED) == 0) { + if ((vp->flags & VTEXTFIXED) == 0) + ckfree(vp->text); + *vpp = vp->next; + ckfree(vp); + } + return (0); +} + + + +/* + * Returns true if the two strings specify the same variable. The first + * variable name is terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ + +static int +varequal(const char *p, const char *q) +{ + while (*p == *q++) { + if (*p++ == '=') + return 1; + } + if (*p == '=' && *(q - 1) == '\0') + return 1; + return 0; +} + +/* + * Search for a variable. + * 'name' may be terminated by '=' or a NUL. + * vppp is set to the pointer to vp, or the list head if vp isn't found + * lenp is set to the number of characters in 'name' + */ + +static struct var * +find_var(const char *name, struct var ***vppp, int *lenp) +{ + unsigned int hashval; + int len; + struct var *vp, **vpp; + const char *p = name; + + hashval = 0; + while (*p && *p != '=') + hashval = 2 * hashval + (unsigned char)*p++; + len = p - name; + + if (lenp) + *lenp = len; + vpp = &vartab[hashval % VTABSIZE]; + if (vppp) + *vppp = vpp; + + for (vp = *vpp ; vp ; vpp = &vp->next, vp = *vpp) { + if (vp->name_len != len) + continue; + if (memcmp(vp->text, name, len) != 0) + continue; + if (vppp) + *vppp = vpp; + return vp; + } + return NULL; +} diff --git a/external/sh/var.h b/external/sh/var.h new file mode 100644 index 00000000..ba8eda5a --- /dev/null +++ b/external/sh/var.h @@ -0,0 +1,130 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kenneth Almquist. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)var.h 8.2 (Berkeley) 5/4/95 + * $FreeBSD: head/bin/sh/var.h 279569 2015-03-03 21:21:43Z jilles $ + */ + +/* + * Shell variables. + */ + +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOLOCAL 0x100 /* ignore forcelocal */ + + +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + int name_len; /* length of name */ + char *text; /* name=value */ + void (*func)(const char *); + /* function to be called when */ + /* the variable gets set/unset */ +}; + + +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + char *text; /* saved text */ +}; + + +extern struct localvar *localvars; +extern int forcelocal; + +extern struct var vifs; +extern struct var vmail; +extern struct var vmpath; +extern struct var vpath; +extern struct var vps1; +extern struct var vps2; +extern struct var vps4; +extern struct var vdisvfork; +#ifndef NO_HISTORY +extern struct var vhistsize; +extern struct var vterm; +#endif + +extern int localeisutf8; +/* The parser uses the locale that was in effect at startup. */ +extern int initial_localeisutf8; + +/* + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. + */ + +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) +#ifndef NO_HISTORY +#define histsizeval() (vhistsize.text + 9) +#define termval() (vterm.text + 5) +#endif + +#define mpathset() ((vmpath.flags & VUNSET) == 0) +#define disvforkset() ((vdisvfork.flags & VUNSET) == 0) + +void initvar(void); +void setvar(const char *, const char *, int); +void setvareq(char *, int); +struct strlist; +void listsetvar(struct strlist *, int); +char *lookupvar(const char *); +char *bltinlookup(const char *, int); +void bltinsetlocale(void); +void bltinunsetlocale(void); +void updatecharset(void); +void initcharset(void); +char **environment(void); +int showvarscmd(int, char **); +void mklocal(char *); +void poplocalvars(void); +int unsetvar(const char *); +int setvarsafe(const char *, const char *, int); diff --git a/external/update_sh.sh b/external/update_sh.sh new file mode 100644 index 00000000..50307e87 --- /dev/null +++ b/external/update_sh.sh @@ -0,0 +1,32 @@ +#! /bin/sh +set -e + +SH_DIR=$1 +if [ -z "${SH_DIR}" ]; then + echo "Usage: $0 /usr/src/bin/sh" >&2 + exit 1 +fi +DESTDIR=external/sh +rm -rf "${DESTDIR}" +mkdir -p "${DESTDIR}" +DESTDIR_REAL="$(realpath "${DESTDIR}")" +ORIG_PWD="${PWD}" +cd "${SH_DIR}" +export MK_TESTS=no +make clean cleanobj +make depend +paths=$(make -V '${.PATH:N.*bltin*}'|xargs realpath) +for src in *.h $(make -V SRCS); do + if [ -f "${src}" ]; then + echo "${PWD}/${src}" + else + for p in ${paths}; do + [ -f "${p}/${src}" ] && echo "${p}/${src}" && break + done + fi +done | sort -u | tar -c -T - --exclude bltin -s ",.*/,,g" -f - | tar -C "${DESTDIR_REAL}" -xf - +cp -R "${SH_DIR}/bltin" "${DESTDIR_REAL}/bltin" +cd "${ORIG_PWD}" +git add -A "${DESTDIR}" +echo "sh_SOURCES= \\" +find "${DESTDIR}" -name '*.c'|sed -e 's,$, \\,' |