/*bin/echo ' #-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;coding:utf-8 -*-┤
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8                                :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
├──────────────────────────────────────────────────────────────────────────────┤
│███▒ ▓░░░▒ █▓█▓ ▒▒███████▓█████ ██▓▓▓███▒██▒▓█▓████████ ▓██▓█████▓██ ░ ░▒  ░  │
│█░ ░ █░▒▒▒ █▓▓▓ ▒▓████▓░███▓█▓▓▓▓█▓▒▓▓███▓▒▒██▓▓█▓█████ ▒██▓█████▓██ ░ ▒░░░  ░│
│███▓     ▒ ▓██▓ █████▓░█▓▓█▓██░░█░████▓█▓██▒██▒▓▓▓█▓▓█▓█▒██▓█████▓██░▒ ▓░     │
│████ █▓     ▒███▓██████████████████████████████▓▓█▓█████▓████████▓██ ▒▓▓▓▒░▒ ░│
│███▓ ▓▓▓▓        ▒███████████▓████████████▓▓██▓▓▓█▓████▓███▓█████▓██ ▒░▓▒░░  ░│
│███▓ █▒▓▒▒░▓        ▒██▓████████████████████████████▓██▓▓████████▓██ ░░▒▒▓▓▒ ░│
│███▓ ▓▒▓▓▓ █▓▓          ▒███████████████████████████▓██▓▓░███████▓██ ░ ▒▒░░▓▓░│
│█▓█▓ █▒░░░ █▓██▓             ██████████████████████████▓█████████▓▓░░░ ▒░ ░░ ▒│
│█▓█▓░█▓▒▓▒ ██▓█▓ ▓               ▓██████████████████▒██▓████████ ▒░▒▒▒▒▒▒▒   ░│
│█▓▓▒░▓▓▒▓▓ █▓▓██ ██  ░ ░ ░░░░  ░░░ ░ ███████████████▓██▓██████ ░▒▓▓▒▓▓░▒▓░▒░  │
│██▓▓░▓▒▒▓▒░▓▓▓▓▓ █▓▓░   ░  ░░ ░ ░     ▓█████████████▒██▓████▒▒▒░░▓▓▒▓▒░▒░░▒░ ░│
│█▓▓▓░█▒▒▒▒ █▓▓▓▓ ██▓▓    ░      ░     ▒▒  ██████████▒██▓███░░░▒▒▓▓▓▓▒░░   ░   │
│██▓▓░█░▒▒▒░█▓▓▓▓ █▓▓█        ░ ░  ░░░░▓░    ████████▒██ ░░░▓▒▒▓▓▓▓ ▓▒░▒▒▒▒░░  │
│▒██▓▒▓░▒▓░░▓▓▓▓▓ ▓▓▓▓     ░░       ▒░░▒░░   ▓███████▒██░   ▒ ▒░░▒▓▒▒░▒▒▓▒ ░   │
│▒▓▓▓▒█▒▒▒▒░█▒▒▓▓ ▓▓█▓▓▓▓▓  ░▒ ░  ▒ ░▓▓▓░░░   █▒  ▓   ██▓███▒▓░▓▓▒░▓░░░░▒▒░░░ ░│
│▒█▒▓░▓▓░▓▓░▓▒▓▒▒ █▓████▓███  ░ ░▒░ ▓▓▒▒▒▒░░▓  ░ ░█ ▓ ▒█████░░  ▓▓▒▒░░▒▒▓▒░  ░░│
│░▓█▓▒▓▓▒▓▒░█▓▒▓▓ █▓████▓███▓▓ ░▒▒▒▓▓▒▒▓▓▒░▒▓░ ▒ ░██  ░ ███▒░▓░ ▓▒▓▒▒░ ░       │
│░█▓▓▓▓▓▓▒▒▓█▓▒▓▓ █▓▓███▓█████▓ ░▒▒▒▒▒▓▒▒░░▒░▓ ░░▓▒ ▒░▒░ ░▒ ▓░▓▓░░ ▓▓░ ░░░░ ░░ │
│ ██░  ░   ▒█▓▓▓▓ ██████▓██████▓░▒▒▒▓▓▓▓▓▓▒▒░▓▒░▓▒▒▒▒▓▓▓░▓▒▒░▒▒▓▓▒▒░▒  ░   ░ ░░│
│   ░░░▒     ▓▓▓▓ █▓▓█▓█▓███████▓░▒▓▒▓▓█▒░▒▓▓▓▒▒▓▓▒▓▓▒█░▒▓▒▒ ░▓▒▓ ▒▒░▒░ ▒ ░░░  │
│  ▒░░ ░ ▓  ░▓▓▓▓ ████▓█▓████████▒▓▒█░░▓▒▓ ▒▓▓░▒▒▓▒▒▓▒▓▒▒▓▒▓▓█▓▓ ▓░▒  ░░░░░ ░  │
│  ░▓ ▒░░░░ ▓█▓▓▓ █▓▒███▓█████████▓░░▒▒▓▒▓▒▒▓▓▒▓▓ ▓▒▓▓▒▓▒▓▒▓▓▒▓▓ ▓▒▒░▒ ▓▒ ░    │
│  ▒░ ░  ░░ ▓▒░░░ █▓▓██▓▓████▓▓▓█▓░░▒▓▓▓▒▒▓░▓▓▓▒▓▒▒▒▓▒▓▓▓▓▒▒▒▒▒░▒▒▒░██         │
│▒ ▒▒ ▒░░░  ▓▓▓▓▒ ▓▓▓███▒███▓███▓▓▒▒░░▒▒▒▒░▒▒▒▒▒▓▓▒▒░▓▓▓▒▓░▓▓▓▒▒▒▓░▓▓▓    ░ ░  │
│▒  ░    ▓  ▓▓▓▓▒ ▓▒▓▓▓█▒███▓▓▓█▒▒▓▓███████▓▒▓░▒▓░▓▒▓▒▓▓▒▒▒▓░▒▒░ ░▒▓▒░         │
│▓  ░░ ░ █  ▓▓▓▓▓ ▓▓▓█▓▓▓███▓█▓█▓████████████░▓▓▓▓▒▓▓▓▓▒▒▓▒▒▒ ▓▓ ▓░ ░▓     ░   │
│▓  ░  ░   ▒▓▓▓▒▒ ▓▓▓█▓█▒██▓▓▓█▓█▓█▓██████████░▓▒▒▓▓▓▒▓▓▓▒ ▒▓ ▒    ░▒▓▓▓█  ░   │
│▓▒░░▒▒░▒ ░░▓▓▓▓▓ ▓▓▓███▓██████████████████████░░▓▒░▓▒▒▒▒▒▒▓▓░       ░▒▒░░     │
│▓   ░▓▒▒   ░░▒▓▒ ░▓▓█▓█▓█████████████████████▒░░ ░ ░ ░▒░   ░ ░   ░▓▒          │
└──────────────────────────────────────────────────────────────────────────────┘
 unbourne is a gnu/systemd init process                          »cosmopolitan»


╔────────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell                                        ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│─╝

  The UNBOURNE SHELL is BASED off the Almquist Shell──colloquially
  known as the Debian Almquist Shell, a.k.a. dash──which perfected
  the work of the late Stephen Bourne whose shell set the standard
  for decades thanks to a spark of brilliance from Ken Thompson.

    git://git.kernel.org/pub/scm/utils/dash/dash.git
    fba95e9e4a5d0f1f1ac9f7d86557e47bc0e2656c
    Tue Jun 19 11:27:37 2018 -0400

  The UNBOURNE SHELL pays HOMAGE to the Stewards of House California:

    Almquist Shell
    Derived from software contributed to Berkeley by Kenneth Almquist.

    Copyright 1991,1993 The Regents of the University of California
    Copyright 1997-2018 Herbert Xu
    Copyright 1997 Christos Zoulas

    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.


╔──────────────────────────────────────────────────────────────────────┬───┬───╗
│ cosmopolitan § the unbourne shell » build                             /   /  │
╚────────────────────────────────────────────────────────────────────'>/dev/null

  cc -Os -o unbourne unbourne.c
  exit

╔────────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » macros                               ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

#include "libc/alg/alg.h"
#include "libc/calls/calls.h"
#include "libc/calls/sigbits.h"
#include "libc/calls/struct/dirent.h"
#include "libc/calls/struct/rlimit.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/stat.h"
#include "libc/calls/struct/tms.h"
#include "libc/calls/termios.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/conv.h"
#include "libc/fmt/fmt.h"
#include "libc/limits.h"
#include "libc/log/log.h"
#include "libc/mem/alloca.h"
#include "libc/mem/mem.h"
#include "libc/paths.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/sysconf.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/at.h"
#include "libc/sysv/consts/f.h"
#include "libc/sysv/consts/fd.h"
#include "libc/sysv/consts/fileno.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/consts/rlim.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/consts/w.h"
#include "third_party/gdtoa/gdtoa.h"
#include "third_party/musl/passwd.h"

#define likely(expr)   __builtin_expect(!!(expr), 1)
#define unlikely(expr) __builtin_expect(!!(expr), 0)

#undef CEOF
#undef rflag

/*
 * The follow should be set to reflect the type of system you have:
 *	JOBS -> 1 if you have Berkeley job control, 0 otherwise.
 *	SHORTNAMES -> 1 if your linker cannot handle long names.
 *	define BSD if you are running 4.2 BSD or later.
 *	define SYSV if you are running under System V.
 *	define DEBUG=1 to compile in debugging ('set -o debug' to turn on)
 *	define DEBUG=2 to compile in and turn on debugging.
 *	define DO_SHAREDVFORK to indicate that vfork(2) shares its address
 *	       with its parent.
 *
 * When debugging is on, debugging info will be written to ./trace and
 * a quit signal will generate a core dump.
 */

#define ALIASDEAD      2
#define ALIASINUSE     1
#define ARITH_MAX_PREC 8
#define ATABSIZE       39
#define CMDTABLESIZE   31
#define JOBS           1
#define NOPTS          17
#define OUTPUT_ERR     01
#define VTABSIZE       39

/* exceptions */
#define EXINT   0
#define EXERROR 1
#define EXEXIT  4

/*
 * 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.
 */
#define plinno (parsefile->linno)

/* Syntax classes */
#define CWORD     0
#define CNL       1
#define CBACK     2
#define CSQUOTE   3
#define CDQUOTE   4
#define CENDQUOTE 5
#define CBQUOTE   6
#define CVAR      7
#define CENDVAR   8
#define CLP       9
#define CRP       10
#define CEOF      11
#define CCTL      12
#define CSPCL     13
#define CIGN      14

/* Syntax classes for is_ functions */
#define ISDIGIT 01
#define ISUPPER 02
#define ISLOWER 04
#define ISUNDER 010
#define ISSPECL 020

#define SYNBASE 130
#define PEOF    -130

#define EOF_NLEFT -99

#define PEOA -129

#define BASESYNTAX (basesyntax + SYNBASE)
#define DQSYNTAX   (dqsyntax + SYNBASE)
#define SQSYNTAX   (sqsyntax + SYNBASE)
#define ARISYNTAX  (arisyntax + SYNBASE)

#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

/* expandarg() flags */
#define EXP_FULL      0x1
#define EXP_TILDE     0x2
#define EXP_VARTILDE  0x4
#define EXP_REDIR     0x8
#define EXP_CASE      0x10
#define EXP_VARTILDE2 0x40
#define EXP_WORD      0x80
#define EXP_QUOTED    0x100
#define EXP_KEEPNUL   0x200
#define EXP_DISCARD   0x400

/* reasons for skipping commands (see comment on breakcmd routine) */
#define SKIPBREAK   (1 << 0)
#define SKIPCONT    (1 << 1)
#define SKIPFUNC    (1 << 2)
#define SKIPFUNCDEF (1 << 3)

#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 TENDBQUOTE 10
#define TREDIR     11
#define TWORD      12
#define TNOT       13
#define TCASE      14
#define TDO        15
#define TDONE      16
#define TELIF      17
#define TELSE      18
#define TESAC      19
#define TFI        20
#define TFOR       21
#define TIF        22
#define TIN        23
#define TTHEN      24
#define TUNTIL     25
#define TWHILE     26
#define TBEGIN     27
#define TEND       28

/* control characters in argument strings */
#define CTL_FIRST    -127
#define CTLESC       -127
#define CTLVAR       -126
#define CTLENDVAR    -125
#define CTLBACKQ     -124
#define CTLARI       -122
#define CTLENDARI    -121
#define CTLQUOTEMARK -120
#define CTL_LAST     -120

/* variable substitution byte (follows CTLVAR) */
#define VSTYPE 0x0f
#define VSNUL  0x10

/* values of VSTYPE field */
#define VSNORMAL       0x1
#define VSMINUS        0x2
#define VSPLUS         0x3
#define VSQUESTION     0x4
#define VSASSIGN       0x5
#define VSTRIMRIGHT    0x6
#define VSTRIMRIGHTMAX 0x7
#define VSTRIMLEFT     0x8
#define VSTRIMLEFTMAX  0x9
#define VSLENGTH       0xa

/* values of checkkwd variable */
#define CHKALIAS   0x1
#define CHKKWD     0x2
#define CHKNL      0x4
#define CHKEOFMARK 0x8

/* flags in argument to evaltree */
#define EV_EXIT   01
#define EV_TESTED 02

#define INT_CHARS (sizeof(int) * CHAR_BIT / 3)

/*
 * 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. :-))
 */
#define barrier() ({ asm volatile("" : : : "memory"); })
#define INTOFF     \
  ({               \
    suppressint++; \
    barrier();     \
    0;             \
  })
#define INTON                                      \
  ({                                               \
    barrier();                                     \
    if (--suppressint == 0 && intpending) onint(); \
    0;                                             \
  })
#define FORCEINTON           \
  ({                         \
    barrier();               \
    suppressint = 0;         \
    if (intpending) onint(); \
    0;                       \
  })
#define SAVEINT(v) ((v) = suppressint)
#define RESTOREINT(v)                                    \
  ({                                                     \
    barrier();                                           \
    if ((suppressint = (v)) == 0 && intpending) onint(); \
    0;                                                   \
  })
#define CLEAR_PENDING_INT intpending = 0
#define int_pending()     intpending

/*
 * Most machines require the value returned from malloc to be aligned
 * in some way.  The following macro will get this right on many machines.
 */
#define SHELL_SIZE \
  (sizeof(union {  \
     int i;        \
     char *cp;     \
     double d;     \
   }) -            \
   1)

/*
 * It appears that grabstackstr() will barf with such alignments
 * because stalloc() will return a string allocated in a new stackblock.
 */
#define SHELL_ALIGN(nbytes) (((nbytes) + SHELL_SIZE) & ~SHELL_SIZE)

/*
 * Minimum size of a block
 *
 * 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 504 was chosen because the Ultrix malloc handles that size
 * well.
 */
#define MINSIZE SHELL_ALIGN(504)

/* flags */
#define VEXPORT    0x001
#define VREADONLY  0x002
#define VSTRFIXED  0x004
#define VTEXTFIXED 0x008
#define VSTACK     0x010
#define VUNSET     0x020
#define VNOFUNC    0x040
#define VNOSET     0x080
#define VNOSAVE    0x100

/*
 * Evaluate a command.
 */
#define ALIASCMD   (kBuiltinCmds + 3)
#define BGCMD      (kBuiltinCmds + 4)
#define BREAKCMD   (kBuiltinCmds + 5)
#define CDCMD      (kBuiltinCmds + 6)
#define COMMANDCMD (kBuiltinCmds + 8)
#define DOTCMD     (kBuiltinCmds + 0)
#define ECHOCMD    (kBuiltinCmds + 10)
#define EVALCMD    (kBuiltinCmds + 11)
#define EXECCMD    (kBuiltinCmds + 12)
#define EXITCMD    (kBuiltinCmds + 13)
#define EXPORTCMD  (kBuiltinCmds + 14)
#define FALSECMD   (kBuiltinCmds + 15)
#define FGCMD      (kBuiltinCmds + 16)
#define GETOPTSCMD (kBuiltinCmds + 17)
#define HASHCMD    (kBuiltinCmds + 18)
#define JOBSCMD    (kBuiltinCmds + 19)
#define KILLCMD    (kBuiltinCmds + 20)
#define LOCALCMD   (kBuiltinCmds + 21)
#define PRINTFCMD  (kBuiltinCmds + 22)
#define PWDCMD     (kBuiltinCmds + 23)
#define READCMD    (kBuiltinCmds + 24)
#define RETURNCMD  (kBuiltinCmds + 26)
#define SETCMD     (kBuiltinCmds + 27)
#define SHIFTCMD   (kBuiltinCmds + 28)
#define TESTCMD    (kBuiltinCmds + 2)
#define TIMESCMD   (kBuiltinCmds + 30)
#define TRAPCMD    (kBuiltinCmds + 31)
#define TRUECMD    (kBuiltinCmds + 1)
#define TYPECMD    (kBuiltinCmds + 33)
#define ULIMITCMD  (kBuiltinCmds + 34)
#define UMASKCMD   (kBuiltinCmds + 35)
#define UNALIASCMD (kBuiltinCmds + 36)
#define UNSETCMD   (kBuiltinCmds + 37)
#define WAITCMD    (kBuiltinCmds + 38)

#define BUILTIN_SPECIAL 0x1
#define BUILTIN_REGULAR 0x2
#define BUILTIN_ASSIGN  0x4

/* mode flags for set_curjob */
#define CUR_DELETE  2
#define CUR_RUNNING 1
#define CUR_STOPPED 0

/* mode flags for dowait */
#define DOWAIT_NORMAL  0
#define DOWAIT_BLOCK   1
#define DOWAIT_WAITCMD 2

/* _rmescape() flags */
#define RMESCAPE_ALLOC 0x01
#define RMESCAPE_GLOB  0x02
#define RMESCAPE_GROW  0x08
#define RMESCAPE_HEAP  0x10

/* Add CTLESC when necessary. */
#define QUOTES_ESC (EXP_FULL | EXP_CASE)

#define IBUFSIZ   (BUFSIZ + 1)
#define OUTBUFSIZ BUFSIZ
#define MEM_OUT   -3

/*
 * 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
#define S_CATCH    2
#define S_IGN      3
#define S_HARD_IGN 4
#define S_RESET    5

#define NCMD      0
#define NPIPE     1
#define NREDIR    2
#define NBACKGND  3
#define NSUBSHELL 4
#define NAND      5
#define NOR       6
#define NSEMI     7
#define NIF       8
#define NWHILE    9
#define NUNTIL    10
#define NFOR      11
#define NCASE     12
#define NCLIST    13
#define NDEFUN    14
#define NARG      15
#define NTO       16
#define NCLOBBER  17
#define NFROM     18
#define NFROMTO   19
#define NAPPEND   20
#define NTOFD     21
#define NFROMFD   22
#define NHERE     23
#define NXHERE    24
#define NNOT      25

/* Mode argument to forkshell.  Don't change FORK_FG or FORK_BG. */
#define FORK_FG    0
#define FORK_BG    1
#define FORK_NOJOB 2

/* mode flags for showjob(s) */
#define SHOW_PGID    0x01
#define SHOW_PID     0x04
#define SHOW_CHANGED 0x08

/* values of cmdtype */
#define CMDUNKNOWN  -1
#define CMDNORMAL   0
#define CMDFUNCTION 1
#define CMDBUILTIN  2

/* action to find_command() */
#define DO_ERR      0x01
#define DO_ABS      0x02
#define DO_NOFUNC   0x04
#define DO_ALTPATH  0x08
#define DO_REGBLTIN 0x10

/* flags passed to redirect */
#define REDIR_PUSH    01
#define REDIR_SAVEFD2 03

#define CD_PHYSICAL 1
#define CD_PRINT    2

#define REALLY_CLOSED -3
#define EMPTY         -2
#define CLOSED        -1
#define PIPESIZE      4096

#define rootshell (!shlvl)

#define eflag optlist[0]
#define fflag optlist[1]
#define Iflag optlist[2]
#define iflag optlist[3]
#define mflag optlist[4]
#define nflag optlist[5]
#define sflag optlist[6]
#define xflag optlist[7]
#define vflag optlist[8]
#define Vflag optlist[9]
#define Eflag optlist[10]
#define Cflag optlist[11]
#define aflag optlist[12]
#define bflag optlist[13]
#define uflag optlist[14]
#define nolog optlist[15]
#define debug optlist[16]

/* Used by expandstr to get here-doc like behaviour. */
#define FAKEEOFMARK (char *)1

/*
 * 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.
 */
/* #undef stdout  /\* TODO(jart): XXX *\/ */
/* #undef stderr */
/* #undef putc */
/* #undef putchar */
#undef fileno
/* #define stdout out1 */
/* #define stderr out2 */
#undef printf
#define printf out1fmt
/* #define putc(c, file) outc(c, file) */
/* #define putchar(c) out1c(c) */
#define FILE      struct output
#define fileno(f) ((f)->fd)
/* #define ferror outerr */
#define INITARGS(argv)
#define setprogname(s)
#define getprogname() commandname

#define setlocate(l, s) 0
#define equal(s1, s2)   (strcmp(s1, s2) == 0)
/* #define getenv(p)       bltinlookup((p), 0) */
#define isodigit(c)   ((c) >= '0' && (c) <= '7')
#define octtobin(c)   ((c) - '0')
#define scopy(s1, s2) ((void)strcpy(s2, s1))

#define TRACE(param)
/* #define TRACE(param)   \ */
/*   do {                 \ */
/*     printf("TRACE: "); \ */
/*     printf param;      \ */
/*   } while (0) */

#define TRACEV(param)
#define digit_val(c)  ((c) - '0')
#define is_alpha(c)   isalpha((unsigned char)(c))
#define is_digit(c)   ((unsigned)((c) - '0') <= 9)
#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c)))
#define is_name(c)    ((c) == '_' || isalpha((unsigned char)(c)))
#define is_special(c) ((is_type + SYNBASE)[(signed char)(c)] & (ISSPECL | ISDIGIT))

#define uninitialized_var(x) x = x /* suppress uninitialized warning w/o code */

/*
 * Shell variables.
 */
#define vifs    varinit[0]
#define vpath   (&vifs)[1]
#define vps1    (&vpath)[1]
#define vps2    (&vps1)[1]
#define vps4    (&vps2)[1]
#define voptind (&vps4)[1]
#define vlineno (&voptind)[1]
#define defifs  (defifsvar + 4)
#define defpath (defpathvar + 36)

/*
 * 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)
#define linenoval()   (vlineno.text + 7)
#define mpathset()    ((vmpath.flags & VUNSET) == 0)
#define environment() listvars(VEXPORT, VUNSET, 0)

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » data structures                      ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

typedef void *pointer;

struct redirtab {
  struct redirtab *next;
  int renamed[10];
};

/*
 * 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 exeception. 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.
 */
struct jmploc {
  jmp_buf loc;
};

/* PEOF (the end of file marker) is defined in syntax.h */
enum {
  INPUT_PUSH_FILE = 1,
  INPUT_NOFILE_OK = 2,
};

struct alias {
  struct alias *next;
  char *name;
  char *val;
  int flag;
};

struct shparam {
  int nparam;           /* # of positional parameters (without $0) */
  unsigned char malloc; /* if parameter list dynamically allocated */
  char **p;             /* parameter list */
  int optind;           /* next parameter to be processed by getopts */
  int optoff;           /* used by getopts */
};

struct strpush {
  struct strpush *prev; /* preceding string on stack */
  char *prevstring;
  int prevnleft;
  struct alias *ap; /* if push was associated with an alias */
  char *string;     /* remember the string since it may change */
  int lastc[2];     /* Remember last two characters for pungetc. */
  int unget;        /* Number of outstanding calls to pungetc. */
};

/*
 * 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 chars left in this buffer */
  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 lastc[2];               /* Remember last two characters for pungetc. */
  int unget;                  /* Number of outstanding calls to pungetc. */
};

struct output {
  char *nextc;
  char *end;
  char *buf;
  unsigned bufsize;
  int fd;
  int flags;
};

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 synstack {
  const char *syntax;
  struct synstack *prev;
  struct synstack *next;
  int innerdq;
  int varpushed;
  int dblquote;
  int varnest;    /* levels of variables expansion */
  int parenlevel; /* levels of parens in arithmetic */
  int dqvarnest;  /* levels of variables expansion within double quotes */
};

struct procstat {
  int pid;    /* process id */
  int status; /* last process status from wait() */
  char *cmd;  /* text of command being run */
};

/*
 * 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 job {
  struct procstat ps0;  /* status of process */
  struct procstat *ps;  /* status or processes when more than one */
  int stopstatus;       /* status of a stopped job */
  unsigned nprocs : 16, /* number of processes */
      state : 8,
#define JOBRUNNING 0
#define JOBSTOPPED 1
#define JOBDONE    2
      sigint : 1,       /* job was killed by SIGINT */
      jobctl : 1,       /* job running under job control */
      waited : 1,       /* true if this entry has been waited for */
      used : 1,         /* true if this entry is in used */
      changed : 1;      /* true if status has changed */
  struct job *prev_job; /* previous job */
};

struct ncmd {
  int type;
  int linno;
  union node *assign;
  union node *args;
  union node *redirect;
};

struct npipe {
  int type;
  int backgnd;
  struct nodelist *cmdlist;
};

struct nredir {
  int type;
  int linno;
  union node *n;
  union node *redirect;
};

struct nbinary {
  int type;
  union node *ch1;
  union node *ch2;
};

struct nif {
  int type;
  union node *test;
  union node *ifpart;
  union node *elsepart;
};

struct nfor {
  int type;
  int linno;
  union node *args;
  union node *body;
  char *var_;
};

struct ncase {
  int type;
  int linno;
  union node *expr;
  union node *cases;
};

struct nclist {
  int type;
  union node *next;
  union node *pattern;
  union node *body;
};

struct ndefun {
  int type;
  int linno;
  char *text;
  union node *body;
};

struct narg {
  int type;
  union node *next;
  char *text;
  struct nodelist *backquote;
};

struct nfile {
  int type;
  union node *next;
  int fd;
  union node *fname;
  char *expfname;
};

struct ndup {
  int type;
  union node *next;
  int fd;
  int dupfd;
  union node *vname;
};

struct nhere {
  int type;
  union node *next;
  int fd;
  union node *doc;
};

struct nnot {
  int type;
  union node *com;
};

union node {
  int type;
  struct ncmd ncmd;
  struct npipe npipe;
  struct nredir nredir;
  struct nbinary nbinary;
  struct nif nif;
  struct nfor nfor;
  struct ncase ncase;
  struct nclist nclist;
  struct ndefun ndefun;
  struct narg narg;
  struct nfile nfile;
  struct ndup ndup;
  struct nhere nhere;
  struct nnot nnot;
};

struct nodelist {
  struct nodelist *next;
  union node *n;
};

struct funcnode {
  int count;
  union node n;
};

struct localvar_list {
  struct localvar_list *next;
  struct localvar *lv;
};

struct Var {
  struct Var *next; /* next entry in hash list */
  int flags;        /* flags are defined above */
  const 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 */
  const char *text;      /* saved text */
};

union yystype {
  int64_t val;
  char *name;
};

struct strlist {
  struct strlist *next;
  char *text;
};

struct arglist {
  struct strlist *list;
  struct strlist **lastp;
};

/*
 * Structure specifying which parts of the string should be searched
 * for IFS characters.
 */
struct ifsregion {
  struct ifsregion *next; /* next region in list */
  int begoff;             /* offset of start of region */
  int endoff;             /* offset of end of region */
  int nulonly;            /* search for nul bytes only */
};

struct builtincmd {
  const char *name;
  int (*builtin)(int, char **);
  unsigned flags;
};

struct cmdentry {
  int cmdtype;
  union param {
    int index;
    const struct builtincmd *cmd;
    struct funcnode *func;
  } u;
};

struct tblentry {
  struct tblentry *next; /* next entry in hash chain */
  union param param;     /* definition of builtin function */
  short cmdtype;         /* index identifying command */
  char rehash;           /* if set, cd done since entry created */
  char cmdname[];        /* name of 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 */
};

struct stack_block {
  struct stack_block *prev;
  char space[MINSIZE];
};

struct stackmark {
  struct stack_block *stackp;
  char *stacknxt;
  unsigned stacknleft;
};

struct limits {
  const char *name;
  int cmd;
  int factor; /* multiply by to get rlim_{cur,max} values */
  char option;
};

struct t_op {
  const char *op_text;
  short op_num, op_type;
};

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » bss                                  ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

static char **argptr; /* argument list for builtin commands */
static char **gargv;
static char **t_wp;
static char *arg0; /* value of $0 */
static char *cmdnextc;
static char *commandname;
static char *expdest; /* output of current string */
static char *expdir;
static char *funcstring;      /* block to allocate strings from */
static char *minusc;          /* argument to -c option */
static char *optionarg;       /* set by nextopt (like getopt) */
static char *optptr;          /* used by nextopt */
static char *trap[NSIG];      /* trap handler commands */
static char *wordtext;        /* text of last word returned by readtoken */
static char basebuf[IBUFSIZ]; /* buffer for top level input file */
static char gotsig[NSIG - 1]; /* indicates specified signal received */
static char nullstr[1];       /* zero length string */
static char optlist[NOPTS];
static char sigmode[NSIG - 1]; /* current value of signal */
static const char *arith_buf;
static const char *arith_startbuf;
static const char *pathopt;
static int back_exitstatus; /* exit status of backquoted command */
static int checkkwd;
static int doprompt; /* if set, prompt the user */
static int errlinno;
static int evalskip; /* set if we are skipping commands */
static int exception;
static int exitstatus;     /* exit status of last command */
static int funcblocksize;  /* size of structures in function */
static int funcline;       /* start line of function, or 0 if not in one */
static int funcstringsize; /* size of strings in node */
static int gotsigchld;     /* received SIGCHLD */
static int initialpgrp;    /* pgrp of shell on invocation */
static int job_warning;
static int jobctl;
static int last_token;
static int lasttoken; /* last token read */
static int lineno;
static int loopnest;   /* current loop nesting level */
static int needprompt; /* true if interactive and at start of line */
static int quoteflag;  /* set if (part of) last token was quoted */
static int rootpid;
static int rval;
static int shlvl;
static int skipcount; /* number of levels to skip */
static int suppressint;
static int tokpushback;       /* last token pushed back */
static int trapcnt;           /* number of non-null traps */
static int ttyfd = -1;        /* control terminal */
static int vforked;           /* Set if we are in the vforked child */
static int whichprompt;       /* 1 == PS1, 2 == PS2 */
static int backgndpid;        /* pid of last background process */
static pointer funcblock;     /* block to allocate function from */
static struct arglist exparg; /* holds expanded arg list */
static struct heredoc *heredoc;
static struct heredoc *heredoclist; /* list of here documents to read */
static struct ifsregion *ifslastp;  /* last struct in list */
static struct ifsregion ifsfirst;   /* first struct in list of ifs regions */
static struct jmploc *handler;
static struct job *curjob; /* current job */
static struct job *jobtab; /* array of jobs */
static struct localvar_list *localvar_stack;
static struct nodelist *argbackq; /* list of back quote expressions */
static struct nodelist *backquotelist;
static struct output preverrout;
static struct parsefile basepf; /* top level input file */
static struct redirtab *redirlist;
static struct shparam shellparam; /* current positional parameters */
static struct stack_block stackbase;
static struct t_op const *t_wp_op;
static struct tblentry **lastcmdentry;
static struct tblentry *cmdtable[CMDTABLESIZE];
static struct Var *vartab[VTABSIZE];
static union node *redirnode;
static union yystype yylval;
static unsigned expdir_max;
static unsigned njobs; /* size of array */
static volatile sig_atomic_t intpending;
static volatile sig_atomic_t pending_sig; /* last pending signal */
static struct alias *atab[ATABSIZE];

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » data                                 ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

static char *curdir = nullstr;  /* current working directory */
static char *physdir = nullstr; /* physical working directory */
static char *sstrend = stackbase.space + MINSIZE;
static char *stacknxt = stackbase.space;
static char defifsvar[] = "IFS= \t\n";
static char defoptindvar[] = "OPTIND=1";
static char linenovar[sizeof("LINENO=") + INT_CHARS + 1] = "LINENO=";
static int builtinloc = -1; /* index in path of %builtin, or -1 */
static int savestatus = -1; /* exit status of last command outside traps */
static struct output errout = {0, 0, 0, 0, 2, 0};
static struct output output = {0, 0, 0, OUTBUFSIZ, 1, 0};
static struct parsefile *parsefile = &basepf; /* current input file */
static struct stack_block *stackp = &stackbase;
static unsigned stacknleft = MINSIZE;

static struct output *out1 = &output;
static struct output *out2 = &errout;

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » rodata                               ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

/* 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, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1,
};

static const char *const tokname[] = {
    "end of file", "newline",   "\";\"",     "\"&\"",   "\"&&\"",   "\"||\"",
    "\"|\"",       "\"(\"",     "\")\"",     "\";;\"",  "\"`\"",    "redirection",
    "word",        "\"!\"",     "\"case\"",  "\"do\"",  "\"done\"", "\"elif\"",
    "\"else\"",    "\"esac\"",  "\"fi\"",    "\"for\"", "\"if\"",   "\"in\"",
    "\"then\"",    "\"until\"", "\"while\"", "\"{\"",   "\"}\"",
};

static const char *const parsekwd[] = {"!",   "case", "do", "done", "elif",  "else",  "esac", "fi",
                                       "for", "if",   "in", "then", "until", "while", "{",    "}"};

static const char defpathvar[] =
    "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";

static const char *const optnames[NOPTS] = {
    "errexit",   "noglob", "ignoreeof", "interactive", "monitor", "noexec",
    "stdin",     "xtrace", "verbose",   "vi",          "emacs",   "noclobber",
    "allexport", "notify", "nounset",   "nolog",       "debug",
};

static const char optletters[NOPTS] = {
    'e', 'f', 'I', 'i', 'm', 'n', 's', 'x', 'v', 'V', 'E', 'C', 'a', 'b', 'u', 0, 0,
};

static const char spcstr[] = " ";
static const char snlfmt[] = "%s\n";
static const char qchars[] = {CTLESC, CTLQUOTEMARK, 0};
static const char illnum[] = "Illegal number: %s";
static const char homestr[] = "HOME";
static const char dolatstr[] = {CTLQUOTEMARK, CTLVAR, VSNORMAL, '@', '=', CTLQUOTEMARK, '\0'};

/* TODO(jart): What's wrong with varinit? */
#if defined(__GNUC__) || defined(__llvm__)
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif

/* Some macros depend on the order, add new variables to the end. */
static void changepath(const char *);
static void getoptsreset(const char *);

static struct Var varinit[] = {
    {0, VSTRFIXED | VTEXTFIXED, defifsvar, 0},
    {0, VSTRFIXED | VTEXTFIXED, defpathvar, changepath},
    {0, VSTRFIXED | VTEXTFIXED, "PS1=$ ", 0},
    {0, VSTRFIXED | VTEXTFIXED, "PS2=> ", 0},
    {0, VSTRFIXED | VTEXTFIXED, "PS4=+ ", 0},
    {0, VSTRFIXED | VTEXTFIXED, defoptindvar, getoptsreset},
    {0, VSTRFIXED | VTEXTFIXED, linenovar, 0},
};

