aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2020-07-23 08:32:27 +0100
committerDenys Vlasenko <vda.linux@googlemail.com>2021-06-05 23:37:19 +0200
commita1b0d3856d9a0419cb74bf4c87525265871b5868 (patch)
tree1c3dface41c9c1f4d23a42819c9e6c2101e4f303
parent8c1f8aa016faee3fa151d134c3544b2dd5bab832 (diff)
downloadbusybox-w32-a1b0d3856d9a0419cb74bf4c87525265871b5868.tar.gz
busybox-w32-a1b0d3856d9a0419cb74bf4c87525265871b5868.tar.bz2
busybox-w32-a1b0d3856d9a0419cb74bf4c87525265871b5868.zip
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>
-rw-r--r--include/platform.h2
-rw-r--r--shell/ash.c160
-rw-r--r--shell/ash_test/ash-psubst/bash_procsub.right9
-rwxr-xr-xshell/ash_test/ash-psubst/bash_procsub.tests33
4 files changed, 187 insertions, 17 deletions
diff --git a/include/platform.h b/include/platform.h
index 4633b2507..9e1fb047d 100644
--- a/include/platform.h
+++ b/include/platform.h
@@ -426,6 +426,8 @@ typedef unsigned smalluint;
426#define HAVE_SYS_STATFS_H 1 426#define HAVE_SYS_STATFS_H 1
427#define HAVE_PRINTF_PERCENTM 1 427#define HAVE_PRINTF_PERCENTM 1
428#define HAVE_WAIT3 1 428#define HAVE_WAIT3 1
429#define HAVE_DEV_FD 1
430#define DEV_FD_PREFIX "/dev/fd/"
429 431
430#if defined(__UCLIBC__) 432#if defined(__UCLIBC__)
431# if UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32) 433# if UCLIBC_VERSION < KERNEL_VERSION(0, 9, 32)
diff --git a/shell/ash.c b/shell/ash.c
index 6a16833b1..05c47950f 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -229,6 +229,14 @@
229#define BASH_READ_D ENABLE_ASH_BASH_COMPAT 229#define BASH_READ_D ENABLE_ASH_BASH_COMPAT
230#define IF_BASH_READ_D IF_ASH_BASH_COMPAT 230#define IF_BASH_READ_D IF_ASH_BASH_COMPAT
231#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT 231#define BASH_WAIT_N ENABLE_ASH_BASH_COMPAT
232/* <(...) and >(...) */
233#if HAVE_DEV_FD
234# define BASH_PROCESS_SUBST ENABLE_ASH_BASH_COMPAT
235# define IF_BASH_PROCESS_SUBST IF_ASH_BASH_COMPAT
236#else
237# define BASH_PROCESS_SUBST 0
238# define IF_BASH_PROCESS_SUBST(...)
239#endif
232 240
233#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 241#if defined(__ANDROID_API__) && __ANDROID_API__ <= 24
234/* Bionic at least up to version 24 has no glob() */ 242/* Bionic at least up to version 24 has no glob() */
@@ -804,6 +812,12 @@ out2str(const char *p)
804#define CTLENDARI ((unsigned char)'\207') 812#define CTLENDARI ((unsigned char)'\207')
805#define CTLQUOTEMARK ((unsigned char)'\210') 813#define CTLQUOTEMARK ((unsigned char)'\210')
806#define CTL_LAST CTLQUOTEMARK 814#define CTL_LAST CTLQUOTEMARK
815#if BASH_PROCESS_SUBST
816# define CTLTOPROC ((unsigned char)'\211')
817# define CTLFROMPROC ((unsigned char)'\212')
818# undef CTL_LAST
819# define CTL_LAST CTLFROMPROC
820#endif
807 821
808/* variable substitution byte (follows CTLVAR) */ 822/* variable substitution byte (follows CTLVAR) */
809#define VSTYPE 0x0f /* type of variable substitution */ 823#define VSTYPE 0x0f /* type of variable substitution */
@@ -1075,6 +1089,10 @@ trace_puts_quoted(char *s)
1075 case CTLESC: c = 'e'; goto backslash; 1089 case CTLESC: c = 'e'; goto backslash;
1076 case CTLVAR: c = 'v'; goto backslash; 1090 case CTLVAR: c = 'v'; goto backslash;
1077 case CTLBACKQ: c = 'q'; goto backslash; 1091 case CTLBACKQ: c = 'q'; goto backslash;
1092#if BASH_PROCESS_SUBST
1093 case CTLTOPROC: c = 'p'; goto backslash;
1094 case CTLFROMPROC: c = 'P'; goto backslash;
1095#endif
1078 backslash: 1096 backslash:
1079 putc('\\', tracefile); 1097 putc('\\', tracefile);
1080 putc(c, tracefile); 1098 putc(c, tracefile);
@@ -1236,8 +1254,17 @@ sharg(union node *arg, FILE *fp)
1236 case CTLENDVAR: 1254 case CTLENDVAR:
1237 putc('}', fp); 1255 putc('}', fp);
1238 break; 1256 break;
1257#if BASH_PROCESS_SUBST
1258 case CTLTOPROC:
1259 putc('>', fp);
1260 goto backq;
1261 case CTLFROMPROC:
1262 putc('<', fp);
1263 goto backq;
1264#endif
1239 case CTLBACKQ: 1265 case CTLBACKQ:
1240 putc('$', fp); 1266 putc('$', fp);
1267 IF_BASH_PROCESS_SUBST(backq:)
1241 putc('(', fp); 1268 putc('(', fp);
1242 shtree(bqlist->n, -1, NULL, fp); 1269 shtree(bqlist->n, -1, NULL, fp);
1243 putc(')', fp); 1270 putc(')', fp);
@@ -3234,8 +3261,13 @@ static const uint8_t syntax_index_table[] ALIGN1 = {
3234 /* 134 CTLARI */ CCTL_CCTL_CCTL_CCTL, 3261 /* 134 CTLARI */ CCTL_CCTL_CCTL_CCTL,
3235 /* 135 CTLENDARI */ CCTL_CCTL_CCTL_CCTL, 3262 /* 135 CTLENDARI */ CCTL_CCTL_CCTL_CCTL,
3236 /* 136 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL, 3263 /* 136 CTLQUOTEMARK */ CCTL_CCTL_CCTL_CCTL,
3264#if BASH_PROCESS_SUBST
3265 /* 137 CTLTOPROC */ CCTL_CCTL_CCTL_CCTL,
3266 /* 138 CTLFROMPROC */ CCTL_CCTL_CCTL_CCTL,
3267#else
3237 /* 137 */ CWORD_CWORD_CWORD_CWORD, 3268 /* 137 */ CWORD_CWORD_CWORD_CWORD,
3238 /* 138 */ CWORD_CWORD_CWORD_CWORD, 3269 /* 138 */ CWORD_CWORD_CWORD_CWORD,
3270#endif
3239 /* 139 */ CWORD_CWORD_CWORD_CWORD, 3271 /* 139 */ CWORD_CWORD_CWORD_CWORD,
3240 /* 140 */ CWORD_CWORD_CWORD_CWORD, 3272 /* 140 */ CWORD_CWORD_CWORD_CWORD,
3241 /* 141 */ CWORD_CWORD_CWORD_CWORD, 3273 /* 141 */ CWORD_CWORD_CWORD_CWORD,
@@ -4849,9 +4881,24 @@ cmdputs(const char *s)
4849 quoted >>= 1; 4881 quoted >>= 1;
4850 subtype = 0; 4882 subtype = 0;
4851 goto dostr; 4883 goto dostr;
4884#if BASH_PROCESS_SUBST
4885 case CTLBACKQ:
4886 c = '$';
4887 str = "(...)";
4888 break;
4889 case CTLTOPROC:
4890 c = '>';
4891 str = "(...)";
4892 break;
4893 case CTLFROMPROC:
4894 c = '<';
4895 str = "(...)";
4896 break;
4897#else
4852 case CTLBACKQ: 4898 case CTLBACKQ:
4853 str = "$(...)"; 4899 str = "$(...)";
4854 goto dostr; 4900 goto dostr;
4901#endif
4855#if ENABLE_FEATURE_SH_MATH 4902#if ENABLE_FEATURE_SH_MATH
4856 case CTLARI: 4903 case CTLARI:
4857 str = "$(("; 4904 str = "$((";
@@ -5891,6 +5938,21 @@ redirectsafe(union node *redir, int flags)
5891 return err; 5938 return err;
5892} 5939}
5893 5940
5941#if BASH_PROCESS_SUBST
5942static void
5943pushfd(int fd)
5944{
5945 struct redirtab *sv;
5946
5947 sv = ckzalloc(sizeof(*sv) + sizeof(sv->two_fd[0]));
5948 sv->pair_count = 1;
5949 sv->two_fd[0].orig_fd = fd;
5950 sv->two_fd[0].moved_to = CLOSED;
5951 sv->next = redirlist;
5952 redirlist = sv;
5953}
5954#endif
5955
5894static struct redirtab* 5956static struct redirtab*
5895pushredir(union node *redir) 5957pushredir(union node *redir)
5896{ 5958{
@@ -6529,10 +6591,20 @@ evaltreenr(union node *n, int flags)
6529} 6591}
6530 6592
6531static void FAST_FUNC 6593static void FAST_FUNC
6532evalbackcmd(union node *n, struct backcmd *result) 6594evalbackcmd(union node *n, struct backcmd *result
6595 IF_BASH_PROCESS_SUBST(, int ctl))
6533{ 6596{
6534 int pip[2]; 6597 int pip[2];
6535 struct job *jp; 6598 struct job *jp;
6599#if BASH_PROCESS_SUBST
6600 /* determine end of pipe used by parent (ip) and child (ic) */
6601 const int ip = (ctl == CTLTOPROC);
6602 const int ic = !(ctl == CTLTOPROC);
6603#else
6604 const int ctl = CTLBACKQ;
6605 const int ip = 0;
6606 const int ic = 1;
6607#endif
6536 6608
6537 result->fd = -1; 6609 result->fd = -1;
6538 result->buf = NULL; 6610 result->buf = NULL;
@@ -6544,15 +6616,17 @@ evalbackcmd(union node *n, struct backcmd *result)
6544 6616
6545 if (pipe(pip) < 0) 6617 if (pipe(pip) < 0)
6546 ash_msg_and_raise_perror("can't create pipe"); 6618 ash_msg_and_raise_perror("can't create pipe");
6547 jp = makejob(/*n,*/ 1); 6619 /* process substitution uses NULL job/node, like openhere() */
6548 if (forkshell(jp, n, FORK_NOJOB) == 0) { 6620 jp = (ctl == CTLBACKQ) ? makejob(/*n,*/ 1) : NULL;
6621 if (forkshell(jp, (ctl == CTLBACKQ) ? n : NULL, FORK_NOJOB) == 0) {
6549 /* child */ 6622 /* child */
6550 FORCE_INT_ON; 6623 FORCE_INT_ON;
6551 close(pip[0]); 6624 close(pip[ip]);
6552 if (pip[1] != 1) { 6625 /* ic is index of child end of pipe *and* fd to connect it to */
6553 /*close(1);*/ 6626 if (pip[ic] != ic) {
6554 dup2_or_raise(pip[1], 1); 6627 /*close(ic);*/
6555 close(pip[1]); 6628 dup2_or_raise(pip[ic], ic);
6629 close(pip[ic]);
6556 } 6630 }
6557/* TODO: eflag clearing makes the following not abort: 6631/* TODO: eflag clearing makes the following not abort:
6558 * ash -c 'set -e; z=$(false;echo foo); echo $z' 6632 * ash -c 'set -e; z=$(false;echo foo); echo $z'
@@ -6568,8 +6642,18 @@ evalbackcmd(union node *n, struct backcmd *result)
6568 /* NOTREACHED */ 6642 /* NOTREACHED */
6569 } 6643 }
6570 /* parent */ 6644 /* parent */
6571 close(pip[1]); 6645#if BASH_PROCESS_SUBST
6572 result->fd = pip[0]; 6646 if (ctl != CTLBACKQ) {
6647 int fd = fcntl(pip[ip], F_DUPFD, 64);
6648 if (fd > 0) {
6649 close(pip[ip]);
6650 pip[ip] = fd;
6651 }
6652 pushfd(pip[ip]);
6653 }
6654#endif
6655 close(pip[ic]);
6656 result->fd = pip[ip];
6573 result->jp = jp; 6657 result->jp = jp;
6574 6658
6575 out: 6659 out:
@@ -6581,8 +6665,11 @@ evalbackcmd(union node *n, struct backcmd *result)
6581 * Expand stuff in backwards quotes. 6665 * Expand stuff in backwards quotes.
6582 */ 6666 */
6583static void 6667static void
6584expbackq(union node *cmd, int flag) 6668expbackq(union node *cmd, int flag IF_BASH_PROCESS_SUBST(, int ctl))
6585{ 6669{
6670#if !BASH_PROCESS_SUBST
6671 const int ctl = CTLBACKQ;
6672#endif
6586 struct backcmd in; 6673 struct backcmd in;
6587 int i; 6674 int i;
6588 char buf[128]; 6675 char buf[128];
@@ -6597,9 +6684,15 @@ expbackq(union node *cmd, int flag)
6597 INT_OFF; 6684 INT_OFF;
6598 startloc = expdest - (char *)stackblock(); 6685 startloc = expdest - (char *)stackblock();
6599 pushstackmark(&smark, startloc); 6686 pushstackmark(&smark, startloc);
6600 evalbackcmd(cmd, &in); 6687 evalbackcmd(cmd, &in IF_BASH_PROCESS_SUBST(, ctl));
6601 popstackmark(&smark); 6688 popstackmark(&smark);
6602 6689
6690 if (ctl != CTLBACKQ) {
6691 sprintf(buf, DEV_FD_PREFIX"%d", in.fd);
6692 strtodest(buf, BASESYNTAX);
6693 goto done;
6694 }
6695
6603 p = in.buf; 6696 p = in.buf;
6604 i = in.nleft; 6697 i = in.nleft;
6605 if (i == 0) 6698 if (i == 0)
@@ -6621,6 +6714,7 @@ expbackq(union node *cmd, int flag)
6621 close(in.fd); 6714 close(in.fd);
6622 back_exitstatus = waitforjob(in.jp); 6715 back_exitstatus = waitforjob(in.jp);
6623 } 6716 }
6717 done:
6624 INT_ON; 6718 INT_ON;
6625 6719
6626 /* Eat all trailing newlines */ 6720 /* Eat all trailing newlines */
@@ -6708,6 +6802,10 @@ argstr(char *p, int flag)
6708 CTLESC, 6802 CTLESC,
6709 CTLVAR, 6803 CTLVAR,
6710 CTLBACKQ, 6804 CTLBACKQ,
6805#if BASH_PROCESS_SUBST
6806 CTLTOPROC,
6807 CTLFROMPROC,
6808#endif
6711#if ENABLE_FEATURE_SH_MATH 6809#if ENABLE_FEATURE_SH_MATH
6712 CTLARI, 6810 CTLARI,
6713 CTLENDARI, 6811 CTLENDARI,
@@ -6807,8 +6905,12 @@ argstr(char *p, int flag)
6807 p = evalvar(p, flag | inquotes); 6905 p = evalvar(p, flag | inquotes);
6808 TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock())); 6906 TRACE(("argstr: evalvar:'%s'\n", (char *)stackblock()));
6809 goto start; 6907 goto start;
6908#if BASH_PROCESS_SUBST
6909 case CTLTOPROC:
6910 case CTLFROMPROC:
6911#endif
6810 case CTLBACKQ: 6912 case CTLBACKQ:
6811 expbackq(argbackq->n, flag | inquotes); 6913 expbackq(argbackq->n, flag | inquotes IF_BASH_PROCESS_SUBST(, c));
6812 goto start; 6914 goto start;
6813#if ENABLE_FEATURE_SH_MATH 6915#if ENABLE_FEATURE_SH_MATH
6814 case CTLARI: 6916 case CTLARI:
@@ -12198,8 +12300,9 @@ realeofmark(const char *eofmark)
12198#define CHECKEND() {goto checkend; checkend_return:;} 12300#define CHECKEND() {goto checkend; checkend_return:;}
12199#define PARSEREDIR() {goto parseredir; parseredir_return:;} 12301#define PARSEREDIR() {goto parseredir; parseredir_return:;}
12200#define PARSESUB() {goto parsesub; parsesub_return:;} 12302#define PARSESUB() {goto parsesub; parsesub_return:;}
12201#define PARSEBACKQOLD() {oldstyle = 1; goto parsebackq; parsebackq_oldreturn:;} 12303#define PARSEBACKQOLD() {style = OLD; goto parsebackq; parsebackq_oldreturn:;}
12202#define PARSEBACKQNEW() {oldstyle = 0; goto parsebackq; parsebackq_newreturn:;} 12304#define PARSEBACKQNEW() {style = NEW; goto parsebackq; parsebackq_newreturn:;}
12305#define PARSEPROCSUB() {style = PSUB; goto parsebackq; parsebackq_psreturn:;}
12203#define PARSEARITH() {goto parsearith; parsearith_return:;} 12306#define PARSEARITH() {goto parsearith; parsearith_return:;}
12204static int 12307static int
12205readtoken1(int c, int syntax, char *eofmark, int striptabs) 12308readtoken1(int c, int syntax, char *eofmark, int striptabs)
@@ -12210,7 +12313,9 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12210 size_t len; 12313 size_t len;
12211 struct nodelist *bqlist; 12314 struct nodelist *bqlist;
12212 smallint quotef; 12315 smallint quotef;
12213 smallint oldstyle; 12316 smallint style;
12317 enum { OLD, NEW, PSUB };
12318#define oldstyle (style == OLD)
12214 smallint pssyntax; /* we are expanding a prompt string */ 12319 smallint pssyntax; /* we are expanding a prompt string */
12215 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) 12320 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
12216 /* syntax stack */ 12321 /* syntax stack */
@@ -12392,6 +12497,15 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12392 pungetc(); 12497 pungetc();
12393 } 12498 }
12394#endif 12499#endif
12500#if BASH_PROCESS_SUBST
12501 if (c == '<' || c == '>') {
12502 if (pgetc() == '(') {
12503 PARSEPROCSUB();
12504 break;
12505 }
12506 pungetc();
12507 }
12508#endif
12395 goto endword; /* exit outer loop */ 12509 goto endword; /* exit outer loop */
12396 } 12510 }
12397 IF_ASH_ALIAS(if (c != PEOA)) 12511 IF_ASH_ALIAS(if (c != PEOA))
@@ -12876,9 +12990,18 @@ parsebackq: {
12876 memcpy(out, str, savelen); 12990 memcpy(out, str, savelen);
12877 STADJUST(savelen, out); 12991 STADJUST(savelen, out);
12878 } 12992 }
12879 USTPUTC(CTLBACKQ, out); 12993#if BASH_PROCESS_SUBST
12994 if (style == PSUB)
12995 USTPUTC(c == '<' ? CTLFROMPROC : CTLTOPROC, out);
12996 else
12997#endif
12998 USTPUTC(CTLBACKQ, out);
12880 if (oldstyle) 12999 if (oldstyle)
12881 goto parsebackq_oldreturn; 13000 goto parsebackq_oldreturn;
13001#if BASH_PROCESS_SUBST
13002 else if (style == PSUB)
13003 goto parsebackq_psreturn;
13004#endif
12882 goto parsebackq_newreturn; 13005 goto parsebackq_newreturn;
12883} 13006}
12884 13007
@@ -13330,6 +13453,9 @@ cmdloop(int top)
13330 if (doing_jobctl) 13453 if (doing_jobctl)
13331 showjobs(SHOW_CHANGED|SHOW_STDERR); 13454 showjobs(SHOW_CHANGED|SHOW_STDERR);
13332#endif 13455#endif
13456#if BASH_PROCESS_SUBST
13457 unwindredir(NULL);
13458#endif
13333 inter = 0; 13459 inter = 0;
13334 if (iflag && top) { 13460 if (iflag && top) {
13335 inter++; 13461 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 000000000..aa16a96be
--- /dev/null
+++ b/shell/ash_test/ash-psubst/bash_procsub.right
@@ -0,0 +1,9 @@
1hello 1
2hello 2
3hello 3
4<(echo "hello 0")
5hello 4
6HI THERE
7hello error
8hello error
9hello 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 000000000..63b836782
--- /dev/null
+++ b/shell/ash_test/ash-psubst/bash_procsub.tests
@@ -0,0 +1,33 @@
1# simplest case
2cat <(echo "hello 1")
3
4# can have more than one
5cat <(echo "hello 2") <(echo "hello 3")
6
7# doesn't work in quotes
8echo "<(echo \"hello 0\")"
9
10# process substitution can be nested inside command substitution
11echo $(cat <(echo "hello 4"))
12
13# example from http://wiki.bash-hackers.org/syntax/expansion/proc_subst
14# process substitutions can be passed to a function as parameters or
15# variables
16f() {
17 cat "$1" >"$x"
18}
19x=>(tr '[:lower:]' '[:upper:]') f <(echo 'hi there')
20
21# process substitution can be combined with redirection on exec
22rm -f err
23# save stderr
24exec 4>&2
25# copy stderr to a file
26exec 2> >(tee err)
27echo "hello error" >&2
28sync
29# restore stderr
30exec 2>&4
31cat err
32rm -f err
33echo "hello stderr" >&2