aboutsummaryrefslogtreecommitdiff
path: root/shell
diff options
context:
space:
mode:
authorRon Yorston <rmy@pobox.com>2018-02-13 09:44:44 +0000
committerRon Yorston <rmy@pobox.com>2018-02-13 09:44:44 +0000
commitdc19a361bd6c6df30338371532691bbc7f7126bb (patch)
tree1fb2cd646d54b5f8e425c4f11f3e09fc21d1966b /shell
parent096aee2bb468d1ab044de36e176ed1f6c7e3674d (diff)
parent3459024bf404af814cacfe90a0deb719e282ae62 (diff)
downloadbusybox-w32-dc19a361bd6c6df30338371532691bbc7f7126bb.tar.gz
busybox-w32-dc19a361bd6c6df30338371532691bbc7f7126bb.tar.bz2
busybox-w32-dc19a361bd6c6df30338371532691bbc7f7126bb.zip
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ash.c330
-rw-r--r--shell/ash_LINENO.patch498
-rw-r--r--shell/ash_test/ash-arith/arith-postinc.right5
-rwxr-xr-xshell/ash_test/ash-arith/arith-postinc.tests5
-rw-r--r--shell/ash_test/ash-arith/arith.right4
-rwxr-xr-xshell/ash_test/ash-arith/arith2.sub12
-rw-r--r--shell/ash_test/ash-misc/control_char1.right3
-rwxr-xr-xshell/ash_test/ash-misc/control_char1.tests3
-rw-r--r--shell/ash_test/ash-misc/control_char2.right2
-rwxr-xr-xshell/ash_test/ash-misc/control_char2.tests3
-rw-r--r--shell/ash_test/ash-misc/for_with_bslashes.right1
-rwxr-xr-xshell/ash_test/ash-misc/for_with_bslashes.tests8
-rw-r--r--shell/ash_test/ash-psubst/emptytick.right4
-rw-r--r--shell/ash_test/ash-quoting/mode_x.right4
-rw-r--r--shell/ash_test/ash-vars/param_expand_bash_substring.right1
-rwxr-xr-xshell/ash_test/ash-vars/param_expand_bash_substring.tests2
-rw-r--r--shell/ash_test/ash-vars/var_bash_repl_empty_pattern.right2
-rwxr-xr-xshell/ash_test/ash-vars/var_bash_repl_empty_pattern.tests3
-rw-r--r--shell/ash_test/ash-vars/var_bash_repl_empty_var.right2
-rwxr-xr-xshell/ash_test/ash-vars/var_bash_repl_empty_var.tests3
-rw-r--r--shell/hush.c385
-rw-r--r--shell/hush_test/hush-arith/arith-postinc.right5
-rwxr-xr-xshell/hush_test/hush-arith/arith-postinc.tests5
-rw-r--r--shell/hush_test/hush-arith/arith.right4
-rwxr-xr-xshell/hush_test/hush-arith/arith2.sub12
-rw-r--r--shell/hush_test/hush-misc/command2.right2
-rwxr-xr-xshell/hush_test/hush-misc/command2.tests6
-rw-r--r--shell/hush_test/hush-misc/control_char1.right3
-rwxr-xr-xshell/hush_test/hush-misc/control_char1.tests3
-rw-r--r--shell/hush_test/hush-misc/control_char2.right2
-rwxr-xr-xshell/hush_test/hush-misc/control_char2.tests3
-rw-r--r--shell/hush_test/hush-misc/for_with_bslashes.right1
-rwxr-xr-xshell/hush_test/hush-misc/for_with_bslashes.tests8
-rw-r--r--shell/hush_test/hush-vars/param_expand_bash_substring.right1
-rwxr-xr-xshell/hush_test/hush-vars/param_expand_bash_substring.tests2
-rw-r--r--shell/hush_test/hush-vars/var_LINENO1.right8
-rwxr-xr-xshell/hush_test/hush-vars/var_LINENO1.tests6
-rw-r--r--shell/hush_test/hush-vars/var_bash_repl_empty_pattern.right2
-rwxr-xr-xshell/hush_test/hush-vars/var_bash_repl_empty_pattern.tests3
-rw-r--r--shell/hush_test/hush-vars/var_bash_repl_empty_var.right2
-rwxr-xr-xshell/hush_test/hush-vars/var_bash_repl_empty_var.tests3
-rw-r--r--shell/math.c19
42 files changed, 696 insertions, 684 deletions
diff --git a/shell/ash.c b/shell/ash.c
index 81845dc60..36ddda1bc 100644
--- a/shell/ash.c
+++ b/shell/ash.c
@@ -66,6 +66,22 @@
66//config: default y 66//config: default y
67//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH 67//config: depends on ASH || SH_IS_ASH || BASH_IS_ASH
68//config: 68//config:
69//config:config ASH_BASH_SOURCE_CURDIR
70//config: bool "'source' and '.' builtins search current directory after $PATH"
71//config: default n # do not encourage non-standard behavior
72//config: depends on ASH_BASH_COMPAT
73//config: help
74//config: This is not compliant with standards. Avoid if possible.
75//config:
76//config:config ASH_BASH_NOT_FOUND_HOOK
77//config: bool "command_not_found_handle hook support"
78//config: default y
79//config: depends on ASH_BASH_COMPAT
80//config: help
81//config: Enable support for the 'command_not_found_handle' hook function,
82//config: from GNU bash, which allows for alternative command not found
83//config: handling.
84//config:
69//config:config ASH_JOB_CONTROL 85//config:config ASH_JOB_CONTROL
70//config: bool "Job control" 86//config: bool "Job control"
71//config: default y 87//config: default y
@@ -278,6 +294,19 @@ typedef long arith_t;
278# error "Do not even bother, ash will not run on NOMMU machine" 294# error "Do not even bother, ash will not run on NOMMU machine"
279#endif 295#endif
280 296
297/* We use a trick to have more optimized code (fewer pointer reloads):
298 * ash.c: extern struct globals *const ash_ptr_to_globals;
299 * ash_ptr_hack.c: struct globals *ash_ptr_to_globals;
300 * This way, compiler in ash.c knows the pointer can not change.
301 *
302 * However, this may break on weird arches or toolchains. In this case,
303 * set "-DBB_GLOBAL_CONST=''" in CONFIG_EXTRA_CFLAGS to disable
304 * this optimization.
305 */
306#ifndef BB_GLOBAL_CONST
307# define BB_GLOBAL_CONST const
308#endif
309
281#if ENABLE_PLATFORM_MINGW32 310#if ENABLE_PLATFORM_MINGW32
282union node; 311union node;
283struct strlist; 312struct strlist;
@@ -393,6 +422,8 @@ struct globals_misc {
393 /* shell level: 0 for the main shell, 1 for its children, and so on */ 422 /* shell level: 0 for the main shell, 1 for its children, and so on */
394 int shlvl; 423 int shlvl;
395#define rootshell (!shlvl) 424#define rootshell (!shlvl)
425 int errlinno;
426
396 char *minusc; /* argument to -c option */ 427 char *minusc; /* argument to -c option */
397 428
398 char *curdir; // = nullstr; /* current working directory */ 429 char *curdir; // = nullstr; /* current working directory */
@@ -469,13 +500,14 @@ struct globals_misc {
469#endif 500#endif
470 pid_t backgndpid; /* pid of last background process */ 501 pid_t backgndpid; /* pid of last background process */
471}; 502};
472extern struct globals_misc *const ash_ptr_to_globals_misc; 503extern struct globals_misc *BB_GLOBAL_CONST ash_ptr_to_globals_misc;
473#define G_misc (*ash_ptr_to_globals_misc) 504#define G_misc (*ash_ptr_to_globals_misc)
474#define exitstatus (G_misc.exitstatus ) 505#define exitstatus (G_misc.exitstatus )
475#define back_exitstatus (G_misc.back_exitstatus ) 506#define back_exitstatus (G_misc.back_exitstatus )
476#define job_warning (G_misc.job_warning) 507#define job_warning (G_misc.job_warning)
477#define rootpid (G_misc.rootpid ) 508#define rootpid (G_misc.rootpid )
478#define shlvl (G_misc.shlvl ) 509#define shlvl (G_misc.shlvl )
510#define errlinno (G_misc.errlinno )
479#define minusc (G_misc.minusc ) 511#define minusc (G_misc.minusc )
480#define curdir (G_misc.curdir ) 512#define curdir (G_misc.curdir )
481#define physdir (G_misc.physdir ) 513#define physdir (G_misc.physdir )
@@ -810,6 +842,7 @@ union node;
810 842
811struct ncmd { 843struct ncmd {
812 smallint type; /* Nxxxx */ 844 smallint type; /* Nxxxx */
845 int linno;
813 union node *assign; 846 union node *assign;
814 union node *args; 847 union node *args;
815 union node *redirect; 848 union node *redirect;
@@ -823,6 +856,7 @@ struct npipe {
823 856
824struct nredir { 857struct nredir {
825 smallint type; 858 smallint type;
859 int linno;
826 union node *n; 860 union node *n;
827 union node *redirect; 861 union node *redirect;
828}; 862};
@@ -842,6 +876,7 @@ struct nif {
842 876
843struct nfor { 877struct nfor {
844 smallint type; 878 smallint type;
879 int linno;
845 union node *args; 880 union node *args;
846 union node *body; 881 union node *body;
847 char *var; 882 char *var;
@@ -849,6 +884,7 @@ struct nfor {
849 884
850struct ncase { 885struct ncase {
851 smallint type; 886 smallint type;
887 int linno;
852 union node *expr; 888 union node *expr;
853 union node *cases; 889 union node *cases;
854}; 890};
@@ -860,6 +896,13 @@ struct nclist {
860 union node *body; 896 union node *body;
861}; 897};
862 898
899struct ndefun {
900 smallint type;
901 int linno;
902 char *text;
903 union node *body;
904};
905
863struct narg { 906struct narg {
864 smallint type; 907 smallint type;
865 union node *next; 908 union node *next;
@@ -911,6 +954,7 @@ union node {
911 struct nfor nfor; 954 struct nfor nfor;
912 struct ncase ncase; 955 struct ncase ncase;
913 struct nclist nclist; 956 struct nclist nclist;
957 struct ndefun ndefun;
914 struct narg narg; 958 struct narg narg;
915 struct nfile nfile; 959 struct nfile nfile;
916 struct ndup ndup; 960 struct ndup ndup;
@@ -1340,7 +1384,6 @@ struct parsefile {
1340 1384
1341static struct parsefile basepf; /* top level input file */ 1385static struct parsefile basepf; /* top level input file */
1342static struct parsefile *g_parsefile = &basepf; /* current input file */ 1386static struct parsefile *g_parsefile = &basepf; /* current input file */
1343static int startlinno; /* line # where last token started */
1344static char *commandname; /* currently executing command */ 1387static char *commandname; /* currently executing command */
1345 1388
1346 1389
@@ -1354,7 +1397,7 @@ ash_vmsg(const char *msg, va_list ap)
1354 if (strcmp(arg0, commandname)) 1397 if (strcmp(arg0, commandname))
1355 fprintf(stderr, "%s: ", commandname); 1398 fprintf(stderr, "%s: ", commandname);
1356 if (!iflag || g_parsefile->pf_fd > 0) 1399 if (!iflag || g_parsefile->pf_fd > 0)
1357 fprintf(stderr, "line %d: ", startlinno); 1400 fprintf(stderr, "line %d: ", errlinno);
1358 } 1401 }
1359 vfprintf(stderr, msg, ap); 1402 vfprintf(stderr, msg, ap);
1360 newline_and_flush(stderr); 1403 newline_and_flush(stderr);
@@ -1407,6 +1450,7 @@ static void raise_error_syntax(const char *) NORETURN;
1407static void 1450static void
1408raise_error_syntax(const char *msg) 1451raise_error_syntax(const char *msg)
1409{ 1452{
1453 errlinno = g_parsefile->linno;
1410 ash_msg_and_raise_error("syntax error: %s", msg); 1454 ash_msg_and_raise_error("syntax error: %s", msg);
1411 /* NOTREACHED */ 1455 /* NOTREACHED */
1412} 1456}
@@ -1529,7 +1573,7 @@ struct globals_memstack {
1529 size_t g_stacknleft; // = MINSIZE; 1573 size_t g_stacknleft; // = MINSIZE;
1530 struct stack_block stackbase; 1574 struct stack_block stackbase;
1531}; 1575};
1532extern struct globals_memstack *const ash_ptr_to_globals_memstack; 1576extern struct globals_memstack *BB_GLOBAL_CONST ash_ptr_to_globals_memstack;
1533#define G_memstack (*ash_ptr_to_globals_memstack) 1577#define G_memstack (*ash_ptr_to_globals_memstack)
1534#define g_stackp (G_memstack.g_stackp ) 1578#define g_stackp (G_memstack.g_stackp )
1535#define g_stacknxt (G_memstack.g_stacknxt ) 1579#define g_stacknxt (G_memstack.g_stacknxt )
@@ -1619,7 +1663,7 @@ sstrdup(const char *p)
1619 return memcpy(stalloc(len), p, len); 1663 return memcpy(stalloc(len), p, len);
1620} 1664}
1621 1665
1622static inline void 1666static ALWAYS_INLINE void
1623grabstackblock(size_t len) 1667grabstackblock(size_t len)
1624{ 1668{
1625 stalloc(len); 1669 stalloc(len);
@@ -2094,6 +2138,7 @@ static const struct {
2094#if ENABLE_ASH_GETOPTS 2138#if ENABLE_ASH_GETOPTS
2095 { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset }, 2139 { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset },
2096#endif 2140#endif
2141 { VSTRFIXED|VTEXTFIXED , NULL /* inited to linenovar */, NULL },
2097#if ENABLE_ASH_RANDOM_SUPPORT 2142#if ENABLE_ASH_RANDOM_SUPPORT
2098 { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random }, 2143 { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
2099#endif 2144#endif
@@ -2114,25 +2159,18 @@ struct globals_var {
2114 int preverrout_fd; /* stderr fd: usually 2, unless redirect moved it */ 2159 int preverrout_fd; /* stderr fd: usually 2, unless redirect moved it */
2115 struct var *vartab[VTABSIZE]; 2160 struct var *vartab[VTABSIZE];
2116 struct var varinit[ARRAY_SIZE(varinit_data)]; 2161 struct var varinit[ARRAY_SIZE(varinit_data)];
2162 int lineno;
2163 char linenovar[sizeof("LINENO=") + sizeof(int)*3];
2117}; 2164};
2118extern struct globals_var *const ash_ptr_to_globals_var; 2165extern struct globals_var *BB_GLOBAL_CONST ash_ptr_to_globals_var;
2119#define G_var (*ash_ptr_to_globals_var) 2166#define G_var (*ash_ptr_to_globals_var)
2120#define shellparam (G_var.shellparam ) 2167#define shellparam (G_var.shellparam )
2121//#define redirlist (G_var.redirlist ) 2168//#define redirlist (G_var.redirlist )
2122#define preverrout_fd (G_var.preverrout_fd) 2169#define preverrout_fd (G_var.preverrout_fd)
2123#define vartab (G_var.vartab ) 2170#define vartab (G_var.vartab )
2124#define varinit (G_var.varinit ) 2171#define varinit (G_var.varinit )
2125#define INIT_G_var() do { \ 2172#define lineno (G_var.lineno )
2126 unsigned i; \ 2173#define linenovar (G_var.linenovar )
2127 (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
2128 barrier(); \
2129 for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
2130 varinit[i].flags = varinit_data[i].flags; \
2131 varinit[i].var_text = varinit_data[i].var_text; \
2132 varinit[i].var_func = varinit_data[i].var_func; \
2133 } \
2134} while (0)
2135
2136#define vifs varinit[0] 2174#define vifs varinit[0]
2137#if ENABLE_ASH_MAIL 2175#if ENABLE_ASH_MAIL
2138# define vmail (&vifs)[1] 2176# define vmail (&vifs)[1]
@@ -2146,14 +2184,28 @@ extern struct globals_var *const ash_ptr_to_globals_var;
2146#define vps4 (&vps2)[1] 2184#define vps4 (&vps2)[1]
2147#if ENABLE_ASH_GETOPTS 2185#if ENABLE_ASH_GETOPTS
2148# define voptind (&vps4)[1] 2186# define voptind (&vps4)[1]
2187# define vlineno (&voptind)[1]
2149# if ENABLE_ASH_RANDOM_SUPPORT 2188# if ENABLE_ASH_RANDOM_SUPPORT
2150# define vrandom (&voptind)[1] 2189# define vrandom (&vlineno)[1]
2151# endif 2190# endif
2152#else 2191#else
2192# define vlineno (&vps4)[1]
2153# if ENABLE_ASH_RANDOM_SUPPORT 2193# if ENABLE_ASH_RANDOM_SUPPORT
2154# define vrandom (&vps4)[1] 2194# define vrandom (&vlineno)[1]
2155# endif 2195# endif
2156#endif 2196#endif
2197#define INIT_G_var() do { \
2198 unsigned i; \
2199 (*(struct globals_var**)&ash_ptr_to_globals_var) = xzalloc(sizeof(G_var)); \
2200 barrier(); \
2201 for (i = 0; i < ARRAY_SIZE(varinit_data); i++) { \
2202 varinit[i].flags = varinit_data[i].flags; \
2203 varinit[i].var_text = varinit_data[i].var_text; \
2204 varinit[i].var_func = varinit_data[i].var_func; \
2205 } \
2206 strcpy(linenovar, "LINENO="); \
2207 vlineno.var_text = linenovar; \
2208} while (0)
2157 2209
2158/* 2210/*
2159 * The following macros access the values of the above variables. 2211 * The following macros access the values of the above variables.
@@ -2289,8 +2341,12 @@ lookupvar(const char *name)
2289 if (v->flags & VDYNAMIC) 2341 if (v->flags & VDYNAMIC)
2290 v->var_func(NULL); 2342 v->var_func(NULL);
2291#endif 2343#endif
2292 if (!(v->flags & VUNSET)) 2344 if (!(v->flags & VUNSET)) {
2345 if (v == &vlineno && v->var_text == linenovar) {
2346 fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
2347 }
2293 return var_end(v->var_text); 2348 return var_end(v->var_text);
2349 }
2294 } 2350 }
2295 return NULL; 2351 return NULL;
2296} 2352}
@@ -5125,7 +5181,7 @@ cmdtxt(union node *n)
5125 p = "; done"; 5181 p = "; done";
5126 goto dodo; 5182 goto dodo;
5127 case NDEFUN: 5183 case NDEFUN:
5128 cmdputs(n->narg.text); 5184 cmdputs(n->ndefun.text);
5129 p = "() { ... }"; 5185 p = "() { ... }";
5130 goto dotail2; 5186 goto dotail2;
5131 case NCMD: 5187 case NCMD:
@@ -6151,6 +6207,26 @@ ash_arith(const char *s)
6151 return result; 6207 return result;
6152} 6208}
6153#endif 6209#endif
6210#if BASH_SUBSTR
6211# if ENABLE_FEATURE_SH_MATH
6212static int substr_atoi(const char *s)
6213{
6214 arith_t t = ash_arith(s);
6215 if (sizeof(t) > sizeof(int)) {
6216 /* clamp very large or very large negative nums for ${v:N:M}:
6217 * else "${v:0:0x100000001}" would work as "${v:0:1}"
6218 */
6219 if (t > INT_MAX)
6220 t = INT_MAX;
6221 if (t < INT_MIN)
6222 t = INT_MIN;
6223 }
6224 return t;
6225}
6226# else
6227# define substr_atoi(s) number(s)
6228# endif
6229#endif
6154 6230
6155/* 6231/*
6156 * expandarg flags 6232 * expandarg flags
@@ -6182,7 +6258,6 @@ ash_arith(const char *s)
6182#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ 6258#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */
6183#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ 6259#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */
6184#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ 6260#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */
6185#define RMESCAPE_SLASH 0x20 /* Stop globbing after slash */
6186 6261
6187/* Add CTLESC when necessary. */ 6262/* Add CTLESC when necessary. */
6188#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) 6263#define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT)
@@ -6363,8 +6438,12 @@ esclen(const char *start, const char *p)
6363/* 6438/*
6364 * Remove any CTLESC characters from a string. 6439 * Remove any CTLESC characters from a string.
6365 */ 6440 */
6441#if !BASH_PATTERN_SUBST
6442#define rmescapes(str, flag, slash_position) \
6443 rmescapes(str, flag)
6444#endif
6366static char * 6445static char *
6367rmescapes(char *str, int flag) 6446rmescapes(char *str, int flag, int *slash_position)
6368{ 6447{
6369 static const char qchars[] ALIGN1 = { 6448 static const char qchars[] ALIGN1 = {
6370 IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' }; 6449 IF_BASH_PATTERN_SUBST('/',) CTLESC, CTLQUOTEMARK, '\0' };
@@ -6373,9 +6452,8 @@ rmescapes(char *str, int flag)
6373 unsigned inquotes; 6452 unsigned inquotes;
6374 unsigned protect_against_glob; 6453 unsigned protect_against_glob;
6375 unsigned globbing; 6454 unsigned globbing;
6376 IF_BASH_PATTERN_SUBST(unsigned slash = flag & RMESCAPE_SLASH;)
6377 6455
6378 p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash)); 6456 p = strpbrk(str, qchars IF_BASH_PATTERN_SUBST(+ !slash_position));
6379 if (!p) 6457 if (!p)
6380 return str; 6458 return str;
6381 6459
@@ -6455,10 +6533,11 @@ rmescapes(char *str, int flag)
6455 goto copy; 6533 goto copy;
6456 } 6534 }
6457#if BASH_PATTERN_SUBST 6535#if BASH_PATTERN_SUBST
6458 else if (*p == '/' && slash) { 6536 else if (slash_position && p == str + *slash_position) {
6459 /* stop handling globbing and mark location of slash */ 6537 /* stop handling globbing */
6460 globbing = slash = 0; 6538 globbing = 0;
6461 *p = CTLESC; 6539 *slash_position = q - r;
6540 slash_position = NULL;
6462 } 6541 }
6463#endif 6542#endif
6464 protect_against_glob = globbing; 6543 protect_against_glob = globbing;
@@ -6482,7 +6561,7 @@ rmescapes(char *str, int flag)
6482static char * 6561static char *
6483preglob(const char *pattern, int flag) 6562preglob(const char *pattern, int flag)
6484{ 6563{
6485 return rmescapes((char *)pattern, flag | RMESCAPE_GLOB); 6564 return rmescapes((char *)pattern, flag | RMESCAPE_GLOB, NULL);
6486} 6565}
6487 6566
6488/* 6567/*
@@ -6837,7 +6916,7 @@ expari(int flag)
6837 expdest = p; 6916 expdest = p;
6838 6917
6839 if (flag & QUOTES_ESC) 6918 if (flag & QUOTES_ESC)
6840 rmescapes(p + 1, 0); 6919 rmescapes(p + 1, 0, NULL);
6841 6920
6842 len = cvtnum(ash_arith(p + 1)); 6921 len = cvtnum(ash_arith(p + 1));
6843 6922
@@ -7125,20 +7204,58 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7125 char *rmesc, *rmescend; 7204 char *rmesc, *rmescend;
7126 char *str; 7205 char *str;
7127 int amount, resetloc; 7206 int amount, resetloc;
7207 int argstr_flags;
7128 IF_BASH_PATTERN_SUBST(int workloc;) 7208 IF_BASH_PATTERN_SUBST(int workloc;)
7129 IF_BASH_PATTERN_SUBST(char *repl = NULL;) 7209 IF_BASH_PATTERN_SUBST(int slash_pos;)
7210 IF_BASH_PATTERN_SUBST(char *repl;)
7130 int zero; 7211 int zero;
7131 char *(*scan)(char*, char*, char*, char*, int, int); 7212 char *(*scan)(char*, char*, char*, char*, int, int);
7132 7213
7133 //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)", 7214 //bb_error_msg("subevalvar(p:'%s',varname:'%s',strloc:%d,subtype:%d,startloc:%d,varflags:%x,quotes:%d)",
7134 // p, varname, strloc, subtype, startloc, varflags, quotes); 7215 // p, varname, strloc, subtype, startloc, varflags, quotes);
7135 7216
7136 argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? 7217#if BASH_PATTERN_SUBST
7137 (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE) : 0) 7218 /* For "${v/pattern/repl}", we must find the delimiter _before_
7138 ); 7219 * argstr() call expands possible variable references in pattern:
7220 * think about "v=a; a=a/; echo ${v/$a/r}" case.
7221 */
7222 repl = NULL;
7223 if (subtype == VSREPLACE || subtype == VSREPLACEALL) {
7224 /* Find '/' and replace with NUL */
7225 repl = p;
7226 for (;;) {
7227 /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */
7228 if (*repl == '\0') {
7229 repl = NULL;
7230 break;
7231 }
7232 if (*repl == '/') {
7233 *repl = '\0';
7234 break;
7235 }
7236 if ((unsigned char)*repl == CTLESC && repl[1])
7237 repl++;
7238 repl++;
7239 }
7240 }
7241#endif
7242 argstr_flags = EXP_TILDE;
7243 if (subtype != VSASSIGN && subtype != VSQUESTION)
7244 argstr_flags |= (flag & (EXP_QUOTED | EXP_QPAT) ? EXP_QPAT : EXP_CASE);
7245 argstr(p, argstr_flags);
7246#if BASH_PATTERN_SUBST
7247 slash_pos = -1;
7248 if (repl) {
7249 slash_pos = expdest - ((char *)stackblock() + strloc);
7250 STPUTC('/', expdest);
7251 argstr(repl + 1, argstr_flags);
7252 *repl = '/';
7253 }
7254#endif
7139 STPUTC('\0', expdest); 7255 STPUTC('\0', expdest);
7140 argbackq = saveargbackq; 7256 argbackq = saveargbackq;
7141 startp = (char *)stackblock() + startloc; 7257 startp = (char *)stackblock() + startloc;
7258 //bb_error_msg("str1:'%s'", (char *)stackblock() + strloc);
7142 7259
7143 switch (subtype) { 7260 switch (subtype) {
7144 case VSASSIGN: 7261 case VSASSIGN:
@@ -7158,13 +7275,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7158 7275
7159 loc = str = stackblock() + strloc; 7276 loc = str = stackblock() + strloc;
7160 7277
7161# if !ENABLE_FEATURE_SH_MATH
7162# define ash_arith number
7163# endif
7164 /* Read POS in ${var:POS:LEN} */ 7278 /* Read POS in ${var:POS:LEN} */
7165 colon = strchr(loc, ':'); 7279 colon = strchr(loc, ':');
7166 if (colon) *colon = '\0'; 7280 if (colon) *colon = '\0';
7167 pos = ash_arith(loc); 7281 pos = substr_atoi(loc);
7168 if (colon) *colon = ':'; 7282 if (colon) *colon = ':';
7169 7283
7170 /* Read LEN in ${var:POS:LEN} */ 7284 /* Read LEN in ${var:POS:LEN} */
@@ -7172,7 +7286,6 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7172 /* *loc != '\0', guaranteed by parser */ 7286 /* *loc != '\0', guaranteed by parser */
7173 if (quotes) { 7287 if (quotes) {
7174 char *ptr; 7288 char *ptr;
7175
7176 /* Adjust the length by the number of escapes */ 7289 /* Adjust the length by the number of escapes */
7177 for (ptr = startp; ptr < (str - 1); ptr++) { 7290 for (ptr = startp; ptr < (str - 1); ptr++) {
7178 if ((unsigned char)*ptr == CTLESC) { 7291 if ((unsigned char)*ptr == CTLESC) {
@@ -7184,19 +7297,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7184 orig_len = len; 7297 orig_len = len;
7185 if (*loc++ == ':') { 7298 if (*loc++ == ':') {
7186 /* ${var::LEN} */ 7299 /* ${var::LEN} */
7187 len = ash_arith(loc); 7300 len = substr_atoi(loc);
7188 } else { 7301 } else {
7189 /* Skip POS in ${var:POS:LEN} */ 7302 /* Skip POS in ${var:POS:LEN} */
7190 len = orig_len; 7303 len = orig_len;
7191 while (*loc && *loc != ':') { 7304 while (*loc && *loc != ':')
7192 loc++; 7305 loc++;
7193 } 7306 if (*loc++ == ':')
7194 if (*loc++ == ':') { 7307 len = substr_atoi(loc);
7195 len = ash_arith(loc);
7196 }
7197 } 7308 }
7198# undef ash_arith
7199
7200 if (pos < 0) { 7309 if (pos < 0) {
7201 /* ${VAR:$((-n)):l} starts n chars from the end */ 7310 /* ${VAR:$((-n)):l} starts n chars from the end */
7202 pos = orig_len + pos; 7311 pos = orig_len + pos;
@@ -7236,6 +7345,8 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7236 resetloc = expdest - (char *)stackblock(); 7345 resetloc = expdest - (char *)stackblock();
7237 7346
7238#if BASH_PATTERN_SUBST 7347#if BASH_PATTERN_SUBST
7348 repl = NULL;
7349
7239 /* We'll comeback here if we grow the stack while handling 7350 /* We'll comeback here if we grow the stack while handling
7240 * a VSREPLACE or VSREPLACEALL, since our pointers into the 7351 * a VSREPLACE or VSREPLACEALL, since our pointers into the
7241 * stack will need rebasing, and we'll need to remove our work 7352 * stack will need rebasing, and we'll need to remove our work
@@ -7250,8 +7361,10 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7250 7361
7251 rmesc = startp; 7362 rmesc = startp;
7252 rmescend = (char *)stackblock() + strloc; 7363 rmescend = (char *)stackblock() + strloc;
7364 //bb_error_msg("str7:'%s'", rmescend);
7253 if (quotes) { 7365 if (quotes) {
7254 rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); 7366//TODO: how to handle slash_pos here if string changes (shortens?)
7367 rmesc = rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW, NULL);
7255 if (rmesc != startp) { 7368 if (rmesc != startp) {
7256 rmescend = expdest; 7369 rmescend = expdest;
7257 startp = (char *)stackblock() + startloc; 7370 startp = (char *)stackblock() + startloc;
@@ -7264,12 +7377,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7264 * The result is a_\_z_c (not a\_\_z_c)! 7377 * The result is a_\_z_c (not a\_\_z_c)!
7265 * 7378 *
7266 * The search pattern and replace string treat backslashes differently! 7379 * The search pattern and replace string treat backslashes differently!
7267 * RMESCAPE_SLASH causes preglob to work differently on the pattern 7380 * "&slash_pos" causes rmescapes() to work differently on the pattern
7268 * and string. It's only used on the first call. 7381 * and string. It's only used on the first call.
7269 */ 7382 */
7270 preglob(str, IF_BASH_PATTERN_SUBST( 7383 //bb_error_msg("str8:'%s' slash_pos:%d", str, slash_pos);
7271 (subtype == VSREPLACE || subtype == VSREPLACEALL) && !repl ? 7384 rmescapes(str, RMESCAPE_GLOB,
7272 RMESCAPE_SLASH : ) 0); 7385 repl ? NULL : (slash_pos < 0 ? NULL : &slash_pos)
7386 );
7273 7387
7274#if BASH_PATTERN_SUBST 7388#if BASH_PATTERN_SUBST
7275 workloc = expdest - (char *)stackblock(); 7389 workloc = expdest - (char *)stackblock();
@@ -7278,11 +7392,12 @@ subevalvar(char *p, char *varname, int strloc, int subtype,
7278 char *idx, *end; 7392 char *idx, *end;
7279 7393
7280 if (!repl) { 7394 if (!repl) {
7281 repl = strchr(str, CTLESC); 7395 //bb_error_msg("str9:'%s' slash_pos:%d", str, slash_pos);
7282 if (repl) 7396 repl = nullstr;
7397 if (slash_pos >= 0) {
7398 repl = str + slash_pos;
7283 *repl++ = '\0'; 7399 *repl++ = '\0';
7284 else 7400 }
7285 repl = nullstr;
7286 } 7401 }
7287 //bb_error_msg("str:'%s' repl:'%s'", str, repl); 7402 //bb_error_msg("str:'%s' repl:'%s'", str, repl);
7288 7403
@@ -7802,7 +7917,7 @@ expandmeta(struct strlist *str /*, int flag*/)
7802 INT_ON; 7917 INT_ON;
7803 nometa: 7918 nometa:
7804 *exparg.lastp = str; 7919 *exparg.lastp = str;
7805 rmescapes(str->text, 0); 7920 rmescapes(str->text, 0, NULL);
7806 exparg.lastp = &str->next; 7921 exparg.lastp = &str->next;
7807 break; 7922 break;
7808 default: /* GLOB_NOSPACE */ 7923 default: /* GLOB_NOSPACE */
@@ -8031,7 +8146,7 @@ expandmeta(struct strlist *str /*, int flag*/)
8031 */ 8146 */
8032 nometa: 8147 nometa:
8033 *exparg.lastp = str; 8148 *exparg.lastp = str;
8034 rmescapes(str->text, 0); 8149 rmescapes(str->text, 0, NULL);
8035 exparg.lastp = &str->next; 8150 exparg.lastp = &str->next;
8036 } else { 8151 } else {
8037 *exparg.lastp = NULL; 8152 *exparg.lastp = NULL;
@@ -8970,6 +9085,10 @@ calcsize(union node *n)
8970 IF_PLATFORM_MINGW32(nodeptrsize += 3); 9085 IF_PLATFORM_MINGW32(nodeptrsize += 3);
8971 break; 9086 break;
8972 case NDEFUN: 9087 case NDEFUN:
9088 calcsize(n->ndefun.body);
9089 funcstringsize += strlen(n->ndefun.text) + 1;
9090 IF_PLATFORM_MINGW32(nodeptrsize += 2);
9091 break;
8973 case NARG: 9092 case NARG:
8974 sizenodelist(n->narg.backquote); 9093 sizenodelist(n->narg.backquote);
8975 funcstringsize += strlen(n->narg.text) + 1; 9094 funcstringsize += strlen(n->narg.text) + 1;
@@ -9067,6 +9186,7 @@ copynode(union node *n)
9067 new->ncmd.redirect = copynode(n->ncmd.redirect); 9186 new->ncmd.redirect = copynode(n->ncmd.redirect);
9068 new->ncmd.args = copynode(n->ncmd.args); 9187 new->ncmd.args = copynode(n->ncmd.args);
9069 new->ncmd.assign = copynode(n->ncmd.assign); 9188 new->ncmd.assign = copynode(n->ncmd.assign);
9189 new->ncmd.linno = n->ncmd.linno;
9070 SAVE_PTR3(new->ncmd.redirect,new->ncmd.args, new->ncmd.assign); 9190 SAVE_PTR3(new->ncmd.redirect,new->ncmd.args, new->ncmd.assign);
9071 break; 9191 break;
9072 case NPIPE: 9192 case NPIPE:
@@ -9079,6 +9199,7 @@ copynode(union node *n)
9079 case NSUBSHELL: 9199 case NSUBSHELL:
9080 new->nredir.redirect = copynode(n->nredir.redirect); 9200 new->nredir.redirect = copynode(n->nredir.redirect);
9081 new->nredir.n = copynode(n->nredir.n); 9201 new->nredir.n = copynode(n->nredir.n);
9202 new->nredir.linno = n->nredir.linno;
9082 SAVE_PTR2(new->nredir.redirect,new->nredir.n); 9203 SAVE_PTR2(new->nredir.redirect,new->nredir.n);
9083 break; 9204 break;
9084 case NAND: 9205 case NAND:
@@ -9100,11 +9221,13 @@ copynode(union node *n)
9100 new->nfor.var = nodeckstrdup(n->nfor.var); 9221 new->nfor.var = nodeckstrdup(n->nfor.var);
9101 new->nfor.body = copynode(n->nfor.body); 9222 new->nfor.body = copynode(n->nfor.body);
9102 new->nfor.args = copynode(n->nfor.args); 9223 new->nfor.args = copynode(n->nfor.args);
9224 new->nfor.linno = n->nfor.linno;
9103 SAVE_PTR3(new->nfor.var,new->nfor.body,new->nfor.args); 9225 SAVE_PTR3(new->nfor.var,new->nfor.body,new->nfor.args);
9104 break; 9226 break;
9105 case NCASE: 9227 case NCASE:
9106 new->ncase.cases = copynode(n->ncase.cases); 9228 new->ncase.cases = copynode(n->ncase.cases);
9107 new->ncase.expr = copynode(n->ncase.expr); 9229 new->ncase.expr = copynode(n->ncase.expr);
9230 new->ncase.linno = n->ncase.linno;
9108 SAVE_PTR2(new->ncase.cases,new->ncase.expr); 9231 SAVE_PTR2(new->ncase.cases,new->ncase.expr);
9109 break; 9232 break;
9110 case NCLIST: 9233 case NCLIST:
@@ -9114,6 +9237,11 @@ copynode(union node *n)
9114 SAVE_PTR3(new->nclist.body,new->nclist.pattern,new->nclist.next); 9237 SAVE_PTR3(new->nclist.body,new->nclist.pattern,new->nclist.next);
9115 break; 9238 break;
9116 case NDEFUN: 9239 case NDEFUN:
9240 new->ndefun.body = copynode(n->ndefun.body);
9241 new->ndefun.text = nodeckstrdup(n->ndefun.text);
9242 new->ndefun.linno = n->ndefun.linno;
9243 SAVE_PTR2(new->ndefun.body,new->ndefun.text);
9244 break;
9117 case NARG: 9245 case NARG:
9118 new->narg.backquote = copynodelist(n->narg.backquote); 9246 new->narg.backquote = copynodelist(n->narg.backquote);
9119 new->narg.text = nodeckstrdup(n->narg.text); 9247 new->narg.text = nodeckstrdup(n->narg.text);
@@ -9190,7 +9318,7 @@ defun(union node *func)
9190 INT_OFF; 9318 INT_OFF;
9191 entry.cmdtype = CMDFUNCTION; 9319 entry.cmdtype = CMDFUNCTION;
9192 entry.u.func = copyfunc(func); 9320 entry.u.func = copyfunc(func);
9193 addcmdentry(func->narg.text, &entry); 9321 addcmdentry(func->ndefun.text, &entry);
9194 INT_ON; 9322 INT_ON;
9195} 9323}
9196 9324
@@ -9200,8 +9328,8 @@ defun(union node *func)
9200#define SKIPFUNC (1 << 2) 9328#define SKIPFUNC (1 << 2)
9201static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ 9329static smallint evalskip; /* set to SKIPxxx if we are skipping commands */
9202static int skipcount; /* number of levels to skip */ 9330static int skipcount; /* number of levels to skip */
9203static int funcnest; /* depth of function calls */
9204static int loopnest; /* current loop nesting level */ 9331static int loopnest; /* current loop nesting level */
9332static int funcline; /* starting line number of current function, or 0 if not in a function */
9205 9333
9206/* Forward decl way out to parsing code - dotrap needs it */ 9334/* Forward decl way out to parsing code - dotrap needs it */
9207static int evalstring(char *s, int flags); 9335static int evalstring(char *s, int flags);
@@ -9296,6 +9424,9 @@ evaltree(union node *n, int flags)
9296 status = !evaltree(n->nnot.com, EV_TESTED); 9424 status = !evaltree(n->nnot.com, EV_TESTED);
9297 goto setstatus; 9425 goto setstatus;
9298 case NREDIR: 9426 case NREDIR:
9427 errlinno = lineno = n->nredir.linno;
9428 if (funcline)
9429 lineno -= funcline - 1;
9299 expredir(n->nredir.redirect); 9430 expredir(n->nredir.redirect);
9300 pushredir(n->nredir.redirect); 9431 pushredir(n->nredir.redirect);
9301 status = redirectsafe(n->nredir.redirect, REDIR_PUSH); 9432 status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
@@ -9450,6 +9581,10 @@ evalfor(union node *n, int flags)
9450 struct stackmark smark; 9581 struct stackmark smark;
9451 int status = 0; 9582 int status = 0;
9452 9583
9584 errlinno = lineno = n->ncase.linno;
9585 if (funcline)
9586 lineno -= funcline - 1;
9587
9453 setstackmark(&smark); 9588 setstackmark(&smark);
9454 arglist.list = NULL; 9589 arglist.list = NULL;
9455 arglist.lastp = &arglist.list; 9590 arglist.lastp = &arglist.list;
@@ -9481,6 +9616,10 @@ evalcase(union node *n, int flags)
9481 struct stackmark smark; 9616 struct stackmark smark;
9482 int status = 0; 9617 int status = 0;
9483 9618
9619 errlinno = lineno = n->ncase.linno;
9620 if (funcline)
9621 lineno -= funcline - 1;
9622
9484 setstackmark(&smark); 9623 setstackmark(&smark);
9485 arglist.list = NULL; 9624 arglist.list = NULL;
9486 arglist.lastp = &arglist.list; 9625 arglist.lastp = &arglist.list;
@@ -9516,6 +9655,10 @@ evalsubshell(union node *n, int flags)
9516 int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ 9655 int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
9517 int status; 9656 int status;
9518 9657
9658 errlinno = lineno = n->nredir.linno;
9659 if (funcline)
9660 lineno -= funcline - 1;
9661
9519 expredir(n->nredir.redirect); 9662 expredir(n->nredir.redirect);
9520 if (!backgnd && (flags & EV_EXIT) && !may_have_traps) 9663 if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
9521 goto nofork; 9664 goto nofork;
@@ -9846,8 +9989,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
9846 struct jmploc *volatile savehandler; 9989 struct jmploc *volatile savehandler;
9847 struct jmploc jmploc; 9990 struct jmploc jmploc;
9848 int e; 9991 int e;
9992 int savefuncline;
9849 9993
9850 saveparam = shellparam; 9994 saveparam = shellparam;
9995 savefuncline = funcline;
9851 savehandler = exception_handler; 9996 savehandler = exception_handler;
9852 e = setjmp(jmploc.loc); 9997 e = setjmp(jmploc.loc);
9853 if (e) { 9998 if (e) {
@@ -9857,7 +10002,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
9857 exception_handler = &jmploc; 10002 exception_handler = &jmploc;
9858 shellparam.malloced = 0; 10003 shellparam.malloced = 0;
9859 func->count++; 10004 func->count++;
9860 funcnest++; 10005 funcline = func->n.ndefun.linno;
9861 INT_ON; 10006 INT_ON;
9862 shellparam.nparam = argc - 1; 10007 shellparam.nparam = argc - 1;
9863 shellparam.p = argv + 1; 10008 shellparam.p = argv + 1;
@@ -9866,11 +10011,11 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
9866 shellparam.optoff = -1; 10011 shellparam.optoff = -1;
9867#endif 10012#endif
9868 pushlocalvars(); 10013 pushlocalvars();
9869 evaltree(func->n.narg.next, flags & EV_TESTED); 10014 evaltree(func->n.ndefun.body, flags & EV_TESTED);
9870 poplocalvars(0); 10015 poplocalvars(0);
9871 funcdone: 10016 funcdone:
9872 INT_OFF; 10017 INT_OFF;
9873 funcnest--; 10018 funcline = savefuncline;
9874 freefunc(func); 10019 freefunc(func);
9875 freeparam(&shellparam); 10020 freeparam(&shellparam);
9876 shellparam = saveparam; 10021 shellparam = saveparam;
@@ -10235,6 +10380,10 @@ evalcommand(union node *cmd, int flags)
10235 char **nargv; 10380 char **nargv;
10236 smallint cmd_is_exec; 10381 smallint cmd_is_exec;
10237 10382
10383 errlinno = lineno = cmd->ncmd.linno;
10384 if (funcline)
10385 lineno -= funcline - 1;
10386
10238 /* First expand the arguments. */ 10387 /* First expand the arguments. */
10239 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); 10388 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
10240 setstackmark(&smark); 10389 setstackmark(&smark);
@@ -10280,7 +10429,7 @@ evalcommand(union node *cmd, int flags)
10280 *nargv = NULL; 10429 *nargv = NULL;
10281 10430
10282 lastarg = NULL; 10431 lastarg = NULL;
10283 if (iflag && funcnest == 0 && argc > 0) 10432 if (iflag && funcline == 0 && argc > 0)
10284 lastarg = nargv[-1]; 10433 lastarg = nargv[-1];
10285 10434
10286 expredir(cmd->ncmd.redirect); 10435 expredir(cmd->ncmd.redirect);
@@ -10410,7 +10559,9 @@ evalcommand(union node *cmd, int flags)
10410 switch (cmdentry.cmdtype) { 10559 switch (cmdentry.cmdtype) {
10411 default: { 10560 default: {
10412 10561
10413#if ENABLE_FEATURE_SH_NOFORK 10562#if ENABLE_FEATURE_SH_STANDALONE \
10563 && ENABLE_FEATURE_SH_NOFORK \
10564 && NUM_APPLETS > 1
10414/* (1) BUG: if variables are set, we need to fork, or save/restore them 10565/* (1) BUG: if variables are set, we need to fork, or save/restore them
10415 * around run_nofork_applet() call. 10566 * around run_nofork_applet() call.
10416 * (2) Should this check also be done in forkshell()? 10567 * (2) Should this check also be done in forkshell()?
@@ -11341,7 +11492,7 @@ shiftcmd(int argc UNUSED_PARAM, char **argv)
11341 if (argv[1]) 11492 if (argv[1])
11342 n = number(argv[1]); 11493 n = number(argv[1]);
11343 if (n > shellparam.nparam) 11494 if (n > shellparam.nparam)
11344 n = 0; /* bash compat, was = shellparam.nparam; */ 11495 return 1;
11345 INT_OFF; 11496 INT_OFF;
11346 shellparam.nparam -= n; 11497 shellparam.nparam -= n;
11347 for (ap1 = shellparam.p; --n >= 0; ap1++) { 11498 for (ap1 = shellparam.p; --n >= 0; ap1++) {
@@ -11811,7 +11962,7 @@ parsefname(void)
11811 if (quoteflag == 0) 11962 if (quoteflag == 0)
11812 n->type = NXHERE; 11963 n->type = NXHERE;
11813 TRACE(("Here document %d\n", n->type)); 11964 TRACE(("Here document %d\n", n->type));
11814 rmescapes(wordtext, 0); 11965 rmescapes(wordtext, 0, NULL);
11815 here->eofmark = wordtext; 11966 here->eofmark = wordtext;
11816 here->next = NULL; 11967 here->next = NULL;
11817 if (heredoclist == NULL) 11968 if (heredoclist == NULL)
@@ -11836,6 +11987,7 @@ simplecmd(void)
11836 union node *vars, **vpp; 11987 union node *vars, **vpp;
11837 union node **rpp, *redir; 11988 union node **rpp, *redir;
11838 int savecheckkwd; 11989 int savecheckkwd;
11990 int savelinno;
11839#if BASH_TEST2 11991#if BASH_TEST2
11840 smallint double_brackets_flag = 0; 11992 smallint double_brackets_flag = 0;
11841#endif 11993#endif
@@ -11849,6 +12001,7 @@ simplecmd(void)
11849 rpp = &redir; 12001 rpp = &redir;
11850 12002
11851 savecheckkwd = CHKALIAS; 12003 savecheckkwd = CHKALIAS;
12004 savelinno = g_parsefile->linno;
11852 for (;;) { 12005 for (;;) {
11853 int t; 12006 int t;
11854 checkkwd = savecheckkwd; 12007 checkkwd = savecheckkwd;
@@ -11938,7 +12091,9 @@ simplecmd(void)
11938 } 12091 }
11939 n->type = NDEFUN; 12092 n->type = NDEFUN;
11940 checkkwd = CHKNL | CHKKWD | CHKALIAS; 12093 checkkwd = CHKNL | CHKKWD | CHKALIAS;
11941 n->narg.next = parse_command(); 12094 n->ndefun.text = n->narg.text;
12095 n->ndefun.linno = g_parsefile->linno;
12096 n->ndefun.body = parse_command();
11942 return n; 12097 return n;
11943 } 12098 }
11944 IF_BASH_FUNCTION(function_flag = 0;) 12099 IF_BASH_FUNCTION(function_flag = 0;)
@@ -11954,6 +12109,7 @@ simplecmd(void)
11954 *rpp = NULL; 12109 *rpp = NULL;
11955 n = stzalloc(sizeof(struct ncmd)); 12110 n = stzalloc(sizeof(struct ncmd));
11956 n->type = NCMD; 12111 n->type = NCMD;
12112 n->ncmd.linno = savelinno;
11957 n->ncmd.args = args; 12113 n->ncmd.args = args;
11958 n->ncmd.assign = vars; 12114 n->ncmd.assign = vars;
11959 n->ncmd.redirect = redir; 12115 n->ncmd.redirect = redir;
@@ -11969,10 +12125,13 @@ parse_command(void)
11969 union node *redir, **rpp; 12125 union node *redir, **rpp;
11970 union node **rpp2; 12126 union node **rpp2;
11971 int t; 12127 int t;
12128 int savelinno;
11972 12129
11973 redir = NULL; 12130 redir = NULL;
11974 rpp2 = &redir; 12131 rpp2 = &redir;
11975 12132
12133 savelinno = g_parsefile->linno;
12134
11976 switch (readtoken()) { 12135 switch (readtoken()) {
11977 default: 12136 default:
11978 raise_error_unexpected_syntax(-1); 12137 raise_error_unexpected_syntax(-1);
@@ -12023,6 +12182,7 @@ parse_command(void)
12023 raise_error_syntax("bad for loop variable"); 12182 raise_error_syntax("bad for loop variable");
12024 n1 = stzalloc(sizeof(struct nfor)); 12183 n1 = stzalloc(sizeof(struct nfor));
12025 n1->type = NFOR; 12184 n1->type = NFOR;
12185 n1->nfor.linno = savelinno;
12026 n1->nfor.var = wordtext; 12186 n1->nfor.var = wordtext;
12027 checkkwd = CHKNL | CHKKWD | CHKALIAS; 12187 checkkwd = CHKNL | CHKKWD | CHKALIAS;
12028 if (readtoken() == TIN) { 12188 if (readtoken() == TIN) {
@@ -12063,6 +12223,7 @@ parse_command(void)
12063 case TCASE: 12223 case TCASE:
12064 n1 = stzalloc(sizeof(struct ncase)); 12224 n1 = stzalloc(sizeof(struct ncase));
12065 n1->type = NCASE; 12225 n1->type = NCASE;
12226 n1->ncase.linno = savelinno;
12066 if (readtoken() != TWORD) 12227 if (readtoken() != TWORD)
12067 raise_error_unexpected_syntax(TWORD); 12228 raise_error_unexpected_syntax(TWORD);
12068 n1->ncase.expr = n2 = stzalloc(sizeof(struct narg)); 12229 n1->ncase.expr = n2 = stzalloc(sizeof(struct narg));
@@ -12114,6 +12275,7 @@ parse_command(void)
12114 case TLP: 12275 case TLP:
12115 n1 = stzalloc(sizeof(struct nredir)); 12276 n1 = stzalloc(sizeof(struct nredir));
12116 n1->type = NSUBSHELL; 12277 n1->type = NSUBSHELL;
12278 n1->nredir.linno = savelinno;
12117 n1->nredir.n = list(0); 12279 n1->nredir.n = list(0);
12118 /*n1->nredir.redirect = NULL; - stzalloc did it */ 12280 /*n1->nredir.redirect = NULL; - stzalloc did it */
12119 t = TRP; 12281 t = TRP;
@@ -12147,6 +12309,7 @@ parse_command(void)
12147 if (n1->type != NSUBSHELL) { 12309 if (n1->type != NSUBSHELL) {
12148 n2 = stzalloc(sizeof(struct nredir)); 12310 n2 = stzalloc(sizeof(struct nredir));
12149 n2->type = NREDIR; 12311 n2->type = NREDIR;
12312 n2->nredir.linno = savelinno;
12150 n2->nredir.n = n1; 12313 n2->nredir.n = n1;
12151 n1 = n2; 12314 n1 = n2;
12152 } 12315 }
@@ -12245,10 +12408,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12245 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ 12408 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
12246 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ 12409 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
12247 int dqvarnest; /* levels of variables expansion within double quotes */ 12410 int dqvarnest; /* levels of variables expansion within double quotes */
12248
12249 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) 12411 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
12250 12412
12251 startlinno = g_parsefile->linno;
12252 bqlist = NULL; 12413 bqlist = NULL;
12253 quotef = 0; 12414 quotef = 0;
12254 IF_FEATURE_SH_MATH(prevsyntax = 0;) 12415 IF_FEATURE_SH_MATH(prevsyntax = 0;)
@@ -12425,7 +12586,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
12425 if (syntax != BASESYNTAX && eofmark == NULL) 12586 if (syntax != BASESYNTAX && eofmark == NULL)
12426 raise_error_syntax("unterminated quoted string"); 12587 raise_error_syntax("unterminated quoted string");
12427 if (varnest != 0) { 12588 if (varnest != 0) {
12428 startlinno = g_parsefile->linno;
12429 /* { */ 12589 /* { */
12430 raise_error_syntax("missing '}'"); 12590 raise_error_syntax("missing '}'");
12431 } 12591 }
@@ -12817,7 +12977,6 @@ parsebackq: {
12817 12977
12818 case PEOF: 12978 case PEOF:
12819 IF_ASH_ALIAS(case PEOA:) 12979 IF_ASH_ALIAS(case PEOA:)
12820 startlinno = g_parsefile->linno;
12821 raise_error_syntax("EOF in backquote substitution"); 12980 raise_error_syntax("EOF in backquote substitution");
12822 12981
12823 case '\n': 12982 case '\n':
@@ -12899,8 +13058,6 @@ parsearith: {
12899 * quoted. 13058 * quoted.
12900 * If the token is TREDIR, then we set redirnode to a structure containing 13059 * If the token is TREDIR, then we set redirnode to a structure containing
12901 * the redirection. 13060 * the redirection.
12902 * In all cases, the variable startlinno is set to the number of the line
12903 * on which the token starts.
12904 * 13061 *
12905 * [Change comment: here documents and internal procedures] 13062 * [Change comment: here documents and internal procedures]
12906 * [Readtoken shouldn't have any arguments. Perhaps we should make the 13063 * [Readtoken shouldn't have any arguments. Perhaps we should make the
@@ -12938,7 +13095,6 @@ xxreadtoken(void)
12938 return lasttoken; 13095 return lasttoken;
12939 } 13096 }
12940 setprompt_if(needprompt, 2); 13097 setprompt_if(needprompt, 2);
12941 startlinno = g_parsefile->linno;
12942 for (;;) { /* until token or start of word found */ 13098 for (;;) { /* until token or start of word found */
12943 c = pgetc(); 13099 c = pgetc();
12944 if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA)) 13100 if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
@@ -12999,7 +13155,6 @@ xxreadtoken(void)
12999 return lasttoken; 13155 return lasttoken;
13000 } 13156 }
13001 setprompt_if(needprompt, 2); 13157 setprompt_if(needprompt, 2);
13002 startlinno = g_parsefile->linno;
13003 for (;;) { /* until token or start of word found */ 13158 for (;;) { /* until token or start of word found */
13004 c = pgetc(); 13159 c = pgetc();
13005 switch (c) { 13160 switch (c) {
@@ -13391,10 +13546,14 @@ find_dot_file(char *name)
13391 if (fullname != name) 13546 if (fullname != name)
13392 stunalloc(fullname); 13547 stunalloc(fullname);
13393 } 13548 }
13549 /* not found in PATH */
13394 13550
13395 /* not found in the PATH */ 13551#if ENABLE_ASH_BASH_SOURCE_CURDIR
13552 return name;
13553#else
13396 ash_msg_and_raise_error("%s: not found", name); 13554 ash_msg_and_raise_error("%s: not found", name);
13397 /* NOTREACHED */ 13555 /* NOTREACHED */
13556#endif
13398} 13557}
13399 13558
13400static int FAST_FUNC 13559static int FAST_FUNC
@@ -13689,8 +13848,21 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path)
13689 /* We failed. If there was an entry for this command, delete it */ 13848 /* We failed. If there was an entry for this command, delete it */
13690 if (cmdp && updatetbl) 13849 if (cmdp && updatetbl)
13691 delete_cmd_entry(); 13850 delete_cmd_entry();
13692 if (act & DO_ERR) 13851 if (act & DO_ERR) {
13852#if ENABLE_ASH_BASH_NOT_FOUND_HOOK
13853 struct tblentry *hookp = cmdlookup("command_not_found_handle", 0);
13854 if (hookp && hookp->cmdtype == CMDFUNCTION) {
13855 char *argv[3];
13856 argv[0] = (char*) "command_not_found_handle";
13857 argv[1] = name;
13858 argv[2] = NULL;
13859 evalfun(hookp->param.func, 2, argv, 0);
13860 entry->cmdtype = CMDUNKNOWN;
13861 return;
13862 }
13863#endif
13693 ash_msg("%s: %s", name, errmsg(e, "not found")); 13864 ash_msg("%s: %s", name, errmsg(e, "not found"));
13865 }
13694 entry->cmdtype = CMDUNKNOWN; 13866 entry->cmdtype = CMDUNKNOWN;
13695 return; 13867 return;
13696 13868
diff --git a/shell/ash_LINENO.patch b/shell/ash_LINENO.patch
deleted file mode 100644
index a71549d6a..000000000
--- a/shell/ash_LINENO.patch
+++ /dev/null
@@ -1,498 +0,0 @@
1This patch is a backport from dash of the combination of:
2 [SHELL] Add preliminary LINENO support
3 [VAR] Fix varinit ordering that broke fc
4 [SHELL] Improve LINENO support
5
6Applies cleanly on top of:
7 commit 9832bbaba966f0e52e183f10cd93fad7f8f643fe
8 Date: Tue Aug 15 15:44:41 2017 +0200
9
10Testsuite needs some tweaks (line numbers in some messages change).
11
12Unfortunately, it is somewhat big:
13
14function old new delta
15parse_command 1581 1658 +77
16calcsize 203 272 +69
17copynode 195 257 +62
18lookupvar 59 108 +49
19evaltree 494 534 +40
20evalfor 152 187 +35
21evalcase 278 313 +35
22evalcommand 1547 1581 +34
23evalsubshell 169 199 +30
24linenovar - 22 +22
25raise_error_syntax 11 29 +18
26evalfun 266 280 +14
27varinit_data 96 108 +12
28cmdtxt 626 631 +5
29lineno - 4 +4
30funcline - 4 +4
31ash_vmsg 144 141 -3
32startlinno 4 - -4
33funcnest 4 - -4
34xxreadtoken 272 259 -13
35readtoken1 2635 2594 -41
36------------------------------------------------------------------------------
37(add/remove: 3/2 grow/shrink: 13/3 up/down: 510/-65) Total: 445 bytes
38 text data bss dec hex filename
39 912030 563 5844 918437 e03a5 busybox_old
40 912473 587 5844 918904 e0578 busybox_unstripped
41
42diff --git a/shell/ash.c b/shell/ash.c
43index 703802f..93a3814 100644
44--- a/shell/ash.c
45+++ b/shell/ash.c
46@@ -312,6 +312,8 @@ struct globals_misc {
47 /* shell level: 0 for the main shell, 1 for its children, and so on */
48 int shlvl;
49 #define rootshell (!shlvl)
50+ int errlinno;
51+
52 char *minusc; /* argument to -c option */
53
54 char *curdir; // = nullstr; /* current working directory */
55@@ -389,6 +391,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc;
56 #define job_warning (G_misc.job_warning)
57 #define rootpid (G_misc.rootpid )
58 #define shlvl (G_misc.shlvl )
59+#define errlinno (G_misc.errlinno )
60 #define minusc (G_misc.minusc )
61 #define curdir (G_misc.curdir )
62 #define physdir (G_misc.physdir )
63@@ -723,6 +726,7 @@ union node;
64
65 struct ncmd {
66 smallint type; /* Nxxxx */
67+ int linno;
68 union node *assign;
69 union node *args;
70 union node *redirect;
71@@ -736,6 +740,7 @@ struct npipe {
72
73 struct nredir {
74 smallint type;
75+ int linno;
76 union node *n;
77 union node *redirect;
78 };
79@@ -755,6 +760,7 @@ struct nif {
80
81 struct nfor {
82 smallint type;
83+ int linno;
84 union node *args;
85 union node *body;
86 char *var;
87@@ -762,6 +768,7 @@ struct nfor {
88
89 struct ncase {
90 smallint type;
91+ int linno;
92 union node *expr;
93 union node *cases;
94 };
95@@ -773,6 +780,13 @@ struct nclist {
96 union node *body;
97 };
98
99+struct ndefun {
100+ smallint type;
101+ int linno;
102+ char *text;
103+ union node *body;
104+};
105+
106 struct narg {
107 smallint type;
108 union node *next;
109@@ -824,6 +838,7 @@ union node {
110 struct nfor nfor;
111 struct ncase ncase;
112 struct nclist nclist;
113+ struct ndefun ndefun;
114 struct narg narg;
115 struct nfile nfile;
116 struct ndup ndup;
117@@ -1253,7 +1268,6 @@ struct parsefile {
118
119 static struct parsefile basepf; /* top level input file */
120 static struct parsefile *g_parsefile = &basepf; /* current input file */
121-static int startlinno; /* line # where last token started */
122 static char *commandname; /* currently executing command */
123
124
125@@ -1267,7 +1281,7 @@ ash_vmsg(const char *msg, va_list ap)
126 if (strcmp(arg0, commandname))
127 fprintf(stderr, "%s: ", commandname);
128 if (!iflag || g_parsefile->pf_fd > 0)
129- fprintf(stderr, "line %d: ", startlinno);
130+ fprintf(stderr, "line %d: ", errlinno);
131 }
132 vfprintf(stderr, msg, ap);
133 newline_and_flush(stderr);
134@@ -1327,6 +1341,7 @@ static void raise_error_syntax(const char *) NORETURN;
135 static void
136 raise_error_syntax(const char *msg)
137 {
138+ errlinno = g_parsefile->linno;
139 ash_msg_and_raise_error("syntax error: %s", msg);
140 /* NOTREACHED */
141 }
142@@ -1993,6 +2008,9 @@ static void changepath(const char *) FAST_FUNC;
143 static void change_random(const char *) FAST_FUNC;
144 #endif
145
146+static int lineno;
147+static char linenovar[sizeof("LINENO=%d") + sizeof(int)*3] = "LINENO=";
148+
149 static const struct {
150 int flags;
151 const char *var_text;
152@@ -2014,6 +2032,7 @@ static const struct {
153 #if ENABLE_ASH_GETOPTS
154 { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset },
155 #endif
156+ { VSTRFIXED|VTEXTFIXED , linenovar , NULL },
157 #if ENABLE_ASH_RANDOM_SUPPORT
158 { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random },
159 #endif
160@@ -2066,12 +2085,14 @@ extern struct globals_var *const ash_ptr_to_globals_var;
161 #define vps4 (&vps2)[1]
162 #if ENABLE_ASH_GETOPTS
163 # define voptind (&vps4)[1]
164+# define vlineno (&voptind)[1]
165 # if ENABLE_ASH_RANDOM_SUPPORT
166-# define vrandom (&voptind)[1]
167+# define vrandom (&vlineno)[1]
168 # endif
169 #else
170+# define vlineno (&vps4)[1]
171 # if ENABLE_ASH_RANDOM_SUPPORT
172-# define vrandom (&vps4)[1]
173+# define vrandom (&vlineno)[1]
174 # endif
175 #endif
176
177@@ -2209,8 +2230,12 @@ lookupvar(const char *name)
178 if (v->flags & VDYNAMIC)
179 v->var_func(NULL);
180 #endif
181- if (!(v->flags & VUNSET))
182+ if (!(v->flags & VUNSET)) {
183+ if (v == &vlineno && v->var_text == linenovar) {
184+ fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno);
185+ }
186 return var_end(v->var_text);
187+ }
188 }
189 return NULL;
190 }
191@@ -4783,7 +4808,7 @@ cmdtxt(union node *n)
192 p = "; done";
193 goto dodo;
194 case NDEFUN:
195- cmdputs(n->narg.text);
196+ cmdputs(n->ndefun.text);
197 p = "() { ... }";
198 goto dotail2;
199 case NCMD:
200@@ -8551,6 +8576,9 @@ calcsize(int funcblocksize, union node *n)
201 funcblocksize = calcsize(funcblocksize, n->nclist.next);
202 break;
203 case NDEFUN:
204+ funcblocksize = calcsize(funcblocksize, n->ndefun.body);
205+ funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1);
206+ break;
207 case NARG:
208 funcblocksize = sizenodelist(funcblocksize, n->narg.backquote);
209 funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */
210@@ -8626,6 +8654,7 @@ copynode(union node *n)
211 new->ncmd.redirect = copynode(n->ncmd.redirect);
212 new->ncmd.args = copynode(n->ncmd.args);
213 new->ncmd.assign = copynode(n->ncmd.assign);
214+ new->ncmd.linno = n->ncmd.linno;
215 break;
216 case NPIPE:
217 new->npipe.cmdlist = copynodelist(n->npipe.cmdlist);
218@@ -8636,6 +8665,7 @@ copynode(union node *n)
219 case NSUBSHELL:
220 new->nredir.redirect = copynode(n->nredir.redirect);
221 new->nredir.n = copynode(n->nredir.n);
222+ new->nredir.linno = n->nredir.linno;
223 break;
224 case NAND:
225 case NOR:
226@@ -8654,10 +8684,12 @@ copynode(union node *n)
227 new->nfor.var = nodeckstrdup(n->nfor.var);
228 new->nfor.body = copynode(n->nfor.body);
229 new->nfor.args = copynode(n->nfor.args);
230+ new->nfor.linno = n->nfor.linno;
231 break;
232 case NCASE:
233 new->ncase.cases = copynode(n->ncase.cases);
234 new->ncase.expr = copynode(n->ncase.expr);
235+ new->ncase.linno = n->ncase.linno;
236 break;
237 case NCLIST:
238 new->nclist.body = copynode(n->nclist.body);
239@@ -8665,6 +8697,10 @@ copynode(union node *n)
240 new->nclist.next = copynode(n->nclist.next);
241 break;
242 case NDEFUN:
243+ new->ndefun.body = copynode(n->ndefun.body);
244+ new->ndefun.text = nodeckstrdup(n->ndefun.text);
245+ new->ndefun.linno = n->ndefun.linno;
246+ break;
247 case NARG:
248 new->narg.backquote = copynodelist(n->narg.backquote);
249 new->narg.text = nodeckstrdup(n->narg.text);
250@@ -8733,7 +8769,7 @@ defun(union node *func)
251 INT_OFF;
252 entry.cmdtype = CMDFUNCTION;
253 entry.u.func = copyfunc(func);
254- addcmdentry(func->narg.text, &entry);
255+ addcmdentry(func->ndefun.text, &entry);
256 INT_ON;
257 }
258
259@@ -8743,8 +8779,8 @@ defun(union node *func)
260 #define SKIPFUNC (1 << 2)
261 static smallint evalskip; /* set to SKIPxxx if we are skipping commands */
262 static int skipcount; /* number of levels to skip */
263-static int funcnest; /* depth of function calls */
264 static int loopnest; /* current loop nesting level */
265+static int funcline; /* starting line number of current function, or 0 if not in a function */
266
267 /* Forward decl way out to parsing code - dotrap needs it */
268 static int evalstring(char *s, int flags);
269@@ -8839,6 +8875,9 @@ evaltree(union node *n, int flags)
270 status = !evaltree(n->nnot.com, EV_TESTED);
271 goto setstatus;
272 case NREDIR:
273+ errlinno = lineno = n->nredir.linno;
274+ if (funcline)
275+ lineno -= funcline - 1;
276 expredir(n->nredir.redirect);
277 pushredir(n->nredir.redirect);
278 status = redirectsafe(n->nredir.redirect, REDIR_PUSH);
279@@ -8993,6 +9032,10 @@ evalfor(union node *n, int flags)
280 struct stackmark smark;
281 int status = 0;
282
283+ errlinno = lineno = n->ncase.linno;
284+ if (funcline)
285+ lineno -= funcline - 1;
286+
287 setstackmark(&smark);
288 arglist.list = NULL;
289 arglist.lastp = &arglist.list;
290@@ -9024,6 +9067,10 @@ evalcase(union node *n, int flags)
291 struct stackmark smark;
292 int status = 0;
293
294+ errlinno = lineno = n->ncase.linno;
295+ if (funcline)
296+ lineno -= funcline - 1;
297+
298 setstackmark(&smark);
299 arglist.list = NULL;
300 arglist.lastp = &arglist.list;
301@@ -9058,6 +9105,10 @@ evalsubshell(union node *n, int flags)
302 int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */
303 int status;
304
305+ errlinno = lineno = n->nredir.linno;
306+ if (funcline)
307+ lineno -= funcline - 1;
308+
309 expredir(n->nredir.redirect);
310 if (!backgnd && (flags & EV_EXIT) && !may_have_traps)
311 goto nofork;
312@@ -9365,8 +9416,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
313 struct jmploc *volatile savehandler;
314 struct jmploc jmploc;
315 int e;
316+ int savefuncline;
317
318 saveparam = shellparam;
319+ savefuncline = funcline;
320 savehandler = exception_handler;
321 e = setjmp(jmploc.loc);
322 if (e) {
323@@ -9376,7 +9429,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
324 exception_handler = &jmploc;
325 shellparam.malloced = 0;
326 func->count++;
327- funcnest++;
328+ funcline = func->n.ndefun.linno;
329 INT_ON;
330 shellparam.nparam = argc - 1;
331 shellparam.p = argv + 1;
332@@ -9385,11 +9438,11 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags)
333 shellparam.optoff = -1;
334 #endif
335 pushlocalvars();
336- evaltree(func->n.narg.next, flags & EV_TESTED);
337+ evaltree(func->n.ndefun.body, flags & EV_TESTED);
338 poplocalvars(0);
339 funcdone:
340 INT_OFF;
341- funcnest--;
342+ funcline = savefuncline;
343 freefunc(func);
344 freeparam(&shellparam);
345 shellparam = saveparam;
346@@ -9753,6 +9806,10 @@ evalcommand(union node *cmd, int flags)
347 char **nargv;
348 smallint cmd_is_exec;
349
350+ errlinno = lineno = cmd->ncmd.linno;
351+ if (funcline)
352+ lineno -= funcline - 1;
353+
354 /* First expand the arguments. */
355 TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags));
356 setstackmark(&smark);
357@@ -9798,7 +9855,7 @@ evalcommand(union node *cmd, int flags)
358 *nargv = NULL;
359
360 lastarg = NULL;
361- if (iflag && funcnest == 0 && argc > 0)
362+ if (iflag && funcline == 0 && argc > 0)
363 lastarg = nargv[-1];
364
365 expredir(cmd->ncmd.redirect);
366@@ -11317,6 +11374,7 @@ simplecmd(void)
367 union node *vars, **vpp;
368 union node **rpp, *redir;
369 int savecheckkwd;
370+ int savelinno;
371 #if BASH_TEST2
372 smallint double_brackets_flag = 0;
373 #endif
374@@ -11330,6 +11388,7 @@ simplecmd(void)
375 rpp = &redir;
376
377 savecheckkwd = CHKALIAS;
378+ savelinno = g_parsefile->linno;
379 for (;;) {
380 int t;
381 checkkwd = savecheckkwd;
382@@ -11419,7 +11478,9 @@ simplecmd(void)
383 }
384 n->type = NDEFUN;
385 checkkwd = CHKNL | CHKKWD | CHKALIAS;
386- n->narg.next = parse_command();
387+ n->ndefun.text = n->narg.text;
388+ n->ndefun.linno = g_parsefile->linno;
389+ n->ndefun.body = parse_command();
390 return n;
391 }
392 IF_BASH_FUNCTION(function_flag = 0;)
393@@ -11435,6 +11496,7 @@ simplecmd(void)
394 *rpp = NULL;
395 n = stzalloc(sizeof(struct ncmd));
396 n->type = NCMD;
397+ n->ncmd.linno = savelinno;
398 n->ncmd.args = args;
399 n->ncmd.assign = vars;
400 n->ncmd.redirect = redir;
401@@ -11450,10 +11512,13 @@ parse_command(void)
402 union node *redir, **rpp;
403 union node **rpp2;
404 int t;
405+ int savelinno;
406
407 redir = NULL;
408 rpp2 = &redir;
409
410+ savelinno = g_parsefile->linno;
411+
412 switch (readtoken()) {
413 default:
414 raise_error_unexpected_syntax(-1);
415@@ -11504,6 +11569,7 @@ parse_command(void)
416 raise_error_syntax("bad for loop variable");
417 n1 = stzalloc(sizeof(struct nfor));
418 n1->type = NFOR;
419+ n1->nfor.linno = savelinno;
420 n1->nfor.var = wordtext;
421 checkkwd = CHKNL | CHKKWD | CHKALIAS;
422 if (readtoken() == TIN) {
423@@ -11544,6 +11610,7 @@ parse_command(void)
424 case TCASE:
425 n1 = stzalloc(sizeof(struct ncase));
426 n1->type = NCASE;
427+ n1->ncase.linno = savelinno;
428 if (readtoken() != TWORD)
429 raise_error_unexpected_syntax(TWORD);
430 n1->ncase.expr = n2 = stzalloc(sizeof(struct narg));
431@@ -11595,6 +11662,7 @@ parse_command(void)
432 case TLP:
433 n1 = stzalloc(sizeof(struct nredir));
434 n1->type = NSUBSHELL;
435+ n1->nredir.linno = savelinno;
436 n1->nredir.n = list(0);
437 /*n1->nredir.redirect = NULL; - stzalloc did it */
438 t = TRP;
439@@ -11628,6 +11696,7 @@ parse_command(void)
440 if (n1->type != NSUBSHELL) {
441 n2 = stzalloc(sizeof(struct nredir));
442 n2->type = NREDIR;
443+ n2->nredir.linno = savelinno;
444 n2->nredir.n = n1;
445 n1 = n2;
446 }
447@@ -11726,10 +11795,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
448 IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */
449 IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */
450 int dqvarnest; /* levels of variables expansion within double quotes */
451-
452 IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;)
453
454- startlinno = g_parsefile->linno;
455 bqlist = NULL;
456 quotef = 0;
457 IF_FEATURE_SH_MATH(prevsyntax = 0;)
458@@ -11906,7 +11973,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs)
459 if (syntax != BASESYNTAX && eofmark == NULL)
460 raise_error_syntax("unterminated quoted string");
461 if (varnest != 0) {
462- startlinno = g_parsefile->linno;
463 /* { */
464 raise_error_syntax("missing '}'");
465 }
466@@ -12298,7 +12364,6 @@ parsebackq: {
467
468 case PEOF:
469 IF_ASH_ALIAS(case PEOA:)
470- startlinno = g_parsefile->linno;
471 raise_error_syntax("EOF in backquote substitution");
472
473 case '\n':
474@@ -12380,8 +12445,6 @@ parsearith: {
475 * quoted.
476 * If the token is TREDIR, then we set redirnode to a structure containing
477 * the redirection.
478- * In all cases, the variable startlinno is set to the number of the line
479- * on which the token starts.
480 *
481 * [Change comment: here documents and internal procedures]
482 * [Readtoken shouldn't have any arguments. Perhaps we should make the
483@@ -12419,7 +12482,6 @@ xxreadtoken(void)
484 return lasttoken;
485 }
486 setprompt_if(needprompt, 2);
487- startlinno = g_parsefile->linno;
488 for (;;) { /* until token or start of word found */
489 c = pgetc();
490 if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA))
491@@ -12480,7 +12542,6 @@ xxreadtoken(void)
492 return lasttoken;
493 }
494 setprompt_if(needprompt, 2);
495- startlinno = g_parsefile->linno;
496 for (;;) { /* until token or start of word found */
497 c = pgetc();
498 switch (c) {
diff --git a/shell/ash_test/ash-arith/arith-postinc.right b/shell/ash_test/ash-arith/arith-postinc.right
new file mode 100644
index 000000000..c95ce02bf
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-postinc.right
@@ -0,0 +1,5 @@
11 1
21 1
31 1
41 1
5Ok:0
diff --git a/shell/ash_test/ash-arith/arith-postinc.tests b/shell/ash_test/ash-arith/arith-postinc.tests
new file mode 100755
index 000000000..3fd9bfed5
--- /dev/null
+++ b/shell/ash_test/ash-arith/arith-postinc.tests
@@ -0,0 +1,5 @@
1echo 1 $((0++1))
2echo 1 $((0--1))
3x=-1; echo 1 $((0-$x))
4x=+1; echo 1 $((0+$x))
5echo Ok:$?
diff --git a/shell/ash_test/ash-arith/arith.right b/shell/ash_test/ash-arith/arith.right
index 9b9ca8e2f..6936f1269 100644
--- a/shell/ash_test/ash-arith/arith.right
+++ b/shell/ash_test/ash-arith/arith.right
@@ -126,6 +126,10 @@ ghi
126./arith2.sub: line 5: arithmetic syntax error 126./arith2.sub: line 5: arithmetic syntax error
1275 5 1275 5
1281 1 1281 1
1296 6
1302 2
1313 3
1321 1
1294 4 1334 4
1300 0 1340 0
131./arith2.sub: line 42: arithmetic syntax error 135./arith2.sub: line 42: arithmetic syntax error
diff --git a/shell/ash_test/ash-arith/arith2.sub b/shell/ash_test/ash-arith/arith2.sub
index f7e3c9235..9105059db 100755
--- a/shell/ash_test/ash-arith/arith2.sub
+++ b/shell/ash_test/ash-arith/arith2.sub
@@ -23,14 +23,14 @@
23echo 5 $(( 4 + ++a )) 23echo 5 $(( 4 + ++a ))
24echo 1 $a 24echo 1 $a
25 25
26# ash doesn't handle it right... 26# this is treated as 4 + ++a
27#ash# echo 6 $(( 4+++a )) 27echo 6 $(( 4+++a ))
28#ash# echo 2 $a 28echo 2 $a
29 a=2 29 a=2
30 30
31# ash doesn't handle it right... 31# this is treated as 4 - --a
32#ash# echo 3 $(( 4---a )) 32echo 3 $(( 4---a ))
33#ash# echo 1 $a 33echo 1 $a
34 a=1 34 a=1
35 35
36echo 4 $(( 4 - -- a )) 36echo 4 $(( 4 - -- a ))
diff --git a/shell/ash_test/ash-misc/control_char1.right b/shell/ash_test/ash-misc/control_char1.right
new file mode 100644
index 000000000..6f8c2533c
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char1.right
@@ -0,0 +1,3 @@
1
2b#c
3Done:0
diff --git a/shell/ash_test/ash-misc/control_char1.tests b/shell/ash_test/ash-misc/control_char1.tests
new file mode 100755
index 000000000..0cfe60141
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char1.tests
@@ -0,0 +1,3 @@
1echo 
2echo 'b#c'
3echo Done:$?
diff --git a/shell/ash_test/ash-misc/control_char2.right b/shell/ash_test/ash-misc/control_char2.right
new file mode 100644
index 000000000..9498b420d
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char2.right
@@ -0,0 +1,2 @@
1
2Done:0
diff --git a/shell/ash_test/ash-misc/control_char2.tests b/shell/ash_test/ash-misc/control_char2.tests
new file mode 100755
index 000000000..e77d7a1a6
--- /dev/null
+++ b/shell/ash_test/ash-misc/control_char2.tests
@@ -0,0 +1,3 @@
1c=`printf '\3'`
2eval "echo $c"
3echo Done:$?
diff --git a/shell/ash_test/ash-misc/for_with_bslashes.right b/shell/ash_test/ash-misc/for_with_bslashes.right
index 02d96692c..cd8501050 100644
--- a/shell/ash_test/ash-misc/for_with_bslashes.right
+++ b/shell/ash_test/ash-misc/for_with_bslashes.right
@@ -5,4 +5,5 @@ b"c
5b'c 5b'c
6b$c 6b$c
7b`true`c 7b`true`c
8b#c
8Zero:0 9Zero:0
diff --git a/shell/ash_test/ash-misc/for_with_bslashes.tests b/shell/ash_test/ash-misc/for_with_bslashes.tests
index 363f3d85b..8acd9808a 100755
--- a/shell/ash_test/ash-misc/for_with_bslashes.tests
+++ b/shell/ash_test/ash-misc/for_with_bslashes.tests
@@ -1,9 +1,5 @@
1# UNFIXED BUG. 1# last word contains ^C character.
2# commented-out words contain ^C character. 2for a in 'a' 'b\c' 'b\\c' 'b"c' "b'c" 'b$c' 'b`true`c' 'b#c'
3# It's a SPECIAL_VAR_SYMBOL, for now hush does not escape it.
4# When it is fixed, update this test.
5
6for a in 'a' 'b\c' 'b\\c' 'b"c' "b'c" 'b$c' 'b`true`c' ### 'b#c'
7do 3do
8 echo $a 4 echo $a
9done 5done
diff --git a/shell/ash_test/ash-psubst/emptytick.right b/shell/ash_test/ash-psubst/emptytick.right
index 7629deba6..459c4f735 100644
--- a/shell/ash_test/ash-psubst/emptytick.right
+++ b/shell/ash_test/ash-psubst/emptytick.right
@@ -1,8 +1,8 @@
10 10
20 20
3./emptytick.tests: line 3: : Permission denied 3./emptytick.tests: line 1: : Permission denied
4127 4127
5./emptytick.tests: line 4: : Permission denied 5./emptytick.tests: line 1: : Permission denied
6127 6127
70 70
80 80
diff --git a/shell/ash_test/ash-quoting/mode_x.right b/shell/ash_test/ash-quoting/mode_x.right
index c2dd3550c..d1f670af6 100644
--- a/shell/ash_test/ash-quoting/mode_x.right
+++ b/shell/ash_test/ash-quoting/mode_x.right
@@ -3,8 +3,8 @@
3+ true '%s\n' one 'two '"'"'three' four 3+ true '%s\n' one 'two '"'"'three' four
4+ this=command 4+ this=command
5+ 'this=command' 5+ 'this=command'
6./mode_x.tests: line 1: this=command: not found 6./mode_x.tests: line 10: this=command: not found
7+ true 7+ true
8+ true 8+ true
9+ 'if' true 9+ 'if' true
10./mode_x.tests: line 1: if: not found 10./mode_x.tests: line 14: if: not found
diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.right b/shell/ash_test/ash-vars/param_expand_bash_substring.right
index 9ad6dbcad..687dd9002 100644
--- a/shell/ash_test/ash-vars/param_expand_bash_substring.right
+++ b/shell/ash_test/ash-vars/param_expand_bash_substring.right
@@ -3,6 +3,7 @@ SHELL: line 1: syntax error: bad substitution
3SHELL: line 1: syntax error: bad substitution 3SHELL: line 1: syntax error: bad substitution
4SHELL: line 1: syntax error: bad substitution 4SHELL: line 1: syntax error: bad substitution
5SHELL: line 1: syntax error: missing '}' 5SHELL: line 1: syntax error: missing '}'
60
61 =|| 71 =||
71:1 =|| 81:1 =||
81:1:2=|| 91:1:2=||
diff --git a/shell/ash_test/ash-vars/param_expand_bash_substring.tests b/shell/ash_test/ash-vars/param_expand_bash_substring.tests
index cce9f123e..512da351b 100755
--- a/shell/ash_test/ash-vars/param_expand_bash_substring.tests
+++ b/shell/ash_test/ash-vars/param_expand_bash_substring.tests
@@ -11,7 +11,7 @@ export var=0123456789
11"$THIS_SH" -c 'echo ${var:}' SHELL 11"$THIS_SH" -c 'echo ${var:}' SHELL
12 12
13# then some funky ones 13# then some funky ones
14# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' 14"$THIS_SH" -c 'echo ${?:0}' SHELL
15 15
16# now some valid ones 16# now some valid ones
17set --; echo "1 =|${1}|" 17set --; echo "1 =|${1}|"
diff --git a/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.right b/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.right
new file mode 100644
index 000000000..d400a7e31
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.right
@@ -0,0 +1,2 @@
1v
2Ok:0
diff --git a/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.tests b/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.tests
new file mode 100755
index 000000000..6e8aa2afa
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_repl_empty_pattern.tests
@@ -0,0 +1,3 @@
1v=v
2echo ${v//}
3echo Ok:$?
diff --git a/shell/ash_test/ash-vars/var_bash_repl_empty_var.right b/shell/ash_test/ash-vars/var_bash_repl_empty_var.right
new file mode 100644
index 000000000..892916783
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_repl_empty_var.right
@@ -0,0 +1,2 @@
1
2Ok:0
diff --git a/shell/ash_test/ash-vars/var_bash_repl_empty_var.tests b/shell/ash_test/ash-vars/var_bash_repl_empty_var.tests
new file mode 100755
index 000000000..73a43d38e
--- /dev/null
+++ b/shell/ash_test/ash-vars/var_bash_repl_empty_var.tests
@@ -0,0 +1,3 @@
1v=''
2echo ${v/*/w}
3echo Ok:$?
diff --git a/shell/hush.c b/shell/hush.c
index 708555ac4..8f1017e3c 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -47,17 +47,13 @@
47 * follow IFS rules more precisely, including update semantics 47 * follow IFS rules more precisely, including update semantics
48 * tilde expansion 48 * tilde expansion
49 * aliases 49 * aliases
50 * builtins mandated by standards we don't support: 50 * "command" missing features:
51 * [un]alias, command, fc: 51 * command -p CMD: run CMD using default $PATH
52 * command -v CMD: print "/path/to/CMD" 52 * (can use this to override standalone shell as well?)
53 * prints "CMD" for builtins
54 * prints "alias ALIAS='EXPANSION'" for aliases
55 * prints nothing and sets $? to 1 if not found
56 * command -V CMD: print "CMD is /path/CMD|a shell builtin|etc"
57 * command [-p] CMD: run CMD, even if a function CMD also exists
58 * (can use this to override standalone shell as well)
59 * -p: use default $PATH
60 * command BLTIN: disables special-ness (e.g. errors do not abort) 53 * command BLTIN: disables special-ness (e.g. errors do not abort)
54 * command -V CMD1 CMD2 CMD3 (multiple args) (not in standard)
55 * builtins mandated by standards we don't support:
56 * [un]alias, fc:
61 * fc -l[nr] [BEG] [END]: list range of commands in history 57 * fc -l[nr] [BEG] [END]: list range of commands in history
62 * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands 58 * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands
63 * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP 59 * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP
@@ -124,6 +120,18 @@
124//config: help 120//config: help
125//config: Enable {abc,def} extension. 121//config: Enable {abc,def} extension.
126//config: 122//config:
123//config:config HUSH_LINENO_VAR
124//config: bool "$LINENO variable"
125//config: default y
126//config: depends on HUSH_BASH_COMPAT
127//config:
128//config:config HUSH_BASH_SOURCE_CURDIR
129//config: bool "'source' and '.' builtins search current directory after $PATH"
130//config: default n # do not encourage non-standard behavior
131//config: depends on HUSH_BASH_COMPAT
132//config: help
133//config: This is not compliant with standards. Avoid if possible.
134//config:
127//config:config HUSH_INTERACTIVE 135//config:config HUSH_INTERACTIVE
128//config: bool "Interactive mode" 136//config: bool "Interactive mode"
129//config: default y 137//config: default y
@@ -253,6 +261,11 @@
253//config: default y 261//config: default y
254//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH 262//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
255//config: 263//config:
264//config:config HUSH_COMMAND
265//config: bool "command builtin"
266//config: default y
267//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH
268//config:
256//config:config HUSH_TRAP 269//config:config HUSH_TRAP
257//config: bool "trap builtin" 270//config: bool "trap builtin"
258//config: default y 271//config: default y
@@ -462,7 +475,10 @@
462# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3) 475# define MINUS_PLUS_EQUAL_QUESTION ("%#:-=+?" + 3)
463#endif 476#endif
464 477
465#define SPECIAL_VAR_SYMBOL 3 478#define SPECIAL_VAR_SYMBOL_STR "\3"
479#define SPECIAL_VAR_SYMBOL 3
480/* The "variable" with name "\1" emits string "\3". Testcase: "echo ^C" */
481#define SPECIAL_VAR_QUOTED_SVS 1
466 482
467struct variable; 483struct variable;
468 484
@@ -608,6 +624,9 @@ typedef enum redir_type {
608struct command { 624struct command {
609 pid_t pid; /* 0 if exited */ 625 pid_t pid; /* 0 if exited */
610 int assignment_cnt; /* how many argv[i] are assignments? */ 626 int assignment_cnt; /* how many argv[i] are assignments? */
627#if ENABLE_HUSH_LINENO_VAR
628 unsigned lineno;
629#endif
611 smallint cmd_type; /* CMD_xxx */ 630 smallint cmd_type; /* CMD_xxx */
612#define CMD_NORMAL 0 631#define CMD_NORMAL 0
613#define CMD_SUBSHELL 1 632#define CMD_SUBSHELL 1
@@ -927,6 +946,10 @@ struct globals {
927 unsigned handled_SIGCHLD; 946 unsigned handled_SIGCHLD;
928 smallint we_have_children; 947 smallint we_have_children;
929#endif 948#endif
949#if ENABLE_HUSH_LINENO_VAR
950 unsigned lineno;
951 char *lineno_var;
952#endif
930 struct FILE_list *FILE_list; 953 struct FILE_list *FILE_list;
931 /* Which signals have non-DFL handler (even with no traps set)? 954 /* Which signals have non-DFL handler (even with no traps set)?
932 * Set at the start to: 955 * Set at the start to:
@@ -1924,7 +1947,7 @@ static void hush_exit(int exitcode)
1924 if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) { 1947 if (G.exiting <= 0 && G_traps && G_traps[0] && G_traps[0][0]) {
1925 char *argv[3]; 1948 char *argv[3];
1926 /* argv[0] is unused */ 1949 /* argv[0] is unused */
1927 argv[1] = G_traps[0]; 1950 argv[1] = xstrdup(G_traps[0]); /* copy, since EXIT trap handler may modify G_traps[0] */
1928 argv[2] = NULL; 1951 argv[2] = NULL;
1929 G.exiting = 1; /* prevent EXIT trap recursion */ 1952 G.exiting = 1; /* prevent EXIT trap recursion */
1930 /* Note: G_traps[0] is not cleared! 1953 /* Note: G_traps[0] is not cleared!
@@ -1985,10 +2008,12 @@ static int check_and_run_traps(void)
1985 smalluint save_rcode; 2008 smalluint save_rcode;
1986 char *argv[3]; 2009 char *argv[3];
1987 /* argv[0] is unused */ 2010 /* argv[0] is unused */
1988 argv[1] = G_traps[sig]; 2011 argv[1] = xstrdup(G_traps[sig]);
2012 /* why strdup? trap can modify itself: trap 'trap "echo oops" INT' INT */
1989 argv[2] = NULL; 2013 argv[2] = NULL;
1990 save_rcode = G.last_exitcode; 2014 save_rcode = G.last_exitcode;
1991 builtin_eval(argv); 2015 builtin_eval(argv);
2016 free(argv[1]);
1992//FIXME: shouldn't it be set to 128 + sig instead? 2017//FIXME: shouldn't it be set to 128 + sig instead?
1993 G.last_exitcode = save_rcode; 2018 G.last_exitcode = save_rcode;
1994 last_sig = sig; 2019 last_sig = sig;
@@ -2131,6 +2156,13 @@ static int set_local_var(char *str, unsigned flags)
2131 } 2156 }
2132 2157
2133 name_len = eq_sign - str + 1; /* including '=' */ 2158 name_len = eq_sign - str + 1; /* including '=' */
2159#if ENABLE_HUSH_LINENO_VAR
2160 if (G.lineno_var) {
2161 if (name_len == 7 && strncmp("LINENO", str, 6) == 0)
2162 G.lineno_var = NULL;
2163 }
2164#endif
2165
2134 var_pp = &G.top_var; 2166 var_pp = &G.top_var;
2135 while ((cur = *var_pp) != NULL) { 2167 while ((cur = *var_pp) != NULL) {
2136 if (strncmp(cur->varstr, str, name_len) != 0) { 2168 if (strncmp(cur->varstr, str, name_len) != 0) {
@@ -2252,10 +2284,16 @@ static int unset_local_var_len(const char *name, int name_len)
2252 2284
2253 if (!name) 2285 if (!name)
2254 return EXIT_SUCCESS; 2286 return EXIT_SUCCESS;
2287
2255#if ENABLE_HUSH_GETOPTS 2288#if ENABLE_HUSH_GETOPTS
2256 if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0) 2289 if (name_len == 6 && strncmp(name, "OPTIND", 6) == 0)
2257 G.getopt_count = 0; 2290 G.getopt_count = 0;
2258#endif 2291#endif
2292#if ENABLE_HUSH_LINENO_VAR
2293 if (name_len == 6 && G.lineno_var && strncmp(name, "LINENO", 6) == 0)
2294 G.lineno_var = NULL;
2295#endif
2296
2259 var_pp = &G.top_var; 2297 var_pp = &G.top_var;
2260 while ((cur = *var_pp) != NULL) { 2298 while ((cur = *var_pp) != NULL) {
2261 if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') { 2299 if (strncmp(cur->varstr, name, name_len) == 0 && cur->varstr[name_len] == '=') {
@@ -2278,7 +2316,7 @@ static int unset_local_var_len(const char *name, int name_len)
2278 return EXIT_SUCCESS; 2316 return EXIT_SUCCESS;
2279} 2317}
2280 2318
2281#if ENABLE_HUSH_UNSET 2319#if ENABLE_HUSH_UNSET || ENABLE_HUSH_GETOPTS
2282static int unset_local_var(const char *name) 2320static int unset_local_var(const char *name)
2283{ 2321{
2284 return unset_local_var_len(name, strlen(name)); 2322 return unset_local_var_len(name, strlen(name));
@@ -2300,7 +2338,7 @@ static void unset_vars(char **strings)
2300 free(strings); 2338 free(strings);
2301} 2339}
2302 2340
2303#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ 2341#if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS
2304static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) 2342static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
2305{ 2343{
2306 char *var = xasprintf("%s=%s", name, val); 2344 char *var = xasprintf("%s=%s", name, val);
@@ -2574,6 +2612,12 @@ static int i_getch(struct in_str *i)
2574 out: 2612 out:
2575 debug_printf("file_get: got '%c' %d\n", ch, ch); 2613 debug_printf("file_get: got '%c' %d\n", ch, ch);
2576 i->last_char = ch; 2614 i->last_char = ch;
2615#if ENABLE_HUSH_LINENO_VAR
2616 if (ch == '\n') {
2617 G.lineno++;
2618 debug_printf_parse("G.lineno++ = %u\n", G.lineno);
2619 }
2620#endif
2577 return ch; 2621 return ch;
2578} 2622}
2579 2623
@@ -3374,8 +3418,13 @@ static void debug_print_tree(struct pipe *pi, int lvl)
3374 3418
3375 pin = 0; 3419 pin = 0;
3376 while (pi) { 3420 while (pi) {
3377 fdprintf(2, "%*spipe %d res_word=%s followup=%d %s\n", lvl*2, "", 3421 fdprintf(2, "%*spipe %d %sres_word=%s followup=%d %s\n",
3378 pin, RES[pi->res_word], pi->followup, PIPE[pi->followup]); 3422 lvl*2, "",
3423 pin,
3424 (IF_HAS_KEYWORDS(pi->pi_inverted ? "! " :) ""),
3425 RES[pi->res_word],
3426 pi->followup, PIPE[pi->followup]
3427 );
3379 prn = 0; 3428 prn = 0;
3380 while (prn < pi->num_cmds) { 3429 while (prn < pi->num_cmds) {
3381 struct command *command = &pi->cmds[prn]; 3430 struct command *command = &pi->cmds[prn];
@@ -3384,6 +3433,9 @@ static void debug_print_tree(struct pipe *pi, int lvl)
3384 fdprintf(2, "%*s cmd %d assignment_cnt:%d", 3433 fdprintf(2, "%*s cmd %d assignment_cnt:%d",
3385 lvl*2, "", prn, 3434 lvl*2, "", prn,
3386 command->assignment_cnt); 3435 command->assignment_cnt);
3436#if ENABLE_HUSH_LINENO_VAR
3437 fdprintf(2, " LINENO:%u", command->lineno);
3438#endif
3387 if (command->group) { 3439 if (command->group) {
3388 fdprintf(2, " group %s: (argv=%p)%s%s\n", 3440 fdprintf(2, " group %s: (argv=%p)%s%s\n",
3389 CMDTYPE[command->cmd_type], 3441 CMDTYPE[command->cmd_type],
@@ -3456,6 +3508,10 @@ static int done_command(struct parse_context *ctx)
3456 ctx->command = command = &pi->cmds[pi->num_cmds]; 3508 ctx->command = command = &pi->cmds[pi->num_cmds];
3457 clear_and_ret: 3509 clear_and_ret:
3458 memset(command, 0, sizeof(*command)); 3510 memset(command, 0, sizeof(*command));
3511#if ENABLE_HUSH_LINENO_VAR
3512 command->lineno = G.lineno;
3513 debug_printf_parse("command->lineno = G.lineno (%u)\n", G.lineno);
3514#endif
3459 return pi->num_cmds; /* used only for 0/nonzero check */ 3515 return pi->num_cmds; /* used only for 0/nonzero check */
3460} 3516}
3461 3517
@@ -3643,9 +3699,9 @@ static const struct reserved_combo* match_reserved_word(o_string *word)
3643 } 3699 }
3644 return NULL; 3700 return NULL;
3645} 3701}
3646/* Return 0: not a keyword, 1: keyword 3702/* Return NULL: not a keyword, else: keyword
3647 */ 3703 */
3648static int reserved_word(o_string *word, struct parse_context *ctx) 3704static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx)
3649{ 3705{
3650# if ENABLE_HUSH_CASE 3706# if ENABLE_HUSH_CASE
3651 static const struct reserved_combo reserved_match = { 3707 static const struct reserved_combo reserved_match = {
@@ -3658,7 +3714,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
3658 return 0; 3714 return 0;
3659 r = match_reserved_word(word); 3715 r = match_reserved_word(word);
3660 if (!r) 3716 if (!r)
3661 return 0; 3717 return r; /* NULL */
3662 3718
3663 debug_printf("found reserved word %s, res %d\n", r->literal, r->res); 3719 debug_printf("found reserved word %s, res %d\n", r->literal, r->res);
3664# if ENABLE_HUSH_CASE 3720# if ENABLE_HUSH_CASE
@@ -3673,7 +3729,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
3673 ctx->ctx_res_w = RES_SNTX; 3729 ctx->ctx_res_w = RES_SNTX;
3674 } 3730 }
3675 ctx->ctx_inverted = 1; 3731 ctx->ctx_inverted = 1;
3676 return 1; 3732 return r;
3677 } 3733 }
3678 if (r->flag & FLAG_START) { 3734 if (r->flag & FLAG_START) {
3679 struct parse_context *old; 3735 struct parse_context *old;
@@ -3685,7 +3741,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
3685 } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { 3741 } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) {
3686 syntax_error_at(word->data); 3742 syntax_error_at(word->data);
3687 ctx->ctx_res_w = RES_SNTX; 3743 ctx->ctx_res_w = RES_SNTX;
3688 return 1; 3744 return r;
3689 } else { 3745 } else {
3690 /* "{...} fi" is ok. "{...} if" is not 3746 /* "{...} fi" is ok. "{...} if" is not
3691 * Example: 3747 * Example:
@@ -3735,7 +3791,7 @@ static int reserved_word(o_string *word, struct parse_context *ctx)
3735 *ctx = *old; /* physical copy */ 3791 *ctx = *old; /* physical copy */
3736 free(old); 3792 free(old);
3737 } 3793 }
3738 return 1; 3794 return r;
3739} 3795}
3740#endif /* HAS_KEYWORDS */ 3796#endif /* HAS_KEYWORDS */
3741 3797
@@ -3801,9 +3857,26 @@ static int done_word(o_string *word, struct parse_context *ctx)
3801 && ctx->ctx_res_w != RES_CASE 3857 && ctx->ctx_res_w != RES_CASE
3802# endif 3858# endif
3803 ) { 3859 ) {
3804 int reserved = reserved_word(word, ctx); 3860 const struct reserved_combo *reserved;
3805 debug_printf_parse("checking for reserved-ness: %d\n", reserved); 3861 reserved = reserved_word(word, ctx);
3862 debug_printf_parse("checking for reserved-ness: %d\n", !!reserved);
3806 if (reserved) { 3863 if (reserved) {
3864# if ENABLE_HUSH_LINENO_VAR
3865/* Case:
3866 * "while ...; do
3867 * cmd ..."
3868 * If we don't close the pipe _now_, immediately after "do", lineno logic
3869 * sees "cmd" as starting at "do" - i.e., at the previous line.
3870 */
3871 if (0
3872 IF_HUSH_IF(|| reserved->res == RES_THEN)
3873 IF_HUSH_IF(|| reserved->res == RES_ELIF)
3874 IF_HUSH_IF(|| reserved->res == RES_ELSE)
3875 IF_HUSH_LOOPS(|| reserved->res == RES_DO)
3876 ) {
3877 done_pipe(ctx, PIPE_SEQ);
3878 }
3879# endif
3807 o_reset_to_empty_unquoted(word); 3880 o_reset_to_empty_unquoted(word);
3808 debug_printf_parse("done_word return %d\n", 3881 debug_printf_parse("done_word return %d\n",
3809 (ctx->ctx_res_w == RES_SNTX)); 3882 (ctx->ctx_res_w == RES_SNTX));
@@ -3840,21 +3913,6 @@ static int done_word(o_string *word, struct parse_context *ctx)
3840 word->o_assignment = MAYBE_ASSIGNMENT; 3913 word->o_assignment = MAYBE_ASSIGNMENT;
3841 } 3914 }
3842 debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); 3915 debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]);
3843
3844 if (word->has_quoted_part
3845 /* optimization: and if it's ("" or '') or ($v... or `cmd`...): */
3846 && (word->data[0] == '\0' || word->data[0] == SPECIAL_VAR_SYMBOL)
3847 /* (otherwise it's known to be not empty and is already safe) */
3848 ) {
3849 /* exclude "$@" - it can expand to no word despite "" */
3850 char *p = word->data;
3851 while (p[0] == SPECIAL_VAR_SYMBOL
3852 && (p[1] & 0x7f) == '@'
3853 && p[2] == SPECIAL_VAR_SYMBOL
3854 ) {
3855 p += 3;
3856 }
3857 }
3858 command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); 3916 command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
3859 debug_print_strings("word appended to argv", command->argv); 3917 debug_print_strings("word appended to argv", command->argv);
3860 } 3918 }
@@ -4503,9 +4561,10 @@ static int parse_dollar(o_string *as_string,
4503 4561
4504 debug_printf_parse("parse_dollar entered: ch='%c'\n", ch); 4562 debug_printf_parse("parse_dollar entered: ch='%c'\n", ch);
4505 if (isalpha(ch)) { 4563 if (isalpha(ch)) {
4564 make_var:
4506 ch = i_getch(input); 4565 ch = i_getch(input);
4507 nommu_addchr(as_string, ch); 4566 nommu_addchr(as_string, ch);
4508 make_var: 4567 /*make_var1:*/
4509 o_addchr(dest, SPECIAL_VAR_SYMBOL); 4568 o_addchr(dest, SPECIAL_VAR_SYMBOL);
4510 while (1) { 4569 while (1) {
4511 debug_printf_parse(": '%c'\n", ch); 4570 debug_printf_parse(": '%c'\n", ch);
@@ -4698,19 +4757,22 @@ static int parse_dollar(o_string *as_string,
4698 } 4757 }
4699#endif 4758#endif
4700 case '_': 4759 case '_':
4760 goto make_var;
4761#if 0
4762 /* TODO: $_ and $-: */
4763 /* $_ Shell or shell script name; or last argument of last command
4764 * (if last command wasn't a pipe; if it was, bash sets $_ to "");
4765 * but in command's env, set to full pathname used to invoke it */
4766 /* $- Option flags set by set builtin or shell options (-i etc) */
4701 ch = i_getch(input); 4767 ch = i_getch(input);
4702 nommu_addchr(as_string, ch); 4768 nommu_addchr(as_string, ch);
4703 ch = i_peek_and_eat_bkslash_nl(input); 4769 ch = i_peek_and_eat_bkslash_nl(input);
4704 if (isalnum(ch)) { /* it's $_name or $_123 */ 4770 if (isalnum(ch)) { /* it's $_name or $_123 */
4705 ch = '_'; 4771 ch = '_';
4706 goto make_var; 4772 goto make_var1;
4707 } 4773 }
4708 /* else: it's $_ */ 4774 /* else: it's $_ */
4709 /* TODO: $_ and $-: */ 4775#endif
4710 /* $_ Shell or shell script name; or last argument of last command
4711 * (if last command wasn't a pipe; if it was, bash sets $_ to "");
4712 * but in command's env, set to full pathname used to invoke it */
4713 /* $- Option flags set by set builtin or shell options (-i etc) */
4714 default: 4776 default:
4715 o_addQchr(dest, '$'); 4777 o_addQchr(dest, '$');
4716 } 4778 }
@@ -4914,7 +4976,8 @@ static struct pipe *parse_stream(char **pstring,
4914 next = i_peek(input); 4976 next = i_peek(input);
4915 4977
4916 is_special = "{}<>;&|()#'" /* special outside of "str" */ 4978 is_special = "{}<>;&|()#'" /* special outside of "str" */
4917 "\\$\"" IF_HUSH_TICK("`"); /* always special */ 4979 "\\$\"" IF_HUSH_TICK("`") /* always special */
4980 SPECIAL_VAR_SYMBOL_STR;
4918 /* Are { and } special here? */ 4981 /* Are { and } special here? */
4919 if (ctx.command->argv /* word [word]{... - non-special */ 4982 if (ctx.command->argv /* word [word]{... - non-special */
4920 || dest.length /* word{... - non-special */ 4983 || dest.length /* word{... - non-special */
@@ -4948,6 +5011,22 @@ static struct pipe *parse_stream(char **pstring,
4948 } 5011 }
4949 5012
4950 if (is_blank) { 5013 if (is_blank) {
5014#if ENABLE_HUSH_LINENO_VAR
5015/* Case:
5016 * "while ...; do<whitespace><newline>
5017 * cmd ..."
5018 * would think that "cmd" starts in <whitespace> -
5019 * i.e., at the previous line.
5020 * We need to skip all whitespace before newlines.
5021 */
5022 while (ch != '\n') {
5023 next = i_peek(input);
5024 if (next != ' ' && next != '\t' && next != '\n')
5025 break; /* next char is not ws */
5026 ch = i_getch(input);
5027 }
5028 /* ch == last eaten whitespace char */
5029#endif
4951 if (done_word(&dest, &ctx)) { 5030 if (done_word(&dest, &ctx)) {
4952 goto parse_error; 5031 goto parse_error;
4953 } 5032 }
@@ -5186,8 +5265,14 @@ static struct pipe *parse_stream(char **pstring,
5186 /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ 5265 /* Note: nommu_addchr(&ctx.as_string, ch) is already done */
5187 5266
5188 switch (ch) { 5267 switch (ch) {
5189 case '#': /* non-comment #: "echo a#b" etc */ 5268 case SPECIAL_VAR_SYMBOL:
5190 o_addQchr(&dest, ch); 5269 /* Convert raw ^C to corresponding special variable reference */
5270 o_addchr(&dest, SPECIAL_VAR_SYMBOL);
5271 o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
5272 /* fall through */
5273 case '#':
5274 /* non-comment #: "echo a#b" etc */
5275 o_addchr(&dest, ch);
5191 break; 5276 break;
5192 case '\\': 5277 case '\\':
5193 if (next == EOF) { 5278 if (next == EOF) {
@@ -5229,6 +5314,11 @@ static struct pipe *parse_stream(char **pstring,
5229 nommu_addchr(&ctx.as_string, ch); 5314 nommu_addchr(&ctx.as_string, ch);
5230 if (ch == '\'') 5315 if (ch == '\'')
5231 break; 5316 break;
5317 if (ch == SPECIAL_VAR_SYMBOL) {
5318 /* Convert raw ^C to corresponding special variable reference */
5319 o_addchr(&dest, SPECIAL_VAR_SYMBOL);
5320 o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS);
5321 }
5232 o_addqchr(&dest, ch); 5322 o_addqchr(&dest, ch);
5233 } 5323 }
5234 } 5324 }
@@ -5534,7 +5624,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha
5534static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash) 5624static char *encode_then_expand_string(const char *str, int process_bkslash, int do_unbackslash)
5535{ 5625{
5536#if !BASH_PATTERN_SUBST 5626#if !BASH_PATTERN_SUBST
5537 const int do_unbackslash = 1; 5627 enum { do_unbackslash = 1 };
5538#endif 5628#endif
5539 char *exp_str; 5629 char *exp_str;
5540 struct in_str input; 5630 struct in_str input;
@@ -5610,6 +5700,10 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
5610 unsigned res_len = 0; 5700 unsigned res_len = 0;
5611 unsigned repl_len = strlen(repl); 5701 unsigned repl_len = strlen(repl);
5612 5702
5703 /* Null pattern never matches, including if "var" is empty */
5704 if (!pattern[0])
5705 return result; /* NULL, no replaces happened */
5706
5613 while (1) { 5707 while (1) {
5614 int size; 5708 int size;
5615 char *s = strstr_pattern(val, pattern, &size); 5709 char *s = strstr_pattern(val, pattern, &size);
@@ -5640,9 +5734,9 @@ static char *replace_pattern(char *val, const char *pattern, const char *repl, c
5640 */ 5734 */
5641static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp) 5735static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, char **pp)
5642{ 5736{
5643 const char *val = NULL; 5737 const char *val;
5644 char *to_be_freed = NULL; 5738 char *to_be_freed;
5645 char *p = *pp; 5739 char *p;
5646 char *var; 5740 char *var;
5647 char first_char; 5741 char first_char;
5648 char exp_op; 5742 char exp_op;
@@ -5651,6 +5745,9 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5651 char *exp_word = exp_word; /* for compiler */ 5745 char *exp_word = exp_word; /* for compiler */
5652 char arg0; 5746 char arg0;
5653 5747
5748 val = NULL;
5749 to_be_freed = NULL;
5750 p = *pp;
5654 *p = '\0'; /* replace trailing SPECIAL_VAR_SYMBOL */ 5751 *p = '\0'; /* replace trailing SPECIAL_VAR_SYMBOL */
5655 var = arg; 5752 var = arg;
5656 exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL; 5753 exp_saveptr = arg[1] ? strchr(VAR_ENCODED_SUBST_OPS, arg[1]) : NULL;
@@ -5773,8 +5870,6 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5773 * and if // is used, it is encoded as \: 5870 * and if // is used, it is encoded as \:
5774 * var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL> 5871 * var\pattern<SPECIAL_VAR_SYMBOL>repl<SPECIAL_VAR_SYMBOL>
5775 */ 5872 */
5776 /* Empty variable always gives nothing: */
5777 // "v=''; echo ${v/*/w}" prints "", not "w"
5778 if (val && val[0]) { 5873 if (val && val[0]) {
5779 /* pattern uses non-standard expansion. 5874 /* pattern uses non-standard expansion.
5780 * repl should be unbackslashed and globbed 5875 * repl should be unbackslashed and globbed
@@ -5810,6 +5905,13 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha
5810 val = to_be_freed; 5905 val = to_be_freed;
5811 free(pattern); 5906 free(pattern);
5812 free(repl); 5907 free(repl);
5908 } else {
5909 /* Empty variable always gives nothing */
5910 // "v=''; echo ${v/*/w}" prints "", not "w"
5911 /* Just skip "replace" part */
5912 *p++ = SPECIAL_VAR_SYMBOL;
5913 p = strchr(p, SPECIAL_VAR_SYMBOL);
5914 *p = '\0';
5813 } 5915 }
5814 } 5916 }
5815#endif /* BASH_PATTERN_SUBST */ 5917#endif /* BASH_PATTERN_SUBST */
@@ -6041,6 +6143,11 @@ static NOINLINE int expand_vars_to_list(o_string *output, int n, char *arg)
6041 arg++; 6143 arg++;
6042 cant_be_null = 0x80; 6144 cant_be_null = 0x80;
6043 break; 6145 break;
6146 case SPECIAL_VAR_QUOTED_SVS:
6147 /* <SPECIAL_VAR_SYMBOL><SPECIAL_VAR_QUOTED_SVS><SPECIAL_VAR_SYMBOL> */
6148 arg++;
6149 val = SPECIAL_VAR_SYMBOL_STR;
6150 break;
6044#if ENABLE_HUSH_TICK 6151#if ENABLE_HUSH_TICK
6045 case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */ 6152 case '`': /* <SPECIAL_VAR_SYMBOL>`cmd<SPECIAL_VAR_SYMBOL> */
6046 *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */ 6153 *p = '\0'; /* replace trailing <SPECIAL_VAR_SYMBOL> */
@@ -6199,7 +6306,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash)
6199 return (char*)list; 6306 return (char*)list;
6200} 6307}
6201 6308
6202/* Used for "eval" builtin and case string */ 6309#if ENABLE_HUSH_CASE
6203static char* expand_strvec_to_string(char **argv) 6310static char* expand_strvec_to_string(char **argv)
6204{ 6311{
6205 char **list; 6312 char **list;
@@ -6221,6 +6328,7 @@ static char* expand_strvec_to_string(char **argv)
6221 debug_printf_expand("strvec_to_string='%s'\n", (char*)list); 6328 debug_printf_expand("strvec_to_string='%s'\n", (char*)list);
6222 return (char*)list; 6329 return (char*)list;
6223} 6330}
6331#endif
6224 6332
6225static char **expand_assignments(char **argv, int count) 6333static char **expand_assignments(char **argv, int count)
6226{ 6334{
@@ -6513,8 +6621,17 @@ static void parse_and_run_string(const char *s)
6513static void parse_and_run_file(FILE *f) 6621static void parse_and_run_file(FILE *f)
6514{ 6622{
6515 struct in_str input; 6623 struct in_str input;
6624#if ENABLE_HUSH_LINENO_VAR
6625 unsigned sv;
6626
6627 sv = G.lineno;
6628 G.lineno = 1;
6629#endif
6516 setup_file_in_str(&input, f); 6630 setup_file_in_str(&input, f);
6517 parse_and_run_stream(&input, ';'); 6631 parse_and_run_stream(&input, ';');
6632#if ENABLE_HUSH_LINENO_VAR
6633 G.lineno = sv;
6634#endif
6518} 6635}
6519 6636
6520#if ENABLE_HUSH_TICK 6637#if ENABLE_HUSH_TICK
@@ -7330,6 +7447,32 @@ static void dump_cmd_in_x_mode(char **argv)
7330# define dump_cmd_in_x_mode(argv) ((void)0) 7447# define dump_cmd_in_x_mode(argv) ((void)0)
7331#endif 7448#endif
7332 7449
7450#if ENABLE_HUSH_COMMAND
7451static void if_command_vV_print_and_exit(char opt_vV, char *cmd, const char *explanation)
7452{
7453 char *to_free;
7454
7455 if (!opt_vV)
7456 return;
7457
7458 to_free = NULL;
7459 if (!explanation) {
7460 char *path = getenv("PATH");
7461 explanation = to_free = find_executable(cmd, &path); /* path == NULL is ok */
7462 if (!explanation)
7463 _exit(1); /* PROG was not found */
7464 if (opt_vV != 'V')
7465 cmd = to_free; /* -v PROG prints "/path/to/PROG" */
7466 }
7467 printf((opt_vV == 'V') ? "%s is %s\n" : "%s\n", cmd, explanation);
7468 free(to_free);
7469 fflush_all();
7470 _exit(0);
7471}
7472#else
7473# define if_command_vV_print_and_exit(a,b,c) ((void)0)
7474#endif
7475
7333#if BB_MMU 7476#if BB_MMU
7334#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \ 7477#define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
7335 pseudo_exec_argv(argv, assignment_cnt, argv_expanded) 7478 pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -7350,7 +7493,11 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7350 char **argv, int assignment_cnt, 7493 char **argv, int assignment_cnt,
7351 char **argv_expanded) 7494 char **argv_expanded)
7352{ 7495{
7496 const struct built_in_command *x;
7353 char **new_env; 7497 char **new_env;
7498#if ENABLE_HUSH_COMMAND
7499 char opt_vV = 0;
7500#endif
7354 7501
7355 new_env = expand_assignments(argv, assignment_cnt); 7502 new_env = expand_assignments(argv, assignment_cnt);
7356 dump_cmd_in_x_mode(new_env); 7503 dump_cmd_in_x_mode(new_env);
@@ -7399,21 +7546,58 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7399 } 7546 }
7400#endif 7547#endif
7401 7548
7549#if ENABLE_HUSH_COMMAND
7550 /* "command BAR": run BAR without looking it up among functions
7551 * "command -v BAR": print "BAR" or "/path/to/BAR"; or exit 1
7552 * "command -V BAR": print "BAR is {a function,a shell builtin,/path/to/BAR}"
7553 */
7554 while (strcmp(argv[0], "command") == 0 && argv[1]) {
7555 char *p;
7556
7557 argv++;
7558 p = *argv;
7559 if (p[0] != '-' || !p[1])
7560 continue; /* bash allows "command command command [-OPT] BAR" */
7561
7562 for (;;) {
7563 p++;
7564 switch (*p) {
7565 case '\0':
7566 argv++;
7567 p = *argv;
7568 if (p[0] != '-' || !p[1])
7569 goto after_opts;
7570 continue; /* next arg is also -opts, process it too */
7571 case 'v':
7572 case 'V':
7573 opt_vV = *p;
7574 continue;
7575 default:
7576 bb_error_msg_and_die("%s: %s: invalid option", "command", argv[0]);
7577 }
7578 }
7579 }
7580 after_opts:
7581# if ENABLE_HUSH_FUNCTIONS
7582 if (opt_vV && find_function(argv[0]))
7583 if_command_vV_print_and_exit(opt_vV, argv[0], "a function");
7584# endif
7585#endif
7586
7402 /* Check if the command matches any of the builtins. 7587 /* Check if the command matches any of the builtins.
7403 * Depending on context, this might be redundant. But it's 7588 * Depending on context, this might be redundant. But it's
7404 * easier to waste a few CPU cycles than it is to figure out 7589 * easier to waste a few CPU cycles than it is to figure out
7405 * if this is one of those cases. 7590 * if this is one of those cases.
7406 */ 7591 */
7407 { 7592 /* Why "BB_MMU ? :" difference in logic? -
7408 /* On NOMMU, it is more expensive to re-execute shell 7593 * On NOMMU, it is more expensive to re-execute shell
7409 * just in order to run echo or test builtin. 7594 * just in order to run echo or test builtin.
7410 * It's better to skip it here and run corresponding 7595 * It's better to skip it here and run corresponding
7411 * non-builtin later. */ 7596 * non-builtin later. */
7412 const struct built_in_command *x; 7597 x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]);
7413 x = BB_MMU ? find_builtin(argv[0]) : find_builtin1(argv[0]); 7598 if (x) {
7414 if (x) { 7599 if_command_vV_print_and_exit(opt_vV, argv[0], "a shell builtin");
7415 exec_builtin(&nommu_save->argv_from_re_execing, x, argv); 7600 exec_builtin(&nommu_save->argv_from_re_execing, x, argv);
7416 }
7417 } 7601 }
7418 7602
7419#if ENABLE_FEATURE_SH_STANDALONE 7603#if ENABLE_FEATURE_SH_STANDALONE
@@ -7421,6 +7605,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7421 { 7605 {
7422 int a = find_applet_by_name(argv[0]); 7606 int a = find_applet_by_name(argv[0]);
7423 if (a >= 0) { 7607 if (a >= 0) {
7608 if_command_vV_print_and_exit(opt_vV, argv[0], "an applet");
7424# if BB_MMU /* see above why on NOMMU it is not allowed */ 7609# if BB_MMU /* see above why on NOMMU it is not allowed */
7425 if (APPLET_IS_NOEXEC(a)) { 7610 if (APPLET_IS_NOEXEC(a)) {
7426 /* Do not leak open fds from opened script files etc. 7611 /* Do not leak open fds from opened script files etc.
@@ -7450,6 +7635,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save,
7450#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU 7635#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
7451 skip: 7636 skip:
7452#endif 7637#endif
7638 if_command_vV_print_and_exit(opt_vV, argv[0], NULL);
7453 execvp_or_die(argv); 7639 execvp_or_die(argv);
7454} 7640}
7455 7641
@@ -7992,6 +8178,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
7992 char **new_env = NULL; 8178 char **new_env = NULL;
7993 struct variable *old_vars = NULL; 8179 struct variable *old_vars = NULL;
7994 8180
8181#if ENABLE_HUSH_LINENO_VAR
8182 if (G.lineno_var)
8183 strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
8184#endif
8185
7995 if (argv[command->assignment_cnt] == NULL) { 8186 if (argv[command->assignment_cnt] == NULL) {
7996 /* Assignments, but no command */ 8187 /* Assignments, but no command */
7997 /* Ensure redirects take effect (that is, create files). 8188 /* Ensure redirects take effect (that is, create files).
@@ -8139,7 +8330,7 @@ static NOINLINE int run_pipe(struct pipe *pi)
8139 return rcode; 8330 return rcode;
8140 } 8331 }
8141 8332
8142 if (ENABLE_FEATURE_SH_NOFORK) { 8333 if (ENABLE_FEATURE_SH_NOFORK && NUM_APPLETS > 1) {
8143 int n = find_applet_by_name(argv_expanded[0]); 8334 int n = find_applet_by_name(argv_expanded[0]);
8144 if (n >= 0 && APPLET_IS_NOFORK(n)) { 8335 if (n >= 0 && APPLET_IS_NOFORK(n)) {
8145 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded); 8336 rcode = redirect_and_varexp_helper(&new_env, &old_vars, command, &squirrel, argv_expanded);
@@ -8196,6 +8387,11 @@ static NOINLINE int run_pipe(struct pipe *pi)
8196 if (cmd_no < pi->num_cmds) 8387 if (cmd_no < pi->num_cmds)
8197 xpiped_pair(pipefds); 8388 xpiped_pair(pipefds);
8198 8389
8390#if ENABLE_HUSH_LINENO_VAR
8391 if (G.lineno_var)
8392 strcpy(G.lineno_var + sizeof("LINENO=")-1, utoa(command->lineno));
8393#endif
8394
8199 command->pid = BB_MMU ? fork() : vfork(); 8395 command->pid = BB_MMU ? fork() : vfork();
8200 if (!command->pid) { /* child */ 8396 if (!command->pid) { /* child */
8201#if ENABLE_HUSH_JOB 8397#if ENABLE_HUSH_JOB
@@ -8387,7 +8583,10 @@ static int run_list(struct pipe *pi)
8387 rword, cond_code, last_rword); 8583 rword, cond_code, last_rword);
8388 8584
8389 sv_errexit_depth = G.errexit_depth; 8585 sv_errexit_depth = G.errexit_depth;
8390 if (IF_HAS_KEYWORDS(rword == RES_IF || rword == RES_ELIF ||) 8586 if (
8587#if ENABLE_HUSH_IF
8588 rword == RES_IF || rword == RES_ELIF ||
8589#endif
8391 pi->followup != PIPE_SEQ 8590 pi->followup != PIPE_SEQ
8392 ) { 8591 ) {
8393 G.errexit_depth++; 8592 G.errexit_depth++;
@@ -8828,17 +9027,19 @@ int hush_main(int argc, char **argv)
8828#if !BB_MMU 9027#if !BB_MMU
8829 G.argv0_for_re_execing = argv[0]; 9028 G.argv0_for_re_execing = argv[0];
8830#endif 9029#endif
9030
8831 /* Deal with HUSH_VERSION */ 9031 /* Deal with HUSH_VERSION */
9032 debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
9033 unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
8832 shell_ver = xzalloc(sizeof(*shell_ver)); 9034 shell_ver = xzalloc(sizeof(*shell_ver));
8833 shell_ver->flg_export = 1; 9035 shell_ver->flg_export = 1;
8834 shell_ver->flg_read_only = 1; 9036 shell_ver->flg_read_only = 1;
8835 /* Code which handles ${var<op>...} needs writable values for all variables, 9037 /* Code which handles ${var<op>...} needs writable values for all variables,
8836 * therefore we xstrdup: */ 9038 * therefore we xstrdup: */
8837 shell_ver->varstr = xstrdup(hush_version_str); 9039 shell_ver->varstr = xstrdup(hush_version_str);
9040
8838 /* Create shell local variables from the values 9041 /* Create shell local variables from the values
8839 * currently living in the environment */ 9042 * currently living in the environment */
8840 debug_printf_env("unsetenv '%s'\n", "HUSH_VERSION");
8841 unsetenv("HUSH_VERSION"); /* in case it exists in initial env */
8842 G.top_var = shell_ver; 9043 G.top_var = shell_ver;
8843 cur_var = G.top_var; 9044 cur_var = G.top_var;
8844 e = environ; 9045 e = environ;
@@ -8904,6 +9105,14 @@ int hush_main(int argc, char **argv)
8904 */ 9105 */
8905#endif 9106#endif
8906 9107
9108#if ENABLE_HUSH_LINENO_VAR
9109 if (ENABLE_HUSH_LINENO_VAR) {
9110 char *p = xasprintf("LINENO=%*s", (int)(sizeof(int)*3), "");
9111 set_local_var(p, /*flags*/ 0);
9112 G.lineno_var = p; /* can't assign before set_local_var("LINENO=...") */
9113 }
9114#endif
9115
8907#if ENABLE_FEATURE_EDITING 9116#if ENABLE_FEATURE_EDITING
8908 G.line_input_state = new_line_input_t(FOR_SHELL); 9117 G.line_input_state = new_line_input_t(FOR_SHELL);
8909#endif 9118#endif
@@ -9346,13 +9555,34 @@ static int FAST_FUNC builtin_eval(char **argv)
9346 int rcode = EXIT_SUCCESS; 9555 int rcode = EXIT_SUCCESS;
9347 9556
9348 argv = skip_dash_dash(argv); 9557 argv = skip_dash_dash(argv);
9349 if (*argv) { 9558 if (argv[0]) {
9350 char *str = expand_strvec_to_string(argv); 9559 char *str = NULL;
9560
9561 if (argv[1]) {
9562 /* "The eval utility shall construct a command by
9563 * concatenating arguments together, separating
9564 * each with a <space> character."
9565 */
9566 char *p;
9567 unsigned len = 0;
9568 char **pp = argv;
9569 do
9570 len += strlen(*pp) + 1;
9571 while (*++pp);
9572 str = p = xmalloc(len);
9573 pp = argv;
9574 do {
9575 p = stpcpy(p, *pp);
9576 *p++ = ' ';
9577 } while (*++pp);
9578 p[-1] = '\0';
9579 }
9580
9351 /* bash: 9581 /* bash:
9352 * eval "echo Hi; done" ("done" is syntax error): 9582 * eval "echo Hi; done" ("done" is syntax error):
9353 * "echo Hi" will not execute too. 9583 * "echo Hi" will not execute too.
9354 */ 9584 */
9355 parse_and_run_string(str); 9585 parse_and_run_string(str ? str : argv[0]);
9356 free(str); 9586 free(str);
9357 rcode = G.last_exitcode; 9587 rcode = G.last_exitcode;
9358 } 9588 }
@@ -9855,7 +10085,7 @@ static int FAST_FUNC builtin_set(char **argv)
9855 10085
9856 /* Nothing known, so abort */ 10086 /* Nothing known, so abort */
9857 error: 10087 error:
9858 bb_error_msg("set: %s: invalid option", arg); 10088 bb_error_msg("%s: %s: invalid option", "set", arg);
9859 return EXIT_FAILURE; 10089 return EXIT_FAILURE;
9860} 10090}
9861#endif 10091#endif
@@ -10038,6 +10268,11 @@ static int FAST_FUNC builtin_source(char **argv)
10038 arg_path = find_in_path(filename); 10268 arg_path = find_in_path(filename);
10039 if (arg_path) 10269 if (arg_path)
10040 filename = arg_path; 10270 filename = arg_path;
10271 else if (!ENABLE_HUSH_BASH_SOURCE_CURDIR) {
10272 errno = ENOENT;
10273 bb_simple_perror_msg(filename);
10274 return EXIT_FAILURE;
10275 }
10041 } 10276 }
10042 input = remember_FILE(fopen_or_warn(filename, "r")); 10277 input = remember_FILE(fopen_or_warn(filename, "r"));
10043 free(arg_path); 10278 free(arg_path);
diff --git a/shell/hush_test/hush-arith/arith-postinc.right b/shell/hush_test/hush-arith/arith-postinc.right
new file mode 100644
index 000000000..c95ce02bf
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-postinc.right
@@ -0,0 +1,5 @@
11 1
21 1
31 1
41 1
5Ok:0
diff --git a/shell/hush_test/hush-arith/arith-postinc.tests b/shell/hush_test/hush-arith/arith-postinc.tests
new file mode 100755
index 000000000..3fd9bfed5
--- /dev/null
+++ b/shell/hush_test/hush-arith/arith-postinc.tests
@@ -0,0 +1,5 @@
1echo 1 $((0++1))
2echo 1 $((0--1))
3x=-1; echo 1 $((0-$x))
4x=+1; echo 1 $((0+$x))
5echo Ok:$?
diff --git a/shell/hush_test/hush-arith/arith.right b/shell/hush_test/hush-arith/arith.right
index 8a201fb3b..c48e468a5 100644
--- a/shell/hush_test/hush-arith/arith.right
+++ b/shell/hush_test/hush-arith/arith.right
@@ -135,6 +135,10 @@ hush: arithmetic syntax error
135hush: arithmetic syntax error 135hush: arithmetic syntax error
1365 5 1365 5
1371 1 1371 1
1386 6
1392 2
1403 3
1411 1
1384 4 1424 4
1390 0 1430 0
140hush: arithmetic syntax error 144hush: arithmetic syntax error
diff --git a/shell/hush_test/hush-arith/arith2.sub b/shell/hush_test/hush-arith/arith2.sub
index f7e3c9235..9105059db 100755
--- a/shell/hush_test/hush-arith/arith2.sub
+++ b/shell/hush_test/hush-arith/arith2.sub
@@ -23,14 +23,14 @@
23echo 5 $(( 4 + ++a )) 23echo 5 $(( 4 + ++a ))
24echo 1 $a 24echo 1 $a
25 25
26# ash doesn't handle it right... 26# this is treated as 4 + ++a
27#ash# echo 6 $(( 4+++a )) 27echo 6 $(( 4+++a ))
28#ash# echo 2 $a 28echo 2 $a
29 a=2 29 a=2
30 30
31# ash doesn't handle it right... 31# this is treated as 4 - --a
32#ash# echo 3 $(( 4---a )) 32echo 3 $(( 4---a ))
33#ash# echo 1 $a 33echo 1 $a
34 a=1 34 a=1
35 35
36echo 4 $(( 4 - -- a )) 36echo 4 $(( 4 - -- a ))
diff --git a/shell/hush_test/hush-misc/command2.right b/shell/hush_test/hush-misc/command2.right
new file mode 100644
index 000000000..e3214f0a9
--- /dev/null
+++ b/shell/hush_test/hush-misc/command2.right
@@ -0,0 +1,2 @@
1test1
2hush: can't execute './test2.sh': Permission denied
diff --git a/shell/hush_test/hush-misc/command2.tests b/shell/hush_test/hush-misc/command2.tests
new file mode 100755
index 000000000..9d9de9a89
--- /dev/null
+++ b/shell/hush_test/hush-misc/command2.tests
@@ -0,0 +1,6 @@
1echo "echo test1; ./test2.sh" >test1.sh
2echo "echo test2" >test2.sh
3
4command . ./test1.sh
5
6rm -f test1.sh test2.sh
diff --git a/shell/hush_test/hush-misc/control_char1.right b/shell/hush_test/hush-misc/control_char1.right
new file mode 100644
index 000000000..6f8c2533c
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char1.right
@@ -0,0 +1,3 @@
1
2b#c
3Done:0
diff --git a/shell/hush_test/hush-misc/control_char1.tests b/shell/hush_test/hush-misc/control_char1.tests
new file mode 100755
index 000000000..0cfe60141
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char1.tests
@@ -0,0 +1,3 @@
1echo 
2echo 'b#c'
3echo Done:$?
diff --git a/shell/hush_test/hush-misc/control_char2.right b/shell/hush_test/hush-misc/control_char2.right
new file mode 100644
index 000000000..9498b420d
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char2.right
@@ -0,0 +1,2 @@
1
2Done:0
diff --git a/shell/hush_test/hush-misc/control_char2.tests b/shell/hush_test/hush-misc/control_char2.tests
new file mode 100755
index 000000000..e77d7a1a6
--- /dev/null
+++ b/shell/hush_test/hush-misc/control_char2.tests
@@ -0,0 +1,3 @@
1c=`printf '\3'`
2eval "echo $c"
3echo Done:$?
diff --git a/shell/hush_test/hush-misc/for_with_bslashes.right b/shell/hush_test/hush-misc/for_with_bslashes.right
index 02d96692c..cd8501050 100644
--- a/shell/hush_test/hush-misc/for_with_bslashes.right
+++ b/shell/hush_test/hush-misc/for_with_bslashes.right
@@ -5,4 +5,5 @@ b"c
5b'c 5b'c
6b$c 6b$c
7b`true`c 7b`true`c
8b#c
8Zero:0 9Zero:0
diff --git a/shell/hush_test/hush-misc/for_with_bslashes.tests b/shell/hush_test/hush-misc/for_with_bslashes.tests
index 363f3d85b..8acd9808a 100755
--- a/shell/hush_test/hush-misc/for_with_bslashes.tests
+++ b/shell/hush_test/hush-misc/for_with_bslashes.tests
@@ -1,9 +1,5 @@
1# UNFIXED BUG. 1# last word contains ^C character.
2# commented-out words contain ^C character. 2for a in 'a' 'b\c' 'b\\c' 'b"c' "b'c" 'b$c' 'b`true`c' 'b#c'
3# It's a SPECIAL_VAR_SYMBOL, for now hush does not escape it.
4# When it is fixed, update this test.
5
6for a in 'a' 'b\c' 'b\\c' 'b"c' "b'c" 'b$c' 'b`true`c' ### 'b#c'
7do 3do
8 echo $a 4 echo $a
9done 5done
diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.right b/shell/hush_test/hush-vars/param_expand_bash_substring.right
index 2f4c51d06..a3cb549f7 100644
--- a/shell/hush_test/hush-vars/param_expand_bash_substring.right
+++ b/shell/hush_test/hush-vars/param_expand_bash_substring.right
@@ -3,6 +3,7 @@ hush: syntax error: unterminated ${name}
3hush: syntax error: unterminated ${name} 3hush: syntax error: unterminated ${name}
4hush: syntax error: unterminated ${name} 4hush: syntax error: unterminated ${name}
50123456789 50123456789
60
61 =|| 71 =||
71:1 =|| 81:1 =||
81:1:2=|| 91:1:2=||
diff --git a/shell/hush_test/hush-vars/param_expand_bash_substring.tests b/shell/hush_test/hush-vars/param_expand_bash_substring.tests
index cce9f123e..512da351b 100755
--- a/shell/hush_test/hush-vars/param_expand_bash_substring.tests
+++ b/shell/hush_test/hush-vars/param_expand_bash_substring.tests
@@ -11,7 +11,7 @@ export var=0123456789
11"$THIS_SH" -c 'echo ${var:}' SHELL 11"$THIS_SH" -c 'echo ${var:}' SHELL
12 12
13# then some funky ones 13# then some funky ones
14# UNFIXED BUG: this should work: "$THIS_SH" -c 'echo ${?:0}' 14"$THIS_SH" -c 'echo ${?:0}' SHELL
15 15
16# now some valid ones 16# now some valid ones
17set --; echo "1 =|${1}|" 17set --; echo "1 =|${1}|"
diff --git a/shell/hush_test/hush-vars/var_LINENO1.right b/shell/hush_test/hush-vars/var_LINENO1.right
new file mode 100644
index 000000000..31e1a4478
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_LINENO1.right
@@ -0,0 +1,8 @@
12:2
23:3
34:4
45:5
52:2
63:3
74:4
85:5
diff --git a/shell/hush_test/hush-vars/var_LINENO1.tests b/shell/hush_test/hush-vars/var_LINENO1.tests
new file mode 100755
index 000000000..851b52cf5
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_LINENO1.tests
@@ -0,0 +1,6 @@
1env | grep LINENO
2echo 2:$LINENO
3echo 3:$LINENO >&2 \
4| { sleep 0.1; echo 4:$LINENO; }
5echo 5:$LINENO
6test "$1" || . ./var_LINENO1.tests norepeat
diff --git a/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.right b/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.right
new file mode 100644
index 000000000..d400a7e31
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.right
@@ -0,0 +1,2 @@
1v
2Ok:0
diff --git a/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.tests b/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.tests
new file mode 100755
index 000000000..6e8aa2afa
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_repl_empty_pattern.tests
@@ -0,0 +1,3 @@
1v=v
2echo ${v//}
3echo Ok:$?
diff --git a/shell/hush_test/hush-vars/var_bash_repl_empty_var.right b/shell/hush_test/hush-vars/var_bash_repl_empty_var.right
new file mode 100644
index 000000000..892916783
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_repl_empty_var.right
@@ -0,0 +1,2 @@
1
2Ok:0
diff --git a/shell/hush_test/hush-vars/var_bash_repl_empty_var.tests b/shell/hush_test/hush-vars/var_bash_repl_empty_var.tests
new file mode 100755
index 000000000..73a43d38e
--- /dev/null
+++ b/shell/hush_test/hush-vars/var_bash_repl_empty_var.tests
@@ -0,0 +1,3 @@
1v=''
2echo ${v/*/w}
3echo Ok:$?
diff --git a/shell/math.c b/shell/math.c
index f01f24362..611b3beab 100644
--- a/shell/math.c
+++ b/shell/math.c
@@ -598,10 +598,24 @@ evaluate_string(arith_state_t *math_state, const char *expr)
598 } 598 }
599 599
600 /* Should be an operator */ 600 /* Should be an operator */
601
602 /* Special case: NUM-- and NUM++ are not recognized if NUM
603 * is a literal number, not a variable. IOW:
604 * "a+++v" is a++ + v.
605 * "7+++v" is 7 + ++v, not 7++ + v.
606 */
607 if (lasttok == TOK_NUM && !numstackptr[-1].var /* number literal */
608 && (expr[0] == '+' || expr[0] == '-')
609 && (expr[1] == expr[0])
610 ) {
611 //bb_error_msg("special %c%c", expr[0], expr[0]);
612 op = (expr[0] == '+' ? TOK_ADD : TOK_SUB);
613 expr += 1;
614 goto tok_found1;
615 }
616
601 p = op_tokens; 617 p = op_tokens;
602 while (1) { 618 while (1) {
603// TODO: bash allows 7+++v, treats it as 7 + ++v
604// we treat it as 7++ + v and reject
605 /* Compare expr to current op_tokens[] element */ 619 /* Compare expr to current op_tokens[] element */
606 const char *e = expr; 620 const char *e = expr;
607 while (1) { 621 while (1) {
@@ -627,6 +641,7 @@ evaluate_string(arith_state_t *math_state, const char *expr)
627 } 641 }
628 tok_found: 642 tok_found:
629 op = p[1]; /* fetch TOK_foo value */ 643 op = p[1]; /* fetch TOK_foo value */
644 tok_found1:
630 /* NB: expr now points past the operator */ 645 /* NB: expr now points past the operator */
631 646
632 /* post grammar: a++ reduce to num */ 647 /* post grammar: a++ reduce to num */