static const char kPrec[ARITH_BINOP_MAX - ARITH_BINOP_MIN] = {
#define ARITH_PRECEDENCE(OP, PREC) [OP - ARITH_BINOP_MIN] = PREC
    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),
#undef ARITH_PRECEDENCE
};

static const short nodesize[26] /* clang-format off */ = {
    SHELL_ALIGN(sizeof(struct ncmd)),    SHELL_ALIGN(sizeof(struct npipe)),
    SHELL_ALIGN(sizeof(struct nredir)),  SHELL_ALIGN(sizeof(struct nredir)),
    SHELL_ALIGN(sizeof(struct nredir)),  SHELL_ALIGN(sizeof(struct nbinary)),
    SHELL_ALIGN(sizeof(struct nbinary)), SHELL_ALIGN(sizeof(struct nbinary)),
    SHELL_ALIGN(sizeof(struct nif)),     SHELL_ALIGN(sizeof(struct nbinary)),
    SHELL_ALIGN(sizeof(struct nbinary)), SHELL_ALIGN(sizeof(struct nfor)),
    SHELL_ALIGN(sizeof(struct ncase)),   SHELL_ALIGN(sizeof(struct nclist)),
    SHELL_ALIGN(sizeof(struct ndefun)),  SHELL_ALIGN(sizeof(struct narg)),
    SHELL_ALIGN(sizeof(struct nfile)),   SHELL_ALIGN(sizeof(struct nfile)),
    SHELL_ALIGN(sizeof(struct nfile)),   SHELL_ALIGN(sizeof(struct nfile)),
    SHELL_ALIGN(sizeof(struct nfile)),   SHELL_ALIGN(sizeof(struct ndup)),
    SHELL_ALIGN(sizeof(struct ndup)),    SHELL_ALIGN(sizeof(struct nhere)),
    SHELL_ALIGN(sizeof(struct nhere)),   SHELL_ALIGN(sizeof(struct nnot)),
} /* clang-format on */;

/* syntax table used when not in quotes */
static const char basesyntax[] /* clang-format off */ = {
    CEOF,  CSPCL,   CWORD,   CCTL,    CCTL,  CCTL,  CCTL,  CCTL,    CCTL,
    CCTL,  CCTL,    CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CSPCL, CNL,   CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CSPCL, CWORD,   CDQUOTE, CWORD,   CVAR,  CWORD, CSPCL, CSQUOTE, CSPCL,
    CSPCL, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CSPCL, CSPCL,   CWORD,   CSPCL,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CBACK, CWORD,   CWORD,
    CWORD, CBQUOTE, CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CWORD,   CWORD,   CWORD, CWORD, CWORD, CWORD,   CWORD,
    CWORD, CWORD,   CSPCL,   CENDVAR, CWORD, CWORD
} /* clang-format on */;

/* syntax table used when in double quotes */
static const char dqsyntax[] /* clang-format off */ = {
    CEOF,  CIGN,    CWORD,     CCTL,    CCTL,  CCTL,  CCTL,  CCTL,  CCTL,
    CCTL,  CCTL,    CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CNL,   CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CCTL,    CENDQUOTE, CWORD,   CVAR,  CWORD, CWORD, CWORD, CWORD,
    CWORD, CCTL,    CWORD,     CWORD,   CCTL,  CWORD, CCTL,  CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CCTL,
    CWORD, CWORD,   CCTL,      CWORD,   CCTL,  CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CCTL,  CBACK, CCTL,  CWORD,
    CWORD, CBQUOTE, CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CWORD,   CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD,   CWORD,     CENDVAR, CCTL,  CWORD
} /* clang-format on */;

/* syntax table used when in single quotes */
static const char sqsyntax[] = {
    CEOF,      CIGN,  CWORD, CCTL,  CCTL,  CCTL,  CCTL,  CCTL,  CCTL,  CCTL,  CCTL,  CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CNL,   CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL,  CWORD, CWORD, CWORD, CWORD, CWORD,
    CENDQUOTE, CWORD, CWORD, CCTL,  CWORD, CWORD, CCTL,  CWORD, CCTL,  CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CCTL,  CWORD, CWORD, CCTL,  CWORD, CCTL,  CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CCTL,      CCTL,  CCTL,  CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,
    CWORD,     CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CWORD, CCTL,  CWORD,
};

/* syntax table used when in arithmetic */
static const char arisyntax[] = {
    CEOF,  CIGN,  CWORD, CCTL,  CCTL,  CCTL,    CCTL,  CCTL,  CCTL,    CCTL,  CCTL,  CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CNL,   CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CVAR,  CWORD, CWORD,
    CWORD, CLP,   CRP,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CBACK, CWORD, CWORD, CWORD, CBQUOTE, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD,   CWORD, CWORD, CWORD, CWORD,
    CWORD, CWORD, CWORD, CWORD, CWORD, CWORD,   CWORD, CWORD, CENDVAR, CWORD, CWORD,
};

/* character classification table */
static const char is_type[] /* clang-format off */ = {
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       0,       0,       0,       0,       0,
    0,       0,       0,       ISSPECL, 0,       ISSPECL, ISSPECL, 0,
    0,       0,       0,       0,       ISSPECL, 0,       0,       ISSPECL,
    0,       0,       ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT,
    ISDIGIT, ISDIGIT, ISDIGIT, ISDIGIT, 0,       0,       0,       0,
    0,       ISSPECL, ISSPECL, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER,
    ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER,
    ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER,
    ISUPPER, ISUPPER, ISUPPER, ISUPPER, ISUPPER, 0,       0,       0,
    0,       ISUNDER, 0,       ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER,
    ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER,
    ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER,
    ISLOWER, ISLOWER, ISLOWER, ISLOWER, ISLOWER, 0,       0,       0,
    0,       0
} /* clang-format on */;

static int aliascmd();
static int bgcmd();
static int breakcmd();
static int cdcmd();
static int commandcmd();
static int dotcmd();
static int echocmd();
static int evalcmd();
static int execcmd();
static int exitcmd();
static int exportcmd();
static int falsecmd();
static int fgcmd();
static int getoptscmd();
static int hashcmd();
static int jobscmd();
static int killcmd();
static int localcmd();
static int printfcmd();
static int pwdcmd();
static int readcmd();
static int returncmd();
static int setcmd();
static int shiftcmd();
static int testcmd();
static int timescmd();
static int trapcmd();
static int truecmd();
static int typecmd();
static int ulimitcmd();
static int umaskcmd();
static int unaliascmd();
static int unsetcmd();
static int waitcmd();

static const struct builtincmd kBuiltinCmds[] = {
    {".", dotcmd, 3},            //
    {":", truecmd, 3},           //
    {"[", testcmd, 0},           //
    {"alias", aliascmd, 6},      //
    {"bg", bgcmd, 2},            //
    {"break", breakcmd, 3},      //
    {"cd", cdcmd, 2},            //
    {"chdir", cdcmd, 0},         //
    {"command", commandcmd, 2},  //
    {"continue", breakcmd, 3},   //
    {"echo", echocmd, 0},        //
    {"eval", NULL, 3},           //
    {"exec", execcmd, 3},        //
    {"exit", exitcmd, 3},        //
    {"export", exportcmd, 7},    //
    {"false", falsecmd, 2},      //
    {"fg", fgcmd, 2},            //
    {"getopts", getoptscmd, 2},  //
    {"hash", hashcmd, 2},        //
    {"jobs", jobscmd, 2},        //
    {"kill", killcmd, 2},        //
    {"local", localcmd, 7},      //
    {"printf", printfcmd, 0},    //
    {"pwd", pwdcmd, 2},          //
    {"read", readcmd, 2},        //
    {"readonly", exportcmd, 7},  //
    {"return", returncmd, 3},    //
    {"set", setcmd, 3},          //
    {"shift", shiftcmd, 3},      //
    {"test", testcmd, 0},        //
    {"times", timescmd, 3},      //
    {"trap", trapcmd, 3},        //
    {"true", truecmd, 2},        //
    {"type", typecmd, 2},        //
    {"ulimit", ulimitcmd, 2},    //
    {"umask", umaskcmd, 2},      //
    {"unalias", unaliascmd, 2},  //
    {"unset", unsetcmd, 3},      //
    {"wait", waitcmd, 2},        //
};

enum token {
  EOI,
  FILRD,
  FILWR,
  FILEX,
  FILEXIST,
  FILREG,
  FILDIR,
  FILCDEV,
  FILBDEV,
  FILFIFO,
  FILSOCK,
  FILSYM,
  FILGZ,
  FILTT,
  FILSUID,
  FILSGID,
  FILSTCK,
  FILNT,
  FILOT,
  FILEQ,
  FILUID,
  FILGID,
  STREZ,
  STRNZ,
  STREQ,
  STRNE,
  STRLT,
  STRGT,
  INTEQ,
  INTNE,
  INTGE,
  INTGT,
  INTLE,
  INTLT,
  UNOT,
  BAND,
  BOR,
  LPAREN,
  RPAREN,
  OPERAND
};

enum token_types { UNOP, BINOP, BUNOP, BBINOP, PAREN };

static struct t_op const ops[] = {
    {"-r", FILRD, UNOP},
    {"-w", FILWR, UNOP},
    {"-x", FILEX, UNOP},
    {"-e", FILEXIST, UNOP},
    {"-f", FILREG, UNOP},
    {"-d", FILDIR, UNOP},
    {"-c", FILCDEV, UNOP},
    {"-b", FILBDEV, UNOP},
    {"-p", FILFIFO, UNOP},
    {"-u", FILSUID, UNOP},
    {"-g", FILSGID, UNOP},
    {"-k", FILSTCK, UNOP},
    {"-s", FILGZ, UNOP},
    {"-t", FILTT, UNOP},
    {"-z", STREZ, UNOP},
    {"-n", STRNZ, UNOP},
    {"-h", FILSYM, UNOP}, /* for backwards compat */
    {"-O", FILUID, UNOP},
    {"-G", FILGID, UNOP},
    {"-L", FILSYM, UNOP},
    {"-S", FILSOCK, UNOP},
    {"=", STREQ, BINOP},
    {"!=", STRNE, BINOP},
    {"<", STRLT, BINOP},
    {">", STRGT, BINOP},
    {"-eq", INTEQ, BINOP},
    {"-ne", INTNE, BINOP},
    {"-ge", INTGE, BINOP},
    {"-gt", INTGT, BINOP},
    {"-le", INTLE, BINOP},
    {"-lt", INTLT, BINOP},
    {"-nt", FILNT, BINOP},
    {"-ot", FILOT, BINOP},
    {"-ef", FILEQ, BINOP},
    {"!", UNOT, BUNOP},
    {"-a", BAND, BBINOP},
    {"-o", BOR, BBINOP},
    {"(", LPAREN, PAREN},
    {")", RPAREN, PAREN},
    {0, 0, 0},
};

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » text                                 ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

/*
 * Hack to calculate maximum length.
 * (length * 8 - 1) * log10(2) + 1 + 1 + 12
 * The second 1 is for the minus sign and the 12 is a safety margin.
 */
static inline int max_int_length(int bytes) {
  return (bytes * 8 - 1) * 0.30102999566398119521 + 14;
}

/* prefix -- see if pfx is a prefix of string. */
static char *prefix(const char *string, const char *pfx) {
  while (*pfx) {
    if (*pfx++ != *string++) return 0;
  }
  return (char *)string;
}

/*
 * Wrapper around strcmp for qsort/bsearch/...
 */
static int pstrcmp(const void *a, const void *b) {
  return strcmp(*(const char *const *)a, *(const char *const *)b);
}

/*
 * Find a string is in a sorted array.
 */
static const char *const *findstring(const char *s, const char *const *array, unsigned nmemb) {
  return bsearch(&s, array, nmemb, sizeof(const char *), pstrcmp);
}

/* Types of operations (passed to the errmsg routine). */
enum ShErrorAction { E_OPEN, E_CREAT, E_EXEC };

/*
 * Return a string describing an error.  The returned string may be a
 * pointer to a static buffer that will be overwritten on the next call.
 * Action describes the operation that got the error.
 */
static const char *errmsg(int e, enum ShErrorAction action) {
  if (e != ENOENT && e != ENOTDIR) return strerror(e);
  switch (action) {
    case E_OPEN:
      return "No such file";
    case E_CREAT:
      return "Directory nonexistent";
    default:
      return "not found";
  }
}

static inline void sigclearmask(void) {
  sigset_t set;
  sigemptyset(&set);
  sigprocmask(SIG_SETMASK, &set, 0);
}

/*
 * 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".
 */
wontreturn static void exraise(int e) {
  if (vforked) _exit(exitstatus);
  INTOFF;
  exception = e;
  longjmp(handler->loc, 1);
}

/*
 * Called from trap.c when a SIGINT is received.  (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.  (The test for iflag is just
 * defensive programming.)
 */
wontreturn static void onint(void) {
  intpending = 0;
  sigclearmask();
  if (!(rootshell && iflag)) {
    signal(SIGINT, SIG_DFL);
    raise(SIGINT);
  }
  exitstatus = SIGINT + 128;
  exraise(EXINT);
}

static pointer ckmalloc(unsigned nbytes) {
  pointer p;
  if (!(p = malloc(nbytes))) abort();
  return p;
}

static pointer ckrealloc(pointer p, unsigned nbytes) {
  if (!(p = realloc(p, nbytes))) abort();
  return p;
}

#define stackblock()     ((void *)stacknxt)
#define stackblocksize() stacknleft
#define STARTSTACKSTR(p) ((p) = stackblock())
#define STPUTC(c, p)     ((p) = _STPUTC((c), (p)))
#define CHECKSTRSPACE(n, p)              \
  ({                                     \
    char *q = (p);                       \
    unsigned l = (n);                    \
    unsigned m = sstrend - q;            \
    if (l > m) (p) = makestrspace(l, q); \
    0;                                   \
  })
#define USTPUTC(c, p)        (*p++ = (c))
#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) - (char *)stackblock())
#define ungrabstackstr(s, p) stunalloc((s))
#define stackstrend()        ((void *)sstrend)
#define ckfree(p)            free((pointer)(p))

static pointer stalloc(unsigned nbytes) {
  char *p;
  unsigned aligned;
  aligned = SHELL_ALIGN(nbytes);
  if (aligned > stacknleft) {
    unsigned len;
    unsigned blocksize;
    struct stack_block *sp;
    blocksize = aligned;
    if (blocksize < MINSIZE) blocksize = MINSIZE;
    len = sizeof(struct stack_block) - MINSIZE + blocksize;
    if (len < blocksize) abort();
    INTOFF;
    sp = ckmalloc(len);
    sp->prev = stackp;
    stacknxt = sp->space;
    stacknleft = blocksize;
    sstrend = stacknxt + blocksize;
    stackp = sp;
    INTON;
  }
  p = stacknxt;
  stacknxt += aligned;
  stacknleft -= aligned;
  return p;
}

static inline void grabstackblock(unsigned len) {
  stalloc(len);
}

static void pushstackmark(struct stackmark *mark, unsigned len) {
  mark->stackp = stackp;
  mark->stacknxt = stacknxt;
  mark->stacknleft = stacknleft;
  grabstackblock(len);
}

static 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 = mark->stacknxt + mark->stacknleft;
  INTON;
}

static void setstackmark(struct stackmark *mark) {
  pushstackmark(mark, stacknxt == stackp->space && stackp != &stackbase);
}

static void stunalloc(pointer p) {
  stacknleft += stacknxt - (char *)p;
  stacknxt = p;
}

/* Like strdup but works with the ash stack. */
static char *sstrdup(const char *p) {
  unsigned len = strlen(p) + 1;
  return memcpy(stalloc(len), p, len);
}

int xwrite(int, const void *, uint64_t);

static void flushout(struct output *dest) {
  unsigned len;
  len = dest->nextc - dest->buf;
  if (!len || dest->fd < 0) return;
  dest->nextc = dest->buf;
  if ((xwrite(dest->fd, dest->buf, len))) dest->flags |= OUTPUT_ERR;
}

static void flushall(void) {
  flushout(&output);
}

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » output routines                      ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│─╝
        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. */

static int xvsnprintf(char *outbuf, unsigned length, const char *fmt, va_list ap) {
  int ret;
  INTOFF;
  ret = vsnprintf(outbuf, length, fmt, ap);
  INTON;
  return ret;
}

static int xvasprintf(char **sp, unsigned size, const char *f, va_list ap) {
  char *s;
  int len;
  va_list ap2;
  va_copy(ap2, ap);
  len = xvsnprintf(*sp, size, f, ap2);
  va_end(ap2);
  if (len < 0) abort();
  if (len < size) return len;
  s = stalloc((len >= stackblocksize() ? len : stackblocksize()) + 1);
  *sp = s;
  len = xvsnprintf(s, len + 1, f, ap);
  return len;
}

static void outmem(const char *p, unsigned len, struct output *dest) {
  unsigned bufsize;
  unsigned offset;
  unsigned nleft;
  nleft = dest->end - dest->nextc;
  if (likely(nleft >= len)) {
  buffered:
    dest->nextc = mempcpy(dest->nextc, p, len);
    return;
  }
  bufsize = dest->bufsize;
  if (!bufsize) {
    (void)0;
  } else if (dest->buf == NULL) {
    offset = 0;
    INTOFF;
    dest->buf = ckrealloc(dest->buf, bufsize);
    dest->bufsize = bufsize;
    dest->end = dest->buf + bufsize;
    dest->nextc = dest->buf + offset;
    INTON;
  } else {
    flushout(dest);
  }
  nleft = dest->end - dest->nextc;
  if (nleft > len) goto buffered;
  if ((xwrite(dest->fd, p, len))) {
    dest->flags |= OUTPUT_ERR;
  }
}

static void outstr(const char *p, struct output *file) {
  unsigned len;
  len = strlen(p);
  outmem(p, len, file);
}

static void outcslow(int c, struct output *dest) {
  char buf = c;
  outmem(&buf, 1, dest);
}

printfesque(3) static int fmtstr(char *outbuf, unsigned length, const char *fmt, ...) {
  va_list ap;
  int ret;
  va_start(ap, fmt);
  ret = xvsnprintf(outbuf, length, fmt, ap);
  va_end(ap);
  return ret > (int)length ? length : ret;
}

printfesque(2) static int xasprintf(char **sp, const char *fmt, ...) {
  va_list ap;
  int ret;
  va_start(ap, fmt);
  ret = xvasprintf(sp, 0, fmt, ap);
  va_end(ap);
  return ret;
}

static void doformat(struct output *dest, const char *f, va_list ap) {
  struct stackmark smark;
  char *s;
  int len;
  int olen;
  setstackmark(&smark);
  s = dest->nextc;
  olen = dest->end - dest->nextc;
  len = xvasprintf(&s, olen, f, ap);
  if (likely(olen > len)) {
    dest->nextc += len;
    goto out;
  }
  outmem(s, len, dest);
out:
  popstackmark(&smark);
}

printfesque(1) static void out1fmt(const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  doformat(out1, fmt, ap);
  va_end(ap);
}

printfesque(2) static void outfmt(struct output *file, const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  doformat(file, fmt, ap);
  va_end(ap);
}

static void exvwarning(const char *msg, va_list ap) {
  struct output *errs;
  const char *name;
  const char *fmt;
  errs = out2;
  name = arg0 ? arg0 : "sh";
  if (!commandname) {
    fmt = "%s: %d: ";
  } else {
    fmt = "%s: %d: %s: ";
  }
  outfmt(errs, fmt, name, errlinno, commandname);
  doformat(errs, msg, ap);
  outcslow('\n', errs);
}

/* error/warning routines for external builtins */
printfesque(1) static void sh_warnx(const char *fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  exvwarning(fmt, ap);
  va_end(ap);
}

/*
 * Exverror is called to raise the error exception.  If the second argument
 * is not NULL then error prints an error message using printf style
 * formatting.  It then raises the error exception.
 */
wontreturn static void exverror(int cond, const char *msg, va_list ap) {
  exvwarning(msg, ap);
  flushall();
  exraise(cond);
}

wontreturn static void exerror(int cond, const char *msg, ...) {
  va_list ap;
  va_start(ap, msg);
  exverror(cond, msg, ap);
  va_end(ap);
}

wontreturn static void sh_error(const char *msg, ...) {
  va_list ap;
  exitstatus = 2;
  va_start(ap, msg);
  exverror(EXERROR, msg, ap);
  va_end(ap);
}

wontreturn static void badnum(const char *s) {
  sh_error(illnum, s);
}

wontreturn static void synerror(const char *msg) {
  errlinno = plinno;
  sh_error("Syntax error: %s", msg);
}

wontreturn static void yyerror(const char *s) {
  sh_error("arithmetic expression: %s: \"%s\"", s, arith_startbuf);
}

/*
 * 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.
 */
wontreturn 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);
}

wontreturn static void varunset(const char *end, const char *var_, const char *umsg, int varflags) {
  const char *msg;
  const char *tail;
  tail = nullstr;
  msg = "parameter not set";
  if (umsg) {
    if (*end == (char)CTLENDVAR) {
      if (varflags & VSNUL) tail = " or null";
    } else
      msg = umsg;
  }
  sh_error("%.*s: %s%s", end - var_ - 1, var_, msg, tail);
}

/*
 * Convert a string into an integer of type int64.  Alow trailing spaces.
 */
static int64_t atomax(const char *s, int base) {
  char *p;
  int64_t r;
  errno = 0;
  r = strtoimax(s, &p, base);
  if (errno == ERANGE) badnum(s);
  /*
   * Disallow completely blank strings in non-arithmetic (base != 0)
   * contexts.
   */
  if (p == s && base) badnum(s);
  while (isspace((unsigned char)*p)) p++;
  if (*p) badnum(s);
  return r;
}

static int64_t atomax10(const char *s) {
  return atomax(s, 10);
}

/*
 * Convert a string of digits to an integer, printing an error message
 * on failure.
 */
static int number(const char *s) {
  int64_t n = atomax10(s);
  if (n < 0 || n > INT_MAX) badnum(s);
  return n;
}

static inline int64_t getn(const char *s) {
  return atomax10(s);
}

/*
 * 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(unsigned min) {
  unsigned newlen;
  newlen = stacknleft * 2;
  if (newlen < stacknleft) sh_error("Out of space");
  min = SHELL_ALIGN(min | 128);
  if (newlen < min) newlen += min;
  if (stacknxt == stackp->space && stackp != &stackbase) {
    struct stack_block *sp;
    struct stack_block *prevstackp;
    unsigned grosslen;
    INTOFF;
    sp = stackp;
    prevstackp = sp->prev;
    grosslen = newlen + sizeof(struct stack_block) - MINSIZE;
    sp = ckrealloc((pointer)sp, grosslen);
    sp->prev = prevstackp;
    stackp = sp;
    stacknxt = sp->space;
    stacknleft = newlen;
    sstrend = sp->space + newlen;
    INTON;
  } else {
    char *oldspace = stacknxt;
    int oldlen = stacknleft;
    char *p = stalloc(newlen);
    /* free the space we just allocated */
    stacknxt = memcpy(p, oldspace, oldlen);
    stacknleft += newlen;
  }
}

/*
 * The following routines are somewhat easier to use than 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 void *growstackstr(void) {
  unsigned len = stackblocksize();
  growstackblock(0);
  return (char *)stackblock() + len;
}

static char *growstackto(unsigned len) {
  if (stackblocksize() < len) growstackblock(len);
  return stackblock();
}

/*
 * Make a copy of a string in safe storage.
 */
static char *savestr(const char *s) {
  char *p = strdup(s);
  if (!p) sh_error("Out of space");
  return p;
}

/* Called from CHECKSTRSPACE. */
static char *makestrspace(unsigned newlen, char *p) {
  unsigned len = p - stacknxt;
  return growstackto(len + newlen) + len;
}

static char *stnputs(const char *s, unsigned n, char *p) {
  p = makestrspace(n, p);
  p = mempcpy(p, s, n);
  return p;
}

static char *stputs(const char *s, char *p) {
  return stnputs(s, strlen(s), p);
}

static char *nodesavestr(s) char *s;
{
  char *rtn = funcstring;
  funcstring = stpcpy(funcstring, s) + 1;
  return rtn;
}

wontreturn static void shellexec(char **, const char *, int);
static char **listvars(int, int, char ***);
static char *argstr(char *p, int flag);
static char *conv_escape(char *, int *);
static char *evalvar(char *, int);
static char *expari(char *start, int flag);
static char *exptilde(char *startp, int flag);
static int shlex(void);
static char *lookupvar(const char *);
static char *mklong(const char *, const char *);
static char *rmescapes(char *, int);
static char *scanleft(char *, char *, char *, char *, int, int);
static char *scanright(char *, char *, char *, char *, int, int);
static char *single_quote(const char *);
static const char *const *findkwd(const char *);
static const char *expandstr(const char *);
static const char *getprompt(void *);
static double getdouble(void);
static enum token t_lex(char **);
static int aexpr(enum token);
static int binop0(void);
static int bltincmd(int, char **);
static int conv_escape_str(char *, char **);
static int decode_signal(const char *, int);
static int decode_signum(const char *);
static int describe_command(struct output *, char *, const char *, int);
static int eprintlist(struct output *, struct strlist *, int);
static int equalf(const char *, const char *);
static int evalbltin(const struct builtincmd *, int, char **, int);
static int evalcase(union node *, int);
static int evalcommand(union node *, int);
static int evalfor(union node *, int);
static int evalfun(struct funcnode *, int, char **, int);
static int evalloop(union node *, int);
static int evalpipe(union node *, int);
static int evalsubshell(union node *, int);
static int filstat(char *, enum token);
static int forkshell(struct job *, union node *, int);
static int getopts(char *, char *, char **);
static int isassignment(const char *p);
static int isoperand(char **);
static int newerf(const char *, const char *);
static int nexpr(enum token);
static int nextopt(const char *);
static int oexpr(enum token);
static int olderf(const char *, const char *);
static int64_t openhere(union node *);
static int openredirect(union node *);
static int options(int);
static int padvance_magic(const char **, const char *, int);
static int patmatch(char *, const char *);
static int peektoken(void);
static int pgetc(void);
static int pgetc2(void);
static int pgetc_eatbnl();
static int pmatch(const char *, const char *);
static int preadbuffer(void);
static ssize_t preadfd(void);
static int primary1(enum token);
static int procargs(int, char **);
static int readtoken(void);
static int readtoken1(int, char const *, char *, int);
static int redirectsafe(union node *, int);
static int savefd(int, int);
static int setinputfile(const char *, int);
static int showvars(const char *, int, int);
static int stoppedjobs(void);
static int test_file_access(const char *, int);
static int unalias(const char *);
static int waitforjob(struct job *);
static int xxreadtoken(void);
static int64_t arith(const char *);
static int64_t assignment(int var_, int noeval);
static int64_t lookupvarint(const char *);
static long varvalue(char *, int, int, int);
static int64_t setvarint(const char *name, int64_t val, int flags);
static struct Var *setvar(const char *name, const char *val, int flags);
static struct Var *setvareq(char *s, int flags);
static struct alias **__lookupalias(const char *);
static struct alias *freealias(struct alias *);
static struct alias *lookupalias(const char *, int);
static struct funcnode *copyfunc(union node *);
static struct job *makejob(union node *, int);
static struct job *vforkexec(union node *n, char **argv, const char *path, int idx);
static struct localvar_list *pushlocalvars(int push);
static struct nodelist *copynodelist(struct nodelist *);
static struct redirtab *pushredir(union node *redir);
static struct strlist *expsort(struct strlist *);
static struct strlist *msort(struct strlist *, int);
static struct tblentry *cmdlookup(const char *, int);
static uint64_t getuintmax(int);
static union node *andor(void);
static union node *command(void);
static union node *copynode(union node *);
static union node *list(int);
static union node *makename(void);
static union node *parsecmd(int);
static union node *pipeline(void);
static union node *simplecmd(void);
static unsigned esclen(const char *, const char *);
static unsigned memtodest(const char *p, unsigned len, int flags);
static unsigned strtodest(const char *p, int flags);
static void addcmdentry(char *, struct cmdentry *);
static void addfname(char *);
static void check_conversion(const char *, const char *);
static void clear_traps(void);
static void clearcmdentry(void);
static void closescript(void);
static void defun(union node *);
static void delete_cmd_entry(void);
static void dotrap(void);
static void dupredirect(union node *, int);
static void exitreset(void);
static void expandarg(union node *arg, struct arglist *arglist, int flag);
static void expandmeta(struct strlist *, int);
static void expbackq(union node *, int);
static void expmeta(char *, unsigned, unsigned);
static void expredir(union node *);
static void find_command(char *, struct cmdentry *, int, const char *);
static void fixredir(union node *, const char *, int);
static void freeparam(volatile struct shparam *);
static void hashcd(void);
static void ignoresig(int);
static void init(void);
static void minus_o(char *, int);
static void mklocal(char *name, int flags);
static void onsig(int);
static void optschanged(void);
static void parsefname(void);
static void parseheredoc(void);
static void popallfiles(void);
static void popfile(void);
static void poplocalvars(int);
static void popredir(int);
static void popstring(void);
static void prehash(union node *);
static void printalias(const struct alias *);
static void printentry(struct tblentry *);
static void pungetc(void);
static void pushfile(void);
static void pushstring(char *, void *);
static void read_profile(const char *);
static void redirect(union node *, int);
static void reset(void);
static void rmaliases(void);
static void setalias(const char *, const char *);
static void setinputfd(int fd, int push);
static void setinputstring(char *);
static void setinteractive(int);
static void setjobctl(int);
static void setparam(char **);
static void setprompt(int);
static void setsignal(int);
static void showjobs(struct output *, int);
static void sigblockall(sigset_t *oldmask);
static void sizenodelist(struct nodelist *);
static void syntax(const char *, const char *);
static void tryexec(char *, char **, char **);
static void unsetfunc(const char *);
static void unsetvar(const char *);
static void unwindfiles(struct parsefile *);
static void unwindlocalvars(struct localvar_list *stop);
static void unwindredir(struct redirtab *stop);
static unsigned cvtnum(int64_t num, int flags);

static int getchr(void) {
  int val = 0;
  if (*gargv) val = **gargv++;
  return val;
}

static char *getstr(void) {
  char *val = nullstr;
  if (*gargv) val = *gargv++;
  return val;
}

/*
 * Check for a valid number.  This should be elsewhere.
 */
static int is_number(const char *p) {
  do {
    if (!is_digit(*p)) return 0;
  } while (*++p != '\0');
  return 1;
}

static inline void freestdout() {
  output.nextc = output.buf;
  output.flags = 0;
}

static inline void outc(int ch, struct output *file) {
  if (file->nextc == file->end)
    outcslow(ch, file);
  else {
    *file->nextc = ch;
    file->nextc++;
  }
}

static inline char *_STPUTC(int c, char *p) {
  if (p == sstrend) p = growstackstr();
  *p++ = c;
  return p;
}

static void ifsfree(void) {
  struct ifsregion *p = ifsfirst.next;
  if (!p) goto out;
  INTOFF;
  do {
    struct ifsregion *ifsp;
    ifsp = p->next;
    ckfree(p);
    p = ifsp;
  } while (p);
  ifsfirst.next = NULL;
  INTON;
out:
  ifslastp = NULL;
}

static void setalias(const char *name, const char *val) {
  struct alias *ap, **app;
  app = __lookupalias(name);
  ap = *app;
  INTOFF;
  if (ap) {
    if (!(ap->flag & ALIASINUSE)) {
      ckfree(ap->val);
    }
    ap->val = savestr(val);
    ap->flag &= ~ALIASDEAD;
  } else {
    /* not found */
    ap = ckmalloc(sizeof(struct alias));
    ap->name = savestr(name);
    ap->val = savestr(val);
    ap->flag = 0;
    ap->next = 0;
    *app = ap;
  }
  INTON;
}

static int unalias(const char *name) {
  struct alias **app;
  app = __lookupalias(name);
  if (*app) {
    INTOFF;
    *app = freealias(*app);
    INTON;
    return (0);
  }
  return (1);
}

static void rmaliases(void) {
  struct alias *ap, **app;
  int i;
  INTOFF;
  for (i = 0; i < ATABSIZE; i++) {
    app = &atab[i];
    for (ap = *app; ap; ap = *app) {
      *app = freealias(*app);
      if (ap == *app) {
        app = &ap->next;
      }
    }
  }
  INTON;
}

struct alias *lookupalias(const char *name, int check) {
  struct alias *ap = *__lookupalias(name);
  if (check && ap && (ap->flag & ALIASINUSE)) return (NULL);
  return (ap);
}

static int aliascmd(int argc, char **argv) {
  /* TODO - sort output */
  char *n, *v;
  int ret = 0;
  struct alias *ap;
  if (argc == 1) {
    int i;
    for (i = 0; i < ATABSIZE; i++)
      for (ap = atab[i]; ap; ap = ap->next) {
        printalias(ap);
      }
    return (0);
  }
  while ((n = *++argv) != NULL) {
    if ((v = strchr(n + 1, '=')) == NULL) { /* n+1: funny ksh stuff */
      if ((ap = *__lookupalias(n)) == NULL) {
        outfmt(out2, "%s: %s not found\n", "alias", n);
        ret = 1;
      } else
        printalias(ap);
    } else {
      *v++ = '\0';
      setalias(n, v);
    }
  }
  return (ret);
}

static int unaliascmd(int argc, char **argv) {
  int i;
  while ((i = nextopt("a")) != '\0') {
    if (i == 'a') {
      rmaliases();
      return (0);
    }
  }
  for (i = 0; *argptr; argptr++) {
    if (unalias(*argptr)) {
      outfmt(out2, "%s: %s not found\n", "unalias", *argptr);
      i = 1;
    }
  }
  return i;
}

static struct alias *freealias(struct alias *ap) {
  struct alias *next;
  if (ap->flag & ALIASINUSE) {
    ap->flag |= ALIASDEAD;
    return ap;
  }
  next = ap->next;
  ckfree(ap->name);
  ckfree(ap->val);
  ckfree(ap);
  return next;
}

static void printalias(const struct alias *ap) {
  out1fmt("%s=%s\n", ap->name, single_quote(ap->val));
}

static struct alias **__lookupalias(const char *name) {
  unsigned int hashval;
  struct alias **app;
  const char *p;
  unsigned int ch;
  p = name;
  ch = (unsigned char)*p;
  hashval = ch << 4;
  while (ch) {
    hashval += ch;
    ch = (unsigned char)*++p;
  }
  app = &atab[hashval % ATABSIZE];
  for (; *app; app = &(*app)->next) {
    if (equal(name, (*app)->name)) {
      break;
    }
  }
  return app;
}

static int shlex() {
  int value;
  const char *buf = arith_buf;
  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 = strtoimax(buf, (char **)&arith_buf, 0);
        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);
        *(char *)mempcpy(yylval.name, p, 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 '+':
        value += ARITH_ADD - '+';
        goto checkeq;
      case '-':
        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;
}

