diff options
author | Ron Yorston <rmy@pobox.com> | 2018-09-10 14:37:07 +0100 |
---|---|---|
committer | Ron Yorston <rmy@pobox.com> | 2018-09-10 14:59:33 +0100 |
commit | d89ced75b204f0eb5611f522864beb81d1b393f5 (patch) | |
tree | 5daa31427e287fe079a0ef551097753773fdb266 /shell | |
parent | f72845d9332fa6311a46dbcad3180d5008182982 (diff) | |
parent | 05b18065ab9c375f6185b65a3631d4c6cc1a4be9 (diff) | |
download | busybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.tar.gz busybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.tar.bz2 busybox-w32-d89ced75b204f0eb5611f522864beb81d1b393f5.zip |
Merge branch 'busybox' into merge
Diffstat (limited to 'shell')
27 files changed, 427 insertions, 195 deletions
diff --git a/shell/ash.c b/shell/ash.c index 4bd1c2c9d..7131609e4 100644 --- a/shell/ash.c +++ b/shell/ash.c | |||
@@ -6272,7 +6272,7 @@ static int substr_atoi(const char *s) | |||
6272 | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ | 6272 | #define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ |
6273 | #define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ | 6273 | #define EXP_VARTILDE2 0x20 /* expand tildes after colons only */ |
6274 | #define EXP_WORD 0x40 /* expand word in parameter expansion */ | 6274 | #define EXP_WORD 0x40 /* expand word in parameter expansion */ |
6275 | #define EXP_QUOTED 0x80 /* expand word in double quotes */ | 6275 | #define EXP_QUOTED 0x100 /* expand word in double quotes */ |
6276 | /* | 6276 | /* |
6277 | * rmescape() flags | 6277 | * rmescape() flags |
6278 | */ | 6278 | */ |
@@ -6606,9 +6606,7 @@ memtodest(const char *p, size_t len, int syntax, int quotes) | |||
6606 | if (quotes & QUOTES_ESC) { | 6606 | if (quotes & QUOTES_ESC) { |
6607 | int n = SIT(c, syntax); | 6607 | int n = SIT(c, syntax); |
6608 | if (n == CCTL | 6608 | if (n == CCTL |
6609 | || (((quotes & EXP_FULL) || syntax != BASESYNTAX) | 6609 | || (syntax != BASESYNTAX && n == CBACK) |
6610 | && n == CBACK | ||
6611 | ) | ||
6612 | ) { | 6610 | ) { |
6613 | USTPUTC(CTLESC, q); | 6611 | USTPUTC(CTLESC, q); |
6614 | } | 6612 | } |
@@ -7236,8 +7234,15 @@ subevalvar(char *p, char *varname, int strloc, int subtype, | |||
7236 | if (subtype == VSREPLACE || subtype == VSREPLACEALL) { | 7234 | if (subtype == VSREPLACE || subtype == VSREPLACEALL) { |
7237 | /* Find '/' and replace with NUL */ | 7235 | /* Find '/' and replace with NUL */ |
7238 | repl = p; | 7236 | repl = p; |
7237 | /* The pattern can't be empty. | ||
7238 | * IOW: if the first char after "${v//" is a slash, | ||
7239 | * it does not terminate the pattern - it's the first char of the pattern: | ||
7240 | * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/") | ||
7241 | * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r") | ||
7242 | */ | ||
7243 | if (*repl == '/') | ||
7244 | repl++; | ||
7239 | for (;;) { | 7245 | for (;;) { |
7240 | /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */ | ||
7241 | if (*repl == '\0') { | 7246 | if (*repl == '\0') { |
7242 | repl = NULL; | 7247 | repl = NULL; |
7243 | break; | 7248 | break; |
@@ -7246,6 +7251,7 @@ subevalvar(char *p, char *varname, int strloc, int subtype, | |||
7246 | *repl = '\0'; | 7251 | *repl = '\0'; |
7247 | break; | 7252 | break; |
7248 | } | 7253 | } |
7254 | /* Handle escaped slashes, e.g. "${v/\//_}" (they are CTLESC'ed by this point) */ | ||
7249 | if ((unsigned char)*repl == CTLESC && repl[1]) | 7255 | if ((unsigned char)*repl == CTLESC && repl[1]) |
7250 | repl++; | 7256 | repl++; |
7251 | repl++; | 7257 | repl++; |
@@ -7549,14 +7555,13 @@ subevalvar(char *p, char *varname, int strloc, int subtype, | |||
7549 | * ash -c 'echo ${#1#}' name:'1=#' | 7555 | * ash -c 'echo ${#1#}' name:'1=#' |
7550 | */ | 7556 | */ |
7551 | static NOINLINE ssize_t | 7557 | static NOINLINE ssize_t |
7552 | varvalue(char *name, int varflags, int flags, int *quotedp) | 7558 | varvalue(char *name, int varflags, int flags, int quoted) |
7553 | { | 7559 | { |
7554 | const char *p; | 7560 | const char *p; |
7555 | int num; | 7561 | int num; |
7556 | int i; | 7562 | int i; |
7557 | ssize_t len = 0; | 7563 | ssize_t len = 0; |
7558 | int sep; | 7564 | int sep; |
7559 | int quoted = *quotedp; | ||
7560 | int subtype = varflags & VSTYPE; | 7565 | int subtype = varflags & VSTYPE; |
7561 | int discard = subtype == VSPLUS || subtype == VSLENGTH; | 7566 | int discard = subtype == VSPLUS || subtype == VSLENGTH; |
7562 | int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; | 7567 | int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; |
@@ -7604,13 +7609,27 @@ varvalue(char *name, int varflags, int flags, int *quotedp) | |||
7604 | case '*': { | 7609 | case '*': { |
7605 | char **ap; | 7610 | char **ap; |
7606 | char sepc; | 7611 | char sepc; |
7612 | char c; | ||
7607 | 7613 | ||
7608 | if (quoted) | 7614 | /* We will set c to 0 or ~0 depending on whether |
7609 | sep = 0; | 7615 | * we're doing field splitting. We won't do field |
7610 | sep |= ifsset() ? ifsval()[0] : ' '; | 7616 | * splitting if either we're quoted or sep is zero. |
7617 | * | ||
7618 | * Instead of testing (quoted || !sep) the following | ||
7619 | * trick optimises away any branches by using the | ||
7620 | * fact that EXP_QUOTED (which is the only bit that | ||
7621 | * can be set in quoted) is the same as EXP_FULL << | ||
7622 | * CHAR_BIT (which is the only bit that can be set | ||
7623 | * in sep). | ||
7624 | */ | ||
7625 | #if EXP_QUOTED >> CHAR_BIT != EXP_FULL | ||
7626 | #error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT | ||
7627 | #endif | ||
7628 | c = !((quoted | ~sep) & EXP_QUOTED) - 1; | ||
7629 | sep &= ~quoted; | ||
7630 | sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' '; | ||
7611 | param: | 7631 | param: |
7612 | sepc = sep; | 7632 | sepc = sep; |
7613 | *quotedp = !sepc; | ||
7614 | ap = shellparam.p; | 7633 | ap = shellparam.p; |
7615 | if (!ap) | 7634 | if (!ap) |
7616 | return -1; | 7635 | return -1; |
@@ -7675,7 +7694,6 @@ evalvar(char *p, int flag) | |||
7675 | char varflags; | 7694 | char varflags; |
7676 | char subtype; | 7695 | char subtype; |
7677 | int quoted; | 7696 | int quoted; |
7678 | char easy; | ||
7679 | char *var; | 7697 | char *var; |
7680 | int patloc; | 7698 | int patloc; |
7681 | int startloc; | 7699 | int startloc; |
@@ -7689,12 +7707,11 @@ evalvar(char *p, int flag) | |||
7689 | 7707 | ||
7690 | quoted = flag & EXP_QUOTED; | 7708 | quoted = flag & EXP_QUOTED; |
7691 | var = p; | 7709 | var = p; |
7692 | easy = (!quoted || (*var == '@' && shellparam.nparam)); | ||
7693 | startloc = expdest - (char *)stackblock(); | 7710 | startloc = expdest - (char *)stackblock(); |
7694 | p = strchr(p, '=') + 1; //TODO: use var_end(p)? | 7711 | p = strchr(p, '=') + 1; //TODO: use var_end(p)? |
7695 | 7712 | ||
7696 | again: | 7713 | again: |
7697 | varlen = varvalue(var, varflags, flag, "ed); | 7714 | varlen = varvalue(var, varflags, flag, quoted); |
7698 | if (varflags & VSNUL) | 7715 | if (varflags & VSNUL) |
7699 | varlen--; | 7716 | varlen--; |
7700 | 7717 | ||
@@ -7740,8 +7757,11 @@ evalvar(char *p, int flag) | |||
7740 | 7757 | ||
7741 | if (subtype == VSNORMAL) { | 7758 | if (subtype == VSNORMAL) { |
7742 | record: | 7759 | record: |
7743 | if (!easy) | 7760 | if (quoted) { |
7744 | goto end; | 7761 | quoted = *var == '@' && shellparam.nparam; |
7762 | if (!quoted) | ||
7763 | goto end; | ||
7764 | } | ||
7745 | recordregion(startloc, expdest - (char *)stackblock(), quoted); | 7765 | recordregion(startloc, expdest - (char *)stackblock(), quoted); |
7746 | goto end; | 7766 | goto end; |
7747 | } | 7767 | } |
@@ -7999,7 +8019,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) | |||
7999 | } | 8019 | } |
8000 | } | 8020 | } |
8001 | } else { | 8021 | } else { |
8002 | if (*p == '\\') | 8022 | if (*p == '\\' && p[1]) |
8003 | esc++; | 8023 | esc++; |
8004 | if (p[esc] == '/') { | 8024 | if (p[esc] == '/') { |
8005 | if (metaflag) | 8025 | if (metaflag) |
@@ -8013,7 +8033,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) | |||
8013 | return; | 8033 | return; |
8014 | p = name; | 8034 | p = name; |
8015 | do { | 8035 | do { |
8016 | if (*p == '\\') | 8036 | if (*p == '\\' && p[1]) |
8017 | p++; | 8037 | p++; |
8018 | *enddir++ = *p; | 8038 | *enddir++ = *p; |
8019 | } while (*p++); | 8039 | } while (*p++); |
@@ -8025,7 +8045,7 @@ expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) | |||
8025 | if (name < start) { | 8045 | if (name < start) { |
8026 | p = name; | 8046 | p = name; |
8027 | do { | 8047 | do { |
8028 | if (*p == '\\') | 8048 | if (*p == '\\' && p[1]) |
8029 | p++; | 8049 | p++; |
8030 | *enddir++ = *p++; | 8050 | *enddir++ = *p++; |
8031 | } while (p < start); | 8051 | } while (p < start); |
@@ -8473,15 +8493,15 @@ static void shellexec(char *prog, char **argv, const char *path, int idx) | |||
8473 | 8493 | ||
8474 | /* Map to POSIX errors */ | 8494 | /* Map to POSIX errors */ |
8475 | switch (e) { | 8495 | switch (e) { |
8476 | case EACCES: | 8496 | default: |
8477 | exerrno = 126; | 8497 | exerrno = 126; |
8478 | break; | 8498 | break; |
8499 | case ELOOP: | ||
8500 | case ENAMETOOLONG: | ||
8479 | case ENOENT: | 8501 | case ENOENT: |
8502 | case ENOTDIR: | ||
8480 | exerrno = 127; | 8503 | exerrno = 127; |
8481 | break; | 8504 | break; |
8482 | default: | ||
8483 | exerrno = 2; | ||
8484 | break; | ||
8485 | } | 8505 | } |
8486 | exitstatus = exerrno; | 8506 | exitstatus = exerrno; |
8487 | TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", | 8507 | TRACE(("shellexec failed for %s, errno %d, suppress_int %d\n", |
@@ -10083,9 +10103,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) | |||
10083 | shellparam.optind = 1; | 10103 | shellparam.optind = 1; |
10084 | shellparam.optoff = -1; | 10104 | shellparam.optoff = -1; |
10085 | #endif | 10105 | #endif |
10086 | pushlocalvars(); | ||
10087 | evaltree(func->n.ndefun.body, flags & EV_TESTED); | 10106 | evaltree(func->n.ndefun.body, flags & EV_TESTED); |
10088 | poplocalvars(0); | ||
10089 | funcdone: | 10107 | funcdone: |
10090 | INT_OFF; | 10108 | INT_OFF; |
10091 | funcline = savefuncline; | 10109 | funcline = savefuncline; |
@@ -10413,6 +10431,7 @@ find_builtin(const char *name) | |||
10413 | /* | 10431 | /* |
10414 | * Execute a simple command. | 10432 | * Execute a simple command. |
10415 | */ | 10433 | */ |
10434 | static void unwindfiles(struct parsefile *stop); | ||
10416 | static int | 10435 | static int |
10417 | isassignment(const char *p) | 10436 | isassignment(const char *p) |
10418 | { | 10437 | { |
@@ -10436,6 +10455,7 @@ evalcommand(union node *cmd, int flags) | |||
10436 | "\0\0", bltincmd /* why three NULs? */ | 10455 | "\0\0", bltincmd /* why three NULs? */ |
10437 | }; | 10456 | }; |
10438 | struct localvar_list *localvar_stop; | 10457 | struct localvar_list *localvar_stop; |
10458 | struct parsefile *file_stop; | ||
10439 | struct redirtab *redir_stop; | 10459 | struct redirtab *redir_stop; |
10440 | struct stackmark smark; | 10460 | struct stackmark smark; |
10441 | union node *argp; | 10461 | union node *argp; |
@@ -10461,6 +10481,7 @@ evalcommand(union node *cmd, int flags) | |||
10461 | TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); | 10481 | TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); |
10462 | setstackmark(&smark); | 10482 | setstackmark(&smark); |
10463 | localvar_stop = pushlocalvars(); | 10483 | localvar_stop = pushlocalvars(); |
10484 | file_stop = g_parsefile; | ||
10464 | back_exitstatus = 0; | 10485 | back_exitstatus = 0; |
10465 | 10486 | ||
10466 | cmdentry.cmdtype = CMDBUILTIN; | 10487 | cmdentry.cmdtype = CMDBUILTIN; |
@@ -10742,7 +10763,6 @@ evalcommand(union node *cmd, int flags) | |||
10742 | goto readstatus; | 10763 | goto readstatus; |
10743 | 10764 | ||
10744 | case CMDFUNCTION: | 10765 | case CMDFUNCTION: |
10745 | poplocalvars(1); | ||
10746 | /* See above for the rationale */ | 10766 | /* See above for the rationale */ |
10747 | dowait(DOWAIT_NONBLOCK, NULL); | 10767 | dowait(DOWAIT_NONBLOCK, NULL); |
10748 | if (evalfun(cmdentry.u.func, argc, argv, flags)) | 10768 | if (evalfun(cmdentry.u.func, argc, argv, flags)) |
@@ -10756,6 +10776,7 @@ evalcommand(union node *cmd, int flags) | |||
10756 | if (cmd->ncmd.redirect) | 10776 | if (cmd->ncmd.redirect) |
10757 | popredir(/*drop:*/ cmd_is_exec); | 10777 | popredir(/*drop:*/ cmd_is_exec); |
10758 | unwindredir(redir_stop); | 10778 | unwindredir(redir_stop); |
10779 | unwindfiles(file_stop); | ||
10759 | unwindlocalvars(localvar_stop); | 10780 | unwindlocalvars(localvar_stop); |
10760 | if (lastarg) { | 10781 | if (lastarg) { |
10761 | /* dsl: I think this is intended to be used to support | 10782 | /* dsl: I think this is intended to be used to support |
@@ -11278,14 +11299,20 @@ popfile(void) | |||
11278 | INT_ON; | 11299 | INT_ON; |
11279 | } | 11300 | } |
11280 | 11301 | ||
11302 | static void | ||
11303 | unwindfiles(struct parsefile *stop) | ||
11304 | { | ||
11305 | while (g_parsefile != stop) | ||
11306 | popfile(); | ||
11307 | } | ||
11308 | |||
11281 | /* | 11309 | /* |
11282 | * Return to top level. | 11310 | * Return to top level. |
11283 | */ | 11311 | */ |
11284 | static void | 11312 | static void |
11285 | popallfiles(void) | 11313 | popallfiles(void) |
11286 | { | 11314 | { |
11287 | while (g_parsefile != &basepf) | 11315 | unwindfiles(&basepf); |
11288 | popfile(); | ||
11289 | } | 11316 | } |
11290 | 11317 | ||
11291 | #if !ENABLE_PLATFORM_MINGW32 | 11318 | #if !ENABLE_PLATFORM_MINGW32 |
@@ -12929,7 +12956,7 @@ parsesub: { | |||
12929 | STPUTC(c, out); | 12956 | STPUTC(c, out); |
12930 | c = pgetc_eatbnl(); | 12957 | c = pgetc_eatbnl(); |
12931 | } while (isdigit(c)); | 12958 | } while (isdigit(c)); |
12932 | } else { | 12959 | } else if (c != '}') { |
12933 | /* $[{[#]]<specialchar>[}] */ | 12960 | /* $[{[#]]<specialchar>[}] */ |
12934 | int cc = c; | 12961 | int cc = c; |
12935 | 12962 | ||
@@ -12955,7 +12982,8 @@ parsesub: { | |||
12955 | } | 12982 | } |
12956 | 12983 | ||
12957 | USTPUTC(cc, out); | 12984 | USTPUTC(cc, out); |
12958 | } | 12985 | } else |
12986 | goto badsub; | ||
12959 | 12987 | ||
12960 | if (c != '}' && subtype == VSLENGTH) { | 12988 | if (c != '}' && subtype == VSLENGTH) { |
12961 | /* ${#VAR didn't end with } */ | 12989 | /* ${#VAR didn't end with } */ |
@@ -14297,38 +14325,35 @@ letcmd(int argc UNUSED_PARAM, char **argv) | |||
14297 | static int FAST_FUNC | 14325 | static int FAST_FUNC |
14298 | readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | 14326 | readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) |
14299 | { | 14327 | { |
14300 | char *opt_n = NULL; | 14328 | struct builtin_read_params params; |
14301 | char *opt_p = NULL; | ||
14302 | char *opt_t = NULL; | ||
14303 | char *opt_u = NULL; | ||
14304 | char *opt_d = NULL; /* optimized out if !BASH */ | ||
14305 | int read_flags = 0; | ||
14306 | const char *r; | 14329 | const char *r; |
14307 | int i; | 14330 | int i; |
14308 | 14331 | ||
14332 | memset(¶ms, 0, sizeof(params)); | ||
14333 | |||
14309 | while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { | 14334 | while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { |
14310 | switch (i) { | 14335 | switch (i) { |
14311 | case 'p': | 14336 | case 'p': |
14312 | opt_p = optionarg; | 14337 | params.opt_p = optionarg; |
14313 | break; | 14338 | break; |
14314 | case 'n': | 14339 | case 'n': |
14315 | opt_n = optionarg; | 14340 | params.opt_n = optionarg; |
14316 | break; | 14341 | break; |
14317 | case 's': | 14342 | case 's': |
14318 | read_flags |= BUILTIN_READ_SILENT; | 14343 | params.read_flags |= BUILTIN_READ_SILENT; |
14319 | break; | 14344 | break; |
14320 | case 't': | 14345 | case 't': |
14321 | opt_t = optionarg; | 14346 | params.opt_t = optionarg; |
14322 | break; | 14347 | break; |
14323 | case 'r': | 14348 | case 'r': |
14324 | read_flags |= BUILTIN_READ_RAW; | 14349 | params.read_flags |= BUILTIN_READ_RAW; |
14325 | break; | 14350 | break; |
14326 | case 'u': | 14351 | case 'u': |
14327 | opt_u = optionarg; | 14352 | params.opt_u = optionarg; |
14328 | break; | 14353 | break; |
14329 | #if BASH_READ_D | 14354 | #if BASH_READ_D |
14330 | case 'd': | 14355 | case 'd': |
14331 | opt_d = optionarg; | 14356 | params.opt_d = optionarg; |
14332 | break; | 14357 | break; |
14333 | #endif | 14358 | #endif |
14334 | default: | 14359 | default: |
@@ -14336,21 +14361,16 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) | |||
14336 | } | 14361 | } |
14337 | } | 14362 | } |
14338 | 14363 | ||
14364 | params.argv = argptr; | ||
14365 | params.setvar = setvar0; | ||
14366 | params.ifs = bltinlookup("IFS"); /* can be NULL */ | ||
14367 | |||
14339 | /* "read -s" needs to save/restore termios, can't allow ^C | 14368 | /* "read -s" needs to save/restore termios, can't allow ^C |
14340 | * to jump out of it. | 14369 | * to jump out of it. |
14341 | */ | 14370 | */ |
14342 | again: | 14371 | again: |
14343 | INT_OFF; | 14372 | INT_OFF; |
14344 | r = shell_builtin_read(setvar0, | 14373 | r = shell_builtin_read(¶ms); |
14345 | argptr, | ||
14346 | bltinlookup("IFS"), /* can be NULL */ | ||
14347 | read_flags, | ||
14348 | opt_n, | ||
14349 | opt_p, | ||
14350 | opt_t, | ||
14351 | opt_u, | ||
14352 | opt_d | ||
14353 | ); | ||
14354 | INT_ON; | 14374 | INT_ON; |
14355 | 14375 | ||
14356 | if ((uintptr_t)r == 1 && errno == EINTR) { | 14376 | if ((uintptr_t)r == 1 && errno == EINTR) { |
@@ -14595,6 +14615,7 @@ init(void) | |||
14595 | } | 14615 | } |
14596 | } | 14616 | } |
14597 | 14617 | ||
14618 | setvareq((char*)defifsvar, VTEXTFIXED); | ||
14598 | setvareq((char*)defoptindvar, VTEXTFIXED); | 14619 | setvareq((char*)defoptindvar, VTEXTFIXED); |
14599 | 14620 | ||
14600 | setvar0("PPID", utoa(getppid())); | 14621 | setvar0("PPID", utoa(getppid())); |
diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.right b/shell/ash_test/ash-glob/glob_bkslash_in_var.right new file mode 100644 index 000000000..f1484b9e4 --- /dev/null +++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist' | ||
2 | Unquoted matching glob in var: 'testdir.TMP/name' | ||
3 | Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist' | ||
4 | Quoted matching glob in var: 'test*.TMP/\name' | ||
diff --git a/shell/ash_test/ash-glob/glob_bkslash_in_var.tests b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests new file mode 100755 index 000000000..e3dedc4ac --- /dev/null +++ b/shell/ash_test/ash-glob/glob_bkslash_in_var.tests | |||
@@ -0,0 +1,10 @@ | |||
1 | mkdir testdir.TMP | ||
2 | >testdir.TMP/name | ||
3 | a="test*.TMP/\name_doesnt_exist" | ||
4 | b="test*.TMP/\name" | ||
5 | printf "Unquoted non-matching glob in var:'%s'\n" $a | ||
6 | printf "Unquoted matching glob in var: '%s'\n" $b | ||
7 | printf "Quoted non-matching glob in var: '%s'\n" "$a" | ||
8 | printf "Quoted matching glob in var: '%s'\n" "$b" | ||
9 | rm -f testdir.TMP/name | ||
10 | rmdir testdir.TMP | ||
diff --git a/shell/ash_test/ash-misc/empty_for1.right b/shell/ash_test/ash-misc/empty_for1.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/ash_test/ash-misc/empty_for1.right | |||
@@ -0,0 +1 @@ | |||
Zero:0 | |||
diff --git a/shell/ash_test/ash-misc/empty_for1.tests b/shell/ash_test/ash-misc/empty_for1.tests new file mode 100755 index 000000000..5a2554d54 --- /dev/null +++ b/shell/ash_test/ash-misc/empty_for1.tests | |||
@@ -0,0 +1,5 @@ | |||
1 | false | ||
2 | for v; do | ||
3 | exit 2 | ||
4 | done | ||
5 | echo Zero:$? | ||
diff --git a/shell/ash_test/ash-misc/env_and_func.right b/shell/ash_test/ash-misc/env_and_func.right index 5fc3488ae..4a1545058 100644 --- a/shell/ash_test/ash-misc/env_and_func.right +++ b/shell/ash_test/ash-misc/env_and_func.right | |||
@@ -1,2 +1,2 @@ | |||
1 | var=val | 1 | var=val |
2 | var=val | 2 | var=old |
diff --git a/shell/ash_test/ash-misc/env_and_func.tests b/shell/ash_test/ash-misc/env_and_func.tests index 3efef1a41..1c63eafd8 100755 --- a/shell/ash_test/ash-misc/env_and_func.tests +++ b/shell/ash_test/ash-misc/env_and_func.tests | |||
@@ -3,6 +3,6 @@ f() { echo "var=$var"; } | |||
3 | # bash: POSIXLY_CORRECT behavior is to "leak" new variable values | 3 | # bash: POSIXLY_CORRECT behavior is to "leak" new variable values |
4 | # out of function invocations (similar to "special builtins" behavior); | 4 | # out of function invocations (similar to "special builtins" behavior); |
5 | # but in "bash mode", they don't leak. | 5 | # but in "bash mode", they don't leak. |
6 | # hush does not "leak" values. ash does. | 6 | # hush does not "leak" values. ash used to, but now does not. |
7 | var=val f | 7 | var=val f |
8 | echo "var=$var" | 8 | echo "var=$var" |
diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right new file mode 100644 index 000000000..439dca578 --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.right | |||
@@ -0,0 +1,2 @@ | |||
1 | -dev-ram | ||
2 | /dev-am | ||
diff --git a/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests new file mode 100755 index 000000000..b83fb8eeb --- /dev/null +++ b/shell/ash_test/ash-vars/var_bash_pattern_starting_with_slash.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | v=/dev/ram | ||
2 | echo ${v////-} | ||
3 | echo ${v///r/-} | ||
diff --git a/shell/ash_test/ash-vars/var_leak.right b/shell/ash_test/ash-vars/var_leak.right index 01a5e3263..764680086 100644 --- a/shell/ash_test/ash-vars/var_leak.right +++ b/shell/ash_test/ash-vars/var_leak.right | |||
@@ -1,4 +1,4 @@ | |||
1 | should be empty: '' | 1 | should be empty: '' |
2 | should be empty: '' | 2 | should be empty: '' |
3 | should be not empty: 'val2' | 3 | should be not empty: 'val2' |
4 | should be not empty: 'val3' | 4 | should be empty: '' |
diff --git a/shell/ash_test/ash-vars/var_leak.tests b/shell/ash_test/ash-vars/var_leak.tests index 5242e24eb..adf66692e 100755 --- a/shell/ash_test/ash-vars/var_leak.tests +++ b/shell/ash_test/ash-vars/var_leak.tests | |||
@@ -15,9 +15,7 @@ VAR='' | |||
15 | VAR=val2 exec 2>&1 | 15 | VAR=val2 exec 2>&1 |
16 | echo "should be not empty: '$VAR'" | 16 | echo "should be not empty: '$VAR'" |
17 | 17 | ||
18 | # ash follows the "function call is a special builtin" rule here | ||
19 | # (bash does not do it) | ||
20 | f() { true; } | 18 | f() { true; } |
21 | VAR='' | 19 | VAR='' |
22 | VAR=val3 f | 20 | VAR=val3 f |
23 | echo "should be not empty: '$VAR'" | 21 | echo "should be empty: '$VAR'" |
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.right b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.right | |||
@@ -0,0 +1 @@ | |||
Zero:0 | |||
diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests new file mode 100755 index 000000000..d382116df --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs5.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS= | ||
2 | set -- | ||
3 | set -- $@ $* | ||
4 | echo Zero:$# | ||
diff --git a/shell/hush.c b/shell/hush.c index e3c6e2de9..881331c5b 100644 --- a/shell/hush.c +++ b/shell/hush.c | |||
@@ -990,7 +990,16 @@ struct globals { | |||
990 | #if ENABLE_HUSH_MEMLEAK | 990 | #if ENABLE_HUSH_MEMLEAK |
991 | unsigned long memleak_value; | 991 | unsigned long memleak_value; |
992 | #endif | 992 | #endif |
993 | #if HUSH_DEBUG | 993 | #if ENABLE_HUSH_MODE_X |
994 | unsigned x_mode_depth; | ||
995 | /* "set -x" output should not be redirectable with subsequent 2>FILE. | ||
996 | * We dup fd#2 to x_mode_fd when "set -x" is executed, and use it | ||
997 | * for all subsequent output. | ||
998 | */ | ||
999 | int x_mode_fd; | ||
1000 | o_string x_mode_buf; | ||
1001 | #endif | ||
1002 | #if HUSH_DEBUG >= 2 | ||
994 | int debug_indent; | 1003 | int debug_indent; |
995 | #endif | 1004 | #endif |
996 | struct sigaction sa; | 1005 | struct sigaction sa; |
@@ -1212,7 +1221,7 @@ static const struct built_in_command bltins2[] = { | |||
1212 | 1221 | ||
1213 | /* Debug printouts. | 1222 | /* Debug printouts. |
1214 | */ | 1223 | */ |
1215 | #if HUSH_DEBUG | 1224 | #if HUSH_DEBUG >= 2 |
1216 | /* prevent disasters with G.debug_indent < 0 */ | 1225 | /* prevent disasters with G.debug_indent < 0 */ |
1217 | # define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "") | 1226 | # define indent() fdprintf(2, "%*s", (G.debug_indent * 2) & 0xff, "") |
1218 | # define debug_enter() (G.debug_indent++) | 1227 | # define debug_enter() (G.debug_indent++) |
@@ -1657,6 +1666,12 @@ static int move_HFILEs_on_redirect(int fd, int avoid_fd) | |||
1657 | } | 1666 | } |
1658 | fl = fl->next_hfile; | 1667 | fl = fl->next_hfile; |
1659 | } | 1668 | } |
1669 | #if ENABLE_HUSH_MODE_X | ||
1670 | if (G.x_mode_fd > 0 && fd == G.x_mode_fd) { | ||
1671 | G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd); | ||
1672 | return 1; /* "found and moved" */ | ||
1673 | } | ||
1674 | #endif | ||
1660 | return 0; /* "not in the list" */ | 1675 | return 0; /* "not in the list" */ |
1661 | } | 1676 | } |
1662 | #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU | 1677 | #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU |
@@ -1668,9 +1683,15 @@ static void close_all_HFILE_list(void) | |||
1668 | * It is disastrous if we share memory with a vforked parent. | 1683 | * It is disastrous if we share memory with a vforked parent. |
1669 | * I'm not sure we never come here after vfork. | 1684 | * I'm not sure we never come here after vfork. |
1670 | * Therefore just close fd, nothing more. | 1685 | * Therefore just close fd, nothing more. |
1686 | * | ||
1687 | * ">" instead of ">=": we don't close fd#0, | ||
1688 | * interactive shell uses hfopen(NULL) as stdin input | ||
1689 | * which has fl->fd == 0, but fd#0 gets redirected in pipes. | ||
1690 | * If we'd close it here, then e.g. interactive "set | sort" | ||
1691 | * with NOFORKed sort, would have sort's input fd closed. | ||
1671 | */ | 1692 | */ |
1672 | /*hfclose(fl); - unsafe */ | 1693 | if (fl->fd > 0) |
1673 | if (fl->fd >= 0) | 1694 | /*hfclose(fl); - unsafe */ |
1674 | close(fl->fd); | 1695 | close(fl->fd); |
1675 | fl = fl->next_hfile; | 1696 | fl = fl->next_hfile; |
1676 | } | 1697 | } |
@@ -2376,6 +2397,12 @@ static int set_local_var(char *str, unsigned flags) | |||
2376 | return 0; | 2397 | return 0; |
2377 | } | 2398 | } |
2378 | 2399 | ||
2400 | static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) | ||
2401 | { | ||
2402 | char *var = xasprintf("%s=%s", name, val); | ||
2403 | set_local_var(var, /*flag:*/ 0); | ||
2404 | } | ||
2405 | |||
2379 | /* Used at startup and after each cd */ | 2406 | /* Used at startup and after each cd */ |
2380 | static void set_pwd_var(unsigned flag) | 2407 | static void set_pwd_var(unsigned flag) |
2381 | { | 2408 | { |
@@ -2422,15 +2449,6 @@ static int unset_local_var(const char *name) | |||
2422 | } | 2449 | } |
2423 | #endif | 2450 | #endif |
2424 | 2451 | ||
2425 | #if BASH_HOSTNAME_VAR || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_READ || ENABLE_HUSH_GETOPTS \ | ||
2426 | || (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT) | ||
2427 | static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) | ||
2428 | { | ||
2429 | char *var = xasprintf("%s=%s", name, val); | ||
2430 | set_local_var(var, /*flag:*/ 0); | ||
2431 | } | ||
2432 | #endif | ||
2433 | |||
2434 | 2452 | ||
2435 | /* | 2453 | /* |
2436 | * Helpers for "var1=val1 var2=val2 cmd" feature | 2454 | * Helpers for "var1=val1 var2=val2 cmd" feature |
@@ -2900,6 +2918,11 @@ static void o_addstr(o_string *o, const char *str) | |||
2900 | o_addblock(o, str, strlen(str)); | 2918 | o_addblock(o, str, strlen(str)); |
2901 | } | 2919 | } |
2902 | 2920 | ||
2921 | static void o_addstr_with_NUL(o_string *o, const char *str) | ||
2922 | { | ||
2923 | o_addblock(o, str, strlen(str) + 1); | ||
2924 | } | ||
2925 | |||
2903 | #if !BB_MMU | 2926 | #if !BB_MMU |
2904 | static void nommu_addchr(o_string *o, int ch) | 2927 | static void nommu_addchr(o_string *o, int ch) |
2905 | { | 2928 | { |
@@ -2910,10 +2933,36 @@ static void nommu_addchr(o_string *o, int ch) | |||
2910 | # define nommu_addchr(o, str) ((void)0) | 2933 | # define nommu_addchr(o, str) ((void)0) |
2911 | #endif | 2934 | #endif |
2912 | 2935 | ||
2913 | static void o_addstr_with_NUL(o_string *o, const char *str) | 2936 | #if ENABLE_HUSH_MODE_X |
2937 | static void x_mode_addchr(int ch) | ||
2914 | { | 2938 | { |
2915 | o_addblock(o, str, strlen(str) + 1); | 2939 | o_addchr(&G.x_mode_buf, ch); |
2940 | } | ||
2941 | static void x_mode_addstr(const char *str) | ||
2942 | { | ||
2943 | o_addstr(&G.x_mode_buf, str); | ||
2916 | } | 2944 | } |
2945 | static void x_mode_addblock(const char *str, int len) | ||
2946 | { | ||
2947 | o_addblock(&G.x_mode_buf, str, len); | ||
2948 | } | ||
2949 | static void x_mode_prefix(void) | ||
2950 | { | ||
2951 | int n = G.x_mode_depth; | ||
2952 | do x_mode_addchr('+'); while (--n >= 0); | ||
2953 | } | ||
2954 | static void x_mode_flush(void) | ||
2955 | { | ||
2956 | int len = G.x_mode_buf.length; | ||
2957 | if (len <= 0) | ||
2958 | return; | ||
2959 | if (G.x_mode_fd > 0) { | ||
2960 | G.x_mode_buf.data[len] = '\n'; | ||
2961 | full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1); | ||
2962 | } | ||
2963 | G.x_mode_buf.length = 0; | ||
2964 | } | ||
2965 | #endif | ||
2917 | 2966 | ||
2918 | /* | 2967 | /* |
2919 | * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side. | 2968 | * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side. |
@@ -3068,6 +3117,13 @@ static int o_save_ptr_helper(o_string *o, int n) | |||
3068 | o->data = xrealloc(o->data, o->maxlen + 1); | 3117 | o->data = xrealloc(o->data, o->maxlen + 1); |
3069 | list = (char**)o->data; | 3118 | list = (char**)o->data; |
3070 | memmove(list + n + 0x10, list + n, string_len); | 3119 | memmove(list + n + 0x10, list + n, string_len); |
3120 | /* | ||
3121 | * expand_on_ifs() has a "previous argv[] ends in IFS?" | ||
3122 | * check. (grep for -prev-ifs-check-). | ||
3123 | * Ensure that argv[-1][last] is not garbage | ||
3124 | * but zero bytes, to save index check there. | ||
3125 | */ | ||
3126 | list[n + 0x10 - 1] = 0; | ||
3071 | o->length += 0x10 * sizeof(list[0]); | 3127 | o->length += 0x10 * sizeof(list[0]); |
3072 | } else { | 3128 | } else { |
3073 | debug_printf_list("list[%d]=%d string_start=%d\n", | 3129 | debug_printf_list("list[%d]=%d string_start=%d\n", |
@@ -3095,6 +3151,41 @@ static int o_get_last_ptr(o_string *o, int n) | |||
3095 | return ((int)(uintptr_t)list[n-1]) + string_start; | 3151 | return ((int)(uintptr_t)list[n-1]) + string_start; |
3096 | } | 3152 | } |
3097 | 3153 | ||
3154 | /* | ||
3155 | * Globbing routines. | ||
3156 | * | ||
3157 | * Most words in commands need to be globbed, even ones which are | ||
3158 | * (single or double) quoted. This stems from the possiblity of | ||
3159 | * constructs like "abc"* and 'abc'* - these should be globbed. | ||
3160 | * Having a different code path for fully-quoted strings ("abc", | ||
3161 | * 'abc') would only help performance-wise, but we still need | ||
3162 | * code for partially-quoted strings. | ||
3163 | * | ||
3164 | * Unfortunately, if we want to match bash and ash behavior in all cases, | ||
3165 | * the logic can't be "shell-syntax argument is first transformed | ||
3166 | * to a string, then globbed, and if globbing does not match anything, | ||
3167 | * it is used verbatim". Here are two examples where it fails: | ||
3168 | * | ||
3169 | * echo 'b\*'? | ||
3170 | * | ||
3171 | * The globbing can't be avoided (because of '?' at the end). | ||
3172 | * The glob pattern is: b\\\*? - IOW, both \ and * are literals | ||
3173 | * and are glob-escaped. If this does not match, bash/ash print b\*? | ||
3174 | * - IOW: they "unbackslash" the glob pattern. | ||
3175 | * Now, look at this: | ||
3176 | * | ||
3177 | * v='\\\*'; echo b$v? | ||
3178 | * | ||
3179 | * The glob pattern is the same here: b\\\*? - the unquoted $v expansion | ||
3180 | * should be used as glob pattern with no changes. However, if glob | ||
3181 | * does not match, bash/ash print b\\\*? - NOT THE SAME as first example! | ||
3182 | * | ||
3183 | * ash implements this by having an encoded representation of the word | ||
3184 | * to glob, which IS NOT THE SAME as the glob pattern - it has more data. | ||
3185 | * Glob pattern is derived from it. If glob fails, the decision what result | ||
3186 | * should be is made using that encoded representation. Not glob pattern. | ||
3187 | */ | ||
3188 | |||
3098 | #if ENABLE_HUSH_BRACE_EXPANSION | 3189 | #if ENABLE_HUSH_BRACE_EXPANSION |
3099 | /* There in a GNU extension, GLOB_BRACE, but it is not usable: | 3190 | /* There in a GNU extension, GLOB_BRACE, but it is not usable: |
3100 | * first, it processes even {a} (no commas), second, | 3191 | * first, it processes even {a} (no commas), second, |
@@ -4877,6 +4968,15 @@ static int parse_dollar(o_string *as_string, | |||
4877 | end_ch = '}' * 0x100 + '/'; | 4968 | end_ch = '}' * 0x100 + '/'; |
4878 | } | 4969 | } |
4879 | o_addchr(dest, ch); | 4970 | o_addchr(dest, ch); |
4971 | /* The pattern can't be empty. | ||
4972 | * IOW: if the first char after "${v//" is a slash, | ||
4973 | * it does not terminate the pattern - it's the first char of the pattern: | ||
4974 | * v=/dev/ram; echo ${v////-} prints -dev-ram (pattern is "/") | ||
4975 | * v=/dev/ram; echo ${v///r/-} prints /dev-am (pattern is "/r") | ||
4976 | */ | ||
4977 | if (i_peek(input) == '/') { | ||
4978 | o_addchr(dest, i_getch(input)); | ||
4979 | } | ||
4880 | again: | 4980 | again: |
4881 | if (!BB_MMU) | 4981 | if (!BB_MMU) |
4882 | pos = dest->length; | 4982 | pos = dest->length; |
@@ -5796,12 +5896,16 @@ static int expand_on_ifs(o_string *output, int n, const char *str) | |||
5796 | /* Start new word... but not always! */ | 5896 | /* Start new word... but not always! */ |
5797 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ | 5897 | /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ |
5798 | if (output->has_quoted_part | 5898 | if (output->has_quoted_part |
5799 | /* Case "v=' a'; echo $v": | 5899 | /* |
5900 | * Case "v=' a'; echo $v": | ||
5800 | * here nothing precedes the space in $v expansion, | 5901 | * here nothing precedes the space in $v expansion, |
5801 | * therefore we should not finish the word | 5902 | * therefore we should not finish the word |
5802 | * (IOW: if there *is* word to finalize, only then do it): | 5903 | * (IOW: if there *is* word to finalize, only then do it): |
5904 | * It's okay if this accesses the byte before first argv[]: | ||
5905 | * past call to o_save_ptr() cleared it to zero byte | ||
5906 | * (grep for -prev-ifs-check-). | ||
5803 | */ | 5907 | */ |
5804 | || (n > 0 && output->data[output->length - 1]) | 5908 | || output->data[output->length - 1] |
5805 | ) { | 5909 | ) { |
5806 | new_word: | 5910 | new_word: |
5807 | o_addchr(output, '\0'); | 5911 | o_addchr(output, '\0'); |
@@ -5856,6 +5960,26 @@ static char *encode_then_expand_string(const char *str) | |||
5856 | return exp_str; | 5960 | return exp_str; |
5857 | } | 5961 | } |
5858 | 5962 | ||
5963 | static const char *first_special_char_in_vararg(const char *cp) | ||
5964 | { | ||
5965 | for (;;) { | ||
5966 | if (!*cp) return NULL; /* string has no special chars */ | ||
5967 | if (*cp == '$') return cp; | ||
5968 | if (*cp == '\\') return cp; | ||
5969 | if (*cp == '\'') return cp; | ||
5970 | if (*cp == '"') return cp; | ||
5971 | #if ENABLE_HUSH_TICK | ||
5972 | if (*cp == '`') return cp; | ||
5973 | #endif | ||
5974 | /* dquoted "${x:+ARG}" should not glob, therefore | ||
5975 | * '*' et al require some non-literal processing: */ | ||
5976 | if (*cp == '*') return cp; | ||
5977 | if (*cp == '?') return cp; | ||
5978 | if (*cp == '[') return cp; | ||
5979 | cp++; | ||
5980 | } | ||
5981 | } | ||
5982 | |||
5859 | /* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}. | 5983 | /* Expanding ARG in ${var#ARG}, ${var%ARG}, or ${var/ARG/ARG}. |
5860 | * These can contain single- and double-quoted strings, | 5984 | * These can contain single- and double-quoted strings, |
5861 | * and treated as if the ARG string is initially unquoted. IOW: | 5985 | * and treated as if the ARG string is initially unquoted. IOW: |
@@ -5875,19 +5999,10 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int | |||
5875 | char *exp_str; | 5999 | char *exp_str; |
5876 | struct in_str input; | 6000 | struct in_str input; |
5877 | o_string dest = NULL_O_STRING; | 6001 | o_string dest = NULL_O_STRING; |
5878 | const char *cp; | ||
5879 | 6002 | ||
5880 | cp = str; | 6003 | if (!first_special_char_in_vararg(str)) { |
5881 | for (;;) { | 6004 | /* string has no special chars */ |
5882 | if (!*cp) return NULL; /* string has no special chars */ | 6005 | return NULL; |
5883 | if (*cp == '$') break; | ||
5884 | if (*cp == '\\') break; | ||
5885 | if (*cp == '\'') break; | ||
5886 | if (*cp == '"') break; | ||
5887 | #if ENABLE_HUSH_TICK | ||
5888 | if (*cp == '`') break; | ||
5889 | #endif | ||
5890 | cp++; | ||
5891 | } | 6006 | } |
5892 | 6007 | ||
5893 | setup_string_in_str(&input, str); | 6008 | setup_string_in_str(&input, str); |
@@ -5968,26 +6083,19 @@ static char *encode_then_expand_vararg(const char *str, int handle_squotes, int | |||
5968 | /* Expanding ARG in ${var+ARG}, ${var-ARG} | 6083 | /* Expanding ARG in ${var+ARG}, ${var-ARG} |
5969 | */ | 6084 | */ |
5970 | static int encode_then_append_var_plusminus(o_string *output, int n, | 6085 | static int encode_then_append_var_plusminus(o_string *output, int n, |
5971 | const char *str, int dquoted) | 6086 | char *str, int dquoted) |
5972 | { | 6087 | { |
5973 | struct in_str input; | 6088 | struct in_str input; |
5974 | o_string dest = NULL_O_STRING; | 6089 | o_string dest = NULL_O_STRING; |
5975 | 6090 | ||
5976 | #if 0 //todo? | 6091 | if (!first_special_char_in_vararg(str) |
5977 | const char *cp; | 6092 | && '\0' == str[strcspn(str, G.ifs)] |
5978 | cp = str; | 6093 | ) { |
5979 | for (;;) { | 6094 | /* string has no special chars |
5980 | if (!*cp) return NULL; /* string has no special chars */ | 6095 | * && string has no $IFS chars |
5981 | if (*cp == '$') break; | 6096 | */ |
5982 | if (*cp == '\\') break; | 6097 | return expand_vars_to_list(output, n, str); |
5983 | if (*cp == '\'') break; | ||
5984 | if (*cp == '"') break; | ||
5985 | #if ENABLE_HUSH_TICK | ||
5986 | if (*cp == '`') break; | ||
5987 | #endif | ||
5988 | cp++; | ||
5989 | } | 6098 | } |
5990 | #endif | ||
5991 | 6099 | ||
5992 | setup_string_in_str(&input, str); | 6100 | setup_string_in_str(&input, str); |
5993 | 6101 | ||
@@ -7182,11 +7290,8 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) | |||
7182 | + (1 << SIGTTIN) | 7290 | + (1 << SIGTTIN) |
7183 | + (1 << SIGTTOU) | 7291 | + (1 << SIGTTOU) |
7184 | , SIG_IGN); | 7292 | , SIG_IGN); |
7185 | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ | ||
7186 | close(channel[0]); /* NB: close _first_, then move fd! */ | 7293 | close(channel[0]); /* NB: close _first_, then move fd! */ |
7187 | xmove_fd(channel[1], 1); | 7294 | xmove_fd(channel[1], 1); |
7188 | /* Prevent it from trying to handle ctrl-z etc */ | ||
7189 | IF_HUSH_JOB(G.run_list_level = 1;) | ||
7190 | # if ENABLE_HUSH_TRAP | 7295 | # if ENABLE_HUSH_TRAP |
7191 | /* Awful hack for `trap` or $(trap). | 7296 | /* Awful hack for `trap` or $(trap). |
7192 | * | 7297 | * |
@@ -7233,7 +7338,12 @@ static int generate_stream_from_string(const char *s, pid_t *pid_p) | |||
7233 | } | 7338 | } |
7234 | # endif | 7339 | # endif |
7235 | # if BB_MMU | 7340 | # if BB_MMU |
7341 | /* Prevent it from trying to handle ctrl-z etc */ | ||
7342 | IF_HUSH_JOB(G.run_list_level = 1;) | ||
7343 | CLEAR_RANDOM_T(&G.random_gen); /* or else $RANDOM repeats in child */ | ||
7236 | reset_traps_to_defaults(); | 7344 | reset_traps_to_defaults(); |
7345 | IF_HUSH_MODE_X(G.x_mode_depth++;) | ||
7346 | //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth); | ||
7237 | parse_and_run_string(s); | 7347 | parse_and_run_string(s); |
7238 | _exit(G.last_exitcode); | 7348 | _exit(G.last_exitcode); |
7239 | # else | 7349 | # else |
@@ -8014,28 +8124,65 @@ static void execvp_or_die(char **argv) | |||
8014 | } | 8124 | } |
8015 | 8125 | ||
8016 | #if ENABLE_HUSH_MODE_X | 8126 | #if ENABLE_HUSH_MODE_X |
8127 | static void x_mode_print_optionally_squoted(const char *str) | ||
8128 | { | ||
8129 | unsigned len; | ||
8130 | const char *cp; | ||
8131 | |||
8132 | cp = str; | ||
8133 | |||
8134 | /* the set of chars which-cause-string-to-be-squoted mimics bash */ | ||
8135 | /* test a char with: bash -c 'set -x; echo "CH"' */ | ||
8136 | if (str[strcspn(str, "\\\"'`$(){}[]<>;#&|~*?!^" | ||
8137 | " " "\001\002\003\004\005\006\007" | ||
8138 | "\010\011\012\013\014\015\016\017" | ||
8139 | "\020\021\022\023\024\025\026\027" | ||
8140 | "\030\031\032\033\034\035\036\037" | ||
8141 | ) | ||
8142 | ] == '\0' | ||
8143 | ) { | ||
8144 | /* string has no special chars */ | ||
8145 | x_mode_addstr(str); | ||
8146 | return; | ||
8147 | } | ||
8148 | |||
8149 | cp = str; | ||
8150 | for (;;) { | ||
8151 | /* print '....' up to EOL or first squote */ | ||
8152 | len = (int)(strchrnul(cp, '\'') - cp); | ||
8153 | if (len != 0) { | ||
8154 | x_mode_addchr('\''); | ||
8155 | x_mode_addblock(cp, len); | ||
8156 | x_mode_addchr('\''); | ||
8157 | cp += len; | ||
8158 | } | ||
8159 | if (*cp == '\0') | ||
8160 | break; | ||
8161 | /* string contains squote(s), print them as \' */ | ||
8162 | x_mode_addchr('\\'); | ||
8163 | x_mode_addchr('\''); | ||
8164 | cp++; | ||
8165 | } | ||
8166 | } | ||
8017 | static void dump_cmd_in_x_mode(char **argv) | 8167 | static void dump_cmd_in_x_mode(char **argv) |
8018 | { | 8168 | { |
8019 | if (G_x_mode && argv) { | 8169 | if (G_x_mode && argv) { |
8020 | /* We want to output the line in one write op */ | 8170 | unsigned n; |
8021 | char *buf, *p; | ||
8022 | int len; | ||
8023 | int n; | ||
8024 | 8171 | ||
8025 | len = 3; | 8172 | /* "+[+++...][ cmd...]\n\0" */ |
8173 | x_mode_prefix(); | ||
8026 | n = 0; | 8174 | n = 0; |
8027 | while (argv[n]) | 8175 | while (argv[n]) { |
8028 | len += strlen(argv[n++]) + 1; | 8176 | x_mode_addchr(' '); |
8029 | buf = xmalloc(len); | 8177 | if (argv[n][0] == '\0') { |
8030 | buf[0] = '+'; | 8178 | x_mode_addchr('\''); |
8031 | p = buf + 1; | 8179 | x_mode_addchr('\''); |
8032 | n = 0; | 8180 | } else { |
8033 | while (argv[n]) | 8181 | x_mode_print_optionally_squoted(argv[n]); |
8034 | p += sprintf(p, " %s", argv[n++]); | 8182 | } |
8035 | *p++ = '\n'; | 8183 | n++; |
8036 | *p = '\0'; | 8184 | } |
8037 | fputs(buf, stderr); | 8185 | x_mode_flush(); |
8038 | free(buf); | ||
8039 | } | 8186 | } |
8040 | } | 8187 | } |
8041 | #else | 8188 | #else |
@@ -8821,16 +8968,25 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8821 | restore_redirects(squirrel); | 8968 | restore_redirects(squirrel); |
8822 | 8969 | ||
8823 | /* Set shell variables */ | 8970 | /* Set shell variables */ |
8824 | if (G_x_mode) | ||
8825 | bb_putchar_stderr('+'); | ||
8826 | i = 0; | 8971 | i = 0; |
8827 | while (i < command->assignment_cnt) { | 8972 | while (i < command->assignment_cnt) { |
8828 | char *p = expand_string_to_string(argv[i], | 8973 | char *p = expand_string_to_string(argv[i], |
8829 | EXP_FLAG_ESC_GLOB_CHARS, | 8974 | EXP_FLAG_ESC_GLOB_CHARS, |
8830 | /*unbackslash:*/ 1 | 8975 | /*unbackslash:*/ 1 |
8831 | ); | 8976 | ); |
8832 | if (G_x_mode) | 8977 | #if ENABLE_HUSH_MODE_X |
8833 | fprintf(stderr, " %s", p); | 8978 | if (G_x_mode) { |
8979 | char *eq; | ||
8980 | if (i == 0) | ||
8981 | x_mode_prefix(); | ||
8982 | x_mode_addchr(' '); | ||
8983 | eq = strchrnul(p, '='); | ||
8984 | if (*eq) eq++; | ||
8985 | x_mode_addblock(p, (eq - p)); | ||
8986 | x_mode_print_optionally_squoted(eq); | ||
8987 | x_mode_flush(); | ||
8988 | } | ||
8989 | #endif | ||
8834 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); | 8990 | debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); |
8835 | if (set_local_var(p, /*flag:*/ 0)) { | 8991 | if (set_local_var(p, /*flag:*/ 0)) { |
8836 | /* assignment to readonly var / putenv error? */ | 8992 | /* assignment to readonly var / putenv error? */ |
@@ -8838,8 +8994,6 @@ static NOINLINE int run_pipe(struct pipe *pi) | |||
8838 | } | 8994 | } |
8839 | i++; | 8995 | i++; |
8840 | } | 8996 | } |
8841 | if (G_x_mode) | ||
8842 | bb_putchar_stderr('\n'); | ||
8843 | /* Redirect error sets $? to 1. Otherwise, | 8997 | /* Redirect error sets $? to 1. Otherwise, |
8844 | * if evaluating assignment value set $?, retain it. | 8998 | * if evaluating assignment value set $?, retain it. |
8845 | * Else, clear $?: | 8999 | * Else, clear $?: |
@@ -9288,11 +9442,11 @@ static int run_list(struct pipe *pi) | |||
9288 | }; /* argv list with one element: "$@" */ | 9442 | }; /* argv list with one element: "$@" */ |
9289 | char **vals; | 9443 | char **vals; |
9290 | 9444 | ||
9445 | G.last_exitcode = rcode = EXIT_SUCCESS; | ||
9291 | vals = (char**)encoded_dollar_at_argv; | 9446 | vals = (char**)encoded_dollar_at_argv; |
9292 | if (pi->next->res_word == RES_IN) { | 9447 | if (pi->next->res_word == RES_IN) { |
9293 | /* if no variable values after "in" we skip "for" */ | 9448 | /* if no variable values after "in" we skip "for" */ |
9294 | if (!pi->next->cmds[0].argv) { | 9449 | if (!pi->next->cmds[0].argv) { |
9295 | G.last_exitcode = rcode = EXIT_SUCCESS; | ||
9296 | debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); | 9450 | debug_printf_exec(": null FOR: exitcode EXIT_SUCCESS\n"); |
9297 | break; | 9451 | break; |
9298 | } | 9452 | } |
@@ -9626,6 +9780,7 @@ static int set_mode(int state, char mode, const char *o_opt) | |||
9626 | break; | 9780 | break; |
9627 | case 'x': | 9781 | case 'x': |
9628 | IF_HUSH_MODE_X(G_x_mode = state;) | 9782 | IF_HUSH_MODE_X(G_x_mode = state;) |
9783 | IF_HUSH_MODE_X(if (G.x_mode_fd <= 0) G.x_mode_fd = dup_CLOEXEC(2, 10);) | ||
9629 | break; | 9784 | break; |
9630 | case 'o': | 9785 | case 'o': |
9631 | if (!o_opt) { | 9786 | if (!o_opt) { |
@@ -9731,6 +9886,10 @@ int hush_main(int argc, char **argv) | |||
9731 | uname(&uts); | 9886 | uname(&uts); |
9732 | set_local_var_from_halves("HOSTNAME", uts.nodename); | 9887 | set_local_var_from_halves("HOSTNAME", uts.nodename); |
9733 | } | 9888 | } |
9889 | #endif | ||
9890 | /* IFS is not inherited from the parent environment */ | ||
9891 | set_local_var_from_halves("IFS", defifs); | ||
9892 | |||
9734 | /* bash also exports SHLVL and _, | 9893 | /* bash also exports SHLVL and _, |
9735 | * and sets (but doesn't export) the following variables: | 9894 | * and sets (but doesn't export) the following variables: |
9736 | * BASH=/bin/bash | 9895 | * BASH=/bin/bash |
@@ -9761,10 +9920,8 @@ int hush_main(int argc, char **argv) | |||
9761 | * TERM=dumb | 9920 | * TERM=dumb |
9762 | * OPTERR=1 | 9921 | * OPTERR=1 |
9763 | * OPTIND=1 | 9922 | * OPTIND=1 |
9764 | * IFS=$' \t\n' | ||
9765 | * PS4='+ ' | 9923 | * PS4='+ ' |
9766 | */ | 9924 | */ |
9767 | #endif | ||
9768 | 9925 | ||
9769 | #if ENABLE_HUSH_LINENO_VAR | 9926 | #if ENABLE_HUSH_LINENO_VAR |
9770 | if (ENABLE_HUSH_LINENO_VAR) { | 9927 | if (ENABLE_HUSH_LINENO_VAR) { |
@@ -10226,6 +10383,8 @@ static int FAST_FUNC builtin_eval(char **argv) | |||
10226 | if (!argv[0]) | 10383 | if (!argv[0]) |
10227 | return EXIT_SUCCESS; | 10384 | return EXIT_SUCCESS; |
10228 | 10385 | ||
10386 | IF_HUSH_MODE_X(G.x_mode_depth++;) | ||
10387 | //bb_error_msg("%s: ++x_mode_depth=%d", __func__, G.x_mode_depth); | ||
10229 | if (!argv[1]) { | 10388 | if (!argv[1]) { |
10230 | /* bash: | 10389 | /* bash: |
10231 | * eval "echo Hi; done" ("done" is syntax error): | 10390 | * eval "echo Hi; done" ("done" is syntax error): |
@@ -10255,6 +10414,8 @@ static int FAST_FUNC builtin_eval(char **argv) | |||
10255 | parse_and_run_string(str); | 10414 | parse_and_run_string(str); |
10256 | free(str); | 10415 | free(str); |
10257 | } | 10416 | } |
10417 | IF_HUSH_MODE_X(G.x_mode_depth--;) | ||
10418 | //bb_error_msg("%s: --x_mode_depth=%d", __func__, G.x_mode_depth); | ||
10258 | return G.last_exitcode; | 10419 | return G.last_exitcode; |
10259 | } | 10420 | } |
10260 | 10421 | ||
@@ -10374,40 +10535,29 @@ static int FAST_FUNC builtin_type(char **argv) | |||
10374 | static int FAST_FUNC builtin_read(char **argv) | 10535 | static int FAST_FUNC builtin_read(char **argv) |
10375 | { | 10536 | { |
10376 | const char *r; | 10537 | const char *r; |
10377 | char *opt_n = NULL; | 10538 | struct builtin_read_params params; |
10378 | char *opt_p = NULL; | 10539 | |
10379 | char *opt_t = NULL; | 10540 | memset(¶ms, 0, sizeof(params)); |
10380 | char *opt_u = NULL; | ||
10381 | char *opt_d = NULL; /* optimized out if !BASH */ | ||
10382 | const char *ifs; | ||
10383 | int read_flags; | ||
10384 | 10541 | ||
10385 | /* "!": do not abort on errors. | 10542 | /* "!": do not abort on errors. |
10386 | * Option string must start with "sr" to match BUILTIN_READ_xxx | 10543 | * Option string must start with "sr" to match BUILTIN_READ_xxx |
10387 | */ | 10544 | */ |
10388 | read_flags = getopt32(argv, | 10545 | params.read_flags = getopt32(argv, |
10389 | #if BASH_READ_D | 10546 | #if BASH_READ_D |
10390 | "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d | 10547 | "!srn:p:t:u:d:", ¶ms.opt_n, ¶ms.opt_p, ¶ms.opt_t, ¶ms.opt_u, ¶ms.opt_d |
10391 | #else | 10548 | #else |
10392 | "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u | 10549 | "!srn:p:t:u:", ¶ms.opt_n, ¶ms.opt_p, ¶ms.opt_t, ¶ms.opt_u |
10393 | #endif | 10550 | #endif |
10394 | ); | 10551 | ); |
10395 | if (read_flags == (uint32_t)-1) | 10552 | if ((uint32_t)params.read_flags == (uint32_t)-1) |
10396 | return EXIT_FAILURE; | 10553 | return EXIT_FAILURE; |
10397 | argv += optind; | 10554 | argv += optind; |
10398 | ifs = get_local_var_value("IFS"); /* can be NULL */ | 10555 | params.argv = argv; |
10556 | params.setvar = set_local_var_from_halves; | ||
10557 | params.ifs = get_local_var_value("IFS"); /* can be NULL */ | ||
10399 | 10558 | ||
10400 | again: | 10559 | again: |
10401 | r = shell_builtin_read(set_local_var_from_halves, | 10560 | r = shell_builtin_read(¶ms); |
10402 | argv, | ||
10403 | ifs, | ||
10404 | read_flags, | ||
10405 | opt_n, | ||
10406 | opt_p, | ||
10407 | opt_t, | ||
10408 | opt_u, | ||
10409 | opt_d | ||
10410 | ); | ||
10411 | 10561 | ||
10412 | if ((uintptr_t)r == 1 && errno == EINTR) { | 10562 | if ((uintptr_t)r == 1 && errno == EINTR) { |
10413 | unsigned sig = check_and_run_traps(); | 10563 | unsigned sig = check_and_run_traps(); |
diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.right b/shell/hush_test/hush-glob/glob_bkslash_in_var.right new file mode 100644 index 000000000..f1484b9e4 --- /dev/null +++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.right | |||
@@ -0,0 +1,4 @@ | |||
1 | Unquoted non-matching glob in var:'test*.TMP/\name_doesnt_exist' | ||
2 | Unquoted matching glob in var: 'testdir.TMP/name' | ||
3 | Quoted non-matching glob in var: 'test*.TMP/\name_doesnt_exist' | ||
4 | Quoted matching glob in var: 'test*.TMP/\name' | ||
diff --git a/shell/hush_test/hush-glob/glob_bkslash_in_var.tests b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests new file mode 100755 index 000000000..e3dedc4ac --- /dev/null +++ b/shell/hush_test/hush-glob/glob_bkslash_in_var.tests | |||
@@ -0,0 +1,10 @@ | |||
1 | mkdir testdir.TMP | ||
2 | >testdir.TMP/name | ||
3 | a="test*.TMP/\name_doesnt_exist" | ||
4 | b="test*.TMP/\name" | ||
5 | printf "Unquoted non-matching glob in var:'%s'\n" $a | ||
6 | printf "Unquoted matching glob in var: '%s'\n" $b | ||
7 | printf "Quoted non-matching glob in var: '%s'\n" "$a" | ||
8 | printf "Quoted matching glob in var: '%s'\n" "$b" | ||
9 | rm -f testdir.TMP/name | ||
10 | rmdir testdir.TMP | ||
diff --git a/shell/hush_test/hush-misc/empty_for1.right b/shell/hush_test/hush-misc/empty_for1.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for1.right | |||
@@ -0,0 +1 @@ | |||
Zero:0 | |||
diff --git a/shell/hush_test/hush-misc/empty_for1.tests b/shell/hush_test/hush-misc/empty_for1.tests new file mode 100755 index 000000000..5a2554d54 --- /dev/null +++ b/shell/hush_test/hush-misc/empty_for1.tests | |||
@@ -0,0 +1,5 @@ | |||
1 | false | ||
2 | for v; do | ||
3 | exit 2 | ||
4 | done | ||
5 | echo Zero:$? | ||
diff --git a/shell/hush_test/hush-misc/env_and_func.tests b/shell/hush_test/hush-misc/env_and_func.tests index 3efef1a41..1c63eafd8 100755 --- a/shell/hush_test/hush-misc/env_and_func.tests +++ b/shell/hush_test/hush-misc/env_and_func.tests | |||
@@ -3,6 +3,6 @@ f() { echo "var=$var"; } | |||
3 | # bash: POSIXLY_CORRECT behavior is to "leak" new variable values | 3 | # bash: POSIXLY_CORRECT behavior is to "leak" new variable values |
4 | # out of function invocations (similar to "special builtins" behavior); | 4 | # out of function invocations (similar to "special builtins" behavior); |
5 | # but in "bash mode", they don't leak. | 5 | # but in "bash mode", they don't leak. |
6 | # hush does not "leak" values. ash does. | 6 | # hush does not "leak" values. ash used to, but now does not. |
7 | var=val f | 7 | var=val f |
8 | echo "var=$var" | 8 | echo "var=$var" |
diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.right b/shell/hush_test/hush-quoting/quote_in_varexp1.right new file mode 100644 index 000000000..99a0aea7c --- /dev/null +++ b/shell/hush_test/hush-quoting/quote_in_varexp1.right | |||
@@ -0,0 +1,2 @@ | |||
1 | '' | ||
2 | Ok:0 | ||
diff --git a/shell/hush_test/hush-quoting/quote_in_varexp1.tests b/shell/hush_test/hush-quoting/quote_in_varexp1.tests new file mode 100755 index 000000000..1b97b0556 --- /dev/null +++ b/shell/hush_test/hush-quoting/quote_in_varexp1.tests | |||
@@ -0,0 +1,2 @@ | |||
1 | x="''''"; echo "${x#"${x+''}"''}" | ||
2 | echo Ok:$? | ||
diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right new file mode 100644 index 000000000..439dca578 --- /dev/null +++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.right | |||
@@ -0,0 +1,2 @@ | |||
1 | -dev-ram | ||
2 | /dev-am | ||
diff --git a/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests new file mode 100755 index 000000000..b83fb8eeb --- /dev/null +++ b/shell/hush_test/hush-vars/var_bash_pattern_starting_with_slash.tests | |||
@@ -0,0 +1,3 @@ | |||
1 | v=/dev/ram | ||
2 | echo ${v////-} | ||
3 | echo ${v///r/-} | ||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.right b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right new file mode 100644 index 000000000..46ffcece7 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.right | |||
@@ -0,0 +1 @@ | |||
Zero:0 | |||
diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests new file mode 100755 index 000000000..d382116df --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs5.tests | |||
@@ -0,0 +1,4 @@ | |||
1 | IFS= | ||
2 | set -- | ||
3 | set -- $@ $* | ||
4 | echo Zero:$# | ||
diff --git a/shell/shell_common.c b/shell/shell_common.c index 82102778c..23e5f1c25 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c | |||
@@ -50,16 +50,7 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator) | |||
50 | //Here we can simply store "VAR=" at buffer start and store read data directly | 50 | //Here we can simply store "VAR=" at buffer start and store read data directly |
51 | //after "=", then pass buffer to setvar() to consume. | 51 | //after "=", then pass buffer to setvar() to consume. |
52 | const char* FAST_FUNC | 52 | const char* FAST_FUNC |
53 | shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | 53 | shell_builtin_read(struct builtin_read_params *params) |
54 | char **argv, | ||
55 | const char *ifs, | ||
56 | int read_flags, | ||
57 | const char *opt_n, | ||
58 | const char *opt_p, | ||
59 | const char *opt_t, | ||
60 | const char *opt_u, | ||
61 | const char *opt_d | ||
62 | ) | ||
63 | { | 54 | { |
64 | struct pollfd pfd[1]; | 55 | struct pollfd pfd[1]; |
65 | #define fd (pfd[0].fd) /* -u FD */ | 56 | #define fd (pfd[0].fd) /* -u FD */ |
@@ -76,9 +67,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
76 | int bufpos; /* need to be able to hold -1 */ | 67 | int bufpos; /* need to be able to hold -1 */ |
77 | int startword; | 68 | int startword; |
78 | smallint backslash; | 69 | smallint backslash; |
70 | char **argv; | ||
71 | const char *ifs; | ||
72 | int read_flags; | ||
79 | 73 | ||
80 | errno = err = 0; | 74 | errno = err = 0; |
81 | 75 | ||
76 | argv = params->argv; | ||
82 | pp = argv; | 77 | pp = argv; |
83 | while (*pp) { | 78 | while (*pp) { |
84 | if (!is_well_formed_var_name(*pp, '\0')) { | 79 | if (!is_well_formed_var_name(*pp, '\0')) { |
@@ -90,29 +85,29 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
90 | } | 85 | } |
91 | 86 | ||
92 | nchars = 0; /* if != 0, -n is in effect */ | 87 | nchars = 0; /* if != 0, -n is in effect */ |
93 | if (opt_n) { | 88 | if (params->opt_n) { |
94 | nchars = bb_strtou(opt_n, NULL, 10); | 89 | nchars = bb_strtou(params->opt_n, NULL, 10); |
95 | if (nchars < 0 || errno) | 90 | if (nchars < 0 || errno) |
96 | return "invalid count"; | 91 | return "invalid count"; |
97 | /* note: "-n 0": off (bash 3.2 does this too) */ | 92 | /* note: "-n 0": off (bash 3.2 does this too) */ |
98 | } | 93 | } |
99 | 94 | ||
100 | end_ms = 0; | 95 | end_ms = 0; |
101 | if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { | 96 | if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { |
102 | end_ms = bb_strtou(opt_t, NULL, 10); | 97 | end_ms = bb_strtou(params->opt_t, NULL, 10); |
103 | if (errno) | 98 | if (errno) |
104 | return "invalid timeout"; | 99 | return "invalid timeout"; |
105 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ | 100 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ |
106 | end_ms = UINT_MAX / 2048; | 101 | end_ms = UINT_MAX / 2048; |
107 | end_ms *= 1000; | 102 | end_ms *= 1000; |
108 | } | 103 | } |
109 | if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) { | 104 | if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) { |
110 | /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */ | 105 | /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */ |
111 | char *p; | 106 | char *p; |
112 | /* Eat up to three fractional digits */ | 107 | /* Eat up to three fractional digits */ |
113 | int frac_digits = 3 + 1; | 108 | int frac_digits = 3 + 1; |
114 | 109 | ||
115 | end_ms = bb_strtou(opt_t, &p, 10); | 110 | end_ms = bb_strtou(params->opt_t, &p, 10); |
116 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ | 111 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ |
117 | end_ms = UINT_MAX / 2048; | 112 | end_ms = UINT_MAX / 2048; |
118 | 113 | ||
@@ -134,13 +129,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
134 | } | 129 | } |
135 | 130 | ||
136 | fd = STDIN_FILENO; | 131 | fd = STDIN_FILENO; |
137 | if (opt_u) { | 132 | if (params->opt_u) { |
138 | fd = bb_strtou(opt_u, NULL, 10); | 133 | fd = bb_strtou(params->opt_u, NULL, 10); |
139 | if (fd < 0 || errno) | 134 | if (fd < 0 || errno) |
140 | return "invalid file descriptor"; | 135 | return "invalid file descriptor"; |
141 | } | 136 | } |
142 | 137 | ||
143 | if (opt_t && end_ms == 0) { | 138 | if (params->opt_t && end_ms == 0) { |
144 | #if !ENABLE_PLATFORM_MINGW32 | 139 | #if !ENABLE_PLATFORM_MINGW32 |
145 | /* "If timeout is 0, read returns immediately, without trying | 140 | /* "If timeout is 0, read returns immediately, without trying |
146 | * to read any data. The exit status is 0 if input is available | 141 | * to read any data. The exit status is 0 if input is available |
@@ -157,14 +152,16 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
157 | #endif | 152 | #endif |
158 | } | 153 | } |
159 | 154 | ||
160 | if (opt_p && isatty(fd)) { | 155 | if (params->opt_p && isatty(fd)) { |
161 | fputs(opt_p, stderr); | 156 | fputs(params->opt_p, stderr); |
162 | fflush_all(); | 157 | fflush_all(); |
163 | } | 158 | } |
164 | 159 | ||
160 | ifs = params->ifs; | ||
165 | if (ifs == NULL) | 161 | if (ifs == NULL) |
166 | ifs = defifs; | 162 | ifs = defifs; |
167 | 163 | ||
164 | read_flags = params->read_flags; | ||
168 | #if !ENABLE_PLATFORM_MINGW32 | 165 | #if !ENABLE_PLATFORM_MINGW32 |
169 | if (nchars || (read_flags & BUILTIN_READ_SILENT)) { | 166 | if (nchars || (read_flags & BUILTIN_READ_SILENT)) { |
170 | tcgetattr(fd, &tty); | 167 | tcgetattr(fd, &tty); |
@@ -193,11 +190,11 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
193 | retval = (const char *)(uintptr_t)0; | 190 | retval = (const char *)(uintptr_t)0; |
194 | startword = 1; | 191 | startword = 1; |
195 | backslash = 0; | 192 | backslash = 0; |
196 | if (opt_t) | 193 | if (params->opt_t) |
197 | end_ms += (unsigned)monotonic_ms(); | 194 | end_ms += (unsigned)monotonic_ms(); |
198 | buffer = NULL; | 195 | buffer = NULL; |
199 | bufpos = 0; | 196 | bufpos = 0; |
200 | delim = opt_d ? *opt_d : '\n'; | 197 | delim = params->opt_d ? params->opt_d[0] : '\n'; |
201 | do { | 198 | do { |
202 | char c; | 199 | char c; |
203 | int timeout; | 200 | int timeout; |
@@ -206,7 +203,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
206 | buffer = xrealloc(buffer, bufpos + 0x101); | 203 | buffer = xrealloc(buffer, bufpos + 0x101); |
207 | 204 | ||
208 | timeout = -1; | 205 | timeout = -1; |
209 | if (opt_t) { | 206 | if (params->opt_t) { |
210 | timeout = end_ms - (unsigned)monotonic_ms(); | 207 | timeout = end_ms - (unsigned)monotonic_ms(); |
211 | /* ^^^^^^^^^^^^^ all values are unsigned, | 208 | /* ^^^^^^^^^^^^^ all values are unsigned, |
212 | * wrapping math is used here, good even if | 209 | * wrapping math is used here, good even if |
@@ -238,7 +235,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
238 | } | 235 | } |
239 | #else | 236 | #else |
240 | errno = 0; | 237 | errno = 0; |
241 | if (isatty(fd) && (opt_n || opt_d || opt_t || | 238 | if (isatty(fd) && (params->opt_n || params->opt_d || params->opt_t || |
242 | (read_flags & BUILTIN_READ_SILENT))) { | 239 | (read_flags & BUILTIN_READ_SILENT))) { |
243 | int64_t key; | 240 | int64_t key; |
244 | 241 | ||
@@ -285,7 +282,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
285 | * without variable names (bash compat). | 282 | * without variable names (bash compat). |
286 | * Thus, "read" and "read REPLY" are not the same. | 283 | * Thus, "read" and "read REPLY" are not the same. |
287 | */ | 284 | */ |
288 | if (!opt_d && argv[0]) { | 285 | if (!params->opt_d && argv[0]) { |
289 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ | 286 | /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ |
290 | const char *is_ifs = strchr(ifs, c); | 287 | const char *is_ifs = strchr(ifs, c); |
291 | if (startword && is_ifs) { | 288 | if (startword && is_ifs) { |
@@ -300,7 +297,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
300 | if (argv[1] != NULL && is_ifs) { | 297 | if (argv[1] != NULL && is_ifs) { |
301 | buffer[bufpos] = '\0'; | 298 | buffer[bufpos] = '\0'; |
302 | bufpos = 0; | 299 | bufpos = 0; |
303 | setvar(*argv, buffer); | 300 | params->setvar(*argv, buffer); |
304 | argv++; | 301 | argv++; |
305 | /* can we skip one non-space ifs char? (2: yes) */ | 302 | /* can we skip one non-space ifs char? (2: yes) */ |
306 | startword = isspace(c) ? 2 : 1; | 303 | startword = isspace(c) ? 2 : 1; |
@@ -352,14 +349,14 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
352 | } | 349 | } |
353 | 350 | ||
354 | /* Use the remainder as a value for the next variable */ | 351 | /* Use the remainder as a value for the next variable */ |
355 | setvar(*argv, buffer); | 352 | params->setvar(*argv, buffer); |
356 | /* Set the rest to "" */ | 353 | /* Set the rest to "" */ |
357 | while (*++argv) | 354 | while (*++argv) |
358 | setvar(*argv, ""); | 355 | params->setvar(*argv, ""); |
359 | } else { | 356 | } else { |
360 | /* Note: no $IFS removal */ | 357 | /* Note: no $IFS removal */ |
361 | buffer[bufpos] = '\0'; | 358 | buffer[bufpos] = '\0'; |
362 | setvar("REPLY", buffer); | 359 | params->setvar("REPLY", buffer); |
363 | } | 360 | } |
364 | 361 | ||
365 | ret: | 362 | ret: |
diff --git a/shell/shell_common.h b/shell/shell_common.h index 875fd9ea7..a1323021d 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h | |||
@@ -30,6 +30,17 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator); | |||
30 | 30 | ||
31 | /* Builtins */ | 31 | /* Builtins */ |
32 | 32 | ||
33 | struct builtin_read_params { | ||
34 | int read_flags; | ||
35 | void FAST_FUNC (*setvar)(const char *name, const char *val); | ||
36 | char **argv; | ||
37 | const char *ifs; | ||
38 | const char *opt_n; | ||
39 | const char *opt_p; | ||
40 | const char *opt_t; | ||
41 | const char *opt_u; | ||
42 | const char *opt_d; | ||
43 | }; | ||
33 | enum { | 44 | enum { |
34 | BUILTIN_READ_SILENT = 1 << 0, | 45 | BUILTIN_READ_SILENT = 1 << 0, |
35 | BUILTIN_READ_RAW = 1 << 1, | 46 | BUILTIN_READ_RAW = 1 << 1, |
@@ -40,16 +51,7 @@ enum { | |||
40 | // shell_builtin_read(setvar,argv,ifs,read_flags) | 51 | // shell_builtin_read(setvar,argv,ifs,read_flags) |
41 | //#endif | 52 | //#endif |
42 | const char* FAST_FUNC | 53 | const char* FAST_FUNC |
43 | shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | 54 | shell_builtin_read(struct builtin_read_params *params); |
44 | char **argv, | ||
45 | const char *ifs, | ||
46 | int read_flags, | ||
47 | const char *opt_n, | ||
48 | const char *opt_p, | ||
49 | const char *opt_t, | ||
50 | const char *opt_u, | ||
51 | const char *opt_d | ||
52 | ); | ||
53 | 55 | ||
54 | int FAST_FUNC | 56 | int FAST_FUNC |
55 | shell_builtin_ulimit(char **argv); | 57 | shell_builtin_ulimit(char **argv); |