aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
Diffstat (limited to 'shell')
-rw-r--r--shell/Config.src129
-rw-r--r--shell/ash.c149
-rw-r--r--shell/ash_test/ash-parsing/argv0.right1
-rwxr-xr-xshell/ash_test/ash-parsing/argv0.tests4
-rw-r--r--shell/ash_test/ash-parsing/brace1.right7
-rwxr-xr-xshell/ash_test/ash-parsing/brace1.tests7
-rw-r--r--shell/ash_test/ash-parsing/brace2.right3
-rwxr-xr-xshell/ash_test/ash-parsing/brace2.tests5
-rw-r--r--shell/ash_test/ash-parsing/comment1.right2
-rwxr-xr-xshell/ash_test/ash-parsing/comment1.tests2
-rw-r--r--shell/ash_test/ash-parsing/eol1.right1
-rwxr-xr-xshell/ash_test/ash-parsing/eol1.tests18
-rw-r--r--shell/ash_test/ash-parsing/escape1.right4
-rwxr-xr-xshell/ash_test/ash-parsing/escape1.tests6
-rw-r--r--shell/ash_test/ash-parsing/escape2.right4
-rwxr-xr-xshell/ash_test/ash-parsing/escape2.tests4
-rw-r--r--shell/ash_test/ash-parsing/escape3.right23
-rwxr-xr-xshell/ash_test/ash-parsing/escape3.tests10
-rw-r--r--shell/ash_test/ash-parsing/escape4.right2
-rwxr-xr-xshell/ash_test/ash-parsing/escape4.tests6
-rw-r--r--shell/ash_test/ash-parsing/escape5.right9
-rwxr-xr-xshell/ash_test/ash-parsing/escape5.tests7
-rw-r--r--shell/ash_test/ash-parsing/group1.right1
-rwxr-xr-xshell/ash_test/ash-parsing/group1.tests1
-rw-r--r--shell/ash_test/ash-parsing/group2.right2
-rwxr-xr-xshell/ash_test/ash-parsing/group2.tests3
-rw-r--r--shell/ash_test/ash-parsing/groups_and_keywords1.right11
-rwxr-xr-xshell/ash_test/ash-parsing/groups_and_keywords1.tests10
-rw-r--r--shell/ash_test/ash-parsing/negate.right36
-rwxr-xr-xshell/ash_test/ash-parsing/negate.tests19
-rw-r--r--shell/ash_test/ash-parsing/noeol.right1
-rwxr-xr-xshell/ash_test/ash-parsing/noeol.tests2
-rw-r--r--shell/ash_test/ash-parsing/noeol2.right1
-rwxr-xr-xshell/ash_test/ash-parsing/noeol2.tests7
-rw-r--r--shell/ash_test/ash-parsing/noeol3.right1
-rwxr-xr-xshell/ash_test/ash-parsing/noeol3.tests2
-rw-r--r--shell/ash_test/ash-parsing/process_subst.right3
-rwxr-xr-xshell/ash_test/ash-parsing/process_subst.tests3
-rw-r--r--shell/ash_test/ash-parsing/quote1.right1
-rwxr-xr-xshell/ash_test/ash-parsing/quote1.tests2
-rw-r--r--shell/ash_test/ash-parsing/quote2.right1
-rwxr-xr-xshell/ash_test/ash-parsing/quote2.tests2
-rw-r--r--shell/ash_test/ash-parsing/quote3.right12
-rwxr-xr-xshell/ash_test/ash-parsing/quote3.tests21
-rw-r--r--shell/ash_test/ash-parsing/quote4.right1
-rwxr-xr-xshell/ash_test/ash-parsing/quote4.tests2
-rw-r--r--shell/ash_test/ash-parsing/starquoted.right8
-rwxr-xr-xshell/ash_test/ash-parsing/starquoted.tests8
-rw-r--r--shell/ash_test/ash-parsing/starquoted2.right8
-rwxr-xr-xshell/ash_test/ash-parsing/starquoted2.tests19
-rw-r--r--shell/ash_test/ash-quoting/mode_x.right10
-rwxr-xr-xshell/ash_test/ash-quoting/mode_x.tests14
-rw-r--r--shell/ash_test/ash-read/read_t0.right3
-rwxr-xr-xshell/ash_test/ash-read/read_t0.tests14
-rw-r--r--shell/ash_test/ash-vars/readonly0.right13
-rwxr-xr-xshell/ash_test/ash-vars/readonly0.tests45
-rw-r--r--shell/ash_test/ash-vars/readonly1.right4
-rwxr-xr-xshell/ash_test/ash-vars/readonly1.tests4
-rwxr-xr-xshell/ash_test/run-all22
-rw-r--r--shell/cttyhack.c56
-rw-r--r--shell/hush.c71
-rw-r--r--shell/hush_test/hush-read/read_t0.right3
-rwxr-xr-xshell/hush_test/hush-read/read_t0.tests14
-rw-r--r--shell/shell_common.c82
64 files changed, 736 insertions, 210 deletions
diff --git a/shell/Config.src b/shell/Config.src
index ccb1b15fe..81c4ec874 100644
--- a/shell/Config.src
+++ b/shell/Config.src
@@ -10,26 +10,26 @@ choice
10 prompt "Choose which shell is aliased to 'sh' name" 10 prompt "Choose which shell is aliased to 'sh' name"
11 default SH_IS_ASH 11 default SH_IS_ASH
12 help 12 help
13 Choose which shell you want to be executed by 'sh' alias. 13 Choose which shell you want to be executed by 'sh' alias.
14 The ash shell is the most bash compatible and full featured one. 14 The ash shell is the most bash compatible and full featured one.
15 15
16# note: cannot use "select ASH" here, it breaks "make allnoconfig" 16# note: cannot use "select ASH" here, it breaks "make allnoconfig"
17config SH_IS_ASH 17config SH_IS_ASH
18 depends on !NOMMU 18 depends on !NOMMU
19 bool "ash" 19 bool "ash"
20 help 20 help
21 Choose ash to be the shell executed by 'sh' name. 21 Choose ash to be the shell executed by 'sh' name.
22 The ash code will be built into busybox. If you don't select 22 The ash code will be built into busybox. If you don't select
23 "ash" choice (CONFIG_ASH), this shell may only be invoked by 23 "ash" choice (CONFIG_ASH), this shell may only be invoked by
24 the name 'sh' (and not 'ash'). 24 the name 'sh' (and not 'ash').
25 25
26config SH_IS_HUSH 26config SH_IS_HUSH
27 bool "hush" 27 bool "hush"
28 help 28 help
29 Choose hush to be the shell executed by 'sh' name. 29 Choose hush to be the shell executed by 'sh' name.
30 The hush code will be built into busybox. If you don't select 30 The hush code will be built into busybox. If you don't select
31 "hush" choice (CONFIG_HUSH), this shell may only be invoked by 31 "hush" choice (CONFIG_HUSH), this shell may only be invoked by
32 the name 'sh' (and not 'hush'). 32 the name 'sh' (and not 'hush').
33 33
34config SH_IS_NONE 34config SH_IS_NONE
35 bool "none" 35 bool "none"
@@ -40,36 +40,36 @@ choice
40 prompt "Choose which shell is aliased to 'bash' name" 40 prompt "Choose which shell is aliased to 'bash' name"
41 default BASH_IS_NONE 41 default BASH_IS_NONE
42 help 42 help
43 Choose which shell you want to be executed by 'bash' alias. 43 Choose which shell you want to be executed by 'bash' alias.
44 The ash shell is the most bash compatible and full featured one, 44 The ash shell is the most bash compatible and full featured one,
45 although compatibility is far from being complete. 45 although compatibility is far from being complete.
46 46
47 Note that selecting this option does not switch on any bash 47 Note that selecting this option does not switch on any bash
48 compatibility code. It merely makes it possible to install 48 compatibility code. It merely makes it possible to install
49 /bin/bash (sym)link and run scripts which start with 49 /bin/bash (sym)link and run scripts which start with
50 #!/bin/bash line. 50 #!/bin/bash line.
51 51
52 Many systems use it in scripts which use bash-specific features, 52 Many systems use it in scripts which use bash-specific features,
53 even simple ones like $RANDOM. Without this option, busybox 53 even simple ones like $RANDOM. Without this option, busybox
54 can't be used for running them because it won't recongnize 54 can't be used for running them because it won't recongnize
55 "bash" as a supported applet name. 55 "bash" as a supported applet name.
56 56
57config BASH_IS_ASH 57config BASH_IS_ASH
58 depends on !NOMMU 58 depends on !NOMMU
59 bool "ash" 59 bool "ash"
60 help 60 help
61 Choose ash to be the shell executed by 'bash' name. 61 Choose ash to be the shell executed by 'bash' name.
62 The ash code will be built into busybox. If you don't select 62 The ash code will be built into busybox. If you don't select
63 "ash" choice (CONFIG_ASH), this shell may only be invoked by 63 "ash" choice (CONFIG_ASH), this shell may only be invoked by
64 the name 'bash' (and not 'ash'). 64 the name 'bash' (and not 'ash').
65 65
66config BASH_IS_HUSH 66config BASH_IS_HUSH
67 bool "hush" 67 bool "hush"
68 help 68 help
69 Choose hush to be the shell executed by 'bash' name. 69 Choose hush to be the shell executed by 'bash' name.
70 The hush code will be built into busybox. If you don't select 70 The hush code will be built into busybox. If you don't select
71 "hush" choice (CONFIG_HUSH), this shell may only be invoked by 71 "hush" choice (CONFIG_HUSH), this shell may only be invoked by
72 the name 'bash' (and not 'hush'). 72 the name 'bash' (and not 'hush').
73 73
74config BASH_IS_NONE 74config BASH_IS_NONE
75 bool "none" 75 bool "none"
@@ -88,71 +88,78 @@ config FEATURE_SH_MATH
88 default y 88 default y
89 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH 89 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
90 help 90 help
91 Enable math support in the shell via $((...)) syntax. 91 Enable math support in the shell via $((...)) syntax.
92 92
93config FEATURE_SH_MATH_64 93config FEATURE_SH_MATH_64
94 bool "Extend POSIX math support to 64 bit" 94 bool "Extend POSIX math support to 64 bit"
95 default y 95 default y
96 depends on FEATURE_SH_MATH 96 depends on FEATURE_SH_MATH
97 help 97 help
98 Enable 64-bit math support in the shell. This will make the shell 98 Enable 64-bit math support in the shell. This will make the shell
99 slightly larger, but will allow computation with very large numbers. 99 slightly larger, but will allow computation with very large numbers.
100 This is not in POSIX, so do not rely on this in portable code. 100 This is not in POSIX, so do not rely on this in portable code.
101 101
102config FEATURE_SH_EXTRA_QUIET 102config FEATURE_SH_EXTRA_QUIET
103 bool "Hide message on interactive shell startup" 103 bool "Hide message on interactive shell startup"
104 default y 104 default y
105 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH 105 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
106 help 106 help
107 Remove the busybox introduction when starting a shell. 107 Remove the busybox introduction when starting a shell.
108 108
109config FEATURE_SH_STANDALONE 109config FEATURE_SH_STANDALONE
110 bool "Standalone shell" 110 bool "Standalone shell"
111 default n 111 default n
112 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH 112 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
113 help 113 help
114 This option causes busybox shells to use busybox applets 114 This option causes busybox shells to use busybox applets
115 in preference to executables in the PATH whenever possible. For 115 in preference to executables in the PATH whenever possible. For
116 example, entering the command 'ifconfig' into the shell would cause 116 example, entering the command 'ifconfig' into the shell would cause
117 busybox to use the ifconfig busybox applet. Specifying the fully 117 busybox to use the ifconfig busybox applet. Specifying the fully
118 qualified executable name, such as '/sbin/ifconfig' will still 118 qualified executable name, such as '/sbin/ifconfig' will still
119 execute the /sbin/ifconfig executable on the filesystem. This option 119 execute the /sbin/ifconfig executable on the filesystem. This option
120 is generally used when creating a statically linked version of busybox 120 is generally used when creating a statically linked version of busybox
121 for use as a rescue shell, in the event that you screw up your system. 121 for use as a rescue shell, in the event that you screw up your system.
122 122
123 This is implemented by re-execing /proc/self/exe (typically) 123 This is implemented by re-execing /proc/self/exe (typically)
124 with right parameters. 124 with right parameters.
125 125
126 However, there are drawbacks: it is problematic in chroot jails 126 However, there are drawbacks: it is problematic in chroot jails
127 without mounted /proc, and ps/top may show command name as 'exe' 127 without mounted /proc, and ps/top may show command name as 'exe'
128 for applets started this way. 128 for applets started this way.
129 129
130config FEATURE_SH_NOFORK 130config FEATURE_SH_NOFORK
131 bool "Run 'nofork' applets directly" 131 bool "Run 'nofork' applets directly"
132 default n 132 default n
133 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH 133 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
134 help 134 help
135 This option causes busybox shells to not execute typical 135 This option causes busybox shells to not execute typical
136 fork/exec/wait sequence, but call <applet>_main directly, 136 fork/exec/wait sequence, but call <applet>_main directly,
137 if possible. (Sometimes it is not possible: for example, 137 if possible. (Sometimes it is not possible: for example,
138 this is not possible in pipes). 138 this is not possible in pipes).
139 139
140 This will be done only for some applets (those which are marked 140 This will be done only for some applets (those which are marked
141 NOFORK in include/applets.h). 141 NOFORK in include/applets.h).
142 142
143 This may significantly speed up some shell scripts. 143 This may significantly speed up some shell scripts.
144 144
145 This feature is relatively new. Use with care. Report bugs 145 This feature is relatively new. Use with care. Report bugs
146 to project mailing list. 146 to project mailing list.
147
148config FEATURE_SH_READ_FRAC
149 bool "read -t N.NNN support (+110 bytes)"
150 default y
151 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
152 help
153 Enable support for fractional second timeout in read builtin.
147 154
148config FEATURE_SH_HISTFILESIZE 155config FEATURE_SH_HISTFILESIZE
149 bool "Use $HISTFILESIZE" 156 bool "Use $HISTFILESIZE"
150 default y 157 default y
151 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH 158 depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
152 help 159 help
153 This option makes busybox shells to use $HISTFILESIZE variable 160 This option makes busybox shells to use $HISTFILESIZE variable
154 to set shell history size. Note that its max value is capped 161 to set shell history size. Note that its max value is capped
155 by "History size" setting in library tuning section. 162 by "History size" setting in library tuning section.
156 163
157endif # Options common to all shells 164endif # Options common to all shells
158 165
diff --git a/shell/ash.c b/shell/ash.c
index dc5561765..28b522d7c 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -31,15 +31,14 @@
31 */ 31 */
32 32
33//config:config ASH 33//config:config ASH
34//config: bool "ash" 34//config: bool "ash (77 kb)"
35//config: default y 35//config: default y
36//config: depends on !NOMMU 36//config: depends on !NOMMU
37//config: help 37//config: help
38//config: Tha 'ash' shell adds about 60k in the default configuration and is 38//config: The most complete and most pedantically correct shell included with
39//config: the most complete and most pedantically correct shell included with 39//config: busybox. This shell is actually a derivative of the Debian 'dash'
40//config: busybox. This shell is actually a derivative of the Debian 'dash' 40//config: shell (by Herbert Xu), which was created by porting the 'ash' shell
41//config: shell (by Herbert Xu), which was created by porting the 'ash' shell 41//config: (written by Kenneth Almquist) from NetBSD.
42//config: (written by Kenneth Almquist) from NetBSD.
43//config: 42//config:
44//config:# ash options 43//config:# ash options
45//config:# note: Don't remove !NOMMU part in the next line; it would break 44//config:# note: Don't remove !NOMMU part in the next line; it would break
@@ -56,11 +55,11 @@
56//config: default y # Y is bigger, but because of uclibc glob() bug, let Y be default for now 55//config: default y # Y is bigger, but because of uclibc glob() bug, let Y be default for now
57//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 56//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
58//config: help 57//config: help
59//config: Do not use glob() function from libc, use internal implementation. 58//config: Do not use glob() function from libc, use internal implementation.
60//config: Use this if you are getting "glob.h: No such file or directory" 59//config: Use this if you are getting "glob.h: No such file or directory"
61//config: or similar build errors. 60//config: or similar build errors.
62//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs 61//config: Note that as of now (2017-01), uclibc and musl glob() both have bugs
63//config: which would break ash if you select N here. 62//config: which would break ash if you select N here.
64//config: 63//config:
65//config:config ASH_BASH_COMPAT 64//config:config ASH_BASH_COMPAT
66//config: bool "bash-compatible extensions" 65//config: bool "bash-compatible extensions"
@@ -82,37 +81,37 @@
82//config: default y 81//config: default y
83//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 82//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
84//config: help 83//config: help
85//config: Enable pseudorandom generator and dynamic variable "$RANDOM". 84//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
86//config: Each read of "$RANDOM" will generate a new pseudorandom value. 85//config: Each read of "$RANDOM" will generate a new pseudorandom value.
87//config: You can reset the generator by using a specified start value. 86//config: You can reset the generator by using a specified start value.
88//config: After "unset RANDOM" the generator will switch off and this 87//config: After "unset RANDOM" the generator will switch off and this
89//config: variable will no longer have special treatment. 88//config: variable will no longer have special treatment.
90//config: 89//config:
91//config:config ASH_EXPAND_PRMT 90//config:config ASH_EXPAND_PRMT
92//config: bool "Expand prompt string" 91//config: bool "Expand prompt string"
93//config: default y 92//config: default y
94//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 93//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
95//config: help 94//config: help
96//config: $PS# may contain volatile content, such as backquote commands. 95//config: $PS# may contain volatile content, such as backquote commands.
97//config: This option recreates the prompt string from the environment 96//config: This option recreates the prompt string from the environment
98//config: variable each time it is displayed. 97//config: variable each time it is displayed.
99//config: 98//config:
100//config:config ASH_IDLE_TIMEOUT 99//config:config ASH_IDLE_TIMEOUT
101//config: bool "Idle timeout variable $TMOUT" 100//config: bool "Idle timeout variable $TMOUT"
102//config: default y 101//config: default y
103//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 102//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
104//config: help 103//config: help
105//config: Enable bash-like auto-logout after $TMOUT seconds of idle time. 104//config: Enable bash-like auto-logout after $TMOUT seconds of idle time.
106//config: 105//config:
107//config:config ASH_MAIL 106//config:config ASH_MAIL
108//config: bool "Check for new mail in interactive shell" 107//config: bool "Check for new mail in interactive shell"
109//config: default y 108//config: default y
110//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 109//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
111//config: help 110//config: help
112//config: Enable "check for new mail" function: 111//config: Enable "check for new mail" function:
113//config: if set, $MAIL file and $MAILPATH list of files 112//config: if set, $MAIL file and $MAILPATH list of files
114//config: are checked for mtime changes, and "you have mail" 113//config: are checked for mtime changes, and "you have mail"
115//config: message is printed if change is detected. 114//config: message is printed if change is detected.
116//config: 115//config:
117//config:config ASH_ECHO 116//config:config ASH_ECHO
118//config: bool "echo builtin" 117//config: bool "echo builtin"
@@ -144,9 +143,9 @@
144//config: default y 143//config: default y
145//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 144//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
146//config: help 145//config: help
147//config: Enable support for the 'command' builtin, which allows 146//config: Enable support for the 'command' builtin, which allows
148//config: you to run the specified command or builtin, 147//config: you to run the specified command or builtin,
149//config: even when there is a function with the same name. 148//config: even when there is a function with the same name.
150//config: 149//config:
151//config: 150//config:
152//config:config ASH_NOCONSOLE 151//config:config ASH_NOCONSOLE
@@ -1746,7 +1745,7 @@ static char *
1746stack_nputstr(const char *s, size_t n, char *p) 1745stack_nputstr(const char *s, size_t n, char *p)
1747{ 1746{
1748 p = makestrspace(n, p); 1747 p = makestrspace(n, p);
1749 p = (char *)memcpy(p, s, n) + n; 1748 p = (char *)mempcpy(p, s, n);
1750 return p; 1749 return p;
1751} 1750}
1752 1751
@@ -1830,7 +1829,7 @@ number(const char *s)
1830} 1829}
1831 1830
1832/* 1831/*
1833 * Produce a possibly single quoted string suitable as input to the shell. 1832 * Produce a single quoted string suitable as input to the shell.
1834 * The return string is allocated on the stack. 1833 * The return string is allocated on the stack.
1835 */ 1834 */
1836static char * 1835static char *
@@ -1849,7 +1848,7 @@ single_quote(const char *s)
1849 q = p = makestrspace(len + 3, p); 1848 q = p = makestrspace(len + 3, p);
1850 1849
1851 *q++ = '\''; 1850 *q++ = '\'';
1852 q = (char *)memcpy(q, s, len) + len; 1851 q = (char *)mempcpy(q, s, len);
1853 *q++ = '\''; 1852 *q++ = '\'';
1854 s += len; 1853 s += len;
1855 1854
@@ -1863,7 +1862,7 @@ single_quote(const char *s)
1863 q = p = makestrspace(len + 3, p); 1862 q = p = makestrspace(len + 3, p);
1864 1863
1865 *q++ = '"'; 1864 *q++ = '"';
1866 q = (char *)memcpy(q, s - len, len) + len; 1865 q = (char *)mempcpy(q, s - len, len);
1867 *q++ = '"'; 1866 *q++ = '"';
1868 1867
1869 STADJUST(q - p, p); 1868 STADJUST(q - p, p);
@@ -1874,6 +1873,47 @@ single_quote(const char *s)
1874 return stackblock(); 1873 return stackblock();
1875} 1874}
1876 1875
1876/*
1877 * Produce a possibly single quoted string suitable as input to the shell.
1878 * If 'conditional' is nonzero, quoting is only done if the string contains
1879 * non-shellsafe characters, or is identical to a shell keyword (reserved
1880 * word); if it is zero, quoting is always done.
1881 * If quoting was done, the return string is allocated on the stack,
1882 * otherwise a pointer to the original string is returned.
1883 */
1884static const char *
1885maybe_single_quote(const char *s)
1886{
1887 const char *p = s;
1888
1889 while (*p) {
1890 /* Assuming ACSII */
1891 /* quote ctrl_chars space !"#$%&'()* */
1892 if (*p < '+')
1893 goto need_quoting;
1894 /* quote ;<=>? */
1895 if (*p >= ';' && *p <= '?')
1896 goto need_quoting;
1897 /* quote `[\ */
1898 if (*p == '`')
1899 goto need_quoting;
1900 if (*p == '[')
1901 goto need_quoting;
1902 if (*p == '\\')
1903 goto need_quoting;
1904 /* quote {|}~ DEL and high bytes */
1905 if (*p > 'z')
1906 goto need_quoting;
1907 /* Not quoting these: +,-./ 0-9 :@ A-Z ]^_ a-z */
1908 /* TODO: maybe avoid quoting % */
1909 p++;
1910 }
1911 return s;
1912
1913 need_quoting:
1914 return single_quote(s);
1915}
1916
1877 1917
1878/* ============ nextopt */ 1918/* ============ nextopt */
1879 1919
@@ -2362,10 +2402,10 @@ setvar(const char *name, const char *val, int flags)
2362 2402
2363 INT_OFF; 2403 INT_OFF;
2364 nameeq = ckmalloc(namelen + vallen + 2); 2404 nameeq = ckmalloc(namelen + vallen + 2);
2365 p = memcpy(nameeq, name, namelen) + namelen; 2405 p = mempcpy(nameeq, name, namelen);
2366 if (val) { 2406 if (val) {
2367 *p++ = '='; 2407 *p++ = '=';
2368 p = memcpy(p, val, vallen) + vallen; 2408 p = mempcpy(p, val, vallen);
2369 } 2409 }
2370 *p = '\0'; 2410 *p = '\0';
2371 setvareq(nameeq, flags | VNOSAVE); 2411 setvareq(nameeq, flags | VNOSAVE);
@@ -2512,8 +2552,7 @@ path_advance(const char **path, const char *name)
2512 growstackblock(); 2552 growstackblock();
2513 q = stackblock(); 2553 q = stackblock();
2514 if (p != start) { 2554 if (p != start) {
2515 memcpy(q, start, p - start); 2555 q = mempcpy(q, start, p - start);
2516 q += p - start;
2517 *q++ = '/'; 2556 *q++ = '/';
2518 } 2557 }
2519 strcpy(q, name); 2558 strcpy(q, name);
@@ -6277,7 +6316,7 @@ rmescapes(char *str, int flag)
6277 } 6316 }
6278 q = r; 6317 q = r;
6279 if (len > 0) { 6318 if (len > 0) {
6280 q = (char *)memcpy(q, str, len) + len; 6319 q = (char *)mempcpy(q, str, len);
6281 } 6320 }
6282 } 6321 }
6283 6322
@@ -10161,18 +10200,36 @@ evalcommand(union node *cmd, int flags)
10161 10200
10162 /* Print the command if xflag is set. */ 10201 /* Print the command if xflag is set. */
10163 if (xflag) { 10202 if (xflag) {
10164 int n; 10203 const char *pfx = "";
10165 const char *p = " %s" + 1; 10204
10205 fdprintf(preverrout_fd, "%s", expandstr(ps4val()));
10166 10206
10167 fdprintf(preverrout_fd, p, expandstr(ps4val()));
10168 sp = varlist.list; 10207 sp = varlist.list;
10169 for (n = 0; n < 2; n++) { 10208 while (sp) {
10170 while (sp) { 10209 char *varval = sp->text;
10171 fdprintf(preverrout_fd, p, sp->text); 10210 char *eq = strchrnul(varval, '=');
10172 sp = sp->next; 10211 if (*eq)
10173 p = " %s"; 10212 eq++;
10174 } 10213 fdprintf(preverrout_fd, "%s%.*s%s",
10175 sp = arglist.list; 10214 pfx,
10215 (int)(eq - varval), varval,
10216 maybe_single_quote(eq)
10217 );
10218 sp = sp->next;
10219 pfx = " ";
10220 }
10221
10222 sp = arglist.list;
10223 while (sp) {
10224 fdprintf(preverrout_fd, "%s%s",
10225 pfx,
10226 /* always quote if matches reserved word: */
10227 findkwd(sp->text)
10228 ? single_quote(sp->text)
10229 : maybe_single_quote(sp->text)
10230 );
10231 sp = sp->next;
10232 pfx = " ";
10176 } 10233 }
10177 safe_write(preverrout_fd, "\n", 1); 10234 safe_write(preverrout_fd, "\n", 1);
10178 } 10235 }
diff --git a/shell/ash_test/ash-parsing/argv0.right b/shell/ash_test/ash-parsing/argv0.right
new file mode 100644
index 000000000..d86bac9de
--- /dev/null
+++ b/shell/ash_test/ash-parsing/argv0.right
@@ -0,0 +1 @@
OK
diff --git a/shell/ash_test/ash-parsing/argv0.tests b/shell/ash_test/ash-parsing/argv0.tests
new file mode 100755
index 000000000..f5c40f6fe
--- /dev/null
+++ b/shell/ash_test/ash-parsing/argv0.tests
@@ -0,0 +1,4 @@
1if test $# = 0; then
2 exec "$THIS_SH" "$0" arg
3fi
4echo OK
diff --git a/shell/ash_test/ash-parsing/brace1.right b/shell/ash_test/ash-parsing/brace1.right
new file mode 100644
index 000000000..10aa7a419
--- /dev/null
+++ b/shell/ash_test/ash-parsing/brace1.right
@@ -0,0 +1,7 @@
1{abc}
2{
3}
4./brace1.tests: line 4: {cmd: not found
5./brace1.tests: line 5: {: not found
6./brace1.tests: line 6: {: not found
7Done: 127
diff --git a/shell/ash_test/ash-parsing/brace1.tests b/shell/ash_test/ash-parsing/brace1.tests
new file mode 100755
index 000000000..2b45927c0
--- /dev/null
+++ b/shell/ash_test/ash-parsing/brace1.tests
@@ -0,0 +1,7 @@
1echo {abc}
2echo {
3echo }
4{cmd
5""{
6{""
7echo Done: $?
diff --git a/shell/ash_test/ash-parsing/brace2.right b/shell/ash_test/ash-parsing/brace2.right
new file mode 100644
index 000000000..37a966654
--- /dev/null
+++ b/shell/ash_test/ash-parsing/brace2.right
@@ -0,0 +1,3 @@
1{q,w}
2{q,w}
3Done
diff --git a/shell/ash_test/ash-parsing/brace2.tests b/shell/ash_test/ash-parsing/brace2.tests
new file mode 100755
index 000000000..ef75f0b70
--- /dev/null
+++ b/shell/ash_test/ash-parsing/brace2.tests
@@ -0,0 +1,5 @@
1v='{q,w}'
2# Should not brace-expand v value
3echo $v
4echo "$v"
5echo Done
diff --git a/shell/ash_test/ash-parsing/comment1.right b/shell/ash_test/ash-parsing/comment1.right
new file mode 100644
index 000000000..a102b1d4e
--- /dev/null
+++ b/shell/ash_test/ash-parsing/comment1.right
@@ -0,0 +1,2 @@
1Nothing:
2String: #should-be-echoed
diff --git a/shell/ash_test/ash-parsing/comment1.tests b/shell/ash_test/ash-parsing/comment1.tests
new file mode 100755
index 000000000..d268860ff
--- /dev/null
+++ b/shell/ash_test/ash-parsing/comment1.tests
@@ -0,0 +1,2 @@
1echo Nothing: #should-not-be-echoed
2echo String: ""#should-be-echoed
diff --git a/shell/ash_test/ash-parsing/eol1.right b/shell/ash_test/ash-parsing/eol1.right
new file mode 100644
index 000000000..31c896f62
--- /dev/null
+++ b/shell/ash_test/ash-parsing/eol1.right
@@ -0,0 +1 @@
Done:0
diff --git a/shell/ash_test/ash-parsing/eol1.tests b/shell/ash_test/ash-parsing/eol1.tests
new file mode 100755
index 000000000..f1b55e8b8
--- /dev/null
+++ b/shell/ash_test/ash-parsing/eol1.tests
@@ -0,0 +1,18 @@
1# bug was that we treated <newline> as ';' in this line:
2true || echo foo |
3echo BAD1 | cat
4
5# variation on the same theme
6true || echo foo |
7# comment
8echo BAD2 | cat
9
10# variation on the same theme
11true || echo foo |
12
13echo BAD3 | cat
14
15# this should error out, but currently works in hush:
16#true || echo foo |;
17
18echo Done:$?
diff --git a/shell/ash_test/ash-parsing/escape1.right b/shell/ash_test/ash-parsing/escape1.right
new file mode 100644
index 000000000..1899b87ef
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape1.right
@@ -0,0 +1,4 @@
1\
2a\b
3\\
4c\\d
diff --git a/shell/ash_test/ash-parsing/escape1.tests b/shell/ash_test/ash-parsing/escape1.tests
new file mode 100755
index 000000000..25ac96b25
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape1.tests
@@ -0,0 +1,6 @@
1test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
2
3echo "\\"
4echo a"\\"b
5echo '\\'
6echo c'\\'d
diff --git a/shell/ash_test/ash-parsing/escape2.right b/shell/ash_test/ash-parsing/escape2.right
new file mode 100644
index 000000000..f55fd4a42
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape2.right
@@ -0,0 +1,4 @@
1*?[a]*
2a*?[a]*b
3*?[a]*
4c*?[a]*d
diff --git a/shell/ash_test/ash-parsing/escape2.tests b/shell/ash_test/ash-parsing/escape2.tests
new file mode 100755
index 000000000..ee718018d
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape2.tests
@@ -0,0 +1,4 @@
1echo "*?[a]*"
2echo a"*?[a]*"b
3echo '*?[a]*'
4echo c'*?[a]*'d
diff --git a/shell/ash_test/ash-parsing/escape3.right b/shell/ash_test/ash-parsing/escape3.right
new file mode 100644
index 000000000..da02a976a
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape3.right
@@ -0,0 +1,23 @@
1v: a \ b \\ c \\\ d \\\\ e
2v: a \ b \\ c \\\ d \\\\ e
3Unquoted:
4.a.
5.\.
6.b.
7.\\.
8.c.
9.\\\.
10.d.
11.\\\\.
12.e.
13Quoted:
14.a.
15.\.
16.b.
17.\\.
18.c.
19.\\\.
20.d.
21.\\\\.
22.e.
23done
diff --git a/shell/ash_test/ash-parsing/escape3.tests b/shell/ash_test/ash-parsing/escape3.tests
new file mode 100755
index 000000000..18705bd0c
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape3.tests
@@ -0,0 +1,10 @@
1test "$CONFIG_FEATURE_FANCY_ECHO" = "y" || exit 77
2
3v='a \ b \\ c \\\ d \\\\ e'
4echo v: $v
5echo v: "$v"
6echo Unquoted:
7for a in $v; do echo .$a.; done
8echo Quoted:
9for a in $v; do echo ".$a."; done
10echo done
diff --git a/shell/ash_test/ash-parsing/escape4.right b/shell/ash_test/ash-parsing/escape4.right
new file mode 100644
index 000000000..5de3e0c90
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape4.right
@@ -0,0 +1,2 @@
1Ok
2End
diff --git a/shell/ash_test/ash-parsing/escape4.tests b/shell/ash_test/ash-parsing/escape4.tests
new file mode 100755
index 000000000..df8bf0af7
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape4.tests
@@ -0,0 +1,6 @@
1i\
2f tr\
3ue; th\
4en echo "O\
5k"; fi; echo "\
6End" \ No newline at end of file
diff --git a/shell/ash_test/ash-parsing/escape5.right b/shell/ash_test/ash-parsing/escape5.right
new file mode 100644
index 000000000..3cdd393c7
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape5.right
@@ -0,0 +1,9 @@
1a\nb\nc\n
2a
3b
4c
5a\nb\nc\n
6a
7b
8c
9Done
diff --git a/shell/ash_test/ash-parsing/escape5.tests b/shell/ash_test/ash-parsing/escape5.tests
new file mode 100755
index 000000000..337a98ec7
--- /dev/null
+++ b/shell/ash_test/ash-parsing/escape5.tests
@@ -0,0 +1,7 @@
1v="a\nb\nc\n"
2echo "$v"
3printf "$v"
4v='a\nb\nc\n'
5echo "$v"
6printf "$v"
7echo Done
diff --git a/shell/ash_test/ash-parsing/group1.right b/shell/ash_test/ash-parsing/group1.right
new file mode 100644
index 000000000..6a7c4be0a
--- /dev/null
+++ b/shell/ash_test/ash-parsing/group1.right
@@ -0,0 +1 @@
word} }
diff --git a/shell/ash_test/ash-parsing/group1.tests b/shell/ash_test/ash-parsing/group1.tests
new file mode 100755
index 000000000..f063fbcb3
--- /dev/null
+++ b/shell/ash_test/ash-parsing/group1.tests
@@ -0,0 +1 @@
{ echo word} }; }
diff --git a/shell/ash_test/ash-parsing/group2.right b/shell/ash_test/ash-parsing/group2.right
new file mode 100644
index 000000000..df4d9306a
--- /dev/null
+++ b/shell/ash_test/ash-parsing/group2.right
@@ -0,0 +1,2 @@
1got TERM
2Done: 0
diff --git a/shell/ash_test/ash-parsing/group2.tests b/shell/ash_test/ash-parsing/group2.tests
new file mode 100755
index 000000000..d99178585
--- /dev/null
+++ b/shell/ash_test/ash-parsing/group2.tests
@@ -0,0 +1,3 @@
1# Bug was in handling of "}&" without space
2{ trap "echo got TERM" TERM; sleep 2; }& sleep 1; kill $!; wait
3echo Done: $?
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords1.right b/shell/ash_test/ash-parsing/groups_and_keywords1.right
new file mode 100644
index 000000000..4c46650dc
--- /dev/null
+++ b/shell/ash_test/ash-parsing/groups_and_keywords1.right
@@ -0,0 +1,11 @@
1Semicolons after } can be omitted 1:
2foo
3bar
4Semicolons after } can be omitted 2:
5foo
6bar
7Semicolons after fi can be omitted:
8foo
9bar
10baz
11Done:0
diff --git a/shell/ash_test/ash-parsing/groups_and_keywords1.tests b/shell/ash_test/ash-parsing/groups_and_keywords1.tests
new file mode 100755
index 000000000..01944d714
--- /dev/null
+++ b/shell/ash_test/ash-parsing/groups_and_keywords1.tests
@@ -0,0 +1,10 @@
1echo "Semicolons after } can be omitted 1:"
2if { echo foo; } then { echo bar; } fi
3
4echo "Semicolons after } can be omitted 2:"
5while { echo foo; } do { echo bar; break; } done
6
7echo "Semicolons after fi can be omitted:"
8while if echo foo; then echo bar; fi do echo baz; break; done
9
10echo Done:$?
diff --git a/shell/ash_test/ash-parsing/negate.right b/shell/ash_test/ash-parsing/negate.right
new file mode 100644
index 000000000..61d2ecd3a
--- /dev/null
+++ b/shell/ash_test/ash-parsing/negate.right
@@ -0,0 +1,36 @@
1! printing !
20
31
41
50
60
70
8!
9a
10b
11c
12! 1
13a 1
14b 1
15c 1
16! 1
17a 1
18b 1
19c 1
200
210
220
230
241
251
261
271
280
290
300
310
321
331
341
351
36Done
diff --git a/shell/ash_test/ash-parsing/negate.tests b/shell/ash_test/ash-parsing/negate.tests
new file mode 100755
index 000000000..51151cbd4
--- /dev/null
+++ b/shell/ash_test/ash-parsing/negate.tests
@@ -0,0 +1,19 @@
1echo ! printing !
2! false
3echo $?
4! true
5echo $?
6if ! false; then false; echo $?; fi
7echo $?
8if ! false; then ! false; echo $?; fi
9echo $?
10PRINTF=`which printf`
11for a in ! a b c; do echo $a; done
12for a in ! a b c; do ! printf "$a "; echo $?; done
13test x"$PRINTF" = x"" && exit 1
14for a in ! a b c; do ! "$PRINTF" "$a "; echo $?; done
15for a in ! a b c; do ! printf "$a " | false; echo $?; done
16for a in ! a b c; do ! printf "$a " | true; echo $?; done
17for a in ! a b c; do ! { printf "$a " | false; }; echo $?; done
18for a in ! a b c; do ! { printf "$a " | true; }; echo $?; done
19echo Done
diff --git a/shell/ash_test/ash-parsing/noeol.right b/shell/ash_test/ash-parsing/noeol.right
new file mode 100644
index 000000000..e427984d4
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol.right
@@ -0,0 +1 @@
HELLO
diff --git a/shell/ash_test/ash-parsing/noeol.tests b/shell/ash_test/ash-parsing/noeol.tests
new file mode 100755
index 000000000..a93113a03
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol.tests
@@ -0,0 +1,2 @@
1# next line has no EOL!
2echo HELLO \ No newline at end of file
diff --git a/shell/ash_test/ash-parsing/noeol2.right b/shell/ash_test/ash-parsing/noeol2.right
new file mode 100644
index 000000000..d00491fd7
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol2.right
@@ -0,0 +1 @@
1
diff --git a/shell/ash_test/ash-parsing/noeol2.tests b/shell/ash_test/ash-parsing/noeol2.tests
new file mode 100755
index 000000000..1220f056f
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol2.tests
@@ -0,0 +1,7 @@
1# last line has no EOL!
2if true
3then
4 echo 1
5else
6 echo 2
7fi \ No newline at end of file
diff --git a/shell/ash_test/ash-parsing/noeol3.right b/shell/ash_test/ash-parsing/noeol3.right
new file mode 100644
index 000000000..c2a0e38f8
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol3.right
@@ -0,0 +1 @@
./noeol3.tests: line 2: syntax error: unterminated quoted string
diff --git a/shell/ash_test/ash-parsing/noeol3.tests b/shell/ash_test/ash-parsing/noeol3.tests
new file mode 100755
index 000000000..ec958ed7a
--- /dev/null
+++ b/shell/ash_test/ash-parsing/noeol3.tests
@@ -0,0 +1,2 @@
1# last line has no EOL!
2echo "unterminated \ No newline at end of file
diff --git a/shell/ash_test/ash-parsing/process_subst.right b/shell/ash_test/ash-parsing/process_subst.right
new file mode 100644
index 000000000..397bc8067
--- /dev/null
+++ b/shell/ash_test/ash-parsing/process_subst.right
@@ -0,0 +1,3 @@
1TESTzzBEST
2TEST$(echo zz)BEST
3TEST'BEST
diff --git a/shell/ash_test/ash-parsing/process_subst.tests b/shell/ash_test/ash-parsing/process_subst.tests
new file mode 100755
index 000000000..21996bc0e
--- /dev/null
+++ b/shell/ash_test/ash-parsing/process_subst.tests
@@ -0,0 +1,3 @@
1echo "TEST`echo zz;echo;echo`BEST"
2echo "TEST`echo '$(echo zz)'`BEST"
3echo "TEST`echo "'"`BEST"
diff --git a/shell/ash_test/ash-parsing/quote1.right b/shell/ash_test/ash-parsing/quote1.right
new file mode 100644
index 000000000..cb382054c
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote1.right
@@ -0,0 +1 @@
'1'
diff --git a/shell/ash_test/ash-parsing/quote1.tests b/shell/ash_test/ash-parsing/quote1.tests
new file mode 100755
index 000000000..f55895466
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote1.tests
@@ -0,0 +1,2 @@
1a=1
2echo "'$a'"
diff --git a/shell/ash_test/ash-parsing/quote2.right b/shell/ash_test/ash-parsing/quote2.right
new file mode 100644
index 000000000..3bc9edcd6
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote2.right
@@ -0,0 +1 @@
>1
diff --git a/shell/ash_test/ash-parsing/quote2.tests b/shell/ash_test/ash-parsing/quote2.tests
new file mode 100755
index 000000000..bd966f30b
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote2.tests
@@ -0,0 +1,2 @@
1a=1
2echo ">$a"
diff --git a/shell/ash_test/ash-parsing/quote3.right b/shell/ash_test/ash-parsing/quote3.right
new file mode 100644
index 000000000..bbe46df67
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote3.right
@@ -0,0 +1,12 @@
1Testing: in ""
2..
3Testing: in ''
4..
5Testing: in $empty
6Testing: in $empty""
7..
8Testing: in $empty''
9..
10Testing: in "$empty"
11..
12Finished
diff --git a/shell/ash_test/ash-parsing/quote3.tests b/shell/ash_test/ash-parsing/quote3.tests
new file mode 100755
index 000000000..b5fd5978c
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote3.tests
@@ -0,0 +1,21 @@
1empty=''
2
3echo 'Testing: in ""'
4for a in ""; do echo ".$a."; done
5
6echo 'Testing: in '"''"
7for a in ''; do echo ".$a."; done
8
9echo 'Testing: in $empty'
10for a in $empty; do echo ".$a."; done
11
12echo 'Testing: in $empty""'
13for a in $empty""; do echo ".$a."; done
14
15echo 'Testing: in $empty'"''"
16for a in $empty''; do echo ".$a."; done
17
18echo 'Testing: in "$empty"'
19for a in "$empty"; do echo ".$a."; done
20
21echo Finished
diff --git a/shell/ash_test/ash-parsing/quote4.right b/shell/ash_test/ash-parsing/quote4.right
new file mode 100644
index 000000000..b2901ea97
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote4.right
@@ -0,0 +1 @@
a b
diff --git a/shell/ash_test/ash-parsing/quote4.tests b/shell/ash_test/ash-parsing/quote4.tests
new file mode 100755
index 000000000..f1dabfa54
--- /dev/null
+++ b/shell/ash_test/ash-parsing/quote4.tests
@@ -0,0 +1,2 @@
1a_b='a b'
2echo "$a_b"
diff --git a/shell/ash_test/ash-parsing/starquoted.right b/shell/ash_test/ash-parsing/starquoted.right
new file mode 100644
index 000000000..b56323fe1
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted.right
@@ -0,0 +1,8 @@
1.1 abc d e f.
2.1.
3.abc.
4.d e f.
5.-1 abc d e f-.
6.-1.
7.abc.
8.d e f-.
diff --git a/shell/ash_test/ash-parsing/starquoted.tests b/shell/ash_test/ash-parsing/starquoted.tests
new file mode 100755
index 000000000..2fe49b1cd
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted.tests
@@ -0,0 +1,8 @@
1if test $# = 0; then
2 exec "$THIS_SH" "$0" 1 abc 'd e f'
3fi
4
5for a in "$*"; do echo ".$a."; done
6for a in "$@"; do echo ".$a."; done
7for a in "-$*-"; do echo ".$a."; done
8for a in "-$@-"; do echo ".$a."; done
diff --git a/shell/ash_test/ash-parsing/starquoted2.right b/shell/ash_test/ash-parsing/starquoted2.right
new file mode 100644
index 000000000..1bff408ca
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted2.right
@@ -0,0 +1,8 @@
1Should be printed
2Would not be printed by bash
3Would not be printed by bash
4Would not be printed by bash
5Should be printed
6Empty:
7Empty:
8Empty:
diff --git a/shell/ash_test/ash-parsing/starquoted2.tests b/shell/ash_test/ash-parsing/starquoted2.tests
new file mode 100755
index 000000000..7c5ff45b8
--- /dev/null
+++ b/shell/ash_test/ash-parsing/starquoted2.tests
@@ -0,0 +1,19 @@
1if test $# != 0; then
2 exec "$THIS_SH" "$0"
3fi
4
5# No params!
6for a in "$*"; do echo Should be printed; done
7for a in "$@"; do echo Should not be printed; done
8# Yes, believe it or not, bash is mesmerized by "$@" and stops
9# treating "" as "this word cannot be expanded to nothing,
10# but must be at least null string". Now it can be expanded to nothing.
11for a in "$@"""; do echo Would not be printed by bash; done
12for a in """$@"; do echo Would not be printed by bash; done
13for a in """$@"''"$@"''; do echo Would not be printed by bash; done
14for a in ""; do echo Should be printed; done
15
16# Bug 207: "$@" expands to nothing, and we erroneously glob "%s\n" twice:
17printf 'Empty:%s\n' "$@"
18printf "Empty:%s\n" "$@"
19printf "Empty:%s\\n" "$@"
diff --git a/shell/ash_test/ash-quoting/mode_x.right b/shell/ash_test/ash-quoting/mode_x.right
new file mode 100644
index 000000000..c2dd3550c
--- /dev/null
+++ b/shell/ash_test/ash-quoting/mode_x.right
@@ -0,0 +1,10 @@
1+ var1=val
2+ var2='one two'
3+ true '%s\n' one 'two '"'"'three' four
4+ this=command
5+ 'this=command'
6./mode_x.tests: line 1: this=command: not found
7+ true
8+ true
9+ 'if' true
10./mode_x.tests: line 1: if: not found
diff --git a/shell/ash_test/ash-quoting/mode_x.tests b/shell/ash_test/ash-quoting/mode_x.tests
new file mode 100755
index 000000000..16dae3f4b
--- /dev/null
+++ b/shell/ash_test/ash-quoting/mode_x.tests
@@ -0,0 +1,14 @@
1set -x
2
3var1=val
4var2='one two'
5true %s\\n one "two 'three" four
6
7# assignment:
8this=command
9# NOT assignment, +x code should show it quoted:
10"this=command"
11
12if true; then true; fi
13# +x code should quote 'if' here:
14"if" true
diff --git a/shell/ash_test/ash-read/read_t0.right b/shell/ash_test/ash-read/read_t0.right
new file mode 100644
index 000000000..f02105961
--- /dev/null
+++ b/shell/ash_test/ash-read/read_t0.right
@@ -0,0 +1,3 @@
1><[0]
2><[0]
3><[1]
diff --git a/shell/ash_test/ash-read/read_t0.tests b/shell/ash_test/ash-read/read_t0.tests
new file mode 100755
index 000000000..6b7bc217b
--- /dev/null
+++ b/shell/ash_test/ash-read/read_t0.tests
@@ -0,0 +1,14 @@
1# ><[0]
2echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
3
4# This would not be deterministic: returns 0 "data exists" if EOF is seen
5# (true terminated) - because EOF is considered to be data (read will not block),
6# else returns 1 "no data".
7## ><[????]
8#true | { read -t 0 reply; echo ">$reply<[$?]"; }
9
10# ><[0]
11true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
12
13# ><[1]
14sleep 0.2 | { read -p IGNORED_PROMPT -t 0 reply; echo ">$reply<[$?]"; }
diff --git a/shell/ash_test/ash-vars/readonly0.right b/shell/ash_test/ash-vars/readonly0.right
new file mode 100644
index 000000000..f3a6bde9e
--- /dev/null
+++ b/shell/ash_test/ash-vars/readonly0.right
@@ -0,0 +1,13 @@
1readonly a='A'
2readonly b='B'
3Ok:0
4
5./readonly0.tests: line 19: a: is read only
6Fail:2
7./readonly0.tests: readonly: line 21: a: is read only
8Fail:2
9
10./readonly0.tests: export: line 27: a: is read only
11Fail:2
12
13Fail:1
diff --git a/shell/ash_test/ash-vars/readonly0.tests b/shell/ash_test/ash-vars/readonly0.tests
new file mode 100755
index 000000000..94af79060
--- /dev/null
+++ b/shell/ash_test/ash-vars/readonly0.tests
@@ -0,0 +1,45 @@
1unset a b
2#
3readonly a=A
4b=B
5readonly b
6# readonly on already readonly var is harmless:
7readonly b a
8readonly | grep '^readonly [ab]='
9# this should work:
10export a b
11export -n a b
12echo Ok:$?
13env | grep -e^a= -e^b= # shows nothing
14
15echo
16# these should all fail (despite the same value being assigned)
17# bash does not abort even in non-interactive more (in script)
18# ash does, using subshell to continue
19true; (a=A)
20echo Fail:$?
21true; (readonly a=A)
22echo Fail:$?
23
24echo
25# in bash, assignment in export fails, but export succeeds! :)
26# we don't mimic that!
27true; (export a=Z)
28echo Fail:$?
29#env | grep '^a='
30#echo "^^^a is exported"
31export -n a # undo that bashism, if it happens
32
33## ash: assignment errors in "a=Z CMD" lead to CMD not executed
34## echo
35## export b
36## # this fails to both set and export a:
37## a=Z env | echo grep '^[ab]='
38## echo "^^^a is not exported"
39## # but external command does get executed, and $? is not mangled (stays 42):
40## (exit 42); a=Z env echo Visible:$?
41
42echo
43# ash: this fails *silently*, bug? bash says "cannot unset: readonly variable"
44true; unset a
45echo Fail:$?
diff --git a/shell/ash_test/ash-vars/readonly1.right b/shell/ash_test/ash-vars/readonly1.right
index 2b363e325..1f5be64c7 100644
--- a/shell/ash_test/ash-vars/readonly1.right
+++ b/shell/ash_test/ash-vars/readonly1.right
@@ -1,2 +1,2 @@
1One:1 1Fail:2
2One:1 2Fail:2
diff --git a/shell/ash_test/ash-vars/readonly1.tests b/shell/ash_test/ash-vars/readonly1.tests
index 81b461f5f..f3cccd940 100755
--- a/shell/ash_test/ash-vars/readonly1.tests
+++ b/shell/ash_test/ash-vars/readonly1.tests
@@ -1,7 +1,7 @@
1readonly bla=123 1readonly bla=123
2# Bare "eval bla=123" should abort ("eval" is a special builtin): 2# Bare "eval bla=123" should abort ("eval" is a special builtin):
3(eval bla=123 2>/dev/null; echo BUG) 3(eval bla=123 2>/dev/null; echo BUG)
4echo One:$? 4echo Fail:$?
5# "command BLTIN" disables "special-ness", should not abort: 5# "command BLTIN" disables "special-ness", should not abort:
6command eval bla=123 2>/dev/null 6command eval bla=123 2>/dev/null
7echo One:$? 7echo Fail:$?
diff --git a/shell/ash_test/run-all b/shell/ash_test/run-all
index 983e6d184..caf033577 100755
--- a/shell/ash_test/run-all
+++ b/shell/ash_test/run-all
@@ -2,10 +2,24 @@
2 2
3TOPDIR=`pwd` 3TOPDIR=`pwd`
4 4
5test -x ash || { 5if test ! -x ash; then
6 echo "No ./ash - creating a link to ../../busybox" 6 if test ! -x ../../busybox; then
7 ln -s ../../busybox ash 7 echo "Can't run tests. Put ash binary into this directory (`pwd`)"
8} 8 exit 1
9 fi
10 echo "No ./ash - creating a link to ../../busybox"
11 ln -s ../../busybox ash
12fi
13if test ! -f .config; then
14 if test ! -f ../../.config; then
15 echo "Missing .config file"
16 exit 1
17 fi
18 cp ../../.config . || exit 1
19fi
20
21eval $(sed -e '/^#/d' -e '/^$/d' -e 's:^:export :' .config)
22
9test -x printenv || gcc -O2 -o printenv printenv.c || exit $? 23test -x printenv || gcc -O2 -o printenv printenv.c || exit $?
10test -x recho || gcc -O2 -o recho recho.c || exit $? 24test -x recho || gcc -O2 -o recho recho.c || exit $?
11test -x zecho || gcc -O2 -o zecho zecho.c || exit $? 25test -x zecho || gcc -O2 -o zecho zecho.c || exit $?
diff --git a/shell/cttyhack.c b/shell/cttyhack.c
index f9b59c263..9004b4763 100644
--- a/shell/cttyhack.c
+++ b/shell/cttyhack.c
@@ -11,48 +11,48 @@
11//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o 11//kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o
12 12
13//config:config CTTYHACK 13//config:config CTTYHACK
14//config: bool "cttyhack" 14//config: bool "cttyhack (2.5 kb)"
15//config: default y 15//config: default y
16//config: help 16//config: help
17//config: One common problem reported on the mailing list is the "can't 17//config: One common problem reported on the mailing list is the "can't
18//config: access tty; job control turned off" error message, which typically 18//config: access tty; job control turned off" error message, which typically
19//config: appears when one tries to use a shell with stdin/stdout on 19//config: appears when one tries to use a shell with stdin/stdout on
20//config: /dev/console. 20//config: /dev/console.
21//config: This device is special - it cannot be a controlling tty. 21//config: This device is special - it cannot be a controlling tty.
22//config: 22//config:
23//config: The proper solution is to use the correct device instead of 23//config: The proper solution is to use the correct device instead of
24//config: /dev/console. 24//config: /dev/console.
25//config: 25//config:
26//config: cttyhack provides a "quick and dirty" solution to this problem. 26//config: cttyhack provides a "quick and dirty" solution to this problem.
27//config: It analyzes stdin with various ioctls, trying to determine whether 27//config: It analyzes stdin with various ioctls, trying to determine whether
28//config: it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line). 28//config: it is a /dev/ttyN or /dev/ttySN (virtual terminal or serial line).
29//config: On Linux it also checks sysfs for a pointer to the active console. 29//config: On Linux it also checks sysfs for a pointer to the active console.
30//config: If cttyhack is able to find the real console device, it closes 30//config: If cttyhack is able to find the real console device, it closes
31//config: stdin/out/err and reopens that device. 31//config: stdin/out/err and reopens that device.
32//config: Then it executes the given program. Opening the device will make 32//config: Then it executes the given program. Opening the device will make
33//config: that device a controlling tty. This may require cttyhack 33//config: that device a controlling tty. This may require cttyhack
34//config: to be a session leader. 34//config: to be a session leader.
35//config: 35//config:
36//config: Example for /etc/inittab (for busybox init): 36//config: Example for /etc/inittab (for busybox init):
37//config: 37//config:
38//config: ::respawn:/bin/cttyhack /bin/sh 38//config: ::respawn:/bin/cttyhack /bin/sh
39//config: 39//config:
40//config: Starting an interactive shell from boot shell script: 40//config: Starting an interactive shell from boot shell script:
41//config: 41//config:
42//config: setsid cttyhack sh 42//config: setsid cttyhack sh
43//config: 43//config:
44//config: Giving controlling tty to shell running with PID 1: 44//config: Giving controlling tty to shell running with PID 1:
45//config: 45//config:
46//config: # exec cttyhack sh 46//config: # exec cttyhack sh
47//config: 47//config:
48//config: Without cttyhack, you need to know exact tty name, 48//config: Without cttyhack, you need to know exact tty name,
49//config: and do something like this: 49//config: and do something like this:
50//config: 50//config:
51//config: # exec setsid sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1' 51//config: # exec setsid sh -c 'exec sh </dev/tty1 >/dev/tty1 2>&1'
52//config: 52//config:
53//config: Starting getty on a controlling tty from a shell script: 53//config: Starting getty on a controlling tty from a shell script:
54//config: 54//config:
55//config: # getty 115200 $(cttyhack) 55//config: # getty 115200 $(cttyhack)
56 56
57//usage:#define cttyhack_trivial_usage 57//usage:#define cttyhack_trivial_usage
58//usage: "[PROG ARGS]" 58//usage: "[PROG ARGS]"
diff --git a/shell/hush.c b/shell/hush.c
index f6da826d3..309ed2139 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -101,18 +101,18 @@
101 * aaa 101 * aaa
102 */ 102 */
103//config:config HUSH 103//config:config HUSH
104//config: bool "hush" 104//config: bool "hush (64 kb)"
105//config: default y 105//config: default y
106//config: help 106//config: help
107//config: hush is a small shell (25k). It handles the normal flow control 107//config: hush is a small shell. It handles the normal flow control
108//config: constructs such as if/then/elif/else/fi, for/in/do/done, while loops, 108//config: constructs such as if/then/elif/else/fi, for/in/do/done, while loops,
109//config: case/esac. Redirections, here documents, $((arithmetic)) 109//config: case/esac. Redirections, here documents, $((arithmetic))
110//config: and functions are supported. 110//config: and functions are supported.
111//config: 111//config:
112//config: It will compile and work on no-mmu systems. 112//config: It will compile and work on no-mmu systems.
113//config: 113//config:
114//config: It does not handle select, aliases, tilde expansion, 114//config: It does not handle select, aliases, tilde expansion,
115//config: &>file and >&file redirection of stdout+stderr. 115//config: &>file and >&file redirection of stdout+stderr.
116//config: 116//config:
117//config:config HUSH_BASH_COMPAT 117//config:config HUSH_BASH_COMPAT
118//config: bool "bash-compatible extensions" 118//config: bool "bash-compatible extensions"
@@ -124,17 +124,17 @@
124//config: default y 124//config: default y
125//config: depends on HUSH_BASH_COMPAT 125//config: depends on HUSH_BASH_COMPAT
126//config: help 126//config: help
127//config: Enable {abc,def} extension. 127//config: Enable {abc,def} extension.
128//config: 128//config:
129//config:config HUSH_INTERACTIVE 129//config:config HUSH_INTERACTIVE
130//config: bool "Interactive mode" 130//config: bool "Interactive mode"
131//config: default y 131//config: default y
132//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 132//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
133//config: help 133//config: help
134//config: Enable interactive mode (prompt and command editing). 134//config: Enable interactive mode (prompt and command editing).
135//config: Without this, hush simply reads and executes commands 135//config: Without this, hush simply reads and executes commands
136//config: from stdin just like a shell script from a file. 136//config: from stdin just like a shell script from a file.
137//config: No prompt, no PS1/PS2 magic shell variables. 137//config: No prompt, no PS1/PS2 magic shell variables.
138//config: 138//config:
139//config:config HUSH_SAVEHISTORY 139//config:config HUSH_SAVEHISTORY
140//config: bool "Save command history to .hush_history" 140//config: bool "Save command history to .hush_history"
@@ -146,18 +146,18 @@
146//config: default y 146//config: default y
147//config: depends on HUSH_INTERACTIVE 147//config: depends on HUSH_INTERACTIVE
148//config: help 148//config: help
149//config: Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current 149//config: Enable job control: Ctrl-Z backgrounds, Ctrl-C interrupts current
150//config: command (not entire shell), fg/bg builtins work. Without this option, 150//config: command (not entire shell), fg/bg builtins work. Without this option,
151//config: "cmd &" still works by simply spawning a process and immediately 151//config: "cmd &" still works by simply spawning a process and immediately
152//config: prompting for next command (or executing next command in a script), 152//config: prompting for next command (or executing next command in a script),
153//config: but no separate process group is formed. 153//config: but no separate process group is formed.
154//config: 154//config:
155//config:config HUSH_TICK 155//config:config HUSH_TICK
156//config: bool "Support process substitution" 156//config: bool "Support process substitution"
157//config: default y 157//config: default y
158//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 158//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
159//config: help 159//config: help
160//config: Enable `command` and $(command). 160//config: Enable `command` and $(command).
161//config: 161//config:
162//config:config HUSH_IF 162//config:config HUSH_IF
163//config: bool "Support if/then/elif/else/fi" 163//config: bool "Support if/then/elif/else/fi"
@@ -174,37 +174,37 @@
174//config: default y 174//config: default y
175//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 175//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
176//config: help 176//config: help
177//config: Enable case ... esac statement. +400 bytes. 177//config: Enable case ... esac statement. +400 bytes.
178//config: 178//config:
179//config:config HUSH_FUNCTIONS 179//config:config HUSH_FUNCTIONS
180//config: bool "Support funcname() { commands; } syntax" 180//config: bool "Support funcname() { commands; } syntax"
181//config: default y 181//config: default y
182//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 182//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
183//config: help 183//config: help
184//config: Enable support for shell functions. +800 bytes. 184//config: Enable support for shell functions. +800 bytes.
185//config: 185//config:
186//config:config HUSH_LOCAL 186//config:config HUSH_LOCAL
187//config: bool "local builtin" 187//config: bool "local builtin"
188//config: default y 188//config: default y
189//config: depends on HUSH_FUNCTIONS 189//config: depends on HUSH_FUNCTIONS
190//config: help 190//config: help
191//config: Enable support for local variables in functions. 191//config: Enable support for local variables in functions.
192//config: 192//config:
193//config:config HUSH_RANDOM_SUPPORT 193//config:config HUSH_RANDOM_SUPPORT
194//config: bool "Pseudorandom generator and $RANDOM variable" 194//config: bool "Pseudorandom generator and $RANDOM variable"
195//config: default y 195//config: default y
196//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 196//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
197//config: help 197//config: help
198//config: Enable pseudorandom generator and dynamic variable "$RANDOM". 198//config: Enable pseudorandom generator and dynamic variable "$RANDOM".
199//config: Each read of "$RANDOM" will generate a new pseudorandom value. 199//config: Each read of "$RANDOM" will generate a new pseudorandom value.
200//config: 200//config:
201//config:config HUSH_MODE_X 201//config:config HUSH_MODE_X
202//config: bool "Support 'hush -x' option and 'set -x' command" 202//config: bool "Support 'hush -x' option and 'set -x' command"
203//config: default y 203//config: default y
204//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 204//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
205//config: help 205//config: help
206//config: This instructs hush to print commands before execution. 206//config: This instructs hush to print commands before execution.
207//config: Adds ~300 bytes. 207//config: Adds ~300 bytes.
208//config: 208//config:
209//config:config HUSH_ECHO 209//config:config HUSH_ECHO
210//config: bool "echo builtin" 210//config: bool "echo builtin"
@@ -236,14 +236,14 @@
236//config: default y 236//config: default y
237//config: depends on HUSH_EXPORT 237//config: depends on HUSH_EXPORT
238//config: help 238//config: help
239//config: export -n unexports variables. It is a bash extension. 239//config: export -n unexports variables. It is a bash extension.
240//config: 240//config:
241//config:config HUSH_READONLY 241//config:config HUSH_READONLY
242//config: bool "readonly builtin" 242//config: bool "readonly builtin"
243//config: default y 243//config: default y
244//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 244//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
245//config: help 245//config: help
246//config: Enable support for read-only variables. 246//config: Enable support for read-only variables.
247//config: 247//config:
248//config:config HUSH_KILL 248//config:config HUSH_KILL
249//config: bool "kill builtin (supports kill %jobspec)" 249//config: bool "kill builtin (supports kill %jobspec)"
@@ -1068,7 +1068,7 @@ static const struct built_in_command bltins1[] = {
1068 BLTIN("export" , builtin_export , "Set environment variables"), 1068 BLTIN("export" , builtin_export , "Set environment variables"),
1069#endif 1069#endif
1070#if ENABLE_HUSH_JOB 1070#if ENABLE_HUSH_JOB
1071 BLTIN("fg" , builtin_fg_bg , "Bring job into foreground"), 1071 BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"),
1072#endif 1072#endif
1073#if ENABLE_HUSH_HELP 1073#if ENABLE_HUSH_HELP
1074 BLTIN("help" , builtin_help , NULL), 1074 BLTIN("help" , builtin_help , NULL),
@@ -1121,7 +1121,7 @@ static const struct built_in_command bltins1[] = {
1121 BLTIN("unset" , builtin_unset , "Unset variables"), 1121 BLTIN("unset" , builtin_unset , "Unset variables"),
1122#endif 1122#endif
1123#if ENABLE_HUSH_WAIT 1123#if ENABLE_HUSH_WAIT
1124 BLTIN("wait" , builtin_wait , "Wait for process"), 1124 BLTIN("wait" , builtin_wait , "Wait for process to finish"),
1125#endif 1125#endif
1126}; 1126};
1127/* These builtins won't be used if we are on NOMMU and need to re-exec 1127/* These builtins won't be used if we are on NOMMU and need to re-exec
@@ -2662,9 +2662,8 @@ static void o_delchr(o_string *o)
2662static void o_addblock(o_string *o, const char *str, int len) 2662static void o_addblock(o_string *o, const char *str, int len)
2663{ 2663{
2664 o_grow_by(o, len); 2664 o_grow_by(o, len);
2665 memcpy(&o->data[o->length], str, len); 2665 ((char*)mempcpy(&o->data[o->length], str, len))[0] = '\0';
2666 o->length += len; 2666 o->length += len;
2667 o->data[o->length] = '\0';
2668} 2667}
2669 2668
2670static void o_addstr(o_string *o, const char *str) 2669static void o_addstr(o_string *o, const char *str)
@@ -5519,17 +5518,15 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
5519 break; 5518 break;
5520 5519
5521 result = xrealloc(result, res_len + (s - val) + repl_len + 1); 5520 result = xrealloc(result, res_len + (s - val) + repl_len + 1);
5522 memcpy(result + res_len, val, s - val); 5521 strcpy(mempcpy(result + res_len, val, s - val), repl);
5523 res_len += s - val; 5522 res_len += (s - val) + repl_len;
5524 strcpy(result + res_len, repl);
5525 res_len += repl_len;
5526 debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result); 5523 debug_printf_varexp("val:'%s' s:'%s' result:'%s'\n", val, s, result);
5527 5524
5528 val = s + size; 5525 val = s + size;
5529 if (exp_op == '/') 5526 if (exp_op == '/')
5530 break; 5527 break;
5531 } 5528 }
5532 if (val[0] && result) { 5529 if (*val && result) {
5533 result = xrealloc(result, res_len + strlen(val) + 1); 5530 result = xrealloc(result, res_len + strlen(val) + 1);
5534 strcpy(result + res_len, val); 5531 strcpy(result + res_len, val);
5535 debug_printf_varexp("val:'%s' result:'%s'\n", val, result); 5532 debug_printf_varexp("val:'%s' result:'%s'\n", val, result);
diff --git a/shell/hush_test/hush-read/read_t0.right b/shell/hush_test/hush-read/read_t0.right
new file mode 100644
index 000000000..f02105961
--- /dev/null
+++ b/shell/hush_test/hush-read/read_t0.right
@@ -0,0 +1,3 @@
1><[0]
2><[0]
3><[1]
diff --git a/shell/hush_test/hush-read/read_t0.tests b/shell/hush_test/hush-read/read_t0.tests
new file mode 100755
index 000000000..6b7bc217b
--- /dev/null
+++ b/shell/hush_test/hush-read/read_t0.tests
@@ -0,0 +1,14 @@
1# ><[0]
2echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
3
4# This would not be deterministic: returns 0 "data exists" if EOF is seen
5# (true terminated) - because EOF is considered to be data (read will not block),
6# else returns 1 "no data".
7## ><[????]
8#true | { read -t 0 reply; echo ">$reply<[$?]"; }
9
10# ><[0]
11true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
12
13# ><[1]
14sleep 0.2 | { read -p IGNORED_PROMPT -t 0 reply; echo ">$reply<[$?]"; }
diff --git a/shell/shell_common.c b/shell/shell_common.c
index 154b860f8..750adc5d8 100644
--- a/shell/shell_common.c
+++ b/shell/shell_common.c
@@ -61,9 +61,10 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
61 const char *opt_u 61 const char *opt_u
62) 62)
63{ 63{
64 struct pollfd pfd[1];
65#define fd (pfd[0].fd) /* -u FD */
64 unsigned err; 66 unsigned err;
65 unsigned end_ms; /* -t TIMEOUT */ 67 unsigned end_ms; /* -t TIMEOUT */
66 int fd; /* -u FD */
67 int nchars; /* -n NUM */ 68 int nchars; /* -n NUM */
68 char **pp; 69 char **pp;
69 char *buffer; 70 char *buffer;
@@ -92,38 +93,43 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
92 return "invalid count"; 93 return "invalid count";
93 /* note: "-n 0": off (bash 3.2 does this too) */ 94 /* note: "-n 0": off (bash 3.2 does this too) */
94 } 95 }
96
95 end_ms = 0; 97 end_ms = 0;
96 if (opt_t) { 98 if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
97 end_ms = bb_strtou(opt_t, NULL, 10); 99 end_ms = bb_strtou(opt_t, NULL, 10);
98 if (errno || end_ms > UINT_MAX / 2048) 100 if (errno)
99 return "invalid timeout"; 101 return "invalid timeout";
102 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
103 end_ms = UINT_MAX / 2048;
100 end_ms *= 1000; 104 end_ms *= 1000;
101#if 0 /* even bash has no -t N.NNN support */ 105 }
102 ts.tv_sec = bb_strtou(opt_t, &p, 10); 106 if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
103 ts.tv_usec = 0; 107 /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */
104 /* EINVAL means number is ok, but not terminated by NUL */ 108 char *p;
105 if (*p == '.' && errno == EINVAL) { 109 /* Eat up to three fractional digits */
106 char *p2; 110 int frac_digits = 3 + 1;
107 if (*++p) { 111
108 int scale; 112 end_ms = bb_strtou(opt_t, &p, 10);
109 ts.tv_usec = bb_strtou(p, &p2, 10); 113 if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
110 if (errno) 114 end_ms = UINT_MAX / 2048;
111 return "invalid timeout"; 115
112 scale = p2 - p; 116 if (errno) {
113 /* normalize to usec */ 117 /* EINVAL = number is ok, but not NUL terminated */
114 if (scale > 6) 118 if (errno != EINVAL || *p != '.')
119 return "invalid timeout";
120 /* Do not check the rest: bash allows "0.123456xyz" */
121 while (*++p && --frac_digits) {
122 end_ms *= 10;
123 end_ms += (*p - '0');
124 if ((unsigned char)(*p - '0') > 9)
115 return "invalid timeout"; 125 return "invalid timeout";
116 while (scale++ < 6)
117 ts.tv_usec *= 10;
118 } 126 }
119 } else if (ts.tv_sec < 0 || errno) {
120 return "invalid timeout";
121 } 127 }
122 if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */ 128 while (--frac_digits > 0) {
123 return "invalid timeout"; 129 end_ms *= 10;
124 } 130 }
125#endif /* if 0 */
126 } 131 }
132
127 fd = STDIN_FILENO; 133 fd = STDIN_FILENO;
128 if (opt_u) { 134 if (opt_u) {
129 fd = bb_strtou(opt_u, NULL, 10); 135 fd = bb_strtou(opt_u, NULL, 10);
@@ -131,6 +137,19 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
131 return "invalid file descriptor"; 137 return "invalid file descriptor";
132 } 138 }
133 139
140 if (opt_t && end_ms == 0) {
141 /* "If timeout is 0, read returns immediately, without trying
142 * to read any data. The exit status is 0 if input is available
143 * on the specified file descriptor, non-zero otherwise."
144 * bash seems to ignore -p PROMPT for this use case.
145 */
146 int r;
147 pfd[0].events = POLLIN;
148 r = poll(pfd, 1, /*timeout:*/ 0);
149 /* Return 0 only if poll returns 1 ("one fd ready"), else return 1: */
150 return (const char *)(uintptr_t)(r <= 0);
151 }
152
134 if (opt_p && isatty(fd)) { 153 if (opt_p && isatty(fd)) {
135 fputs(opt_p, stderr); 154 fputs(opt_p, stderr);
136 fflush_all(); 155 fflush_all();
@@ -165,21 +184,24 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
165 retval = (const char *)(uintptr_t)0; 184 retval = (const char *)(uintptr_t)0;
166 startword = 1; 185 startword = 1;
167 backslash = 0; 186 backslash = 0;
168 if (end_ms) /* NB: end_ms stays nonzero: */ 187 if (opt_t)
169 end_ms = ((unsigned)monotonic_ms() + end_ms) | 1; 188 end_ms += (unsigned)monotonic_ms();
170 buffer = NULL; 189 buffer = NULL;
171 bufpos = 0; 190 bufpos = 0;
172 do { 191 do {
173 char c; 192 char c;
174 struct pollfd pfd[1];
175 int timeout; 193 int timeout;
176 194
177 if ((bufpos & 0xff) == 0) 195 if ((bufpos & 0xff) == 0)
178 buffer = xrealloc(buffer, bufpos + 0x101); 196 buffer = xrealloc(buffer, bufpos + 0x101);
179 197
180 timeout = -1; 198 timeout = -1;
181 if (end_ms) { 199 if (opt_t) {
182 timeout = end_ms - (unsigned)monotonic_ms(); 200 timeout = end_ms - (unsigned)monotonic_ms();
201 /* ^^^^^^^^^^^^^ all values are unsigned,
202 * wrapping math is used here, good even if
203 * 32-bit unix time wrapped (year 2038+).
204 */
183 if (timeout <= 0) { /* already late? */ 205 if (timeout <= 0) { /* already late? */
184 retval = (const char *)(uintptr_t)1; 206 retval = (const char *)(uintptr_t)1;
185 goto ret; 207 goto ret;
@@ -192,9 +214,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
192 */ 214 */
193 errno = 0; 215 errno = 0;
194#if !ENABLE_PLATFORM_MINGW32 216#if !ENABLE_PLATFORM_MINGW32
195 pfd[0].fd = fd;
196 pfd[0].events = POLLIN; 217 pfd[0].events = POLLIN;
197 if (poll(pfd, 1, timeout) != 1) { 218 if (poll(pfd, 1, timeout) <= 0) {
198 /* timed out, or EINTR */ 219 /* timed out, or EINTR */
199 err = errno; 220 err = errno;
200 retval = (const char *)(uintptr_t)1; 221 retval = (const char *)(uintptr_t)1;
@@ -278,6 +299,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
278 299
279 errno = err; 300 errno = err;
280 return retval; 301 return retval;
302#undef fd
281} 303}
282 304
283/* ulimit builtin */ 305/* ulimit builtin */