/*
 * Compares two strings up to the first = or '\0'. The first string must
 * be terminated by '='; the second may be terminated by either '=' or
 * '\0'.
 */
static int varcmp(const char *p, const char *q) {
  int c, d;
  while ((c = *p) == (d = *q)) {
    if (!c || c == '=') goto out;
    p++;
    q++;
  }
  if (c == '=') c = 0;
  if (d == '=') d = 0;
out:
  return c - d;
}

static inline int varequal(const char *a, const char *b) {
  return !varcmp(a, b);
}

/*
 * Search the environment of a builtin command.
 */
static inline char *bltinlookup(const char *name) {
  return lookupvar(name);
}

static inline int arith_prec(int op) {
  return kPrec[op - ARITH_BINOP_MIN];
}

static inline int higher_prec(int op1, int op2) {
  return arith_prec(op1) < arith_prec(op2);
}

static int64_t do_binop(int op, int64_t a, int64_t b) {
  switch (op) {
    default:
    case ARITH_REM:
    case ARITH_DIV:
      if (!b) yyerror("division by zero");
      return op == ARITH_REM ? a % b : a / b;
    case ARITH_MUL:
      return a * b;
    case ARITH_ADD:
      return a + b;
    case ARITH_SUB:
      return a - b;
    case ARITH_LSHIFT:
      return a << b;
    case ARITH_RSHIFT:
      return a >> b;
    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 int64_t primary(int token, union yystype *val, int op, int noeval) {
  int64_t result;
again:
  switch (token) {
    case ARITH_LPAREN:
      result = assignment(op, noeval);
      if (last_token != ARITH_RPAREN) yyerror("expecting ')'");
      last_token = shlex();
      return result;
    case ARITH_NUM:
      last_token = op;
      return val->val;
    case ARITH_VAR:
      last_token = op;
      return noeval ? val->val : lookupvarint(val->name);
    case ARITH_ADD:
      token = op;
      *val = yylval;
      op = shlex();
      goto again;
    case ARITH_SUB:
      *val = yylval;
      return -primary(op, val, shlex(), noeval);
    case ARITH_NOT:
      *val = yylval;
      return !primary(op, val, shlex(), noeval);
    case ARITH_BNOT:
      *val = yylval;
      return ~primary(op, val, shlex(), noeval);
    default:
      yyerror("expecting primary");
  }
}

static int64_t binop2(int64_t a, int op, int prec, int noeval) {
  for (;;) {
    union yystype val;
    int64_t b;
    int op2;
    int token;
    token = shlex();
    val = yylval;
    b = primary(token, &val, shlex(), 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) >= prec) {
      return a;
    }
    op = op2;
  }
}

static int64_t binop(int token, union yystype *val, int op, int noeval) {
  int64_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 int64_t and (int token, union yystype *val, int op, int noeval) {
  int64_t a = binop(token, val, op, noeval);
  int64_t b;
  op = last_token;
  if (op != ARITH_AND) return a;
  token = shlex();
  *val = yylval;
  b = and(token, val, shlex(), noeval | !a);
  return a && b;
}

static int64_t or (int token, union yystype *val, int op, int noeval) {
  int64_t a = and(token, val, op, noeval);
  int64_t b;
  op = last_token;
  if (op != ARITH_OR) return a;
  token = shlex();
  *val = yylval;
  b = or (token, val, shlex(), noeval | !!a);
  return a || b;
}

static int64_t cond(int token, union yystype *val, int op, int noeval) {
  int64_t a = or (token, val, op, noeval);
  int64_t b;
  int64_t c;
  if (last_token != ARITH_QMARK) return a;
  b = assignment(shlex(), noeval | !a);
  if (last_token != ARITH_COLON) yyerror("expecting ':'");
  token = shlex();
  *val = yylval;
  c = cond(token, val, shlex(), noeval | !!a);
  return a ? b : c;
}

static int64_t assignment(int var_, int noeval) {
  union yystype val = yylval;
  int op = shlex();
  int64_t result;
  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(shlex(), noeval);
  if (noeval) return result;
  return setvarint(
      val.name, (op == ARITH_ASS ? result : do_binop(op - 11, lookupvarint(val.name), result)), 0);
}

static int64_t arith(const char *s) {
  int64_t result;
  arith_buf = arith_startbuf = s;
  result = assignment(shlex(), 0);
  if (last_token) yyerror("expecting EOF");
  return result;
}

/*
 * The cd and pwd commands.
 */
static inline int padvance(const char **path, const char *name) {
  return padvance_magic(path, name, 1);
}

static char *getpwd(void);
static const char *updatepwd(const char *);
static int cdopt(void);
static int docd(const char *, int);
static void setpwd(const char *, int);

static int cdopt() {
  int flags = 0;
  int i, j;
  j = 'L';
  while ((i = nextopt("LP"))) {
    if (i != j) {
      flags ^= CD_PHYSICAL;
      j = i;
    }
  }
  return flags;
}

static int cdcmd(int argc, char **argv) {
  const char *dest;
  const char *path;
  const char *p;
  char c;
  struct stat statb;
  int flags;
  int len;
  flags = cdopt();
  dest = *argptr;
  if (!dest)
    dest = bltinlookup(homestr);
  else if (dest[0] == '-' && dest[1] == '\0') {
    dest = bltinlookup("OLDPWD");
    flags |= CD_PRINT;
  }
  if (!dest) dest = nullstr;
  if (*dest == '/') goto step6;
  if (*dest == '.') {
    c = dest[1];
  dotdot:
    switch (c) {
      case '\0':
      case '/':
        goto step6;
      case '.':
        c = dest[2];
        if (c != '.') goto dotdot;
    }
  }
  if (!*dest) dest = ".";
  path = bltinlookup("CDPATH");
  while (p = path, (len = padvance_magic(&path, dest, 0)) >= 0) {
    c = *p;
    p = stalloc(len);
    if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) {
      if (c && c != ':') flags |= CD_PRINT;
    docd:
      if (!docd(p, flags)) goto out;
      goto err;
    }
  }
step6:
  p = dest;
  goto docd;
err:
  sh_error("can't cd to %s", dest);
  unreachable;
out:
  if (flags & CD_PRINT) out1fmt(snlfmt, curdir);
  return 0;
}

/*
 * Actually do the chdir.  We also call hashcd to let the routines in exec.c
 * know that the current directory has changed.
 */
static int docd(const char *dest, int flags) {
  const char *dir = 0;
  int err;
  TRACE(("docd(\"%s\", %d) called\n", dest, flags));
  INTOFF;
  if (!(flags & CD_PHYSICAL)) {
    dir = updatepwd(dest);
    if (dir) dest = dir;
  }
  err = chdir(dest);
  if (err) goto out;
  setpwd(dir, 1);
  hashcd();
out:
  INTON;
  return err;
}

/*
 * Update curdir (the name of the current directory) in response to a
 * cd command.
 */
static const char *updatepwd(const char *dir) {
  char *new;
  char *p;
  char *cdcomppath;
  const char *lim;
  cdcomppath = sstrdup(dir);
  STARTSTACKSTR(new);
  if (*dir != '/') {
    if (curdir == nullstr) return 0;
    new = stputs(curdir, new);
  }
  new = makestrspace(strlen(dir) + 2, new);
  lim = (char *)stackblock() + 1;
  if (*dir != '/') {
    if (new[-1] != '/') USTPUTC('/', new);
    if (new > lim &&*lim == '/') lim++;
  } else {
    USTPUTC('/', new);
    cdcomppath++;
    if (dir[1] == '/' && dir[2] != '/') {
      USTPUTC('/', new);
      cdcomppath++;
      lim++;
    }
  }
  p = strtok(cdcomppath, "/");
  while (p) {
    switch (*p) {
      case '.':
        if (p[1] == '.' && p[2] == '\0') {
          while (new > lim) {
            STUNPUTC(new);
            if (new[-1] == '/') break;
          }
          break;
        } else if (p[1] == '\0')
          break;
        /* fall through */
      default:
        new = stputs(p, new);
        USTPUTC('/', new);
    }
    p = strtok(0, "/");
  }
  if (new > lim) STUNPUTC(new);
  *new = 0;
  return stackblock();
}

/*
 * Find out what the current directory is. If we already know the
 * current directory, this routine returns immediately.
 */
static inline char *getpwd() {
  char buf[PATH_MAX];
  if (getcwd(buf, sizeof(buf))) return savestr(buf);
  sh_warnx("getcwd() failed: %s", strerror(errno));
  return nullstr;
}

static int pwdcmd(int argc, char **argv) {
  int flags;
  const char *dir = curdir;
  flags = cdopt();
  if (flags) {
    if (physdir == nullstr) setpwd(dir, 0);
    dir = physdir;
  }
  out1fmt(snlfmt, dir);
  return 0;
}

static void setpwd(const char *val, int setold) {
  char *oldcur, *dir;
  oldcur = dir = curdir;
  if (setold) {
    setvar("OLDPWD", oldcur, VEXPORT);
  }
  INTOFF;
  if (physdir != nullstr) {
    if (physdir != oldcur) free(physdir);
    physdir = nullstr;
  }
  if (oldcur == val || !val) {
    char *s = getpwd();
    physdir = s;
    if (!val) dir = s;
  } else
    dir = savestr(val);
  if (oldcur != dir && oldcur != nullstr) {
    free(oldcur);
  }
  curdir = dir;
  INTON;
  setvar("PWD", dir, VEXPORT);
}

/*
 * Errors and exceptions.
 */

/*
 * NEOF is returned by parsecmd when it encounters an end of file.  It
 * must be distinct from NULL, so we use the address of a variable that
 * happens to be handy.
 */
#define NEOF ((union node *)&tokpushback)

/*
 * Return of a legal variable name (a letter or underscore followed by zero or
 * more letters, underscores, and digits).
 */
static char *endofname(const char *name) {
  char *p;
  p = (char *)name;
  if (!is_name(*p)) return p;
  while (*++p) {
    if (!is_in_name(*p)) break;
  }
  return p;
}

static inline int goodname(const char *p) {
  return !*endofname(p);
}
static inline int parser_eof(void) {
  return tokpushback && lasttoken == TEOF;
}
static inline int have_traps(void) {
  return trapcnt;
}

static const struct builtincmd kBltin = {
    .name = nullstr,
    .builtin = bltincmd,
    .flags = BUILTIN_REGULAR,
};

/*
 * Evaluate a parse tree.  The value is left in the global variable
 * exitstatus.
 */
static int evaltree(union node *n, int flags) {
  int checkexit = 0;
  int (*evalfn)(union node *, int);
  unsigned isor;
  int status = 0;
  if (n == NULL) {
    TRACE(("evaltree(NULL) called\n"));
    goto out;
  }
  dotrap();
  TRACE(("pid %d, evaltree(%p: %d, %d) called\n", getpid(), n, n->type, flags));
  switch (n->type) {
    default:
    case NNOT:
      status = !evaltree(n->nnot.com, EV_TESTED);
      goto setstatus;
    case NREDIR:
      errlinno = lineno = n->nredir.linno;
      if (funcline) lineno -= funcline - 1;
      expredir(n->nredir.redirect);
      pushredir(n->nredir.redirect);
      status = (redirectsafe(n->nredir.redirect, REDIR_PUSH)
                    ?: evaltree(n->nredir.n, flags & EV_TESTED));
      if (n->nredir.redirect) popredir(0);
      goto setstatus;
    case NCMD:
      evalfn = evalcommand;
    checkexit:
      if (eflag && !(flags & EV_TESTED)) checkexit = ~0;
      goto calleval;
    case NFOR:
      evalfn = evalfor;
      goto calleval;
    case NWHILE:
    case NUNTIL:
      evalfn = evalloop;
      goto calleval;
    case NSUBSHELL:
    case NBACKGND:
      evalfn = evalsubshell;
      goto checkexit;
    case NPIPE:
      evalfn = evalpipe;
      goto checkexit;
    case NCASE:
      evalfn = evalcase;
      goto calleval;
    case NAND:
    case NOR:
    case NSEMI:
      isor = n->type - NAND;
      status = evaltree(n->nbinary.ch1, (flags | ((isor >> 1) - 1)) & EV_TESTED);
      /* XXX: -Wlogical-not-parentheses:
         if (!status == isor || evalskip) break; */
      if ((!status) == isor || evalskip) break;
      n = n->nbinary.ch2;
    evaln:
      evalfn = evaltree;
    calleval:
      status = evalfn(n, flags);
      goto setstatus;
    case NIF:
      status = evaltree(n->nif.test, EV_TESTED);
      if (evalskip) break;
      if (!status) {
        n = n->nif.ifpart;
        goto evaln;
      } else if (n->nif.elsepart) {
        n = n->nif.elsepart;
        goto evaln;
      }
      status = 0;
      goto setstatus;
    case NDEFUN:
      defun(n);
    setstatus:
      exitstatus = status;
      break;
  }
out:
  if (checkexit & status) goto exexit;
  dotrap();
  if (flags & EV_EXIT) {
  exexit:
    exraise(EXEXIT);
  }
  return exitstatus;
}

wontreturn static void evaltreenr(union node *n, int flags) {
  evaltree(n, flags);
  abort();
}

/*
 * Execute a command or commands contained in a string.
 */
static int evalstring(char *s, int flags) {
  union node *n;
  struct stackmark smark;
  int status;
  s = sstrdup(s);
  setinputstring(s);
  setstackmark(&smark);
  status = 0;
  for (; (n = parsecmd(0)) != NEOF; popstackmark(&smark)) {
    int i;
    i = evaltree(n, flags & ~(parser_eof() ? 0 : EV_EXIT));
    if (n) status = i;
    if (evalskip) break;
  }
  popstackmark(&smark);
  popfile();
  stunalloc(s);
  return status;
}

static int evalcmd(int argc, char **argv, int flags) {
  char *p;
  char *concat;
  char **ap;
  if (argc > 1) {
    p = argv[1];
    if (argc > 2) {
      STARTSTACKSTR(concat);
      ap = argv + 2;
      for (;;) {
        concat = stputs(p, concat);
        if ((p = *ap++) == NULL) break;
        STPUTC(' ', concat);
      }
      STPUTC('\0', concat);
      p = grabstackstr(concat);
    }
    return evalstring(p, flags & EV_TESTED);
  }
  return 0;
}

static int skiploop(void) {
  int skip = evalskip;
  switch (skip) {
    case 0:
      break;
    case SKIPBREAK:
    case SKIPCONT:
      if (likely(--skipcount <= 0)) {
        evalskip = 0;
        break;
      }
      skip = SKIPBREAK;
      break;
  }
  return skip;
}

static int evalloop(union node *n, int flags) {
  int skip;
  int status;
  loopnest++;
  status = 0;
  flags &= EV_TESTED;
  do {
    int i;
    i = evaltree(n->nbinary.ch1, EV_TESTED);
    skip = skiploop();
    if (skip == SKIPFUNC) status = i;
    if (skip) continue;
    if (n->type != NWHILE) i = !i;
    if (i != 0) break;
    status = evaltree(n->nbinary.ch2, flags);
    skip = skiploop();
  } while (!(skip & ~SKIPCONT));
  loopnest--;
  return status;
}

static int evalfor(union node *n, int flags) {
  struct arglist arglist;
  union node *argp;
  struct strlist *sp;
  struct stackmark smark;
  int status;
  errlinno = lineno = n->nfor.linno;
  if (funcline) lineno -= funcline - 1;
  setstackmark(&smark);
  arglist.lastp = &arglist.list;
  for (argp = n->nfor.args; argp; argp = argp->narg.next) {
    expandarg(argp, &arglist, EXP_FULL | EXP_TILDE);
  }
  *arglist.lastp = NULL;
  status = 0;
  loopnest++;
  flags &= EV_TESTED;
  for (sp = arglist.list; sp; sp = sp->next) {
    setvar(n->nfor.var_, sp->text, 0);
    status = evaltree(n->nfor.body, flags);
    if (skiploop() & ~SKIPCONT) break;
  }
  loopnest--;
  popstackmark(&smark);
  return status;
}

/*
 * See if a pattern matches in a case statement.
 */
static int casematch(union node *pattern, char *val) {
  struct stackmark smark;
  int result;
  setstackmark(&smark);
  argbackq = pattern->narg.backquote;
  STARTSTACKSTR(expdest);
  argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
  ifsfree();
  result = patmatch(stackblock(), val);
  popstackmark(&smark);
  return result;
}

static int evalcase(union node *n, int flags) {
  union node *cp;
  union node *patp;
  struct arglist arglist;
  struct stackmark smark;
  int status = 0;
  errlinno = lineno = n->ncase.linno;
  if (funcline) lineno -= funcline - 1;
  setstackmark(&smark);
  arglist.lastp = &arglist.list;
  expandarg(n->ncase.expr, &arglist, EXP_TILDE);
  for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) {
    for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) {
      if (casematch(patp, arglist.list->text)) {
        /* Ensure body is non-empty as otherwise
         * EV_EXIT may prevent us from setting the
         * exit status.
         */
        if (evalskip == 0 && cp->nclist.body) {
          status = evaltree(cp->nclist.body, flags);
        }
        goto out;
      }
    }
  }
out:
  popstackmark(&smark);
  return status;
}

/*
 * Kick off a subshell to evaluate a tree.
 */
static int evalsubshell(union node *n, int flags) {
  struct job *jp;
  int backgnd = (n->type == NBACKGND);
  int status;
  errlinno = lineno = n->nredir.linno;
  if (funcline) lineno -= funcline - 1;
  expredir(n->nredir.redirect);
  if (!backgnd && flags & EV_EXIT && !have_traps()) goto nofork;
  INTOFF;
  jp = makejob(n, 1);
  if (forkshell(jp, n, backgnd) == 0) {
    INTON;
    flags |= EV_EXIT;
    if (backgnd) flags &= ~EV_TESTED;
  nofork:
    redirect(n->nredir.redirect, 0);
    evaltreenr(n->nredir.n, flags);
    /* never returns */
  }
  status = 0;
  if (!backgnd) status = waitforjob(jp);
  INTON;
  return status;
}

/*
 * 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 NFROMTO:
      case NFROM:
      case NTO:
      case NCLOBBER:
      case NAPPEND:
        expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR);
        redir->nfile.expfname = fn.list->text;
        break;
      case NFROMFD:
      case NTOFD:
        if (redir->ndup.vname) {
          expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE);
          fixredir(redir, fn.list->text, 1);
        }
        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 int evalpipe(union node *n, int flags) {
  struct job *jp;
  struct nodelist *lp;
  int pipelen;
  int prevfd;
  int pip[2];
  int status = 0;
  TRACE(("evalpipe(0x%lx) called\n", (long)n));
  pipelen = 0;
  for (lp = n->npipe.cmdlist; lp; lp = lp->next) pipelen++;
  flags |= EV_EXIT;
  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) {
        close(prevfd);
        sh_error("Pipe call failed");
      }
    }
    if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) {
      INTON;
      if (pip[1] >= 0) {
        close(pip[0]);
      }
      if (prevfd > 0) {
        dup2(prevfd, 0);
        close(prevfd);
      }
      if (pip[1] > 1) {
        dup2(pip[1], 1);
        close(pip[1]);
      }
      evaltreenr(lp->n, flags);
      /* never returns */
    }
    if (prevfd >= 0) close(prevfd);
    prevfd = pip[0];
    close(pip[1]);
  }
  if (n->npipe.backgnd == 0) {
    status = waitforjob(jp);
    TRACE(("evalpipe:  job done exit status %d\n", status));
  }
  INTON;
  return status;
}

/*
 * 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.
 */
static void evalbackcmd(union node *n, struct backcmd *result) {
  int pip[2];
  struct job *jp;
  result->fd = -1;
  result->buf = NULL;
  result->nleft = 0;
  result->jp = NULL;
  if (n == NULL) {
    goto out;
  }
  if (pipe(pip) < 0) sh_error("Pipe call failed");
  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]);
    }
    ifsfree();
    evaltreenr(n, EV_EXIT);
    unreachable;
  }
  close(pip[1]);
  result->fd = pip[0];
  result->jp = jp;
out:
  TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", result->fd, result->buf,
         result->nleft, result->jp));
}

static struct strlist *fill_arglist(struct arglist *arglist, union node **argpp) {
  struct strlist **lastp = arglist->lastp;
  union node *argp;
  while ((argp = *argpp)) {
    expandarg(argp, arglist, EXP_FULL | EXP_TILDE);
    *argpp = argp->narg.next;
    if (*lastp) break;
  }
  return *lastp;
}

static int parse_command_args(struct arglist *arglist, union node **argpp, const char **path) {
  struct strlist *sp = arglist->list;
  char *cp, c;
  for (;;) {
    sp = unlikely(sp->next != NULL) ? sp->next : fill_arglist(arglist, argpp);
    if (!sp) return 0;
    cp = sp->text;
    if (*cp++ != '-') break;
    if (!(c = *cp++)) break;
    if (c == '-' && !*cp) {
      if (likely(!sp->next) && !fill_arglist(arglist, argpp)) return 0;
      sp = sp->next;
      break;
    }
    do {
      switch (c) {
        case 'p':
          *path = defpath;
          break;
        default:
          /* run 'typecmd' for other options */
          return 0;
      }
    } while ((c = *cp++));
  }
  arglist->list = sp;
  return DO_NOFUNC;
}

/*
 * Execute a simple command.
 */
static int evalcommand(union node *cmd, int flags) {
  struct localvar_list *localvar_stop;
  struct parsefile *file_stop;
  struct redirtab *redir_stop;
  struct stackmark smark;
  union node *argp;
  struct arglist arglist;
  struct arglist varlist;
  char **argv;
  int argc;
  struct strlist *osp;
  struct strlist *sp;
  struct cmdentry cmdentry;
  struct job *jp;
  char *lastarg;
  const char *path;
  int spclbltin;
  int cmd_flag;
  int execcmd;
  int status;
  char **nargv;
  int vflags;
  int vlocal;
  errlinno = lineno = cmd->ncmd.linno;
  if (funcline) lineno -= funcline - 1;
  /* First expand the arguments. */
  TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
  setstackmark(&smark);
  file_stop = parsefile;
  back_exitstatus = 0;
  cmdentry.cmdtype = CMDBUILTIN;
  cmdentry.u.cmd = &kBltin;
  varlist.lastp = &varlist.list;
  *varlist.lastp = NULL;
  arglist.lastp = &arglist.list;
  *arglist.lastp = NULL;
  cmd_flag = 0;
  execcmd = 0;
  spclbltin = -1;
  vflags = 0;
  vlocal = 0;
  path = NULL;
  argc = 0;
  argp = cmd->ncmd.args;
  if ((osp = fill_arglist(&arglist, &argp))) {
    int pseudovarflag = 0;
    for (;;) {
      find_command(arglist.list->text, &cmdentry, cmd_flag | DO_REGBLTIN, pathval());
      vlocal++;
      /* implement bltin and command here */
      if (cmdentry.cmdtype != CMDBUILTIN) break;
      pseudovarflag = cmdentry.u.cmd->flags & BUILTIN_ASSIGN;
      if (likely(spclbltin < 0)) {
        spclbltin = cmdentry.u.cmd->flags & BUILTIN_SPECIAL;
        vlocal = spclbltin ^ BUILTIN_SPECIAL;
      }
      execcmd = cmdentry.u.cmd == EXECCMD;
      if (likely(cmdentry.u.cmd != COMMANDCMD)) break;
      cmd_flag = parse_command_args(&arglist, &argp, &path);
      if (!cmd_flag) break;
    }
    for (; argp; argp = argp->narg.next) {
      expandarg(
          argp, &arglist,
          (pseudovarflag && isassignment(argp->narg.text)) ? EXP_VARTILDE : EXP_FULL | EXP_TILDE);
    }
    for (sp = arglist.list; sp; sp = sp->next) argc++;
    if (execcmd && argc > 1) vflags = VEXPORT;
  }
  localvar_stop = pushlocalvars(vlocal);
  /* Reserve one extra spot at the front for shellexec. */
  nargv = stalloc(sizeof(char *) * (argc + 2));
  argv = ++nargv;
  for (sp = arglist.list; sp; sp = sp->next) {
    TRACE(("evalcommand arg: %s\n", sp->text));
    *nargv++ = sp->text;
  }
  *nargv = NULL;
  lastarg = NULL;
  if (iflag && funcline == 0 && argc > 0) lastarg = nargv[-1];
  preverrout.fd = 2;
  expredir(cmd->ncmd.redirect);
  redir_stop = pushredir(cmd->ncmd.redirect);
  status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2);
  if (unlikely(status)) {
  bail:
    exitstatus = status;
    /* We have a redirection error. */
    if (spclbltin > 0) exraise(EXERROR);
    goto out;
  }
  for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) {
    struct strlist **spp;
    spp = varlist.lastp;
    expandarg(argp, &varlist, EXP_VARTILDE);
    if (vlocal)
      mklocal((*spp)->text, VEXPORT);
    else
      setvareq((*spp)->text, vflags);
  }
  /* Print the command if xflag is set. */
  if (xflag) {
    struct output *out;
    int sep;
    out = &preverrout;
    outstr(expandstr(ps4val()), out);
    sep = 0;
    sep = eprintlist(out, varlist.list, sep);
    eprintlist(out, osp, sep);
    outcslow('\n', out);
  }
  /* Now locate the command. */
  if (cmdentry.cmdtype != CMDBUILTIN || !(cmdentry.u.cmd->flags & BUILTIN_REGULAR)) {
    path = unlikely(path != NULL) ? path : pathval(); /* wut */
    find_command(argv[0], &cmdentry, cmd_flag | DO_ERR, path);
  }
  jp = NULL;
  /* Execute the command. */
  switch (cmdentry.cmdtype) {
    case CMDUNKNOWN:
      status = 127;
      goto bail;
    default:
      /* Fork off a child process if necessary. */
      if (!(flags & EV_EXIT) || have_traps()) {
        INTOFF;
        jp = vforkexec(cmd, argv, path, cmdentry.u.index);
        break;
      }
      shellexec(argv, path, cmdentry.u.index);
      unreachable;
    case CMDBUILTIN:
      if (evalbltin(cmdentry.u.cmd, argc, argv, flags) &&
          !(exception == EXERROR && spclbltin <= 0)) {
      raise:
        longjmp(handler->loc, 1);
      }
      break;
    case CMDFUNCTION:
      if (evalfun(cmdentry.u.func, argc, argv, flags)) goto raise;
      break;
  }
  status = waitforjob(jp);
  FORCEINTON;
out:
  if (cmd->ncmd.redirect) popredir(execcmd);
  unwindredir(redir_stop);
  unwindfiles(file_stop);
  unwindlocalvars(localvar_stop);
  if (lastarg) {
    /* dsl: I think this is intended to be used to support
     * '_' in 'vi' command mode during line editing...
     * However I implemented that within libedit itself.
     */
    setvar("_", lastarg, 0);
  }
  popstackmark(&smark);
  return status;
}

static int evalbltin(const struct builtincmd *cmd, int argc, char **argv, int flags) {
  char *volatile savecmdname;
  struct jmploc *volatile savehandler;
  struct jmploc jmploc;
  int status;
  int i;

  savecmdname = commandname;
  savehandler = handler;
  if ((i = setjmp(jmploc.loc))) goto cmddone;
  handler = &jmploc;
  commandname = argv[0];
  argptr = argv + 1;
  optptr = NULL; /* initialize nextopt */
  if (cmd == EVALCMD)
    status = evalcmd(argc, argv, flags);
  else
    status = (*cmd->builtin)(argc, argv);
  flushall();
  status |= out1->flags;
  exitstatus = status;
cmddone:
  freestdout();
  commandname = savecmdname;
  handler = savehandler;

  return i;
}

/*
 * Free a parse tree.
 */
static void freefunc(struct funcnode *f) {
  if (f && --f->count < 0) ckfree(f);
}

static int evalfun(struct funcnode *func, int argc, char **argv, int flags) {
  volatile struct shparam saveparam;
  struct jmploc *volatile savehandler;
  struct jmploc jmploc;
  int e;
  int savefuncline;
  int saveloopnest;
  saveparam = shellparam;
  savefuncline = funcline;
  saveloopnest = loopnest;
  savehandler = handler;
  if ((e = setjmp(jmploc.loc))) {
    goto funcdone;
  }
  INTOFF;
  handler = &jmploc;
  shellparam.malloc = 0;
  func->count++;
  funcline = func->n.ndefun.linno;
  loopnest = 0;
  INTON;
  shellparam.nparam = argc - 1;
  shellparam.p = argv + 1;
  shellparam.optind = 1;
  shellparam.optoff = -1;
  evaltree(func->n.ndefun.body, flags & EV_TESTED);
funcdone:
  INTOFF;
  loopnest = saveloopnest;
  funcline = savefuncline;
  freefunc(func);
  freeparam(&shellparam);
  shellparam = saveparam;
  handler = savehandler;
  INTON;
  evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
  return e;
}

/*
 * 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->type == NCMD && n->ncmd.args) {
    if (goodname(n->ncmd.args->narg.text)) {
      find_command(n->ncmd.args->narg.text, &entry, 0, pathval());
    }
  }
}

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » builtins                             ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│─╝
  Builtin commands whose functions are closely tied to evaluation are
  implemented here. */

static int falsecmd(int argc, char **argv) {
  return 1;
}

static int truecmd(int argc, char **argv) {
  return 0;
}

/* No command given. */
static int bltincmd(int argc, char **argv) {
  /*
   * Preserve exitstatus of a previous possible redirection
   * as POSIX mandates
   */
  return back_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.
 */
static int breakcmd(int argc, char **argv) {
  int n = argc > 1 ? number(argv[1]) : 1;
  if (n <= 0) badnum(argv[1]);
  if (n > loopnest) n = loopnest;
  if (n > 0) {
    evalskip = (**argv == 'c') ? SKIPCONT : SKIPBREAK;
    skipcount = n;
  }
  return 0;
}

/* The return command. */
static int returncmd(int argc, char **argv) {
  int skip;
  int status;
  /*
   * If called outside a function, do what ksh does;
   * skip the rest of the file.
   */
  if (argv[1]) {
    skip = SKIPFUNC;
    status = number(argv[1]);
  } else {
    skip = SKIPFUNCDEF;
    status = exitstatus;
  }
  evalskip = skip;
  return status;
}

static int execcmd(int argc, char **argv) {
  if (argc > 1) {
    iflag = 0; /* exit on error */
    mflag = 0;
    optschanged();
    shellexec(argv + 1, pathval(), 0);
  }
  return 0;
}

static int eprintlist(struct output *out, struct strlist *sp, int sep) {
  while (sp) {
    const char *p;
    p = &" %s"[!sep]; /* XXX: -Wstring-plus-int: p = " %s" + (1 - sep); */
    sep |= 1;
    outfmt(out, p, sp->text);
    sp = sp->next;
  }
  return sep;
}

/*
 * 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.
 */

/*
 * Exec a program.  Never returns.  If you change this routine, you may
 * have to change the find_command routine as well.
 */
wontreturn static void shellexec(char **argv, const char *path, int idx) {
  char *cmdname;
  int e;
  char **envp;
  int exerrno;
  envp = environment();
  if (strchr(argv[0], '/') != NULL) {
    tryexec(argv[0], argv, envp);
    e = errno;
  } else {
    e = ENOENT;
    while (padvance(&path, argv[0]) >= 0) {
      cmdname = stackblock();
      if (--idx < 0 && pathopt == NULL) {
        tryexec(cmdname, argv, envp);
        if (errno != ENOENT && errno != ENOTDIR) e = errno;
      }
    }
  }
  /* Map to POSIX errors */
  exerrno = (e == ELOOP || e == ENAMETOOLONG || e == ENOENT || e == ENOTDIR) ? 127 : 126;
  exitstatus = exerrno;
  TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", argv[0], e, suppressint));
  exerror(EXEXIT, "%s: %s", argv[0], errmsg(e, E_EXEC));
}

static void tryexec(char *cmd, char **argv, char **envp) {
  char *const path_bshell = _PATH_BSHELL;
repeat:
  execve(cmd, argv, envp);
  if (cmd != path_bshell && errno == ENOEXEC) {
    *argv-- = cmd;
    *argv = cmd = path_bshell;
    goto repeat;
  }
}

static const char *legal_pathopt(const char *opt, const char *term, int magic) {
  switch (magic) {
    case 0:
      opt = NULL;
      break;
    case 1:
      opt = prefix(opt, "builtin") ?: prefix(opt, "func");
      break;
    default:
      opt += strcspn(opt, term);
      break;
  }
  if (opt && *opt == '%') opt++;
  return opt;
}

/*
 * 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.
 *
 * If magic is 0 then pathopt recognition will be disabled.  If magic is
 * 1 we shall recognise %builtin/%func.  Otherwise we shall accept any
 * pathopt.
 */
static int padvance_magic(const char **path, const char *name, int magic) {
  const char *term = "%:";
  const char *lpathopt;
  const char *p;
  char *q;
  const char *start;
  unsigned qlen;
  unsigned len;
  if (*path == NULL) return -1;
  lpathopt = NULL;
  start = *path;
  if (*start == '%' && (p = legal_pathopt(start + 1, term, magic))) {
    lpathopt = start + 1;
    start = p;
    term = ":";
  }
  len = strcspn(start, term);
  p = start + len;
  if (*p == '%') {
    unsigned extra = strchrnul(p, ':') - p;
    if (legal_pathopt(p + 1, term, magic))
      lpathopt = p + 1;
    else
      len += extra;
    p += extra;
  }
  pathopt = lpathopt;
  *path = *p == ':' ? p + 1 : NULL;
  /* "2" is for '/' and '\0' */
  qlen = len + strlen(name) + 2;
  q = growstackto(qlen);
  if (likely(len)) {
    q = mempcpy(q, start, len);
    *q++ = '/';
  }
  strcpy(q, name);
  return qlen;
}

/*** Command hashing code ***/
static int hashcmd(int argc, char **argv) {
  struct tblentry **pp;
  struct tblentry *cmdp;
  int c;
  struct cmdentry entry;
  char *name;
  while ((c = nextopt("r")) != '\0') {
    clearcmdentry();
    return 0;
  }
  if (*argptr == NULL) {
    for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
      for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
        if (cmdp->cmdtype == CMDNORMAL) {
          printentry(cmdp);
        }
      }
    }
    return 0;
  }
  c = 0;
  while ((name = *argptr) != NULL) {
    if ((cmdp = cmdlookup(name, 0)) &&
        (cmdp->cmdtype == CMDNORMAL ||
         (cmdp->cmdtype == CMDBUILTIN && !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
          builtinloc > 0))) {
      delete_cmd_entry();
    }
    find_command(name, &entry, DO_ERR, pathval());
    if (entry.cmdtype == CMDUNKNOWN) c = 1;
    argptr++;
  }
  return c;
}

