xref: /third_party/toybox/toys/pending/sh.c (revision 0f66f451)
10f66f451Sopenharmony_ci/* sh.c - toybox shell
20f66f451Sopenharmony_ci *
30f66f451Sopenharmony_ci * Copyright 2006 Rob Landley <rob@landley.net>
40f66f451Sopenharmony_ci *
50f66f451Sopenharmony_ci * The POSIX-2008/SUSv4 spec for this is at:
60f66f451Sopenharmony_ci * http://opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html
70f66f451Sopenharmony_ci * and http://opengroup.org/onlinepubs/9699919799/utilities/sh.html
80f66f451Sopenharmony_ci *
90f66f451Sopenharmony_ci * The first link describes the following shell builtins:
100f66f451Sopenharmony_ci *
110f66f451Sopenharmony_ci *   break colon continue dot eval exec exit export readonly return set shift
120f66f451Sopenharmony_ci *   times trap unset
130f66f451Sopenharmony_ci *
140f66f451Sopenharmony_ci * The second link (the utilities directory) also contains specs for the
150f66f451Sopenharmony_ci * following shell builtins:
160f66f451Sopenharmony_ci *
170f66f451Sopenharmony_ci *   alias bg cd command fc fg getopts hash jobs kill read type ulimit
180f66f451Sopenharmony_ci *   umask unalias wait
190f66f451Sopenharmony_ci *
200f66f451Sopenharmony_ci * Things like the bash man page are good to read too.
210f66f451Sopenharmony_ci *
220f66f451Sopenharmony_ci * TODO: "make sh" doesn't work (nofork builtins need to be included)
230f66f451Sopenharmony_ci * TODO: test that $PS1 color changes work without stupid \[ \] hack
240f66f451Sopenharmony_ci * TODO: make fake pty wrapper for test infrastructure
250f66f451Sopenharmony_ci * TODO: // Handle embedded NUL bytes in the command line.
260f66f451Sopenharmony_ci * TODO: var=val command
270f66f451Sopenharmony_ci * existing but considered builtins: false kill pwd true time
280f66f451Sopenharmony_ci * buitins: alias bg command fc fg getopts jobs newgrp read umask unalias wait
290f66f451Sopenharmony_ci * "special" builtins: break continue : . eval exec export readonly return set
300f66f451Sopenharmony_ci *   shift times trap unset
310f66f451Sopenharmony_ci * | & ; < > ( ) $ ` \ " ' <space> <tab> <newline>
320f66f451Sopenharmony_ci * * ? [ # ~ = %
330f66f451Sopenharmony_ci * ! { } case do done elif else esac fi for if in then until while
340f66f451Sopenharmony_ci * [[ ]] function select
350f66f451Sopenharmony_ci * $@ $* $# $? $- $$ $! $0
360f66f451Sopenharmony_ci * ENV HOME IFS LANG LC_ALL LINENO PATH PPID PS1 PS2 PS4 PWD
370f66f451Sopenharmony_ci * label:
380f66f451Sopenharmony_ci * TODO: test exit from "trap EXIT" doesn't recurse
390f66f451Sopenharmony_ci * TODO: ! history expansion
400f66f451Sopenharmony_ci *
410f66f451Sopenharmony_ci * bash man page:
420f66f451Sopenharmony_ci * control operators || & && ; ;; ;& ;;& ( ) | |& <newline>
430f66f451Sopenharmony_ci * reserved words
440f66f451Sopenharmony_ci *   ! case  coproc  do done elif else esac fi for  function  if  in  select
450f66f451Sopenharmony_ci *   then until while { } time [[ ]]
460f66f451Sopenharmony_ci
470f66f451Sopenharmony_ci
480f66f451Sopenharmony_ci
490f66f451Sopenharmony_ciUSE_SH(NEWTOY(cd, NULL, TOYFLAG_NOFORK))
500f66f451Sopenharmony_ciUSE_SH(NEWTOY(exit, NULL, TOYFLAG_NOFORK))
510f66f451Sopenharmony_ci
520f66f451Sopenharmony_ciUSE_SH(NEWTOY(sh, "c:i", TOYFLAG_BIN))
530f66f451Sopenharmony_ciUSE_SH(OLDTOY(toysh, sh, TOYFLAG_BIN))
540f66f451Sopenharmony_ciUSE_SH(OLDTOY(bash, sh, TOYFLAG_BIN))
550f66f451Sopenharmony_ci// Login lies in argv[0], so add some aliases to catch that
560f66f451Sopenharmony_ciUSE_SH(OLDTOY(-sh, sh, 0))
570f66f451Sopenharmony_ciUSE_SH(OLDTOY(-toysh, sh, 0))
580f66f451Sopenharmony_ciUSE_SH(OLDTOY(-bash, sh, 0))
590f66f451Sopenharmony_ci
600f66f451Sopenharmony_ciconfig SH
610f66f451Sopenharmony_ci  bool "sh (toysh)"
620f66f451Sopenharmony_ci  default n
630f66f451Sopenharmony_ci  help
640f66f451Sopenharmony_ci    usage: sh [-c command] [script]
650f66f451Sopenharmony_ci
660f66f451Sopenharmony_ci    Command shell.  Runs a shell script, or reads input interactively
670f66f451Sopenharmony_ci    and responds to it.
680f66f451Sopenharmony_ci
690f66f451Sopenharmony_ci    -c	command line to execute
700f66f451Sopenharmony_ci    -i	interactive mode (default when STDIN is a tty)
710f66f451Sopenharmony_ci
720f66f451Sopenharmony_ci# These are here for the help text, they're not selectable and control nothing
730f66f451Sopenharmony_ciconfig CD
740f66f451Sopenharmony_ci  bool
750f66f451Sopenharmony_ci  default n
760f66f451Sopenharmony_ci  depends on SH
770f66f451Sopenharmony_ci  help
780f66f451Sopenharmony_ci    usage: cd [-PL] [path]
790f66f451Sopenharmony_ci
800f66f451Sopenharmony_ci    Change current directory.  With no arguments, go $HOME.
810f66f451Sopenharmony_ci
820f66f451Sopenharmony_ci    -P	Physical path: resolve symlinks in path
830f66f451Sopenharmony_ci    -L	Local path: .. trims directories off $PWD (default)
840f66f451Sopenharmony_ci
850f66f451Sopenharmony_ciconfig EXIT
860f66f451Sopenharmony_ci  bool
870f66f451Sopenharmony_ci  default n
880f66f451Sopenharmony_ci  depends on SH
890f66f451Sopenharmony_ci  help
900f66f451Sopenharmony_ci    usage: exit [status]
910f66f451Sopenharmony_ci
920f66f451Sopenharmony_ci    Exit shell.  If no return value supplied on command line, use value
930f66f451Sopenharmony_ci    of most recent command, or 0 if none.
940f66f451Sopenharmony_ci*/
950f66f451Sopenharmony_ci
960f66f451Sopenharmony_ci#define FOR_sh
970f66f451Sopenharmony_ci#include "toys.h"
980f66f451Sopenharmony_ci
990f66f451Sopenharmony_ciGLOBALS(
1000f66f451Sopenharmony_ci  char *command;
1010f66f451Sopenharmony_ci
1020f66f451Sopenharmony_ci  long lineno;
1030f66f451Sopenharmony_ci
1040f66f451Sopenharmony_ci  struct double_list functions;
1050f66f451Sopenharmony_ci  unsigned options;
1060f66f451Sopenharmony_ci
1070f66f451Sopenharmony_ci  // Running jobs.
1080f66f451Sopenharmony_ci  struct sh_job {
1090f66f451Sopenharmony_ci    struct sh_job *next, *prev;
1100f66f451Sopenharmony_ci    unsigned jobno;
1110f66f451Sopenharmony_ci
1120f66f451Sopenharmony_ci    // Every pipeline has at least one set of arguments or it's Not A Thing
1130f66f451Sopenharmony_ci    struct sh_arg {
1140f66f451Sopenharmony_ci      char **v;
1150f66f451Sopenharmony_ci      int c;
1160f66f451Sopenharmony_ci    } pipeline;
1170f66f451Sopenharmony_ci
1180f66f451Sopenharmony_ci    // null terminated array of running processes in pipeline
1190f66f451Sopenharmony_ci    struct sh_process {
1200f66f451Sopenharmony_ci      struct string_list *delete; // expanded strings
1210f66f451Sopenharmony_ci      int pid, exit;   // status? Stopped? Exited?
1220f66f451Sopenharmony_ci      struct sh_arg arg;
1230f66f451Sopenharmony_ci    } *procs, *proc;
1240f66f451Sopenharmony_ci  } *jobs, *job;
1250f66f451Sopenharmony_ci  unsigned jobcnt;
1260f66f451Sopenharmony_ci)
1270f66f451Sopenharmony_ci
1280f66f451Sopenharmony_ci#define SH_NOCLOBBER 1   // set -C
1290f66f451Sopenharmony_ci
1300f66f451Sopenharmony_civoid cd_main(void)
1310f66f451Sopenharmony_ci{
1320f66f451Sopenharmony_ci  char *dest = *toys.optargs ? *toys.optargs : getenv("HOME");
1330f66f451Sopenharmony_ci
1340f66f451Sopenharmony_ci// TODO: -LPE@
1350f66f451Sopenharmony_ci// TODO: cd .. goes up $PWD path we used to get here, not ./..
1360f66f451Sopenharmony_ci  xchdir(dest ? dest : "/");
1370f66f451Sopenharmony_ci}
1380f66f451Sopenharmony_ci
1390f66f451Sopenharmony_civoid exit_main(void)
1400f66f451Sopenharmony_ci{
1410f66f451Sopenharmony_ci  exit(*toys.optargs ? atoi(*toys.optargs) : 0);
1420f66f451Sopenharmony_ci}
1430f66f451Sopenharmony_ci
1440f66f451Sopenharmony_ci// like error_msg() but exit from shell scripts
1450f66f451Sopenharmony_civoid syntax_err(char *msg, ...)
1460f66f451Sopenharmony_ci{
1470f66f451Sopenharmony_ci  va_list va;
1480f66f451Sopenharmony_ci
1490f66f451Sopenharmony_ci  va_start(va, msg);
1500f66f451Sopenharmony_ci  verror_msg(msg, 0, va);
1510f66f451Sopenharmony_ci  va_end(va);
1520f66f451Sopenharmony_ci
1530f66f451Sopenharmony_ci  if (*toys.optargs) xexit();
1540f66f451Sopenharmony_ci}
1550f66f451Sopenharmony_ci
1560f66f451Sopenharmony_ci// Print prompt, parsing escapes
1570f66f451Sopenharmony_cistatic void do_prompt(char *prompt)
1580f66f451Sopenharmony_ci{
1590f66f451Sopenharmony_ci  char *s, c, cc;
1600f66f451Sopenharmony_ci
1610f66f451Sopenharmony_ci  if (!prompt) prompt = "\\$ ";
1620f66f451Sopenharmony_ci  while (*prompt) {
1630f66f451Sopenharmony_ci    c = *(prompt++);
1640f66f451Sopenharmony_ci
1650f66f451Sopenharmony_ci    if (c=='!') {
1660f66f451Sopenharmony_ci      if (*prompt=='!') prompt++;
1670f66f451Sopenharmony_ci      else {
1680f66f451Sopenharmony_ci        printf("%ld", TT.lineno);
1690f66f451Sopenharmony_ci        continue;
1700f66f451Sopenharmony_ci      }
1710f66f451Sopenharmony_ci    } else if (c=='\\') {
1720f66f451Sopenharmony_ci      int i = 0;
1730f66f451Sopenharmony_ci
1740f66f451Sopenharmony_ci      cc = *(prompt++);
1750f66f451Sopenharmony_ci      if (!cc) goto down;
1760f66f451Sopenharmony_ci
1770f66f451Sopenharmony_ci      // \nnn \dD{}hHjlstT@AuvVwW!#$
1780f66f451Sopenharmony_ci      // Ignore bash's "nonprintable" hack; query our cursor position instead.
1790f66f451Sopenharmony_ci      if (cc=='[' || cc==']') continue;
1800f66f451Sopenharmony_ci      else if (cc=='$') putchar(getuid() ? '$' : '#');
1810f66f451Sopenharmony_ci      else if (cc=='h' || cc=='H') {
1820f66f451Sopenharmony_ci        *toybuf = 0;
1830f66f451Sopenharmony_ci        gethostname(toybuf, sizeof(toybuf)-1);
1840f66f451Sopenharmony_ci        if (cc=='h' && (s = strchr(toybuf, '.'))) *s = 0;
1850f66f451Sopenharmony_ci        fputs(toybuf, stdout);
1860f66f451Sopenharmony_ci      } else if (cc=='s') fputs(getbasename(*toys.argv), stdout);
1870f66f451Sopenharmony_ci      else {
1880f66f451Sopenharmony_ci        if (!(c = unescape(cc))) {
1890f66f451Sopenharmony_ci          c = '\\';
1900f66f451Sopenharmony_ci          prompt--;
1910f66f451Sopenharmony_ci        }
1920f66f451Sopenharmony_ci        i++;
1930f66f451Sopenharmony_ci      }
1940f66f451Sopenharmony_ci      if (!i) continue;
1950f66f451Sopenharmony_ci    }
1960f66f451Sopenharmony_cidown:
1970f66f451Sopenharmony_ci    putchar(c);
1980f66f451Sopenharmony_ci  }
1990f66f451Sopenharmony_ci  fflush(stdout);
2000f66f451Sopenharmony_ci}
2010f66f451Sopenharmony_ci
2020f66f451Sopenharmony_ci// quote removal, brace, tilde, parameter/variable, $(command),
2030f66f451Sopenharmony_ci// $((arithmetic)), split, path
2040f66f451Sopenharmony_ci#define NO_PATH  (1<<0)
2050f66f451Sopenharmony_ci#define NO_SPLIT (1<<1)
2060f66f451Sopenharmony_ci// TODO: ${name:?error} causes an error/abort here (syntax_err longjmp?)
2070f66f451Sopenharmony_ci// TODO: $1 $@ $* need args marshalled down here: function+structure?
2080f66f451Sopenharmony_ci// arg = append to this
2090f66f451Sopenharmony_ci// new = string to expand
2100f66f451Sopenharmony_ci// flags = type of expansions (not) to do
2110f66f451Sopenharmony_ci// delete = append new allocations to this so they can be freed later
2120f66f451Sopenharmony_ci// TODO: at_args: $1 $2 $3 $* $@
2130f66f451Sopenharmony_cistatic void expand_arg(struct sh_arg *arg, char *new, unsigned flags,
2140f66f451Sopenharmony_ci  struct string_list **delete)
2150f66f451Sopenharmony_ci{
2160f66f451Sopenharmony_ci  if (!(arg->c&32)) arg->v = xrealloc(arg->v, sizeof(void *)*(arg->c+33));
2170f66f451Sopenharmony_ci
2180f66f451Sopenharmony_ci  arg->v[arg->c++] = new;
2190f66f451Sopenharmony_ci  arg->v[arg->c] = 0;
2200f66f451Sopenharmony_ci
2210f66f451Sopenharmony_ci/*
2220f66f451Sopenharmony_ci  char *s = word, *new = 0;
2230f66f451Sopenharmony_ci
2240f66f451Sopenharmony_ci  // replacement
2250f66f451Sopenharmony_ci  while (*s) {
2260f66f451Sopenharmony_ci    if (*s == '$') {
2270f66f451Sopenharmony_ci      s++;
2280f66f451Sopenharmony_ci    } else if (*strchr("*?[{", *s)) {
2290f66f451Sopenharmony_ci      s++;
2300f66f451Sopenharmony_ci    } else if (*s == '<' || *s == '>') {
2310f66f451Sopenharmony_ci      s++;
2320f66f451Sopenharmony_ci    } else s++;
2330f66f451Sopenharmony_ci  }
2340f66f451Sopenharmony_ci
2350f66f451Sopenharmony_ci  return new;
2360f66f451Sopenharmony_ci*/
2370f66f451Sopenharmony_ci}
2380f66f451Sopenharmony_ci
2390f66f451Sopenharmony_ci// Assign one variable
2400f66f451Sopenharmony_ci// s: key=val
2410f66f451Sopenharmony_ci// type: 0 = whatever it was before, local otherwise
2420f66f451Sopenharmony_ci#define TAKE_MEM 0x80000000
2430f66f451Sopenharmony_ci// declare -aAilnrux
2440f66f451Sopenharmony_ci// ft
2450f66f451Sopenharmony_civoid setvar(char *s, unsigned type)
2460f66f451Sopenharmony_ci{
2470f66f451Sopenharmony_ci  if (type&TAKE_MEM) type ^= TAKE_MEM;
2480f66f451Sopenharmony_ci  else s = xstrdup(s);
2490f66f451Sopenharmony_ci
2500f66f451Sopenharmony_ci  // local, export, readonly, integer...
2510f66f451Sopenharmony_ci  xsetenv(s, 0);
2520f66f451Sopenharmony_ci}
2530f66f451Sopenharmony_ci
2540f66f451Sopenharmony_cichar *getvar(char *s)
2550f66f451Sopenharmony_ci{
2560f66f451Sopenharmony_ci  return getenv(s);
2570f66f451Sopenharmony_ci}
2580f66f451Sopenharmony_ci
2590f66f451Sopenharmony_ci// return length of match found at this point
2600f66f451Sopenharmony_cistatic int anystart(char *s, char **try)
2610f66f451Sopenharmony_ci{
2620f66f451Sopenharmony_ci  while (*try) {
2630f66f451Sopenharmony_ci    if (strstart(&s, *try)) return strlen(*try);
2640f66f451Sopenharmony_ci    try++;
2650f66f451Sopenharmony_ci  }
2660f66f451Sopenharmony_ci
2670f66f451Sopenharmony_ci  return 0;
2680f66f451Sopenharmony_ci}
2690f66f451Sopenharmony_ci
2700f66f451Sopenharmony_cistatic int anystr(char *s, char **try)
2710f66f451Sopenharmony_ci{
2720f66f451Sopenharmony_ci  while (*try) if (!strcmp(s, *try++)) return 1;
2730f66f451Sopenharmony_ci
2740f66f451Sopenharmony_ci  return 0;
2750f66f451Sopenharmony_ci}
2760f66f451Sopenharmony_ci
2770f66f451Sopenharmony_ci// return length of valid prefix that could go before redirect
2780f66f451Sopenharmony_ciint redir_prefix(char *word)
2790f66f451Sopenharmony_ci{
2800f66f451Sopenharmony_ci  char *s = word;
2810f66f451Sopenharmony_ci
2820f66f451Sopenharmony_ci  if (*s == '{') {
2830f66f451Sopenharmony_ci    for (s++; isalnum(*s) || *s=='_'; s++);
2840f66f451Sopenharmony_ci    if (*s == '}' && s != word+1) s++;
2850f66f451Sopenharmony_ci    else s = word;
2860f66f451Sopenharmony_ci  } else while (isdigit(*s)) s++;
2870f66f451Sopenharmony_ci
2880f66f451Sopenharmony_ci  return s-word;
2890f66f451Sopenharmony_ci}
2900f66f451Sopenharmony_ci
2910f66f451Sopenharmony_ci// TODO |&
2920f66f451Sopenharmony_ci
2930f66f451Sopenharmony_ci// rd[0] = next, 1 = prev, 2 = len, 3-x = to/from redirection pairs.
2940f66f451Sopenharmony_ci// Execute the commands in a pipeline segment
2950f66f451Sopenharmony_cistruct sh_process *run_command(struct sh_arg *arg, int **rdlist)
2960f66f451Sopenharmony_ci{
2970f66f451Sopenharmony_ci  struct sh_process *pp = xzalloc(sizeof(struct sh_process));
2980f66f451Sopenharmony_ci  struct toy_list *tl;
2990f66f451Sopenharmony_ci  char *s, *ss, *sss;
3000f66f451Sopenharmony_ci  unsigned envlen, j;
3010f66f451Sopenharmony_ci  int fd, here = 0, rdcount = 0, *rd = 0, *rr, hfd = 0;
3020f66f451Sopenharmony_ci
3030f66f451Sopenharmony_ci  // Grab variable assignments
3040f66f451Sopenharmony_ci  for (envlen = 0; envlen<arg->c; envlen++) {
3050f66f451Sopenharmony_ci    s = arg->v[envlen];
3060f66f451Sopenharmony_ci    for (j=0; s[j] && (s[j]=='_' || !ispunct(s[j])); j++);
3070f66f451Sopenharmony_ci    if (!j || s[j] != '=') break;
3080f66f451Sopenharmony_ci  }
3090f66f451Sopenharmony_ci
3100f66f451Sopenharmony_ci  // perform assignments locally if there's no command
3110f66f451Sopenharmony_ci  if (envlen == arg->c) {
3120f66f451Sopenharmony_ci    for (j = 0; j<envlen; j++) {
3130f66f451Sopenharmony_ci      struct sh_arg aa;
3140f66f451Sopenharmony_ci
3150f66f451Sopenharmony_ci      aa.c = 0;
3160f66f451Sopenharmony_ci      expand_arg(&aa, arg->v[j], NO_PATH|NO_SPLIT, 0);
3170f66f451Sopenharmony_ci      setvar(*aa.v, TAKE_MEM);
3180f66f451Sopenharmony_ci      free(aa.v);
3190f66f451Sopenharmony_ci    }
3200f66f451Sopenharmony_ci    free(pp);
3210f66f451Sopenharmony_ci
3220f66f451Sopenharmony_ci    return 0;
3230f66f451Sopenharmony_ci  }
3240f66f451Sopenharmony_ci
3250f66f451Sopenharmony_ci  // We vfork() instead of fork to support nommu systems, and do
3260f66f451Sopenharmony_ci  // redirection setup in the parent process. Open new filehandles
3270f66f451Sopenharmony_ci  // and move them to temporary values >10. The rd[] array has pairs of
3280f66f451Sopenharmony_ci  // filehandles: child replaces fd1 with fd2 via dup2() and close() after
3290f66f451Sopenharmony_ci  // the vfork(). fd2 is <<1, if bottom bit set don't close it (dup instead).
3300f66f451Sopenharmony_ci  // If fd2 < 0 it's a here document (parent process writes to a pipe later).
3310f66f451Sopenharmony_ci
3320f66f451Sopenharmony_ci  // Expand arguments and perform redirections
3330f66f451Sopenharmony_ci  for (j = envlen; j<arg->c; j++) {
3340f66f451Sopenharmony_ci
3350f66f451Sopenharmony_ci    // Is this a redirect?
3360f66f451Sopenharmony_ci    ss = (s = arg->v[j]) + redir_prefix(arg->v[j]);
3370f66f451Sopenharmony_ci    if (!anystr(ss, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>", ">&",
3380f66f451Sopenharmony_ci      ">|", ">", "&>>", "&>", 0}))
3390f66f451Sopenharmony_ci    {
3400f66f451Sopenharmony_ci      // Nope: save/expand argument and loop
3410f66f451Sopenharmony_ci      expand_arg(&pp->arg, s, 0, 0);
3420f66f451Sopenharmony_ci
3430f66f451Sopenharmony_ci      continue;
3440f66f451Sopenharmony_ci    }
3450f66f451Sopenharmony_ci
3460f66f451Sopenharmony_ci    // Yes. Expand rd[] and find first unused filehandle >10
3470f66f451Sopenharmony_ci    if (!(rdcount&31)) {
3480f66f451Sopenharmony_ci      if (rd) dlist_lpop((void *)rdlist);
3490f66f451Sopenharmony_ci      rd = xrealloc(rd, (2*rdcount+3+2*32)*sizeof(int *));
3500f66f451Sopenharmony_ci      dlist_add_nomalloc((void *)rdlist, (void *)rd);
3510f66f451Sopenharmony_ci    }
3520f66f451Sopenharmony_ci    rr = rd+3+rdcount;
3530f66f451Sopenharmony_ci    if (!hfd)
3540f66f451Sopenharmony_ci      for (hfd = 10; hfd<99999; hfd++) if (-1 == fcntl(hfd, F_GETFL)) break;
3550f66f451Sopenharmony_ci
3560f66f451Sopenharmony_ci    // error check: premature EOF, target fd too high, or redirect file splits
3570f66f451Sopenharmony_ci    if (++j == arg->c || (isdigit(*s) && ss-s>5)) goto flush;
3580f66f451Sopenharmony_ci    fd = pp->arg.c;
3590f66f451Sopenharmony_ci
3600f66f451Sopenharmony_ci    // expand arguments for everything but << and <<-
3610f66f451Sopenharmony_ci    if (strncmp(ss, "<<", 2) || ss[2] == '<') {
3620f66f451Sopenharmony_ci      expand_arg(&pp->arg, arg->v[j], NO_PATH|(NO_SPLIT*!strcmp(ss, "<<<")), 0);
3630f66f451Sopenharmony_ci      if (fd+1 != pp->arg.c) goto flush;
3640f66f451Sopenharmony_ci      sss = pp->arg.v[--pp->arg.c];
3650f66f451Sopenharmony_ci    } else dlist_add((void *)&pp->delete, sss = xstrdup(arg->v[j]));
3660f66f451Sopenharmony_ci
3670f66f451Sopenharmony_ci    // rd[] entries come in pairs: first is which fd gets redirected after
3680f66f451Sopenharmony_ci    // vfork(), I.E. the [n] part of [n]<word
3690f66f451Sopenharmony_ci
3700f66f451Sopenharmony_ci    if (isdigit(*ss)) fd = atoi(ss);
3710f66f451Sopenharmony_ci    else if (*ss == '{') {
3720f66f451Sopenharmony_ci      ss++;
3730f66f451Sopenharmony_ci      // when we close a filehandle, we _read_ from {var}, not write to it
3740f66f451Sopenharmony_ci      if ((!strcmp(s, "<&") || !strcmp(s, ">&")) && !strcmp(sss, "-")) {
3750f66f451Sopenharmony_ci        ss = xstrndup(ss, (s-ss)-1);
3760f66f451Sopenharmony_ci        sss = getvar(ss);
3770f66f451Sopenharmony_ci        free(ss);
3780f66f451Sopenharmony_ci        fd = -1;
3790f66f451Sopenharmony_ci        if (sss) fd = atoi(sss);
3800f66f451Sopenharmony_ci        if (fd<0) goto flush;
3810f66f451Sopenharmony_ci        if (fd>2) {
3820f66f451Sopenharmony_ci          rr[0] = fd;
3830f66f451Sopenharmony_ci          rr[1] = fd<<1; // close it
3840f66f451Sopenharmony_ci          rdcount++;
3850f66f451Sopenharmony_ci        }
3860f66f451Sopenharmony_ci        continue;
3870f66f451Sopenharmony_ci      } else setvar(xmprintf("%.*s=%d", (int)(s-ss), ss, hfd),  TAKE_MEM);
3880f66f451Sopenharmony_ci    } else fd = *ss != '<';
3890f66f451Sopenharmony_ci    *rr = fd;
3900f66f451Sopenharmony_ci
3910f66f451Sopenharmony_ci    // at this point for [n]<word s = start of [n], ss = start of <, sss = word
3920f66f451Sopenharmony_ci
3930f66f451Sopenharmony_ci    // second entry in this rd[] pair is new fd to dup2() after vfork(),
3940f66f451Sopenharmony_ci    // I.E. for [n]<word the fd if you open("word"). It's stored <<1 and the
3950f66f451Sopenharmony_ci    // low bit set means don't close(rr[1]) after dup2(rr[1]>>1, rr[0]);
3960f66f451Sopenharmony_ci
3970f66f451Sopenharmony_ci    // fd<0 means HERE document. Canned input stored earlier, becomes pipe later
3980f66f451Sopenharmony_ci    if (!strcmp(s, "<<<") || !strcmp(s, "<<-") || !strcmp(s, "<<")) {
3990f66f451Sopenharmony_ci      fd = --here<<2;
4000f66f451Sopenharmony_ci      if (s[2] == '-') fd += 1;          // zap tabs
4010f66f451Sopenharmony_ci      if (s[strcspn(s, "\"'")]) fd += 2; // it was quoted so no expansion
4020f66f451Sopenharmony_ci      rr[1] = fd;
4030f66f451Sopenharmony_ci      rdcount++;
4040f66f451Sopenharmony_ci
4050f66f451Sopenharmony_ci      continue;
4060f66f451Sopenharmony_ci    }
4070f66f451Sopenharmony_ci
4080f66f451Sopenharmony_ci    // Handle file descriptor duplication/close (&> &>> <& >& with number or -)
4090f66f451Sopenharmony_ci    if (strchr(ss, '&') && ss[2] != '>') {
4100f66f451Sopenharmony_ci      char *dig = sss;
4110f66f451Sopenharmony_ci
4120f66f451Sopenharmony_ci      // These redirect existing fd so nothing to open()
4130f66f451Sopenharmony_ci      while (isdigit(dig)) dig++;
4140f66f451Sopenharmony_ci      if (dig-sss>5) {
4150f66f451Sopenharmony_ci        s = sss;
4160f66f451Sopenharmony_ci        goto flush;
4170f66f451Sopenharmony_ci      }
4180f66f451Sopenharmony_ci
4190f66f451Sopenharmony_ci// TODO can't check if fd is open here, must do it when actual redirects happen
4200f66f451Sopenharmony_ci      if (!*dig || (*dig=='-' && !dig[1])) {
4210f66f451Sopenharmony_ci        rr[1] = (((dig==sss) ? *rr : atoi(sss))<<1)+(*dig != '-');
4220f66f451Sopenharmony_ci        rdcount++;
4230f66f451Sopenharmony_ci
4240f66f451Sopenharmony_ci        continue;
4250f66f451Sopenharmony_ci      }
4260f66f451Sopenharmony_ci    }
4270f66f451Sopenharmony_ci
4280f66f451Sopenharmony_ci    // Permissions to open external file with: < > >> <& >& <> >| &>> &>
4290f66f451Sopenharmony_ci    if (!strcmp(ss, "<>")) fd = O_CREAT|O_RDWR;
4300f66f451Sopenharmony_ci    else if (strstr(ss, ">>")) fd = O_CREAT|O_APPEND;
4310f66f451Sopenharmony_ci    else {
4320f66f451Sopenharmony_ci      fd = (*ss != '<') ? O_CREAT|O_WRONLY|O_TRUNC : O_RDONLY;
4330f66f451Sopenharmony_ci      if (!strcmp(ss, ">") && (TT.options&SH_NOCLOBBER)) {
4340f66f451Sopenharmony_ci        struct stat st;
4350f66f451Sopenharmony_ci
4360f66f451Sopenharmony_ci        // Not _just_ O_EXCL: > /dev/null allowed
4370f66f451Sopenharmony_ci        if (stat(sss, &st) || !S_ISREG(st.st_mode)) fd |= O_EXCL;
4380f66f451Sopenharmony_ci      }
4390f66f451Sopenharmony_ci    }
4400f66f451Sopenharmony_ci
4410f66f451Sopenharmony_ci    // Open the file
4420f66f451Sopenharmony_ci// TODO: /dev/fd/# /dev/{stdin,stdout,stderr} /dev/{tcp,udp}/host/port
4430f66f451Sopenharmony_ci    if (-1 == (fd = xcreate(sss, fd|WARN_ONLY, 777)) || hfd != dup2(fd, hfd)) {
4440f66f451Sopenharmony_ci      pp->exit = 1;
4450f66f451Sopenharmony_ci      s = 0;
4460f66f451Sopenharmony_ci
4470f66f451Sopenharmony_ci      goto flush;
4480f66f451Sopenharmony_ci    }
4490f66f451Sopenharmony_ci    if (fd != hfd) close(fd);
4500f66f451Sopenharmony_ci    rr[1] = hfd<<1;
4510f66f451Sopenharmony_ci    rdcount++;
4520f66f451Sopenharmony_ci
4530f66f451Sopenharmony_ci    // queue up a 2>&1 ?
4540f66f451Sopenharmony_ci    if (strchr(ss, '&')) {
4550f66f451Sopenharmony_ci      if (!(31&++rdcount)) rd = xrealloc(rd, (2*rdcount+66)*sizeof(int *));
4560f66f451Sopenharmony_ci      rr = rd+3+rdcount;
4570f66f451Sopenharmony_ci      rr[0] = 2;
4580f66f451Sopenharmony_ci      rr[1] = 1+(1<<1);
4590f66f451Sopenharmony_ci      rdcount++;
4600f66f451Sopenharmony_ci    }
4610f66f451Sopenharmony_ci  }
4620f66f451Sopenharmony_ci  if (rd) rd[2] = rdcount;
4630f66f451Sopenharmony_ci
4640f66f451Sopenharmony_ci// TODO: ok, now _use_ in_rd[in_rdcount] and rd[rdcount]. :)
4650f66f451Sopenharmony_ci
4660f66f451Sopenharmony_ci// TODO: handle ((math)) here
4670f66f451Sopenharmony_ci
4680f66f451Sopenharmony_ci// TODO use envlen
4690f66f451Sopenharmony_ci// TODO: check for functions
4700f66f451Sopenharmony_ci
4710f66f451Sopenharmony_ci  // Is this command a builtin that should run in this process?
4720f66f451Sopenharmony_ci  if ((tl = toy_find(*pp->arg.v))
4730f66f451Sopenharmony_ci    && (tl->flags & (TOYFLAG_NOFORK|TOYFLAG_MAYFORK)))
4740f66f451Sopenharmony_ci  {
4750f66f451Sopenharmony_ci    struct toy_context temp;
4760f66f451Sopenharmony_ci    sigjmp_buf rebound;
4770f66f451Sopenharmony_ci
4780f66f451Sopenharmony_ci    // This fakes lots of what toybox_main() does.
4790f66f451Sopenharmony_ci    memcpy(&temp, &toys, sizeof(struct toy_context));
4800f66f451Sopenharmony_ci    memset(&toys, 0, sizeof(struct toy_context));
4810f66f451Sopenharmony_ci
4820f66f451Sopenharmony_ci// TODO: redirect stdin/out
4830f66f451Sopenharmony_ci    if (!sigsetjmp(rebound, 1)) {
4840f66f451Sopenharmony_ci      toys.rebound = &rebound;
4850f66f451Sopenharmony_ci// must be null terminated
4860f66f451Sopenharmony_ci      toy_init(tl, pp->arg.v);
4870f66f451Sopenharmony_ci      tl->toy_main();
4880f66f451Sopenharmony_ci    }
4890f66f451Sopenharmony_ci    pp->exit = toys.exitval;
4900f66f451Sopenharmony_ci    if (toys.optargs != toys.argv+1) free(toys.optargs);
4910f66f451Sopenharmony_ci    if (toys.old_umask) umask(toys.old_umask);
4920f66f451Sopenharmony_ci    memcpy(&toys, &temp, sizeof(struct toy_context));
4930f66f451Sopenharmony_ci  } else {
4940f66f451Sopenharmony_ci    int pipe[2];
4950f66f451Sopenharmony_ci
4960f66f451Sopenharmony_ci    pipe[0] = 0;
4970f66f451Sopenharmony_ci    pipe[1] = 1;
4980f66f451Sopenharmony_ci// TODO: redirect and pipe
4990f66f451Sopenharmony_ci// TODO: redirecting stderr needs xpopen3() or rethink
5000f66f451Sopenharmony_ci    if (-1 == (pp->pid = xpopen_both(pp->arg.v, pipe)))
5010f66f451Sopenharmony_ci      perror_msg("%s: vfork", *pp->arg.v);
5020f66f451Sopenharmony_ci// TODO: don't close stdin/stdout!
5030f66f451Sopenharmony_ci    else pp->exit = xpclose_both(pp->pid, 0);
5040f66f451Sopenharmony_ci  }
5050f66f451Sopenharmony_ci
5060f66f451Sopenharmony_ci  s = 0;
5070f66f451Sopenharmony_ciflush:
5080f66f451Sopenharmony_ci  if (s) {
5090f66f451Sopenharmony_ci    syntax_err("bad %s", s);
5100f66f451Sopenharmony_ci    if (!pp->exit) pp->exit = 1;
5110f66f451Sopenharmony_ci  }
5120f66f451Sopenharmony_ci  for (j = 0; j<rdcount; j++) if (rd[4+2*j]>6) close(rd[4+2*j]>>1);
5130f66f451Sopenharmony_ci  if (rdcount) free(dlist_lpop((void *)rdlist));
5140f66f451Sopenharmony_ci
5150f66f451Sopenharmony_ci  return pp;
5160f66f451Sopenharmony_ci}
5170f66f451Sopenharmony_ci
5180f66f451Sopenharmony_ci// parse next word from command line. Returns end, or 0 if need continuation
5190f66f451Sopenharmony_ci// caller eats leading spaces
5200f66f451Sopenharmony_cistatic char *parse_word(char *start)
5210f66f451Sopenharmony_ci{
5220f66f451Sopenharmony_ci  int i, j, quote = 0, q, qc = 0;
5230f66f451Sopenharmony_ci  char *end = start, *s;
5240f66f451Sopenharmony_ci
5250f66f451Sopenharmony_ci  // (( is a special quote at the start of a word
5260f66f451Sopenharmony_ci  if (strstart(&end, "((")) toybuf[quote++] = 255;
5270f66f451Sopenharmony_ci
5280f66f451Sopenharmony_ci  // find end of this word
5290f66f451Sopenharmony_ci  while (*end) {
5300f66f451Sopenharmony_ci    i = 0;
5310f66f451Sopenharmony_ci
5320f66f451Sopenharmony_ci    // barf if we're near overloading quote stack (nesting ridiculously deep)
5330f66f451Sopenharmony_ci    if (quote>4000) {
5340f66f451Sopenharmony_ci      syntax_err("tilt");
5350f66f451Sopenharmony_ci      return (void *)1;
5360f66f451Sopenharmony_ci    }
5370f66f451Sopenharmony_ci
5380f66f451Sopenharmony_ci    q = quote ? toybuf[quote-1] : 0;
5390f66f451Sopenharmony_ci    // Handle quote contexts
5400f66f451Sopenharmony_ci    if (q) {
5410f66f451Sopenharmony_ci
5420f66f451Sopenharmony_ci      // when waiting for parentheses, they nest
5430f66f451Sopenharmony_ci      if ((q == ')' || q == '\xff') && (*end == '(' || *end == ')')) {
5440f66f451Sopenharmony_ci        if (*end == '(') qc++;
5450f66f451Sopenharmony_ci        else if (qc) qc--;
5460f66f451Sopenharmony_ci        else if (q == '\xff') {
5470f66f451Sopenharmony_ci          // (( can end with )) or retroactively become two (( if we hit one )
5480f66f451Sopenharmony_ci          if (strstart(&end, "))")) quote--;
5490f66f451Sopenharmony_ci          else return start+1;
5500f66f451Sopenharmony_ci        }
5510f66f451Sopenharmony_ci        end++;
5520f66f451Sopenharmony_ci
5530f66f451Sopenharmony_ci      // end quote?
5540f66f451Sopenharmony_ci      } else if (*end == q) quote--, end++;
5550f66f451Sopenharmony_ci
5560f66f451Sopenharmony_ci      // single quote claims everything
5570f66f451Sopenharmony_ci      else if (q == '\'') end++;
5580f66f451Sopenharmony_ci      else i++;
5590f66f451Sopenharmony_ci
5600f66f451Sopenharmony_ci      // loop if we already handled a symbol
5610f66f451Sopenharmony_ci      if (!i) continue;
5620f66f451Sopenharmony_ci    } else {
5630f66f451Sopenharmony_ci      // Things that only matter when unquoted
5640f66f451Sopenharmony_ci
5650f66f451Sopenharmony_ci      if (isspace(*end)) break;
5660f66f451Sopenharmony_ci
5670f66f451Sopenharmony_ci      // Things we should only return at the _start_ of a word
5680f66f451Sopenharmony_ci
5690f66f451Sopenharmony_ci      // Redirections. 123<<file- parses as 2 args: "123<<" "file-".
5700f66f451Sopenharmony_ci      // Greedy matching: >&; becomes >& ; not > &;
5710f66f451Sopenharmony_ci      s = end + redir_prefix(end);
5720f66f451Sopenharmony_ci      j = anystart(s, (char *[]){"<<<", "<<-", "<<", "<&", "<>", "<", ">>",
5730f66f451Sopenharmony_ci        ">&", ">|", ">", 0});
5740f66f451Sopenharmony_ci      if (j) s += j;
5750f66f451Sopenharmony_ci
5760f66f451Sopenharmony_ci      // Control characters
5770f66f451Sopenharmony_ci      else s = end + anystart(end, (char *[]){";;&", ";;", ";&", ";", "||",
5780f66f451Sopenharmony_ci          "|&", "|", "&&", "&>>", "&>", "&", "(", ")", 0});
5790f66f451Sopenharmony_ci      if (s != end) return (end == start) ? s : end;
5800f66f451Sopenharmony_ci      i++;
5810f66f451Sopenharmony_ci    }
5820f66f451Sopenharmony_ci
5830f66f451Sopenharmony_ci    // Things the same unquoted or in most non-single-quote contexts
5840f66f451Sopenharmony_ci
5850f66f451Sopenharmony_ci    // start new quote context?
5860f66f451Sopenharmony_ci    if (strchr("\"'`", *end)) toybuf[quote++] = *end++;
5870f66f451Sopenharmony_ci    else if (q != '"' && (strstart(&end, "<(") || strstart(&end,">(")))
5880f66f451Sopenharmony_ci      toybuf[quote++]=')';
5890f66f451Sopenharmony_ci
5900f66f451Sopenharmony_ci    // backslash escapes
5910f66f451Sopenharmony_ci    else if (*end == '\\') {
5920f66f451Sopenharmony_ci      if (!end[1] || (end[1]=='\n' && !end[2])) return 0;
5930f66f451Sopenharmony_ci      end += 2;
5940f66f451Sopenharmony_ci    } else if (*end++ == '$') {
5950f66f451Sopenharmony_ci      if (-1 != (i = stridx("({[", *end))) {
5960f66f451Sopenharmony_ci        toybuf[quote++] = ")}]"[i];
5970f66f451Sopenharmony_ci        end++;
5980f66f451Sopenharmony_ci      }
5990f66f451Sopenharmony_ci    }
6000f66f451Sopenharmony_ci  }
6010f66f451Sopenharmony_ci
6020f66f451Sopenharmony_ci  return quote ? 0 : end;
6030f66f451Sopenharmony_ci}
6040f66f451Sopenharmony_ci
6050f66f451Sopenharmony_ci// if then fi for while until select done done case esac break continue return
6060f66f451Sopenharmony_ci
6070f66f451Sopenharmony_ci// Allocate more space for arg, and possibly terminator
6080f66f451Sopenharmony_civoid argxtend(struct sh_arg *arg)
6090f66f451Sopenharmony_ci{
6100f66f451Sopenharmony_ci  if (!(arg->c&31)) arg->v = xrealloc(arg->v, (33+arg->c)*sizeof(void *));
6110f66f451Sopenharmony_ci}
6120f66f451Sopenharmony_ci
6130f66f451Sopenharmony_ci// Pipeline segments
6140f66f451Sopenharmony_cistruct sh_pipeline {
6150f66f451Sopenharmony_ci  struct sh_pipeline *next, *prev;
6160f66f451Sopenharmony_ci  int count, here, type;
6170f66f451Sopenharmony_ci  struct sh_arg arg[1];
6180f66f451Sopenharmony_ci};
6190f66f451Sopenharmony_ci
6200f66f451Sopenharmony_ci// run a series of "command | command && command" with redirects.
6210f66f451Sopenharmony_ciint run_pipeline(struct sh_pipeline **pl, int *rd)
6220f66f451Sopenharmony_ci{
6230f66f451Sopenharmony_ci  struct sh_process *pp;
6240f66f451Sopenharmony_ci  int rc = 0;
6250f66f451Sopenharmony_ci
6260f66f451Sopenharmony_ci  for (;;) {
6270f66f451Sopenharmony_ci// TODO job control
6280f66f451Sopenharmony_ci    if (!(pp = run_command((*pl)->arg, &rd))) rc = 0;
6290f66f451Sopenharmony_ci    else {
6300f66f451Sopenharmony_ci//wait4(pp);
6310f66f451Sopenharmony_ci      llist_traverse(pp->delete, free);
6320f66f451Sopenharmony_ci      rc = pp->exit;
6330f66f451Sopenharmony_ci      free(pp);
6340f66f451Sopenharmony_ci    }
6350f66f451Sopenharmony_ci
6360f66f451Sopenharmony_ci    if ((*pl)->next && !(*pl)->next->type) *pl = (*pl)->next;
6370f66f451Sopenharmony_ci    else return rc;
6380f66f451Sopenharmony_ci  }
6390f66f451Sopenharmony_ci}
6400f66f451Sopenharmony_ci
6410f66f451Sopenharmony_ci
6420f66f451Sopenharmony_ci
6430f66f451Sopenharmony_ci// scratch space (state held between calls). Don't want to make it global yet
6440f66f451Sopenharmony_ci// because this could be reentrant.
6450f66f451Sopenharmony_cistruct sh_function {
6460f66f451Sopenharmony_ci  char *name;
6470f66f451Sopenharmony_ci  struct sh_pipeline *pipeline;
6480f66f451Sopenharmony_ci  struct double_list *expect;
6490f66f451Sopenharmony_ci// TODO: lifetime rules for arg? remember "shift" command.
6500f66f451Sopenharmony_ci  struct sh_arg *arg; // arguments to function call
6510f66f451Sopenharmony_ci  char *end;
6520f66f451Sopenharmony_ci};
6530f66f451Sopenharmony_ci
6540f66f451Sopenharmony_ci// Free one pipeline segment.
6550f66f451Sopenharmony_civoid free_pipeline(void *pipeline)
6560f66f451Sopenharmony_ci{
6570f66f451Sopenharmony_ci  struct sh_pipeline *pl = pipeline;
6580f66f451Sopenharmony_ci  int i, j;
6590f66f451Sopenharmony_ci
6600f66f451Sopenharmony_ci  if (pl) for (j=0; j<=pl->count; j++) {
6610f66f451Sopenharmony_ci    for (i = 0; i<=pl->arg->c; i++)  free(pl->arg[j].v[i]);
6620f66f451Sopenharmony_ci    free(pl->arg[j].v);
6630f66f451Sopenharmony_ci  }
6640f66f451Sopenharmony_ci  free(pl);
6650f66f451Sopenharmony_ci}
6660f66f451Sopenharmony_ci
6670f66f451Sopenharmony_ci// Return end of current block, or NULL if we weren't in block and fell off end.
6680f66f451Sopenharmony_cistruct sh_pipeline *block_end(struct sh_pipeline *pl)
6690f66f451Sopenharmony_ci{
6700f66f451Sopenharmony_ci  int i = 0;
6710f66f451Sopenharmony_ci
6720f66f451Sopenharmony_ci  while (pl) {
6730f66f451Sopenharmony_ci    if (pl->type == 1 || pl->type == 'f') i++;
6740f66f451Sopenharmony_ci    else if (pl->type == 3) if (--i<1) break;
6750f66f451Sopenharmony_ci    pl = pl->next;
6760f66f451Sopenharmony_ci  }
6770f66f451Sopenharmony_ci
6780f66f451Sopenharmony_ci  return 0;
6790f66f451Sopenharmony_ci}
6800f66f451Sopenharmony_ci
6810f66f451Sopenharmony_civoid free_function(struct sh_function *sp)
6820f66f451Sopenharmony_ci{
6830f66f451Sopenharmony_ci  llist_traverse(sp->pipeline, free_pipeline);
6840f66f451Sopenharmony_ci  llist_traverse(sp->expect, free);
6850f66f451Sopenharmony_ci  memset(sp, 0, sizeof(struct sh_function));
6860f66f451Sopenharmony_ci}
6870f66f451Sopenharmony_ci
6880f66f451Sopenharmony_ci// TODO this has to add to a namespace context. Functions within functions...
6890f66f451Sopenharmony_cistruct sh_pipeline *add_function(char *name, struct sh_pipeline *pl)
6900f66f451Sopenharmony_ci{
6910f66f451Sopenharmony_cidprintf(2, "stub add_function");
6920f66f451Sopenharmony_ci
6930f66f451Sopenharmony_ci  return block_end(pl->next);
6940f66f451Sopenharmony_ci}
6950f66f451Sopenharmony_ci
6960f66f451Sopenharmony_ci// Add a line of shell script to a shell function. Returns 0 if finished,
6970f66f451Sopenharmony_ci// 1 to request another line of input (> prompt), -1 for syntax err
6980f66f451Sopenharmony_cistatic int parse_line(char *line, struct sh_function *sp)
6990f66f451Sopenharmony_ci{
7000f66f451Sopenharmony_ci  char *start = line, *delete = 0, *end, *last = 0, *s, *ex, done = 0;
7010f66f451Sopenharmony_ci  struct sh_pipeline *pl = sp->pipeline ? sp->pipeline->prev : 0;
7020f66f451Sopenharmony_ci  struct sh_arg *arg = 0;
7030f66f451Sopenharmony_ci  long i;
7040f66f451Sopenharmony_ci
7050f66f451Sopenharmony_ci  // Resume appending to last statement?
7060f66f451Sopenharmony_ci  if (pl) {
7070f66f451Sopenharmony_ci    arg = pl->arg;
7080f66f451Sopenharmony_ci
7090f66f451Sopenharmony_ci    // Extend/resume quoted block
7100f66f451Sopenharmony_ci    if (arg->c<0) {
7110f66f451Sopenharmony_ci      delete = start = xmprintf("%s%s", arg->v[arg->c = (-arg->c)-1], start);
7120f66f451Sopenharmony_ci      free(arg->v[arg->c]);
7130f66f451Sopenharmony_ci      arg->v[arg->c] = 0;
7140f66f451Sopenharmony_ci
7150f66f451Sopenharmony_ci    // is a HERE document in progress?
7160f66f451Sopenharmony_ci    } else if (pl->count != pl->here) {
7170f66f451Sopenharmony_ci      arg += 1+pl->here;
7180f66f451Sopenharmony_ci
7190f66f451Sopenharmony_ci      argxtend(arg);
7200f66f451Sopenharmony_ci      if (strcmp(line, arg->v[arg->c])) {
7210f66f451Sopenharmony_ci        // Add this line
7220f66f451Sopenharmony_ci        arg->v[arg->c+1] = arg->v[arg->c];
7230f66f451Sopenharmony_ci        arg->v[arg->c++] = xstrdup(line);
7240f66f451Sopenharmony_ci      // EOF hit, end HERE document
7250f66f451Sopenharmony_ci      } else {
7260f66f451Sopenharmony_ci        arg->v[arg->c] = 0;
7270f66f451Sopenharmony_ci        pl->here++;
7280f66f451Sopenharmony_ci      }
7290f66f451Sopenharmony_ci      start = 0;
7300f66f451Sopenharmony_ci
7310f66f451Sopenharmony_ci    // Nope, new segment
7320f66f451Sopenharmony_ci    } else pl = 0;
7330f66f451Sopenharmony_ci  }
7340f66f451Sopenharmony_ci
7350f66f451Sopenharmony_ci  // Parse words, assemble argv[] pipelines, check flow control and HERE docs
7360f66f451Sopenharmony_ci  if (start) for (;;) {
7370f66f451Sopenharmony_ci    ex = sp->expect ? sp->expect->prev->data : 0;
7380f66f451Sopenharmony_ci
7390f66f451Sopenharmony_ci    // Look for << HERE redirections in completed pipeline segment
7400f66f451Sopenharmony_ci    if (pl && pl->count == -1) {
7410f66f451Sopenharmony_ci      pl->count = 0;
7420f66f451Sopenharmony_ci      arg = pl->arg;
7430f66f451Sopenharmony_ci
7440f66f451Sopenharmony_ci      // find arguments of the form [{n}]<<[-] with another one after it
7450f66f451Sopenharmony_ci      for (i = 0; i<arg->c; i++) {
7460f66f451Sopenharmony_ci        s = arg->v[i] + redir_prefix(arg->v[i]);
7470f66f451Sopenharmony_ci        if (strcmp(s, "<<") && strcmp(s, "<<-") && strcmp(s, "<<<")) continue;
7480f66f451Sopenharmony_ci        if (i+1 == arg->c) goto flush;
7490f66f451Sopenharmony_ci
7500f66f451Sopenharmony_ci        // Add another arg[] to the pipeline segment (removing/readding to list
7510f66f451Sopenharmony_ci        // because realloc can move pointer)
7520f66f451Sopenharmony_ci        dlist_lpop(&sp->pipeline);
7530f66f451Sopenharmony_ci        pl = xrealloc(pl, sizeof(*pl) + ++pl->count*sizeof(struct sh_arg));
7540f66f451Sopenharmony_ci        dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
7550f66f451Sopenharmony_ci
7560f66f451Sopenharmony_ci        // queue up HERE EOF so input loop asks for more lines.
7570f66f451Sopenharmony_ci        arg[pl->count].v = xzalloc(2*sizeof(void *));
7580f66f451Sopenharmony_ci        *arg[pl->count].v = arg->v[++i];
7590f66f451Sopenharmony_ci        arg[pl->count].c = -(s[2] == '<'); // note <<< as c = -1
7600f66f451Sopenharmony_ci      }
7610f66f451Sopenharmony_ci      pl = 0;
7620f66f451Sopenharmony_ci    }
7630f66f451Sopenharmony_ci    if (done) break;
7640f66f451Sopenharmony_ci    s = 0;
7650f66f451Sopenharmony_ci
7660f66f451Sopenharmony_ci    // skip leading whitespace/comment here to know where next word starts
7670f66f451Sopenharmony_ci    for (;;) {
7680f66f451Sopenharmony_ci      if (isspace(*start)) ++start;
7690f66f451Sopenharmony_ci      else if (*start=='#') while (*start && *start != '\n') ++start;
7700f66f451Sopenharmony_ci      else break;
7710f66f451Sopenharmony_ci    }
7720f66f451Sopenharmony_ci
7730f66f451Sopenharmony_ci    // Parse next word and detect overflow (too many nested quotes).
7740f66f451Sopenharmony_ci    if ((end = parse_word(start)) == (void *)1)
7750f66f451Sopenharmony_ci      goto flush;
7760f66f451Sopenharmony_ci
7770f66f451Sopenharmony_ci    // Is this a new pipeline segment?
7780f66f451Sopenharmony_ci    if (!pl) {
7790f66f451Sopenharmony_ci      pl = xzalloc(sizeof(struct sh_pipeline));
7800f66f451Sopenharmony_ci      arg = pl->arg;
7810f66f451Sopenharmony_ci      dlist_add_nomalloc((void *)&sp->pipeline, (void *)pl);
7820f66f451Sopenharmony_ci    }
7830f66f451Sopenharmony_ci    argxtend(arg);
7840f66f451Sopenharmony_ci
7850f66f451Sopenharmony_ci    // Do we need to request another line to finish word (find ending quote)?
7860f66f451Sopenharmony_ci    if (!end) {
7870f66f451Sopenharmony_ci      // Save unparsed bit of this line, we'll need to re-parse it.
7880f66f451Sopenharmony_ci      arg->v[arg->c] = xstrndup(start, strlen(start));
7890f66f451Sopenharmony_ci      arg->c = -(arg->c+1);
7900f66f451Sopenharmony_ci      free(delete);
7910f66f451Sopenharmony_ci
7920f66f451Sopenharmony_ci      return 1;
7930f66f451Sopenharmony_ci    }
7940f66f451Sopenharmony_ci
7950f66f451Sopenharmony_ci    // Ok, we have a word. What does it _mean_?
7960f66f451Sopenharmony_ci
7970f66f451Sopenharmony_ci    // Did we hit end of line or ) outside a function declaration?
7980f66f451Sopenharmony_ci    // ) is only saved at start of a statement, ends current statement
7990f66f451Sopenharmony_ci    if (end == start || (arg->c && *start == ')' && pl->type!='f')) {
8000f66f451Sopenharmony_ci      arg->v[arg->c] = 0;
8010f66f451Sopenharmony_ci
8020f66f451Sopenharmony_ci      if (pl->type == 'f' && arg->c<3) {
8030f66f451Sopenharmony_ci        s = "function()";
8040f66f451Sopenharmony_ci        goto flush;
8050f66f451Sopenharmony_ci      }
8060f66f451Sopenharmony_ci
8070f66f451Sopenharmony_ci      // "for" on its own line is an error.
8080f66f451Sopenharmony_ci      if (arg->c == 1 && ex && !memcmp(ex, "do\0A", 4)) {
8090f66f451Sopenharmony_ci        s = "newline";
8100f66f451Sopenharmony_ci        goto flush;
8110f66f451Sopenharmony_ci      }
8120f66f451Sopenharmony_ci
8130f66f451Sopenharmony_ci      // don't save blank pipeline segments
8140f66f451Sopenharmony_ci      if (!arg->c) free_pipeline(dlist_lpop(&sp->pipeline));
8150f66f451Sopenharmony_ci
8160f66f451Sopenharmony_ci      // stop at EOL, else continue with new pipeline segment for )
8170f66f451Sopenharmony_ci      if (end == start) done++;
8180f66f451Sopenharmony_ci      pl->count = -1;
8190f66f451Sopenharmony_ci      last = 0;
8200f66f451Sopenharmony_ci
8210f66f451Sopenharmony_ci      continue;
8220f66f451Sopenharmony_ci    }
8230f66f451Sopenharmony_ci
8240f66f451Sopenharmony_ci    // Save argument (strdup) and check for flow control
8250f66f451Sopenharmony_ci    s = arg->v[arg->c] = xstrndup(start, end-start);
8260f66f451Sopenharmony_ci    start = end;
8270f66f451Sopenharmony_ci    if (strchr(";|&", *s)) {
8280f66f451Sopenharmony_ci
8290f66f451Sopenharmony_ci      // flow control without a statement is an error
8300f66f451Sopenharmony_ci      if (!arg->c) goto flush;
8310f66f451Sopenharmony_ci
8320f66f451Sopenharmony_ci      // treat ; as newline so we don't have to check both elsewhere.
8330f66f451Sopenharmony_ci      if (!strcmp(s, ";")) {
8340f66f451Sopenharmony_ci        arg->v[arg->c] = 0;
8350f66f451Sopenharmony_ci        free(s);
8360f66f451Sopenharmony_ci        s = 0;
8370f66f451Sopenharmony_ci      }
8380f66f451Sopenharmony_ci      last = s;
8390f66f451Sopenharmony_ci      pl->count = -1;
8400f66f451Sopenharmony_ci
8410f66f451Sopenharmony_ci      continue;
8420f66f451Sopenharmony_ci    } else arg->v[++arg->c] = 0;
8430f66f451Sopenharmony_ci
8440f66f451Sopenharmony_ci    // is a function() in progress?
8450f66f451Sopenharmony_ci    if (arg->c>1 && !strcmp(s, "(")) pl->type = 'f';
8460f66f451Sopenharmony_ci    if (pl->type=='f') {
8470f66f451Sopenharmony_ci      if (arg->c == 2 && strcmp(s, "(")) goto flush;
8480f66f451Sopenharmony_ci      if (arg->c == 3) {
8490f66f451Sopenharmony_ci        if (strcmp(s, ")")) goto flush;
8500f66f451Sopenharmony_ci
8510f66f451Sopenharmony_ci        // end function segment, expect function body
8520f66f451Sopenharmony_ci        pl->count = -1;
8530f66f451Sopenharmony_ci        last = 0;
8540f66f451Sopenharmony_ci        dlist_add(&sp->expect, "}");
8550f66f451Sopenharmony_ci        dlist_add(&sp->expect, 0);
8560f66f451Sopenharmony_ci        dlist_add(&sp->expect, "{");
8570f66f451Sopenharmony_ci
8580f66f451Sopenharmony_ci        continue;
8590f66f451Sopenharmony_ci      }
8600f66f451Sopenharmony_ci
8610f66f451Sopenharmony_ci    // a for/select must have at least one additional argument on same line
8620f66f451Sopenharmony_ci    } else if (ex && !memcmp(ex, "do\0A", 4)) {
8630f66f451Sopenharmony_ci
8640f66f451Sopenharmony_ci      // Sanity check and break the segment
8650f66f451Sopenharmony_ci      if (strncmp(s, "((", 2) && strchr(s, '=')) goto flush;
8660f66f451Sopenharmony_ci      pl->count = -1;
8670f66f451Sopenharmony_ci      sp->expect->prev->data = "do\0C";
8680f66f451Sopenharmony_ci
8690f66f451Sopenharmony_ci      continue;
8700f66f451Sopenharmony_ci
8710f66f451Sopenharmony_ci    // flow control is the first word of a pipeline segment
8720f66f451Sopenharmony_ci    } else if (arg->c>1) continue;
8730f66f451Sopenharmony_ci
8740f66f451Sopenharmony_ci    // Do we expect something that _must_ come next? (no multiple statements)
8750f66f451Sopenharmony_ci    if (ex) {
8760f66f451Sopenharmony_ci      // When waiting for { it must be next symbol, but can be on a new line.
8770f66f451Sopenharmony_ci      if (!strcmp(ex, "{")) {
8780f66f451Sopenharmony_ci        if (strcmp(s, "{")) goto flush;
8790f66f451Sopenharmony_ci        free(arg->v[--arg->c]);  // don't save the {, function starts the block
8800f66f451Sopenharmony_ci        free(dlist_lpop(&sp->expect));
8810f66f451Sopenharmony_ci
8820f66f451Sopenharmony_ci        continue;
8830f66f451Sopenharmony_ci
8840f66f451Sopenharmony_ci      // The "test" part of for/select loops can have (at most) one "in" line,
8850f66f451Sopenharmony_ci      // for {((;;))|name [in...]} do
8860f66f451Sopenharmony_ci      } else if (!memcmp(ex, "do\0C", 4)) {
8870f66f451Sopenharmony_ci        if (strcmp(s, "do")) {
8880f66f451Sopenharmony_ci          // can only have one "in" line between for/do, but not with for(())
8890f66f451Sopenharmony_ci          if (!pl->prev->type) goto flush;
8900f66f451Sopenharmony_ci          if (!strncmp(pl->prev->arg->v[1], "((", 2)) goto flush;
8910f66f451Sopenharmony_ci          else if (strcmp(s, "in")) goto flush;
8920f66f451Sopenharmony_ci
8930f66f451Sopenharmony_ci          continue;
8940f66f451Sopenharmony_ci        }
8950f66f451Sopenharmony_ci      }
8960f66f451Sopenharmony_ci    }
8970f66f451Sopenharmony_ci
8980f66f451Sopenharmony_ci    // start of a new block?
8990f66f451Sopenharmony_ci
9000f66f451Sopenharmony_ci    // for/select requires variable name on same line, can't break segment yet
9010f66f451Sopenharmony_ci    if (!strcmp(s, "for") || !strcmp(s, "select")) {
9020f66f451Sopenharmony_ci      if (!pl->type) pl->type = 1;
9030f66f451Sopenharmony_ci      dlist_add(&sp->expect, "do\0A");
9040f66f451Sopenharmony_ci
9050f66f451Sopenharmony_ci      continue;
9060f66f451Sopenharmony_ci    }
9070f66f451Sopenharmony_ci
9080f66f451Sopenharmony_ci    end = 0;
9090f66f451Sopenharmony_ci    if (!strcmp(s, "if")) end = "then";
9100f66f451Sopenharmony_ci    else if (!strcmp(s, "while") || !strcmp(s, "until")) end = "do\0B";
9110f66f451Sopenharmony_ci    else if (!strcmp(s, "case")) end = "esac";
9120f66f451Sopenharmony_ci    else if (!strcmp(s, "{")) end = "}";
9130f66f451Sopenharmony_ci    else if (!strcmp(s, "[[")) end = "]]";
9140f66f451Sopenharmony_ci    else if (!strcmp(s, "(")) end = ")";
9150f66f451Sopenharmony_ci
9160f66f451Sopenharmony_ci    // Expecting NULL means a statement: I.E. any otherwise unrecognized word
9170f66f451Sopenharmony_ci    else if (sp->expect && !ex) {
9180f66f451Sopenharmony_ci      free(dlist_lpop(&sp->expect));
9190f66f451Sopenharmony_ci      continue;
9200f66f451Sopenharmony_ci    } else if (!ex) goto check;
9210f66f451Sopenharmony_ci
9220f66f451Sopenharmony_ci    // Did we start a new statement?
9230f66f451Sopenharmony_ci    if (end) {
9240f66f451Sopenharmony_ci      pl->type = 1;
9250f66f451Sopenharmony_ci
9260f66f451Sopenharmony_ci      // Only innermost statement needed in { { { echo ;} ;} ;} and such
9270f66f451Sopenharmony_ci      if (sp->expect && !sp->expect->prev->data) free(dlist_lpop(&sp->expect));
9280f66f451Sopenharmony_ci
9290f66f451Sopenharmony_ci    // If we got here we expect a specific word to end this block: is this it?
9300f66f451Sopenharmony_ci    } else if (!strcmp(s, ex)) {
9310f66f451Sopenharmony_ci      // can't "if | then" or "while && do", only ; & or newline works
9320f66f451Sopenharmony_ci      if (last && (strcmp(ex, "then") || strcmp(last, "&"))) {
9330f66f451Sopenharmony_ci        s = end;
9340f66f451Sopenharmony_ci        goto flush;
9350f66f451Sopenharmony_ci      }
9360f66f451Sopenharmony_ci
9370f66f451Sopenharmony_ci      free(dlist_lpop(&sp->expect));
9380f66f451Sopenharmony_ci      pl->type = anystr(s, (char *[]){"fi", "done", "esac", "}", "]]", ")", 0})
9390f66f451Sopenharmony_ci        ? 3 : 2;
9400f66f451Sopenharmony_ci
9410f66f451Sopenharmony_ci      // if it's a multipart block, what comes next?
9420f66f451Sopenharmony_ci      if (!strcmp(s, "do")) end = "done";
9430f66f451Sopenharmony_ci      else if (!strcmp(s, "then")) end = "fi\0A";
9440f66f451Sopenharmony_ci
9450f66f451Sopenharmony_ci    // fi could have elif, which queues a then.
9460f66f451Sopenharmony_ci    } else if (!strcmp(ex, "fi")) {
9470f66f451Sopenharmony_ci      if (!strcmp(s, "elif")) {
9480f66f451Sopenharmony_ci        free(dlist_lpop(&sp->expect));
9490f66f451Sopenharmony_ci        end = "then";
9500f66f451Sopenharmony_ci      // catch duplicate else while we're here
9510f66f451Sopenharmony_ci      } else if (!strcmp(s, "else")) {
9520f66f451Sopenharmony_ci        if (ex[3] != 'A') {
9530f66f451Sopenharmony_ci          s = "2 else";
9540f66f451Sopenharmony_ci          goto flush;
9550f66f451Sopenharmony_ci        }
9560f66f451Sopenharmony_ci        free(dlist_lpop(&sp->expect));
9570f66f451Sopenharmony_ci        end = "fi\0B";
9580f66f451Sopenharmony_ci      }
9590f66f451Sopenharmony_ci    }
9600f66f451Sopenharmony_ci
9610f66f451Sopenharmony_ci    // Do we need to queue up the next thing to expect?
9620f66f451Sopenharmony_ci    if (end) {
9630f66f451Sopenharmony_ci      if (!pl->type) pl->type = 2;
9640f66f451Sopenharmony_ci      dlist_add(&sp->expect, end);
9650f66f451Sopenharmony_ci      dlist_add(&sp->expect, 0);    // they're all preceded by a statement
9660f66f451Sopenharmony_ci      pl->count = -1;
9670f66f451Sopenharmony_ci    }
9680f66f451Sopenharmony_ci
9690f66f451Sopenharmony_cicheck:
9700f66f451Sopenharmony_ci    // syntax error check: these can't be the first word in an unexpected place
9710f66f451Sopenharmony_ci    if (!pl->type && anystr(s, (char *[]){"then", "do", "esac", "}", "]]", ")",
9720f66f451Sopenharmony_ci        "done", "fi", "elif", "else", 0})) goto flush;
9730f66f451Sopenharmony_ci  }
9740f66f451Sopenharmony_ci  free(delete);
9750f66f451Sopenharmony_ci
9760f66f451Sopenharmony_ci  // advance past <<< arguments (stored as here documents, but no new input)
9770f66f451Sopenharmony_ci  pl = sp->pipeline->prev;
9780f66f451Sopenharmony_ci  while (pl->count<pl->here && pl->arg[pl->count].c<0)
9790f66f451Sopenharmony_ci    pl->arg[pl->count++].c = 0;
9800f66f451Sopenharmony_ci
9810f66f451Sopenharmony_ci  // return if HERE document pending or more flow control needed to complete
9820f66f451Sopenharmony_ci  if (sp->expect) return 1;
9830f66f451Sopenharmony_ci  if (sp->pipeline && pl->count != pl->here) return 1;
9840f66f451Sopenharmony_ci  dlist_terminate(sp->pipeline);
9850f66f451Sopenharmony_ci
9860f66f451Sopenharmony_ci  // Don't need more input, can start executing.
9870f66f451Sopenharmony_ci
9880f66f451Sopenharmony_ci  return 0;
9890f66f451Sopenharmony_ci
9900f66f451Sopenharmony_ciflush:
9910f66f451Sopenharmony_ci  if (s) syntax_err("bad %s", s);
9920f66f451Sopenharmony_ci  free_function(sp);
9930f66f451Sopenharmony_ci
9940f66f451Sopenharmony_ci  return 0-!!s;
9950f66f451Sopenharmony_ci}
9960f66f451Sopenharmony_ci
9970f66f451Sopenharmony_cistatic void dump_state(struct sh_function *sp)
9980f66f451Sopenharmony_ci{
9990f66f451Sopenharmony_ci  struct sh_pipeline *pl;
10000f66f451Sopenharmony_ci  int q = 0;
10010f66f451Sopenharmony_ci  long i;
10020f66f451Sopenharmony_ci
10030f66f451Sopenharmony_ci  if (sp->expect) {
10040f66f451Sopenharmony_ci    struct double_list *dl;
10050f66f451Sopenharmony_ci
10060f66f451Sopenharmony_ci    for (dl = sp->expect; dl; dl = (dl->next == sp->expect) ? 0 : dl->next)
10070f66f451Sopenharmony_ci      dprintf(2, "expecting %s\n", dl->data);
10080f66f451Sopenharmony_ci    if (sp->pipeline)
10090f66f451Sopenharmony_ci      dprintf(2, "pipeline count=%d here=%d\n", sp->pipeline->prev->count,
10100f66f451Sopenharmony_ci        sp->pipeline->prev->here);
10110f66f451Sopenharmony_ci  }
10120f66f451Sopenharmony_ci
10130f66f451Sopenharmony_ci  for (pl = sp->pipeline; pl ; pl = (pl->next == sp->pipeline) ? 0 : pl->next) {
10140f66f451Sopenharmony_ci    for (i = 0; i<pl->arg->c; i++)
10150f66f451Sopenharmony_ci      printf("arg[%d][%ld]=%s\n", q, i, pl->arg->v[i]);
10160f66f451Sopenharmony_ci    printf("type=%d term[%d]=%s\n", pl->type, q++, pl->arg->v[pl->arg->c]);
10170f66f451Sopenharmony_ci  }
10180f66f451Sopenharmony_ci}
10190f66f451Sopenharmony_ci
10200f66f451Sopenharmony_ci/* Flow control statements:
10210f66f451Sopenharmony_ci
10220f66f451Sopenharmony_ci  if/then/elif/else/fi, for select while until/do/done, case/esac,
10230f66f451Sopenharmony_ci  {/}, [[/]], (/), function assignment
10240f66f451Sopenharmony_ci*/
10250f66f451Sopenharmony_ci
10260f66f451Sopenharmony_ci
10270f66f451Sopenharmony_ci
10280f66f451Sopenharmony_ci// run a shell function, handling flow control statements
10290f66f451Sopenharmony_cistatic void run_function(struct sh_function *sp)
10300f66f451Sopenharmony_ci{
10310f66f451Sopenharmony_ci  struct sh_pipeline *pl = sp->pipeline, *end;
10320f66f451Sopenharmony_ci  struct blockstack {
10330f66f451Sopenharmony_ci    struct blockstack *next;
10340f66f451Sopenharmony_ci    struct sh_pipeline *start, *end;
10350f66f451Sopenharmony_ci    int run, loop, *redir;
10360f66f451Sopenharmony_ci
10370f66f451Sopenharmony_ci    struct sh_arg farg;          // for/select arg stack
10380f66f451Sopenharmony_ci    struct string_list *fdelete; // farg's cleanup list
10390f66f451Sopenharmony_ci    char *fvar;                  // for/select's iteration variable name
10400f66f451Sopenharmony_ci  } *blk = 0, *new;
10410f66f451Sopenharmony_ci  long i;
10420f66f451Sopenharmony_ci
10430f66f451Sopenharmony_ci  // iterate through the commands
10440f66f451Sopenharmony_ci  while (pl) {
10450f66f451Sopenharmony_ci    char *s = *pl->arg->v, *ss = pl->arg->v[1];
10460f66f451Sopenharmony_ci//dprintf(2, "s=%s %s %d %s %d\n", s, ss, pl->type, blk ? blk->start->arg->v[0] : "X", blk ? blk->run : 0);
10470f66f451Sopenharmony_ci    // Normal executable statement?
10480f66f451Sopenharmony_ci    if (!pl->type) {
10490f66f451Sopenharmony_ci// TODO: break & is supported? Seriously? Also break > potato
10500f66f451Sopenharmony_ci// TODO: break multiple aguments
10510f66f451Sopenharmony_ci      if (!strcmp(s, "break") || !strcmp(s, "continue")) {
10520f66f451Sopenharmony_ci
10530f66f451Sopenharmony_ci        // How many layers to peel off?
10540f66f451Sopenharmony_ci        i = ss ? atol(ss) : 0;
10550f66f451Sopenharmony_ci        if (i<1) i = 1;
10560f66f451Sopenharmony_ci        if (!blk || pl->arg->c>2 || ss[strspn(ss, "0123456789")]) {
10570f66f451Sopenharmony_ci          syntax_err("bad %s", s);
10580f66f451Sopenharmony_ci          break;
10590f66f451Sopenharmony_ci        }
10600f66f451Sopenharmony_ci        i = atol(ss);
10610f66f451Sopenharmony_ci        if (!i) i++;
10620f66f451Sopenharmony_ci        while (i && blk) {
10630f66f451Sopenharmony_ci          if (--i && *s == 'c') {
10640f66f451Sopenharmony_ci            pl = blk->start;
10650f66f451Sopenharmony_ci            break;
10660f66f451Sopenharmony_ci          }
10670f66f451Sopenharmony_ci          pl = blk->end;
10680f66f451Sopenharmony_ci          llist_traverse(blk->fdelete, free);
10690f66f451Sopenharmony_ci          free(llist_pop(&blk));
10700f66f451Sopenharmony_ci        }
10710f66f451Sopenharmony_ci        pl = pl->next;
10720f66f451Sopenharmony_ci
10730f66f451Sopenharmony_ci        continue;
10740f66f451Sopenharmony_ci      }
10750f66f451Sopenharmony_ci
10760f66f451Sopenharmony_ci// inherit redirects?
10770f66f451Sopenharmony_ci// returns last statement of pipeline
10780f66f451Sopenharmony_ci      if (!blk) toys.exitval = run_pipeline(&pl, 0);
10790f66f451Sopenharmony_ci      else if (blk->run) toys.exitval = run_pipeline(&pl, blk->redir);
10800f66f451Sopenharmony_ci      else while (pl->next && !pl->next->type) pl = pl->next;
10810f66f451Sopenharmony_ci
10820f66f451Sopenharmony_ci    // Starting a new block?
10830f66f451Sopenharmony_ci    } else if (pl->type == 1) {
10840f66f451Sopenharmony_ci
10850f66f451Sopenharmony_ci      // are we entering this block (rather than looping back to it)?
10860f66f451Sopenharmony_ci      if (!blk || blk->start != pl) {
10870f66f451Sopenharmony_ci
10880f66f451Sopenharmony_ci        // If it's a nested block we're not running, skip ahead.
10890f66f451Sopenharmony_ci        end = block_end(pl->next);
10900f66f451Sopenharmony_ci        if (blk && !blk->run) {
10910f66f451Sopenharmony_ci          pl = end;
10920f66f451Sopenharmony_ci          if (pl) pl = pl->next;
10930f66f451Sopenharmony_ci          continue;
10940f66f451Sopenharmony_ci        }
10950f66f451Sopenharmony_ci
10960f66f451Sopenharmony_ci        // It's a new block we're running, save context and add it to the stack.
10970f66f451Sopenharmony_ci        new = xzalloc(sizeof(*blk));
10980f66f451Sopenharmony_ci        new->next = blk;
10990f66f451Sopenharmony_ci        blk = new;
11000f66f451Sopenharmony_ci        blk->start = pl;
11010f66f451Sopenharmony_ci        blk->end = end;
11020f66f451Sopenharmony_ci        blk->run = 1;
11030f66f451Sopenharmony_ci// TODO perform block end redirects to blk->redir
11040f66f451Sopenharmony_ci      }
11050f66f451Sopenharmony_ci
11060f66f451Sopenharmony_ci      // What flow control statement is this?
11070f66f451Sopenharmony_ci
11080f66f451Sopenharmony_ci      // if/then/elif/else/fi, while until/do/done - no special handling needed
11090f66f451Sopenharmony_ci
11100f66f451Sopenharmony_ci      // for select/do/done
11110f66f451Sopenharmony_ci      if (!strcmp(s, "for") || !strcmp(s, "select")) {
11120f66f451Sopenharmony_ci        if (blk->loop);
11130f66f451Sopenharmony_ci        else if (!strncmp(blk->fvar = ss, "((", 2)) {
11140f66f451Sopenharmony_ci          blk->loop = 1;
11150f66f451Sopenharmony_cidprintf(2, "TODO skipped init for((;;)), need math parser\n");
11160f66f451Sopenharmony_ci        } else {
11170f66f451Sopenharmony_ci
11180f66f451Sopenharmony_ci          // populate blk->farg with expanded arguments
11190f66f451Sopenharmony_ci          if (!pl->next->type) {
11200f66f451Sopenharmony_ci            for (i = 1; i<pl->next->arg->c; i++)
11210f66f451Sopenharmony_ci              expand_arg(&blk->farg, pl->next->arg->v[i], 0, &blk->fdelete);
11220f66f451Sopenharmony_ci          } else expand_arg(&blk->farg, "\"$@\"", 0, &blk->fdelete);
11230f66f451Sopenharmony_ci        }
11240f66f451Sopenharmony_ci        pl = pl->next;
11250f66f451Sopenharmony_ci      }
11260f66f451Sopenharmony_ci
11270f66f451Sopenharmony_ci/* TODO
11280f66f451Sopenharmony_cicase/esac
11290f66f451Sopenharmony_ci{/}
11300f66f451Sopenharmony_ci[[/]]
11310f66f451Sopenharmony_ci(/)
11320f66f451Sopenharmony_ci((/))
11330f66f451Sopenharmony_cifunction/}
11340f66f451Sopenharmony_ci*/
11350f66f451Sopenharmony_ci
11360f66f451Sopenharmony_ci    // gearshift from block start to block body
11370f66f451Sopenharmony_ci    } else if (pl->type == 2) {
11380f66f451Sopenharmony_ci
11390f66f451Sopenharmony_ci      // Handle if statement
11400f66f451Sopenharmony_ci      if (!strcmp(s, "then")) blk->run = blk->run && !toys.exitval;
11410f66f451Sopenharmony_ci      else if (!strcmp(s, "else") || !strcmp(s, "elif")) blk->run = !blk->run;
11420f66f451Sopenharmony_ci      else if (!strcmp(s, "do")) {
11430f66f451Sopenharmony_ci        ss = *blk->start->arg->v;
11440f66f451Sopenharmony_ci        if (!strcmp(ss, "while")) blk->run = blk->run && !toys.exitval;
11450f66f451Sopenharmony_ci        else if (!strcmp(ss, "until")) blk->run = blk->run && toys.exitval;
11460f66f451Sopenharmony_ci        else if (blk->loop >= blk->farg.c) {
11470f66f451Sopenharmony_ci          blk->run = 0;
11480f66f451Sopenharmony_ci          pl = block_end(pl);
11490f66f451Sopenharmony_ci          continue;
11500f66f451Sopenharmony_ci        } else if (!strncmp(blk->fvar, "((", 2)) {
11510f66f451Sopenharmony_cidprintf(2, "TODO skipped running for((;;)), need math parser\n");
11520f66f451Sopenharmony_ci        } else setvar(xmprintf("%s=%s", blk->fvar, blk->farg.v[blk->loop++]),
11530f66f451Sopenharmony_ci          TAKE_MEM);
11540f66f451Sopenharmony_ci      }
11550f66f451Sopenharmony_ci
11560f66f451Sopenharmony_ci    // end of block
11570f66f451Sopenharmony_ci    } else if (pl->type == 3) {
11580f66f451Sopenharmony_ci
11590f66f451Sopenharmony_ci      // repeating block?
11600f66f451Sopenharmony_ci      if (blk->run && !strcmp(s, "done")) {
11610f66f451Sopenharmony_ci        pl = blk->start;
11620f66f451Sopenharmony_ci        continue;
11630f66f451Sopenharmony_ci      }
11640f66f451Sopenharmony_ci
11650f66f451Sopenharmony_ci      // if ending a block, pop stack.
11660f66f451Sopenharmony_ci      llist_traverse(blk->fdelete, free);
11670f66f451Sopenharmony_ci      free(llist_pop(&blk));
11680f66f451Sopenharmony_ci
11690f66f451Sopenharmony_ci// TODO unwind redirects (cleanup blk->redir)
11700f66f451Sopenharmony_ci
11710f66f451Sopenharmony_ci    } else if (pl->type == 'f') pl = add_function(s, pl);
11720f66f451Sopenharmony_ci
11730f66f451Sopenharmony_ci    pl = pl->next;
11740f66f451Sopenharmony_ci  }
11750f66f451Sopenharmony_ci
11760f66f451Sopenharmony_ci  // Cleanup from syntax_err();
11770f66f451Sopenharmony_ci  while (blk) {
11780f66f451Sopenharmony_ci    llist_traverse(blk->fdelete, free);
11790f66f451Sopenharmony_ci    free(llist_pop(&blk));
11800f66f451Sopenharmony_ci  }
11810f66f451Sopenharmony_ci
11820f66f451Sopenharmony_ci  return;
11830f66f451Sopenharmony_ci}
11840f66f451Sopenharmony_ci
11850f66f451Sopenharmony_civoid subshell_imports(void)
11860f66f451Sopenharmony_ci{
11870f66f451Sopenharmony_ci/*
11880f66f451Sopenharmony_ci  // TODO cull local variables because 'env "()=42" env | grep 42' works.
11890f66f451Sopenharmony_ci
11900f66f451Sopenharmony_ci  // vfork() means subshells have to export and then re-import locals/functions
11910f66f451Sopenharmony_ci  sprintf(toybuf, "(%d#%d)", getpid(), getppid());
11920f66f451Sopenharmony_ci  if ((s = getenv(toybuf))) {
11930f66f451Sopenharmony_ci    char *from, *to, *ss;
11940f66f451Sopenharmony_ci
11950f66f451Sopenharmony_ci    unsetenv(toybuf);
11960f66f451Sopenharmony_ci    ss = s;
11970f66f451Sopenharmony_ci
11980f66f451Sopenharmony_ci    // Loop through packing \\ until \0
11990f66f451Sopenharmony_ci    for (from = to = s; *from; from++, to++) {
12000f66f451Sopenharmony_ci      *to = *from;
12010f66f451Sopenharmony_ci      if (*from != '\\') continue;
12020f66f451Sopenharmony_ci      if (from[1] == '\\' || from[1] == '0') from++;
12030f66f451Sopenharmony_ci      if (from[1] != '0') continue;
12040f66f451Sopenharmony_ci      *to = 0;
12050f66f451Sopenharmony_ci
12060f66f451Sopenharmony_ci      // save chunk
12070f66f451Sopenharmony_ci      for (ss = s; ss<to; ss++) {
12080f66f451Sopenharmony_ci        if (*ss == '=') {
12090f66f451Sopenharmony_ci          // first char of name is variable type ala declare
12100f66f451Sopenharmony_ci          if (s+1<ss && strchr("aAilnru", *s)) {
12110f66f451Sopenharmony_ci            setvar(ss, *s);
12120f66f451Sopenharmony_ci
12130f66f451Sopenharmony_ci            break;
12140f66f451Sopenharmony_ci          }
12150f66f451Sopenharmony_ci        } else if (!strncmp(ss, "(){", 3)) {
12160f66f451Sopenharmony_ci          FILE *ff = fmemopen(s, to-s, "r");
12170f66f451Sopenharmony_ci
12180f66f451Sopenharmony_ci          while ((new = xgetline(ff, 0))) {
12190f66f451Sopenharmony_ci            if ((prompt = parse_line(new, &scratch))<0) break;
12200f66f451Sopenharmony_ci            free(new);
12210f66f451Sopenharmony_ci          }
12220f66f451Sopenharmony_ci          if (!prompt) {
12230f66f451Sopenharmony_ci            add_function(s, scratch.pipeline);
12240f66f451Sopenharmony_ci            free_function(&scratch);
12250f66f451Sopenharmony_ci            break;
12260f66f451Sopenharmony_ci          }
12270f66f451Sopenharmony_ci          fclose(ff);
12280f66f451Sopenharmony_ci        } else if (!isspace(*s) && !ispunct(*s)) continue;
12290f66f451Sopenharmony_ci
12300f66f451Sopenharmony_ci        error_exit("bad locals");
12310f66f451Sopenharmony_ci      }
12320f66f451Sopenharmony_ci      s = from+1;
12330f66f451Sopenharmony_ci    }
12340f66f451Sopenharmony_ci  }
12350f66f451Sopenharmony_ci*/
12360f66f451Sopenharmony_ci}
12370f66f451Sopenharmony_ci
12380f66f451Sopenharmony_civoid sh_main(void)
12390f66f451Sopenharmony_ci{
12400f66f451Sopenharmony_ci  FILE *f;
12410f66f451Sopenharmony_ci  char *new;
12420f66f451Sopenharmony_ci  struct sh_function scratch;
12430f66f451Sopenharmony_ci  int prompt = 0;
12440f66f451Sopenharmony_ci
12450f66f451Sopenharmony_ci  // Set up signal handlers and grab control of this tty.
12460f66f451Sopenharmony_ci
12470f66f451Sopenharmony_ci  // Read environment for exports from parent shell
12480f66f451Sopenharmony_ci  subshell_imports();
12490f66f451Sopenharmony_ci
12500f66f451Sopenharmony_ci  memset(&scratch, 0, sizeof(scratch));
12510f66f451Sopenharmony_ci  if (TT.command) f = fmemopen(TT.command, strlen(TT.command), "r");
12520f66f451Sopenharmony_ci  else if (*toys.optargs) f = xfopen(*toys.optargs, "r");
12530f66f451Sopenharmony_ci  else {
12540f66f451Sopenharmony_ci    f = stdin;
12550f66f451Sopenharmony_ci    if (isatty(0)) toys.optflags |= FLAG_i;
12560f66f451Sopenharmony_ci  }
12570f66f451Sopenharmony_ci
12580f66f451Sopenharmony_ci  for (;;) {
12590f66f451Sopenharmony_ci
12600f66f451Sopenharmony_ci    // Prompt and read line
12610f66f451Sopenharmony_ci    if (f == stdin) {
12620f66f451Sopenharmony_ci      char *s = getenv(prompt ? "PS2" : "PS1");
12630f66f451Sopenharmony_ci
12640f66f451Sopenharmony_ci      if (!s) s = prompt ? "> " : (getpid() ? "\\$ " : "# ");
12650f66f451Sopenharmony_ci      do_prompt(s);
12660f66f451Sopenharmony_ci    } else TT.lineno++;
12670f66f451Sopenharmony_ci    if (!(new = xgetline(f ? f : stdin, 0))) break;
12680f66f451Sopenharmony_ci
12690f66f451Sopenharmony_ci// TODO if (!isspace(*new)) add_to_history(line);
12700f66f451Sopenharmony_ci
12710f66f451Sopenharmony_ci    // returns 0 if line consumed, command if it needs more data
12720f66f451Sopenharmony_ci    prompt = parse_line(new, &scratch);
12730f66f451Sopenharmony_ciif (0) dump_state(&scratch);
12740f66f451Sopenharmony_ci    if (prompt != 1) {
12750f66f451Sopenharmony_ci// TODO: ./blah.sh one two three: put one two three in scratch.arg
12760f66f451Sopenharmony_ci      if (!prompt) run_function(&scratch);
12770f66f451Sopenharmony_ci      free_function(&scratch);
12780f66f451Sopenharmony_ci      prompt = 0;
12790f66f451Sopenharmony_ci    }
12800f66f451Sopenharmony_ci    free(new);
12810f66f451Sopenharmony_ci  }
12820f66f451Sopenharmony_ci
12830f66f451Sopenharmony_ci  if (prompt) error_exit("%ld:unfinished line"+4*!TT.lineno, TT.lineno);
12840f66f451Sopenharmony_ci  toys.exitval = f && ferror(f);
12850f66f451Sopenharmony_ci}
1286