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