static void printentry(struct tblentry *cmdp) {
  int idx;
  const char *path;
  char *name;
  idx = cmdp->param.index;
  path = pathval();
  do {
    padvance(&path, cmdp->cmdname);
  } while (--idx >= 0);
  name = stackblock();
  outstr(name, out1);
  out1fmt(snlfmt, cmdp->rehash ? "*" : nullstr);
}

static int cmdloop(int top);

/*
 * Read a file containing shell functions.
 */
static void readcmdfile(char *name) {
  setinputfile(name, INPUT_PUSH_FILE);
  cmdloop(0);
  popfile();
}

/*
 * Search the table of builtin commands.
 */
static struct builtincmd *find_builtin(const char *name) {
  return bsearch(&name, kBuiltinCmds, sizeof(kBuiltinCmds) / sizeof(kBuiltinCmds[0]),
                 sizeof(kBuiltinCmds[0]), pstrcmp);
}

/*
 * Resolve a command name.  If you change this routine, you may have to
 * change the shellexec routine as well.
 */
static void find_command(char *name, struct cmdentry *entry, int act, const char *path) {
  char *fullname;
  struct stat statb;
  struct tblentry *cmdp;
  struct builtincmd *bcmd;
  int e, bit, idx, prev, updatetbl, len;
  /* If name contains a slash, don't use PATH or hash table */
  if (strchr(name, '/') != NULL) {
    entry->u.index = -1;
    if (act & DO_ABS) {
      while (stat(name, &statb) < 0) {
        entry->cmdtype = CMDUNKNOWN;
        return;
      }
    }
    entry->cmdtype = CMDNORMAL;
    return;
  }
  updatetbl = (path == pathval());
  if (!updatetbl) act |= DO_ALTPATH;
  /* If name is in the table, check answer will be ok */
  if ((cmdp = cmdlookup(name, 0)) != NULL) {
    switch (cmdp->cmdtype) {
      default:
      case CMDNORMAL:
        bit = DO_ALTPATH | DO_REGBLTIN;
        break;
      case CMDFUNCTION:
        bit = DO_NOFUNC;
        break;
      case CMDBUILTIN:
        bit = cmdp->param.cmd->flags & BUILTIN_REGULAR ? 0 : DO_REGBLTIN;
        break;
    }
    if (act & bit) {
      if (act & bit & DO_REGBLTIN) goto fail;
      updatetbl = 0;
      cmdp = NULL;
    } else if (cmdp->rehash == 0) {
      /* if not invalidated by cd, we're done */
      goto success;
    }
  }
  /* If %builtin not in path, check for builtin next */
  bcmd = find_builtin(name);
  if (bcmd && ((bcmd->flags & BUILTIN_REGULAR) | (act & DO_ALTPATH) | (builtinloc <= 0))) {
    goto builtin_success;
  }
  if (act & DO_REGBLTIN) goto fail;
  /* We have to search path. */
  prev = -1;                  /* where to start */
  if (cmdp && cmdp->rehash) { /* doing a rehash */
    if (cmdp->cmdtype == CMDBUILTIN)
      prev = builtinloc;
    else
      prev = cmdp->param.index;
  }
  e = ENOENT;
  idx = -1;
loop:
  while ((len = padvance(&path, name)) >= 0) {
    const char *lpathopt = pathopt;
    fullname = stackblock();
    idx++;
    if (lpathopt) {
      if (*lpathopt == 'b') {
        if (bcmd) goto builtin_success;
        continue;
      } else if (!(act & DO_NOFUNC)) {
        /* handled below */
      } else {
        /* ignore unimplemented options */
        continue;
      }
    }
    /* if rehash, don't redo absolute path names */
    if (fullname[0] == '/' && idx <= prev) {
      if (idx < prev) continue;
      TRACE(("searchexec \"%s\": no change\n", name));
      goto success;
    }
    while (stat(fullname, &statb) < 0) {
      if (errno != ENOENT && errno != ENOTDIR) e = errno;
      goto loop;
    }
    e = EACCES; /* if we fail, this will be the error */
    if (!S_ISREG(statb.st_mode)) continue;
    if (lpathopt) { /* this is a %func directory */
      stalloc(len);
      readcmdfile(fullname);
      if ((cmdp = cmdlookup(name, 0)) == NULL || cmdp->cmdtype != CMDFUNCTION) {
        sh_error("%s not defined in %s", name, fullname);
      }
      stunalloc(fullname);
      goto success;
    }
    TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname));
    if (!updatetbl) {
      entry->cmdtype = CMDNORMAL;
      entry->u.index = idx;
      return;
    }
    INTOFF;
    cmdp = cmdlookup(name, 1);
    cmdp->cmdtype = CMDNORMAL;
    cmdp->param.index = idx;
    INTON;
    goto success;
  }
  /* We failed.  If there was an entry for this command, delete it */
  if (cmdp && updatetbl) delete_cmd_entry();
  if (act & DO_ERR) sh_warnx("%s: %s", name, errmsg(e, E_EXEC));
fail:
  entry->cmdtype = CMDUNKNOWN;
  return;
builtin_success:
  if (!updatetbl) {
    entry->cmdtype = CMDBUILTIN;
    entry->u.cmd = bcmd;
    return;
  }
  INTOFF;
  cmdp = cmdlookup(name, 1);
  cmdp->cmdtype = CMDBUILTIN;
  cmdp->param.cmd = bcmd;
  INTON;
success:
  cmdp->rehash = 0;
  entry->cmdtype = cmdp->cmdtype;
  entry->u = cmdp->param;
}

/*
 * Called when a cd is done.  Marks all commands so the next time they
 * are executed they will be rehashed.
 */
static void hashcd(void) {
  struct tblentry **pp;
  struct tblentry *cmdp;
  for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) {
    for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
      if (cmdp->cmdtype == CMDNORMAL ||
          (cmdp->cmdtype == CMDBUILTIN && !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
           builtinloc > 0)) {
        cmdp->rehash = 1;
      }
    }
  }
}

/*
 * Fix command hash table when PATH changed.
 * Called before PATH is changed.  The argument is the new value of PATH;
 * pathval() still returns the old value at this point.
 * Called with interrupts off.
 */
static void changepath(const char *newval) {
  int idx;
  int bltin;
  const char *neu;
  neu = newval;
  idx = 0;
  bltin = -1;
  for (;;) {
    if (*neu == '%' && prefix(neu + 1, "builtin")) {
      bltin = idx;
      break;
    }
    neu = strchr(neu, ':');
    if (!neu) break;
    idx++;
    neu++;
  }
  builtinloc = bltin;
  clearcmdentry();
}

/*
 * Clear out command entries.  The argument specifies the first entry in
 * PATH which has changed.
 */
static 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 ||
          (cmdp->cmdtype == CMDBUILTIN && !(cmdp->param.cmd->flags & BUILTIN_REGULAR) &&
           builtinloc > 0)) {
        *pp = cmdp->next;
        ckfree(cmdp);
      } else {
        pp = &cmdp->next;
      }
    }
  }
  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.
 *
 * Interrupts must be off if called with add != 0.
 */
static struct tblentry *cmdlookup(const char *name, int add) {
  unsigned int hashval;
  const char *p;
  struct tblentry *cmdp;
  struct tblentry **pp;
  p = name;
  hashval = (unsigned char)*p << 4;
  while (*p) hashval += (unsigned char)*p++;
  hashval &= 0x7FFF;
  pp = &cmdtable[hashval % CMDTABLESIZE];
  for (cmdp = *pp; cmdp; cmdp = cmdp->next) {
    if (equal(cmdp->cmdname, name)) break;
    pp = &cmdp->next;
  }
  if (add && cmdp == NULL) {
    cmdp = *pp = ckmalloc(sizeof(struct tblentry) + strlen(name) + 1);
    cmdp->next = NULL;
    cmdp->cmdtype = CMDUNKNOWN;
    strcpy(cmdp->cmdname, name);
  }
  lastcmdentry = pp;
  return cmdp;
}

/*
 * Delete the command entry returned on the last lookup.
 */
static void delete_cmd_entry(void) {
  struct tblentry *cmdp;
  INTOFF;
  cmdp = *lastcmdentry;
  *lastcmdentry = cmdp->next;
  if (cmdp->cmdtype == CMDFUNCTION) freefunc(cmdp->param.func);
  ckfree(cmdp);
  INTON;
}

/*
 * Add a new command entry, replacing any existing command entry for
 * the same name - except special builtins.
 */
static void addcmdentry(char *name, struct cmdentry *entry) {
  struct tblentry *cmdp;
  cmdp = cmdlookup(name, 1);
  if (cmdp->cmdtype == CMDFUNCTION) {
    freefunc(cmdp->param.func);
  }
  cmdp->cmdtype = entry->cmdtype;
  cmdp->param = entry->u;
  cmdp->rehash = 0;
}

/* Define a shell function. */
static void defun(union node *func) {
  struct cmdentry entry;
  INTOFF;
  entry.cmdtype = CMDFUNCTION;
  entry.u.func = copyfunc(func);
  addcmdentry(func->ndefun.text, &entry);
  INTON;
}

/* Delete a function if it exists. */
static void unsetfunc(const char *name) {
  struct tblentry *cmdp;
  if ((cmdp = cmdlookup(name, 0)) != NULL && cmdp->cmdtype == CMDFUNCTION) {
    delete_cmd_entry();
  }
}

/* Locate and print what a word is... */
static int typecmd(int argc, char **argv) {
  int i;
  int err = 0;
  for (i = 1; i < argc; i++) {
    err |= describe_command(out1, argv[i], NULL, 1);
  }
  return err;
}

static int describe_command(struct output *out, char *command, const char *path, int verbose) {
  struct cmdentry entry;
  struct tblentry *cmdp;
  const struct alias *ap;
  if (verbose) {
    outstr(command, out);
  }
  /* First look at the keywords */
  if (findkwd(command)) {
    outstr(verbose ? " is a shell keyword" : command, out);
    goto out;
  }
  /* Then look at the aliases */
  if ((ap = lookupalias(command, 0)) != NULL) {
    if (verbose) {
      outfmt(out, " is an alias for %s", ap->val);
    } else {
      outstr("alias ", out);
      printalias(ap);
      return 0;
    }
    goto out;
  }
  /* Then if the standard search path is used, check if it is
   * a tracked alias.
   */
  if (path == NULL) {
    path = pathval();
    cmdp = cmdlookup(command, 0);
  } else {
    cmdp = NULL;
  }
  if (cmdp != NULL) {
    entry.cmdtype = cmdp->cmdtype;
    entry.u = cmdp->param;
  } else {
    /* Finally use brute force */
    find_command(command, &entry, DO_ABS, path);
  }
  switch (entry.cmdtype) {
    case CMDNORMAL: {
      int j = entry.u.index;
      char *p;
      if (j == -1) {
        p = command;
      } else {
        do {
          padvance(&path, command);
        } while (--j >= 0);
        p = stackblock();
      }
      if (verbose) {
        outfmt(out, " is%s %s", cmdp ? " a tracked alias for" : nullstr, p);
      } else {
        outstr(p, out);
      }
      break;
    }
    case CMDFUNCTION:
      if (verbose) {
        outstr(" is a shell function", out);
      } else {
        outstr(command, out);
      }
      break;
    case CMDBUILTIN:
      if (verbose) {
        outfmt(out, " is a %sshell builtin",
               entry.u.cmd->flags & BUILTIN_SPECIAL ? "special " : nullstr);
      } else {
        outstr(command, out);
      }
      break;
    default:
      if (verbose) {
        outstr(": not found\n", out);
      }
      return 127;
  }
out:
  outc('\n', out);
  return 0;
}

static int commandcmd(argc, argv) int argc;
char **argv;
{
  char *cmd;
  int c;
  enum {
    VERIFY_BRIEF = 1,
    VERIFY_VERBOSE = 2,
  } verify = 0;
  const char *path = NULL;
  while ((c = nextopt("pvV")) != '\0')
    if (c == 'V')
      verify |= VERIFY_VERBOSE;
    else if (c == 'v')
      verify |= VERIFY_BRIEF;
    else
      path = defpath;
  cmd = *argptr;
  if (verify && cmd) {
    return describe_command(out1, cmd, path, verify - VERIFY_BRIEF);
  }
  return 0;
}

/*
 * Prepare a pattern for a glob(3) call.
 *
 * Returns an stalloced string.
 */
static inline char *preglob(const char *pattern, int flag) {
  flag |= RMESCAPE_GLOB;
  return rmescapes((char *)pattern, flag);
}

static unsigned esclen(const char *start, const char *p) {
  unsigned esc = 0;
  while (p > start && *--p == (char)CTLESC) {
    esc++;
  }
  return esc;
}

static inline const char *getpwhome(const char *name) {
  struct passwd *pw = getpwnam(name);
  return pw ? pw->pw_dir : 0;
}

static void ifsbreakup(char *string, int maxargs, struct arglist *arglist);
static void recordregion(int start, int end, int nulonly);

/*
 * Perform variable substitution and command substitution on an
 * argument, placing the resulting list of arguments in arglist. If
 * EXP_FULL is true, perform splitting and file name expansion. When
 * arglist is NULL, perform here document expansion.
 */
static void expandarg(union node *arg, struct arglist *arglist, int flag) {
  struct strlist *sp;
  char *p;
  argbackq = arg->narg.backquote;
  STARTSTACKSTR(expdest);
  argstr(arg->narg.text, flag);
  if (arglist == NULL) {
    /* here document expanded */
    goto out;
  }
  p = grabstackstr(expdest);
  exparg.lastp = &exparg.list;
  /*
   * TODO - EXP_REDIR
   */
  if (flag & EXP_FULL) {
    ifsbreakup(p, -1, &exparg);
    *exparg.lastp = NULL;
    exparg.lastp = &exparg.list;
    expandmeta(exparg.list, flag);
  } else {
    sp = (struct strlist *)stalloc(sizeof(struct strlist));
    sp->text = p;
    *exparg.lastp = sp;
    exparg.lastp = &sp->next;
  }
  *exparg.lastp = NULL;
  if (exparg.list) {
    *arglist->lastp = exparg.list;
    arglist->lastp = exparg.lastp;
  }
out:
  ifsfree();
}

/*
 * Perform variable and command substitution. If EXP_FULL is set, output
 * CTLESC characters to allow for further processing. Otherwise treat $@
 * like $* since no splitting will be performed.
 */
static char *argstr(char *p, int flag) {
  static const int DOLATSTRLEN = 6;
  static const char spclchars[] = {'=',    ':',      CTLQUOTEMARK, CTLENDVAR, CTLESC,
                                   CTLVAR, CTLBACKQ, CTLARI,       CTLENDARI, 0};
  const char *reject = spclchars;
  int c;
  int breakall = (flag & (EXP_WORD | EXP_QUOTED)) == EXP_WORD;
  int inquotes;
  unsigned length;
  int startloc;
  reject += !!(flag & EXP_VARTILDE2);
  reject += flag & EXP_VARTILDE ? 0 : 2;
  inquotes = 0;
  length = 0;
  if (flag & EXP_TILDE) {
    flag &= ~EXP_TILDE;
  tilde:
    if (*p == '~') p = exptilde(p, flag);
  }
start:
  startloc = expdest - (char *)stackblock();
  for (;;) {
    int end;
    length += strcspn(p + length, reject);
    end = 0;
    c = (signed char)p[length];
    if (!(c & 0x80) || c == CTLENDARI || c == CTLENDVAR) {
      /*
       * c == '=' || c == ':' || c == '\0' ||
       * c == CTLENDARI || c == CTLENDVAR
       */
      length++;
      /* c == '\0' || c == CTLENDARI || c == CTLENDVAR */
      end = !!((c - 1) & 0x80);
    }
    if (length > 0 && !(flag & EXP_DISCARD)) {
      int newloc;
      char *q;
      q = stnputs(p, length, expdest);
      q[-1] &= end - 1;
      expdest = q - (flag & EXP_WORD ? end : 0);
      newloc = expdest - (char *)stackblock() - end;
      if (breakall && !inquotes && newloc > startloc) {
        recordregion(startloc, newloc, 0);
      }
      startloc = newloc;
    }
    p += length + 1;
    length = 0;
    if (end) break;
    switch (c) {
      case '=':
        flag |= EXP_VARTILDE2;
        reject++;
        /* fall through */
      case ':':
        /*
         * sort of a hack - expand tildes in variable
         * assignments (after the first '=' and after ':'s).
         */
        if (*--p == '~') {
          goto tilde;
        }
        continue;
      case CTLQUOTEMARK:
        /* "$@" syntax adherence hack */
        if (!inquotes && !memcmp(p, dolatstr + 1, DOLATSTRLEN - 1)) {
          p = evalvar(p + 1, flag | EXP_QUOTED) + 1;
          goto start;
        }
        inquotes ^= EXP_QUOTED;
      addquote:
        if (flag & QUOTES_ESC) {
          p--;
          length++;
          startloc++;
        }
        break;
      case CTLESC:
        startloc++;
        length++;
        goto addquote;
      case CTLVAR:
        p = evalvar(p, flag | inquotes);
        goto start;
      case CTLBACKQ:
        expbackq(argbackq->n, flag | inquotes);
        goto start;
      case CTLARI:
        p = expari(p, flag | inquotes);
        goto start;
    }
  }
  return p - 1;
}

static char *exptilde(char *startp, int flag) {
  signed char c;
  char *name;
  const char *home;
  char *p;
  p = startp;
  name = p + 1;
  while ((c = *++p) != '\0') {
    switch (c) {
      case CTLESC:
        return (startp);
      case CTLQUOTEMARK:
        return (startp);
      case ':':
        if (flag & EXP_VARTILDE) goto done;
        break;
      case '/':
      case CTLENDVAR:
        goto done;
    }
  }
done:
  if (flag & EXP_DISCARD) goto out;
  *p = '\0';
  if (*name == '\0') {
    home = lookupvar(homestr);
  } else {
    home = getpwhome(name);
  }
  *p = c;
  if (!home) goto lose;
  strtodest(home, flag | EXP_QUOTED);
out:
  return (p);
lose:
  return (startp);
}

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. Backup to start of expression,
 * evaluate, place result in (backed up) result, adjust string position.
 */
static char *expari(char *start, int flag) {
  struct stackmark sm;
  int begoff;
  int endoff;
  int len;
  int64_t result;
  char *p;
  p = stackblock();
  begoff = expdest - p;
  p = argstr(start, flag & EXP_DISCARD);
  if (flag & EXP_DISCARD) goto out;
  start = stackblock();
  endoff = expdest - start;
  start += begoff;
  STADJUST(start - expdest, expdest);
  removerecordregions(begoff);
  if (likely(flag & QUOTES_ESC)) rmescapes(start, 0);
  pushstackmark(&sm, endoff);
  result = arith(start);
  popstackmark(&sm);
  len = cvtnum(result, flag);
  if (likely(!(flag & EXP_QUOTED))) recordregion(begoff, begoff + len, 0);
out:
  return p;
}

/*
 * Expand stuff in backwards quotes.
 */
static void expbackq(union node *cmd, int flag) {
  struct backcmd in;
  int i;
  char buf[128];
  char *p;
  char *dest;
  int startloc;
  struct stackmark smark;
  if (flag & EXP_DISCARD) goto out;
  INTOFF;
  startloc = expdest - (char *)stackblock();
  pushstackmark(&smark, startloc);
  evalbackcmd(cmd, (struct backcmd *)&in);
  popstackmark(&smark);
  p = in.buf;
  i = in.nleft;
  if (i == 0) goto read;
  for (;;) {
    memtodest(p, i, flag);
  read:
    if (in.fd < 0) break;
    do {
      i = read(in.fd, buf, sizeof buf);
    } while (i < 0 && errno == EINTR);
    TRACE(("expbackq: read returns %d\n", i));
    if (i <= 0) break;
    p = buf;
  }
  if (in.buf) ckfree(in.buf);
  if (in.fd >= 0) {
    close(in.fd);
    back_exitstatus = waitforjob(in.jp);
  }
  INTON;
  /* Eat all trailing newlines */
  dest = expdest;
  for (; dest > (char *)stackblock() && dest[-1] == '\n';) {
    STUNPUTC(dest);
  }
  expdest = dest;
  if (!(flag & EXP_QUOTED)) {
    recordregion(startloc, dest - (char *)stackblock(), 0);
  }
  TRACE(("evalbackq: size=%d: \"%.*s\"\n", (dest - (char *)stackblock()) - startloc,
         (dest - (char *)stackblock()) - startloc, (char *)stackblock() + startloc));
out:
  argbackq = argbackq->next;
}

static char *scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes, int zero) {
  char *loc;
  char *loc2;
  char c;
  loc = startp;
  loc2 = rmesc;
  do {
    int match;
    const char *s = loc2;
    c = *loc2;
    if (zero) {
      *loc2 = '\0';
      s = rmesc;
    }
    match = pmatch(str, s);
    *loc2 = c;
    if (match) return loc;
    if (quotes && *loc == (char)CTLESC) loc++;
    loc++;
    loc2++;
  } while (c);
  return 0;
}

static char *scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, int zero) {
  int esc = 0;
  char *loc;
  char *loc2;
  for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
    int match;
    char c = *loc2;
    const char *s = loc2;
    if (zero) {
      *loc2 = '\0';
      s = rmesc;
    }
    match = pmatch(str, s);
    *loc2 = c;
    if (match) return loc;
    loc--;
    if (quotes) {
      if (--esc < 0) {
        esc = esclen(startp, loc);
      }
      if (esc % 2) {
        esc--;
        loc--;
      }
    }
  }
  return 0;
}

static char *subevalvar(char *start, char *str, int strloc, int startloc, int varflags, int flag) {
  int subtype = varflags & VSTYPE;
  int quotes = flag & QUOTES_ESC;
  char *startp;
  char *loc;
  long amount;
  char *rmesc, *rmescend;
  int zero;
  char *(*scan)(char *, char *, char *, char *, int, int);
  char *p;
  p = argstr(start, (flag & EXP_DISCARD) | EXP_TILDE | (str ? 0 : EXP_CASE));
  if (flag & EXP_DISCARD) return p;
  startp = (char *)stackblock() + startloc;
  switch (subtype) {
    case VSASSIGN:
      setvar(str, startp, 0);
      loc = startp;
      goto out;
    case VSQUESTION:
      varunset(start, str, startp, varflags);
      unreachable;
  }
  subtype -= VSTRIMRIGHT;
  rmesc = startp;
  rmescend = (char *)stackblock() + strloc;
  if (quotes) {
    rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
    if (rmesc != startp) {
      rmescend = expdest;
      startp = (char *)stackblock() + startloc;
    }
  }
  rmescend--;
  str = (char *)stackblock() + strloc;
  preglob(str, 0);
  /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
  zero = subtype >> 1;
  /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
  scan = (subtype & 1) ^ zero ? scanleft : scanright;
  loc = scan(startp, rmesc, rmescend, str, quotes, zero);
  if (loc) {
    if (zero) {
      memmove(startp, loc, str - loc);
      loc = startp + (str - loc) - 1;
    }
    *loc = '\0';
  } else
    loc = str - 1;
out:
  amount = loc - expdest;
  STADJUST(amount, expdest);
  /* Remove any recorded regions beyond start of variable */
  removerecordregions(startloc);
  return p;
}

/*
 * 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_;
  int patloc;
  int startloc;
  long varlen;
  int quoted;
  varflags = *p++;
  subtype = varflags & VSTYPE;
  quoted = flag & EXP_QUOTED;
  var_ = p;
  startloc = expdest - (char *)stackblock();
  p = strchr(p, '=') + 1;
again:
  varlen = varvalue(var_, varflags, flag, quoted);
  if (varflags & VSNUL) varlen--;
  switch (subtype) {
    case VSPLUS:
      varlen = -1 - varlen;
      /* fall through */
    case 0:
    case VSMINUS:
      p = argstr(p, flag | EXP_TILDE | EXP_WORD);
      if (varlen < 0) return p;
      goto record;
    case VSASSIGN:
    case VSQUESTION:
      if (varlen >= 0) goto record;
      p = subevalvar(p, var_, 0, startloc, varflags, flag & ~QUOTES_ESC);
      if (flag & EXP_DISCARD) return p;
      varflags &= ~VSNUL;
      goto again;
  }
  if (varlen < 0 && uflag) varunset(p, var_, 0, 0);
  if (subtype == VSLENGTH) {
    if (flag & EXP_DISCARD) return p;
    cvtnum(varlen > 0 ? varlen : 0, flag);
    goto record;
  }
  if (subtype == VSNORMAL) goto record;
  flag |= varlen < 0 ? EXP_DISCARD : 0;
  if (!(flag & EXP_DISCARD)) {
    /*
     * Terminate the string and start recording the pattern
     * right after it
     */
    STPUTC('\0', expdest);
  }
  patloc = expdest - (char *)stackblock();
  p = subevalvar(p, NULL, patloc, startloc, varflags, flag);
record:
  if (flag & EXP_DISCARD) return p;
  if (quoted) {
    quoted = *var_ == '@' && shellparam.nparam;
    if (!quoted) return p;
  }
  recordregion(startloc, expdest - (char *)stackblock(), quoted);
  return p;
}

/*
 * Put a string on the stack.
 */
static unsigned memtodest(const char *p, unsigned len, int flags) {
  const char *syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
  char *q;
  char *s;
  if (unlikely(!len)) return 0;
  q = makestrspace(len * 2, expdest);
  s = q;
  do {
    int c = (signed char)*p++;
    if (c) {
      if ((flags & QUOTES_ESC) &&
          ((syntax[c] == CCTL) || (flags & EXP_QUOTED && syntax[c] == CBACK))) {
        USTPUTC(CTLESC, q);
      }
    } else if (!(flags & EXP_KEEPNUL))
      continue;
    USTPUTC(c, q);
  } while (--len);
  expdest = q;
  return q - s;
}

static unsigned strtodest(const char *p, int flags) {
  unsigned len = strlen(p);
  memtodest(p, len, flags);
  return len;
}

/*
 * Add the value of a specialized variable to the stack string.
 */
static long varvalue(char *name, int varflags, int flags, int quoted) {
  int num;
  char *p;
  int i;
  int sep;
  char sepc;
  char **ap;
  int subtype = varflags & VSTYPE;
  int discard = (subtype == VSPLUS || subtype == VSLENGTH) | (flags & EXP_DISCARD);
  long len = 0;
  char c;
  if (!subtype) {
    if (discard) return -1;
    sh_error("Bad substitution");
  }
  flags |= EXP_KEEPNUL;
  flags &= discard ? ~QUOTES_ESC : ~0;
  sep = (flags & EXP_FULL) << CHAR_BIT;
  switch (*name) {
    case '$':
      num = rootpid;
      goto numvar;
    case '?':
      num = exitstatus;
      goto numvar;
    case '#':
      num = shellparam.nparam;
      goto numvar;
    case '!':
      num = backgndpid;
      if (num == 0) return -1;
    numvar:
      len = cvtnum(num, flags);
      break;
    case '-':
      p = makestrspace(NOPTS, expdest);
      for (i = NOPTS - 1; i >= 0; i--) {
        if (optlist[i] && optletters[i]) {
          USTPUTC(optletters[i], p);
          len++;
        }
      }
      expdest = p;
      break;
    case '@':
      if (quoted && sep) goto param;
      /* fall through */
    case '*':
      /* We will set c to 0 or ~0 depending on whether
       * we're doing field splitting.  We won't do field
       * splitting if either we're quoted or sep is zero.
       *
       * Instead of testing (quoted || !sep) the following
       * trick optimises away any branches by using the
       * fact that EXP_QUOTED (which is the only bit that
       * can be set in quoted) is the same as EXP_FULL <<
       * CHAR_BIT (which is the only bit that can be set
       * in sep).
       */
      /* #if EXP_QUOTED >> CHAR_BIT != EXP_FULL */
      /* #error The following two lines expect EXP_QUOTED == EXP_FULL <<
       * CHAR_BIT */
      /* #endif */
      c = !((quoted | ~sep) & EXP_QUOTED) - 1;
      sep &= ~quoted;
      sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
    param:
      sepc = sep;
      if (!(ap = shellparam.p)) return -1;
      while ((p = *ap++)) {
        len += strtodest(p, flags);
        if (*ap && sep) {
          len++;
          memtodest(&sepc, 1, flags);
        }
      }
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      num = atoi(name);
      if (num < 0 || num > shellparam.nparam) return -1;
      p = num ? shellparam.p[num - 1] : arg0;
      goto value;
    default:
      p = lookupvar(name);
    value:
      if (!p) return -1;
      len = strtodest(p, flags);
      break;
  }
  if (discard) STADJUST(-len, expdest);
  return len;
}

/*
 * Record the fact that we have to scan this region of the
 * string for IFS characters.
 */
static void recordregion(int start, int end, int nulonly) {
  struct ifsregion *ifsp;
  if (ifslastp == NULL) {
    ifsp = &ifsfirst;
  } else {
    INTOFF;
    ifsp = (struct ifsregion *)ckmalloc(sizeof(struct ifsregion));
    ifsp->next = NULL;
    ifslastp->next = ifsp;
    INTON;
  }
  ifslastp = ifsp;
  ifslastp->begoff = start;
  ifslastp->endoff = end;
  ifslastp->nulonly = nulonly;
}

/*
 * 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.
 * If maxargs is non-negative, at most maxargs arguments will be created, by
 * joining together the last arguments.
 */
static void ifsbreakup(char *string, int maxargs, struct arglist *arglist) {
  struct ifsregion *ifsp;
  struct strlist *sp;
  char *start;
  char *p;
  char *q;
  char *r = NULL;
  const char *ifs, *realifs;
  int ifsspc;
  int nulonly;
  start = string;
  if (ifslastp != NULL) {
    ifsspc = 0;
    nulonly = 0;
    realifs = ifsset() ? ifsval() : defifs;
    ifsp = &ifsfirst;
    do {
      int afternul;
      p = string + ifsp->begoff;
      afternul = nulonly;
      nulonly = ifsp->nulonly;
      ifs = nulonly ? nullstr : realifs;
      ifsspc = 0;
      while (p < string + ifsp->endoff) {
        int c;
        bool isifs;
        bool isdefifs;
        q = p;
        c = *p++;
        if (c == (char)CTLESC) c = *p++;
        isifs = !!strchr(ifs, c);
        isdefifs = false;
        if (isifs) isdefifs = !!strchr(defifs, c);
        /* If only reading one more argument:
         * If we have exactly one field,
         * read that field without its terminator.
         * If we have more than one field,
         * read all fields including their terminators,
         * except for trailing IFS whitespace.
         *
         * This means that if we have only IFS
         * characters left, and at most one
         * of them is non-whitespace, we stop
         * reading here.
         * Otherwise, we read all the remaining
         * characters except for trailing
         * IFS whitespace.
         *
         * In any case, r indicates the start
         * of the characters to remove, or NULL
         * if no characters should be removed.
         */
        if (!maxargs) {
          if (isdefifs) {
            if (!r) r = q;
            continue;
          }
          if (!(isifs && ifsspc)) r = NULL;
          ifsspc = 0;
          continue;
        }
        if (ifsspc) {
          if (isifs) q = p;
          start = q;
          if (isdefifs) continue;
          isifs = false;
        }
        if (isifs) {
          if (!(afternul || nulonly)) ifsspc = isdefifs;
          /* Ignore IFS whitespace at start */
          if (q == start && ifsspc) {
            start = p;
            ifsspc = 0;
            continue;
          }
          if (maxargs > 0 && !--maxargs) {
            r = q;
            continue;
          }
          *q = '\0';
          sp = (struct strlist *)stalloc(sizeof *sp);
          sp->text = start;
          *arglist->lastp = sp;
          arglist->lastp = &sp->next;
          start = p;
          continue;
        }
        ifsspc = 0;
      }
    } while ((ifsp = ifsp->next) != NULL);
    if (nulonly) goto add;
  }
  if (r) *r = '\0';
  if (!*start) return;
add:
  sp = (struct strlist *)stalloc(sizeof *sp);
  sp->text = start;
  *arglist->lastp = sp;
  arglist->lastp = &sp->next;
}

/*
 * Expand shell metacharacters.  At this point, the only control characters
 * should be escapes.  The results are stored in the list exparg.
 */
