hush: fix "export PS1=xyz" and "local PS1=xyz" messing up prompt

function                                             old     new   delta
helper_export_local                                  215     253     +38
leave_var_nest_level                                 107     127     +20
run_pipe                                            1840    1857     +17
handle_changed_special_names                         101     105      +4
shell_builtin_read                                  1399    1398      -1
done_word                                            767     766      -1
parse_stream                                        2249    2245      -4
set_local_var                                        437     430      -7
is_well_formed_var_name                               66       -     -66
------------------------------------------------------------------------------
(add/remove: 0/1 grow/shrink: 4/4 up/down: 79/-79)              Total: 0 bytes
   text	   data	    bss	    dec	    hex	filename
 952376	    485	   7296	 960157	  ea69d	busybox_old
 952400	    485	   7296	 960181	  ea6b5	busybox_unstripped

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2019-05-14 18:53:24 +02:00
parent 875ce094cf
commit d8bd7012a3
3 changed files with 40 additions and 32 deletions

View File

@ -2248,9 +2248,12 @@ static const char* FAST_FUNC get_local_var_value(const char *name)
return NULL; return NULL;
} }
#if (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT) \
|| (ENABLE_HUSH_LINENO_VAR || ENABLE_HUSH_GETOPTS)
static void handle_changed_special_names(const char *name, unsigned name_len) static void handle_changed_special_names(const char *name, unsigned name_len)
{ {
if (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT if (ENABLE_HUSH_INTERACTIVE && ENABLE_FEATURE_EDITING_FANCY_PROMPT
&& G_interactive_fd
&& name_len == 3 && name[0] == 'P' && name[1] == 'S' && name_len == 3 && name[0] == 'P' && name[1] == 'S'
) { ) {
cmdedit_update_prompt(); cmdedit_update_prompt();
@ -2274,6 +2277,10 @@ static void handle_changed_special_names(const char *name, unsigned name_len)
#endif #endif
} }
} }
#else
/* Do not even bother evaluating arguments */
# define handle_changed_special_names(...) ((void)0)
#endif
/* str holds "NAME=VAL" and is expected to be malloced. /* str holds "NAME=VAL" and is expected to be malloced.
* We take ownership of it. * We take ownership of it.
@ -2289,6 +2296,7 @@ static int set_local_var(char *str, unsigned flags)
char *free_me = NULL; char *free_me = NULL;
char *eq_sign; char *eq_sign;
int name_len; int name_len;
int retval;
unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT); unsigned local_lvl = (flags >> SETFLAG_VARLVL_SHIFT);
eq_sign = strchr(str, '='); eq_sign = strchr(str, '=');
@ -2402,24 +2410,24 @@ static int set_local_var(char *str, unsigned flags)
#endif #endif
if (flags & SETFLAG_EXPORT) if (flags & SETFLAG_EXPORT)
cur->flg_export = 1; cur->flg_export = 1;
retval = 0;
if (cur->flg_export) { if (cur->flg_export) {
if (flags & SETFLAG_UNEXPORT) { if (flags & SETFLAG_UNEXPORT) {
cur->flg_export = 0; cur->flg_export = 0;
/* unsetenv was already done */ /* unsetenv was already done */
} else { } else {
int i;
debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level); debug_printf_env("%s: putenv '%s'/%u\n", __func__, cur->varstr, cur->var_nest_level);
i = putenv(cur->varstr); retval = putenv(cur->varstr);
/* only now we can free old exported malloced string */ /* fall through to "free(free_me)" -
free(free_me); * only now we can free old exported malloced string
return i; */
} }
} }
free(free_me); free(free_me);
handle_changed_special_names(cur->varstr, name_len - 1); handle_changed_special_names(cur->varstr, name_len - 1);
return 0; return retval;
} }
static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val) static void FAST_FUNC set_local_var_from_halves(const char *name, const char *val)
@ -2492,6 +2500,11 @@ static void add_vars(struct variable *var)
} else { } else {
debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level); debug_printf_env("%s: restoring variable '%s'/%u\n", __func__, var->varstr, var->var_nest_level);
} }
/* Testcase (interactive):
* f() { local PS1='\w \$ '; }; f
* the below call is needed to notice restored PS1 when f returns.
*/
handle_changed_special_names(var->varstr, endofname(var->varstr) - var->varstr);
var = next; var = next;
} }
} }
@ -4198,7 +4211,7 @@ static int done_word(struct parse_context *ctx)
#if ENABLE_HUSH_LOOPS #if ENABLE_HUSH_LOOPS
if (ctx->ctx_res_w == RES_FOR) { if (ctx->ctx_res_w == RES_FOR) {
if (ctx->word.has_quoted_part if (ctx->word.has_quoted_part
|| !is_well_formed_var_name(command->argv[0], '\0') || endofname(command->argv[0])[0] != '\0'
) { ) {
/* bash says just "not a valid identifier" */ /* bash says just "not a valid identifier" */
syntax_error("not a valid identifier in for"); syntax_error("not a valid identifier in for");
@ -5372,7 +5385,7 @@ static struct pipe *parse_stream(char **pstring,
if ((ctx.is_assignment == MAYBE_ASSIGNMENT if ((ctx.is_assignment == MAYBE_ASSIGNMENT
|| ctx.is_assignment == WORD_IS_KEYWORD) || ctx.is_assignment == WORD_IS_KEYWORD)
&& ch == '=' && ch == '='
&& is_well_formed_var_name(ctx.word.data, '=') && endofname(ctx.word.data)[0] == '='
) { ) {
ctx.is_assignment = DEFINITELY_ASSIGNMENT; ctx.is_assignment = DEFINITELY_ASSIGNMENT;
debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]);
@ -7598,10 +7611,10 @@ static int save_fd_on_redirect(int fd, int avoid_fd, struct squirrel **sqp)
avoid_fd = 9; avoid_fd = 9;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_INTERACTIVE
if (fd == G.interactive_fd) { if (fd == G_interactive_fd) {
/* Testcase: "ls -l /proc/$$/fd 255>&-" should work */ /* Testcase: "ls -l /proc/$$/fd 255>&-" should work */
G.interactive_fd = xdup_CLOEXEC_and_close(G.interactive_fd, avoid_fd); G_interactive_fd = xdup_CLOEXEC_and_close(G_interactive_fd, avoid_fd);
debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G.interactive_fd); debug_printf_redir("redirect_fd %d: matches interactive_fd, moving it to %d\n", fd, G_interactive_fd);
return 1; /* "we closed fd" */ return 1; /* "we closed fd" */
} }
#endif #endif
@ -7677,7 +7690,7 @@ static void restore_redirects(struct squirrel *sq)
free(sq); free(sq);
} }
/* If moved, G.interactive_fd stays on new fd, not restoring it */ /* If moved, G_interactive_fd stays on new fd, not restoring it */
} }
#if ENABLE_FEATURE_SH_STANDALONE && BB_MMU #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
@ -7694,7 +7707,7 @@ static int internally_opened_fd(int fd, struct squirrel *sq)
int i; int i;
#if ENABLE_HUSH_INTERACTIVE #if ENABLE_HUSH_INTERACTIVE
if (fd == G.interactive_fd) if (fd == G_interactive_fd)
return 1; return 1;
#endif #endif
/* If this one of script's fds? */ /* If this one of script's fds? */
@ -7885,6 +7898,11 @@ static void remove_nested_vars(void)
*cur_pp = cur->next; *cur_pp = cur->next;
/* Free */ /* Free */
if (!cur->max_len) { if (!cur->max_len) {
/* Testcase (interactive):
* f() { local PS1='\w \$ '; }; f
* we should forget local PS1:
*/
handle_changed_special_names(cur->varstr, endofname(cur->varstr) - cur->varstr);
debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level);
free(cur->varstr); free(cur->varstr);
} }
@ -10687,9 +10705,7 @@ static int helper_export_local(char **argv, unsigned flags)
{ {
do { do {
char *name = *argv; char *name = *argv;
char *name_end = strchrnul(name, '='); const char *name_end = endofname(name);
/* So far we do not check that name is valid (TODO?) */
if (*name_end == '\0') { if (*name_end == '\0') {
struct variable *var, **vpp; struct variable *var, **vpp;
@ -10747,8 +10763,15 @@ static int helper_export_local(char **argv, unsigned flags)
*/ */
name = xasprintf("%s=", name); name = xasprintf("%s=", name);
} else { } else {
if (*name_end != '=') {
bb_error_msg("'%s': bad variable name", name);
/* do not parse following argv[]s: */
return 1;
}
/* (Un)exporting/making local NAME=VALUE */ /* (Un)exporting/making local NAME=VALUE */
name = xstrdup(name); name = xstrdup(name);
/* Testcase: export PS1='\w \$ ' */
unbackslash(name);
} }
debug_printf_env("%s: set_local_var('%s')\n", __func__, name); debug_printf_env("%s: set_local_var('%s')\n", __func__, name);
if (set_local_var(name, flags)) if (set_local_var(name, flags))

View File

@ -22,19 +22,6 @@
const char defifsvar[] ALIGN1 = "IFS= \t\n"; const char defifsvar[] ALIGN1 = "IFS= \t\n";
const char defoptindvar[] ALIGN1 = "OPTIND=1"; const char defoptindvar[] ALIGN1 = "OPTIND=1";
int FAST_FUNC is_well_formed_var_name(const char *s, char terminator)
{
if (!s || !(isalpha(*s) || *s == '_'))
return 0;
do
s++;
while (isalnum(*s) || *s == '_');
return *s == terminator;
}
/* read builtin */ /* read builtin */
/* Needs to be interruptible: shell must handle traps and shell-special signals /* Needs to be interruptible: shell must handle traps and shell-special signals
@ -70,7 +57,7 @@ shell_builtin_read(struct builtin_read_params *params)
argv = params->argv; argv = params->argv;
pp = argv; pp = argv;
while (*pp) { while (*pp) {
if (!is_well_formed_var_name(*pp, '\0')) { if (endofname(*pp)[0] != '\0') {
/* Mimic bash message */ /* Mimic bash message */
bb_error_msg("read: '%s': not a valid identifier", *pp); bb_error_msg("read: '%s': not a valid identifier", *pp);
return (const char *)(uintptr_t)1; return (const char *)(uintptr_t)1;

View File

@ -26,8 +26,6 @@ extern const char defifsvar[] ALIGN1; /* "IFS= \t\n" */
extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */ extern const char defoptindvar[] ALIGN1; /* "OPTIND=1" */
int FAST_FUNC is_well_formed_var_name(const char *s, char terminator);
/* Builtins */ /* Builtins */
struct builtin_read_params { struct builtin_read_params {