From 95f7953f2c46c7b9c799250aa8dc6eb10cc5c726 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 2 Aug 2017 14:26:33 +0200 Subject: do not use `a' quoting style in comments Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- shell/ash_test/printenv.c | 2 +- shell/math.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 1deae7c2f..78baa9aac 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13877,7 +13877,7 @@ int ash_main(int argc UNUSED_PARAM, char **argv) * may be used to endorse or promote products derived from this software * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c index c0c5e197c..c86308d3b 100644 --- a/shell/ash_test/printenv.c +++ b/shell/ash_test/printenv.c @@ -56,7 +56,7 @@ main (argc, argv) if (**argv == **envp && strncmp (*envp, *argv, len) == 0) { eval = *envp + len; - /* If the environment variable doesn't have an `=', ignore it. */ + /* If the environment variable doesn't have an '=', ignore it. */ if (*eval == '=') { puts (eval + 1); diff --git a/shell/math.c b/shell/math.c index 006221b6a..f01f24362 100644 --- a/shell/math.c +++ b/shell/math.c @@ -743,7 +743,7 @@ arith(arith_state_t *math_state, const char *expr) * may be used to endorse or promote products derived from this software * without specific prior written permission. * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -- cgit v1.2.3-55-g6feb From 7c40ddd9500907925041131374cb43eb87ef5494 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 2 Aug 2017 16:37:39 +0200 Subject: NOFORK fixes "rm -i FILE" and "yes" can now be interrupted by ^C in hush. This also now works: $ usleep 19999999 ^C $ echo $? 130 function old new delta run_pipe 1668 1711 +43 pseudo_exec_argv 312 321 +9 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 52/0) Total: 52 bytes Signed-off-by: Denys Vlasenko --- coreutils/rm.c | 5 +++-- coreutils/seq.c | 5 +++-- coreutils/usleep.c | 7 +++++++ coreutils/yes.c | 3 ++- docs/nofork_noexec.txt | 3 +++ shell/hush.c | 31 ++++++++++++++++++++++++++++++- 6 files changed, 48 insertions(+), 6 deletions(-) (limited to 'shell') diff --git a/coreutils/rm.c b/coreutils/rm.c index f91c94570..5e4acab8c 100644 --- a/coreutils/rm.c +++ b/coreutils/rm.c @@ -16,7 +16,8 @@ //config: help //config: rm is used to remove files or directories. -//applet:IF_RM(APPLET_NOFORK(rm, rm, BB_DIR_BIN, BB_SUID_DROP, rm)) +//applet:IF_RM(APPLET_NOEXEC(rm, rm, BB_DIR_BIN, BB_SUID_DROP, rm)) +/* was NOFORK, but then "rm -i FILE" can't be ^C'ed if run by hush */ //kbuild:lib-$(CONFIG_RM) += rm.o @@ -36,7 +37,7 @@ #include "libbb.h" -/* This is a NOFORK applet. Be very careful! */ +/* This is a NOEXEC applet. Be very careful! */ int rm_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int rm_main(int argc UNUSED_PARAM, char **argv) diff --git a/coreutils/seq.c b/coreutils/seq.c index f36dbb4ec..c26ff06b9 100644 --- a/coreutils/seq.c +++ b/coreutils/seq.c @@ -12,7 +12,8 @@ //config: help //config: print a sequence of numbers -//applet:IF_SEQ(APPLET_NOFORK(seq, seq, BB_DIR_USR_BIN, BB_SUID_DROP, seq)) +//applet:IF_SEQ(APPLET_NOEXEC(seq, seq, BB_DIR_USR_BIN, BB_SUID_DROP, seq)) +/* was NOFORK, but then "seq 1 999999999" can't be ^C'ed if run by hush */ //kbuild:lib-$(CONFIG_SEQ) += seq.o @@ -26,7 +27,7 @@ #include "libbb.h" -/* This is a NOFORK applet. Be very careful! */ +/* This is a NOEXEC applet. Be very careful! */ int seq_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int seq_main(int argc, char **argv) diff --git a/coreutils/usleep.c b/coreutils/usleep.c index 7c25aada1..684ab781b 100644 --- a/coreutils/usleep.c +++ b/coreutils/usleep.c @@ -38,6 +38,13 @@ int usleep_main(int argc UNUSED_PARAM, char **argv) bb_show_usage(); } + /* Safe wrt NOFORK? (noforks are not allowed to run for + * a long time). Try "usleep 99999999" + ^C + "echo $?" + * in hush with FEATURE_SH_NOFORK=y. + * At least on uclibc, usleep() thanslates to nanosleep() + * which returns early on any signal (even caught one), + * and uclibc does not loop back on EINTR. + */ usleep(xatou(argv[1])); return EXIT_SUCCESS; diff --git a/coreutils/yes.c b/coreutils/yes.c index ea35d146c..c244bfe10 100644 --- a/coreutils/yes.c +++ b/coreutils/yes.c @@ -17,7 +17,8 @@ //config: yes is used to repeatedly output a specific string, or //config: the default string 'y'. -//applet:IF_YES(APPLET_NOFORK(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) +//applet:IF_YES(APPLET_NOEXEC(yes, yes, BB_DIR_USR_BIN, BB_SUID_DROP, yes)) +/* was NOFORK, but then yes can't be ^C'ed if run by hush */ //kbuild:lib-$(CONFIG_YES) += yes.o diff --git a/docs/nofork_noexec.txt b/docs/nofork_noexec.txt index a24dd9c27..0ad4e6e60 100644 --- a/docs/nofork_noexec.txt +++ b/docs/nofork_noexec.txt @@ -52,6 +52,9 @@ xargs, find, shells do it (grep for "spawn_and_wait" and This poses much more serious limitations on what applet can do: * all NOEXEC limitations apply. +* do not run for a long time or wait for user input: + hush shell only handles signals (like ^C) after you return + from APPLET_main(). * do not ever exit() or exec(). - xfuncs are okay. They are using special trick to return to the caller applet instead of dying when they detect "x" condition. diff --git a/shell/hush.c b/shell/hush.c index 9f946d82f..cfefb7324 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7363,6 +7363,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, */ close_saved_fds_and_FILE_fds(); //FIXME: should also close saved redir fds + /* Without this, "rm -i FILE" can't be ^C'ed: */ + switch_off_special_sigs(G.special_sig_mask); debug_printf_exec("running applet '%s'\n", argv[0]); run_applet_no_and_exit(a, argv[0], argv); } @@ -8045,6 +8047,24 @@ static NOINLINE int run_pipe(struct pipe *pi) add_vars(old_vars); /* clean_up_and_ret0: */ restore_redirects(squirrel); + /* + * Try "usleep 99999999" + ^C + "echo $?" + * with FEATURE_SH_NOFORK=y. + */ + if (!funcp) { + /* It was builtin or nofork. + * if this would be a real fork/execed program, + * it should have died if a fatal sig was received. + * But OTOH, there was no separate process, + * the sig was sent to _shell_, not to non-existing + * child. + * Let's just handle ^C only, this one is obvious: + * we aren't ok with exitcode 0 when ^C was pressed + * during builtin/nofork. + */ + if (sigismember(&G.pending_set, SIGINT)) + rcode = 128 + SIGINT; + } clean_up_and_ret1: free(argv_expanded); IF_HAS_KEYWORDS(if (pi->pi_inverted) rcode = !rcode;) @@ -8060,6 +8080,14 @@ static NOINLINE int run_pipe(struct pipe *pi) if (rcode == 0) { debug_printf_exec(": run_nofork_applet '%s' '%s'...\n", argv_expanded[0], argv_expanded[1]); + /* + * Note: signals (^C) can't interrupt here. + * We remember them and they will be acted upon + * after applet returns. + * This makes applets which can run for a long time + * and/or wait for user input ineligible for NOFORK: + * for example, "yes" or "rm" (rm -i waits for input). + */ rcode = run_nofork_applet(n, argv_expanded); } goto clean_up_and_ret; @@ -8491,7 +8519,7 @@ static int run_list(struct pipe *pi) G.last_bg_pid = pi->cmds[pi->num_cmds - 1].pid; G.last_bg_pid_exitcode = 0; debug_printf_exec(": cmd&: exitcode EXIT_SUCCESS\n"); -/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash says 0 */ +/* Check pi->pi_inverted? "! sleep 1 & echo $?": bash says 1. dash and ash say 0 */ rcode = EXIT_SUCCESS; goto check_traps; } else { @@ -10178,6 +10206,7 @@ static int wait_for_child_or_signal(struct pipe *waitfor_pipe, pid_t waitfor_pid /* So, did we get a signal? */ sig = check_and_run_traps(); if (sig /*&& sig != SIGCHLD - always true */) { + /* Do this for any (non-ignored) signal, not only for ^C */ ret = 128 + sig; break; } -- cgit v1.2.3-55-g6feb From dd4b446f76736c0a13a61a38d7d816b6e6b5fca2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 2 Aug 2017 16:52:12 +0200 Subject: hush: make SIGINT handling visually less confusing $ echo $$ 18448 $ echo $? <=== NOTHING?? $ That empty line does not look right. After this patch: $ echo $$ 18448 $ echo $? ^C $ function old new delta fgetc_interactive 245 246 +1 Signed-off-by: Denys Vlasenko --- shell/hush.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index cfefb7324..93ed0bc0b 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2415,14 +2415,14 @@ static int get_user_input(struct in_str *i) /*timeout*/ -1 ); /* read_line_input intercepts ^C, "convert" it to SIGINT */ - if (r == 0) { - write(STDOUT_FILENO, "^C", 2); + if (r == 0) raise(SIGINT); - } check_and_run_traps(); if (r != 0 && !G.flag_SIGINT) break; /* ^C or SIGINT: repeat */ + /* bash prints ^C even on real SIGINT (non-kbd generated) */ + write(STDOUT_FILENO, "^C", 2); G.last_exitcode = 128 + SIGINT; } if (r < 0) { -- cgit v1.2.3-55-g6feb From 84ea60ed65f6ea6fd3b2170e44bbff5de410a78b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 2 Aug 2017 17:27:28 +0200 Subject: line editing: make read_line_input() not take timeout param It's almost always -1. function old new delta read_line_input 3902 3912 +10 new_line_input_t 24 31 +7 pgetc 583 585 +2 save_command_ps_at_cur_history 80 78 -2 read_line 76 74 -2 fgetc_interactive 246 244 -2 addLines 84 82 -2 doCommands 2226 2222 -4 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/5 up/down: 19/-12) Total: 7 bytes Signed-off-by: Denys Vlasenko --- editors/ed.c | 6 +++--- include/libbb.h | 11 ++++++----- libbb/lineedit.c | 23 ++++++++++++++++------- shell/ash.c | 5 +++-- shell/hush.c | 3 +-- util-linux/fdisk.c | 2 +- 6 files changed, 30 insertions(+), 20 deletions(-) (limited to 'shell') diff --git a/editors/ed.c b/editors/ed.c index 7f21ded92..05797692c 100644 --- a/editors/ed.c +++ b/editors/ed.c @@ -360,7 +360,7 @@ static void addLines(int num) * 0 on ctrl-C, * >0 length of input string, including terminating '\n' */ - len = read_line_input(NULL, "", buf, sizeof(buf), /*timeout*/ -1); + len = read_line_input(NULL, "", buf, sizeof(buf)); if (len <= 0) { /* Previously, ctrl-C was exiting to shell. * Now we exit to ed prompt. Is in important? */ @@ -789,7 +789,7 @@ static void doCommands(void) * 0 on ctrl-C, * >0 length of input string, including terminating '\n' */ - len = read_line_input(NULL, ": ", buf, sizeof(buf), /*timeout*/ -1); + len = read_line_input(NULL, ": ", buf, sizeof(buf)); if (len <= 0) return; while (len && isspace(buf[--len])) @@ -892,7 +892,7 @@ static void doCommands(void) } if (!dirty) return; - len = read_line_input(NULL, "Really quit? ", buf, 16, /*timeout*/ -1); + len = read_line_input(NULL, "Really quit? ", buf, 16); /* read error/EOF - no way to continue */ if (len < 0) return; diff --git a/include/libbb.h b/include/libbb.h index 9aba71949..46180c5aa 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1639,9 +1639,9 @@ enum { * buffer[0] is used as a counter of buffered chars and must be 0 * on first call. * timeout: - * -2: do not poll for input; - * -1: poll(-1) (i.e. block); - * >=0: poll for TIMEOUT milliseconds, return -1/EAGAIN on timeout + * -2: do not poll(-1) for input - read() it, return on EAGAIN at once + * -1: poll(-1) (i.e. block even on NONBLOCKed fd) + * >=0: poll() for TIMEOUT milliseconds, return -1/EAGAIN on timeout */ int64_t read_key(int fd, char *buffer, int timeout) FAST_FUNC; void read_key_ungets(char *buffer, const char *str, unsigned len) FAST_FUNC; @@ -1657,6 +1657,7 @@ unsigned size_from_HISTFILESIZE(const char *hp) FAST_FUNC; # endif typedef struct line_input_t { int flags; + int timeout; const char *path_lookup; # if MAX_HISTORY int cnt_history; @@ -1692,7 +1693,7 @@ line_input_t *new_line_input_t(int flags) FAST_FUNC; * 0 on ctrl-C (the line entered is still returned in 'command'), * >0 length of input string, including terminating '\n' */ -int read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize, int timeout) FAST_FUNC; +int read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize) FAST_FUNC; void show_history(const line_input_t *st) FAST_FUNC; # if ENABLE_FEATURE_EDITING_SAVE_ON_EXIT void save_history(line_input_t *st); @@ -1700,7 +1701,7 @@ void save_history(line_input_t *st); #else #define MAX_HISTORY 0 int read_line_input(const char* prompt, char* command, int maxsize) FAST_FUNC; -#define read_line_input(state, prompt, command, maxsize, timeout) \ +#define read_line_input(state, prompt, command, maxsize) \ read_line_input(prompt, command, maxsize) #endif diff --git a/libbb/lineedit.c b/libbb/lineedit.c index e5721b063..0106093a1 100644 --- a/libbb/lineedit.c +++ b/libbb/lineedit.c @@ -1267,6 +1267,7 @@ line_input_t* FAST_FUNC new_line_input_t(int flags) { line_input_t *n = xzalloc(sizeof(*n)); n->flags = flags; + n->timeout = -1; #if MAX_HISTORY > 0 n->max_history = MAX_HISTORY; #endif @@ -2130,7 +2131,7 @@ enum { * Backspace deletes last matched char. * Control keys exit search and return to normal editing (at current history line). */ -static int32_t reverse_i_search(void) +static int32_t reverse_i_search(int timeout) { char match_buf[128]; /* for user input */ char read_key_buffer[KEYCODE_BUFFER_SIZE]; @@ -2152,8 +2153,8 @@ static int32_t reverse_i_search(void) int h; unsigned match_buf_len = strlen(match_buf); -//FIXME: correct timeout? - ic = lineedit_read_key(read_key_buffer, -1); +//FIXME: correct timeout? (i.e. count it down?) + ic = lineedit_read_key(read_key_buffer, timeout); switch (ic) { case CTRL('R'): /* searching for the next match */ @@ -2256,9 +2257,10 @@ static int32_t reverse_i_search(void) * (in both cases the cursor remains on the input line, '\n' is not printed) * >0 length of input string, including terminating '\n' */ -int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize, int timeout) +int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *command, int maxsize) { int len; + int timeout; #if ENABLE_FEATURE_TAB_COMPLETION smallint lastWasTab = 0; #endif @@ -2297,8 +2299,15 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman maxsize = MAX_LINELEN; S.maxsize = maxsize; - /* With zero flags, no other fields are ever used */ - state = st ? st : (line_input_t*) &const_int_0; + timeout = -1; + /* Make state->flags == 0 if st is NULL. + * With zeroed flags, no other fields are ever referenced. + */ + state = (line_input_t*) &const_int_0; + if (st) { + state = st; + timeout = st->timeout; + } #if MAX_HISTORY > 0 # if ENABLE_FEATURE_EDITING_SAVEHISTORY if (state->hist_file) @@ -2510,7 +2519,7 @@ int FAST_FUNC read_line_input(line_input_t *st, const char *prompt, char *comman } #if ENABLE_FEATURE_REVERSE_SEARCH case CTRL('R'): - ic = ic_raw = reverse_i_search(); + ic = ic_raw = reverse_i_search(timeout); goto again; #endif diff --git a/shell/ash.c b/shell/ash.c index 78baa9aac..b285e3d33 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -10185,8 +10185,8 @@ preadfd(void) if (!iflag || g_parsefile->pf_fd != STDIN_FILENO) nr = nonblock_immune_read(g_parsefile->pf_fd, buf, IBUFSIZ - 1); else { - int timeout = -1; # if ENABLE_ASH_IDLE_TIMEOUT + int timeout = -1; if (iflag) { const char *tmout_var = lookupvar("TMOUT"); if (tmout_var) { @@ -10195,12 +10195,13 @@ preadfd(void) timeout = -1; } } + line_input_state->timeout = timeout; # endif # if ENABLE_FEATURE_TAB_COMPLETION line_input_state->path_lookup = pathval(); # endif reinit_unicode_for_ash(); - nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ, timeout); + nr = read_line_input(line_input_state, cmdedit_prompt, buf, IBUFSIZ); if (nr == 0) { /* ^C pressed, "convert" to SIGINT */ write(STDOUT_FILENO, "^C", 2); diff --git a/shell/hush.c b/shell/hush.c index 93ed0bc0b..6fa4e1630 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2411,8 +2411,7 @@ static int get_user_input(struct in_str *i) /* buglet: SIGINT will not make new prompt to appear _at once_, * only after . (^C works immediately) */ r = read_line_input(G.line_input_state, prompt_str, - G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1, - /*timeout*/ -1 + G.user_input_buf, CONFIG_FEATURE_EDITING_MAX_LEN-1 ); /* read_line_input intercepts ^C, "convert" it to SIGINT */ if (r == 0) diff --git a/util-linux/fdisk.c b/util-linux/fdisk.c index e00f85864..4828c0a51 100644 --- a/util-linux/fdisk.c +++ b/util-linux/fdisk.c @@ -644,7 +644,7 @@ read_line(const char *prompt) { int sz; - sz = read_line_input(NULL, prompt, line_buffer, sizeof(line_buffer), /*timeout*/ -1); + sz = read_line_input(NULL, prompt, line_buffer, sizeof(line_buffer)); if (sz <= 0) exit(EXIT_SUCCESS); /* Ctrl-D or Ctrl-C */ -- cgit v1.2.3-55-g6feb From 39701204cfa0f261beb2dc056024634e4c3afd71 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 2 Aug 2017 19:44:05 +0200 Subject: hush: do not accept "if() { echo; }" function def function old new delta parse_stream 2634 2692 +58 msg_and_die_if_script - 21 +21 syntax_error_unexpected_ch 41 46 +5 syntax_error_at 14 18 +4 die_if_script 31 28 -3 setup_redirects 319 308 -11 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 3/2 up/down: 88/-14) Total: 74 bytes Signed-off-by: Denys Vlasenko --- .../ash-parsing/groups_and_keywords2.right | 3 ++ .../ash-parsing/groups_and_keywords2.tests | 9 ++++ shell/hush.c | 53 +++++++++++++++------- .../hush-parsing/groups_and_keywords2.right | 3 ++ .../hush-parsing/groups_and_keywords2.tests | 9 ++++ 5 files changed, 60 insertions(+), 17 deletions(-) create mode 100644 shell/ash_test/ash-parsing/groups_and_keywords2.right create mode 100755 shell/ash_test/ash-parsing/groups_and_keywords2.tests create mode 100644 shell/hush_test/hush-parsing/groups_and_keywords2.right create mode 100755 shell/hush_test/hush-parsing/groups_and_keywords2.tests (limited to 'shell') diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.right b/shell/ash_test/ash-parsing/groups_and_keywords2.right new file mode 100644 index 000000000..3fcbeb662 --- /dev/null +++ b/shell/ash_test/ash-parsing/groups_and_keywords2.right @@ -0,0 +1,3 @@ +./groups_and_keywords2.tests: eval: line 1: syntax error: unexpected ")" +Fail:2 +./groups_and_keywords2.tests: line 8: syntax error: unexpected ")" diff --git a/shell/ash_test/ash-parsing/groups_and_keywords2.tests b/shell/ash_test/ash-parsing/groups_and_keywords2.tests new file mode 100755 index 000000000..ab33b909f --- /dev/null +++ b/shell/ash_test/ash-parsing/groups_and_keywords2.tests @@ -0,0 +1,9 @@ +# This is an error +(eval 'if() { echo; }') +echo Fail:$? +# ^^^^^^ bash prints 1, but interactively it sets $? = 2 +# we print 2 + +# This is an error, and it aborts in script +if() { echo; } +echo Not reached diff --git a/shell/hush.c b/shell/hush.c index 6fa4e1630..b04f793f1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1272,7 +1272,7 @@ static void xxfree(void *ptr) * HUSH_DEBUG >= 2 prints line number in this file where it was detected. */ #if HUSH_DEBUG < 2 -# define die_if_script(lineno, ...) die_if_script(__VA_ARGS__) +# define msg_and_die_if_script(lineno, ...) msg_and_die_if_script(__VA_ARGS__) # define syntax_error(lineno, msg) syntax_error(msg) # define syntax_error_at(lineno, msg) syntax_error_at(msg) # define syntax_error_unterm_ch(lineno, ch) syntax_error_unterm_ch(ch) @@ -1280,7 +1280,16 @@ static void xxfree(void *ptr) # define syntax_error_unexpected_ch(lineno, ch) syntax_error_unexpected_ch(ch) #endif -static void die_if_script(unsigned lineno, const char *fmt, ...) +static void die_if_script(void) +{ + if (!G_interactive_fd) { + if (G.last_exitcode) /* sometines it's 2, not 1 (bash compat) */ + xfunc_error_retval = G.last_exitcode; + xfunc_die(); + } +} + +static void msg_and_die_if_script(unsigned lineno, const char *fmt, ...) { va_list p; @@ -1290,8 +1299,7 @@ static void die_if_script(unsigned lineno, const char *fmt, ...) va_start(p, fmt); bb_verror_msg(fmt, p, NULL); va_end(p); - if (!G_interactive_fd) - xfunc_die(); + die_if_script(); } static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) @@ -1300,16 +1308,20 @@ static void syntax_error(unsigned lineno UNUSED_PARAM, const char *msg) bb_error_msg("syntax error: %s", msg); else bb_error_msg("syntax error"); + die_if_script(); } static void syntax_error_at(unsigned lineno UNUSED_PARAM, const char *msg) { bb_error_msg("syntax error at '%s'", msg); + die_if_script(); } static void syntax_error_unterm_str(unsigned lineno UNUSED_PARAM, const char *s) { bb_error_msg("syntax error: unterminated %s", s); +//? source4.tests fails: in bash, echo ${^} in script does not terminate the script +// die_if_script(); } static void syntax_error_unterm_ch(unsigned lineno, char ch) @@ -1327,17 +1339,18 @@ static void syntax_error_unexpected_ch(unsigned lineno UNUSED_PARAM, int ch) bb_error_msg("hush.c:%u", lineno); #endif bb_error_msg("syntax error: unexpected %s", ch == EOF ? "EOF" : msg); + die_if_script(); } #if HUSH_DEBUG < 2 -# undef die_if_script +# undef msg_and_die_if_script # undef syntax_error # undef syntax_error_at # undef syntax_error_unterm_ch # undef syntax_error_unterm_str # undef syntax_error_unexpected_ch #else -# define die_if_script(...) die_if_script(__LINE__, __VA_ARGS__) +# define msg_and_die_if_script(...) msg_and_die_if_script(__LINE__, __VA_ARGS__) # define syntax_error(msg) syntax_error(__LINE__, msg) # define syntax_error_at(msg) syntax_error_at(__LINE__, msg) # define syntax_error_unterm_ch(ch) syntax_error_unterm_ch(__LINE__, ch) @@ -1800,7 +1813,7 @@ static void restore_ttypgrp_and__exit(void) * echo END_OF_SCRIPT * lseeks fd in input FILE object from EOF to "e" in "echo END_OF_SCRIPT". * This makes "echo END_OF_SCRIPT" executed twice. - * Similar problems can be seen with die_if_script() -> xfunc_die() + * Similar problems can be seen with msg_and_die_if_script() -> xfunc_die() * and in `cmd` handling. * If set as die_func(), this makes xfunc_die() exit via _exit(), not exit(): */ @@ -3383,7 +3396,7 @@ static int done_command(struct parse_context *ctx) #if 0 /* Instead we emit error message at run time */ if (ctx->pending_redirect) { /* For example, "cmd >" (no filename to redirect to) */ - die_if_script("syntax error: %s", "invalid redirect"); + syntax_error("invalid redirect"); ctx->pending_redirect = NULL; } #endif @@ -3949,7 +3962,7 @@ static int parse_redirect(struct parse_context *ctx, #if 0 /* Instead we emit error message at run time */ if (ctx->pending_redirect) { /* For example, "cmd > pending_redirect, so we know what to do at the @@ -5021,10 +5034,16 @@ static struct pipe *parse_stream(char **pstring, else o_free_unsafe(&ctx.as_string); #endif - debug_leave(); + if (ch != ';' && IS_NULL_PIPE(ctx.list_head)) { + /* Example: bare "{ }", "()" */ + G.last_exitcode = 2; /* bash compat */ + syntax_error_unexpected_ch(ch); + goto parse_error2; + } debug_printf_parse("parse_stream return %p: " "end_trigger char found\n", ctx.list_head); + debug_leave(); return ctx.list_head; } } @@ -5282,8 +5301,8 @@ static struct pipe *parse_stream(char **pstring, /* proper use of this character is caught by end_trigger: * if we see {, we call parse_group(..., end_trigger='}') * and it will match } earlier (not here). */ - syntax_error_unexpected_ch(ch); G.last_exitcode = 2; + syntax_error_unexpected_ch(ch); goto parse_error2; default: if (HUSH_DEBUG) @@ -5513,7 +5532,7 @@ static arith_t expand_and_evaluate_arith(const char *arg, const char **errmsg_p) if (errmsg_p) *errmsg_p = math_state.errmsg; if (math_state.errmsg) - die_if_script(math_state.errmsg); + msg_and_die_if_script(math_state.errmsg); return res; } #endif @@ -5780,7 +5799,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /* in bash, len=-n means strlen()-n */ len = (arith_t)strlen(val) - beg + len; if (len < 0) /* bash compat */ - die_if_script("%s: substring expression < 0", var); + msg_and_die_if_script("%s: substring expression < 0", var); } if (len <= 0 || !val || beg >= strlen(val)) { arith_err: @@ -5794,7 +5813,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha } debug_printf_varexp("val:'%s'\n", val); #else /* not (HUSH_SUBSTR_EXPANSION && FEATURE_SH_MATH) */ - die_if_script("malformed ${%s:...}", var); + msg_and_die_if_script("malformed ${%s:...}", var); val = NULL; #endif } else { /* one of "-=+?" */ @@ -5831,7 +5850,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha exp_word = to_be_freed; if (exp_op == '?') { /* mimic bash message */ - die_if_script("%s: %s", + msg_and_die_if_script("%s: %s", var, exp_word[0] ? exp_word @@ -5848,7 +5867,7 @@ static NOINLINE const char *expand_one_var(char **to_be_freed_pp, char *arg, cha /* ${var=[word]} or ${var:=[word]} */ if (isdigit(var[0]) || var[0] == '#') { /* mimic bash message */ - die_if_script("$%s: cannot assign in this way", var); + msg_and_die_if_script("$%s: cannot assign in this way", var); val = NULL; } else { char *new_var = xasprintf("%s=%s", var, val); @@ -6862,7 +6881,7 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) * "cmd >" (no filename) * "cmd > rd_type].mode; diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.right b/shell/hush_test/hush-parsing/groups_and_keywords2.right new file mode 100644 index 000000000..ae74a5db9 --- /dev/null +++ b/shell/hush_test/hush-parsing/groups_and_keywords2.right @@ -0,0 +1,3 @@ +hush: syntax error: unexpected ) +Fail:2 +hush: syntax error: unexpected ) diff --git a/shell/hush_test/hush-parsing/groups_and_keywords2.tests b/shell/hush_test/hush-parsing/groups_and_keywords2.tests new file mode 100755 index 000000000..ab33b909f --- /dev/null +++ b/shell/hush_test/hush-parsing/groups_and_keywords2.tests @@ -0,0 +1,9 @@ +# This is an error +(eval 'if() { echo; }') +echo Fail:$? +# ^^^^^^ bash prints 1, but interactively it sets $? = 2 +# we print 2 + +# This is an error, and it aborts in script +if() { echo; } +echo Not reached -- cgit v1.2.3-55-g6feb From 19c9f31af17f2c34e93c9c322b5c546ffbcda6ad Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 3 Aug 2017 19:52:47 +0200 Subject: nofork: fix a bug uncovered by hush testsuite (forgotten fflush) function old new delta run_nofork_applet 280 287 +7 Signed-off-by: Denys Vlasenko --- libbb/vfork_daemon_rexec.c | 2 ++ shell/hush_test/run-all | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index 487ecb0e4..98512bb00 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -143,6 +143,8 @@ int FAST_FUNC run_nofork_applet(int applet_no, char **argv) applet_name = tmp_argv[0]; /* Finally we can call NOFORK applet's main() */ rc = applet_main[applet_no](argc, tmp_argv); + /* Important for shells: `which CMD` was failing */ + fflush_all(); } else { /* xfunc died in NOFORK applet */ } diff --git a/shell/hush_test/run-all b/shell/hush_test/run-all index 1dd0edc39..3fbc7c531 100755 --- a/shell/hush_test/run-all +++ b/shell/hush_test/run-all @@ -80,7 +80,7 @@ do_test() case $? in 0) echo " ok";; 77) echo " skip (feature disabled)";; - *) echo " fail"; tret=1;; + *) echo " fail ($?)"; tret=1;; esac done exit ${tret} -- cgit v1.2.3-55-g6feb From cfd392bea9f52539baae9be4833075e464075958 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 3 Aug 2017 19:56:29 +0200 Subject: ash: add a fixme comment at run_nofork_applet Signed-off-by: Denys Vlasenko --- shell/ash.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index b285e3d33..8c9f4adc6 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9918,11 +9918,13 @@ evalcommand(union node *cmd, int flags) if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { listsetvar(varlist.list, VEXPORT|VSTACK); /* run _main() */ +//FIXME: do we need INT_OFF / INT_ON here? +//wouldn't open files and allocations leak on ^C otherwise? status = run_nofork_applet(applet_no, argv); break; } #endif - /* Can we avoid forking off? For example, very last command + /* Can we avoid forking? For example, very last command * in a script or a subshell does not need forking, * we can just exec it. */ -- cgit v1.2.3-55-g6feb From 49e6bf2db92d896a71d08eb364069ba50fa82781 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Aug 2017 14:28:16 +0200 Subject: sheel: improve comments on signal handling Signed-off-by: Denys Vlasenko --- NOFORK_NOEXEC.lst | 4 +++- docs/nofork_noexec.txt | 24 +++++++++++++++++++----- shell/ash.c | 27 ++++++++++++++++----------- shell/hush.c | 7 +++++++ 4 files changed, 45 insertions(+), 17 deletions(-) (limited to 'shell') diff --git a/NOFORK_NOEXEC.lst b/NOFORK_NOEXEC.lst index 12ae1cd55..14019bf7d 100644 --- a/NOFORK_NOEXEC.lst +++ b/NOFORK_NOEXEC.lst @@ -4,10 +4,12 @@ Why can't be NOFORK: interactive: may wait for user input, ^C has to work spawner: "tool PROG ARGS" which changes program's environment - must fork changes state: e.g. environment, signal handlers +alloc+xfunc: xmalloc, then xfunc - leaks memory if xfunc dies +open+xfunc: opens fd, then calls xfunc - fd is leaked if xfunc dies runner: sometimes may run for long(ish) time, and/or works with network: ^C has to work (cat BIGFILE, chmod -R, ftpget, nc) -"runners" can become eligible after hush is taught ^C to interrupt NOFORKs! +"runners" can become eligible after shell is taught ^C to interrupt NOFORKs! Why can't be NOEXEC: suid: runs under different uid - must fork+exec diff --git a/docs/nofork_noexec.txt b/docs/nofork_noexec.txt index cfb02145a..b45a4be89 100644 --- a/docs/nofork_noexec.txt +++ b/docs/nofork_noexec.txt @@ -64,15 +64,27 @@ This poses much more serious limitations on what applet can do: * do not use shared global data, or save/restore shared global data (e.g. bb_common_bufsiz1) prior to returning. - getopt32() is ok to use. You do not need to save/restore option_mask32, - it is already done by core code. + xfunc_error_retval, and logmode - it is already done by core code. * if you allocate memory, you can use xmalloc() only on the very first allocation. All other allocations should use malloc[_or_warn](). After first allocation, you cannot use any xfuncs. Otherwise, failing xfunc will return to caller applet without freeing malloced data! -* All allocated data, opened files, signal handlers, termios settings, - O_NONBLOCK flags etc should be freed/closed/restored prior to return. -* ... +* the same applies to other resources, such as open fds: no xfuncs after + acquiring them! +* All allocated data, opened files, signal handlers, termios settings + etc should be freed/closed/restored prior to return. + +Currently, ash shell signal handling is implemented in a way that signals +have non-SA_RESTARTed handlers. This means that system calls can +return EINTR. An example of such problem is "yes" applet: +it is implemented so that it has a writing loop, this loop is exited on +any write error, and in the case of user pressing ^C the error was EINTR. +The problem is, the error causes stdout FILE* object to get into error +state, needing clearerr() - or else subsequent shell output will also +not work. ("yes" has been downgraded to NOEXEC, since hush signal handling +does not have this problem - which makes "yes" to not exit on ^C (bug). +But stray EINTRs can be seen in any NOFORK under ash, until ash is fixed). NOFORK applets give the most of speed advantage, but are trickiest to implement. In order to minimize amount of bugs and maintenance, @@ -82,6 +94,8 @@ frequently executed from shell/find/xargs, particularly in shell script loops. Applets which mess with signal handlers, termios etc are probably not worth the effort. +Applets which must be interruptible by ^C in shells can not be NOFORKs. + Any NOFORK applet is also a NOEXEC applet. @@ -94,7 +108,7 @@ API to call NOFORK applets is two functions: First one is directly used by shells if FEATURE_SH_NOFORK=y. Second one is used by many applets, but main users are xargs and find. -It itself calls run_nofork_applet(), if argv[0] turned out to be a name +It itself calls run_nofork_applet(), if argv[0] is a name of a NOFORK applet. run_nofork_applet() saves/inits/restores option parsing, xfunc_error_retval, diff --git a/shell/ash.c b/shell/ash.c index 8c9f4adc6..ca9926b54 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -3536,9 +3536,12 @@ setsignal(int signo) #endif } } -//TODO: if !rootshell, we reset SIGQUIT to DFL, -//whereas we have to restore it to what shell got on entry -//from the parent. See comment above + /* if !rootshell, we reset SIGQUIT to DFL, + * whereas we have to restore it to what shell got on entry. + * This is handled by the fact that if signal was IGNored on entry, + * then cur_act is S_HARD_IGN and we never change its sigaction + * (see code below). + */ if (signo == SIGCHLD) new_act = S_CATCH; @@ -3566,6 +3569,8 @@ setsignal(int signo) if (cur_act == S_HARD_IGN || cur_act == new_act) return; + *t = new_act; + act.sa_handler = SIG_DFL; switch (new_act) { case S_CATCH: @@ -3575,16 +3580,13 @@ setsignal(int signo) act.sa_handler = SIG_IGN; break; } - /* flags and mask matter only if !DFL and !IGN, but we do it * for all cases for more deterministic behavior: */ - act.sa_flags = 0; + act.sa_flags = 0; //TODO: why not SA_RESTART? sigfillset(&act.sa_mask); sigaction_set(signo, &act); - - *t = new_act; } /* mode flags for set_curjob */ @@ -13429,7 +13431,9 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) INT_ON; if ((uintptr_t)r == 1 && errno == EINTR) { - /* to get SIGCHLD: sleep 1 & read x; echo $x */ + /* To get SIGCHLD: sleep 1 & read x; echo $x + * Correct behavior is to not exit "read" + */ if (pending_sig == 0) goto again; } @@ -13544,13 +13548,14 @@ exitshell(void) /* NOTREACHED */ } -static void +/* Don't inline: conserve stack of caller from having our locals too */ +static NOINLINE void init(void) { /* we will never free this */ basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); - sigmode[SIGCHLD - 1] = S_DFL; + sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ setsignal(SIGCHLD); /* bash re-enables SIGHUP which is SIG_IGNed on entry. @@ -13561,7 +13566,6 @@ init(void) { char **envp; const char *p; - struct stat st1, st2; initvar(); for (envp = environ; envp && *envp; envp++) { @@ -13587,6 +13591,7 @@ init(void) #endif p = lookupvar("PWD"); if (p) { + struct stat st1, st2; if (p[0] != '/' || stat(p, &st1) || stat(".", &st2) || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino ) { diff --git a/shell/hush.c b/shell/hush.c index b04f793f1..bb80f422c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -1979,6 +1979,9 @@ static int check_and_run_traps(void) break; #if ENABLE_HUSH_JOB case SIGHUP: { +//TODO: why are we doing this? ash and dash don't do this, +//they have no handler for SIGHUP at all, +//they rely on kernel to send SIGHUP+SIGCONT to orphaned process groups struct pipe *job; debug_printf_exec("%s: sig:%d default SIGHUP handler\n", __func__, sig); /* bash is observed to signal whole process groups, @@ -8646,6 +8649,10 @@ static void install_sighandlers(unsigned mask) */ if (sig == SIGCHLD) continue; + /* bash re-enables SIGHUP which is SIG_IGNed on entry. + * Try: "trap '' HUP; bash; echo RET" and type "kill -HUP $$" + */ + //if (sig == SIGHUP) continue; - TODO? if (old_handler == SIG_IGN) { /* oops... restore back to IGN, and record this fact */ install_sighandler(sig, old_handler); -- cgit v1.2.3-55-g6feb From d329e34c96e0429602fe39489586cd61f97a2877 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Aug 2017 14:50:03 +0200 Subject: ash: INT_OFF/INT_ON around run_nofork_applet() function old new delta evalcommand 1441 1447 +6 Signed-off-by: Denys Vlasenko --- shell/ash.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index ca9926b54..2afa5e83d 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -9919,10 +9919,26 @@ evalcommand(union node *cmd, int flags) int applet_no = (- cmdentry.u.index - 2); if (applet_no >= 0 && APPLET_IS_NOFORK(applet_no)) { listsetvar(varlist.list, VEXPORT|VSTACK); - /* run _main() */ -//FIXME: do we need INT_OFF / INT_ON here? -//wouldn't open files and allocations leak on ^C otherwise? + /* + * Run _main(). + * Signals (^C) can't interrupt here. + * Otherwise we can mangle stdio or malloc internal state. + * This makes applets which can run for a long time + * and/or wait for user input ineligible for NOFORK: + * for example, "yes" or "rm" (rm -i waits for input). + */ + INT_OFF; status = run_nofork_applet(applet_no, argv); + /* + * Try enabling NOFORK for "yes" applet. + * ^C _will_ stop it (write returns EINTR), + * but this causes stdout FILE to be stuck + * and needing clearerr(). What if other applets + * also can get EINTRs? Do we need to switch + * our signals to SA_RESTART? + */ + /*clearerr(stdout);*/ + INT_ON; break; } #endif -- cgit v1.2.3-55-g6feb From 7344755823df5510b089cc5db30f8c1cdebdc5a2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 4 Aug 2017 12:16:46 +0200 Subject: ash: remove no-longer-used variable As of 035486c75 (ash: significant overhaul of redirect saving logic, 2017-07-31), the sv_pos variable is no longer used (just assigned to, with no further effect). Let's just remove it. Signed-off-by: Johannes Schindelin Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 2afa5e83d..2ad86c187 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -5546,12 +5546,10 @@ static void redirect(union node *redir, int flags) { struct redirtab *sv; - int sv_pos; if (!redir) return; - sv_pos = 0; sv = NULL; INT_OFF; if (flags & REDIR_PUSH) -- cgit v1.2.3-55-g6feb From f8cdc7a2bcd0a9d067f5ca7da8ce7bc9c98cf34e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Aug 2017 15:24:49 +0200 Subject: ash: BASH_XTRACEFD bashism Based on patch by Johannes Schindelin function old new delta evalcommand 1447 1500 +53 Signed-off-by: Denys Vlasenko --- shell/ash.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 2ad86c187..e80425f5e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -188,6 +188,7 @@ #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT #define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT #define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT +#define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 /* Bionic at least up to version 24 has no glob() */ @@ -9792,6 +9793,15 @@ evalcommand(union node *cmd, int flags) expredir(cmd->ncmd.redirect); redir_stop = pushredir(cmd->ncmd.redirect); preverrout_fd = 2; + if (BASH_XTRACEFD && xflag) { + /* NB: bash closes fd == $BASH_XTRACEFD when it is changed. + * we do not emulate this. We only use its value. + */ + const char *xtracefd = lookupvar("BASH_XTRACEFD"); + if (xtracefd && is_number(xtracefd)) + preverrout_fd = atoi(xtracefd); + + } status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH | REDIR_SAVEFD2); path = vpath.var_text; -- cgit v1.2.3-55-g6feb From 5c527dc57e74c1b60c910dc1a3f3ec9683fca43d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 4 Aug 2017 19:55:01 +0200 Subject: make 17 state-changing execing applets (ex: "nice PROG ARGS") noexec The applets with " [opts] PROG ARGS" API very quickly exec another program, noexec is okay for them: chpst/envdir/envuidgid/softlimit/setuidgid chroot chrt ionice nice nohup setarch/linux32/linux64 taskset cttyhack "reset" and "sulogin" applets don't have this form, but also exec another program at once, thus made noexec too. Signed-off-by: Denys Vlasenko --- NOFORK_NOEXEC.lst | 46 +++++++++++++++++++++++----------------------- console-tools/reset.c | 2 +- coreutils/chroot.c | 3 ++- coreutils/nice.c | 2 +- coreutils/nohup.c | 2 +- loginutils/sulogin.c | 4 ++-- runit/chpst.c | 12 ++++++------ shell/cttyhack.c | 2 +- util-linux/chrt.c | 2 +- util-linux/ionice.c | 2 +- util-linux/setarch.c | 8 ++++---- util-linux/taskset.c | 2 +- 12 files changed, 44 insertions(+), 43 deletions(-) (limited to 'shell') diff --git a/NOFORK_NOEXEC.lst b/NOFORK_NOEXEC.lst index ccd8f0c96..5ec9ae3fe 100644 --- a/NOFORK_NOEXEC.lst +++ b/NOFORK_NOEXEC.lst @@ -20,7 +20,7 @@ suid: runs under different uid - must fork+exec Why shouldn't be NOFORK/NOEXEC: rare: not started often enough to bother optimizing (example: poweroff) daemon: runs indefinitely; these are also always fit "rare" category -longterm: often runs for a long time (many seconds), execing would make +longterm: often runs for a long time (many seconds), execing makes memory footprint smaller complex: no immediately obvious reason why NOFORK wouldn't work, but does some non-obvoius operations (example: fuser, lsof, losetup); @@ -66,9 +66,9 @@ chgrp - noexec. runner chmod - noexec. runner chown - noexec. runner chpasswd - runner (list of "user:password"s from stdin) -chpst - noexec candidate, spawner -chroot - noexec candidate, spawner -chrt - noexec candidate, spawner +chpst - noexec. spawner +chroot - noexec. spawner +chrt - noexec. spawner chvt - leaks: get_console_fd_or_die() may open a new fd, or return one of stdio fds. Also, "rare" category. noexec candidate. cksum - noexec. runner clear - NOFORK @@ -80,7 +80,7 @@ cpio - runner crond - daemon crontab 0 leaks: open+xasprintf cryptpw - changes state: with --password-fd=N, moves N to stdin. Also, "rare" category. noexec candidate. -cttyhack - noexec candidate, spawner +cttyhack - noexec. spawner cut - noexec. runner date - noexec. nofork candidate(needs to stop messing up env, free xasprintf result, not use xfuncs after xasprintf) dc - runner (eats stdin if no params) @@ -107,8 +107,8 @@ ed - interactive, longterm egrep - longterm runner ("CMD | egrep ..." may run indefinitely, better to exec to conserve memory) eject - leaks: open+ioctl_or_perror_and_die, changes state (moves fds) env - noexec. spawner, changes state (env) -envdir - noexec candidate, spawner -envuidgid - noexec candidate, spawner +envdir - noexec. spawner +envuidgid - noexec. spawner expand - runner expr - leaks: nested allocs factor - runner (eats stdin if no params) @@ -128,7 +128,7 @@ flash_eraseall flash_lock flash_unlock flashcp -flock - spawner, changes state (file locks) +flock - spawner, changes state (file locks), let's play safe and not be noexec fold - noexec. runner free - nofork candidate(struct globals, needs to close /proc/meminfo fd) freeramdisk - leaks: open+ioctl_or_perror_and_die @@ -170,7 +170,7 @@ init - daemon inotifyd - daemon insmod - noexec install - runner -ionice - spawner +ionice - noexec. spawner iostat - runner ip - noexec candidate ipaddr - noexec candidate @@ -190,8 +190,8 @@ klogd - daemon last - runner (I've got 1300 lines of output when tried it) less - interactive, longterm link - NOFORK -linux32 - spawner -linux64 - spawner +linux32 - noexec. spawner +linux64 - noexec. spawner linuxrc - daemon ln - noexec loadfont - leaks: config_open+bb_error_msg_and_die("map format") @@ -247,11 +247,11 @@ netstat - runner with -c nice - noexec candidate, spawner nl - runner nmeter - longterm -nohup - noexec candidate (maybe free concat_path_file result?), spawner +nohup - noexec. spawner nproc - NOFORK ntpd - daemon od - runner -openvt - spawner +openvt - longterm: spawns a child and waits for it partprobe - noexec candidate (simple), leaks: open+ioctl_or_perror_and_die(BLKRRPART) passwd - suid paste - noexec. runner @@ -304,15 +304,15 @@ scriptreplay sed - runner sendmail - runner seq - noexec. runner -setarch - spawner +setarch - noexec. spawner setconsole setfont setkeycodes setlogcons -setpriv - spawner +setpriv - spawner, changes state, let's play safe and not be noexec setserial -setsid - spawner -setuidgid +setsid - spawner, uses fork_or_rexec() [not audted to work in noexec], let's play safe and not be noexec +setuidgid - noexec. spawner sha1sum - noexec. runner sha256sum - noexec. runner sha3sum - noexec. runner @@ -323,7 +323,7 @@ shuf - noexec. runner slattach sleep - runner, longterm smemcap - runner -softlimit - noexec candidate, spawner +softlimit - noexec. spawner sort - noexec. runner split - runner ssl_client - longterm @@ -332,21 +332,21 @@ stat - nofork candidate(needs fewer allocs) strings - runner stty - noexec/nofork candidate. has no allocs or opens except xmove_fd(xopen("-F DEVICE"),STDIN). tcsetattr(STDIN) is not a problem: it would work the same across processes sharing this fd su - suid, spawner -sulogin - spawner +sulogin - noexec. spawner sum - runner sv - noexec candidate, needs ^C (uses usleep(420000)) svc - noexec candidate, needs ^C (uses usleep(420000)) svlogd - daemon swapoff - rare swapon - rare -switch_root - spawner, rare, changes state +switch_root - spawner, rare, changes state (oh yes), execing may be important to free binary's inode sync - NOFORK sysctl - noexec candidate, leaks: xstrdup+xmalloc_read syslogd - daemon tac - noexec. runner tail - runner tar - runner -taskset - spawner +taskset - noexec. spawner tcpsvd - daemon tee - runner telnet - interactive, longterm @@ -354,8 +354,8 @@ telnetd - daemon test - NOFORK tftp - runner tftpd - daemon -time - spawner, changes state (signals) -timeout - spawner, changes state (signals) +time - spawner, longterm, changes state (signals) +timeout - spawner, longterm, changes state (signals) top - interactive, longterm touch - NOFORK tr - runner diff --git a/console-tools/reset.c b/console-tools/reset.c index 04e5b0ca1..f2b900ddb 100644 --- a/console-tools/reset.c +++ b/console-tools/reset.c @@ -16,7 +16,7 @@ //config: This program is used to reset the terminal screen, if it //config: gets messed up. -//applet:IF_RESET(APPLET(reset, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_RESET(APPLET_NOEXEC(reset, reset, BB_DIR_USR_BIN, BB_SUID_DROP, reset)) //kbuild:lib-$(CONFIG_RESET) += reset.o diff --git a/coreutils/chroot.c b/coreutils/chroot.c index 5645d72df..78751df84 100644 --- a/coreutils/chroot.c +++ b/coreutils/chroot.c @@ -13,7 +13,7 @@ //config: chroot is used to change the root directory and run a command. //config: The default command is '/bin/sh'. -//applet:IF_CHROOT(APPLET(chroot, BB_DIR_USR_SBIN, BB_SUID_DROP)) +//applet:IF_CHROOT(APPLET_NOEXEC(chroot, chroot, BB_DIR_USR_SBIN, BB_SUID_DROP, chroot)) //kbuild:lib-$(CONFIG_CHROOT) += chroot.o @@ -40,6 +40,7 @@ int chroot_main(int argc UNUSED_PARAM, char **argv) ++argv; if (!*argv) bb_show_usage(); + xchroot(*argv); ++argv; diff --git a/coreutils/nice.c b/coreutils/nice.c index 0bf055299..d6818cf00 100644 --- a/coreutils/nice.c +++ b/coreutils/nice.c @@ -12,7 +12,7 @@ //config: help //config: nice runs a program with modified scheduling priority. -//applet:IF_NICE(APPLET(nice, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_NICE(APPLET_NOEXEC(nice, nice, BB_DIR_BIN, BB_SUID_DROP, nice)) //kbuild:lib-$(CONFIG_NICE) += nice.o diff --git a/coreutils/nohup.c b/coreutils/nohup.c index df271c738..8a70ec4df 100644 --- a/coreutils/nohup.c +++ b/coreutils/nohup.c @@ -15,7 +15,7 @@ //config: help //config: run a command immune to hangups, with output to a non-tty. -//applet:IF_NOHUP(APPLET(nohup, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_NOHUP(APPLET_NOEXEC(nohup, nohup, BB_DIR_USR_BIN, BB_SUID_DROP, nohup)) //kbuild:lib-$(CONFIG_NOHUP) += nohup.o diff --git a/loginutils/sulogin.c b/loginutils/sulogin.c index d5a463cac..27ea5dff0 100644 --- a/loginutils/sulogin.c +++ b/loginutils/sulogin.c @@ -12,7 +12,7 @@ //config: sulogin is invoked when the system goes into single user //config: mode (this is done through an entry in inittab). -//applet:IF_SULOGIN(APPLET(sulogin, BB_DIR_SBIN, BB_SUID_DROP)) +//applet:IF_SULOGIN(APPLET_NOEXEC(sulogin, sulogin, BB_DIR_SBIN, BB_SUID_DROP, sulogin)) //kbuild:lib-$(CONFIG_SULOGIN) += sulogin.o @@ -34,7 +34,7 @@ int sulogin_main(int argc UNUSED_PARAM, char **argv) /* Note: sulogin is not a suid app. It is meant to be run by init * for single user / emergency mode. init starts it as root. - * Normal users (potentially malisious ones) can only run it under + * Normal users (potentially malicious ones) can only run it under * their UID, therefore no paranoia here is warranted: * $LD_LIBRARY_PATH in env, TTY = /dev/sda * are no more dangerous here than in e.g. cp applet. diff --git a/runit/chpst.c b/runit/chpst.c index ccc96539d..c061a91ea 100644 --- a/runit/chpst.c +++ b/runit/chpst.c @@ -59,12 +59,12 @@ ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. //config: help //config: Sets soft resource limits as specified by options -//applet:IF_CHPST(APPLET(chpst, BB_DIR_USR_BIN, BB_SUID_DROP)) -// APPLET_ODDNAME:name main location suid_type help -//applet:IF_ENVDIR( APPLET_ODDNAME(envdir, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envdir)) -//applet:IF_ENVUIDGID(APPLET_ODDNAME(envuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envuidgid)) -//applet:IF_SETUIDGID(APPLET_ODDNAME(setuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, setuidgid)) -//applet:IF_SOFTLIMIT(APPLET_ODDNAME(softlimit, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, softlimit)) +//applet:IF_CHPST( APPLET_NOEXEC(chpst, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, chpst)) +// APPLET_NOEXEC:name main location suid_type help +//applet:IF_ENVDIR( APPLET_NOEXEC(envdir, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envdir)) +//applet:IF_ENVUIDGID(APPLET_NOEXEC(envuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envuidgid)) +//applet:IF_SETUIDGID(APPLET_NOEXEC(setuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, setuidgid)) +//applet:IF_SOFTLIMIT(APPLET_NOEXEC(softlimit, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, softlimit)) //kbuild:lib-$(CONFIG_CHPST) += chpst.o //kbuild:lib-$(CONFIG_ENVDIR) += chpst.o diff --git a/shell/cttyhack.c b/shell/cttyhack.c index 9004b4763..849fe9e48 100644 --- a/shell/cttyhack.c +++ b/shell/cttyhack.c @@ -6,7 +6,7 @@ */ #include "libbb.h" -//applet:IF_CTTYHACK(APPLET(cttyhack, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_CTTYHACK(APPLET_NOEXEC(cttyhack, cttyhack, BB_DIR_BIN, BB_SUID_DROP, cttyhack)) //kbuild:lib-$(CONFIG_CTTYHACK) += cttyhack.o diff --git a/util-linux/chrt.c b/util-linux/chrt.c index 4bc8b6cfa..52523df02 100644 --- a/util-linux/chrt.c +++ b/util-linux/chrt.c @@ -12,7 +12,7 @@ //config: manipulate real-time attributes of a process. //config: This requires sched_{g,s}etparam support in your libc. -//applet:IF_CHRT(APPLET(chrt, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_CHRT(APPLET_NOEXEC(chrt, chrt, BB_DIR_USR_BIN, BB_SUID_DROP, chrt)) //kbuild:lib-$(CONFIG_CHRT) += chrt.o diff --git a/util-linux/ionice.c b/util-linux/ionice.c index c7b7f0373..5b9664d25 100644 --- a/util-linux/ionice.c +++ b/util-linux/ionice.c @@ -14,7 +14,7 @@ //config: Set/set program io scheduling class and priority //config: Requires kernel >= 2.6.13 -//applet:IF_IONICE(APPLET(ionice, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_IONICE(APPLET_NOEXEC(ionice, ionice, BB_DIR_BIN, BB_SUID_DROP, ionice)) //kbuild:lib-$(CONFIG_IONICE) += ionice.o diff --git a/util-linux/setarch.c b/util-linux/setarch.c index d4b568832..520865318 100644 --- a/util-linux/setarch.c +++ b/util-linux/setarch.c @@ -30,10 +30,10 @@ //config: help //config: Alias to "setarch linux64". -//applet:IF_SETARCH(APPLET(setarch, BB_DIR_BIN, BB_SUID_DROP)) -// APPLET_ODDNAME:name main location suid_type help -//applet:IF_LINUX32(APPLET_ODDNAME(linux32, setarch, BB_DIR_BIN, BB_SUID_DROP, linux32)) -//applet:IF_LINUX64(APPLET_ODDNAME(linux64, setarch, BB_DIR_BIN, BB_SUID_DROP, linux64)) +//applet:IF_SETARCH(APPLET_NOEXEC(setarch, setarch, BB_DIR_BIN, BB_SUID_DROP, setarch)) +// APPLET_NOEXEC:name main location suid_type help +//applet:IF_LINUX32(APPLET_NOEXEC(linux32, setarch, BB_DIR_BIN, BB_SUID_DROP, linux32)) +//applet:IF_LINUX64(APPLET_NOEXEC(linux64, setarch, BB_DIR_BIN, BB_SUID_DROP, linux64)) //kbuild:lib-$(CONFIG_SETARCH) += setarch.o //kbuild:lib-$(CONFIG_LINUX32) += setarch.o diff --git a/util-linux/taskset.c b/util-linux/taskset.c index 9957b1a71..89dea176e 100644 --- a/util-linux/taskset.c +++ b/util-linux/taskset.c @@ -22,7 +22,7 @@ //config: affinity parameter 0xHHHHHHHHHHHHHHHHHHHH can be arbitrarily long //config: in this case. Otherwise, it is limited to sizeof(long). -//applet:IF_TASKSET(APPLET(taskset, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_TASKSET(APPLET_NOEXEC(taskset, taskset, BB_DIR_USR_BIN, BB_SUID_DROP, taskset)) //kbuild:lib-$(CONFIG_TASKSET) += taskset.o //usage:#define taskset_trivial_usage -- cgit v1.2.3-55-g6feb From 0f14f41e72d48836a5287d00f05cea236b25be40 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 6 Aug 2017 20:06:19 +0200 Subject: ash: do not set a signal to SIG_DFL if it already is function old new delta setsignal 312 338 +26 Signed-off-by: Denys Vlasenko --- shell/ash.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index e80425f5e..e8f3ed26b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -366,7 +366,7 @@ struct globals_misc { #define S_DFL 1 /* default signal handling (SIG_DFL) */ #define S_CATCH 2 /* signal is caught */ #define S_IGN 3 /* signal is ignored (SIG_IGN) */ -#define S_HARD_IGN 4 /* signal is ignored permanently */ +#define S_HARD_IGN 4 /* signal is ignored permanently (it was SIG_IGN on entry to shell) */ /* indicates specified signal received */ uint8_t gotsig[NSIG - 1]; /* offset by 1: "signal" 0 is meaningless */ @@ -3566,6 +3566,12 @@ setsignal(int signo) cur_act = S_IGN; /* don't hard ignore these */ } } + if (act.sa_handler == SIG_DFL && new_act == S_DFL) { + /* installing SIG_DFL over SIG_DFL is a no-op */ + /* saves one sigaction call in each "sh -c SCRIPT" invocation */ + *t = S_DFL; + return; + } } if (cur_act == S_HARD_IGN || cur_act == new_act) return; -- cgit v1.2.3-55-g6feb From 248a67fb75a0d2c98f4f9935b7bb9e11382b2c78 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Aug 2017 18:18:09 +0200 Subject: free,stat: make NOEXEC pkill/pgrep/pidof uncovered another quirk: what about noexec's _process names_? Signed-off-by: Denys Vlasenko --- NOFORK_NOEXEC.lst | 18 ++++++++++-------- coreutils/stat.c | 2 +- libbb/vfork_daemon_rexec.c | 2 ++ procps/free.c | 7 +++++-- procps/pgrep.c | 6 +++++- procps/pidof.c | 4 ++++ shell/ash.c | 2 ++ shell/hush.c | 2 ++ 8 files changed, 31 insertions(+), 12 deletions(-) (limited to 'shell') diff --git a/NOFORK_NOEXEC.lst b/NOFORK_NOEXEC.lst index 70f38d867..8ec3bdbe6 100644 --- a/NOFORK_NOEXEC.lst +++ b/NOFORK_NOEXEC.lst @@ -16,6 +16,8 @@ leak categories. Why can't be NOEXEC: suid: runs under different uid - must fork+exec +if it's important that /proc/PID/cmdline and comm are correct. + ("pkill sh" killing itself before it kills real "sh" is no fun) Why shouldn't be NOFORK/NOEXEC: rare: not started often enough to bother optimizing (example: poweroff) @@ -131,7 +133,7 @@ flash_unlock - hardware flashcp - hardware flock - spawner, changes state (file locks), let's play safe and not be noexec fold - noexec. runner -free - nofork candidate(struct globals, needs to close /proc/meminfo fd) +free - noexec. nofork candidate(struct globals, needs to close /proc/meminfo fd) freeramdisk - leaks: open+ioctl_or_perror_and_die fsck - interactive, longterm fsck.minix - needs ^C @@ -172,7 +174,7 @@ inotifyd - daemon insmod - noexec install - runner ionice - noexec. spawner -iostat - runner +iostat - longterm: "iostat 1" runs indefinitely ip - noexec candidate ipaddr - noexec candidate ipcalc - noexec candidate @@ -244,7 +246,7 @@ mv - noexec candidate, runner nameif - noexec. openlog(), leaks: config_open2+ioctl_or_perror_and_die nbd-client - noexec nc - runner -netstat - runner with -c +netstat - longterm with -c (continuous listing) nice - noexec. spawner nl - runner nmeter - longterm @@ -257,13 +259,13 @@ partprobe - noexec. leaks: open+ioctl_or_perror_and_die(BLKRRPART) passwd - suid paste - noexec. runner patch - needs ^C -pgrep - nofork candidate(xregcomp, procps_scan - are they ok?) -pidof - nofork candidate(uses find_pid_by_name, is that ok?) +pgrep - must fork+exec to get correct /proc/PID/cmdline and comm field +pidof - must fork+exec to get correct /proc/PID/cmdline and comm field ping - suid, longterm ping6 - suid, longterm pipe_progress - longterm pivot_root - NOFORK -pkill - nofork candidate(xregcomp, procps_scan - are they ok?) +pkill - must fork+exec to get correct /proc/PID/cmdline and comm field pmap - noexec candidate, leaks: open+xstrdup popmaildir - runner poweroff - rare @@ -329,7 +331,7 @@ sort - noexec. runner split - runner ssl_client - longterm start-stop-daemon - not noexec: uses bb_common_bufsiz1 -stat - nofork candidate(needs fewer allocs) +stat - noexec. nofork candidate(needs fewer allocs) strings - runner stty - noexec. nofork candidate: has no allocs or opens except xmove_fd(xopen("-F DEVICE"),STDIN). tcsetattr(STDIN) is not a problem: it would work the same across processes sharing this fd su - suid, spawner @@ -338,7 +340,7 @@ sum - runner sv - noexec. needs ^C (uses usleep(420000)) svc - noexec. needs ^C (uses usleep(420000)) svlogd - daemon -swapoff - rare +swapoff - longterm: may cause memory pressure, execing is beneficial swapon - rare switch_root - spawner, rare, changes state (oh yes), execing may be important to free binary's inode sync - NOFORK diff --git a/coreutils/stat.c b/coreutils/stat.c index 3b85808b5..4e926a908 100644 --- a/coreutils/stat.c +++ b/coreutils/stat.c @@ -36,7 +36,7 @@ //config: Without this, stat will not support the '-f' option to display //config: information about filesystem status. -//applet:IF_STAT(APPLET(stat, BB_DIR_BIN, BB_SUID_DROP)) +//applet:IF_STAT(APPLET_NOEXEC(stat, stat, BB_DIR_BIN, BB_SUID_DROP, stat)) //kbuild:lib-$(CONFIG_STAT) += stat.o diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index f84e678b5..50ecea762 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -175,6 +175,8 @@ int FAST_FUNC spawn_and_wait(char **argv) return wait4pid(rc); /* child */ +//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] +//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) /* reset some state and run without execing */ /* msg_eol = "\n"; - no caller needs this reinited yet */ diff --git a/procps/free.c b/procps/free.c index 618664e08..b57e4a322 100644 --- a/procps/free.c +++ b/procps/free.c @@ -15,7 +15,7 @@ //config: memory in the system, as well as the buffers used by the kernel. //config: The shared memory column should be ignored; it is obsolete. -//applet:IF_FREE(APPLET(free, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_FREE(APPLET_NOEXEC(free, free, BB_DIR_USR_BIN, BB_SUID_DROP, free)) //kbuild:lib-$(CONFIG_FREE) += free.o @@ -47,7 +47,10 @@ struct globals { #endif } FIX_ALIASING; #define G (*(struct globals*)bb_common_bufsiz1) -#define INIT_G() do { setup_common_bufsiz(); } while (0) +#define INIT_G() do { \ + setup_common_bufsiz(); \ + /* NB: noexec applet - globals not zeroed */ \ +} while (0) static unsigned long long scale(unsigned long d) diff --git a/procps/pgrep.c b/procps/pgrep.c index a3ca9e295..a16a6e959 100644 --- a/procps/pgrep.c +++ b/procps/pgrep.c @@ -18,9 +18,13 @@ //config: help //config: Send signals to processes by name. -//applet:IF_PGREP(APPLET(pgrep, BB_DIR_USR_BIN, BB_SUID_DROP)) +//applet:IF_PGREP(APPLET_ODDNAME(pgrep, pgrep, BB_DIR_USR_BIN, BB_SUID_DROP, pgrep)) // APPLET_ODDNAME:name main location suid_type help //applet:IF_PKILL(APPLET_ODDNAME(pkill, pgrep, BB_DIR_USR_BIN, BB_SUID_DROP, pkill)) +/* can't be noexec: can find _itself_ under wrong name, since after fork only, + * /proc/PID/cmdline and comm are wrong! Can fix comm (prctl(PR_SET_NAME)), + * but cmdline? + */ //kbuild:lib-$(CONFIG_PGREP) += pgrep.o //kbuild:lib-$(CONFIG_PKILL) += pgrep.o diff --git a/procps/pidof.c b/procps/pidof.c index 41247a02c..98d7949f8 100644 --- a/procps/pidof.c +++ b/procps/pidof.c @@ -30,6 +30,10 @@ //config: of the pidof, in other words the calling shell or shell script. //applet:IF_PIDOF(APPLET(pidof, BB_DIR_BIN, BB_SUID_DROP)) +/* can't be noexec: can find _itself_ under wrong name, since after fork only, + * /proc/PID/cmdline and comm are wrong! Can fix comm (prctl(PR_SET_NAME)), + * but cmdline? + */ //kbuild:lib-$(CONFIG_PIDOF) += pidof.o diff --git a/shell/ash.c b/shell/ash.c index e8f3ed26b..0a323e957 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7803,6 +7803,8 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c while (*envp) putenv(*envp++); popredir(/*drop:*/ 1); +//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] +//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) run_applet_no_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ diff --git a/shell/hush.c b/shell/hush.c index bb80f422c..b4fe7146b 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7387,6 +7387,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, /* Without this, "rm -i FILE" can't be ^C'ed: */ switch_off_special_sigs(G.special_sig_mask); debug_printf_exec("running applet '%s'\n", argv[0]); +//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] +//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) run_applet_no_and_exit(a, argv[0], argv); } # endif -- cgit v1.2.3-55-g6feb From f2cf1cc716216308a8a6d07e3afab23be07a6b02 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Aug 2017 18:45:33 +0200 Subject: noexec: set comm field for noexecs function old new delta set_task_comm - 18 +18 tryexec 152 159 +7 pseudo_exec_argv 321 328 +7 main 106 97 -9 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 3/2 up/down: 34/-13) Total: 23 bytes Signed-off-by: Denys Vlasenko --- include/libbb.h | 5 +++++ libbb/appletlib.c | 13 ++++++++++--- libbb/vfork_daemon_rexec.c | 7 +++++-- shell/ash.c | 5 ++++- shell/hush.c | 5 ++++- 5 files changed, 28 insertions(+), 7 deletions(-) (limited to 'shell') diff --git a/include/libbb.h b/include/libbb.h index 51e8f27a5..e4a19ac04 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1116,6 +1116,11 @@ int run_nofork_applet(int applet_no, char **argv) FAST_FUNC; extern int find_applet_by_name(const char *name) FAST_FUNC; extern void run_applet_no_and_exit(int a, const char *name, char **argv) NORETURN FAST_FUNC; #endif +#if defined(__linux__) +void set_task_comm(const char *comm) FAST_FUNC; +#else +# define set_task_comm(name) ((void)0) +#endif /* Helpers for daemonization. * diff --git a/libbb/appletlib.c b/libbb/appletlib.c index fa28d433b..ce259446b 100644 --- a/libbb/appletlib.c +++ b/libbb/appletlib.c @@ -911,6 +911,14 @@ int busybox_main(int argc UNUSED_PARAM, char **argv) } # endif +#if defined(__linux__) && (NUM_APPLETS > 1) +void FAST_FUNC set_task_comm(const char *comm) +{ + /* okay if too long (truncates) */ + prctl(PR_SET_NAME, (long)comm, 0, 0, 0); +} +#endif + # if NUM_APPLETS > 0 void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **argv) { @@ -1064,15 +1072,14 @@ int main(int argc UNUSED_PARAM, char **argv) applet_name++; applet_name = bb_basename(applet_name); -# if defined(__linux__) /* If we are a result of execv("/proc/self/exe"), fix ugly comm of "exe" */ if (ENABLE_FEATURE_SH_STANDALONE || ENABLE_FEATURE_PREFER_APPLETS || !BB_MMU ) { - prctl(PR_SET_NAME, (long)applet_name, 0, 0, 0); + if (NUM_APPLETS > 1) + set_task_comm(applet_name); } -# endif parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */ run_applet_and_exit(applet_name, argv); diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index 50ecea762..546cc9e36 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -175,8 +175,6 @@ int FAST_FUNC spawn_and_wait(char **argv) return wait4pid(rc); /* child */ -//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] -//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) /* reset some state and run without execing */ /* msg_eol = "\n"; - no caller needs this reinited yet */ @@ -185,6 +183,11 @@ int FAST_FUNC spawn_and_wait(char **argv) * init, or a NOFORK applet. But none of those call us * as of yet (and that should probably always stay true). */ +//TODO: think pidof, pgrep, pkill! +//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), +//but one from procps-ng-3.3.10 needs more! +//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) + set_task_comm(argv[0]); /* xfunc_error_retval and applet_name are init by: */ run_applet_no_and_exit(a, argv[0], argv); } diff --git a/shell/ash.c b/shell/ash.c index 0a323e957..507d15c90 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7803,8 +7803,11 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c while (*envp) putenv(*envp++); popredir(/*drop:*/ 1); -//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] +//TODO: think pidof, pgrep, pkill! +//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), +//but one from procps-ng-3.3.10 needs more! //Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) + set_task_comm(argv[0]); run_applet_no_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ diff --git a/shell/hush.c b/shell/hush.c index b4fe7146b..021c1f0ff 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7387,8 +7387,11 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, /* Without this, "rm -i FILE" can't be ^C'ed: */ switch_off_special_sigs(G.special_sig_mask); debug_printf_exec("running applet '%s'\n", argv[0]); -//TODO: prctl(PR_SET_NAME, (long)argv[0], 0, 0, 0);? [think pidof, pgrep, pkill] +//TODO: think pidof, pgrep, pkill! +//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), +//but one from procps-ng-3.3.10 needs more! //Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) + set_task_comm(argv[0]); run_applet_no_and_exit(a, argv[0], argv); } # endif -- cgit v1.2.3-55-g6feb From c9c1ccc4ed7e7525a2e3c07d855c7a27c3534430 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Aug 2017 18:59:35 +0200 Subject: noexec: do GETOPT_RESET() before entering APPLET_main() hush -c 'yes | head -1' was not happy. function old new delta tryexec 159 169 +10 pseudo_exec_argv 328 338 +10 Signed-off-by: Denys Vlasenko --- libbb/vfork_daemon_rexec.c | 1 + shell/ash.c | 1 + shell/hush.c | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) (limited to 'shell') diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index 546cc9e36..9d3cb9d54 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -176,6 +176,7 @@ int FAST_FUNC spawn_and_wait(char **argv) /* child */ /* reset some state and run without execing */ + GETOPT_RESET(); /* msg_eol = "\n"; - no caller needs this reinited yet */ logmode = LOGMODE_STDIO; diff --git a/shell/ash.c b/shell/ash.c index 507d15c90..bedd27b0d 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7803,6 +7803,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c while (*envp) putenv(*envp++); popredir(/*drop:*/ 1); + GETOPT_RESET(); //TODO: think pidof, pgrep, pkill! //set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), //but one from procps-ng-3.3.10 needs more! diff --git a/shell/hush.c b/shell/hush.c index 021c1f0ff..b890107a2 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7386,12 +7386,13 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, //FIXME: should also close saved redir fds /* Without this, "rm -i FILE" can't be ^C'ed: */ switch_off_special_sigs(G.special_sig_mask); - debug_printf_exec("running applet '%s'\n", argv[0]); + GETOPT_RESET(); //TODO: think pidof, pgrep, pkill! //set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), //but one from procps-ng-3.3.10 needs more! //Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) set_task_comm(argv[0]); + debug_printf_exec("running applet '%s'\n", argv[0]); run_applet_no_and_exit(a, argv[0], argv); } # endif -- cgit v1.2.3-55-g6feb From 80e8e3cc0542ac6242d49eaf223146dcbf2fa0da Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 7 Aug 2017 19:24:57 +0200 Subject: noexec: consolidate code function old new delta run_noexec_applet_and_exit - 61 +61 find_applet_by_name 128 124 -4 run_applet_no_and_exit 441 434 -7 tryexec 169 152 -17 pseudo_exec_argv 338 321 -17 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 0/6 up/down: 61/-48) Total: 13 bytes Signed-off-by: Denys Vlasenko --- include/libbb.h | 5 +++-- libbb/appletlib.c | 2 -- libbb/vfork_daemon_rexec.c | 37 +++++++++++++++++++++---------------- shell/ash.c | 8 +------- shell/hush.c | 8 +------- 5 files changed, 26 insertions(+), 34 deletions(-) (limited to 'shell') diff --git a/include/libbb.h b/include/libbb.h index e4a19ac04..3f3e033fe 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1112,9 +1112,10 @@ int wait_for_exitstatus(pid_t pid) FAST_FUNC; int spawn_and_wait(char **argv) FAST_FUNC; /* Does NOT check that applet is NOFORK, just blindly runs it */ int run_nofork_applet(int applet_no, char **argv) FAST_FUNC; +void run_noexec_applet_and_exit(int a, const char *name, char **argv) NORETURN FAST_FUNC; #ifndef BUILD_INDIVIDUAL -extern int find_applet_by_name(const char *name) FAST_FUNC; -extern void run_applet_no_and_exit(int a, const char *name, char **argv) NORETURN FAST_FUNC; +int find_applet_by_name(const char *name) FAST_FUNC; +void run_applet_no_and_exit(int a, const char *name, char **argv) NORETURN FAST_FUNC; #endif #if defined(__linux__) void set_task_comm(const char *comm) FAST_FUNC; diff --git a/libbb/appletlib.c b/libbb/appletlib.c index ce259446b..5b84920a4 100644 --- a/libbb/appletlib.c +++ b/libbb/appletlib.c @@ -924,8 +924,6 @@ void FAST_FUNC run_applet_no_and_exit(int applet_no, const char *name, char **ar { int argc = string_array_len(argv); - /* Reinit some shared global data */ - xfunc_error_retval = EXIT_FAILURE; /* * We do not use argv[0]: do not want to repeat massaging of * "-/sbin/halt" -> "halt", for example. diff --git a/libbb/vfork_daemon_rexec.c b/libbb/vfork_daemon_rexec.c index 9d3cb9d54..a349459f0 100644 --- a/libbb/vfork_daemon_rexec.c +++ b/libbb/vfork_daemon_rexec.c @@ -158,6 +158,26 @@ int FAST_FUNC run_nofork_applet(int applet_no, char **argv) } #endif /* FEATURE_PREFER_APPLETS || FEATURE_SH_NOFORK */ +#if (NUM_APPLETS > 1) && (ENABLE_FEATURE_PREFER_APPLETS || ENABLE_FEATURE_SH_STANDALONE) +void FAST_FUNC run_noexec_applet_and_exit(int a, const char *name, char **argv) +{ + /* reset some state and run without execing */ + /* msg_eol = "\n"; - no caller needs this reinited yet */ + logmode = LOGMODE_STDIO; + xfunc_error_retval = EXIT_FAILURE; + die_func = NULL; + GETOPT_RESET(); + +//TODO: think pidof, pgrep, pkill! +//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), +//but one from procps-ng-3.3.10 needs more! +//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) + set_task_comm(name); + /* xfunc_error_retval and applet_name are init by: */ + run_applet_no_and_exit(a, name, argv); +} +#endif + int FAST_FUNC spawn_and_wait(char **argv) { int rc; @@ -175,22 +195,7 @@ int FAST_FUNC spawn_and_wait(char **argv) return wait4pid(rc); /* child */ - /* reset some state and run without execing */ - GETOPT_RESET(); - - /* msg_eol = "\n"; - no caller needs this reinited yet */ - logmode = LOGMODE_STDIO; - /* die_func = NULL; - needed if the caller is a shell, - * init, or a NOFORK applet. But none of those call us - * as of yet (and that should probably always stay true). - */ -//TODO: think pidof, pgrep, pkill! -//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), -//but one from procps-ng-3.3.10 needs more! -//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) - set_task_comm(argv[0]); - /* xfunc_error_retval and applet_name are init by: */ - run_applet_no_and_exit(a, argv[0], argv); + run_noexec_applet_and_exit(a, argv[0], argv); } # endif } diff --git a/shell/ash.c b/shell/ash.c index bedd27b0d..6dc1cfef7 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7803,13 +7803,7 @@ tryexec(IF_FEATURE_SH_STANDALONE(int applet_no,) const char *cmd, char **argv, c while (*envp) putenv(*envp++); popredir(/*drop:*/ 1); - GETOPT_RESET(); -//TODO: think pidof, pgrep, pkill! -//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), -//but one from procps-ng-3.3.10 needs more! -//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) - set_task_comm(argv[0]); - run_applet_no_and_exit(applet_no, cmd, argv); + run_noexec_applet_and_exit(applet_no, cmd, argv); } /* re-exec ourselves with the new arguments */ execve(bb_busybox_exec_path, argv, envp); diff --git a/shell/hush.c b/shell/hush.c index b890107a2..8dc531657 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -7386,14 +7386,8 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, //FIXME: should also close saved redir fds /* Without this, "rm -i FILE" can't be ^C'ed: */ switch_off_special_sigs(G.special_sig_mask); - GETOPT_RESET(); -//TODO: think pidof, pgrep, pkill! -//set_task_comm() makes our pidof find NOEXECs (e.g. "yes >/dev/null"), -//but one from procps-ng-3.3.10 needs more! -//Rewrite /proc/PID/cmdline? (need to save argv0 and length at init for this to work!) - set_task_comm(argv[0]); debug_printf_exec("running applet '%s'\n", argv[0]); - run_applet_no_and_exit(a, argv[0], argv); + run_noexec_applet_and_exit(a, argv[0], argv); } # endif /* Re-exec ourselves */ -- cgit v1.2.3-55-g6feb From 3bef5d89b0c667e9fb7d1d9b44ba9b30d4d084e4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 8 Aug 2017 16:46:39 +0200 Subject: ash: implement -d DELIM option for read The POSIX standard only requires the read builtin to handle -r: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html However, Bash introduced the option -d to override IFS for just one invocation, and it is quite useful. It is also super easy to implement in BusyBox' ash, so let's do that. The motivation: This option is used by Git's test suite. function old new delta .rodata 163505 163587 +82 shell_builtin_read 1244 1289 +45 readcmd 233 259 +26 builtin_read 258 263 +5 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 158/0) Total: 158 bytes Signed-off-by: Johannes Schindelin Signed-off-by: Denys Vlasenko --- shell/ash.c | 15 ++++++++++++--- shell/shell_common.c | 10 +++++++--- shell/shell_common.h | 3 ++- 3 files changed, 21 insertions(+), 7 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 6dc1cfef7..fd1772351 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -189,6 +189,8 @@ #define BASH_HOSTNAME_VAR ENABLE_ASH_BASH_COMPAT #define BASH_SHLVL_VAR ENABLE_ASH_BASH_COMPAT #define BASH_XTRACEFD ENABLE_ASH_BASH_COMPAT +#define BASH_READ_D ENABLE_ASH_BASH_COMPAT +#define IF_BASH_READ_D IF_ASH_BASH_COMPAT #if defined(__ANDROID_API__) && __ANDROID_API__ <= 24 /* Bionic at least up to version 24 has no glob() */ @@ -13402,10 +13404,10 @@ letcmd(int argc UNUSED_PARAM, char **argv) * -p PROMPT Display PROMPT on stderr (if input is from tty) * -t SECONDS Timeout after SECONDS (tty or pipe only) * -u FD Read from given FD instead of fd 0 + * -d DELIM End on DELIM char, not newline * This uses unbuffered input, which may be avoidable in some cases. * TODO: bash also has: * -a ARRAY Read into array[0],[1],etc - * -d DELIM End on DELIM char, not newline * -e Use line editing (tty only) */ static int FAST_FUNC @@ -13415,11 +13417,12 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) char *opt_p = NULL; char *opt_t = NULL; char *opt_u = NULL; + char *opt_d = NULL; /* optimized out if !BASH */ int read_flags = 0; const char *r; int i; - while ((i = nextopt("p:u:rt:n:s")) != '\0') { + while ((i = nextopt("p:u:rt:n:sd:")) != '\0') { switch (i) { case 'p': opt_p = optionarg; @@ -13439,6 +13442,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) case 'u': opt_u = optionarg; break; +#if BASH_READ_D + case 'd': + opt_d = optionarg; + break; +#endif default: break; } @@ -13456,7 +13464,8 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) opt_n, opt_p, opt_t, - opt_u + opt_u, + opt_d ); INT_ON; diff --git a/shell/shell_common.c b/shell/shell_common.c index a9f8d8413..2db8ea3e2 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -54,7 +54,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), const char *opt_n, const char *opt_p, const char *opt_t, - const char *opt_u + const char *opt_u, + const char *opt_d ) { struct pollfd pfd[1]; @@ -237,14 +238,17 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), continue; } } - if (c == '\n') + if (opt_d) { + if (c == *opt_d) + break; + } else if (c == '\n') break; /* $IFS splitting. NOT done if we run "read" * without variable names (bash compat). * Thus, "read" and "read REPLY" are not the same. */ - if (argv[0]) { + if (!opt_d && argv[0]) { /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */ const char *is_ifs = strchr(ifs, c); if (startword && is_ifs) { diff --git a/shell/shell_common.h b/shell/shell_common.h index a82535c86..1b79bffca 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h @@ -42,7 +42,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), const char *opt_n, const char *opt_p, const char *opt_t, - const char *opt_u + const char *opt_u, + const char *opt_d ); int FAST_FUNC -- cgit v1.2.3-55-g6feb From 1f41c885fcafc67c9e957b74ab9d223e09ff949a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 9 Aug 2017 13:52:36 +0200 Subject: hush: implement -d DELIM option for 'read' The POSIX standard only requires the 'read' builtin to handle '-r': http://pubs.opengroup.org/onlinepubs/9699919799/utilities/read.html However, Bash introduced the option '-d ' to override IFS for just one invocation, and it is quite useful. We already support this in ash, let's add it to hush, too. function old new delta builtin_read 263 284 +21 .rodata 163587 163589 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/0 up/down: 23/0) Total: 23 bytes Signed-off-by: Johannes Schindelin Signed-off-by: Denys Vlasenko --- shell/hush.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 8dc531657..af3b95b86 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -352,6 +352,7 @@ #define BASH_SOURCE ENABLE_HUSH_BASH_COMPAT #define BASH_HOSTNAME_VAR ENABLE_HUSH_BASH_COMPAT #define BASH_TEST2 (ENABLE_HUSH_BASH_COMPAT && ENABLE_HUSH_TEST) +#define BASH_READ_D ENABLE_HUSH_BASH_COMPAT /* Build knobs */ @@ -9434,13 +9435,20 @@ static int FAST_FUNC builtin_read(char **argv) char *opt_p = NULL; char *opt_t = NULL; char *opt_u = NULL; + char *opt_d = NULL; /* optimized out if !BASH */ const char *ifs; int read_flags; /* "!": do not abort on errors. * Option string must start with "sr" to match BUILTIN_READ_xxx */ - read_flags = getopt32(argv, "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u); + read_flags = getopt32(argv, +#if BASH_READ_D + "!srn:p:t:u:d:", &opt_n, &opt_p, &opt_t, &opt_u, &opt_d +#else + "!srn:p:t:u:", &opt_n, &opt_p, &opt_t, &opt_u +#endif + ); if (read_flags == (uint32_t)-1) return EXIT_FAILURE; argv += optind; @@ -9454,7 +9462,8 @@ static int FAST_FUNC builtin_read(char **argv) opt_n, opt_p, opt_t, - opt_u + opt_u, + opt_d ); if ((uintptr_t)r == 1 && errno == EINTR) { -- cgit v1.2.3-55-g6feb From cde46f75cbe8069e06b68396a7f6dc9564fbf2c4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 9 Aug 2017 14:04:07 +0200 Subject: shell: more efficient check for EOL in read function old new delta shell_builtin_read 1334 1320 -14 Signed-off-by: Denys Vlasenko --- shell/shell_common.c | 7 +++---- shell/shell_common.h | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/shell_common.c b/shell/shell_common.c index 2db8ea3e2..7a0799ed5 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -65,6 +65,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), int nchars; /* -n NUM */ char **pp; char *buffer; + char delim; struct termios tty, old_tty; const char *retval; int bufpos; /* need to be able to hold -1 */ @@ -185,6 +186,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), end_ms += (unsigned)monotonic_ms(); buffer = NULL; bufpos = 0; + delim = opt_d ? *opt_d : '\n'; do { char c; int timeout; @@ -238,10 +240,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), continue; } } - if (opt_d) { - if (c == *opt_d) - break; - } else if (c == '\n') + if (c == delim) /* '\n' or -d CHAR */ break; /* $IFS splitting. NOT done if we run "read" diff --git a/shell/shell_common.h b/shell/shell_common.h index 1b79bffca..875fd9ea7 100644 --- a/shell/shell_common.h +++ b/shell/shell_common.h @@ -34,6 +34,11 @@ enum { BUILTIN_READ_SILENT = 1 << 0, BUILTIN_READ_RAW = 1 << 1, }; +//TODO? do not provide bashisms if not asked for: +//#if !ENABLE_HUSH_BASH_COMPAT && !ENABLE_ASH_BASH_COMPAT +//#define shell_builtin_read(setvar,argv,ifs,read_flags,n,p,t,u,d) +// shell_builtin_read(setvar,argv,ifs,read_flags) +//#endif const char* FAST_FUNC shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), char **argv, -- cgit v1.2.3-55-g6feb From 11f2e99c13b42675bb65cf2cfd3e3a98f95f2cee Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 10 Aug 2017 16:34:03 +0200 Subject: hush: optional times builtin function old new delta builtin_times - 108 +108 bltins1 360 372 +12 static.times_tbl - 9 +9 ------------------------------------------------------------------------------ (add/remove: 2/0 grow/shrink: 1/0 up/down: 129/0) Total: 129 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 16 +++++++++------- shell/hush.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index fd1772351..5c03f1fdc 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -13351,21 +13351,23 @@ static const unsigned char timescmd_str[] ALIGN1 = { static int FAST_FUNC timescmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) { - unsigned long clk_tck, s, t; + unsigned clk_tck; const unsigned char *p; struct tms buf; clk_tck = bb_clk_tck(); - times(&buf); + times(&buf); p = timescmd_str; do { + unsigned sec, frac; + unsigned long t; t = *(clock_t *)(((char *) &buf) + p[1]); - s = t / clk_tck; - t = t % clk_tck; - out1fmt("%lum%lu.%03lus%c", - s / 60, s % 60, - (t * 1000) / clk_tck, + sec = t / clk_tck; + frac = t % clk_tck; + out1fmt("%um%u.%03us%c", + sec / 60, sec % 60, + (frac * 1000) / clk_tck, p[0]); p += 2; } while (*p); diff --git a/shell/hush.c b/shell/hush.c index af3b95b86..b53d1dcfb 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -48,7 +48,7 @@ * tilde expansion * aliases * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts, times: + * [un]alias, command, fc, getopts: * command -v CMD: print "/path/to/CMD" * prints "CMD" for builtins * prints "alias ALIAS='EXPANSION'" for aliases @@ -59,7 +59,6 @@ * -p: use default $PATH * command BLTIN: disables special-ness (e.g. errors do not abort) * getopts: getopt() for shells - * times: print getrusage(SELF/CHILDREN).ru_utime/ru_stime * fc -l[nr] [BEG] [END]: list range of commands in history * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP @@ -265,6 +264,11 @@ //config: default y //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: +//config:config HUSH_TIMES +//config: bool "times builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: //config:config HUSH_READ //config: bool "read builtin" //config: default y @@ -325,6 +329,7 @@ #if ENABLE_HUSH_CASE # include #endif +#include #include /* for setting $HOSTNAME */ #include "busybox.h" /* for APPLET_IS_NOFORK/NOEXEC */ @@ -1011,6 +1016,9 @@ static int builtin_trap(char **argv) FAST_FUNC; #if ENABLE_HUSH_TYPE static int builtin_type(char **argv) FAST_FUNC; #endif +#if ENABLE_HUSH_TIMES +static int builtin_times(char **argv) FAST_FUNC; +#endif static int builtin_true(char **argv) FAST_FUNC; #if ENABLE_HUSH_UMASK static int builtin_umask(char **argv) FAST_FUNC; @@ -1105,6 +1113,9 @@ static const struct built_in_command bltins1[] = { #if BASH_SOURCE BLTIN("source" , builtin_source , NULL), #endif +#if ENABLE_HUSH_TIMES + BLTIN("times" , builtin_times , NULL), +#endif #if ENABLE_HUSH_TRAP BLTIN("trap" , builtin_trap , "Trap signals"), #endif @@ -10407,6 +10418,41 @@ static int FAST_FUNC builtin_return(char **argv) } #endif +#if ENABLE_HUSH_TIMES +static int FAST_FUNC builtin_times(char **argv UNUSED_PARAM) +{ + static const uint8_t times_tbl[] ALIGN1 = { + ' ', offsetof(struct tms, tms_utime), + '\n', offsetof(struct tms, tms_stime), + ' ', offsetof(struct tms, tms_cutime), + '\n', offsetof(struct tms, tms_cstime), + 0 + }; + const uint8_t *p; + unsigned clk_tck; + struct tms buf; + + clk_tck = bb_clk_tck(); + + times(&buf); + p = times_tbl; + do { + unsigned sec, frac; + unsigned long t; + t = *(clock_t *)(((char *) &buf) + p[1]); + sec = t / clk_tck; + frac = t % clk_tck; + printf("%um%u.%03us%c", + sec / 60, sec % 60, + (frac * 1000) / clk_tck, + p[0]); + p += 2; + } while (*p); + + return EXIT_SUCCESS; +} +#endif + #if ENABLE_HUSH_MEMLEAK static int FAST_FUNC builtin_memleak(char **argv UNUSED_PARAM) { -- cgit v1.2.3-55-g6feb From 4628945cd8d4679912f126d5f18f954210abb7d0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 00:59:36 +0200 Subject: ash: fix "unset OPTIND" throwing an error message Added test was failing quite severely. Now only one subtest fails (OPTERR=0 has no effect). Signed-off-by: Denys Vlasenko --- shell/ash.c | 4 +- shell/ash_test/ash-getopts/getopt_simple.right | 34 ++++++++++++ shell/ash_test/ash-getopts/getopt_simple.tests | 75 ++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-getopts/getopt_simple.right create mode 100755 shell/ash_test/ash-getopts/getopt_simple.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 5c03f1fdc..15c7c325a 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -2099,7 +2099,9 @@ extern struct globals_var *const ash_ptr_to_globals_var; static void FAST_FUNC getoptsreset(const char *value) { - shellparam.optind = number(value) ?: 1; + shellparam.optind = 1; + if (is_number(value)) + shellparam.optind = number(value) ?: 1; shellparam.optoff = -1; } #endif diff --git a/shell/ash_test/ash-getopts/getopt_simple.right b/shell/ash_test/ash-getopts/getopt_simple.right new file mode 100644 index 000000000..07e3c57f5 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_simple.right @@ -0,0 +1,34 @@ +*** no OPTIND, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** OPTIND=1, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** OPTIND=0, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** unset OPTIND, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** optstring:'ab' args:-a -b c +1 rc:0 var:'a' OPTIND:2 +2 rc:0 var:'b' OPTIND:3 +3 rc:1 var:'?' OPTIND:3 +*** unset OPTIND, optstring:'ab' args:-a c -c -b d +var:'a' OPTIND:2 +exited: rc:0 var:'?' OPTIND:2 +*** unset OPTIND, optstring:'ab' args:-a -c -b d +var:'a' OPTIND:2 +Illegal option -c +var:'?' OPTIND:3 +var:'b' OPTIND:4 +exited: rc:0 var:'?' OPTIND:4 +*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d +var:'a' OPTIND:2 +var:'?' OPTIND:3 +var:'b' OPTIND:4 +exited: rc:0 var:'?' OPTIND:4 diff --git a/shell/ash_test/ash-getopts/getopt_simple.tests b/shell/ash_test/ash-getopts/getopt_simple.tests new file mode 100755 index 000000000..8615ae366 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_simple.tests @@ -0,0 +1,75 @@ +# Simple usage cases for getopts. +# +# OPTIND is either not touched at all (first loop with getopts, +# relying on shell startup init), or getopts state is reset +# before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0". +# +# Each option is a separate argument (no "-abc"). This conceptually +# needs only $OPTIND to hold getopts state. +# +# We check that loop does not stop on unknown option (sets "?"), +# stops on _first_ non-option argument. + +echo "*** no OPTIND, optstring:'ab' args:-a -b c" +var=QWERTY +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +# unfortunately, "rc:0" is shown since while's overall exitcode is "success" +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior =1 +echo "*** OPTIND=1, optstring:'ab' args:-a -b c" +OPTIND=1 +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior =0 +echo "*** OPTIND=0, optstring:'ab' args:-a -b c" +OPTIND=0 +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior "unset" +echo "*** unset OPTIND, optstring:'ab' args:-a -b c" +unset OPTIND +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# What is the final exitcode? +echo "*** optstring:'ab' args:-a -b c" +unset OPTIND +getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND" +getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND" +getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND" + +# Where would it stop? c or -c? +echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d" +unset OPTIND +while getopts "ab" var -a c -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# What happens on unknown option? +echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d" +unset OPTIND +while getopts "ab" var -a -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# ORTERR=0 suppresses error message? +echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d" +unset OPTIND +OPTERR=0 +while getopts "ab" var -a -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" -- cgit v1.2.3-55-g6feb From 74d40589288890fffea437ed2f38ad1f2dc740e5 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 01:32:46 +0200 Subject: hush: getopts builtin function old new delta builtin_getopts - 271 +271 bltins1 372 384 +12 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/0 up/down: 283/0) Total: 283 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-getopts/getopt_positional.right | 6 ++ shell/ash_test/ash-getopts/getopt_positional.tests | 8 +++ shell/hush.c | 77 +++++++++++++++++++++- .../hush_test/hush-getopts/getopt_positional.right | 6 ++ .../hush_test/hush-getopts/getopt_positional.tests | 8 +++ shell/hush_test/hush-getopts/getopt_simple.right | 34 ++++++++++ shell/hush_test/hush-getopts/getopt_simple.tests | 75 +++++++++++++++++++++ 7 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-getopts/getopt_positional.right create mode 100755 shell/ash_test/ash-getopts/getopt_positional.tests create mode 100644 shell/hush_test/hush-getopts/getopt_positional.right create mode 100755 shell/hush_test/hush-getopts/getopt_positional.tests create mode 100644 shell/hush_test/hush-getopts/getopt_simple.right create mode 100755 shell/hush_test/hush-getopts/getopt_simple.tests (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_positional.right b/shell/ash_test/ash-getopts/getopt_positional.right new file mode 100644 index 000000000..37d0ec845 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.right @@ -0,0 +1,6 @@ +*** no OPTIND, optstring:'we' args:-q -w -e r -t -y +Illegal option -q +var:'?' OPTIND:2 +var:'w' OPTIND:3 +var:'e' OPTIND:4 +exited: var:'?' OPTIND:4 diff --git a/shell/ash_test/ash-getopts/getopt_positional.tests b/shell/ash_test/ash-getopts/getopt_positional.tests new file mode 100755 index 000000000..a5404a2a0 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_positional.tests @@ -0,0 +1,8 @@ +set -- -q -w -e r -t -y +echo "*** no OPTIND, optstring:'we' args:$*" +var=QWERTY +while getopts "we" var; do + echo "var:'$var' OPTIND:$OPTIND" +done +# unfortunately, "rc:0" is shown since while's overall exitcode is "success" +echo "exited: var:'$var' OPTIND:$OPTIND" diff --git a/shell/hush.c b/shell/hush.c index b53d1dcfb..dba12c12e 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -48,7 +48,7 @@ * tilde expansion * aliases * builtins mandated by standards we don't support: - * [un]alias, command, fc, getopts: + * [un]alias, command, fc: * command -v CMD: print "/path/to/CMD" * prints "CMD" for builtins * prints "alias ALIAS='EXPANSION'" for aliases @@ -58,7 +58,6 @@ * (can use this to override standalone shell as well) * -p: use default $PATH * command BLTIN: disables special-ness (e.g. errors do not abort) - * getopts: getopt() for shells * fc -l[nr] [BEG] [END]: list range of commands in history * fc [-e EDITOR] [BEG] [END]: edit/rerun range of commands * fc -s [PAT=REP] [CMD]: rerun CMD, replacing PAT with REP @@ -294,6 +293,11 @@ //config: default y //config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH //config: +//config:config HUSH_GETOPTS +//config: bool "getopts builtin" +//config: default y +//config: depends on HUSH || SH_IS_HUSH || BASH_IS_HUSH +//config: //config:config HUSH_MEMLEAK //config: bool "memleak builtin (debugging)" //config: default n @@ -983,6 +987,9 @@ static int builtin_readonly(char **argv) FAST_FUNC; static int builtin_fg_bg(char **argv) FAST_FUNC; static int builtin_jobs(char **argv) FAST_FUNC; #endif +#if ENABLE_HUSH_GETOPTS +static int builtin_getopts(char **argv) FAST_FUNC; +#endif #if ENABLE_HUSH_HELP static int builtin_help(char **argv) FAST_FUNC; #endif @@ -1079,6 +1086,9 @@ static const struct built_in_command bltins1[] = { #if ENABLE_HUSH_JOB BLTIN("fg" , builtin_fg_bg , "Bring job to foreground"), #endif +#if ENABLE_HUSH_GETOPTS + BLTIN("getopts" , builtin_getopts , NULL), +#endif #if ENABLE_HUSH_HELP BLTIN("help" , builtin_help , NULL), #endif @@ -9859,6 +9869,69 @@ static int FAST_FUNC builtin_shift(char **argv) return EXIT_FAILURE; } +#if ENABLE_HUSH_GETOPTS +static int FAST_FUNC builtin_getopts(char **argv) +{ +/* +TODO: +if a character is followed by a colon, the option is expected to have +an argument, which should be separated from it by white space. +When an option requires an argument, getopts places that argument into +the variable OPTARG. + +If an invalid option is seen, getopts places ? into VAR and, if +not silent, prints an error message and unsets OPTARG. If +getopts is silent, the option character found is placed in +OPTARG and no diagnostic message is printed. + +If a required argument is not found, and getopts is not silent, +a question mark (?) is placed in VAR, OPTARG is unset, and a +diagnostic message is printed. If getopts is silent, then a +colon (:) is placed in VAR and OPTARG is set to the option +character found. + +Test that VAR is a valid variable name? +*/ + char cbuf[2]; + const char *cp, *optstring, *var; + int c, exitcode; + + optstring = *++argv; + if (!optstring || !(var = *++argv)) { + bb_error_msg("usage: getopts OPTSTRING VAR [ARGS]"); + return EXIT_FAILURE; + } + + cp = get_local_var_value("OPTERR"); + opterr = cp ? atoi(cp) : 1; + cp = get_local_var_value("OPTIND"); + optind = cp ? atoi(cp) : 0; + + /* getopts stops on first non-option. Add "+" to force that */ + /*if (optstring[0] != '+')*/ { + char *s = alloca(strlen(optstring) + 2); + sprintf(s, "+%s", optstring); + optstring = s; + } + + if (argv[1]) + argv[0] = G.global_argv[0]; /* for error messages */ + else + argv = G.global_argv; + c = getopt(string_array_len(argv), argv, optstring); + exitcode = EXIT_SUCCESS; + if (c < 0) { /* -1: end of options */ + exitcode = EXIT_FAILURE; + c = '?'; + } + cbuf[0] = c; + cbuf[1] = '\0'; + set_local_var_from_halves(var, cbuf); + set_local_var_from_halves("OPTIND", utoa(optind)); + return exitcode; +} +#endif + static int FAST_FUNC builtin_source(char **argv) { char *arg_path, *filename; diff --git a/shell/hush_test/hush-getopts/getopt_positional.right b/shell/hush_test/hush-getopts/getopt_positional.right new file mode 100644 index 000000000..f1c942476 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.right @@ -0,0 +1,6 @@ +*** no OPTIND, optstring:'we' args:-q -w -e r -t -y +./getopt_positional.tests: invalid option -- q +var:'?' OPTIND:2 +var:'w' OPTIND:3 +var:'e' OPTIND:4 +exited: var:'?' OPTIND:4 diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests new file mode 100755 index 000000000..a5404a2a0 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_positional.tests @@ -0,0 +1,8 @@ +set -- -q -w -e r -t -y +echo "*** no OPTIND, optstring:'we' args:$*" +var=QWERTY +while getopts "we" var; do + echo "var:'$var' OPTIND:$OPTIND" +done +# unfortunately, "rc:0" is shown since while's overall exitcode is "success" +echo "exited: var:'$var' OPTIND:$OPTIND" diff --git a/shell/hush_test/hush-getopts/getopt_simple.right b/shell/hush_test/hush-getopts/getopt_simple.right new file mode 100644 index 000000000..b4855fa1a --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.right @@ -0,0 +1,34 @@ +*** no OPTIND, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** OPTIND=1, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** OPTIND=0, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** unset OPTIND, optstring:'ab' args:-a -b c +var:'a' OPTIND:2 +var:'b' OPTIND:3 +exited: rc:0 var:'?' OPTIND:3 +*** optstring:'ab' args:-a -b c +1 rc:0 var:'a' OPTIND:2 +2 rc:0 var:'b' OPTIND:3 +3 rc:1 var:'?' OPTIND:3 +*** unset OPTIND, optstring:'ab' args:-a c -c -b d +var:'a' OPTIND:2 +exited: rc:0 var:'?' OPTIND:2 +*** unset OPTIND, optstring:'ab' args:-a -c -b d +var:'a' OPTIND:2 +./getopt_simple.tests: invalid option -- c +var:'?' OPTIND:3 +var:'b' OPTIND:4 +exited: rc:0 var:'?' OPTIND:4 +*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d +var:'a' OPTIND:2 +var:'?' OPTIND:3 +var:'b' OPTIND:4 +exited: rc:0 var:'?' OPTIND:4 diff --git a/shell/hush_test/hush-getopts/getopt_simple.tests b/shell/hush_test/hush-getopts/getopt_simple.tests new file mode 100755 index 000000000..8615ae366 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_simple.tests @@ -0,0 +1,75 @@ +# Simple usage cases for getopts. +# +# OPTIND is either not touched at all (first loop with getopts, +# relying on shell startup init), or getopts state is reset +# before new loop with "unset OPTIND", "OPTIND=1" or "OPTIND=0". +# +# Each option is a separate argument (no "-abc"). This conceptually +# needs only $OPTIND to hold getopts state. +# +# We check that loop does not stop on unknown option (sets "?"), +# stops on _first_ non-option argument. + +echo "*** no OPTIND, optstring:'ab' args:-a -b c" +var=QWERTY +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +# unfortunately, "rc:0" is shown since while's overall exitcode is "success" +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior =1 +echo "*** OPTIND=1, optstring:'ab' args:-a -b c" +OPTIND=1 +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior =0 +echo "*** OPTIND=0, optstring:'ab' args:-a -b c" +OPTIND=0 +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# Resetting behavior "unset" +echo "*** unset OPTIND, optstring:'ab' args:-a -b c" +unset OPTIND +while getopts "ab" var -a -b c; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# What is the final exitcode? +echo "*** optstring:'ab' args:-a -b c" +unset OPTIND +getopts "ab" var -a -b c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND" +getopts "ab" var -a -b c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND" +getopts "ab" var -a -b c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND" + +# Where would it stop? c or -c? +echo "*** unset OPTIND, optstring:'ab' args:-a c -c -b d" +unset OPTIND +while getopts "ab" var -a c -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# What happens on unknown option? +echo "*** unset OPTIND, optstring:'ab' args:-a -c -b d" +unset OPTIND +while getopts "ab" var -a -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +# ORTERR=0 suppresses error message? +echo "*** unset OPTIND, OPTERR=0, optstring:'ab' args:-a -c -b d" +unset OPTIND +OPTERR=0 +while getopts "ab" var -a -c -b d; do + echo "var:'$var' OPTIND:$OPTIND" +done +echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" -- cgit v1.2.3-55-g6feb From 81f962f3df0d7194b7a52c6f83259727759094c4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 02:05:21 +0200 Subject: hush: teach getopts to set/unset OPTARG Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-getopts/getopt_optarg.right | 18 ++++++++++++++++++ shell/ash_test/ash-getopts/getopt_optarg.tests | 16 ++++++++++++++++ shell/hush.c | 10 +++++----- shell/hush_test/hush-getopts/getopt_optarg.right | 18 ++++++++++++++++++ shell/hush_test/hush-getopts/getopt_optarg.tests | 16 ++++++++++++++++ shell/hush_test/hush-getopts/getopt_positional.tests | 1 - 6 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 shell/ash_test/ash-getopts/getopt_optarg.right create mode 100755 shell/ash_test/ash-getopts/getopt_optarg.tests create mode 100644 shell/hush_test/hush-getopts/getopt_optarg.right create mode 100755 shell/hush_test/hush-getopts/getopt_optarg.tests (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_optarg.right b/shell/ash_test/ash-getopts/getopt_optarg.right new file mode 100644 index 000000000..dff28de57 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_optarg.right @@ -0,0 +1,18 @@ +*** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y +Illegal option -q +var:'?' OPTIND:2 OPTARG:'' +var:'w' OPTIND:4 OPTARG:'e' +Illegal option -r +var:'?' OPTIND:5 OPTARG:'' +var:'t' OPTIND:6 OPTARG:'' +Illegal option -y +var:'?' OPTIND:7 OPTARG:'' +exited: var:'?' OPTIND:7 OPTARG:'' +*** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t +var:'w' OPTIND:3 OPTARG:'1' +var:'w' OPTIND:4 OPTARG:'2' +var:'w' OPTIND:6 OPTARG:'-e' +var:'e' OPTIND:7 OPTARG:'' +var:'t' OPTIND:8 OPTARG:'' +var:'t' OPTIND:9 OPTARG:'' +exited: var:'?' OPTIND:9 OPTARG:'' diff --git a/shell/ash_test/ash-getopts/getopt_optarg.tests b/shell/ash_test/ash-getopts/getopt_optarg.tests new file mode 100755 index 000000000..b346284f0 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_optarg.tests @@ -0,0 +1,16 @@ +set -- -q -w e -r -t -y +echo "*** no OPTIND, optstring:'w:et' args:$*" +var=QWERTY +OPTARG=ASDFGH +while getopts "w:et" var; do + echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +done +echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +set -- -w 1 -w2 -w -e -e -t -t +echo "*** OPTIND=0, optstring:'w:et' args:$*" +OPTIND=0 +while getopts "w:et" var; do + echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +done +echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" diff --git a/shell/hush.c b/shell/hush.c index dba12c12e..f9a8de423 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9874,11 +9874,6 @@ static int FAST_FUNC builtin_getopts(char **argv) { /* TODO: -if a character is followed by a colon, the option is expected to have -an argument, which should be separated from it by white space. -When an option requires an argument, getopts places that argument into -the variable OPTARG. - If an invalid option is seen, getopts places ? into VAR and, if not silent, prints an error message and unsets OPTARG. If getopts is silent, the option character found is placed in @@ -9906,6 +9901,7 @@ Test that VAR is a valid variable name? opterr = cp ? atoi(cp) : 1; cp = get_local_var_value("OPTIND"); optind = cp ? atoi(cp) : 0; + optarg = NULL; /* getopts stops on first non-option. Add "+" to force that */ /*if (optstring[0] != '+')*/ { @@ -9924,6 +9920,10 @@ Test that VAR is a valid variable name? exitcode = EXIT_FAILURE; c = '?'; } + if (optarg) + set_local_var_from_halves("OPTARG", optarg); + else + unset_local_var("OPTARG"); cbuf[0] = c; cbuf[1] = '\0'; set_local_var_from_halves(var, cbuf); diff --git a/shell/hush_test/hush-getopts/getopt_optarg.right b/shell/hush_test/hush-getopts/getopt_optarg.right new file mode 100644 index 000000000..9dbd8460e --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_optarg.right @@ -0,0 +1,18 @@ +*** no OPTIND, optstring:'w:et' args:-q -w e -r -t -y +./getopt_optarg.tests: invalid option -- q +var:'?' OPTIND:2 OPTARG:'' +var:'w' OPTIND:4 OPTARG:'e' +./getopt_optarg.tests: invalid option -- r +var:'?' OPTIND:5 OPTARG:'' +var:'t' OPTIND:6 OPTARG:'' +./getopt_optarg.tests: invalid option -- y +var:'?' OPTIND:7 OPTARG:'' +exited: var:'?' OPTIND:7 OPTARG:'' +*** OPTIND=0, optstring:'w:et' args:-w 1 -w2 -w -e -e -t -t +var:'w' OPTIND:3 OPTARG:'1' +var:'w' OPTIND:4 OPTARG:'2' +var:'w' OPTIND:6 OPTARG:'-e' +var:'e' OPTIND:7 OPTARG:'' +var:'t' OPTIND:8 OPTARG:'' +var:'t' OPTIND:9 OPTARG:'' +exited: var:'?' OPTIND:9 OPTARG:'' diff --git a/shell/hush_test/hush-getopts/getopt_optarg.tests b/shell/hush_test/hush-getopts/getopt_optarg.tests new file mode 100755 index 000000000..b346284f0 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_optarg.tests @@ -0,0 +1,16 @@ +set -- -q -w e -r -t -y +echo "*** no OPTIND, optstring:'w:et' args:$*" +var=QWERTY +OPTARG=ASDFGH +while getopts "w:et" var; do + echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +done +echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +set -- -w 1 -w2 -w -e -e -t -t +echo "*** OPTIND=0, optstring:'w:et' args:$*" +OPTIND=0 +while getopts "w:et" var; do + echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +done +echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests index a5404a2a0..ddf063363 100755 --- a/shell/hush_test/hush-getopts/getopt_positional.tests +++ b/shell/hush_test/hush-getopts/getopt_positional.tests @@ -4,5 +4,4 @@ var=QWERTY while getopts "we" var; do echo "var:'$var' OPTIND:$OPTIND" done -# unfortunately, "rc:0" is shown since while's overall exitcode is "success" echo "exited: var:'$var' OPTIND:$OPTIND" -- cgit v1.2.3-55-g6feb From 9a7d0a01918df5a963b6c90177b321ff743282b2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 02:37:48 +0200 Subject: shell: add OPTARG poisoning to getopt_optarg.tests ash fails this! Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-getopts/getopt_optarg.tests | 2 ++ shell/hush.c | 19 ++++++++++++++----- shell/hush_test/hush-getopts/getopt_optarg.tests | 2 ++ 3 files changed, 18 insertions(+), 5 deletions(-) (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_optarg.tests b/shell/ash_test/ash-getopts/getopt_optarg.tests index b346284f0..33682e868 100755 --- a/shell/ash_test/ash-getopts/getopt_optarg.tests +++ b/shell/ash_test/ash-getopts/getopt_optarg.tests @@ -4,6 +4,7 @@ var=QWERTY OPTARG=ASDFGH while getopts "w:et" var; do echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + OPTARG=ASDFGH done echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" @@ -12,5 +13,6 @@ echo "*** OPTIND=0, optstring:'w:et' args:$*" OPTIND=0 while getopts "w:et" var; do echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + OPTARG=ASDFGH done echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" diff --git a/shell/hush.c b/shell/hush.c index f9a8de423..dc05f24b9 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9872,7 +9872,8 @@ static int FAST_FUNC builtin_shift(char **argv) #if ENABLE_HUSH_GETOPTS static int FAST_FUNC builtin_getopts(char **argv) { -/* +/* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html + TODO: If an invalid option is seen, getopts places ? into VAR and, if not silent, prints an error message and unsets OPTARG. If @@ -9886,6 +9887,8 @@ colon (:) is placed in VAR and OPTARG is set to the option character found. Test that VAR is a valid variable name? + +"Whenever the shell is invoked, OPTIND shall be initialized to 1" */ char cbuf[2]; const char *cp, *optstring, *var; @@ -9920,14 +9923,20 @@ Test that VAR is a valid variable name? exitcode = EXIT_FAILURE; c = '?'; } - if (optarg) - set_local_var_from_halves("OPTARG", optarg); - else - unset_local_var("OPTARG"); cbuf[0] = c; cbuf[1] = '\0'; set_local_var_from_halves(var, cbuf); set_local_var_from_halves("OPTIND", utoa(optind)); + + /* Always set or unset, never left as-is, even on exit/error: + * "If no option was found, or if the option that was found + * does not have an option-argument, OPTARG shall be unset." + */ + if (optarg) + set_local_var_from_halves("OPTARG", optarg); + else + unset_local_var("OPTARG"); + return exitcode; } #endif diff --git a/shell/hush_test/hush-getopts/getopt_optarg.tests b/shell/hush_test/hush-getopts/getopt_optarg.tests index b346284f0..33682e868 100755 --- a/shell/hush_test/hush-getopts/getopt_optarg.tests +++ b/shell/hush_test/hush-getopts/getopt_optarg.tests @@ -4,6 +4,7 @@ var=QWERTY OPTARG=ASDFGH while getopts "w:et" var; do echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + OPTARG=ASDFGH done echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" @@ -12,5 +13,6 @@ echo "*** OPTIND=0, optstring:'w:et' args:$*" OPTIND=0 while getopts "w:et" var; do echo "var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + OPTARG=ASDFGH done echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" -- cgit v1.2.3-55-g6feb From d16e612c93d1a698c1a9d931b786cf3500996ae3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 15:41:39 +0200 Subject: hush: fix redirect code (was using uninitialized variables) Signed-off-by: Denys Vlasenko --- shell/hush.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index dc05f24b9..1e58d71e0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -6741,7 +6741,8 @@ static struct squirrel *add_squirrel(struct squirrel *sq, int fd, int avoid_fd) int moved_to; int i; - if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { + i = 0; + if (sq) for (; sq[i].orig_fd >= 0; i++) { /* If we collide with an already moved fd... */ if (fd == sq[i].moved_to) { sq[i].moved_to = fcntl_F_DUPFD(sq[i].moved_to, avoid_fd); @@ -6769,7 +6770,8 @@ static struct squirrel *add_squirrel_closed(struct squirrel *sq, int fd) { int i; - if (sq) for (i = 0; sq[i].orig_fd >= 0; i++) { + i = 0; + if (sq) for (; sq[i].orig_fd >= 0; i++) { /* If we collide with an already moved fd... */ if (fd == sq[i].orig_fd) { /* Examples: -- cgit v1.2.3-55-g6feb From 129e1ce72c28ec14aee459cceffc25ed9d5cae85 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 17:00:39 +0200 Subject: hush: add a test which fails due to uclibc bug in getopt() Signed-off-by: Denys Vlasenko --- .../ash-getopts/getopt_test_libc_bug.right | 26 +++++++++++++++ .../ash-getopts/getopt_test_libc_bug.tests | 38 ++++++++++++++++++++++ .../hush-getopts/getopt_test_libc_bug.right | 26 +++++++++++++++ .../hush-getopts/getopt_test_libc_bug.tests | 38 ++++++++++++++++++++++ 4 files changed, 128 insertions(+) create mode 100644 shell/ash_test/ash-getopts/getopt_test_libc_bug.right create mode 100755 shell/ash_test/ash-getopts/getopt_test_libc_bug.tests create mode 100644 shell/hush_test/hush-getopts/getopt_test_libc_bug.right create mode 100755 shell/hush_test/hush-getopts/getopt_test_libc_bug.tests (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.right b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right new file mode 100644 index 000000000..f6ad4602d --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.right @@ -0,0 +1,26 @@ +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +Illegal option -b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +Illegal option -d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' + +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +Illegal option -b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +Illegal option -d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' + +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +Illegal option -b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +Illegal option -d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests new file mode 100755 index 000000000..6c0781f20 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests @@ -0,0 +1,38 @@ +# This test can fail with libc with buggy getopt() implementation. +# If getopt() wants to parse multi-option args (-abc), +# it needs to remember a position withit current arg. +# +# If this position is kept as a POINTER, not an offset, +# and if argv[] ADDRESSES (not contents!) change, it blows up. + +echo "*** optstring:'ac' args:-a -b -c -d e" +getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# Above: args are (usually) in the same locations in memory. +# Below: variable allocations change the location. + +echo +echo "*** optstring:'ac' args:-a -b -c -d e" +unset OPTIND +OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# Sligntly different attempts to force reallocations + +echo +echo "*** optstring:'ac' args:-a -b -c -d e" +unset OPTIND +export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# All copies of code above should generate identical output diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.right b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right new file mode 100644 index 000000000..6694e8f0c --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.right @@ -0,0 +1,26 @@ +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' + +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' + +*** optstring:'ac' args:-a -b -c -d e +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- b +2 rc:0 var:'?' OPTIND:3 OPTARG:'' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +./getopt_test_libc_bug.tests: invalid option -- d +4 rc:0 var:'?' OPTIND:5 OPTARG:'' +5 rc:1 var:'?' OPTIND:5 OPTARG:'' diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests new file mode 100755 index 000000000..6c0781f20 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests @@ -0,0 +1,38 @@ +# This test can fail with libc with buggy getopt() implementation. +# If getopt() wants to parse multi-option args (-abc), +# it needs to remember a position withit current arg. +# +# If this position is kept as a POINTER, not an offset, +# and if argv[] ADDRESSES (not contents!) change, it blows up. + +echo "*** optstring:'ac' args:-a -b -c -d e" +getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# Above: args are (usually) in the same locations in memory. +# Below: variable allocations change the location. + +echo +echo "*** optstring:'ac' args:-a -b -c -d e" +unset OPTIND +OPTARG=QWERTY; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +NEWVAR=NEWVAL; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# Sligntly different attempts to force reallocations + +echo +echo "*** optstring:'ac' args:-a -b -c -d e" +unset OPTIND +export OPTARG; getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export NEWVAR; getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR111; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +# All copies of code above should generate identical output -- cgit v1.2.3-55-g6feb From 419db0391e47444dd12fb052613fd415694d04f0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 11 Aug 2017 17:21:14 +0200 Subject: hush: implement "silent" optstrings of ":opts" Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-getopts/getopt_silent.right | 6 +++ shell/ash_test/ash-getopts/getopt_silent.tests | 23 ++++++++++++ shell/hush.c | 48 +++++++++++++++--------- shell/hush_test/hush-getopts/getopt_silent.right | 6 +++ shell/hush_test/hush-getopts/getopt_silent.tests | 23 ++++++++++++ 5 files changed, 89 insertions(+), 17 deletions(-) create mode 100644 shell/ash_test/ash-getopts/getopt_silent.right create mode 100755 shell/ash_test/ash-getopts/getopt_silent.tests create mode 100644 shell/hush_test/hush-getopts/getopt_silent.right create mode 100755 shell/hush_test/hush-getopts/getopt_silent.tests (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_silent.right b/shell/ash_test/ash-getopts/getopt_silent.right new file mode 100644 index 000000000..03d4eb149 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_silent.right @@ -0,0 +1,6 @@ +*** optstring:':ac' args:-a -b -c +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +2 rc:0 var:'?' OPTIND:3 OPTARG:'b' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +4 rc:1 var:'?' OPTIND:4 OPTARG:'' +5 rc:1 var:'?' OPTIND:4 OPTARG:'' diff --git a/shell/ash_test/ash-getopts/getopt_silent.tests b/shell/ash_test/ash-getopts/getopt_silent.tests new file mode 100755 index 000000000..097d7ba85 --- /dev/null +++ b/shell/ash_test/ash-getopts/getopt_silent.tests @@ -0,0 +1,23 @@ +# Open Group Base Specifications Issue 7: +# """ +# If an unknown option is met, VAR shall be set to "?". In this case, +# if the first character in optstring is ":", OPTARG shall be set +# to the option character found, but no output shall be written +# to standard error; otherwise, the shell variable OPTARG shall be +# unset and a diagnostic message shall be written to standard error." +# ... +# If an option-argument is missing: +# If the first character of optstring is ":", VAR shall be set to ":" +# and OPTARG shall be set to the option character found. +# """ + +echo "*** optstring:':ac' args:-a -b -c" +getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +# Previous line should result in "rc:1", which is normally treated +# in getopts loops as exit condition. +# Nevertheless, let's verify that calling it yet another time doesn't do +# anything weird: +getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" diff --git a/shell/hush.c b/shell/hush.c index 1e58d71e0..517b8c109 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9877,11 +9877,6 @@ static int FAST_FUNC builtin_getopts(char **argv) /* http://pubs.opengroup.org/onlinepubs/9699919799/utilities/getopts.html TODO: -If an invalid option is seen, getopts places ? into VAR and, if -not silent, prints an error message and unsets OPTARG. If -getopts is silent, the option character found is placed in -OPTARG and no diagnostic message is printed. - If a required argument is not found, and getopts is not silent, a question mark (?) is placed in VAR, OPTARG is unset, and a diagnostic message is printed. If getopts is silent, then a @@ -9902,11 +9897,16 @@ Test that VAR is a valid variable name? return EXIT_FAILURE; } - cp = get_local_var_value("OPTERR"); - opterr = cp ? atoi(cp) : 1; + if (optstring[0] == ':') { + opterr = 0; + } else { + cp = get_local_var_value("OPTERR"); + opterr = cp ? atoi(cp) : 1; + } cp = get_local_var_value("OPTIND"); optind = cp ? atoi(cp) : 0; optarg = NULL; + cbuf[1] = '\0'; /* getopts stops on first non-option. Add "+" to force that */ /*if (optstring[0] != '+')*/ { @@ -9920,25 +9920,39 @@ Test that VAR is a valid variable name? else argv = G.global_argv; c = getopt(string_array_len(argv), argv, optstring); + + /* Set OPTARG */ + /* Always set or unset, never left as-is, even on exit/error: + * "If no option was found, or if the option that was found + * does not have an option-argument, OPTARG shall be unset." + */ + cp = optarg; + if (c == '?') { + /* If ":optstring" and unknown option is seen, + * it is stored to OPTARG. + */ + if (optstring[1] == ':') { + cbuf[0] = optopt; + cp = cbuf; + } + } + if (cp) + set_local_var_from_halves("OPTARG", cp); + else + unset_local_var("OPTARG"); + + /* Convert -1 to "?" */ exitcode = EXIT_SUCCESS; if (c < 0) { /* -1: end of options */ exitcode = EXIT_FAILURE; c = '?'; } + + /* Set OPTIND */ cbuf[0] = c; - cbuf[1] = '\0'; set_local_var_from_halves(var, cbuf); set_local_var_from_halves("OPTIND", utoa(optind)); - /* Always set or unset, never left as-is, even on exit/error: - * "If no option was found, or if the option that was found - * does not have an option-argument, OPTARG shall be unset." - */ - if (optarg) - set_local_var_from_halves("OPTARG", optarg); - else - unset_local_var("OPTARG"); - return exitcode; } #endif diff --git a/shell/hush_test/hush-getopts/getopt_silent.right b/shell/hush_test/hush-getopts/getopt_silent.right new file mode 100644 index 000000000..03d4eb149 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_silent.right @@ -0,0 +1,6 @@ +*** optstring:':ac' args:-a -b -c +1 rc:0 var:'a' OPTIND:2 OPTARG:'' +2 rc:0 var:'?' OPTIND:3 OPTARG:'b' +3 rc:0 var:'c' OPTIND:4 OPTARG:'' +4 rc:1 var:'?' OPTIND:4 OPTARG:'' +5 rc:1 var:'?' OPTIND:4 OPTARG:'' diff --git a/shell/hush_test/hush-getopts/getopt_silent.tests b/shell/hush_test/hush-getopts/getopt_silent.tests new file mode 100755 index 000000000..097d7ba85 --- /dev/null +++ b/shell/hush_test/hush-getopts/getopt_silent.tests @@ -0,0 +1,23 @@ +# Open Group Base Specifications Issue 7: +# """ +# If an unknown option is met, VAR shall be set to "?". In this case, +# if the first character in optstring is ":", OPTARG shall be set +# to the option character found, but no output shall be written +# to standard error; otherwise, the shell variable OPTARG shall be +# unset and a diagnostic message shall be written to standard error." +# ... +# If an option-argument is missing: +# If the first character of optstring is ":", VAR shall be set to ":" +# and OPTARG shall be set to the option character found. +# """ + +echo "*** optstring:':ac' args:-a -b -c" +getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "3 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" +# Previous line should result in "rc:1", which is normally treated +# in getopts loops as exit condition. +# Nevertheless, let's verify that calling it yet another time doesn't do +# anything weird: +getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" -- cgit v1.2.3-55-g6feb From 007ce9f807c99e14799c6579c8d06f422eccfcb2 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 13 Aug 2017 02:59:00 +0200 Subject: shell: tweak getopts tests, no code changes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-getopts/getopt_test_libc_bug.tests | 4 ++-- shell/hush_test/hush-getopts/getopt_optarg.tests | 6 ++++++ shell/hush_test/hush-getopts/getopt_positional.tests | 6 ++++++ shell/hush_test/hush-getopts/getopt_silent.tests | 6 ++++++ shell/hush_test/hush-getopts/getopt_simple.tests | 6 ++++++ shell/hush_test/hush-getopts/getopt_test_libc_bug.tests | 10 ++++++++-- 6 files changed, 34 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests index 6c0781f20..fcaac81a2 100755 --- a/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests +++ b/shell/ash_test/ash-getopts/getopt_test_libc_bug.tests @@ -1,6 +1,6 @@ # This test can fail with libc with buggy getopt() implementation. # If getopt() wants to parse multi-option args (-abc), -# it needs to remember a position withit current arg. +# it needs to remember a position within current arg. # # If this position is kept as a POINTER, not an offset, # and if argv[] ADDRESSES (not contents!) change, it blows up. @@ -24,7 +24,7 @@ VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$ VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" -# Sligntly different attempts to force reallocations +# Slightly different attempts to force reallocations echo echo "*** optstring:'ac' args:-a -b -c -d e" diff --git a/shell/hush_test/hush-getopts/getopt_optarg.tests b/shell/hush_test/hush-getopts/getopt_optarg.tests index 33682e868..881cc7884 100755 --- a/shell/hush_test/hush-getopts/getopt_optarg.tests +++ b/shell/hush_test/hush-getopts/getopt_optarg.tests @@ -1,3 +1,5 @@ +( + set -- -q -w e -r -t -y echo "*** no OPTIND, optstring:'w:et' args:$*" var=QWERTY @@ -16,3 +18,7 @@ while getopts "w:et" var; do OPTARG=ASDFGH done echo "exited: var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +) 2>&1 \ +| sed -e 's/ unrecognized option: / invalid option -- /' \ + -e 's/ illegal option -- / invalid option -- /' \ diff --git a/shell/hush_test/hush-getopts/getopt_positional.tests b/shell/hush_test/hush-getopts/getopt_positional.tests index ddf063363..20716bb0c 100755 --- a/shell/hush_test/hush-getopts/getopt_positional.tests +++ b/shell/hush_test/hush-getopts/getopt_positional.tests @@ -1,3 +1,5 @@ +( + set -- -q -w -e r -t -y echo "*** no OPTIND, optstring:'we' args:$*" var=QWERTY @@ -5,3 +7,7 @@ while getopts "we" var; do echo "var:'$var' OPTIND:$OPTIND" done echo "exited: var:'$var' OPTIND:$OPTIND" + +) 2>&1 \ +| sed -e 's/ unrecognized option: / invalid option -- /' \ + -e 's/ illegal option -- / invalid option -- /' \ diff --git a/shell/hush_test/hush-getopts/getopt_silent.tests b/shell/hush_test/hush-getopts/getopt_silent.tests index 097d7ba85..5f255db7f 100755 --- a/shell/hush_test/hush-getopts/getopt_silent.tests +++ b/shell/hush_test/hush-getopts/getopt_silent.tests @@ -11,6 +11,8 @@ # and OPTARG shall be set to the option character found. # """ +( + echo "*** optstring:':ac' args:-a -b -c" getopts ":ac" var -a -b -c; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" getopts ":ac" var -a -b -c; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" @@ -21,3 +23,7 @@ getopts ":ac" var -a -b -c; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPT # Nevertheless, let's verify that calling it yet another time doesn't do # anything weird: getopts ":ac" var -a -b -c; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" + +) 2>&1 \ +| sed -e 's/ unrecognized option: / invalid option -- /' \ + -e 's/ illegal option -- / invalid option -- /' \ diff --git a/shell/hush_test/hush-getopts/getopt_simple.tests b/shell/hush_test/hush-getopts/getopt_simple.tests index 8615ae366..50718cc98 100755 --- a/shell/hush_test/hush-getopts/getopt_simple.tests +++ b/shell/hush_test/hush-getopts/getopt_simple.tests @@ -10,6 +10,8 @@ # We check that loop does not stop on unknown option (sets "?"), # stops on _first_ non-option argument. +( + echo "*** no OPTIND, optstring:'ab' args:-a -b c" var=QWERTY while getopts "ab" var -a -b c; do @@ -73,3 +75,7 @@ while getopts "ab" var -a -c -b d; do echo "var:'$var' OPTIND:$OPTIND" done echo "exited: rc:$? var:'$var' OPTIND:$OPTIND" + +) 2>&1 \ +| sed -e 's/ unrecognized option: / invalid option -- /' \ + -e 's/ illegal option -- / invalid option -- /' \ diff --git a/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests index 6c0781f20..ab7bc3b09 100755 --- a/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests +++ b/shell/hush_test/hush-getopts/getopt_test_libc_bug.tests @@ -1,10 +1,12 @@ # This test can fail with libc with buggy getopt() implementation. # If getopt() wants to parse multi-option args (-abc), -# it needs to remember a position withit current arg. +# it needs to remember a position within current arg. # # If this position is kept as a POINTER, not an offset, # and if argv[] ADDRESSES (not contents!) change, it blows up. +( + echo "*** optstring:'ac' args:-a -b -c -d e" getopts "ac" var -a -b -c -d e; echo "1 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" getopts "ac" var -a -b -c -d e; echo "2 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" @@ -24,7 +26,7 @@ VAR111=NEWVAL; getopts "ac" var -a -b -c -d e; echo "3 rc:$? var:'$var' OPTIND:$ VAR222=NEWVAL; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" VAR333=NEWVAL; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" -# Sligntly different attempts to force reallocations +# Slightly different attempts to force reallocations echo echo "*** optstring:'ac' args:-a -b -c -d e" @@ -36,3 +38,7 @@ export VAR222; getopts "ac" var -a -b -c -d e; echo "4 rc:$? var:'$var' OPTIND:$ export VAR333; getopts "ac" var -a -b -c -d e; echo "5 rc:$? var:'$var' OPTIND:$OPTIND OPTARG:'$OPTARG'" # All copies of code above should generate identical output + +) 2>&1 \ +| sed -e 's/ unrecognized option: / invalid option -- /' \ + -e 's/ illegal option -- / invalid option -- /' \ -- cgit v1.2.3-55-g6feb From 452cc1d9bdd7848e960919916de7c405512cad05 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 14 Aug 2017 14:23:45 +0200 Subject: ash: [PARSER] Catch variable length expansions on non-existant specials Upstream commit: Date: Thu, 30 Oct 2014 11:53:35 +0800 [PARSER] Catch variable length expansions on non-existant specials Currently we only check special variable names that follow directly after $ or ${. So errors such as ${#&} are not caught. This patch fixes that by moving the is_special check to just before we print out the special variable name. Signed-off-by: Herbert Xu function old new delta readtoken1 2630 2635 +5 Signed-off-by: Denys Vlasenko --- shell/ash.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 15c7c325a..1917b552c 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -12119,7 +12119,7 @@ parsesub: { STPUTC(c, out); c = pgetc_eatbnl(); } while (isdigit(c)); - } else if (is_special(c)) { + } else { /* $[{[#]][}] */ int cc = c; @@ -12137,10 +12137,16 @@ parsesub: { cc = '#'; } } + + if (!is_special(cc)) { + if (subtype == VSLENGTH) + subtype = 0; + goto badsub; + } + USTPUTC(cc, out); - } else { - goto badsub; } + if (c != '}' && subtype == VSLENGTH) { /* ${#VAR didn't end with } */ goto badsub; -- cgit v1.2.3-55-g6feb From c2aea025bba33323b7a35dd33428277f21121251 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 14 Aug 2017 15:01:32 +0200 Subject: ash: update testsuite (we now error out on ${#=}) Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/param_expand_assign.right | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'shell') diff --git a/shell/ash_test/ash-vars/param_expand_assign.right b/shell/ash_test/ash-vars/param_expand_assign.right index 9b07d8cd4..6e9ea1379 100644 --- a/shell/ash_test/ash-vars/param_expand_assign.right +++ b/shell/ash_test/ash-vars/param_expand_assign.right @@ -1,6 +1,6 @@ SHELL: line 1: syntax error: bad substitution SHELL: line 1: syntax error: bad substitution -0 +SHELL: line 1: syntax error: bad substitution 0 SHELL: line 1: 1: bad variable name SHELL: line 1: 1: bad variable name -- cgit v1.2.3-55-g6feb From 0485b677d2ccd8cd60579446cd1addcb4f322db3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 14 Aug 2017 19:46:56 +0200 Subject: ash: one "current line = 1" might be missing, fix that I'm not sure this is necessary, but dash has this init here. Just in case, do it too. Signed-off-by: Denys Vlasenko --- shell/ash.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 1917b552c..224d77633 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1229,6 +1229,10 @@ struct strpush { int unget; }; +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ struct parsefile { struct parsefile *prev; /* preceding file on stack */ int linno; /* current line */ @@ -1902,10 +1906,6 @@ nextopt(const char *optstring) /* ============ Shell variables */ -/* - * The parsefile structure pointed to by the global variable parsefile - * contains information about the current file being read. - */ struct shparam { int nparam; /* # of positional parameters (without $0) */ #if ENABLE_ASH_GETOPTS @@ -13603,6 +13603,7 @@ init(void) { /* we will never free this */ basepf.next_to_pgetc = basepf.buf = ckmalloc(IBUFSIZ); + basepf.linno = 1; sigmode[SIGCHLD - 1] = S_DFL; /* ensure we install handler even if it is SIG_IGNed */ setsignal(SIGCHLD); -- cgit v1.2.3-55-g6feb From 4476c703015d026dfd8057a28010c33943aa2a9c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 15 Aug 2017 15:27:41 +0200 Subject: ash,hush: comment and debug tweaks, no code changes Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 +++-- shell/hush.c | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 224d77633..58999fac5 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7704,8 +7704,9 @@ static int patmatch(char *pattern, const char *string) { char *p = preglob(pattern, 0); - //bb_error_msg("fnmatch(pattern:'%s',str:'%s')", p, string); - return pmatch(p, string); + int r = pmatch(p, string); + //bb_error_msg("!fnmatch(pattern:'%s',str:'%s',0):%d", p, string, r); + return r; } /* diff --git a/shell/hush.c b/shell/hush.c index 517b8c109..42f95ef6b 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9948,7 +9948,7 @@ Test that VAR is a valid variable name? c = '?'; } - /* Set OPTIND */ + /* Set VAR and OPTIND */ cbuf[0] = c; set_local_var_from_halves(var, cbuf); set_local_var_from_halves("OPTIND", utoa(optind)); -- cgit v1.2.3-55-g6feb From 9832bbaba966f0e52e183f10cd93fad7f8f643fe Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 15 Aug 2017 15:44:41 +0200 Subject: ash: unset OPTARG if getopts exits 1, support OPTERR=0 behavior function old new delta getoptscmd 522 547 +25 Signed-off-by: Denys Vlasenko --- shell/ash.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 58999fac5..703802ff5 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -10948,6 +10948,7 @@ getopts(char *optstr, char *optvar, char **optfirst) p = *optnext; if (p == NULL || *p != '-' || *++p == '\0') { atend: + unsetvar("OPTARG"); p = NULL; done = 1; goto out; @@ -10960,7 +10961,11 @@ getopts(char *optstr, char *optvar, char **optfirst) c = *p++; for (q = optstr; *q != c;) { if (*q == '\0') { - if (optstr[0] == ':') { + /* OPTERR is a bashism */ + const char *cp = lookupvar("OPTERR"); + if ((cp && LONE_CHAR(cp, '0')) + || (optstr[0] == ':') + ) { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ setvar0("OPTARG", sbuf); @@ -10977,7 +10982,11 @@ getopts(char *optstr, char *optvar, char **optfirst) if (*++q == ':') { if (*p == '\0' && (p = *optnext) == NULL) { - if (optstr[0] == ':') { + /* OPTERR is a bashism */ + const char *cp = lookupvar("OPTERR"); + if ((cp && LONE_CHAR(cp, '0')) + || (optstr[0] == ':') + ) { sbuf[0] = c; /*sbuf[1] = '\0'; - already is */ setvar0("OPTARG", sbuf); -- cgit v1.2.3-55-g6feb From 048491fbdccc35edd481218baeedb31c5253aa12 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 17 Aug 2017 12:36:39 +0200 Subject: hush: trivial code shrink in builtin_getopts function old new delta builtin_getopts 368 363 -5 Signed-off-by: Denys Vlasenko --- shell/hush.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'shell') diff --git a/shell/hush.c b/shell/hush.c index 42f95ef6b..cdc3a8618 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9897,12 +9897,13 @@ Test that VAR is a valid variable name? return EXIT_FAILURE; } - if (optstring[0] == ':') { - opterr = 0; - } else { + c = 0; + if (optstring[0] != ':') { cp = get_local_var_value("OPTERR"); - opterr = cp ? atoi(cp) : 1; + /* 0 if "OPTERR=0", 1 otherwise */ + c = (!cp || NOT_LONE_CHAR(cp, '0')); } + opterr = c; cp = get_local_var_value("OPTIND"); optind = cp ? atoi(cp) : 0; optarg = NULL; -- cgit v1.2.3-55-g6feb From fec2b1f79dc2f6bfd96f44719300f9c773d88208 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 17 Aug 2017 16:43:33 +0200 Subject: ash: stage backported LINENO support as a separate patch Looks biggish and not particularly useful, but may be easier to just eat the impact if future backports from dash would be otherwise increasingly difficult. Signed-off-by: Denys Vlasenko --- shell/ash_LINENO.patch | 498 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 498 insertions(+) create mode 100644 shell/ash_LINENO.patch (limited to 'shell') diff --git a/shell/ash_LINENO.patch b/shell/ash_LINENO.patch new file mode 100644 index 000000000..a71549d6a --- /dev/null +++ b/shell/ash_LINENO.patch @@ -0,0 +1,498 @@ +This patch is a backport from dash of the combination of: + [SHELL] Add preliminary LINENO support + [VAR] Fix varinit ordering that broke fc + [SHELL] Improve LINENO support + +Applies cleanly on top of: + commit 9832bbaba966f0e52e183f10cd93fad7f8f643fe + Date: Tue Aug 15 15:44:41 2017 +0200 + +Testsuite needs some tweaks (line numbers in some messages change). + +Unfortunately, it is somewhat big: + +function old new delta +parse_command 1581 1658 +77 +calcsize 203 272 +69 +copynode 195 257 +62 +lookupvar 59 108 +49 +evaltree 494 534 +40 +evalfor 152 187 +35 +evalcase 278 313 +35 +evalcommand 1547 1581 +34 +evalsubshell 169 199 +30 +linenovar - 22 +22 +raise_error_syntax 11 29 +18 +evalfun 266 280 +14 +varinit_data 96 108 +12 +cmdtxt 626 631 +5 +lineno - 4 +4 +funcline - 4 +4 +ash_vmsg 144 141 -3 +startlinno 4 - -4 +funcnest 4 - -4 +xxreadtoken 272 259 -13 +readtoken1 2635 2594 -41 +------------------------------------------------------------------------------ +(add/remove: 3/2 grow/shrink: 13/3 up/down: 510/-65) Total: 445 bytes + text data bss dec hex filename + 912030 563 5844 918437 e03a5 busybox_old + 912473 587 5844 918904 e0578 busybox_unstripped + +diff --git a/shell/ash.c b/shell/ash.c +index 703802f..93a3814 100644 +--- a/shell/ash.c ++++ b/shell/ash.c +@@ -312,6 +312,8 @@ struct globals_misc { + /* shell level: 0 for the main shell, 1 for its children, and so on */ + int shlvl; + #define rootshell (!shlvl) ++ int errlinno; ++ + char *minusc; /* argument to -c option */ + + char *curdir; // = nullstr; /* current working directory */ +@@ -389,6 +391,7 @@ extern struct globals_misc *const ash_ptr_to_globals_misc; + #define job_warning (G_misc.job_warning) + #define rootpid (G_misc.rootpid ) + #define shlvl (G_misc.shlvl ) ++#define errlinno (G_misc.errlinno ) + #define minusc (G_misc.minusc ) + #define curdir (G_misc.curdir ) + #define physdir (G_misc.physdir ) +@@ -723,6 +726,7 @@ union node; + + struct ncmd { + smallint type; /* Nxxxx */ ++ int linno; + union node *assign; + union node *args; + union node *redirect; +@@ -736,6 +740,7 @@ struct npipe { + + struct nredir { + smallint type; ++ int linno; + union node *n; + union node *redirect; + }; +@@ -755,6 +760,7 @@ struct nif { + + struct nfor { + smallint type; ++ int linno; + union node *args; + union node *body; + char *var; +@@ -762,6 +768,7 @@ struct nfor { + + struct ncase { + smallint type; ++ int linno; + union node *expr; + union node *cases; + }; +@@ -773,6 +780,13 @@ struct nclist { + union node *body; + }; + ++struct ndefun { ++ smallint type; ++ int linno; ++ char *text; ++ union node *body; ++}; ++ + struct narg { + smallint type; + union node *next; +@@ -824,6 +838,7 @@ union node { + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; ++ struct ndefun ndefun; + struct narg narg; + struct nfile nfile; + struct ndup ndup; +@@ -1253,7 +1268,6 @@ struct parsefile { + + static struct parsefile basepf; /* top level input file */ + static struct parsefile *g_parsefile = &basepf; /* current input file */ +-static int startlinno; /* line # where last token started */ + static char *commandname; /* currently executing command */ + + +@@ -1267,7 +1281,7 @@ ash_vmsg(const char *msg, va_list ap) + if (strcmp(arg0, commandname)) + fprintf(stderr, "%s: ", commandname); + if (!iflag || g_parsefile->pf_fd > 0) +- fprintf(stderr, "line %d: ", startlinno); ++ fprintf(stderr, "line %d: ", errlinno); + } + vfprintf(stderr, msg, ap); + newline_and_flush(stderr); +@@ -1327,6 +1341,7 @@ static void raise_error_syntax(const char *) NORETURN; + static void + raise_error_syntax(const char *msg) + { ++ errlinno = g_parsefile->linno; + ash_msg_and_raise_error("syntax error: %s", msg); + /* NOTREACHED */ + } +@@ -1993,6 +2008,9 @@ static void changepath(const char *) FAST_FUNC; + static void change_random(const char *) FAST_FUNC; + #endif + ++static int lineno; ++static char linenovar[sizeof("LINENO=%d") + sizeof(int)*3] = "LINENO="; ++ + static const struct { + int flags; + const char *var_text; +@@ -2014,6 +2032,7 @@ static const struct { + #if ENABLE_ASH_GETOPTS + { VSTRFIXED|VTEXTFIXED , defoptindvar, getoptsreset }, + #endif ++ { VSTRFIXED|VTEXTFIXED , linenovar , NULL }, + #if ENABLE_ASH_RANDOM_SUPPORT + { VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM", change_random }, + #endif +@@ -2066,12 +2085,14 @@ extern struct globals_var *const ash_ptr_to_globals_var; + #define vps4 (&vps2)[1] + #if ENABLE_ASH_GETOPTS + # define voptind (&vps4)[1] ++# define vlineno (&voptind)[1] + # if ENABLE_ASH_RANDOM_SUPPORT +-# define vrandom (&voptind)[1] ++# define vrandom (&vlineno)[1] + # endif + #else ++# define vlineno (&vps4)[1] + # if ENABLE_ASH_RANDOM_SUPPORT +-# define vrandom (&vps4)[1] ++# define vrandom (&vlineno)[1] + # endif + #endif + +@@ -2209,8 +2230,12 @@ lookupvar(const char *name) + if (v->flags & VDYNAMIC) + v->var_func(NULL); + #endif +- if (!(v->flags & VUNSET)) ++ if (!(v->flags & VUNSET)) { ++ if (v == &vlineno && v->var_text == linenovar) { ++ fmtstr(linenovar+7, sizeof(linenovar)-7, "%d", lineno); ++ } + return var_end(v->var_text); ++ } + } + return NULL; + } +@@ -4783,7 +4808,7 @@ cmdtxt(union node *n) + p = "; done"; + goto dodo; + case NDEFUN: +- cmdputs(n->narg.text); ++ cmdputs(n->ndefun.text); + p = "() { ... }"; + goto dotail2; + case NCMD: +@@ -8551,6 +8576,9 @@ calcsize(int funcblocksize, union node *n) + funcblocksize = calcsize(funcblocksize, n->nclist.next); + break; + case NDEFUN: ++ funcblocksize = calcsize(funcblocksize, n->ndefun.body); ++ funcblocksize += SHELL_ALIGN(strlen(n->ndefun.text) + 1); ++ break; + case NARG: + funcblocksize = sizenodelist(funcblocksize, n->narg.backquote); + funcblocksize += SHELL_ALIGN(strlen(n->narg.text) + 1); /* was funcstringsize += ... */ +@@ -8626,6 +8654,7 @@ copynode(union node *n) + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); ++ new->ncmd.linno = n->ncmd.linno; + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); +@@ -8636,6 +8665,7 @@ copynode(union node *n) + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); ++ new->nredir.linno = n->nredir.linno; + break; + case NAND: + case NOR: +@@ -8654,10 +8684,12 @@ copynode(union node *n) + new->nfor.var = nodeckstrdup(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); ++ new->nfor.linno = n->nfor.linno; + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); ++ new->ncase.linno = n->ncase.linno; + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); +@@ -8665,6 +8697,10 @@ copynode(union node *n) + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: ++ new->ndefun.body = copynode(n->ndefun.body); ++ new->ndefun.text = nodeckstrdup(n->ndefun.text); ++ new->ndefun.linno = n->ndefun.linno; ++ break; + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodeckstrdup(n->narg.text); +@@ -8733,7 +8769,7 @@ defun(union node *func) + INT_OFF; + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); +- addcmdentry(func->narg.text, &entry); ++ addcmdentry(func->ndefun.text, &entry); + INT_ON; + } + +@@ -8743,8 +8779,8 @@ defun(union node *func) + #define SKIPFUNC (1 << 2) + static smallint evalskip; /* set to SKIPxxx if we are skipping commands */ + static int skipcount; /* number of levels to skip */ +-static int funcnest; /* depth of function calls */ + static int loopnest; /* current loop nesting level */ ++static int funcline; /* starting line number of current function, or 0 if not in a function */ + + /* Forward decl way out to parsing code - dotrap needs it */ + static int evalstring(char *s, int flags); +@@ -8839,6 +8875,9 @@ evaltree(union node *n, int flags) + status = !evaltree(n->nnot.com, EV_TESTED); + goto setstatus; + case NREDIR: ++ errlinno = lineno = n->nredir.linno; ++ if (funcline) ++ lineno -= funcline - 1; + expredir(n->nredir.redirect); + pushredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); +@@ -8993,6 +9032,10 @@ evalfor(union node *n, int flags) + struct stackmark smark; + int status = 0; + ++ errlinno = lineno = n->ncase.linno; ++ if (funcline) ++ lineno -= funcline - 1; ++ + setstackmark(&smark); + arglist.list = NULL; + arglist.lastp = &arglist.list; +@@ -9024,6 +9067,10 @@ evalcase(union node *n, int flags) + struct stackmark smark; + int status = 0; + ++ errlinno = lineno = n->ncase.linno; ++ if (funcline) ++ lineno -= funcline - 1; ++ + setstackmark(&smark); + arglist.list = NULL; + arglist.lastp = &arglist.list; +@@ -9058,6 +9105,10 @@ evalsubshell(union node *n, int flags) + int backgnd = (n->type == NBACKGND); /* FORK_BG(1) if yes, else FORK_FG(0) */ + int status; + ++ errlinno = lineno = n->nredir.linno; ++ if (funcline) ++ lineno -= funcline - 1; ++ + expredir(n->nredir.redirect); + if (!backgnd && (flags & EV_EXIT) && !may_have_traps) + goto nofork; +@@ -9365,8 +9416,10 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; ++ int savefuncline; + + saveparam = shellparam; ++ savefuncline = funcline; + savehandler = exception_handler; + e = setjmp(jmploc.loc); + if (e) { +@@ -9376,7 +9429,7 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) + exception_handler = &jmploc; + shellparam.malloced = 0; + func->count++; +- funcnest++; ++ funcline = func->n.ndefun.linno; + INT_ON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +@@ -9385,11 +9438,11 @@ evalfun(struct funcnode *func, int argc, char **argv, int flags) + shellparam.optoff = -1; + #endif + pushlocalvars(); +- evaltree(func->n.narg.next, flags & EV_TESTED); ++ evaltree(func->n.ndefun.body, flags & EV_TESTED); + poplocalvars(0); + funcdone: + INT_OFF; +- funcnest--; ++ funcline = savefuncline; + freefunc(func); + freeparam(&shellparam); + shellparam = saveparam; +@@ -9753,6 +9806,10 @@ evalcommand(union node *cmd, int flags) + char **nargv; + smallint cmd_is_exec; + ++ errlinno = lineno = cmd->ncmd.linno; ++ if (funcline) ++ lineno -= funcline - 1; ++ + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); +@@ -9798,7 +9855,7 @@ evalcommand(union node *cmd, int flags) + *nargv = NULL; + + lastarg = NULL; +- if (iflag && funcnest == 0 && argc > 0) ++ if (iflag && funcline == 0 && argc > 0) + lastarg = nargv[-1]; + + expredir(cmd->ncmd.redirect); +@@ -11317,6 +11374,7 @@ simplecmd(void) + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; ++ int savelinno; + #if BASH_TEST2 + smallint double_brackets_flag = 0; + #endif +@@ -11330,6 +11388,7 @@ simplecmd(void) + rpp = &redir; + + savecheckkwd = CHKALIAS; ++ savelinno = g_parsefile->linno; + for (;;) { + int t; + checkkwd = savecheckkwd; +@@ -11419,7 +11478,9 @@ simplecmd(void) + } + n->type = NDEFUN; + checkkwd = CHKNL | CHKKWD | CHKALIAS; +- n->narg.next = parse_command(); ++ n->ndefun.text = n->narg.text; ++ n->ndefun.linno = g_parsefile->linno; ++ n->ndefun.body = parse_command(); + return n; + } + IF_BASH_FUNCTION(function_flag = 0;) +@@ -11435,6 +11496,7 @@ simplecmd(void) + *rpp = NULL; + n = stzalloc(sizeof(struct ncmd)); + n->type = NCMD; ++ n->ncmd.linno = savelinno; + n->ncmd.args = args; + n->ncmd.assign = vars; + n->ncmd.redirect = redir; +@@ -11450,10 +11512,13 @@ parse_command(void) + union node *redir, **rpp; + union node **rpp2; + int t; ++ int savelinno; + + redir = NULL; + rpp2 = &redir; + ++ savelinno = g_parsefile->linno; ++ + switch (readtoken()) { + default: + raise_error_unexpected_syntax(-1); +@@ -11504,6 +11569,7 @@ parse_command(void) + raise_error_syntax("bad for loop variable"); + n1 = stzalloc(sizeof(struct nfor)); + n1->type = NFOR; ++ n1->nfor.linno = savelinno; + n1->nfor.var = wordtext; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (readtoken() == TIN) { +@@ -11544,6 +11610,7 @@ parse_command(void) + case TCASE: + n1 = stzalloc(sizeof(struct ncase)); + n1->type = NCASE; ++ n1->ncase.linno = savelinno; + if (readtoken() != TWORD) + raise_error_unexpected_syntax(TWORD); + n1->ncase.expr = n2 = stzalloc(sizeof(struct narg)); +@@ -11595,6 +11662,7 @@ parse_command(void) + case TLP: + n1 = stzalloc(sizeof(struct nredir)); + n1->type = NSUBSHELL; ++ n1->nredir.linno = savelinno; + n1->nredir.n = list(0); + /*n1->nredir.redirect = NULL; - stzalloc did it */ + t = TRP; +@@ -11628,6 +11696,7 @@ parse_command(void) + if (n1->type != NSUBSHELL) { + n2 = stzalloc(sizeof(struct nredir)); + n2->type = NREDIR; ++ n2->nredir.linno = savelinno; + n2->nredir.n = n1; + n1 = n2; + } +@@ -11726,10 +11795,8 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) + IF_FEATURE_SH_MATH(int arinest;) /* levels of arithmetic expansion */ + IF_FEATURE_SH_MATH(int parenlevel;) /* levels of parens in arithmetic */ + int dqvarnest; /* levels of variables expansion within double quotes */ +- + IF_BASH_DOLLAR_SQUOTE(smallint bash_dollar_squote = 0;) + +- startlinno = g_parsefile->linno; + bqlist = NULL; + quotef = 0; + IF_FEATURE_SH_MATH(prevsyntax = 0;) +@@ -11906,7 +11973,6 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) + if (syntax != BASESYNTAX && eofmark == NULL) + raise_error_syntax("unterminated quoted string"); + if (varnest != 0) { +- startlinno = g_parsefile->linno; + /* { */ + raise_error_syntax("missing '}'"); + } +@@ -12298,7 +12364,6 @@ parsebackq: { + + case PEOF: + IF_ASH_ALIAS(case PEOA:) +- startlinno = g_parsefile->linno; + raise_error_syntax("EOF in backquote substitution"); + + case '\n': +@@ -12380,8 +12445,6 @@ parsearith: { + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. +- * In all cases, the variable startlinno is set to the number of the line +- * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the +@@ -12419,7 +12482,6 @@ xxreadtoken(void) + return lasttoken; + } + setprompt_if(needprompt, 2); +- startlinno = g_parsefile->linno; + for (;;) { /* until token or start of word found */ + c = pgetc(); + if (c == ' ' || c == '\t' IF_ASH_ALIAS( || c == PEOA)) +@@ -12480,7 +12542,6 @@ xxreadtoken(void) + return lasttoken; + } + setprompt_if(needprompt, 2); +- startlinno = g_parsefile->linno; + for (;;) { /* until token or start of word found */ + c = pgetc(); + switch (c) { -- cgit v1.2.3-55-g6feb