static void expandmeta(struct strlist *str, int flag) {
  static const char metachars[] = {'*', '?', '[', 0};
  /* TODO - EXP_REDIR */
  while (str) {
    struct strlist **savelastp;
    struct strlist *sp;
    char *p;
    unsigned len;
    if (fflag) goto nometa;
    if (!strpbrk(str->text, metachars)) goto nometa;
    savelastp = exparg.lastp;
    INTOFF;
    p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
    len = strlen(p);
    expdir_max = len + PATH_MAX;
    expdir = ckmalloc(expdir_max);
    expmeta(p, len, 0);
    ckfree(expdir);
    if (p != str->text) ckfree(p);
    INTON;
    if (exparg.lastp == savelastp) {
      /*
       * no matches
       */
    nometa:
      *exparg.lastp = str;
      rmescapes(str->text, 0);
      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 *name, unsigned name_len, unsigned expdir_len) {
  char *enddir = expdir + expdir_len;
  char *p;
  const char *cp;
  char *start;
  char *endname;
  int metaflag;
  struct stat statb;
  DIR *dirp;
  struct dirent *dp;
  int atend;
  int matchdot;
  int esc;
  metaflag = 0;
  start = name;
  for (p = name; esc = 0, *p; p += esc + 1) {
    if (*p == '*' || *p == '?')
      metaflag = 1;
    else if (*p == '[') {
      char *q = p + 1;
      if (*q == '!') q++;
      for (;;) {
        if (*q == '\\') q++;
        if (*q == '/' || *q == '\0') break;
        if (*++q == ']') {
          metaflag = 1;
          break;
        }
      }
    } else {
      if (*p == '\\' && p[1]) esc++;
      if (p[esc] == '/') {
        if (metaflag) break;
        start = p + esc + 1;
      }
    }
  }
  if (metaflag == 0) { /* we've reached the end of the file name */
    if (!expdir_len) return;
    p = name;
    do {
      if (*p == '\\' && p[1]) p++;
      *enddir++ = *p;
    } while (*p++);
    if (lstat(expdir, &statb) >= 0) addfname(expdir);
    return;
  }
  endname = p;
  if (name < start) {
    p = name;
    do {
      if (*p == '\\' && p[1]) p++;
      *enddir++ = *p++;
    } while (p < start);
  }
  *enddir = 0;
  cp = expdir;
  expdir_len = enddir - cp;
  if (!expdir_len) cp = ".";
  if ((dirp = opendir(cp)) == NULL) return;
  if (*endname == 0) {
    atend = 1;
  } else {
    atend = 0;
    *endname = '\0';
    endname += esc + 1;
  }
  name_len -= endname - name;
  matchdot = 0;
  p = start;
  if (*p == '\\') p++;
  if (*p == '.') matchdot++;
  while (!int_pending() && (dp = readdir(dirp)) != NULL) {
    if (dp->d_name[0] == '.' && !matchdot) continue;
    if (pmatch(start, dp->d_name)) {
      if (atend) {
        scopy(dp->d_name, enddir);
        addfname(expdir);
      } else {
        unsigned offset;
        unsigned len;
        p = stpcpy(enddir, dp->d_name);
        *p = '/';
        offset = p - expdir + 1;
        len = offset + name_len + NAME_MAX;
        if (len > expdir_max) {
          len += PATH_MAX;
          expdir = ckrealloc(expdir, len);
          expdir_max = len;
        }
        expmeta(endname, name_len, offset);
        enddir = expdir + expdir_len;
      }
    }
  }
  closedir(dirp);
  if (!atend) endname[-esc - 1] = esc ? '\\' : '/';
}

/*
 * Add a file name to the list.
 */
static void addfname(char *name) {
  struct strlist *sp;
  sp = (struct strlist *)stalloc(sizeof *sp);
  sp->text = sstrdup(name);
  *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;
}

/*
 * Returns true if the pattern matches the string.
 */
static inline int patmatch(char *pattern, const char *string) {
  return pmatch(preglob(pattern, 0), string);
}

static int ccmatch(const char *p, int chr, const char **r) {
  static const struct class {
    char name[10];
    int (*fn)(int);
  } classes[] = {{.name = ":alnum:]", .fn = isalnum}, {.name = ":cntrl:]", .fn = iscntrl},
                 {.name = ":lower:]", .fn = islower}, {.name = ":space:]", .fn = isspace},
                 {.name = ":alpha:]", .fn = isalpha}, {.name = ":digit:]", .fn = isdigit},
                 {.name = ":print:]", .fn = isprint}, {.name = ":upper:]", .fn = isupper},
                 {.name = ":blank:]", .fn = isblank}, {.name = ":graph:]", .fn = isgraph},
                 {.name = ":punct:]", .fn = ispunct}, {.name = ":xdigit:]", .fn = isxdigit}};
  const struct class *class, *end;
  end = classes + sizeof(classes) / sizeof(classes[0]);
  for (class = classes; class < end; class ++) {
    const char *q;
    q = prefix(p, class->name);
    if (!q) continue;
    *r = q;
    return class->fn(chr);
  }
  *r = 0;
  return 0;
}

static int pmatch(const char *pattern, const char *string) {
  const char *p, *q;
  char c;
  p = pattern;
  q = string;
  for (;;) {
    switch (c = *p++) {
      case '\0':
        goto breakloop;
      case '\\':
        if (*p) {
          c = *p++;
        }
        goto dft;
      case '?':
        if (*q++ == '\0') return 0;
        break;
      case '*':
        c = *p;
        while (c == '*') c = *++p;
        if (c != '\\' && c != '?' && c != '*' && c != '[') {
          while (*q != c) {
            if (*q == '\0') return 0;
            q++;
          }
        }
        do {
          if (pmatch(p, q)) return 1;
        } while (*q++ != '\0');
        return 0;
      case '[': {
        const char *startp;
        int invert, found;
        char chr;
        startp = p;
        invert = 0;
        if (*p == '!') {
          invert++;
          p++;
        }
        found = 0;
        chr = *q;
        if (chr == '\0') return 0;
        c = *p++;
        do {
          if (!c) {
            p = startp;
            c = '[';
            goto dft;
          }
          if (c == '[') {
            const char *r;
            found |= !!ccmatch(p, chr, &r);
            if (r) {
              p = r;
              continue;
            }
          } else if (c == '\\')
            c = *p++;
          if (*p == '-' && p[1] != ']') {
            p++;
            if (*p == '\\') p++;
            if (chr >= c && chr <= *p) found = 1;
            p++;
          } else {
            if (chr == c) found = 1;
          }
        } while ((c = *p++) != ']');
        if (found == invert) return 0;
        q++;
        break;
      }
      dft:
      default:
        if (*q++ != c) return 0;
        break;
    }
  }
breakloop:
  if (*q != '\0') return 0;
  return 1;
}

/*
 * Remove any CTLESC characters from a string.
 */
static char *rmescapes(char *str, int flag) {
  char *p, *q, *r;
  int notescaped;
  int globbing;
  p = strpbrk(str, qchars);
  if (!p) {
    return str;
  }
  q = p;
  r = str;
  if (flag & RMESCAPE_ALLOC) {
    unsigned len = p - str;
    unsigned fulllen = len + strlen(p) + 1;
    if (flag & RMESCAPE_GROW) {
      int strloc = str - (char *)stackblock();
      r = makestrspace(fulllen, expdest);
      str = (char *)stackblock() + strloc;
      p = str + len;
    } else if (flag & RMESCAPE_HEAP) {
      r = ckmalloc(fulllen);
    } else {
      r = stalloc(fulllen);
    }
    q = r;
    if (len > 0) {
      q = mempcpy(q, str, len);
    }
  }
  globbing = flag & RMESCAPE_GLOB;
  notescaped = globbing;
  while (*p) {
    if (*p == (char)CTLQUOTEMARK) {
      p++;
      notescaped = globbing;
      continue;
    }
    if (*p == '\\') {
      /* naked back slash */
      notescaped = 0;
      goto copy;
    }
    if (*p == (char)CTLESC) {
      p++;
      if (notescaped) *q++ = '\\';
    }
    notescaped = globbing;
  copy:
    *q++ = *p++;
  }
  *q = '\0';
  if (flag & RMESCAPE_GROW) {
    expdest = r;
    STADJUST(q - r + 1, expdest);
  }
  return r;
}

/*
 * Our own itoa().
 */
static unsigned cvtnum(int64_t num, int flags) {
  int len = max_int_length(sizeof(num));
  char buf[len];
  len = fmtstr(buf, len, "%ld", num);
  return memtodest(buf, len, flags);
}

/*
 * Read a character from the script, returning PEOF on end of file.
 * Nul characters in the input are silently discarded.
 */
static int pgetc(void) {
  int c;
  if (parsefile->unget) return parsefile->lastc[--parsefile->unget];
  if (--parsefile->nleft >= 0)
    c = (signed char)*parsefile->nextc++;
  else
    c = preadbuffer();
  parsefile->lastc[1] = parsefile->lastc[0];
  parsefile->lastc[0] = c;
  return c;
}

/*
 * Same as pgetc(), but ignores PEOA.
 */
static int pgetc2() {
  int c;
  do {
    c = pgetc();
  } while (c == PEOA);
  return c;
}

static ssize_t preadfd(void) {
  ssize_t nr;
  char *buf = parsefile->buf;
  parsefile->nextc = buf;
retry:
  nr = read(parsefile->fd, buf, IBUFSIZ - 1);
  if (nr < 0) {
    if (errno == EINTR) goto retry;
    if (parsefile->fd == 0 && errno == EAGAIN) {
      int flags = fcntl(0, F_GETFL, 0);
      if (flags >= 0 && flags & O_NONBLOCK) {
        flags &= ~O_NONBLOCK;
        if (fcntl(0, F_SETFL, flags) >= 0) {
          outstr("sh: turning off NDELAY mode\n", out2);
          goto retry;
        }
      }
    }
  }
  return nr;
}

/*
 * Refill the input buffer and return the next input character:
 *
 * 1) If a string was pushed back on the input, pop it;
 * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading
 *    from a string so we can't refill the buffer, return EOF.
 * 3) If the is more stuff in this buffer, use it else call read to fill it.
 * 4) Process input up to the next newline, deleting nul characters.
 */
static int preadbuffer(void) {
  char *q;
  int more;
  char savec;
  if (unlikely(parsefile->strpush)) {
    if (parsefile->nleft == -1 && parsefile->strpush->ap && parsefile->nextc[-1] != ' ' &&
        parsefile->nextc[-1] != '\t') {
      return PEOA;
    }
    popstring();
    return pgetc();
  }
  if (unlikely(parsefile->nleft == EOF_NLEFT || parsefile->buf == NULL)) return PEOF;
  flushall();
  more = parsefile->lleft;
  if (more <= 0) {
  again:
    if ((more = preadfd()) <= 0) {
      parsefile->lleft = parsefile->nleft = EOF_NLEFT;
      return PEOF;
    }
  }
  q = parsefile->nextc;
  /* delete nul characters */
  for (;;) {
    int c;
    more--;
    c = *q;
    if (!c)
      memmove(q, q + 1, more);
    else {
      q++;
      if (c == '\n') {
        parsefile->nleft = q - parsefile->nextc - 1;
        break;
      }
    }
    if (more <= 0) {
      parsefile->nleft = q - parsefile->nextc - 1;
      if (parsefile->nleft < 0) goto again;
      break;
    }
  }
  parsefile->lleft = more;
  savec = *q;
  *q = '\0';
  if (vflag) {
    outstr(parsefile->nextc, out2);
  }
  *q = savec;
  return (signed char)*parsefile->nextc++;
}

/*
 * Undo a call to pgetc.  Only two characters may be pushed back.
 * PEOF may be pushed back.
 */
static void pungetc(void) {
  parsefile->unget++;
}

/*
 * Push a string back onto the input at this current parsefile level.
 * We handle aliases this way.
 */
static void pushstring(char *s, void *ap) {
  struct strpush *sp;
  unsigned len;
  len = strlen(s);
  INTOFF;
  /*dprintf("*** calling pushstring: %s, %d\n", s, len);*/
  if (parsefile->strpush) {
    sp = ckmalloc(sizeof(struct strpush));
    sp->prev = parsefile->strpush;
    parsefile->strpush = sp;
  } else
    sp = parsefile->strpush = &(parsefile->basestrpush);
  sp->prevstring = parsefile->nextc;
  sp->prevnleft = parsefile->nleft;
  sp->unget = parsefile->unget;
  memcpy(sp->lastc, parsefile->lastc, sizeof(sp->lastc));
  sp->ap = (struct alias *)ap;
  if (ap) {
    ((struct alias *)ap)->flag |= ALIASINUSE;
    sp->string = s;
  }
  parsefile->nextc = s;
  parsefile->nleft = len;
  parsefile->unget = 0;
  INTON;
}

static void popstring(void) {
  struct strpush *sp = parsefile->strpush;
  INTOFF;
  if (sp->ap) {
    if (parsefile->nextc[-1] == ' ' || parsefile->nextc[-1] == '\t') {
      checkkwd |= CHKALIAS;
    }
    if (sp->string != sp->ap->val) {
      ckfree(sp->string);
    }
    sp->ap->flag &= ~ALIASINUSE;
    if (sp->ap->flag & ALIASDEAD) {
      unalias(sp->ap->name);
    }
  }
  parsefile->nextc = sp->prevstring;
  parsefile->nleft = sp->prevnleft;
  parsefile->unget = sp->unget;
  memcpy(parsefile->lastc, sp->lastc, sizeof(sp->lastc));
  /*dprintf("*** 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.
 */
static int setinputfile(const char *fname, int flags) {
  int fd;
  INTOFF;
  if ((fd = open(fname, O_RDONLY, 0)) < 0) {
    if (flags & INPUT_NOFILE_OK) goto out;
    exitstatus = 127;
    exerror(EXERROR, "Can't open %s", fname);
  }
  if (fd < 10) fd = savefd(fd, fd);
  setinputfd(fd, flags & INPUT_PUSH_FILE);
out:
  INTON;
  return fd;
}

/*
 * Like setinputfile, but takes an open file descriptor.  Call this with
 * interrupts off.
 */
static void setinputfd(int fd, int push) {
  if (push) {
    pushfile();
    parsefile->buf = 0;
  }
  parsefile->fd = fd;
  if (parsefile->buf == NULL) parsefile->buf = ckmalloc(IBUFSIZ);
  parsefile->lleft = parsefile->nleft = 0;
  plinno = 1;
}

/*
 * Like setinputfile, but takes input from a string.
 */
static void setinputstring(char *string) {
  INTOFF;
  pushfile();
  parsefile->nextc = string;
  parsefile->nleft = 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;
  pf = (struct parsefile *)ckmalloc(sizeof(struct parsefile));
  pf->prev = parsefile;
  pf->fd = -1;
  pf->strpush = NULL;
  pf->basestrpush.prev = NULL;
  pf->unget = 0;
  parsefile = pf;
}

static 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);
  INTON;
}

static void unwindfiles(struct parsefile *stop) {
  while (parsefile != stop) popfile();
}

/*
 * Return to top level.
 */
static void popallfiles(void) {
  unwindfiles(&basepf);
}

/*
 * Close the file(s) that the shell is reading commands from.  Called
 * after a fork is done.
 */
static void closescript(void) {
  popallfiles();
  if (parsefile->fd > 0) {
    close(parsefile->fd);
    parsefile->fd = 0;
  }
}

static char *commandtext(union node *);
static int dowait(int, struct job *);
static int getstatus(struct job *);
static int jobno(const struct job *);
static int restartjob(struct job *, int);
static int sprint_status(char *, int, int);
static int waitproc(int, int *);
static struct job *getjob(const char *, int);
static struct job *growjobtab(void);
static void cmdlist(union node *, int);
static void cmdputs(const char *);
static void cmdtxt(union node *);
static void forkchild(struct job *, union node *, int);
static void forkparent(struct job *, union node *, int, int);
static void freejob(struct job *);
static void set_curjob(struct job *, unsigned);
static void showpipe(struct job *, struct output *);
static void xtcsetpgrp(int, int);

static void set_curjob(struct job *jp, unsigned mode) {
  struct job *jp1;
  struct job **jpp, **curp;
  /* first remove from list */
  jpp = curp = &curjob;
  do {
    jp1 = *jpp;
    if (jp1 == jp) break;
    jpp = &jp1->prev_job;
  } while (1);
  *jpp = jp1->prev_job;
  /* Then re-insert in correct position */
  jpp = curp;
  switch (mode) {
    default:
    case CUR_DELETE:
      /* job being deleted */
      break;
    case CUR_RUNNING:
      /* newly created job or backgrounded job,
         put after all stopped jobs. */
      do {
        jp1 = *jpp;
        if (!JOBS || !jp1 || jp1->state != JOBSTOPPED) break;
        jpp = &jp1->prev_job;
      } while (1);
      /* FALLTHROUGH */
    case CUR_STOPPED:
      /* newly stopped job - becomes curjob */
      jp->prev_job = *jpp;
      *jpp = jp;
      break;
  }
}

/*
 * Turn job control on and off.
 *
 * Note:  This code assumes that the third arg to ioctl is a character
 * pointer, which is true on Berkeley systems but not System V.  Since
 * System V doesn't have job control yet, this isn't a problem now.
 *
 * Called with interrupts off.
 */
static void setjobctl(int on) {
  int fd;
  int pgrp;
  if (IsWindows()) return; /* TODO(jart) */
  if (on == jobctl || rootshell == 0) return;
  if (on) {
    int ofd;
    ofd = fd = open(_PATH_TTY, O_RDWR, 0);
    if (fd < 0) {
      fd += 3;
      while (!isatty(fd))
        if (--fd < 0) goto out;
    }
    fd = savefd(fd, ofd);
    do { /* while we are in the background */
      if ((pgrp = tcgetpgrp(fd)) < 0) {
      out:
        sh_warnx("can't access tty; job control turned off");
        mflag = on = 0;
        goto close;
      }
      if (pgrp == getpgrp()) break;
      killpg(0, SIGTTIN);
    } while (1);
    initialpgrp = pgrp;
    setsignal(SIGTSTP);
    setsignal(SIGTTOU);
    setsignal(SIGTTIN);
    pgrp = rootpid;
    setpgid(0, pgrp);
    xtcsetpgrp(fd, pgrp);
  } else {
    /* turning job control off */
    fd = ttyfd;
    pgrp = initialpgrp;
    xtcsetpgrp(fd, pgrp);
    setpgid(0, pgrp);
    setsignal(SIGTSTP);
    setsignal(SIGTTOU);
    setsignal(SIGTTIN);
  close:
    close(fd);
    fd = -1;
  }
  ttyfd = fd;
  jobctl = on;
}

static int decode_signum(const char *string) {
  int signo = -1;
  if (is_number(string)) {
    signo = atoi(string);
    if (signo >= NSIG) signo = -1;
  }
  return signo;
}

static int killcmd(argc, argv) int argc;
char **argv;
{
  int signo = -1;
  int list = 0;
  int i;
  int pid;
  struct job *jp;
  if (argc <= 1) {
  usage:
    sh_error("Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n"
             "kill -l [exitstatus]");
  }
  if (**++argv == '-') {
    signo = decode_signal(*argv + 1, 1);
    if (signo < 0) {
      int c;
      while ((c = nextopt("ls:")) != '\0') switch (c) {
          default:
          case 'l':
            list = 1;
            break;
          case 's':
            signo = decode_signal(optionarg, 1);
            if (signo < 0) {
              sh_error("invalid signal number or name: %s", optionarg);
            }
            break;
        }
      argv = argptr;
    } else
      argv++;
  }
  if (!list && signo < 0) signo = SIGTERM;
  if ((signo < 0 || !*argv) ^ list) {
    goto usage;
  }
  if (list) {
    struct output *out;
    out = out1;
    if (!*argv) {
      outstr("0\n", out);
      for (i = 1; i < NSIG; i++) {
        outfmt(out, snlfmt, strsignal(i));
      }
      return 0;
    }
    signo = number(*argv);
    if (signo > 128) signo -= 128;
    if (0 < signo && signo < NSIG)
      outfmt(out, snlfmt, strsignal(signo));
    else
      sh_error("invalid signal number or exit status: %s", *argv);
    return 0;
  }
  i = 0;
  do {
    if (**argv == '%') {
      jp = getjob(*argv, 0);
      pid = -jp->ps[0].pid;
    } else
      pid = **argv == '-' ? -number(*argv + 1) : number(*argv);
    if (kill(pid, signo) != 0) {
      sh_warnx("%s\n", strerror(errno));
      i = 1;
    }
  } while (*++argv);
  return i;
}

static int jobno(const struct job *jp) {
  return jp - jobtab + 1;
}

static int fgcmd(int argc, char **argv) {
  struct job *jp;
  struct output *out;
  int mode;
  int retval;
  mode = (**argv == 'f') ? FORK_FG : FORK_BG;
  nextopt(nullstr);
  argv = argptr;
  out = out1;
  do {
    jp = getjob(*argv, 1);
    if (mode == FORK_BG) {
      set_curjob(jp, CUR_RUNNING);
      outfmt(out, "[%d] ", jobno(jp));
    }
    outstr(jp->ps->cmd, out);
    showpipe(jp, out);
    retval = restartjob(jp, mode);
  } while (*argv && *++argv);
  return retval;
}

static int bgcmd(int argc, char **argv) __attribute__((__alias__("fgcmd")));

static int restartjob(struct job *jp, int mode) {
  struct procstat *ps;
  int i;
  int status;
  int pgid;
  INTOFF;
  if (jp->state == JOBDONE) goto out;
  jp->state = JOBRUNNING;
  pgid = jp->ps->pid;
  if (mode == FORK_FG) xtcsetpgrp(ttyfd, pgid);
  killpg(pgid, SIGCONT);
  ps = jp->ps;
  i = jp->nprocs;
  do {
    if (WIFSTOPPED(ps->status)) {
      ps->status = -1;
    }
  } while (ps++, --i);
out:
  status = (mode == FORK_FG) ? waitforjob(jp) : 0;
  INTON;
  return status;
}

static int sprint_status(char *os, int status, int sigonly) {
  char *s = os;
  int st;
  st = WEXITSTATUS(status);
  if (!WIFEXITED(status)) {
    st = WSTOPSIG(status);
    if (!WIFSTOPPED(status)) st = WTERMSIG(status);
    if (sigonly) {
      if (st == SIGINT || st == SIGPIPE) goto out;
      if (WIFSTOPPED(status)) goto out;
    }
    s = stpncpy(s, strsignal(st), 32);
  } else if (!sigonly) {
    if (st)
      s += fmtstr(s, 16, "Done(%d)", st);
    else
      s = stpcpy(s, "Done");
  }
out:
  return s - os;
}

static void showjob(struct output *out, struct job *jp, int mode) {
  struct procstat *ps;
  struct procstat *psend;
  int col;
  int indent;
  char s[80];
  ps = jp->ps;
  if (mode & SHOW_PGID) {
    /* just output process (group) id of pipeline */
    outfmt(out, "%d\n", ps->pid);
    return;
  }
  col = fmtstr(s, 16, "[%d]   ", jobno(jp));
  indent = col;
  if (jp == curjob)
    s[col - 2] = '+';
  else if (curjob && jp == curjob->prev_job)
    s[col - 2] = '-';
  if (mode & SHOW_PID) col += fmtstr(s + col, 16, "%d ", ps->pid);
  psend = ps + jp->nprocs;
  if (jp->state == JOBRUNNING) {
    scopy("Running", s + col);
    col += strlen("Running");
  } else {
    int status = psend[-1].status;
    if (jp->state == JOBSTOPPED) status = jp->stopstatus;
    col += sprint_status(s + col, status, 0);
  }
  goto start;
  do {
    /* for each process */
    col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3;
  start:
    outfmt(out, "%s%*c%s", s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd);
    if (!(mode & SHOW_PID)) {
      showpipe(jp, out);
      break;
    }
    if (++ps == psend) {
      outcslow('\n', out);
      break;
    }
  } while (1);
  jp->changed = 0;
  if (jp->state == JOBDONE) {
    TRACE(("showjob: freeing job %d\n", jobno(jp)));
    freejob(jp);
  }
}

static int jobscmd(int argc, char **argv) {
  int mode, m;
  struct output *out;
  mode = 0;
  while ((m = nextopt("lp")))
    if (m == 'l')
      mode = SHOW_PID;
    else
      mode = SHOW_PGID;
  out = out1;
  argv = argptr;
  if (*argv) do
      showjob(out, getjob(*argv, 0), mode);
    while (*++argv);
  else
    showjobs(out, mode);
  return 0;
}

/*
 * Print a list of jobs.  If "change" is nonzero, only print jobs whose
 * statuses have changed since the last call to showjobs.
 */
static void showjobs(struct output *out, int mode) {
  struct job *jp;
  TRACE(("showjobs(%x) called\n", mode));
  /* If not even one one job changed, there is nothing to do */
  dowait(DOWAIT_NORMAL, NULL);
  for (jp = curjob; jp; jp = jp->prev_job) {
    if (!(mode & SHOW_CHANGED) || jp->changed) showjob(out, jp, mode);
  }
}

/*
 * Mark a job structure as unused.
 */
static void freejob(struct job *jp) {
  struct procstat *ps;
  int i;
  INTOFF;
  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;
  set_curjob(jp, CUR_DELETE);
  INTON;
}

static int waitcmd(int argc, char **argv) {
  struct job *job;
  int retval;
  struct job *jp;
  nextopt(nullstr);
  retval = 0;
  argv = argptr;
  if (!*argv) {
    /* wait for all jobs */
    for (;;) {
      jp = curjob;
      while (1) {
        if (!jp) {
          /* no running procs */
          goto out;
        }
        if (jp->state == JOBRUNNING) break;
        jp->waited = 1;
        jp = jp->prev_job;
      }
      if (!dowait(DOWAIT_WAITCMD, 0)) goto sigout;
    }
  }
  retval = 127;
  do {
    if (**argv != '%') {
      int pid = number(*argv);
      job = curjob;
      goto start;
      do {
        if (job->ps[job->nprocs - 1].pid == pid) break;
        job = job->prev_job;
      start:
        if (!job) goto repeat;
      } while (1);
    } else
      job = getjob(*argv, 0);
    /* loop until process terminated or stopped */
    if (!dowait(DOWAIT_WAITCMD, job)) goto sigout;
    job->waited = 1;
    retval = getstatus(job);
  repeat:;
  } while (*++argv);
out:
  return retval;
sigout:
  retval = 128 + pending_sig;
  goto out;
}

/*
 * Convert a job name to a job structure.
 */
static struct job *getjob(const char *name, int getctl) {
  struct job *jp;
  struct job *found;
  const char *err_msg = "No such job: %s";
  unsigned num;
  int c;
  const char *p;
  char *(*match)(const char *, const char *);
  jp = curjob;
  p = name;
  if (!p) goto currentjob;
  if (*p != '%') goto err;
  c = *++p;
  if (!c) goto currentjob;
  if (!p[1]) {
    if (c == '+' || c == '%') {
    currentjob:
      err_msg = "No current job";
      goto check;
    } else if (c == '-') {
      if (jp) jp = jp->prev_job;
      err_msg = "No previous job";
    check:
      if (!jp) goto err;
      goto gotit;
    }
  }
  if (is_number(p)) {
    num = atoi(p);
    if (num > 0 && num <= njobs) {
      jp = jobtab + num - 1;
      if (jp->used) goto gotit;
      goto err;
    }
  }
  match = prefix;
  if (*p == '?') {
    match = strstr;
    p++;
  }
  found = 0;
  while (jp) {
    if (match(jp->ps[0].cmd, p)) {
      if (found) goto err;
      found = jp;
      err_msg = "%s: ambiguous";
    }
    jp = jp->prev_job;
  }
  if (!found) goto err;
  jp = found;
gotit:
  err_msg = "job %s not created under job control";
  if (getctl && jp->jobctl == 0) goto err;
  return jp;
err:
  sh_error(err_msg, name);
}

/*
 * Return a new job structure.
 * Called with interrupts off.
 */
struct job *makejob(union node *node, int nprocs) {
  int i;
  struct job *jp;
  for (i = njobs, jp = jobtab;; jp++) {
    if (--i < 0) {
      jp = growjobtab();
      break;
    }
    if (jp->used == 0) break;
    if (jp->state != JOBDONE || !jp->waited) continue;
    if (jobctl) continue;
    freejob(jp);
    break;
  }
  memset(jp, 0, sizeof(*jp));
  if (jobctl) jp->jobctl = 1;
  jp->prev_job = curjob;
  curjob = jp;
  jp->used = 1;
  jp->ps = &jp->ps0;
  if (nprocs > 1) {
    jp->ps = ckmalloc(nprocs * sizeof(struct procstat));
  }
  TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, jobno(jp)));
  return jp;
}

static struct job *growjobtab(void) {
  unsigned len;
  long offset;
  struct job *jp, *jq;
  len = njobs * sizeof(*jp);
  jq = jobtab;
  jp = ckrealloc(jq, len + 4 * sizeof(*jp));
  offset = (char *)jp - (char *)jq;
  if (offset) {
    /* Relocate pointers */
    unsigned l = len;
    jq = (struct job *)((char *)jq + l);
    while (l) {
      l -= sizeof(*jp);
      jq--;
#define joff(p)  ((struct job *)((char *)(p) + l))
#define jmove(p) (p) = (void *)((char *)(p) + offset)
      if (likely(joff(jp)->ps == &jq->ps0)) jmove(joff(jp)->ps);
      if (joff(jp)->prev_job) jmove(joff(jp)->prev_job);
    }
    if (curjob) jmove(curjob);
#undef joff
#undef jmove
  }
  njobs += 4;
  jobtab = jp;
  jp = (struct job *)((char *)jp + len);
  jq = jp + 3;
  do {
    jq->used = 0;
  } while (--jq >= jp);
  return jp;
}

/*
 * Fork off a subshell.  If we are doing job control, give the subshell its
 * own process group.  Jp is a job structure that the job is to be added to.
 * N is the command that will be evaluated by the child.  Both jp and n may
 * be NULL.  The mode parameter can be one of the following:
 *	FORK_FG - Fork off a foreground process.
 *	FORK_BG - Fork off a background process.
 *	FORK_NOJOB - Like FORK_FG, but don't give the process its own
 *		     process group even if job control is on.
 *
 * When job control is turned off, background processes have their standard
 * input redirected to /dev/null (except for the second and later processes
 * in a pipeline).
 *
 * Called with interrupts off.
 */
static void forkchild(struct job *jp, union node *n, int mode) {
  int lvforked;
  int oldlvl;
  TRACE(("Child shell %d\n", getpid()));
  oldlvl = shlvl;
  lvforked = vforked;
  if (!lvforked) {
    shlvl++;
    closescript();
    clear_traps();
    /* do job control only in root shell */
    jobctl = 0;
  }
  if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) {
    int pgrp;
    if (jp->nprocs == 0)
      pgrp = getpid();
    else
      pgrp = jp->ps[0].pid;
    /* This can fail because we are doing it in the parent also */
    (void)setpgid(0, pgrp);
    if (mode == FORK_FG) xtcsetpgrp(ttyfd, pgrp);
    setsignal(SIGTSTP);
    setsignal(SIGTTOU);
  } else if (mode == FORK_BG) {
    ignoresig(SIGINT);
    ignoresig(SIGQUIT);
    if (jp->nprocs == 0) {
      close(0);
      if (open(_PATH_DEVNULL, O_RDONLY, 0) != 0) sh_error("Can't open %s", _PATH_DEVNULL);
    }
  }
  if (!oldlvl && iflag) {
    if (mode != FORK_BG) {
      setsignal(SIGINT);
      setsignal(SIGQUIT);
    }
    setsignal(SIGTERM);
  }
  if (lvforked) return;
  for (jp = curjob; jp; jp = jp->prev_job) freejob(jp);
}

static void forkparent(struct job *jp, union node *n, int mode, int pid) {
  if (pid < 0) {
    TRACE(("Fork failed, errno=%d", errno));
    if (jp) freejob(jp);
    sh_error("Cannot fork");
    unreachable;
  }
  TRACE(("In parent shell:  child = %d\n", pid));
  if (!jp) return;
  if (mode != FORK_NOJOB && jp->jobctl) {
    int pgrp;
    if (jp->nprocs == 0)
      pgrp = pid;
    else
      pgrp = jp->ps[0].pid;
    /* This can fail because we are doing it in the child also */
    (void)setpgid(pid, pgrp);
  }
  if (mode == FORK_BG) {
    backgndpid = pid; /* set $! */
    set_curjob(jp, CUR_RUNNING);
  }
  if (jp) {
    struct procstat *ps = &jp->ps[jp->nprocs++];
    ps->pid = pid;
    ps->status = -1;
    ps->cmd = nullstr;
    if (jobctl && n) ps->cmd = commandtext(n);
  }
}

static int forkshell(struct job *jp, union node *n, int mode) {
  int pid;
  TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode));
  pid = fork();
  if (pid == 0) {
    forkchild(jp, n, mode);
  } else {
    forkparent(jp, n, mode, pid);
  }
  return pid;
}

static struct job *vforkexec(union node *n, char **argv, const char *path, int idx) {
  struct job *jp;
  int pid;
  jp = makejob(n, 1);
  sigblockall(NULL);
  vforked++;
  pid = vfork();
  if (!pid) {
    forkchild(jp, n, FORK_FG);
    sigclearmask();
    shellexec(argv, path, idx);
    unreachable;
  }
  vforked = 0;
  sigclearmask();
  forkparent(jp, n, FORK_FG, pid);
  return jp;
}

/*
 * Wait for job to finish.
 *
 * Under job control we have the problem that while a child process is
 * running interrupts generated by the user are sent to the child but not
 * to the shell.  This means that an infinite loop started by an inter-
 * active user may be hard to kill.  With job control turned off, an
 * interactive user may place an interactive program inside a loop.  If
 * the interactive program catches interrupts, the user doesn't want
 * these interrupts to also abort the loop.  The approach we take here
 * is to have the shell ignore interrupt signals while waiting for a
 * forground process to terminate, and then send itself an interrupt
 * signal if the child process was terminated by an interrupt signal.
 * Unfortunately, some programs want to do a bit of cleanup and then
 * exit on interrupt; unless these processes terminate themselves by
 * sending a signal to themselves (instead of calling exit) they will
 * confuse this approach.
 *
 * Called with interrupts off.
 */
static int waitforjob(struct job *jp) {
  int st;
  TRACE(("waitforjob(%%%d) called\n", jp ? jobno(jp) : 0));
  dowait(jp ? DOWAIT_BLOCK : DOWAIT_NORMAL, jp);
  if (!jp) return exitstatus;
  st = getstatus(jp);
  if (jp->jobctl) {
    xtcsetpgrp(ttyfd, rootpid);
    /*
     * This is truly gross.
     * If we're doing job control, then we did a TIOCSPGRP which
     * caused us (the shell) to no longer be in the controlling
     * session -- so we wouldn't have seen any ^C/SIGINT.  So, we
     * intuit from the subprocess exit status whether a SIGINT
     * occurred, and if so interrupt ourselves.  Yuck.  - mycroft
     */
    if (jp->sigint) raise(SIGINT);
  }
  if (!JOBS || jp->state == JOBDONE) freejob(jp);
  return st;
}

/*
 * Wait for a process to terminate.
 */
static int waitone(int block, struct job *job) {
  int pid;
  int status;
  struct job *jp;
  struct job *thisjob = NULL;
  int state;
  INTOFF;
  TRACE(("dowait(%d) called\n", block));
  pid = waitproc(block, &status);
  TRACE(("wait returns pid %d, status=%d\n", pid, status));
  if (pid <= 0) goto out;
  for (jp = curjob; jp; jp = jp->prev_job) {
    struct procstat *sp;
    struct procstat *spend;
    if (jp->state == JOBDONE) continue;
    state = JOBDONE;
    spend = jp->ps + jp->nprocs;
    sp = jp->ps;
    do {
      if (sp->pid == pid) {
        TRACE(("Job %d: changing status of proc %d from 0x%x to 0x%x\n", jobno(jp), pid, sp->status,
               status));
        sp->status = status;
        thisjob = jp;
      }
      if (sp->status == -1) state = JOBRUNNING;
      if (state == JOBRUNNING) continue;
      if (WIFSTOPPED(sp->status)) {
        jp->stopstatus = sp->status;
        state = JOBSTOPPED;
      }
    } while (++sp < spend);
    if (thisjob) goto gotjob;
  }
  goto out;
gotjob:
  if (state != JOBRUNNING) {
    thisjob->changed = 1;
    if (thisjob->state != state) {
      TRACE(("Job %d: changing state from %d to %d\n", jobno(thisjob), thisjob->state, state));
      thisjob->state = state;
      if (state == JOBSTOPPED) {
        set_curjob(thisjob, CUR_STOPPED);
      }
    }
  }
out:
  INTON;
  if (thisjob && thisjob == job) {
    char s[48 + 1];
    int len;
    len = sprint_status(s, status, 1);
    if (len) {
      s[len] = '\n';
      s[len + 1] = 0;
      outstr(s, out2);
    }
  }
  return pid;
}

static int dowait(int block, struct job *jp) {
  int pid = block == DOWAIT_NORMAL ? gotsigchld : 1;
  while (jp ? jp->state == JOBRUNNING : pid > 0) {
    if (!jp) gotsigchld = 0;
    pid = waitone(block, jp);
  }
  return pid;
}

/*
 * Do a wait system call.  If block is zero, we return -1 rather than
 * blocking.  If block is DOWAIT_WAITCMD, we return 0 when a signal
 * other than SIGCHLD interrupted the wait.
 *
 * We use sigsuspend in conjunction with a non-blocking wait3 in
 * order to ensure that waitcmd exits promptly upon the reception
 * of a signal.
 *
 * For code paths other than waitcmd we either use a blocking wait3
 * or a non-blocking wait3.  For the latter case the caller of dowait
 * must ensure that it is called over and over again until all dead
 * children have been reaped.  Otherwise zombies may linger.
 */
