diff options
author | Ron Yorston | 2020-07-23 08:32:27 +0100 |
---|---|---|
committer | Denys Vlasenko | 2021-06-05 23:37:19 +0200 |
commit | a1b0d3856d9a0419cb74bf4c87525265871b5868 (patch) | |
tree | 1c3dface41c9c1f4d23a42819c9e6c2101e4f303 /shell | |
parent | 8c1f8aa016faee3fa151d134c3544b2dd5bab832 (diff) | |
download | busybox-a1b0d3856d9a0419cb74bf4c87525265871b5868.zip busybox-a1b0d3856d9a0419cb74bf4c87525265871b5868.tar.gz |
ash: add process substitution in bash-compatibility mode
Process substitution is a Korn shell feature that's also available
in bash and some other shells. This patch implements process
substitution in ash when ASH_BASH_COMPAT is enabled.
function old new delta
argstr 1386 1522 +136
strtodest - 52 +52
readtoken1 3346 3392 +46
.rodata 183206 183250 +44
unwindredir - 28 +28
cmdloop 365 372 +7
static.spclchars 10 12 +2
cmdputs 380 367 -13
exitreset 86 69 -17
evalcommand 1754 1737 -17
varvalue 675 634 -41
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 5/4 up/down: 315/-88) Total: 227 bytes
text data bss dec hex filename
953967 4219 1904 960090 ea65a busybox_old
954192 4219 1904 960315 ea73b busybox_unstripped
v2: Replace array of file descriptors with a linked list.
Include tests that were unaccountably omitted from v1.
v3: Update linked list code to the intended version.
v4: Change order of conditional code in cmdputs().
v5: Use existing popredir() mechanism to manage file descriptors.
v6: Rebase to latest version of BusyBox ash. Reduce code churn.
Signed-off-by: Ron Yorston <rmy@pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
Diffstat (limited to 'shell')
-rw-r--r-- | shell/ash.c | 160 | ||||
-rw-r--r-- | shell/ash_test/ash-psubst/bash_procsub.right | 9 | ||||
-rwxr-xr-x | shell/ash_test/ash-psubst/bash_procsub.tests | 33 |
3 files changed, 185 insertions, 17 deletions
diff --git a/shell/ash.c b/shell/ash.c index 6a16833..05c4795 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -229,6 +229,14 @@ #define BASH_READ_D ENABLE_ASH_BASH_COMPAT #define IF_BASH_READ_D IF_ASH_BASH_COMPAT #define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT +/* <(...) and >(...) */ +#if HAVE_DEV_FD +# define BASH_PROCESS_SUBST ENABLE_ASH_BASH_COMPAT +# define IF_BASH_PROCESS_SUBST IF_ASH_BASH_COMPAT +#else +# define BASH_PROCESS_SUBST 0 +# define IF_BASH_PROCESS_SUBST(...) +#endif #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 /* Bionic at least up to version 24 has no glob() */ @@ -804,6 +812,12 @@ out2str(const char *p) #define CTLENDARI ((unsigned char)'\207') #define CTLQUOTEMARK ((unsigned char)'\210') #define CTL_LAST CTLQUOTEMARK +#if BASH_PROCESS_SUBST +# define CTLTOPROC ((unsigned char)'\211') +# define CTLFROMPROC ((unsigned char)'\212') +# undef CTL_LAST +# define CTL_LAST CTLFROMPROC +#endif /* variable substitution byte (follows CTLVAR) */ #define VSTYPE 0x0f /* type of variable substitution */ @@ -1075,6 +1089,10 @@ trace_puts_quoted(char *s) case CTLESC: c = 'e'; goto backslash; case CTLVAR: c = 'v'; goto backslash; case CTLBACKQ: c = 'q'; goto backslash; +#if BASH_PROCESS_SUBST + case CTLTOPROC: c = 'p'; goto backslash; + case CTLFROMPROC: c = 'P'; goto backslash; +#endif backslash: putc('\\', tracefile); putc(c, tracefile); @@ -1236,8 +1254,17 @@ sharg(union node *arg, FILE *fp) case CTLENDVAR: putc('}', fp); break; +#if BASH_PROCESS_SUBST + case CTLTOPROC: + putc('>', fp); + goto backq; + case CTLFROMPROC: + putc('<', fp); + goto backq; +#endif case CTLBACKQ: putc('$', fp); + IF_BASH_PROCESS_SUBST(backq:) putc('(', fp); shtree(bqlist->n, -1, NULL, fp); putc(')', fp); @@ -3234,8 +3261,13 @@ static const uint8_t syntax_index_table[] ALIGN1 = { /* 134 CTLARI */ CCTL_CCTL_CCTL_CCTL, /* 135 CTLENDARI */ CCTL_CCTL_CCTL_CCTL, /* 136 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL, +#if BASH_PROCESS_SUBST + /* 137 CTLTOPROC */ CCTL_CCTL_CCTL_CCTL, + /* 138 CTLFROMPROC */ CCTL_CCTL_CCTL_CCTL, +#else /* 137 */ CWORD_CWORD_CWORD_CWORD, /* 138 */ CWORD_CWORD_CWORD_CWORD, +#endif /* 139 */ CWORD_CWORD_CWORD_CWORD, /* 140 */ CWORD_CWORD_CWORD_CWORD, /* 141 */ CWORD_CWORD_CWORD_CWORD, @@ -4849,9 +4881,24 @@ cmdputs(const char *s) quoted >>= 1; subtype = 0; goto dostr; +#if BASH_PROCESS_SUBST + case CTLBACKQ: + c = '$'; + str = "(...)"; + break; + case CTLTOPROC: + c = '>'; + str = "(...)"; + break; + case CTLFROMPROC: + c = '<'; + str = "(...)"; + break; +#else case CTLBACKQ: str = "$(...)"; goto dostr; +#endif #if ENABLE_FEATURE_SH_MATH case CTLARI: str = "$(("; @@ -5891,6 +5938,21 @@ redirectsafe(union node *redir, int flags) return err; } +#if BASH_PROCESS_SUBST +static void +pushfd(int fd) +{ + struct redirtab *sv; + + sv = ckzalloc(sizeof(*sv) + sizeof(sv->two_fd[0])); + sv->pair_count = 1; + sv->two_fd[0].orig_fd = fd; + sv->two_fd[0].moved_to = CLOSED; + sv->next = redirlist; + redirlist = sv; +} +#endif + static struct redirtab* pushredir(union node *redir) { @@ -6529,10 +6591,20 @@ evaltreenr(union node *n, int flags) } static void FAST_FUNC -evalbackcmd(union node *n, struct backcmd *result) +evalbackcmd(union node *n, struct backcmd *result + IF_BASH_PROCESS_SUBST(, int ctl)) { int pip[2]; struct job *jp; +#if BASH_PROCESS_SUBST + /* determine end of pipe used by parent (ip) and child (ic) */ + const int ip = (ctl == CTLTOPROC); + const int ic = !(ctl == CTLTOPROC); +#else + const int ctl = CTLBACKQ; + const int ip = 0; + const int ic = 1; +#endif result->fd = -1; result->buf = NULL; @@ -6544,15 +6616,17 @@ evalbackcmd(union node *n, struct backcmd *result) if (pipe(pip) < 0) ash_msg_and_raise_perror("can't create pipe"); - jp = makejob(/*n,*/ 1); - if (forkshell(jp, n, FORK_NOJOB) == 0) { + /* process substitution uses NULL job/node, like openhere() */ + jp = (ctl == CTLBACKQ) ? makejob(/*n,*/ 1) : NULL; + if (forkshell(jp, (ctl == CTLBACKQ) ? n : NULL, FORK_NOJOB) == 0) { /* child */ FORCE_INT_ON; - close(pip[0]); - if (pip[1] != 1) { - /*close(1);*/ - dup2_or_raise(pip[1], 1); - close(pip[1]); + close(pip[ip]); + /* ic is index of child end of pipe *and* fd to connect it to */ + if (pip[ic] != ic) { + /*close(ic);*/ + dup2_or_raise(pip[ic], ic); + close(pip[ic]); } /* TODO: eflag clearing makes the following not abort: * ash -c 'set -e; z=$(false;echo foo); echo $z' @@ -6568,8 +6642,18 @@ evalbackcmd(union node *n, struct backcmd *result) /* NOTREACHED */ } /* parent */ - close(pip[1]); - result->fd = pip[0]; +#if BASH_PROCESS_SUBST + if (ctl != CTLBACKQ) { + int fd = fcntl(pip[ip], F_DUPFD, 64); + if (fd > 0) { + close(pip[ip]); + pip[ip] = fd; + } + pushfd(pip[ip]); + } +#endif + close(pip[ic]); + result->fd = pip[ip]; result->jp = jp; out: @@ -6581,8 +6665,11 @@ evalbackcmd(union node *n, struct backcmd *result) * Expand stuff in backwards quotes. */ static void -expbackq(union node *cmd, int flag) +expbackq(union node *cmd, int flag IF_BASH_PROCESS_SUBST(, int ctl)) { +#if !BASH_PROCESS_SUBST + const int ctl = CTLBACKQ; +#endif struct backcmd in; int i; char buf[128]; @@ -6597,9 +6684,15 @@ expbackq(union node *cmd, int flag) INT_OFF; startloc = expdest - (char *)stackblock(); pushstackmark(&smark, startloc); - evalbackcmd(cmd, &in); + evalbackcmd(cmd, &in IF_BASH_PROCESS_SUBST(, ctl)); popstackmark(&smark); + if (ctl != CTLBACKQ) { + sprintf(buf, DEV_FD_PREFIX"%d", in.fd); + strtodest(buf, BASESYNTAX); + goto done; + } + p = in.buf; i = in.nleft; if (i == 0) @@ -6621,6 +6714,7 @@ expbackq(union node *cmd, int flag) close(in.fd); back_exitstatus = waitforjob(in.jp); } + done: INT_ON; /* Eat all trailing newlines */ @@ -6708,6 +6802,10 @@ argstr(char *p, int flag) CTLESC, CTLVAR, CTLBACKQ, +#if BASH_PROCESS_SUBST + CTLTOPROC, + CTLFROMPROC, +#endif #if ENABLE_FEATURE_SH_MATH CTLARI, CTLENDARI, @@ -6807,8 +6905,12 @@ argstr(char *p, int flag) p = evalvar(p, flag | inquotes); TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock())); goto start; +#if BASH_PROCESS_SUBST + case CTLTOPROC: + case CTLFROMPROC: +#endif case CTLBACKQ: - expbackq(argbackq->n, flag | inquotes); + expbackq(argbackq->n, flag | inquotes IF_BASH_PROCESS_SUBST(, c)); goto start; #if ENABLE_FEATURE_SH_MATH case CTLARI: @@ -12198,8 +12300,9 @@ realeofmark(const char *eofmark) #define CHECKEND() {goto checkend; checkend_return:;} #define PARSEREDIR() {goto parseredir; parseredir_return:;} #define PARSESUB() {goto parsesub; parsesub_return:;} -#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;} -#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;} +#define PARSEBACKQOLD() {style = OLD; goto parsebackq; parsebackq_oldreturn:;} +#define PARSEBACKQNEW() {style = NEW; goto parsebackq; parsebackq_newreturn:;} +#define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;} #define PARSEARITH() {goto parsearith; parsearith_return:;} static int readtoken1(int c, int syntax, char *eofmark, int striptabs) @@ -12210,7 +12313,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) size_t len; struct nodelist *bqlist; smallint quotef; - smallint oldstyle; + smallint style; + enum { OLD, NEW, PSUB }; +#define oldstyle (style == OLD) smallint pssyntax; /* we are expanding a prompt string */ IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) /* syntax stack */ @@ -12392,6 +12497,15 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) pungetc(); } #endif +#if BASH_PROCESS_SUBST + if (c == '<' || c == '>') { + if (pgetc() == '(') { + PARSEPROCSUB(); + break; + } + pungetc(); + } +#endif goto endword; /* exit outer loop */ } IF_ASH_ALIAS(if (c != PEOA)) @@ -12876,9 +12990,18 @@ parsebackq: { memcpy(out, str, savelen); STADJUST(savelen, out); } - USTPUTC(CTLBACKQ, out); +#if BASH_PROCESS_SUBST + if (style == PSUB) + USTPUTC(c == '<' ? CTLFROMPROC : CTLTOPROC, out); + else +#endif + USTPUTC(CTLBACKQ, out); if (oldstyle) goto parsebackq_oldreturn; +#if BASH_PROCESS_SUBST + else if (style == PSUB) + goto parsebackq_psreturn; +#endif goto parsebackq_newreturn; } @@ -13330,6 +13453,9 @@ cmdloop(int top) if (doing_jobctl) showjobs(SHOW_CHANGED|SHOW_STDERR); #endif +#if BASH_PROCESS_SUBST + unwindredir(NULL); +#endif inter = 0; if (iflag && top) { inter++; diff --git a/shell/ash_test/ash-psubst/bash_procsub.right b/shell/ash_test/ash-psubst/bash_procsub.right new file mode 100644 index 0000000..aa16a96 --- /dev/null +++ b/shell/ash_test/ash-psubst/bash_procsub.right @@ -0,0 +1,9 @@ +hello 1 +hello 2 +hello 3 +<(echo "hello 0") +hello 4 +HI THERE +hello error +hello error +hello stderr diff --git a/shell/ash_test/ash-psubst/bash_procsub.tests b/shell/ash_test/ash-psubst/bash_procsub.tests new file mode 100755 index 0000000..63b8367 --- /dev/null +++ b/shell/ash_test/ash-psubst/bash_procsub.tests @@ -0,0 +1,33 @@ +# simplest case +cat <(echo "hello 1") + +# can have more than one +cat <(echo "hello 2") <(echo "hello 3") + +# doesn't work in quotes +echo "<(echo \"hello 0\")" + +# process substitution can be nested inside command substitution +echo $(cat <(echo "hello 4")) + +# example from http://wiki.bash-hackers.org/syntax/expansion/proc_subst +# process substitutions can be passed to a function as parameters or +# variables +f() { + cat "$1" >"$x" +} +x=>(tr '[:lower:]' '[:upper:]') f <(echo 'hi there') + +# process substitution can be combined with redirection on exec +rm -f err +# save stderr +exec 4>&2 +# copy stderr to a file +exec 2> >(tee err) +echo "hello error" >&2 +sync +# restore stderr +exec 2>&4 +cat err +rm -f err +echo "hello stderr" >&2 |