static int waitproc(int block, int *status) {
  sigset_t oldmask;
  int flags = block == DOWAIT_BLOCK ? 0 : WNOHANG;
  int err;
  if (jobctl) flags |= WUNTRACED;
  do {
    err = wait3(status, flags, NULL);
    if (err || (err = -!block)) break;
    sigblockall(&oldmask);
    while (!gotsigchld && !pending_sig) sigsuspend(&oldmask);
    sigclearmask();
    err = 0;
  } while (gotsigchld);
  return err;
}

/*
 * return 1 if there are stopped jobs, otherwise 0
 */
static int stoppedjobs(void) {
  struct job *jp;
  int retval;
  retval = 0;
  if (job_warning) goto out;
  jp = curjob;
  if (jp && jp->state == JOBSTOPPED) {
    outstr("You have stopped jobs.\n", out2);
    job_warning = 2;
    retval++;
  }
out:
  return retval;
}

/*
 * Return a string identifying a command (to be printed by the
 * jobs command).
 */
static char *commandtext(union node *n) {
  char *name;
  STARTSTACKSTR(cmdnextc);
  cmdtxt(n);
  name = stackblock();
  TRACE(("commandtext: name %p, end %p\n", name, cmdnextc));
  return savestr(name);
}

static void cmdtxt(union node *n) {
  union node *np;
  struct nodelist *lp;
  const char *p;
  char s[2];
  if (!n) return;
  switch (n->type) {
    default:
    case NPIPE:
      lp = n->npipe.cmdlist;
      for (;;) {
        cmdtxt(lp->n);
        lp = lp->next;
        if (!lp) break;
        cmdputs(" | ");
      }
      break;
    case NSEMI:
      p = "; ";
      goto binop;
    case NAND:
      p = " && ";
      goto binop;
    case NOR:
      p = " || ";
    binop:
      cmdtxt(n->nbinary.ch1);
      cmdputs(p);
      n = n->nbinary.ch2;
      goto donode;
    case NREDIR:
    case NBACKGND:
      n = n->nredir.n;
      goto donode;
    case NNOT:
      cmdputs("!");
      n = n->nnot.com;
    donode:
      cmdtxt(n);
      break;
    case NIF:
      cmdputs("if ");
      cmdtxt(n->nif.test);
      cmdputs("; then ");
      if (n->nif.elsepart) {
        cmdtxt(n->nif.ifpart);
        cmdputs("; else ");
        n = n->nif.elsepart;
      } else {
        n = n->nif.ifpart;
      }
      p = "; fi";
      goto dotail;
    case NSUBSHELL:
      cmdputs("(");
      n = n->nredir.n;
      p = ")";
      goto dotail;
    case NWHILE:
      p = "while ";
      goto until;
    case NUNTIL:
      p = "until ";
    until:
      cmdputs(p);
      cmdtxt(n->nbinary.ch1);
      n = n->nbinary.ch2;
      p = "; done";
    dodo:
      cmdputs("; do ");
    dotail:
      cmdtxt(n);
      goto dotail2;
    case NFOR:
      cmdputs("for ");
      cmdputs(n->nfor.var_);
      cmdputs(" in ");
      cmdlist(n->nfor.args, 1);
      n = n->nfor.body;
      p = "; done";
      goto dodo;
    case NDEFUN:
      cmdputs(n->ndefun.text);
      p = "() { ... }";
      goto dotail2;
    case NCMD:
      cmdlist(n->ncmd.args, 1);
      cmdlist(n->ncmd.redirect, 0);
      break;
    case NARG:
      p = n->narg.text;
    dotail2:
      cmdputs(p);
      break;
    case NHERE:
    case NXHERE:
      p = "<<...";
      goto dotail2;
    case NCASE:
      cmdputs("case ");
      cmdputs(n->ncase.expr->narg.text);
      cmdputs(" in ");
      for (np = n->ncase.cases; np; np = np->nclist.next) {
        cmdtxt(np->nclist.pattern);
        cmdputs(") ");
        cmdtxt(np->nclist.body);
        cmdputs(";; ");
      }
      p = "esac";
      goto dotail2;
    case NTO:
      p = ">";
      goto redir;
    case NCLOBBER:
      p = ">|";
      goto redir;
    case NAPPEND:
      p = ">>";
      goto redir;
    case NTOFD:
      p = ">&";
      goto redir;
    case NFROM:
      p = "<";
      goto redir;
    case NFROMFD:
      p = "<&";
      goto redir;
    case NFROMTO:
      p = "<>";
    redir:
      s[0] = n->nfile.fd + '0';
      s[1] = '\0';
      cmdputs(s);
      cmdputs(p);
      if (n->type == NTOFD || n->type == NFROMFD) {
        s[0] = n->ndup.dupfd + '0';
        p = s;
        goto dotail2;
      } else {
        n = n->nfile.fname;
        goto donode;
      }
  }
}

static void cmdlist(union node *np, int sep) {
  for (; np; np = np->narg.next) {
    if (!sep) cmdputs(spcstr);
    cmdtxt(np);
    if (sep && np->narg.next) cmdputs(spcstr);
  }
}

static void cmdputs(const char *s) {
  const char *p, *str;
  char cc[2] = " ";
  char *nextc;
  signed char c;
  int subtype = 0;
  int quoted = 0;
  static const char vstype[VSTYPE + 1][4] = {
      "", "}", "-", "+", "?", "=", "%", "%%", "#", "##",
  };
  nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc);
  p = s;
  while ((c = *p++) != 0) {
    str = 0;
    switch (c) {
      case CTLESC:
        c = *p++;
        break;
      case CTLVAR:
        subtype = *p++;
        if ((subtype & VSTYPE) == VSLENGTH)
          str = "${#";
        else
          str = "${";
        goto dostr;
      case CTLENDVAR:
        str = &"\"}"[!quoted];
        quoted >>= 1;
        subtype = 0;
        goto dostr;
      case CTLBACKQ:
        str = "$(...)";
        goto dostr;
      case CTLARI:
        str = "$((";
        goto dostr;
      case CTLENDARI:
        str = "))";
        goto dostr;
      case CTLQUOTEMARK:
        quoted ^= 1;
        c = '"';
        break;
      case '=':
        if (subtype == 0) break;
        if ((subtype & VSTYPE) != VSNORMAL) quoted <<= 1;
        str = vstype[subtype & VSTYPE];
        if (subtype & VSNUL)
          c = ':';
        else
          goto checkstr;
        break;
      case '\'':
      case '\\':
      case '"':
      case '$':
        /* These can only happen inside quotes */
        cc[0] = c;
        str = cc;
        c = '\\';
        break;
      default:
        break;
    }
    USTPUTC(c, nextc);
  checkstr:
    if (!str) continue;
  dostr:
    while ((c = *str++)) {
      USTPUTC(c, nextc);
    }
  }
  if (quoted & 1) {
    USTPUTC('"', nextc);
  }
  *nextc = 0;
  cmdnextc = nextc;
}

static void showpipe(struct job *jp, struct output *out) {
  struct procstat *sp;
  struct procstat *spend;
  spend = jp->ps + jp->nprocs;
  for (sp = jp->ps + 1; sp < spend; sp++) outfmt(out, " | %s", sp->cmd);
  outcslow('\n', out);
  flushall();
}

static void xtcsetpgrp(int fd, int pgrp) {
  if (tcsetpgrp(fd, pgrp)) sh_error("Cannot set tty process group (%s)", strerror(errno));
}

static int getstatus(struct job *job) {
  int status;
  int retval;
  status = job->ps[job->nprocs - 1].status;
  retval = WEXITSTATUS(status);
  if (!WIFEXITED(status)) {
    retval = WSTOPSIG(status);
    if (!WIFSTOPPED(status)) {
      /* XXX: limits number of signals */
      retval = WTERMSIG(status);
      if (retval == SIGINT) job->sigint = 1;
    }
    retval += 128;
  }
  TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", jobno(job), job->nprocs, status,
         retval));
  return retval;
}

/** handle one line of the read command.
 *  more fields than variables -> remainder shall be part of last variable.
 *  less fields than variables -> remaining variables unset.
 *
 *  @param line complete line of input
 *  @param ac argument count
 *  @param ap argument (variable) list
 *  @param len length of line including trailing '\0'
 */
static void readcmd_handle_line(char *s, int ac, char **ap) {
  struct arglist arglist;
  struct strlist *sl;
  s = grabstackstr(s);
  arglist.lastp = &arglist.list;
  ifsbreakup(s, ac, &arglist);
  *arglist.lastp = NULL;
  ifsfree();
  sl = arglist.list;
  do {
    if (!sl) {
      /* nullify remaining arguments */
      do {
        setvar(*ap, nullstr, 0);
      } while (*++ap);
      return;
    }
    /* set variable to field */
    rmescapes(sl->text, 0);
    setvar(*ap, sl->text, 0);
    sl = sl->next;
  } while (*++ap);
}

/*
 * The read builtin.  The -e option causes backslashes to escape the
 * following character. The -p option followed by an argument prompts
 * with the argument.
 *
 * This uses unbuffered input, which may be avoidable in some cases.
 */
static int readcmd(int argc, char **argv) {
  char **ap;
  char c;
  int rflag;
  char *prompt;
  char *p;
  int startloc;
  int newloc;
  int status;
  int i;
  rflag = 0;
  prompt = NULL;
  while ((i = nextopt("p:r")) != '\0') {
    if (i == 'p')
      prompt = optionarg;
    else
      rflag = 1;
  }
  if (prompt && isatty(0)) {
    outstr(prompt, out2);
  }
  if (*(ap = argptr) == NULL) sh_error("arg count");
  status = 0;
  STARTSTACKSTR(p);
  goto start;
  for (;;) {
    switch (read(STDIN_FILENO, &c, 1)) {
      case 1:
        break;
      default:
        if (errno == EINTR && !pending_sig) continue;
        /* fall through */
      case 0:
        status = 1;
        goto out;
    }
    if (c == '\0') continue;
    if (newloc >= startloc) {
      if (c == '\n') goto resetbs;
      goto put;
    }
    if (!rflag && c == '\\') {
      newloc = p - (char *)stackblock();
      continue;
    }
    if (c == '\n') break;
  put:
    CHECKSTRSPACE(2, p);
    if (strchr(qchars, c)) USTPUTC(CTLESC, p);
    USTPUTC(c, p);
    if (newloc >= startloc) {
    resetbs:
      recordregion(startloc, newloc, 0);
    start:
      startloc = p - (char *)stackblock();
      newloc = startloc - 1;
    }
  }
out:
  recordregion(startloc, p - (char *)stackblock(), 0);
  STACKSTRNUL(p);
  readcmd_handle_line(p + 1, argc - (ap - argv), ap);
  return status;
}

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » builtins » umask                     ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│─╝
  This code was ripped from pdksh 5.2.14 and hacked for use with
  dash by Herbert Xu. Public Domain. */

static int umaskcmd(int argc, char **argv) {
  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 buf[18];
      int j;
      mask = ~mask;
      ap = buf;
      for (i = 0; i < 3; i++) {
        *ap++ = "ugo"[i];
        *ap++ = '=';
        for (j = 0; j < 3; j++)
          if (mask & (1u << (8 - (3 * i + j)))) *ap++ = "rwx"[j];
        *ap++ = ',';
      }
      ap[-1] = '\0';
      out1fmt("%s\n", buf);
    } else {
      out1fmt("%.4o\n", mask);
    }
  } else {
    int new_mask;
    if (isdigit((unsigned char)*ap)) {
      new_mask = 0;
      do {
        if (*ap >= '8' || *ap < '0') sh_error(illnum, *argptr);
        new_mask = (new_mask << 3) + (*ap - '0');
      } while (*++ap != '\0');
    } else {
      int positions, new_val;
      char op;
      mask = ~mask;
      new_mask = mask;
      positions = 0;
      while (*ap) {
        while (*ap && strchr("augo", *ap)) switch (*ap++) {
            case 'a':
              positions |= 0111;
              break;
            case 'u':
              positions |= 0100;
              break;
            case 'g':
              positions |= 0010;
              break;
            case 'o':
              positions |= 0001;
              break;
          }
        if (!positions) positions = 0111; /* default is a */
        if (!strchr("=+-", op = *ap)) break;
        ap++;
        new_val = 0;
        while (*ap && strchr("rwxugoXs", *ap)) switch (*ap++) {
            case 'r':
              new_val |= 04;
              break;
            case 'w':
              new_val |= 02;
              break;
            case 'x':
              new_val |= 01;
              break;
            case 'u':
              new_val |= mask >> 6;
              break;
            case 'g':
              new_val |= mask >> 3;
              break;
            case 'o':
              new_val |= mask >> 0;
              break;
            case 'X':
              if (mask & 0111) new_val |= 01;
              break;
            case 's': /* ignored */
              break;
          }
        new_val = (new_val & 07) * positions;
        switch (op) {
          case '-':
            new_mask &= ~new_val;
            break;
          case '=':
            new_mask = new_val | (new_mask & ~(positions * 07));
            break;
          case '+':
            new_mask |= new_val;
        }
        if (*ap == ',') {
          positions = 0;
          ap++;
        } else if (!strchr("=+-", *ap))
          break;
      }
      if (*ap) {
        sh_error("Illegal mode: %s", *argptr);
        return 1;
      }
      new_mask = ~new_mask;
    }
    umask(new_mask);
  }
  return 0;
}

/*───────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § the unbourne shell » builtins » ulimit                    ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│─╝
  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. */

enum limtype { SOFT = 0x1, HARD = 0x2 };

static void printlim(enum limtype how, const struct rlimit *limit, const struct limits *l) {
  uint64_t val;
  val = limit->rlim_max;
  if (how & SOFT) val = limit->rlim_cur;
  if (val == RLIM_INFINITY)
    out1fmt("unlimited\n");
  else {
    val /= l->factor;
    out1fmt("%ld\n", (int64_t)val);
  }
}

static int ulimitcmd(int argc, char **argv) {
  static const struct limits limits[] = {{(char *)0, 0, 0, '\0'}};
  int c;
  uint64_t val = 0;
  enum limtype how = SOFT | HARD;
  const struct limits *l;
  int set, all = 0;
  int optc, what;
  struct rlimit limit;
  what = 'f';
  while ((optc = nextopt("HSa")) != '\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->option != what; l++)
    ;
  set = *argptr ? 1 : 0;
  if (set) {
    char *p = *argptr;
    if (all || argptr[1]) sh_error("too many arguments");
    if (strcmp(p, "unlimited") == 0)
      val = RLIM_INFINITY;
    else {
      val = (uint64_t)0;
      while ((c = *p++) >= '0' && c <= '9') {
        val = (val * 10) + (long)(c - '0');
        if (val < (uint64_t)0) break;
      }
      if (c) sh_error("bad number");
      val *= l->factor;
    }
  }
  if (all) {
    for (l = limits; l->name; l++) {
      getrlimit(l->cmd, &limit);
      out1fmt("%-20s ", l->name);
      printlim(how, &limit, l);
    }
    return 0;
  }
  getrlimit(l->cmd, &limit);
  if (set) {
    if (how & HARD) limit.rlim_max = val;
    if (how & SOFT) limit.rlim_cur = val;
    if (setrlimit(l->cmd, &limit) < 0) sh_error("error setting limit (%s)", strerror(errno));
  } else {
    printlim(how, &limit, l);
  }
  return 0;
}

/*
 * Produce a possibly single quoted string suitable as input to the shell.
 * The return string is allocated on the stack.
 */
static char *single_quote(const char *s) {
  char *p;
  STARTSTACKSTR(p);
  do {
    char *q;
    unsigned len;
    len = strchrnul(s, '\'') - s;
    q = p = makestrspace(len + 3, p);
    *q++ = '\'';
    q = mempcpy(q, s, len);
    *q++ = '\'';
    s += len;
    STADJUST(q - p, p);
    len = strspn(s, "'");
    if (!len) break;
    q = p = makestrspace(len + 3, p);
    *q++ = '"';
    q = mempcpy(q, s, len);
    *q++ = '"';
    s += len;
    STADJUST(q - p, p);
  } while (*s);
  USTPUTC(0, p);
  return stackblock();
}

/*
 * Process the shell command line arguments.
 */
static int procargs(int argc, char **argv) {
  int i;
  const char *xminusc;
  char **xargv;
  int login;
  xargv = argv;
  login = xargv[0] && xargv[0][0] == '-';
  arg0 = xargv[0];
  if (argc > 0) xargv++;
  for (i = 0; i < NOPTS; i++) optlist[i] = 2;
  argptr = xargv;
  login |= options(1);
  xargv = argptr;
  xminusc = minusc;
  if (*xargv == NULL) {
    if (xminusc) sh_error("-c requires an argument");
    sflag = 1;
  }
  /* JART: fuck tty check this is documented behavior w/ no args */
  if (iflag == 2 && sflag == 1 /* && isatty(0) && isatty(1) */) iflag = 1;
  if (mflag == 2) mflag = iflag;
  for (i = 0; i < NOPTS; i++)
    if (optlist[i] == 2) optlist[i] = 0;
  /* POSIX 1003.2: first arg after -c cmd is $0, remainder $1... */
  if (xminusc) {
    minusc = *xargv++;
    if (*xargv) goto setarg0;
  } else if (!sflag) {
    setinputfile(*xargv, 0);
  setarg0:
    arg0 = *xargv++;
    commandname = arg0;
  }
  shellparam.p = xargv;
  shellparam.optind = 1;
  shellparam.optoff = -1;
  /* assert(shellparam.malloc == 0 && shellparam.nparam == 0); */
  while (*xargv) {
    shellparam.nparam++;
    xargv++;
  }
  optschanged();
  return login;
}

static void optschanged(void) {
  setinteractive(iflag);
  setjobctl(mflag);
}

static void setoption(int flag, int val) {
  int i;
  for (i = 0; i < NOPTS; i++)
    if (optletters[i] == flag) {
      optlist[i] = val;
      if (val) {
        /* #%$ hack for ksh semantics */
        if (flag == 'V')
          Eflag = 0;
        else if (flag == 'E')
          Vflag = 0;
      }
      return;
    }
  sh_error("Illegal option -%c", flag);
  unreachable;
}

/*
 * Process shell options.  The global variable argptr contains a pointer
 * to the argument list; we advance it past the options.
 */
static int options(int cmdline) {
  char *p;
  int val;
  int c;
  int login = 0;
  if (cmdline) minusc = NULL;
  while ((p = *argptr) != NULL) {
    argptr++;
    if ((c = *p++) == '-') {
      val = 1;
      if (p[0] == '\0' || (p[0] == '-' && p[1] == '\0')) {
        if (!cmdline) {
          /* "-" means turn off -x and -v */
          if (p[0] == '\0') xflag = vflag = 0;
          /* "--" means reset params */
          else if (*argptr == NULL)
            setparam(argptr);
        }
        break; /* "-" or  "--" terminates options */
      }
    } else if (c == '+') {
      val = 0;
    } else {
      argptr--;
      break;
    }
    while ((c = *p++) != '\0') {
      if (c == 'c' && cmdline) {
        minusc = p; /* command is after shell args*/
      } else if (c == 'l' && cmdline) {
        login = 1;
      } else if (c == 'o') {
        minus_o(*argptr, val);
        if (*argptr) argptr++;
      } else {
        setoption(c, val);
      }
    }
  }
  return login;
}

static void minus_o(char *name, int val) {
  int i;
  if (name == NULL) {
    if (val) {
      outstr("Current option settings\n", out1);
      for (i = 0; i < NOPTS; i++) {
        out1fmt("%-16s%s\n", optnames[i], optlist[i] ? "on" : "off");
      }
    } else {
      for (i = 0; i < NOPTS; i++) {
        out1fmt("set %s %s\n", optlist[i] ? "-o" : "+o", optnames[i]);
      }
    }
  } else {
    for (i = 0; i < NOPTS; i++)
      if (equal(name, optnames[i])) {
        optlist[i] = val;
        return;
      }
    sh_error("Illegal option -o %s", name);
  }
}

/*
 * Set the shell parameters.
 */
static 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.optind = 1;
  shellparam.optoff = -1;
}

/*
 * Free the list of positional parameters.
 */
static void freeparam(volatile struct shparam *param) {
  char **ap;
  if (param->malloc) {
    for (ap = param->p; *ap; ap++) ckfree(*ap);
    ckfree(param->p);
  }
}

/*
 * The shift builtin command.
 */
static int shiftcmd(int argc, char **argv) {
  int n;
  char **ap1, **ap2;
  n = 1;
  if (argc > 1) n = number(argv[1]);
  if (n > shellparam.nparam) sh_error("can't shift that many");
  INTOFF;
  shellparam.nparam -= n;
  for (ap1 = shellparam.p; --n >= 0; ap1++) {
    if (shellparam.malloc) ckfree(*ap1);
  }
  ap2 = shellparam.p;
  while ((*ap2++ = *ap1++) != NULL)
    ;
  shellparam.optind = 1;
  shellparam.optoff = -1;
  INTON;
  return 0;
}

/*
 * The set command builtin.
 */
static int setcmd(int argc, char **argv) {
  if (argc == 1) return showvars(nullstr, 0, VUNSET);
  INTOFF;
  options(0);
  optschanged();
  if (*argptr != NULL) {
    setparam(argptr);
  }
  INTON;
  return 0;
}

static void getoptsreset(value) const char *value;
{
  shellparam.optind = number(value) ?: 1;
  shellparam.optoff = -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.
 */
static int getoptscmd(int argc, char **argv) {
  char **optbase;
  if (argc < 3)
    sh_error("Usage: getopts optstring var [arg]");
  else if (argc == 3) {
    optbase = shellparam.p;
    if ((unsigned)shellparam.optind > shellparam.nparam + 1) {
      shellparam.optind = 1;
      shellparam.optoff = -1;
    }
  } else {
    optbase = &argv[3];
    if ((unsigned)shellparam.optind > argc - 2) {
      shellparam.optind = 1;
      shellparam.optoff = -1;
    }
  }
  return getopts(argv[1], argv[2], optbase);
}

static int getopts(char *optstr, char *optvar, char **optfirst) {
  char *p, *q;
  char c = '?';
  int done = 0;
  char s[2];
  char **optnext;
  int ind = shellparam.optind;
  int off = shellparam.optoff;
  shellparam.optind = -1;
  optnext = optfirst + ind - 1;
  if (ind <= 1 || off < 0 || strlen(optnext[-1]) < off)
    p = NULL;
  else
    p = optnext[-1] + off;
  if (p == NULL || *p == '\0') {
    /* Current word is done, advance */
    p = *optnext;
    if (p == NULL || *p != '-' || *++p == '\0') {
    atend:
      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';
        setvar("OPTARG", s, 0);
      } else {
        outfmt(&errout, "Illegal option -%c\n", c);
        (void)unsetvar("OPTARG");
      }
      c = '?';
      goto out;
    }
    if (*++q == ':') q++;
  }
  if (*++q == ':') {
    if (*p == '\0' && (p = *optnext) == NULL) {
      if (optstr[0] == ':') {
        s[0] = c;
        s[1] = '\0';
        setvar("OPTARG", s, 0);
        c = ':';
      } else {
        outfmt(&errout, "No arg for -%c option\n", c);
        (void)unsetvar("OPTARG");
        c = '?';
      }
      goto out;
    }
    if (p == *optnext) optnext++;
    setvar("OPTARG", p, 0);
    p = NULL;
  } else
    setvar("OPTARG", nullstr, 0);
out:
  ind = optnext - optfirst + 1;
  setvarint("OPTIND", ind, VNOFUNC);
  s[0] = c;
  s[1] = '\0';
  setvar(optvar, s, 0);
  shellparam.optoff = p ? p - *(optnext - 1) : -1;
  shellparam.optind = ind;
  return done;
}

/*
 * XXX - should get rid of.  have all builtins use getopt(3).  the
 * library getopt must have the BSD extension function variable "optreset"
 * otherwise it can't be used within the shell safely.
 *
 * 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.
 */
static int nextopt(const char *optstring) {
  char *p;
  const char *q;
  char c;
  if ((p = 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') sh_error("Illegal option -%c", c);
    if (*++q == ':') q++;
  }
  if (*++q == ':') {
    if (*p == '\0' && (p = *argptr++) == NULL) {
      sh_error("No arg for -%c option", c);
    }
    optionarg = p;
    p = NULL;
  }
  optptr = p;
  return c;
}

/* values returned by readtoken */
static int isassignment(const char *p) {
  const char *q = endofname(p);
  if (p == q) return 0;
  return *q == '=';
}

static inline int realeofmark(const char *eofmark) {
  return eofmark && eofmark != FAKEEOFMARK;
}

/*
 * Read and parse a command.  Returns NEOF on end of file.  (NULL is a
 * valid parse tree indicating a blank line.)
 */
static union node *parsecmd(int interact) {
  tokpushback = 0;
  checkkwd = 0;
  heredoclist = 0;
  doprompt = interact;
  if (doprompt) setprompt(doprompt);
  needprompt = 0;
  return list(1);
}

static union node *list(int nlflag) {
  union node *n1, *n2, *n3;
  int tok;
  n1 = NULL;
  for (;;) {
    switch (peektoken()) {
      case TNL:
        if (!(nlflag & 1)) break;
        parseheredoc();
        return n1;
      case TEOF:
        if (!n1 && (nlflag & 1)) n1 = NEOF;
        parseheredoc();
        return n1;
    }
    checkkwd = CHKNL | CHKKWD | CHKALIAS;
    if (nlflag == 2 && tokendlist[peektoken()]) return n1;
    nlflag |= 2;
    n2 = andor();
    tok = readtoken();
    if (tok == TBACKGND) {
      if (n2->type == NPIPE) {
        n2->npipe.backgnd = 1;
      } else {
        if (n2->type != NREDIR) {
          n3 = stalloc(sizeof(struct nredir));
          n3->nredir.n = n2;
          n3->nredir.redirect = NULL;
          n2 = n3;
        }
        n2->type = NBACKGND;
      }
    }
    if (n1 == NULL) {
      n1 = n2;
    } else {
      n3 = (union node *)stalloc(sizeof(struct nbinary));
      n3->type = NSEMI;
      n3->nbinary.ch1 = n1;
      n3->nbinary.ch2 = n2;
      n1 = n3;
    }
    switch (tok) {
      case TNL:
      case TEOF:
        tokpushback++;
        /* fall through */
      case TBACKGND:
      case TSEMI:
        break;
      default:
        if ((nlflag & 1)) synexpect(-1);
        tokpushback++;
        return n1;
    }
  }
}

static union node *andor(void) {
  union node *n1, *n2, *n3;
  int t;
  n1 = pipeline();
  for (;;) {
    if ((t = readtoken()) == TAND) {
      t = NAND;
    } else if (t == TOR) {
      t = NOR;
    } else {
      tokpushback++;
      return n1;
    }
    checkkwd = CHKNL | CHKKWD | CHKALIAS;
    n2 = pipeline();
    n3 = (union node *)stalloc(sizeof(struct nbinary));
    n3->type = t;
    n3->nbinary.ch1 = n1;
    n3->nbinary.ch2 = n2;
    n1 = n3;
  }
}

static union node *pipeline(void) {
  union node *n1, *n2, *pipenode;
  struct nodelist *lp, *prev;
  int negate;
  negate = 0;
  TRACE(("pipeline: entered\n"));
  if (readtoken() == TNOT) {
    negate = !negate;
    checkkwd = CHKKWD | CHKALIAS;
  } else
    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;
      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;
  union node **rpp2;
  int t;
  int savelinno;
  redir = NULL;
  rpp2 = &redir;
  savelinno = plinno;
  switch (readtoken()) {
    default:
      synexpect(-1);
      unreachable;
    case TIF:
      n1 = (union node *)stalloc(sizeof(struct nif));
      n1->type = NIF;
      n1->nif.test = list(0);
      if (readtoken() != TTHEN) synexpect(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;
        n2->nif.test = list(0);
        if (readtoken() != TTHEN) synexpect(TTHEN);
        n2->nif.ifpart = list(0);
      }
      if (lasttoken == TELSE)
        n2->nif.elsepart = list(0);
      else {
        n2->nif.elsepart = NULL;
        tokpushback++;
      }
      t = TFI;
      break;
    case TWHILE:
    case TUNTIL: {
      int got;
      n1 = (union node *)stalloc(sizeof(struct nbinary));
      n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL;
      n1->nbinary.ch1 = list(0);
      if ((got = readtoken()) != TDO) {
        TRACE(("expecting DO got %s %s\n", tokname[got], got == TWORD ? wordtext : ""));
        synexpect(TDO);
      }
      n1->nbinary.ch2 = list(0);
      t = TDONE;
      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.linno = savelinno;
      n1->nfor.var_ = wordtext;
      checkkwd = CHKNL | CHKKWD | CHKALIAS;
      if (readtoken() == TIN) {
        app = &ap;
        while (readtoken() == TWORD) {
          n2 = (union node *)stalloc(sizeof(struct narg));
          n2->type = NARG;
          n2->narg.text = wordtext;
          n2->narg.backquote = backquotelist;
          *app = n2;
          app = &n2->narg.next;
        }
        *app = NULL;
        n1->nfor.args = ap;
        if (lasttoken != TNL && lasttoken != TSEMI) synexpect(-1);
      } else {
        n2 = (union node *)stalloc(sizeof(struct narg));
        n2->type = NARG;
        n2->narg.text = (char *)dolatstr;
        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 != TSEMI) tokpushback++;
      }
      checkkwd = CHKNL | CHKKWD | CHKALIAS;
      if (readtoken() != TDO) synexpect(TDO);
      n1->nfor.body = list(0);
      t = TDONE;
      break;
    case TCASE:
      n1 = (union node *)stalloc(sizeof(struct ncase));
      n1->type = NCASE;
      n1->ncase.linno = savelinno;
      if (readtoken() != TWORD) synexpect(TWORD);
      n1->ncase.expr = n2 = (union node *)stalloc(sizeof(struct narg));
      n2->type = NARG;
      n2->narg.text = wordtext;
      n2->narg.backquote = backquotelist;
      n2->narg.next = NULL;
      checkkwd = CHKNL | CHKKWD | CHKALIAS;
      if (readtoken() != TIN) synexpect(TIN);
      cpp = &n1->ncase.cases;
    next_case:
      checkkwd = CHKNL | CHKKWD;
      t = readtoken();
      while (t != TESAC) {
        if (lasttoken == TLP) readtoken();
        *cpp = cp = (union node *)stalloc(sizeof(struct nclist));
        cp->type = NCLIST;
        app = &cp->nclist.pattern;
        for (;;) {
          *app = ap = (union node *)stalloc(sizeof(struct narg));
          ap->type = NARG;
          ap->narg.text = wordtext;
          ap->narg.backquote = backquotelist;
          if (readtoken() != TPIPE) break;
          app = &ap->narg.next;
          readtoken();
        }
        ap->narg.next = NULL;
        if (lasttoken != TRP) synexpect(TRP);
        cp->nclist.body = list(2);
        cpp = &cp->nclist.next;
        checkkwd = CHKNL | CHKKWD;
        if ((t = readtoken()) != TESAC) {
          if (t != TENDCASE)
            synexpect(TENDCASE);
          else
            goto next_case;
        }
      }
      *cpp = NULL;
      goto redir;
    case TLP:
      n1 = (union node *)stalloc(sizeof(struct nredir));
      n1->type = NSUBSHELL;
      n1->nredir.linno = savelinno;
      n1->nredir.n = list(0);
      n1->nredir.redirect = NULL;
      t = TRP;
      break;
    case TBEGIN:
      n1 = list(0);
      t = TEND;
      break;
    case TWORD:
    case TREDIR:
      tokpushback++;
      return simplecmd();
  }
  if (readtoken() != t) synexpect(t);
redir:
  /* Now check for redirection which may follow command */
  checkkwd = CHKKWD | CHKALIAS;
  rpp = rpp2;
  while (readtoken() == TREDIR) {
    *rpp = n2 = redirnode;
    rpp = &n2->nfile.next;
    parsefname();
  }
  tokpushback++;
  *rpp = NULL;
  if (redir) {
    if (n1->type != NSUBSHELL) {
      n2 = (union node *)stalloc(sizeof(struct nredir));
      n2->type = NREDIR;
      n2->nredir.linno = savelinno;
      n2->nredir.n = n1;
      n1 = n2;
    }
    n1->nredir.redirect = redir;
  }
  return n1;
}

static union node *simplecmd(void) {
  union node *args, **app;
  union node *n = NULL;
  union node *vars, **vpp;
  union node **rpp, *redir;
  int savecheckkwd;
  int savelinno;
  args = NULL;
  app = &args;
  vars = NULL;
  vpp = &vars;
  redir = NULL;
  rpp = &redir;
  savecheckkwd = CHKALIAS;
  savelinno = plinno;
  for (;;) {
    checkkwd = savecheckkwd;
    switch (readtoken()) {
      case TWORD:
        n = (union node *)stalloc(sizeof(struct narg));
        n->type = NARG;
        n->narg.text = wordtext;
        n->narg.backquote = backquotelist;
        if (savecheckkwd && isassignment(wordtext)) {
          *vpp = n;
          vpp = &n->narg.next;
        } else {
          *app = n;
          app = &n->narg.next;
          savecheckkwd = 0;
        }
        break;
      case TREDIR:
        *rpp = n = redirnode;
        rpp = &n->nfile.next;
        parsefname(); /* read name of redirection file */
        break;
      case TLP:
        if (args && app == &args->narg.next && !vars && !redir) {
          struct builtincmd *bcmd;
          const char *name;
          /* We have a function */
          if (readtoken() != TRP) synexpect(TRP);
          name = n->narg.text;
          if (!goodname(name) || ((bcmd = find_builtin(name)) && bcmd->flags & BUILTIN_SPECIAL))
            synerror("Bad function name");
          n->type = NDEFUN;
          checkkwd = CHKNL | CHKKWD | CHKALIAS;
          n->ndefun.text = n->narg.text;
          n->ndefun.linno = plinno;
          n->ndefun.body = command();
          return n;
        }
        /* fall through */
      default:
        tokpushback++;
        goto out;
    }
  }
out:
  *app = NULL;
  *vpp = NULL;
  *rpp = NULL;
  n = (union node *)stalloc(sizeof(struct ncmd));
  n->type = NCMD;
  n->ncmd.linno = savelinno;
  n->ncmd.args = args;
  n->ncmd.assign = vars;
  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 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;
  if (n->type == NHERE) checkkwd = CHKEOFMARK;
  if (readtoken() != TWORD) synexpect(-1);
  if (n->type == NHERE) {
    struct heredoc *here = heredoc;
    struct heredoc *p;
    if (quoteflag == 0) n->type = NXHERE;
    TRACE(("Here document %d\n", n->type));
    rmescapes(wordtext, 0);
    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;
  here = heredoclist;
  heredoclist = 0;
  while (here) {
    if (needprompt) {
      setprompt(2);
    }
    if (here->here->type == NHERE)
      readtoken1(pgetc(), SQSYNTAX, here->eofmark, here->striptabs);
    else
      readtoken1(pgetc_eatbnl(), DQSYNTAX, here->eofmark, here->striptabs);
    n = (union node *)stalloc(sizeof(struct narg));
    n->narg.type = NARG;
    n->narg.next = NULL;
    n->narg.text = wordtext;
    n->narg.backquote = backquotelist;
    here->here->nhere.doc = n;
    here = here->next;
  }
}

static int peektoken(void) {
  int t;
  t = readtoken();
  tokpushback++;
  return (t);
}

static int readtoken(void) {
  int t;
  int kwd = checkkwd;
top:
  t = xxreadtoken();
  /*
   * eat newlines
   */
  if (kwd & CHKNL) {
    while (t == TNL) {
      parseheredoc();
      t = xxreadtoken();
    }
  }
  if (t != TWORD || quoteflag) {
    goto out;
  }
  /*
   * check for keywords
   */
  if (kwd & CHKKWD) {
    const char *const *pp;
    const int KWDOFFSET = 13;
    if ((pp = findkwd(wordtext))) {
      lasttoken = t = pp - parsekwd + KWDOFFSET;
      TRACE(("keyword %s recognized\n", tokname[t]));
      goto out;
    }
  }
  if (checkkwd & CHKALIAS) {
    struct alias *ap;
    if ((ap = lookupalias(wordtext, 1)) != NULL) {
      if (*ap->val) {
        pushstring(ap->val, ap);
      }
      goto top;
    }
  }
out:
  checkkwd = 0;
  return (t);
}

static void nlprompt(void) {
  plinno++;
  if (doprompt) setprompt(2);
}

static void nlnoprompt(void) {
  plinno++;
  needprompt = doprompt;
}

/*
 * 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.
 *
 * [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.]
 */
static int xxreadtoken(void) {
#define RETURN(token) return lasttoken = token
  int c;
  if (tokpushback) {
    tokpushback = 0;
    return lasttoken;
  }
  if (needprompt) {
    setprompt(2);
  }
  for (;;) { /* until token or start of word found */
    c = pgetc_eatbnl();
    switch (c) {
      case ' ':
      case '\t':
      case PEOA:
        continue;
      case '#':
        while ((c = pgetc()) != '\n' && c != PEOF)
          ;
        pungetc();
        continue;
      case '\n':
        nlnoprompt();
        RETURN(TNL);
      case PEOF:
        RETURN(TEOF);
      case '&':
        if (pgetc_eatbnl() == '&') RETURN(TAND);
        pungetc();
        RETURN(TBACKGND);
      case '|':
        if (pgetc_eatbnl() == '|') RETURN(TOR);
        pungetc();
        RETURN(TPIPE);
      case ';':
        if (pgetc_eatbnl() == ';') RETURN(TENDCASE);
        pungetc();
        RETURN(TSEMI);
      case '(':
        RETURN(TLP);
      case ')':
        RETURN(TRP);
    }
    break;
  }
  return readtoken1(c, BASESYNTAX, (char *)NULL, 0);
#undef RETURN
}

static int pgetc_eatbnl(void) {
  int c;
  while ((c = pgetc()) == '\\') {
    if (pgetc2() != '\n') {
      pungetc();
      break;
    }
    nlprompt();
  }
  return c;
}

static int pgetc_top(struct synstack *stack) {
  return stack->syntax == SQSYNTAX ? pgetc() : pgetc_eatbnl();
}

static void synstack_push(struct synstack **stack, struct synstack *next, const char *syntax) {
  memset(next, 0, sizeof(*next));
  next->syntax = syntax;
  next->next = *stack;
  (*stack)->prev = next;
  *stack = next;
}

static void synstack_pop(struct synstack **stack) {
  *stack = (*stack)->next;
}

/*
 * 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 CHECKEND()  \
  {                 \
    goto checkend;  \
  checkend_return:; \
  }
#define PARSEREDIR()  \
  {                   \
    goto parseredir;  \
  parseredir_return:; \
  }
#define PARSESUB()  \
  {                 \
    goto parsesub;  \
  parsesub_return:; \
  }
#define PARSEBACKQOLD()  \
  {                      \
    oldstyle = 1;        \
    goto parsebackq;     \
  parsebackq_oldreturn:; \
  }
#define PARSEBACKQNEW()  \
  {                      \
    oldstyle = 0;        \
    goto parsebackq;     \
  parsebackq_newreturn:; \
  }
#define PARSEARITH()  \
  {                   \
    goto parsearith;  \
  parsearith_return:; \
  }

static int readtoken1(int firstc, char const *syntax, char *eofmark, int striptabs) {
  int c = firstc;
  char *out;
  unsigned len;
  struct nodelist *bqlist;
  int quotef;
  int oldstyle;
  /* syntax stack */
  struct synstack synbase = {.syntax = syntax};
  struct synstack *synstack = &synbase;
  if (syntax == DQSYNTAX) synstack->dblquote = 1;
  quotef = 0;
  bqlist = NULL;
  STARTSTACKSTR(out);
loop : {                   /* for each line, until end of word */
  CHECKEND();              /* set c to PEOF if at end of here document */
  for (;;) {               /* until end of line or end of word */
    CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */
    switch (synstack->syntax[c]) {
      case CNL: /* '\n' */
        if (synstack->syntax == BASESYNTAX && !synstack->varnest)
          goto endword; /* exit outer loop */
        USTPUTC(c, out);
        nlprompt();
        c = pgetc_top(synstack);
        goto loop; /* continue outer loop */
      case CWORD:
        USTPUTC(c, out);
        break;
      case CCTL:
        if ((!eofmark) | synstack->dblquote | synstack->varnest) USTPUTC(CTLESC, out);
        USTPUTC(c, out);
        break;
      /* backslash */
      case CBACK:
        c = pgetc2();
        if (c == PEOF) {
          USTPUTC(CTLESC, out);
          USTPUTC('\\', out);
          pungetc();
        } else {
          if (synstack->dblquote && c != '\\' && c != '`' && c != '$' &&
              (c != '"' || (eofmark != NULL && !synstack->varnest)) &&
              (c != '}' || !synstack->varnest)) {
            USTPUTC(CTLESC, out);
            USTPUTC('\\', out);
          }
          USTPUTC(CTLESC, out);
          USTPUTC(c, out);
          quotef++;
        }
        break;
      case CSQUOTE:
        synstack->syntax = SQSYNTAX;
      quotemark:
        if (eofmark == NULL) {
          USTPUTC(CTLQUOTEMARK, out);
        }
        break;
      case CDQUOTE:
        synstack->syntax = DQSYNTAX;
        synstack->dblquote = 1;
      toggledq:
        if (synstack->varnest) synstack->innerdq ^= 1;
        goto quotemark;
      case CENDQUOTE:
        if (eofmark && !synstack->varnest) {
          USTPUTC(c, out);
          break;
        }
        if (synstack->dqvarnest == 0) {
          synstack->syntax = BASESYNTAX;
          synstack->dblquote = 0;
        }
        quotef++;
        if (c == '"') goto toggledq;
        goto quotemark;
      case CVAR:    /* '$' */
        PARSESUB(); /* parse substitution */
        break;
      case CENDVAR: /* '}' */
        if (!synstack->innerdq && synstack->varnest > 0) {
          if (!--synstack->varnest && synstack->varpushed)
            synstack_pop(&synstack);
          else if (synstack->dqvarnest > 0)
            synstack->dqvarnest--;
          USTPUTC(CTLENDVAR, out);
        } else {
          USTPUTC(c, out);
        }
        break;
      case CLP: /* '(' in arithmetic */
        synstack->parenlevel++;
        USTPUTC(c, out);
        break;
      case CRP: /* ')' in arithmetic */
        if (synstack->parenlevel > 0) {
          USTPUTC(c, out);
          --synstack->parenlevel;
        } else {
          if (pgetc_eatbnl() == ')') {
            USTPUTC(CTLENDARI, out);
            synstack_pop(&synstack);
          } else {
            /*
             * unbalanced parens
             *  (don't 2nd guess - no error)
             */
            pungetc();
            USTPUTC(')', out);
          }
        }
        break;
      case CBQUOTE: /* '`' */
        if (checkkwd & CHKEOFMARK) {
          USTPUTC('`', out);
          break;
        }
        PARSEBACKQOLD();
        break;
      case CEOF:
        goto endword; /* exit outer loop */
      case CIGN:
        break;
      default:
        if (synstack->varnest == 0) goto endword; /* exit outer loop */
        if (c != PEOA) {
          USTPUTC(c, out);
        }
    }
    c = pgetc_top(synstack);
  }
}
endword:
  if (synstack->syntax == ARISYNTAX) synerror("Missing '))'");
  if (synstack->syntax != BASESYNTAX && eofmark == NULL) synerror("Unterminated quoted string");
  if (synstack->varnest != 0) {
    /* { */
    synerror("Missing '}'");
  }
  USTPUTC('\0', out);
  len = out - (char *)stackblock();
  out = stackblock();
  if (eofmark == NULL) {
    if ((c == '>' || c == '<') && quotef == 0 && len <= 2 && (*out == '\0' || is_digit(*out))) {
      PARSEREDIR();
      return lasttoken = TREDIR;
    } else {
      pungetc();
    }
  }
  quoteflag = quotef;
  backquotelist = bqlist;
  grabstackblock(len);
  wordtext = out;
  return lasttoken = TWORD;
  /* end of readtoken routine */

  /*
   * 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.
   */
checkend : {
  if (realeofmark(eofmark)) {
    int markloc;
    char *p;
    if (c == PEOA) {
      c = pgetc2();
    }
    if (striptabs) {
      while (c == '\t') {
        c = pgetc2();
      }
    }
    markloc = out - (char *)stackblock();
    for (p = eofmark; STPUTC(c, out), *p; p++) {
      if (c != *p) goto more_heredoc;
      c = pgetc2();
    }
    if (c == '\n' || c == PEOF) {
      c = PEOF;
      nlnoprompt();
    } else {
      int len2;
    more_heredoc:
      p = (char *)stackblock() + markloc + 1;
      len2 = out - p;
      if (len2) {
        len2 -= c < 0;
        c = p[-1];
        if (len2) {
          char *str;
          str = alloca(len2 + 1);
          *(char *)mempcpy(str, p, len2) = 0;
          pushstring(str, NULL);
        }
      }
    }
    STADJUST((char *)stackblock() + markloc - out, out);
  }
  goto checkend_return;
}

  /*
   * 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.
   */
parseredir : {
  char fd = *out;
  union node *np;
  np = (union node *)stalloc(sizeof(struct nfile));
  if (c == '>') {
    np->nfile.fd = 1;
    c = pgetc_eatbnl();
    if (c == '>')
      np->type = NAPPEND;
    else if (c == '|')
      np->type = NCLOBBER;
    else if (c == '&')
      np->type = NTOFD;
    else {
      np->type = NTO;
      pungetc();
    }
  } else { /* c == '<' */
    np->nfile.fd = 0;
    switch (c = pgetc_eatbnl()) {
      case '<':
        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_eatbnl()) == '-') {
          heredoc->striptabs = 1;
        } else {
          heredoc->striptabs = 0;
          pungetc();
        }
        break;
      case '&':
        np->type = NFROMFD;
        break;
      case '>':
        np->type = NFROMTO;
        break;
      default:
        np->type = NFROM;
        pungetc();
        break;
    }
  }
  if (fd != '\0') np->nfile.fd = digit_val(fd);
  redirnode = np;
  goto parseredir_return;
}

  /*
   * Parse a substitution.  At this point, we have read the dollar sign
   * and nothing else.
   */
parsesub : {
  int subtype;
  int typeloc;
  char *p;
  static const char types[] = "}-+?=";
  c = pgetc_eatbnl();
  if ((checkkwd & CHKEOFMARK) || c <= PEOA ||
      (c != '(' && c != '{' && !is_name(c) && !is_special(c))) {
    USTPUTC('$', out);
    pungetc();
  } else if (c == '(') { /* $(command) or $((arith)) */
    if (pgetc_eatbnl() == '(') {
      PARSEARITH();
    } else {
      pungetc();
      PARSEBACKQNEW();
    }
  } else {
    const char *newsyn = synstack->syntax;
    USTPUTC(CTLVAR, out);
    typeloc = out - (char *)stackblock();
    STADJUST(1, out);
    subtype = VSNORMAL;
    if (likely(c == '{')) {
      c = pgetc_eatbnl();
      subtype = 0;
    }
  varname:
    if (is_name(c)) {
      do {
        STPUTC(c, out);
        c = pgetc_eatbnl();
      } while (is_in_name(c));
    } else if (is_digit(c)) {
      do {
        STPUTC(c, out);
        c = pgetc_eatbnl();
      } while (is_digit(c));
    } else if (c != '}') {
      int cc = c;
      c = pgetc_eatbnl();
      if (!subtype && cc == '#') {
        subtype = VSLENGTH;
        if (c == '_' || isalnum(c)) goto varname;
        cc = c;
        c = pgetc_eatbnl();
        if (cc == '}' || c != '}') {
          pungetc();
          subtype = 0;
          c = cc;
          cc = '#';
        }
      }
      if (!is_special(cc)) {
        if (subtype == VSLENGTH) subtype = 0;
        goto badsub;
      }
      USTPUTC(cc, out);
    } else
      goto badsub;
    if (subtype == 0) {
      int cc = c;
      switch (c) {
        case ':':
          subtype = VSNUL;
          c = pgetc_eatbnl();
          /*FALLTHROUGH*/
        default:
          p = strchr(types, c);
          if (p == NULL) break;
          subtype |= p - types + VSNORMAL;
          break;
        case '%':
        case '#':
          subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT;
          c = pgetc_eatbnl();
          if (c == cc)
            subtype++;
          else
            pungetc();
          newsyn = BASESYNTAX;
          break;
      }
    } else {
    badsub:
      pungetc();
    }
    if (newsyn == ARISYNTAX) newsyn = DQSYNTAX;
    if ((newsyn != synstack->syntax || synstack->innerdq) && subtype != VSNORMAL) {
      synstack_push(&synstack, synstack->prev ?: alloca(sizeof(*synstack)), newsyn);
      synstack->varpushed++;
      synstack->dblquote = newsyn != BASESYNTAX;
    }
    *((char *)stackblock() + typeloc) = subtype;
    if (subtype != VSNORMAL) {
      synstack->varnest++;
      if (synstack->dblquote) synstack->dqvarnest++;
    }
    STPUTC('=', out);
  }
  goto parsesub_return;
}

  /*
   * Called to parse command substitutions.  Newstyle is set if the command
   * is enclosed inside $(...); nlpp is a pointer to the head of the linked
   * list of commands (passed by reference), and savelen is the number of
   * characters on the top of the stack which must be preserved.
   */
parsebackq : {
  struct nodelist **nlpp;
  union node *n;
  char *str;
  unsigned savelen;
  struct heredoc *saveheredoclist;
  int uninitialized_var(saveprompt);
  str = NULL;
  savelen = out - (char *)stackblock();
  if (savelen > 0) {
    str = alloca(savelen);
    memcpy(str, stackblock(), savelen);
  }
  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 *pout;
    int pc;
    unsigned psavelen;
    char *pstr;
    STARTSTACKSTR(pout);
    for (;;) {
      if (needprompt) {
        setprompt(2);
      }
      switch (pc = pgetc_eatbnl()) {
        case '`':
          goto done;
        case '\\':
          pc = pgetc_eatbnl();
          if (pc != '\\' && pc != '`' && pc != '$' && (!synstack->dblquote || pc != '"'))
            STPUTC('\\', pout);
          if (pc > PEOA) {
            break;
          }
          /* fall through */
        case PEOF:
        case PEOA:
          synerror("EOF in backquote substitution");
        case '\n':
          nlnoprompt();
          break;
        default:
          break;
      }
      STPUTC(pc, pout);
    }
  done:
    STPUTC('\0', pout);
    psavelen = pout - (char *)stackblock();
    if (psavelen > 0) {
      pstr = grabstackstr(pout);
      setinputstring(pstr);
    }
  }
  nlpp = &bqlist;
  while (*nlpp) nlpp = &(*nlpp)->next;
  *nlpp = (struct nodelist *)stalloc(sizeof(struct nodelist));
  (*nlpp)->next = NULL;
  saveheredoclist = heredoclist;
  heredoclist = NULL;
  if (oldstyle) {
    saveprompt = doprompt;
    doprompt = 0;
  }
  n = list(2);
  if (oldstyle)
    doprompt = saveprompt;
  else {
    if (readtoken() != TRP) synexpect(TRP);
    setinputstring(nullstr);
    parseheredoc();
  }
  heredoclist = saveheredoclist;
  (*nlpp)->n = n;
  /* Start reading from old file again. */
  popfile();
  /* Ignore any pushed back tokens left from the backquote parsing. */
  if (oldstyle) tokpushback = 0;
  out = growstackto(savelen + 1);
  if (str) {
    memcpy(out, str, savelen);
    STADJUST(savelen, out);
  }
  USTPUTC(CTLBACKQ, out);
  if (oldstyle)
    goto parsebackq_oldreturn;
  else
    goto parsebackq_newreturn;
}

/*
 * Parse an arithmetic expansion (indicate start of one and set state)
 */
parsearith : {
  synstack_push(&synstack, synstack->prev ?: alloca(sizeof(*synstack)), ARISYNTAX);
  synstack->dblquote = 1;
  USTPUTC(CTLARI, out);
  goto parsearith_return;
}

} /* end of readtoken */

static void setprompt(int which) {
  struct stackmark smark;
  int show;
  needprompt = 0;
  whichprompt = which;
  show = 1;
  if (show) {
    pushstackmark(&smark, stackblocksize());
    outstr(getprompt(NULL), out2);
    popstackmark(&smark);
  }
}

static const char *expandstr(const char *ps) {
  union node n;
  int saveprompt;
  /* XXX Fix (char *) cast. */
  setinputstring((char *)ps);
  saveprompt = doprompt;
  doprompt = 0;
  readtoken1(pgetc_eatbnl(), DQSYNTAX, FAKEEOFMARK, 0);
  doprompt = saveprompt;
  popfile();
  n.narg.type = NARG;
  n.narg.next = NULL;
  n.narg.text = wordtext;
  n.narg.backquote = backquotelist;
  expandarg(&n, NULL, EXP_QUOTED);
  return stackblock();
}

/*
 * called by editline -- any expansions to the prompt
 *    should be added here.
 */
static const char *getprompt(void *unused) {
  const char *prompt;
  switch (whichprompt) {
    default:
    case 0:
      return nullstr;
    case 1:
      prompt = ps1val();
      break;
    case 2:
      prompt = ps2val();
      break;
  }
  return expandstr(prompt);
}

const char *const *findkwd(const char *s) {
  return findstring(s, parsekwd, sizeof(parsekwd) / sizeof(const char *));
}

/*
 * 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.
 */
static void redirect(union node *redir, int flags) {
  union node *n;
  struct redirtab *sv;
  int i;
  int fd;
  int newfd;
  int *p;
  if (!redir) return;
  sv = NULL;
  INTOFF;
  if (likely(flags & REDIR_PUSH)) sv = redirlist;
  n = redir;
  do {
    newfd = openredirect(n);
    if (newfd < -1) continue;
    fd = n->nfile.fd;
    if (sv) {
      p = &sv->renamed[fd];
      i = *p;
      if (likely(i == EMPTY)) {
        i = CLOSED;
        if (fd != newfd) {
          i = savefd(fd, fd);
          fd = -1;
        }
      }
      if (i == newfd) /* Can only happen if i == newfd == CLOSED */
        i = REALLY_CLOSED;
      *p = i;
    }
    if (fd == newfd) continue;
    dupredirect(n, newfd);
  } while ((n = n->nfile.next));
  INTON;
  if (flags & REDIR_SAVEFD2 && sv->renamed[2] >= 0) preverrout.fd = sv->renamed[2];
}

static int openredirect(union node *redir) {
  struct stat sb;
  char *fname;
  int f;
  switch (redir->nfile.type) {
    case NFROM:
      fname = redir->nfile.expfname;
      if ((f = open(fname, O_RDONLY, 0)) < 0) goto eopen;
      break;
    case NFROMTO:
      fname = redir->nfile.expfname;
      if ((f = open(fname, O_RDWR | O_CREAT, 0666)) < 0) goto ecreate;
      break;
    case NTO:
      /* Take care of noclobber mode. */
      if (Cflag) {
        fname = redir->nfile.expfname;
        if (stat(fname, &sb) < 0) {
          if ((f = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0666)) < 0) goto ecreate;
        } else if (!S_ISREG(sb.st_mode)) {
          if ((f = open(fname, O_WRONLY, 0666)) < 0) goto ecreate;
          if (!fstat(f, &sb) && S_ISREG(sb.st_mode)) {
            close(f);
            errno = EEXIST;
            goto ecreate;
          }
        } else {
          errno = EEXIST;
          goto ecreate;
        }
        break;
      }
      /* FALLTHROUGH */
    case NCLOBBER:
      fname = redir->nfile.expfname;
      if ((f = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0) goto ecreate;
      break;
    case NAPPEND:
      fname = redir->nfile.expfname;
      if ((f = open(fname, O_WRONLY | O_CREAT | O_APPEND, 0666)) < 0) goto ecreate;
      break;
    case NTOFD:
    case NFROMFD:
      f = redir->ndup.dupfd;
      if (f == redir->nfile.fd) f = -2;
      break;
    default:
      /* Fall through to eliminate warning. */
    case NHERE:
    case NXHERE:
      f = openhere(redir);
      break;
  }
  return f;
ecreate:
  sh_error("cannot create %s: %s", fname, errmsg(errno, E_CREAT));
eopen:
  sh_error("cannot open %s: %s", fname, errmsg(errno, E_OPEN));
}

static void dupredirect(union node *redir, int f) {
  int fd = redir->nfile.fd;
  int err = 0;
  if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) {
    /* if not ">&-" */
    if (f >= 0) {
      if (dup2(f, fd) < 0) {
        err = errno;
        goto err;
      }
      return;
    }
    f = fd;
  } else if (dup2(f, fd) < 0) {
    err = errno;
  }
  close(f);
  if (err < 0) goto err;
  return;
err:
  sh_error("%ld: %s", f, strerror(err));
}

/*
 * 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 int64_t openhere(union node *redir) {
  char *p;
  int pip[2];
  unsigned len = 0;
  if (pipe(pip) < 0) sh_error("Pipe call failed");
  p = redir->nhere.doc->narg.text;
  if (redir->type == NXHERE) {
    expandarg(redir->nhere.doc, NULL, EXP_QUOTED);
    p = stackblock();
  }
  len = strlen(p);
  if (len <= PIPESIZE) {
    xwrite(pip[1], p, len);
    goto out;
  }
  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(SIGPIPE, SIG_DFL);
    xwrite(pip[1], p, len);
    _exit(0);
  }
out:
  close(pip[1]);
  return pip[0];
}

/*
 * Undo the effects of the last redirection.
 */
static void popredir(int drop) {
  struct redirtab *rp;
  int i;
  INTOFF;
  rp = redirlist;
  for (i = 0; i < 10; i++) {
    switch (rp->renamed[i]) {
      case CLOSED:
        if (!drop) close(i);
        break;
      case EMPTY:
      case REALLY_CLOSED:
        break;
      default:
        if (!drop) dup2(rp->renamed[i], i);
        close(rp->renamed[i]);
        break;
    }
  }
  redirlist = rp->next;
  ckfree(rp);
  INTON;
}

/*
 * Move a file descriptor to > 10.  Invokes sh_error on error unless
 * the original file dscriptor is not open.
 */
static int savefd(int from, int ofd) {
  int newfd;
  int err;
  newfd = fcntl(from, F_DUPFD, 10);
  err = newfd < 0 ? errno : 0;
  if (err != EBADF) {
    close(ofd);
    if (err) {
      sh_error("%d: %s", from, strerror(err));
    } else {
      fcntl(newfd, F_SETFD, FD_CLOEXEC);
    }
  }
  return newfd;
}

static int redirectsafe(union node *redir, int flags) {
  int err;
  volatile int saveint;
  struct jmploc *volatile savehandler = handler;
  struct jmploc jmploc;
  SAVEINT(saveint);
  if (!(err = setjmp(jmploc.loc) * 2)) {
    handler = &jmploc;
    redirect(redir, flags);
  }
  handler = savehandler;
  if (err && exception != EXERROR) longjmp(handler->loc, 1);
  RESTOREINT(saveint);
  return err;
}

static void unwindredir(struct redirtab *stop) {
  while (redirlist != stop) popredir(0);
}

static struct redirtab *pushredir(union node *redir) {
  struct redirtab *sv;
  struct redirtab *q;
  int i;
  q = redirlist;
  if (!redir) goto out;
  sv = ckmalloc(sizeof(struct redirtab));
  sv->next = q;
  redirlist = sv;
  for (i = 0; i < 10; i++) sv->renamed[i] = EMPTY;
out:
  return q;
}

/*
 * The trap builtin.
 */
static int trapcmd(int argc, char **argv) {
  char *action;
  char **ap;
  int signo;
  nextopt(nullstr);
  ap = argptr;
  if (!*ap) {
    for (signo = 0; signo < NSIG; signo++) {
      if (trap[signo] != NULL) {
        out1fmt("trap -- %s %s\n", single_quote(trap[signo]), strsignal(signo));
      }
    }
    return 0;
  }
  if (!ap[1] || decode_signum(*ap) >= 0)
    action = NULL;
  else
    action = *ap++;
  while (*ap) {
    if ((signo = decode_signal(*ap, 0)) < 0) {
      outfmt(out2, "trap: %s: bad trap\n", *ap);
      return 1;
    }
    INTOFF;
    if (action) {
      if (action[0] == '-' && action[1] == '\0')
        action = NULL;
      else {
        if (*action) trapcnt++;
        action = savestr(action);
      }
    }
    if (trap[signo]) {
      if (*trap[signo]) trapcnt--;
      ckfree(trap[signo]);
    }
    trap[signo] = action;
    if (signo != 0) setsignal(signo);
    INTON;
    ap++;
  }
  return 0;
}

/*
 * Clear traps on a fork.
 */
static void clear_traps(void) {
  char **tp;
  INTOFF;
  for (tp = trap; tp < &trap[NSIG]; tp++) {
    if (*tp && **tp) { /* trap not NULL or SIG_IGN */
      ckfree(*tp);
      *tp = NULL;
      if (tp != &trap[0]) setsignal(tp - trap);
    }
  }
  trapcnt = 0;
  INTON;
}

/*
 * Set the signal handler for the specified signal.  The routine figures
 * out what it should be set to.
 */
static void setsignal(int signo) {
  int action;
  int lvforked;
  char *t, tsig;
  struct sigaction act;
  lvforked = vforked;
  if ((t = trap[signo]) == NULL)
    action = S_DFL;
  else if (*t != '\0')
    action = S_CATCH;
  else
    action = S_IGN;
  if (rootshell && action == S_DFL && !lvforked) {
    if (signo == SIGINT) {
      if (iflag || minusc || sflag == 0) action = S_CATCH;
    } else if (signo == SIGQUIT || signo == SIGTERM) {
      if (iflag) action = S_IGN;
    } else if (signo == SIGTSTP || signo == SIGTTOU) {
      if (mflag) action = S_IGN;
    }
  }
  if (signo == SIGCHLD) action = S_CATCH;
  t = &sigmode[signo - 1];
  tsig = *t;
  if (tsig == 0) {
    /*
     * current setting unknown
     */
    if (sigaction(signo, 0, &act) == -1) {
      /*
       * Pretend it worked; maybe we should give a warning
       * here, but other shells don't. We don't alter
       * sigmode, so that we retry every time.
       */
      return;
    }
    if (act.sa_handler == SIG_IGN) {
      if (mflag && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU)) {
        tsig = S_IGN; /* don't hard ignore these */
      } else
        tsig = S_HARD_IGN;
    } else {
      tsig = S_RESET; /* force to be set */
    }
  }
  if (tsig == S_HARD_IGN || tsig == action) return;
  switch (action) {
    case S_CATCH:
      act.sa_handler = onsig;
      break;
    case S_IGN:
      act.sa_handler = SIG_IGN;
      break;
    default:
      act.sa_handler = SIG_DFL;
  }
  if (!lvforked) *t = action;
  act.sa_flags = 0;
  sigfillset(&act.sa_mask);
  sigaction(signo, &act, 0);
}

/*
 * Ignore a signal.
 */
static void ignoresig(int signo) {
  if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) {
    signal(signo, SIG_IGN);
  }
  if (!vforked) sigmode[signo - 1] = S_HARD_IGN;
}

/*
 * Signal handler.
 */
static void onsig(int signo) {
  if (vforked) return;
  if (signo == SIGCHLD) {
    gotsigchld = 1;
    if (!trap[SIGCHLD]) return;
  }
  gotsig[signo - 1] = 1;
  pending_sig = signo;
  if (signo == SIGINT && !trap[SIGINT]) {
    if (!suppressint) onint();
    intpending = 1;
  }
}

/*
 * Called to execute a trap.  Perhaps we should avoid entering new trap
 * handlers while we are executing a trap handler.
 */
static void dotrap(void) {
  char *p;
  char *q;
  int i;
  int status, last_status;
  if (!pending_sig) return;
  status = savestatus;
  last_status = status;
  if (likely(status < 0)) {
    status = exitstatus;
    savestatus = status;
  }
  pending_sig = 0;
  barrier();
  for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) {
    if (!*q) continue;
    if (evalskip) {
      pending_sig = i + 1;
      break;
    }
    *q = 0;
    p = trap[i + 1];
    if (!p) continue;
    evalstring(p, 0);
    if (evalskip != SKIPFUNC) exitstatus = status;
  }
  savestatus = last_status;
}

/*
 * Controls whether the shell is interactive or not.
 */
static void setinteractive(int on) {
  static int is_interactive;
  if (++on == is_interactive) return;
  is_interactive = on;
  setsignal(SIGINT);
  setsignal(SIGQUIT);
  setsignal(SIGTERM);
}

/*
 * Called to exit the shell.
 */
wontreturn static void exitshell(void) {
  struct jmploc loc;
  char *p;
  savestatus = exitstatus;
  TRACE(("pid %d, exitshell(%d)\n", getpid(), savestatus));
  if (setjmp(loc.loc)) goto out;
  handler = &loc;
  if ((p = trap[0])) {
    trap[0] = NULL;
    evalskip = 0;
    evalstring(p, 0);
  }
out:
  /*
   * Disable job control so that whoever had the foreground before we
   * started can get it back.
   */
  if (likely(!setjmp(loc.loc))) setjobctl(0);
  flushall();
  _exit(savestatus);
}

static int decode_signal(const char *string, int minsig) {
  int signo;
  signo = decode_signum(string);
  if (signo >= 0) return signo;
  for (signo = minsig; signo < NSIG; signo++) {
    if (!strcasecmp(string, strsignal(signo))) {
      return signo;
    }
  }
  return -1;
}

static void sigblockall(sigset_t *oldmask) {
  sigset_t mask;
  sigfillset(&mask);
  sigprocmask(SIG_SETMASK, &mask, oldmask);
}

#define PF(f, func)                                \
  {                                                \
    switch ((char *)param - (char *)array) {       \
      default:                                     \
        (void)printf(f, array[0], array[1], func); \
        break;                                     \
      case sizeof(*param):                         \
        (void)printf(f, array[0], func);           \
        break;                                     \
      case 0:                                      \
        (void)printf(f, func);                     \
        break;                                     \
    }                                              \
  }

#define ASPF(sp, f, func)                                 \
  ({                                                      \
    int ret;                                              \
    switch ((char *)param - (char *)array) {              \
      default:                                            \
        ret = xasprintf(sp, f, array[0], array[1], func); \
        break;                                            \
      case sizeof(*param):                                \
        ret = xasprintf(sp, f, array[0], func);           \
        break;                                            \
      case 0:                                             \
        ret = xasprintf(sp, f, func);                     \
        break;                                            \
    }                                                     \
    ret;                                                  \
  })

static int print_escape_str(const char *f, int *param, int *array, char *s) {
  struct stackmark smark;
  char *p, *q;
  int done;
  int len;
  int total;
  setstackmark(&smark);
  done = conv_escape_str(s, &q);
  p = stackblock();
  len = q - p;
  total = len - 1;
  q[-1] = (!!((f[1] - 's') | done) - 1) & f[2];
  total += !!q[-1];
  if (f[1] == 's') goto easy;
  p = makestrspace(len, q);
  memset(p, 'X', total);
  p[total] = 0;
  q = stackblock();
  total = ASPF(&p, f, p);
  len = strchrnul(p, 'X') - p;
  memcpy(p + len, q, strspn(p + len, "X"));
easy:
  outmem(p, total, out1);
  popstackmark(&smark);
  return done;
}

static int printfcmd(int argc, char *argv[]) {
  static const char kSkip1[] = "#-+ 0";
  static const char kSkip2[] = "*0123456789";
  char *fmt;
  char *format;
  int ch;
  rval = 0;
  nextopt(nullstr);
  argv = argptr;
  format = *argv;
  if (!format) sh_error("usage: printf format [arg ...]");
  gargv = ++argv;
  do {
    /*
     * 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.
     */
    /* find next format specification */
    for (fmt = format; (ch = *fmt++);) {
      char *start;
      char nextch;
      int array[2];
      int *param;
      if (ch == '\\') {
        int c_ch;
        fmt = conv_escape(fmt, &c_ch);
        ch = c_ch;
        goto pc;
      }
      if (ch != '%' || (*fmt == '%' && (++fmt || 1))) {
      pc:
        outc(ch, out1);
        continue;
      }
      /* Ok - we've found a format specification,
         Save its address for a later printf(). */
      start = fmt - 1;
      param = array;
      /* skip to field width */
      fmt += strspn(fmt, kSkip1);
      if (*fmt == '*') {
        ++fmt;
        *param++ = getuintmax(1);
      } else {
        /* skip to possible '.',
         * get following precision
         */
        fmt += strspn(fmt, kSkip2);
      }
      if (*fmt == '.') {
        ++fmt;
        if (*fmt == '*') {
          ++fmt;
          *param++ = getuintmax(1);
        } else
          fmt += strspn(fmt, kSkip2);
      }
      ch = *fmt;
      if (!ch) sh_error("missing format character");
      /* null terminate format string to we can use it
         as an argument to printf. */
      nextch = fmt[1];
      fmt[1] = 0;
      switch (ch) {
        case 'b':
          *fmt = 's';
          /* escape if a \c was encountered */
          if (print_escape_str(start, param, array, getstr())) goto out;
          *fmt = 'b';
          break;
        case 'c': {
          int p = getchr();
          PF(start, p);
          break;
        }
        case 's': {
          char *p = getstr();
          PF(start, p);
          break;
        }
        case 'd':
        case 'i': {
          uint64_t p = getuintmax(1);
          start = mklong(start, fmt);
          PF(start, p);
          break;
        }
        case 'o':
        case 'u':
        case 'x':
        case 'X': {
          uint64_t p = getuintmax(0);
          start = mklong(start, fmt);
          PF(start, p);
          break;
        }
        case 'a':
        case 'A':
        case 'e':
        case 'E':
        case 'f':
        case 'F':
        case 'g':
        case 'G': {
          double p = getdouble();
          PF(start, p);
          break;
        }
        default:
          sh_error("%s: invalid directive", start);
      }
      *++fmt = nextch;
    }
  } while (gargv != argv && *gargv);
out:
  return rval;
}

/*
 * Print SysV echo(1) style escape string
 *	Halts processing string if a \c escape is encountered.
 */
static int conv_escape_str(char *str, char **sp) {
  int c;
  int ch;
  char *cp;
  /* convert string into a temporary buffer... */
  STARTSTACKSTR(cp);
  do {
    c = ch = *str++;
    if (ch != '\\') continue;
    c = *str++;
    if (c == 'c') {
      /* \c as in SYSV echo - abort all processing.... */
      c = ch = 0x100;
      continue;
    }
    /*
     * %b string octal constants are not like those in C.
     * They start with a \0, and are followed by 0, 1, 2,
     * or 3 octal digits.
     */
    if (c == '0' && isodigit(*str)) str++;
    /* Finally test for sequences valid in the format string */
    str = conv_escape(str - 1, &c);
  } while (STPUTC(c, cp), (char)ch);
  *sp = cp;
  return ch;
}

/*
 * Print "standard" escape characters
 */
static char *conv_escape(char *str, int *conv_ch) {
  int value;
  int ch;
  ch = *str;
  switch (ch) {
    default:
      if (!isodigit(*str)) {
        value = '\\';
        goto out;
      }
      ch = 3;
      value = 0;
      do {
        value <<= 3;
        value += octtobin(*str++);
      } while (isodigit(*str) && --ch);
      goto out;
    case '\\':
      value = '\\';
      break; /* backslash */
    case 'a':
      value = '\a';
      break; /* alert */
    case 'b':
      value = '\b';
      break; /* backspace */
    case 'f':
      value = '\f';
      break; /* form-feed */
    case 'e':
      value = '\e';
      break; /* escape */
    case 'n':
      value = '\n';
      break; /* newline */
    case 'r':
      value = '\r';
      break; /* carriage-return */
    case 't':
      value = '\t';
      break; /* tab */
    case 'v':
      value = '\v';
      break; /* vertical-tab */
  }
  str++;
out:
  *conv_ch = value;
  return str;
}

#define PRIdMAX "ld"

static char *mklong(const char *str, const char *ch) {
  /*
   * Replace a string like "%92.3u" with "%92.3"PRIuMAX.
   *
   * Although C99 does not guarantee it, we assume PRIiMAX,
   * PRIoMAX, PRIuMAX, PRIxMAX, and PRIXMAX are all the same
   * as PRIdMAX with the final 'd' replaced by the corresponding
   * character.
   */
  char *copy;
  unsigned len;
  len = ch - str + sizeof(PRIdMAX);
  STARTSTACKSTR(copy);
  copy = makestrspace(len, copy);
  memcpy(copy, str, len - sizeof(PRIdMAX));
  memcpy(copy + len - sizeof(PRIdMAX), PRIdMAX, sizeof(PRIdMAX));
  copy[len - 2] = *ch;
  return (copy);
}

static uint64_t getuintmax(int sign) {
  uint64_t val = 0;
  char *cp, *ep;
  cp = *gargv;
  if (cp == NULL) goto out;
  gargv++;
  val = (unsigned char)cp[1];
  if (*cp == '\"' || *cp == '\'') goto out;
  errno = 0;
  val = sign ? strtoimax(cp, &ep, 0) : strtoumax(cp, &ep, 0);
  check_conversion(cp, ep);
out:
  return val;
}

static double getdouble(void) {
  double val;
  char *cp, *ep;
  cp = *gargv;
  if (cp == NULL) return 0;
  gargv++;
  if (*cp == '\"' || *cp == '\'') return (unsigned char)cp[1];
  errno = 0;
  val = strtod(cp, &ep);
  check_conversion(cp, ep);
  return val;
}

static void check_conversion(const char *s, const char *ep) {
  if (*ep) {
    if (ep == s)
      sh_warnx("%s: expected numeric value", s);
    else
      sh_warnx("%s: not completely converted", s);
    rval = 1;
  } else if (errno == ERANGE) {
    sh_warnx("%s: %s", s, strerror(ERANGE));
    rval = 1;
  }
}

static int echocmd(int argc, char **argv) {
  const char *lastfmt = snlfmt;
  int nonl;
  if (*++argv && equal(*argv, "-n")) {
    argv++;
    lastfmt = "%s";
  }
  do {
    const char *fmt = "%s ";
    char *s = *argv;
    if (!s || !*++argv) fmt = lastfmt;
    nonl = print_escape_str(fmt, NULL, NULL, s ?: nullstr);
  } while (!nonl && *argv);
  return 0;
}

/*
 * 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.
 */

/* 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>
*/

static inline int faccessat_confused_about_superuser(void) {
  return 0;
}

static const struct t_op *getop(const char *s) {
  const struct t_op *op;
  for (op = ops; op->op_text; op++) {
    if (strcmp(s, op->op_text) == 0) return op;
  }
  return NULL;
}

static int testcmd(int argc, char **argv) {
  const struct t_op *op;
  enum token n;
  int res = 1;
  if (*argv[0] == '[') {
    if (*argv[--argc] != ']') sh_error("missing ]");
    argv[argc] = NULL;
  }
  t_wp_op = NULL;
recheck:
  argv++;
  argc--;
  if (argc < 1) return res;
  /*
   * POSIX prescriptions: he who wrote this deserves the Nobel
   * peace prize.
   */
  switch (argc) {
    case 3:
      op = getop(argv[1]);
      if (op && op->op_type == BINOP) {
        n = OPERAND;
        goto eval;
      }
      /* fall through */
    case 4:
      if (!strcmp(argv[0], "(") && !strcmp(argv[argc - 1], ")")) {
        argv[--argc] = NULL;
        argv++;
        argc--;
      } else if (!strcmp(argv[0], "!")) {
        res = 0;
        goto recheck;
      }
  }
  n = t_lex(argv);
eval:
  t_wp = argv;
  res ^= oexpr(n);
  argv = t_wp;
  if (argv[0] != NULL && argv[1] != NULL) syntax(argv[0], "unexpected operator");
  return res;
}

static void syntax(const char *op, const char *msg) {
  if (op && *op) {
    sh_error("%s: %s", op, msg);
  } else {
    sh_error("%s", msg);
  }
}

static int oexpr(enum token n) {
  int res = 0;
  for (;;) {
    res |= aexpr(n);
    n = t_lex(t_wp + 1);
    if (n != BOR) break;
    n = t_lex(t_wp += 2);
  }
  return res;
}

static int aexpr(enum token n) {
  int res = 1;
  for (;;) {
    if (!nexpr(n)) res = 0;
    n = t_lex(t_wp + 1);
    if (n != BAND) break;
    n = t_lex(t_wp += 2);
  }
  return res;
}

static int nexpr(enum token n) {
  if (n != UNOT) return primary1(n);
  n = t_lex(t_wp + 1);
  if (n != EOI) t_wp++;
  return !nexpr(n);
}

static int primary1(enum token n) {
  enum token nn;
  int res;
  if (n == EOI) return 0; /* missing expression */
  if (n == LPAREN) {
    if ((nn = t_lex(++t_wp)) == RPAREN) return 0; /* missing expression */
    res = oexpr(nn);
    if (t_lex(++t_wp) != RPAREN) syntax(NULL, "closing paren expected");
    return res;
  }
  if (t_wp_op && t_wp_op->op_type == UNOP) {
    /* unary expression */
    if (*++t_wp == NULL) syntax(t_wp_op->op_text, "argument expected");
    switch (n) {
      case STREZ:
        return strlen(*t_wp) == 0;
      case STRNZ:
        return strlen(*t_wp) != 0;
      case FILTT:
        return isatty(getn(*t_wp));
      case FILRD:
        return test_file_access(*t_wp, R_OK);
      case FILWR:
        return test_file_access(*t_wp, W_OK);
      case FILEX:
        return test_file_access(*t_wp, X_OK);
      default:
        return filstat(*t_wp, n);
    }
  }
  if (t_lex(t_wp + 1), t_wp_op && t_wp_op->op_type == BINOP) {
    return binop0();
  }
  return strlen(*t_wp) > 0;
}

static int binop0(void) {
  const char *opnd1, *opnd2;
  struct t_op const *op;
  opnd1 = *t_wp;
  (void)t_lex(++t_wp);
  op = t_wp_op;
  if ((opnd2 = *++t_wp) == (char *)0) syntax(op->op_text, "argument expected");
  switch (op->op_num) {
    default:
    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 getn(opnd1) == getn(opnd2);
    case INTNE:
      return getn(opnd1) != getn(opnd2);
    case INTGE:
      return getn(opnd1) >= getn(opnd2);
    case INTGT:
      return getn(opnd1) > getn(opnd2);
    case INTLE:
      return getn(opnd1) <= getn(opnd2);
    case INTLT:
      return getn(opnd1) < getn(opnd2);
    case FILNT:
      return newerf(opnd1, opnd2);
    case FILOT:
      return olderf(opnd1, opnd2);
    case FILEQ:
      return equalf(opnd1, opnd2);
  }
}

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 FILEXIST:
      return 1;
    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;
    case FILUID:
      return s.st_uid == geteuid();
    case FILGID:
      return s.st_gid == getegid();
    default:
      return 1;
  }
}

static enum token t_lex(char **tp) {
  struct t_op const *op;
  char *s = *tp;
  if (s == 0) {
    t_wp_op = (struct t_op *)0;
    return EOI;
  }
  op = getop(s);
  if (op && !(op->op_type == UNOP && isoperand(tp)) && !(op->op_num == LPAREN && !tp[1])) {
    t_wp_op = op;
    return op->op_num;
  }
  t_wp_op = (struct t_op *)0;
  return OPERAND;
}

static int isoperand(char **tp) {
  struct t_op const *op;
  char *s;
  if (!(s = tp[1])) return 1;
  if (!tp[2]) return 0;
  op = getop(s);
  return op && op->op_type == BINOP;
}

static int newerf(const char *f1, const char *f2) {
  struct stat b1, b2;
  return (stat(f1, &b1) == 0 && stat(f2, &b2) == 0 &&
          (b1.st_mtim.tv_sec > b2.st_mtim.tv_sec ||
           (b1.st_mtim.tv_sec == b2.st_mtim.tv_sec && (b1.st_mtim.tv_nsec > b2.st_mtim.tv_nsec))));
}

static int olderf(const char *f1, const char *f2) {
  struct stat b1, b2;
  return (stat(f1, &b1) == 0 && stat(f2, &b2) == 0 &&
          (b1.st_mtim.tv_sec < b2.st_mtim.tv_sec ||
           (b1.st_mtim.tv_sec == b2.st_mtim.tv_sec && (b1.st_mtim.tv_nsec < b2.st_mtim.tv_nsec))));
}

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);
}

static int has_exec_bit_set(const char *path) {
  struct stat st;
  if (stat(path, &st)) return 0;
  return st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH);
}

static int test_file_access(const char *path, int mode) {
  if (faccessat_confused_about_superuser() && mode == X_OK && geteuid() == 0 &&
      !has_exec_bit_set(path)) {
    return 0;
  }
  return !faccessat(AT_FDCWD, path, mode, AT_EACCESS);
}

static int timescmd() {
  struct tms buf;
  long int clk_tck = sysconf(_SC_CLK_TCK);
  times(&buf);
  printf("%dm%fs %dm%fs\n%dm%fs %dm%fs\n", (int)(buf.tms_utime / clk_tck / 60),
         ((double)buf.tms_utime) / clk_tck, (int)(buf.tms_stime / clk_tck / 60),
         ((double)buf.tms_stime) / clk_tck, (int)(buf.tms_cutime / clk_tck / 60),
         ((double)buf.tms_cutime) / clk_tck, (int)(buf.tms_cstime / clk_tck / 60),
         ((double)buf.tms_cstime) / clk_tck);
  return 0;
}

/*
 * Find the appropriate entry in the hash table from the name.
 */
static struct Var **hashvar(const char *p) {
  unsigned int hashval;
  hashval = ((unsigned char)*p) << 4;
  while (*p && *p != '=') hashval += (unsigned char)*p++;
  return &vartab[hashval % VTABSIZE];
}

/*
 * This routine initializes the builtin variables.  It is called when the
 * shell is initialized.
 */
static void initvar(void) {
  struct Var *vp;
  struct Var *end;
  struct Var **vpp;
  vp = varinit;
  end = vp + sizeof(varinit) / sizeof(varinit[0]);
  do {
    vpp = hashvar(vp->text);
    vp->next = *vpp;
    *vpp = vp;
  } while (++vp < end);
  /* PS1 depends on uid */
  if (!geteuid()) {
    vps1.text = "PS1=# ";
  }
}

/*
 * Set the value of a variable.  The flags argument is ored with the
 * flags of the variable.  If val is NULL, the variable is unset.
 */
static struct Var *setvar(const char *name, const char *val, int flags) {
  char *p, *q;
  unsigned namelen;
  char *nameeq;
  unsigned vallen;
  struct Var *vp;
  q = endofname(name);
  p = strchrnul(q, '=');
  namelen = p - name;
  if (!namelen || p != q) sh_error("%.*s: bad variable name", namelen, name);
  vallen = 0;
  if (val == NULL) {
    flags |= VUNSET;
  } else {
    vallen = strlen(val);
  }
  INTOFF;
  p = mempcpy(nameeq = ckmalloc(namelen + vallen + 2), name, namelen);
  if (val) {
    *p++ = '=';
    p = mempcpy(p, val, vallen);
  }
  *p = '\0';
  vp = setvareq(nameeq, flags | VNOSAVE);
  INTON;
  return vp;
}

/*
 * Set the given integer as the value of a variable.  The flags argument is
 * ored with the flags of the variable.
 */
static int64_t setvarint(const char *name, int64_t val, int flags) {
  int len = max_int_length(sizeof(val));
  char buf[len];
  fmtstr(buf, len, "%ld", val);
  setvar(name, buf, flags);
  return val;
}

static struct Var **findvar(struct Var **vpp, const char *name) {
  for (; *vpp; vpp = &(*vpp)->next) {
    if (varequal((*vpp)->text, name)) {
      break;
    }
  }
  return vpp;
}

/*
 * 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.
 * Called with interrupts off.
 */
static struct Var *setvareq(char *s, int flags) {
  struct Var *vp, **vpp;
  vpp = hashvar(s);
  flags |= (VEXPORT & (((unsigned)(1 - aflag)) - 1));
  vpp = findvar(vpp, s);
  vp = *vpp;
  if (vp) {
    if (vp->flags & VREADONLY) {
      const char *n;
      if (flags & VNOSAVE) free(s);
      n = vp->text;
      sh_error("%.*s: is read only", strchrnul(n, '=') - n, n);
    }
    if (flags & VNOSET) goto out;
    if (vp->func && (flags & VNOFUNC) == 0) (*vp->func)(strchrnul(s, '=') + 1);
    if ((vp->flags & (VTEXTFIXED | VSTACK)) == 0) ckfree(vp->text);
    if (((flags & (VEXPORT | VREADONLY | VSTRFIXED | VUNSET)) | (vp->flags & VSTRFIXED)) ==
        VUNSET) {
      *vpp = vp->next;
      ckfree(vp);
    out_free:
      if ((flags & (VTEXTFIXED | VSTACK | VNOSAVE)) == VNOSAVE) ckfree(s);
      goto out;
    }
    flags |= vp->flags & ~(VTEXTFIXED | VSTACK | VNOSAVE | VUNSET);
  } else {
    if (flags & VNOSET) goto out;
    if ((flags & (VEXPORT | VREADONLY | VSTRFIXED | VUNSET)) == VUNSET) goto out_free;
    /* not found */
    vp = ckmalloc(sizeof(*vp));
    vp->next = *vpp;
    vp->func = NULL;
    *vpp = vp;
  }
  if (!(flags & (VTEXTFIXED | VSTACK | VNOSAVE))) s = savestr(s);
  vp->text = s;
  vp->flags = flags;
out:
  return vp;
}

/*
 * Find the value of a variable.  Returns NULL if not set.
 */
static char *lookupvar(const char *name) {
  struct Var *v;
  if ((v = *findvar(hashvar(name), name)) && !(v->flags & VUNSET)) {
    if (v == &vlineno && v->text == linenovar) {
      fmtstr(linenovar + 7, sizeof(linenovar) - 7, "%d", lineno);
    }
    return strchrnul(v->text, '=') + 1;
  }
  return NULL;
}

static int64_t lookupvarint(const char *name) {
  return atomax(lookupvar(name) ?: nullstr, 0);
}

/*
 * Generate a list of variables satisfying the given conditions.
 */
static char **listvars(int on, int off, char ***end) {
  struct Var **vpp;
  struct Var *vp;
  char **ep;
  int mask;
  STARTSTACKSTR(ep);
  vpp = vartab;
  mask = on | off;
  do {
    for (vp = *vpp; vp; vp = vp->next)
      if ((vp->flags & mask) == on) {
        if (ep == stackstrend()) ep = growstackstr();
        *ep++ = (char *)vp->text;
      }
  } while (++vpp < vartab + VTABSIZE);
  if (ep == stackstrend()) ep = growstackstr();
  if (end) *end = ep;
  *ep++ = NULL;
  return grabstackstr(ep);
}

static int vpcmp(const void *a, const void *b) {
  return varcmp(*(const char **)a, *(const char **)b);
}

/*
 * POSIX requires that 'set' (but not export or readonly) output the
 * variables in lexicographic order - by the locale's collating order (sigh).
 * Maybe we could keep them in an ordered balanced binary tree
 * instead of hashed lists.
 * For now just roll 'em through qsort for printing...
 */
static int showvars(const char *prefix, int on, int off) {
  const char *sep;
  char **ep, **epend;
  ep = listvars(on, off, &epend);
  qsort(ep, epend - ep, sizeof(char *), vpcmp);
  sep = *prefix ? spcstr : prefix;
  for (; ep < epend; ep++) {
    const char *p;
    const char *q;
    p = strchrnul(*ep, '=');
    q = nullstr;
    if (*p) q = single_quote(++p);
    out1fmt("%s%s%.*s%s\n", prefix, sep, (int)(p - *ep), *ep, q);
  }
  return 0;
}

/*
 * The export and readonly commands.
 */
static int exportcmd(int argc, char **argv) {
  struct Var *vp;
  char *name;
  const char *p;
  char **aptr;
  int flag = argv[0][0] == 'r' ? VREADONLY : VEXPORT;
  int notp;
  notp = nextopt("p") - 'p';
  if (notp && ((name = *(aptr = argptr)))) {
    do {
      if ((p = strchr(name, '=')) != NULL) {
        p++;
      } else {
        if ((vp = *findvar(hashvar(name), name))) {
          vp->flags |= flag;
          continue;
        }
      }
      setvar(name, p, flag);
    } while ((name = *++aptr) != NULL);
  } else {
    showvars(argv[0], flag, 0);
  }
  return 0;
}

/*
 * The "local" command.
 */
static int localcmd(int argc, char **argv) {
  char *name;
  if (!localvar_stack) sh_error("not in a function");
  argv = argptr;
  while ((name = *argv++) != NULL) {
    mklocal(name, 0);
  }
  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.
 */
static void mklocal(char *name, int flags) {
  struct localvar *lvp;
  struct Var **vpp;
  struct Var *vp;
  INTOFF;
  lvp = ckmalloc(sizeof(struct localvar));
  if (name[0] == '-' && name[1] == '\0') {
    char *p;
    p = ckmalloc(sizeof(optlist));
    lvp->text = memcpy(p, optlist, sizeof(optlist));
    vp = NULL;
  } else {
    char *eq;
    vpp = hashvar(name);
    vp = *findvar(vpp, name);
    eq = strchr(name, '=');
    if (vp == NULL) {
      if (eq)
        vp = setvareq(name, VSTRFIXED | flags);
      else
        vp = setvar(name, NULL, VSTRFIXED | flags);
      lvp->flags = VUNSET;
    } else {
      lvp->text = vp->text;
      lvp->flags = vp->flags;
      vp->flags |= VSTRFIXED | VTEXTFIXED;
      if (eq) setvareq(name, flags);
    }
  }
  lvp->vp = vp;
  lvp->next = localvar_stack->lv;
  localvar_stack->lv = lvp;
  INTON;
}

/*
 * Called after a function returns.
 * Interrupts must be off.
 */
static void poplocalvars(int keep) {
  struct localvar_list *ll;
  struct localvar *lvp, *next;
  struct Var *vp;
  INTOFF;
  ll = localvar_stack;
  localvar_stack = ll->next;
  next = ll->lv;
  ckfree(ll);
  while ((lvp = next) != NULL) {
    next = lvp->next;
    vp = lvp->vp;
    TRACE(("poplocalvar %s\n", vp ? vp->text : "-"));
    if (keep) {
      int bits = VSTRFIXED;
      if (lvp->flags != VUNSET) {
        if (vp->text == lvp->text)
          bits |= VTEXTFIXED;
        else if (!(lvp->flags & (VTEXTFIXED | VSTACK)))
          ckfree(lvp->text);
      }
      vp->flags &= ~bits;
      vp->flags |= (lvp->flags & bits);
      if ((vp->flags & (VEXPORT | VREADONLY | VSTRFIXED | VUNSET)) == VUNSET) unsetvar(vp->text);
    } else if (vp == NULL) { /* $- saved */
      memcpy(optlist, lvp->text, sizeof(optlist));
      ckfree(lvp->text);
      optschanged();
    } else if (lvp->flags == VUNSET) {
      vp->flags &= ~(VSTRFIXED | VREADONLY);
      unsetvar(vp->text);
    } else {
      if (vp->func) (*vp->func)(strchrnul(lvp->text, '=') + 1);
      if ((vp->flags & (VTEXTFIXED | VSTACK)) == 0) ckfree(vp->text);
      vp->flags = lvp->flags;
      vp->text = lvp->text;
    }
    ckfree(lvp);
  }
  INTON;
}

/*
 * Create a new localvar environment.
 */
static struct localvar_list *pushlocalvars(int push) {
  struct localvar_list *ll;
  struct localvar_list *top;
  top = localvar_stack;
  if (!push) goto out;
  INTOFF;
  ll = ckmalloc(sizeof(*ll));
  ll->lv = NULL;
  ll->next = top;
  localvar_stack = ll;
  INTON;
out:
  return top;
}

static void unwindlocalvars(struct localvar_list *stop) {
  while (localvar_stack != stop) poplocalvars(0);
}

/*
 * The unset builtin command.  We unset the function before we unset the
 * variable to allow a function to be unset when there is a readonly variable
 * with the same name.
 */
static int unsetcmd(int argc, char **argv) {
  char **ap;
  int i;
  int flag = 0;
  while ((i = nextopt("vf")) != '\0') {
    flag = i;
  }
  for (ap = argptr; *ap; ap++) {
    if (flag != 'f') {
      unsetvar(*ap);
      continue;
    }
    if (flag != 'v') unsetfunc(*ap);
  }
  return 0;
}

static void unsetvar(const char *s) {
  setvar(s, 0, 0);
}

/*
 * Initialization code.
 */
static void init() {
  /* from input.c: */
  {
    basepf.nextc = basepf.buf = basebuf;
    basepf.linno = 1;
  }
  /* from trap.c: */
  {
    sigmode[SIGCHLD - 1] = S_DFL;
    setsignal(SIGCHLD);
  }
  /* from output.c: */
  {} /* from var.c: */
  {
    char **envp;
    static char ppid[32] = "PPID=";
    const char *p;
    struct stat st1, st2;
    initvar();
    for (envp = environ; *envp; envp++) {
      p = endofname(*envp);
      if (p != *envp && *p == '=') {
        setvareq(*envp, VEXPORT | VTEXTFIXED);
      }
    }
    setvareq(defifsvar, VTEXTFIXED);
    setvareq(defoptindvar, VTEXTFIXED);
    fmtstr(ppid + 5, sizeof(ppid) - 5, "%ld", (long)getppid());
    setvareq(ppid, VTEXTFIXED);
    p = lookupvar("PWD");
    if (p) {
      if (*p != '/' || stat(p, &st1) || stat(".", &st2) || st1.st_dev != st2.st_dev ||
          st1.st_ino != st2.st_ino) {
        p = 0;
      }
    }
    setpwd(p, 0);
  }
}

/*
 * This routine is called when an error or an interrupt occurs in an
 * interactive shell and control is returned to the main command loop
 * but prior to exitshell.
 */
static void exitreset() {
  /* from eval.c: */
  {
    evalskip = 0;
    loopnest = 0;
    if (savestatus >= 0) {
      exitstatus = savestatus;
      savestatus = -1;
    }
  }
  /* from expand.c: */
  { ifsfree(); }
  /* from redir.c: */
  {
    /*
     * Discard all saved file descriptors.
     */
    unwindredir(0);
  }
}

/*
 * This routine is called when an error or an interrupt occurs in an
 * interactive shell and control is returned to the main command loop.
 */
static void reset() {
  /* from input.c: */
  {
    /* clear input buffer */
    basepf.lleft = basepf.nleft = 0;
    popallfiles();
  }
  /* from output.c: */
  {} /* from var.c: */
  {
    unwindlocalvars(0);
  }
}

static void calcsize(union node *n) {
  if (n == NULL) return;
  funcblocksize += nodesize[n->type];
  switch (n->type) {
    case NCMD:
      calcsize(n->ncmd.redirect);
      calcsize(n->ncmd.args);
      calcsize(n->ncmd.assign);
      break;
    case NPIPE:
      sizenodelist(n->npipe.cmdlist);
      break;
    case NREDIR:
    case NBACKGND:
    case NSUBSHELL:
      calcsize(n->nredir.redirect);
      calcsize(n->nredir.n);
      break;
    case NAND:
    case NOR:
    case NSEMI:
    case NWHILE:
    case NUNTIL:
      calcsize(n->nbinary.ch2);
      calcsize(n->nbinary.ch1);
      break;
    case NIF:
      calcsize(n->nif.elsepart);
      calcsize(n->nif.ifpart);
      calcsize(n->nif.test);
      break;
    case NFOR:
      funcstringsize += strlen(n->nfor.var_) + 1;
      calcsize(n->nfor.body);
      calcsize(n->nfor.args);
      break;
    case NCASE:
      calcsize(n->ncase.cases);
      calcsize(n->ncase.expr);
      break;
    case NCLIST:
      calcsize(n->nclist.body);
      calcsize(n->nclist.pattern);
      calcsize(n->nclist.next);
      break;
    case NDEFUN:
      calcsize(n->ndefun.body);
      funcstringsize += strlen(n->ndefun.text) + 1;
      break;
    case NARG:
      sizenodelist(n->narg.backquote);
      funcstringsize += strlen(n->narg.text) + 1;
      calcsize(n->narg.next);
      break;
    case NTO:
    case NCLOBBER:
    case NFROM:
    case NFROMTO:
    case NAPPEND:
      calcsize(n->nfile.fname);
      calcsize(n->nfile.next);
      break;
    case NTOFD:
    case NFROMFD:
      calcsize(n->ndup.vname);
      calcsize(n->ndup.next);
      break;
    case NHERE:
    case NXHERE:
      calcsize(n->nhere.doc);
      calcsize(n->nhere.next);
      break;
    case NNOT:
      calcsize(n->nnot.com);
      break;
  };
}

/*
 * Make a copy of a parse tree.
 */
static struct funcnode *copyfunc(union node *n) {
  struct funcnode *f;
  unsigned blocksize;
  funcblocksize = offsetof(struct funcnode, n);
  funcstringsize = 0;
  calcsize(n);
  blocksize = funcblocksize;
  f = ckmalloc(blocksize + funcstringsize);
  funcblock = (char *)f + offsetof(struct funcnode, n);
  funcstring = (char *)f + blocksize;
  copynode(n);
  f->count = 0;
  return f;
}

static void sizenodelist(struct nodelist *lp) {
  while (lp) {
    funcblocksize += SHELL_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 NCMD:
      new->ncmd.redirect = copynode(n->ncmd.redirect);
      new->ncmd.args = copynode(n->ncmd.args);
      new->ncmd.assign = copynode(n->ncmd.assign);
      new->ncmd.linno = n->ncmd.linno;
      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);
      new->nredir.linno = n->nredir.linno;
      break;
    case NAND:
    case NOR:
    case NSEMI:
    case NWHILE:
    case NUNTIL:
      new->nbinary.ch2 = copynode(n->nbinary.ch2);
      new->nbinary.ch1 = copynode(n->nbinary.ch1);
      break;
    case NIF:
      new->nif.elsepart = copynode(n->nif.elsepart);
      new->nif.ifpart = copynode(n->nif.ifpart);
      new->nif.test = copynode(n->nif.test);
      break;
    case NFOR:
      new->nfor.var_ = nodesavestr(n->nfor.var_);
      new->nfor.body = copynode(n->nfor.body);
      new->nfor.args = copynode(n->nfor.args);
      new->nfor.linno = n->nfor.linno;
      break;
    case NCASE:
      new->ncase.cases = copynode(n->ncase.cases);
      new->ncase.expr = copynode(n->ncase.expr);
      new->ncase.linno = n->ncase.linno;
      break;
    case NCLIST:
      new->nclist.body = copynode(n->nclist.body);
      new->nclist.pattern = copynode(n->nclist.pattern);
      new->nclist.next = copynode(n->nclist.next);
      break;
    case NDEFUN:
      new->ndefun.body = copynode(n->ndefun.body);
      new->ndefun.text = nodesavestr(n->ndefun.text);
      new->ndefun.linno = n->ndefun.linno;
      break;
    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 NCLOBBER:
    case NFROM:
    case NFROMTO:
    case NAPPEND:
      new->nfile.fname = copynode(n->nfile.fname);
      new->nfile.fd = n->nfile.fd;
      new->nfile.next = copynode(n->nfile.next);
      break;
    case NTOFD:
    case NFROMFD:
      new->ndup.vname = copynode(n->ndup.vname);
      new->ndup.dupfd = n->ndup.dupfd;
      new->ndup.fd = n->ndup.fd;
      new->ndup.next = copynode(n->ndup.next);
      break;
    case NHERE:
    case NXHERE:
      new->nhere.doc = copynode(n->nhere.doc);
      new->nhere.fd = n->nhere.fd;
      new->nhere.next = copynode(n->nhere.next);
      break;
    case NNOT:
      new->nnot.com = copynode(n->nnot.com);
      break;
  };
  new->type = n->type;
  return new;
}

static struct nodelist *copynodelist(struct nodelist *lp) {
  struct nodelist *start;
  struct nodelist **lpp;
  lpp = &start;
  while (lp) {
    *lpp = funcblock;
    funcblock = (char *)funcblock + SHELL_ALIGN(sizeof(struct nodelist));
    (*lpp)->n = copynode(lp->n);
    lp = lp->next;
    lpp = &(*lpp)->next;
  }
  *lpp = NULL;
  return start;
}

/*
 * Read and execute commands.  "Top" is nonzero for the top level command
 * loop; it turns on prompting if the shell is interactive.
 */
static int cmdloop(int top) {
  union node *n;
  struct stackmark smark;
  int inter;
  int status = 0;
  int numeof = 0;
  TRACE(("cmdloop(%d) called\n", top));
  for (;;) {
    int skip;
    setstackmark(&smark);
    if (jobctl) showjobs(out2, SHOW_CHANGED);
    inter = 0;
    if (iflag && top) {
      inter++;
      /* chkmail(); */
    }
    n = parsecmd(inter);
    /* showtree(n); DEBUG */
    if (n == NEOF) {
      if (!top || numeof >= 50) break;
      if (!stoppedjobs()) {
        if (!Iflag) break;
        outstr("\nUse \"exit\" to leave shell.\n", out2);
      }
      numeof++;
    } else if (nflag == 0) {
      int i;
      job_warning = (job_warning == 2) ? 1 : 0;
      numeof = 0;
      i = evaltree(n, 0);
      if (n) status = i;
    }
    popstackmark(&smark);
    skip = evalskip;
    if (skip) {
      evalskip &= ~(SKIPFUNC | SKIPFUNCDEF);
      break;
    }
  }
  return status;
}

/*
 * Read /etc/profile or .profile.  Return on error.
 */
static void read_profile(const char *name) {
  name = expandstr(name);
  if (setinputfile(name, INPUT_PUSH_FILE | INPUT_NOFILE_OK) < 0) return;
  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;
  int len;
  /* don't try this for absolute or relative paths */
  if (strchr(basename, '/')) return basename;
  while ((len = padvance(&path, basename)) >= 0) {
    fullname = stackblock();
    if ((!pathopt || *pathopt == 'f') && !stat(fullname, &statb) && S_ISREG(statb.st_mode)) {
      /* This will be freed by the caller. */
      return stalloc(len);
    }
  }
  /* not found in the PATH */
  sh_error("%s: not found", basename);
}

static int dotcmd(int argc, char **argv) {
  int status = 0;
  nextopt(nullstr);
  argv = argptr;
  if (*argv) {
    char *fullname;
    fullname = find_dot_file(*argv);
    setinputfile(fullname, INPUT_PUSH_FILE);
    commandname = fullname;
    status = cmdloop(0);
    popfile();
  }
  return status;
}

static int exitcmd(int argc, char **argv) {
  if (stoppedjobs()) return 0;
  if (argc > 1) savestatus = number(argv[1]);
  exraise(EXEXIT);
}

/**
 * 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) {
  showcrashreports();
  unsetenv("PS1");
  char *shinit;
  volatile int state;
  struct jmploc jmploc;
  struct stackmark smark;
  int login;
  state = 0;
  if (unlikely(setjmp(jmploc.loc))) {
    int e;
    int s;
    exitreset();
    e = exception;
    s = state;
    if (e == EXEXIT || s == 0 || iflag == 0 || shlvl) exitshell();
    reset();
    if (e == EXINT) {
      outcslow('\n', out2);
    }
    popstackmark(&smark);
    FORCEINTON; /* enable interrupts */
    if (s == 1) {
      goto state1;
    } else if (s == 2) {
      goto state2;
    } else if (s == 3) {
      goto state3;
    } else {
      goto state4;
    }
  }
  handler = &jmploc;
  rootpid = getpid();
  init();
  setstackmark(&smark);
  login = procargs(argc, argv);
  if (login) {
    state = 1;
    read_profile("/etc/profile");
  state1:
    state = 2;
    read_profile("$HOME/.profile");
  }
state2:
  state = 3;
  if (iflag) {
    if ((shinit = lookupvar("ENV")) != NULL && *shinit != '\0') {
      read_profile(shinit);
    }
  }
  popstackmark(&smark);
state3:
  state = 4;
  if (minusc) evalstring(minusc, sflag ? 0 : EV_EXIT);
  if (sflag || minusc == NULL) {
  state4: /* XXX ??? - why isn't this before the "if" statement */
    cmdloop(1);
  }
  exitshell();
}
