From eb0de05d682b28fcf7465358ea31cf8574c1221b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 Apr 2018 17:54:07 +0200 Subject: hush: fix func_return2.tests on NOMMU function old new delta hush_main 1714 1718 +4 Signed-off-by: Denys Vlasenko --- shell/hush.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/shell/hush.c b/shell/hush.c index d5ea3b21f..3a4b5d894 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -9393,6 +9393,13 @@ int hush_main(int argc, char **argv) # if ENABLE_HUSH_LOOPS optarg++; G.depth_of_loop = bb_strtou(optarg, &optarg, 16); +# endif +# if ENABLE_HUSH_FUNCTIONS + /* nommu uses re-exec trick for "... | func | ...", + * should allow "return". + * This accidentally allows returns in subshells. + */ + G_flag_return_in_progress = -1; # endif break; } -- cgit v1.2.3-55-g6feb From 9db344a0f4ed5f6f893940b734215d05e48320e9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 9 Apr 2018 19:05:11 +0200 Subject: hush: fix var_leaks.tests and var_preserved.tests on NOMMU function old new delta remove_nested_vars - 77 +77 run_pipe 1756 1786 +30 pseudo_exec_argv 376 379 +3 leave_var_nest_level 98 32 -66 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 2/1 up/down: 110/-66) Total: 44 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 108 ++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 59 insertions(+), 49 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 3a4b5d894..885561389 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -2266,6 +2266,7 @@ static int set_local_var(char *str, unsigned flags) } /* Not found or shadowed - create new variable struct */ + debug_printf_env("%s: alloc new var '%s'/%u\n", __func__, str, local_lvl); cur = xzalloc(sizeof(*cur)); cur->var_nest_level = local_lvl; cur->next = *cur_pp; @@ -2420,7 +2421,7 @@ static void set_vars_and_save_old(char **strings) * global linked list. */ } - //bb_error_msg("G.var_nest_level:%d", G.var_nest_level); + debug_printf_env("%s: env override '%s'/%u\n", __func__, *s, G.var_nest_level); set_local_var(*s, (G.var_nest_level << SETFLAG_VARLVL_SHIFT) | SETFLAG_EXPORT); } else if (HUSH_DEBUG) { bb_error_msg_and_die("BUG in varexp4"); @@ -7358,6 +7359,58 @@ static void unset_func(const char *name) } # endif +static void remove_nested_vars(void) +{ + struct variable *cur; + struct variable **cur_pp; + + cur_pp = &G.top_var; + while ((cur = *cur_pp) != NULL) { + if (cur->var_nest_level <= G.var_nest_level) { + cur_pp = &cur->next; + continue; + } + /* Unexport */ + if (cur->flg_export) { + debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + bb_unsetenv(cur->varstr); + } + /* Remove from global list */ + *cur_pp = cur->next; + /* Free */ + if (!cur->max_len) { + debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); + free(cur->varstr); + } + free(cur); + } +} + +static void enter_var_nest_level(void) +{ + G.var_nest_level++; + debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); + + /* Try: f() { echo -n .; f; }; f + * struct variable::var_nest_level is uint16_t, + * thus limiting recursion to < 2^16. + * In any case, with 8 Mbyte stack SEGV happens + * not too long after 2^16 recursions anyway. + */ + if (G.var_nest_level > 0xff00) + bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); +} + +static void leave_var_nest_level(void) +{ + G.var_nest_level--; + debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); + if (HUSH_DEBUG && (int)G.var_nest_level < 0) + bb_error_msg_and_die("BUG: nesting underflow"); + + remove_nested_vars(); +} + # if BB_MMU #define exec_function(to_free, funcp, argv) \ exec_function(funcp, argv) @@ -7392,7 +7445,7 @@ static void exec_function(char ***to_free, /* "we are in a function, ok to use return" */ G_flag_return_in_progress = -1; - G.var_nest_level++; + enter_var_nest_level(); IF_HUSH_LOCAL(G.func_nest_level++;) /* On MMU, funcp->body is always non-NULL */ @@ -7412,53 +7465,6 @@ static void exec_function(char ***to_free, # endif } -static void enter_var_nest_level(void) -{ - G.var_nest_level++; - debug_printf_env("var_nest_level++ %u\n", G.var_nest_level); - - /* Try: f() { echo -n .; f; }; f - * struct variable::var_nest_level is uint16_t, - * thus limiting recursion to < 2^16. - * In any case, with 8 Mbyte stack SEGV happens - * not too long after 2^16 recursions anyway. - */ - if (G.var_nest_level > 0xff00) - bb_error_msg_and_die("fatal recursion (depth %u)", G.var_nest_level); -} - -static void leave_var_nest_level(void) -{ - struct variable *cur; - struct variable **cur_pp; - - cur_pp = &G.top_var; - while ((cur = *cur_pp) != NULL) { - if (cur->var_nest_level < G.var_nest_level) { - cur_pp = &cur->next; - continue; - } - /* Unexport */ - if (cur->flg_export) { - debug_printf_env("unexporting nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - bb_unsetenv(cur->varstr); - } - /* Remove from global list */ - *cur_pp = cur->next; - /* Free */ - if (!cur->max_len) { - debug_printf_env("freeing nested '%s'/%u\n", cur->varstr, cur->var_nest_level); - free(cur->varstr); - } - free(cur); - } - - G.var_nest_level--; - debug_printf_env("var_nest_level-- %u\n", G.var_nest_level); - if (HUSH_DEBUG && (int)G.var_nest_level < 0) - bb_error_msg_and_die("BUG: nesting underflow"); -} - static int run_function(const struct function *funcp, char **argv) { int rc; @@ -7648,6 +7654,7 @@ static NOINLINE void pseudo_exec_argv(nommu_save_t *nommu_save, G.shadowed_vars_pp = NULL; /* "don't save, free them instead" */ #else G.shadowed_vars_pp = &nommu_save->old_vars; + G.var_nest_level++; #endif set_vars_and_save_old(new_env); G.shadowed_vars_pp = sv_shadowed; @@ -8522,6 +8529,7 @@ static NOINLINE int run_pipe(struct pipe *pi) while (cmd_no < pi->num_cmds) { struct fd_pair pipefds; #if !BB_MMU + int sv_var_nest_level = G.var_nest_level; volatile nommu_save_t nommu_save; nommu_save.old_vars = NULL; nommu_save.argv = NULL; @@ -8615,6 +8623,8 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Clean up after vforked child */ free(nommu_save.argv); free(nommu_save.argv_from_re_execing); + G.var_nest_level = sv_var_nest_level; + remove_nested_vars(); add_vars(nommu_save.old_vars); #endif free(argv_expanded); -- cgit v1.2.3-55-g6feb From 1f27fa98380c134cd0f5f03b4cd3dfe2e83c09b7 Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Fri, 9 Feb 2018 09:52:52 +0000 Subject: testsuite: allow tests to work in paths containing spaces Tweak some tests so they work when the path to the test directory or $HOME contains spaces. Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- testsuite/grep.tests | 2 +- testsuite/printf.tests | 2 +- testsuite/pwd/pwd-prints-working-directory | 2 +- testsuite/sum.tests | 6 +++--- testsuite/xargs/xargs-works | 6 ++++-- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/testsuite/grep.tests b/testsuite/grep.tests index d0b0d2767..e57889790 100755 --- a/testsuite/grep.tests +++ b/testsuite/grep.tests @@ -15,7 +15,7 @@ testing "grep (exit with error)" "grep nonexistent 2> /dev/null ; echo \$?" \ "1\n" "" "" -testing "grep (exit success)" "grep grep $0 > /dev/null 2>&1 ; echo \$?" "0\n" \ +testing "grep (exit success)" "grep grep '$0' > /dev/null 2>&1 ; echo \$?" "0\n" \ "" "" # Test various data sources and destinations diff --git a/testsuite/printf.tests b/testsuite/printf.tests index 9a3c87473..963ded94b 100755 --- a/testsuite/printf.tests +++ b/testsuite/printf.tests @@ -20,7 +20,7 @@ testing "printf produces no further output 2" \ "" "" testing "printf repeatedly uses pattern for each argv" \ - "${bb}printf '%s\n' foo \$HOME" \ + "${bb}printf '%s\n' foo '$HOME'" \ "foo\n$HOME\n" \ "" "" diff --git a/testsuite/pwd/pwd-prints-working-directory b/testsuite/pwd/pwd-prints-working-directory index 971adb5a6..fc7fea7c9 100644 --- a/testsuite/pwd/pwd-prints-working-directory +++ b/testsuite/pwd/pwd-prints-working-directory @@ -1,4 +1,4 @@ # shell's $PWD may leave symlinks unresolved. # "pwd" may be a built-in and have the same problem. # External pwd _can't_ have that problem (current dir on Unix is physical). -test $(`which pwd`) = $(busybox pwd) +test "$(`which pwd`)" = "$(busybox pwd)" diff --git a/testsuite/sum.tests b/testsuite/sum.tests index b9f4cbfa8..e6379349f 100755 --- a/testsuite/sum.tests +++ b/testsuite/sum.tests @@ -13,12 +13,12 @@ # test can create a file "actual" instead of writing to stdout testing "sum -r file doesn't print file's name" \ - "sum -r $0 | grep -c $0 && echo wrongly_printed_filename || echo yes" \ + "sum -r '$0' | grep -c '$0' && echo wrongly_printed_filename || echo yes" \ "0\nyes\n" "" "" testing "sum -r file file does print both names" \ - "sum -r $0 $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \ + "sum -r '$0' '$0' | grep -c '$0' && echo yes || echo wrongly_omitted_filename" \ "2\nyes\n" "" "" testing "sum -s file does print file's name" \ - "sum -s $0 | grep -c $0 && echo yes || echo wrongly_omitted_filename" \ + "sum -s '$0' | grep -c '$0' && echo yes || echo wrongly_omitted_filename" \ "1\nyes\n" "" "" exit $FAILCOUNT diff --git a/testsuite/xargs/xargs-works b/testsuite/xargs/xargs-works index c95869e89..a4bba7630 100644 --- a/testsuite/xargs/xargs-works +++ b/testsuite/xargs/xargs-works @@ -1,4 +1,6 @@ +# FEATURE: CONFIG_FEATURE_XARGS_SUPPORT_ZERO_TERM + [ -n "$d" ] || d=.. -find "$d" -name \*works -type f | xargs md5sum > logfile.gnu -find "$d" -name \*works -type f | busybox xargs md5sum > logfile.bb +find "$d" -name \*works -type f -print0 | xargs -0 md5sum > logfile.gnu +find "$d" -name \*works -type f -print0 | busybox xargs -0 md5sum > logfile.bb diff -u logfile.gnu logfile.bb -- cgit v1.2.3-55-g6feb From 57b7efb0d5b16fe9d2c19b45fd240fe552bb5c36 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 01:20:26 +0200 Subject: ash: trivial code shrink function old new delta parse_command 1677 1674 -3 Signed-off-by: Denys Vlasenko --- shell/ash.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shell/ash.c b/shell/ash.c index 24958c0fc..303542197 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11662,7 +11662,8 @@ simplecmd(void) *vpp = NULL; *rpp = NULL; n = stzalloc(sizeof(struct ncmd)); - n->type = NCMD; + if (NCMD != 0) + n->type = NCMD; n->ncmd.linno = savelinno; n->ncmd.args = args; n->ncmd.assign = vars; -- cgit v1.2.3-55-g6feb From e93031e6dced47e8f5a86408b4aa3f89aef647c7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 01:23:19 +0200 Subject: ash: if "[[" bashism is not supported, do not handle it anywhere Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/shell/ash.c b/shell/ash.c index 303542197..45c747dbc 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11611,10 +11611,12 @@ simplecmd(void) case TLP: function_flag = 0; break; +# if BASH_TEST2 case TWORD: if (strcmp("[[", wordtext) == 0) goto do_func; /* fall through */ +# endif default: raise_error_unexpected_syntax(-1); } -- cgit v1.2.3-55-g6feb From 09b7a7ec0ea5ef602ff543dad1a90b6174a4f1c8 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 03:22:10 +0200 Subject: hush: put "current word" structure into parsing context function old new delta done_word 790 767 -23 parse_stream 3018 2919 -99 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-122) Total: -122 bytes Signed-off-by: Denys Vlasenko --- shell/hush.c | 242 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 120 insertions(+), 122 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 885561389..0c57803f1 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -522,7 +522,6 @@ typedef struct o_string { * possibly empty one: word"", wo''rd etc. */ smallint has_quoted_part; smallint has_empty_slot; - smallint o_assignment; /* 0:maybe, 1:yes, 2:no */ } o_string; enum { EXP_FLAG_SINGLEWORD = 0x80, /* must be 0x80 */ @@ -531,13 +530,6 @@ enum { * by prepending \ to *, ?, [, \ */ EXP_FLAG_ESC_GLOB_CHARS = 0x1, }; -enum { - MAYBE_ASSIGNMENT = 0, - DEFINITELY_ASSIGNMENT = 1, - NOT_ASSIGNMENT = 2, - /* Not an assignment, but next word may be: "if v=xyz cmd;" */ - WORD_IS_KEYWORD = 3, -}; /* Used for initialization: o_string foo = NULL_O_STRING; */ #define NULL_O_STRING { NULL } @@ -694,9 +686,11 @@ struct parse_context { struct command *command; /* last redirect in command->redirects list */ struct redir_struct *pending_redirect; + o_string word; #if !BB_MMU o_string as_string; #endif + smallint is_assignment; /* 0:maybe, 1:yes, 2:no, 3:keyword */ #if HAS_KEYWORDS smallint ctx_res_w; smallint ctx_inverted; /* "! cmd | cmd" */ @@ -717,6 +711,13 @@ struct parse_context { struct parse_context *stack; #endif }; +enum { + MAYBE_ASSIGNMENT = 0, + DEFINITELY_ASSIGNMENT = 1, + NOT_ASSIGNMENT = 2, + /* Not an assignment, but next word may be: "if v=xyz cmd;" */ + WORD_IS_KEYWORD = 3, +}; /* On program start, environ points to initial environment. * putenv adds new pointers into it, unsetenv removes them. @@ -3671,6 +3672,8 @@ static void done_pipe(struct parse_context *ctx, pipe_style type) static void initialize_context(struct parse_context *ctx) { memset(ctx, 0, sizeof(*ctx)); + if (MAYBE_ASSIGNMENT != 0) + ctx->is_assignment = MAYBE_ASSIGNMENT; ctx->pipe = ctx->list_head = new_pipe(); /* Create the memory for command, roughly: * ctx->pipe->cmds = new struct command; @@ -3752,7 +3755,7 @@ static const struct reserved_combo* match_reserved_word(o_string *word) } /* Return NULL: not a keyword, else: keyword */ -static const struct reserved_combo* reserved_word(o_string *word, struct parse_context *ctx) +static const struct reserved_combo* reserved_word(struct parse_context *ctx) { # if ENABLE_HUSH_CASE static const struct reserved_combo reserved_match = { @@ -3761,9 +3764,9 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c # endif const struct reserved_combo *r; - if (word->has_quoted_part) + if (ctx->word.has_quoted_part) return 0; - r = match_reserved_word(word); + r = match_reserved_word(&ctx->word); if (!r) return r; /* NULL */ @@ -3790,7 +3793,7 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c initialize_context(ctx); ctx->stack = old; } else if (/*ctx->ctx_res_w == RES_NONE ||*/ !(ctx->old_flag & (1 << r->res))) { - syntax_error_at(word->data); + syntax_error_at(ctx->word.data); ctx->ctx_res_w = RES_SNTX; return r; } else { @@ -3803,8 +3806,8 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c ctx->ctx_res_w = r->res; ctx->old_flag = r->flag; - word->o_assignment = r->assignment_flag; - debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); + ctx->is_assignment = r->assignment_flag; + debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); if (ctx->old_flag & FLAG_END) { struct parse_context *old; @@ -3850,12 +3853,12 @@ static const struct reserved_combo* reserved_word(o_string *word, struct parse_c * Normal return is 0. Syntax errors return 1. * Note: on return, word is reset, but not o_free'd! */ -static int done_word(o_string *word, struct parse_context *ctx) +static int done_word(struct parse_context *ctx) { struct command *command = ctx->command; - debug_printf_parse("done_word entered: '%s' %p\n", word->data, command); - if (word->length == 0 && !word->has_quoted_part) { + debug_printf_parse("done_word entered: '%s' %p\n", ctx->word.data, command); + if (ctx->word.length == 0 && !ctx->word.has_quoted_part) { debug_printf_parse("done_word return 0: true null, ignored\n"); return 0; } @@ -3885,7 +3888,7 @@ static int done_word(o_string *word, struct parse_context *ctx) // <pending_redirect->rd_filename = xstrdup(word->data); + ctx->pending_redirect->rd_filename = xstrdup(ctx->word.data); /* Cater for >\file case: * >\a creates file a; >\\a, >"\a", >"\\a" create file \a * Same with heredocs: @@ -3894,17 +3897,17 @@ static int done_word(o_string *word, struct parse_context *ctx) if (ctx->pending_redirect->rd_type == REDIRECT_HEREDOC) { unbackslash(ctx->pending_redirect->rd_filename); /* Is it <<"HEREDOC"? */ - if (word->has_quoted_part) { + if (ctx->word.has_quoted_part) { ctx->pending_redirect->rd_dup |= HEREDOC_QUOTED; } } - debug_printf_parse("word stored in rd_filename: '%s'\n", word->data); + debug_printf_parse("word stored in rd_filename: '%s'\n", ctx->word.data); ctx->pending_redirect = NULL; } else { #if HAS_KEYWORDS # if ENABLE_HUSH_CASE if (ctx->ctx_dsemicolon - && strcmp(word->data, "esac") != 0 /* not "... pattern) cmd;; esac" */ + && strcmp(ctx->word.data, "esac") != 0 /* not "... pattern) cmd;; esac" */ ) { /* already done when ctx_dsemicolon was set to 1: */ /* ctx->ctx_res_w = RES_MATCH; */ @@ -3921,7 +3924,7 @@ static int done_word(o_string *word, struct parse_context *ctx) # endif ) { const struct reserved_combo *reserved; - reserved = reserved_word(word, ctx); + reserved = reserved_word(ctx); debug_printf_parse("checking for reserved-ness: %d\n", !!reserved); if (reserved) { # if ENABLE_HUSH_LINENO_VAR @@ -3940,7 +3943,7 @@ static int done_word(o_string *word, struct parse_context *ctx) done_pipe(ctx, PIPE_SEQ); } # endif - o_reset_to_empty_unquoted(word); + o_reset_to_empty_unquoted(&ctx->word); debug_printf_parse("done_word return %d\n", (ctx->ctx_res_w == RES_SNTX)); return (ctx->ctx_res_w == RES_SNTX); @@ -3948,7 +3951,7 @@ static int done_word(o_string *word, struct parse_context *ctx) # if defined(CMD_SINGLEWORD_NOGLOB) if (0 # if BASH_TEST2 - || strcmp(word->data, "[[") == 0 + || strcmp(ctx->word.data, "[[") == 0 # endif /* In bash, local/export/readonly are special, args * are assignments and therefore expansion of them @@ -3965,9 +3968,9 @@ static int done_word(o_string *word, struct parse_context *ctx) * $ "export" i=`echo 'aaa bbb'`; echo "$i" * aaa */ - IF_HUSH_LOCAL( || strcmp(word->data, "local") == 0) - IF_HUSH_EXPORT( || strcmp(word->data, "export") == 0) - IF_HUSH_READONLY( || strcmp(word->data, "readonly") == 0) + IF_HUSH_LOCAL( || strcmp(ctx->word.data, "local") == 0) + IF_HUSH_EXPORT( || strcmp(ctx->word.data, "export") == 0) + IF_HUSH_READONLY(|| strcmp(ctx->word.data, "readonly") == 0) ) { command->cmd_type = CMD_SINGLEWORD_NOGLOB; } @@ -3978,7 +3981,7 @@ static int done_word(o_string *word, struct parse_context *ctx) if (command->group) { /* "{ echo foo; } echo bar" - bad */ - syntax_error_at(word->data); + syntax_error_at(ctx->word.data); debug_printf_parse("done_word return 1: syntax error, " "groups and arglists don't mix\n"); return 1; @@ -3986,26 +3989,26 @@ static int done_word(o_string *word, struct parse_context *ctx) /* If this word wasn't an assignment, next ones definitely * can't be assignments. Even if they look like ones. */ - if (word->o_assignment != DEFINITELY_ASSIGNMENT - && word->o_assignment != WORD_IS_KEYWORD + if (ctx->is_assignment != DEFINITELY_ASSIGNMENT + && ctx->is_assignment != WORD_IS_KEYWORD ) { - word->o_assignment = NOT_ASSIGNMENT; + ctx->is_assignment = NOT_ASSIGNMENT; } else { - if (word->o_assignment == DEFINITELY_ASSIGNMENT) { + if (ctx->is_assignment == DEFINITELY_ASSIGNMENT) { command->assignment_cnt++; debug_printf_parse("++assignment_cnt=%d\n", command->assignment_cnt); } - debug_printf_parse("word->o_assignment was:'%s'\n", assignment_flag[word->o_assignment]); - word->o_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx->is_assignment was:'%s'\n", assignment_flag[ctx->is_assignment]); + ctx->is_assignment = MAYBE_ASSIGNMENT; } - debug_printf_parse("word->o_assignment='%s'\n", assignment_flag[word->o_assignment]); - command->argv = add_string_to_strings(command->argv, xstrdup(word->data)); + debug_printf_parse("ctx->is_assignment='%s'\n", assignment_flag[ctx->is_assignment]); + command->argv = add_string_to_strings(command->argv, xstrdup(ctx->word.data)); debug_print_strings("word appended to argv", command->argv); } #if ENABLE_HUSH_LOOPS if (ctx->ctx_res_w == RES_FOR) { - if (word->has_quoted_part + if (ctx->word.has_quoted_part || !is_well_formed_var_name(command->argv[0], '\0') ) { /* bash says just "not a valid identifier" */ @@ -4026,7 +4029,7 @@ static int done_word(o_string *word, struct parse_context *ctx) } #endif - o_reset_to_empty_unquoted(word); + o_reset_to_empty_unquoted(&ctx->word); debug_printf_parse("done_word return 0\n"); return 0; @@ -4310,14 +4313,10 @@ static struct pipe *parse_stream(char **pstring, int end_trigger); -#if !ENABLE_HUSH_FUNCTIONS -#define parse_group(dest, ctx, input, ch) \ - parse_group(ctx, input, ch) -#endif -static int parse_group(o_string *dest, struct parse_context *ctx, +static int parse_group(struct parse_context *ctx, struct in_str *input, int ch) { - /* dest contains characters seen prior to ( or {. + /* ctx->word contains characters seen prior to ( or {. * Typically it's empty, but for function defs, * it contains function name (without '()'). */ #if BB_MMU @@ -4331,9 +4330,9 @@ static int parse_group(o_string *dest, struct parse_context *ctx, debug_printf_parse("parse_group entered\n"); #if ENABLE_HUSH_FUNCTIONS - if (ch == '(' && !dest->has_quoted_part) { - if (dest->length) - if (done_word(dest, ctx)) + if (ch == '(' && !ctx->word.has_quoted_part) { + if (ctx->word.length) + if (done_word(ctx)) return 1; if (!command->argv) goto skip; /* (... */ @@ -4365,8 +4364,8 @@ static int parse_group(o_string *dest, struct parse_context *ctx, #if 0 /* Prevented by caller */ if (command->argv /* word [word]{... */ - || dest->length /* word{... */ - || dest->has_quoted_part /* ""{... */ + || ctx->word.length /* word{... */ + || ctx->word.has_quoted_part /* ""{... */ ) { syntax_error(NULL); debug_printf_parse("parse_group return 1: " @@ -4972,29 +4971,28 @@ static struct pipe *parse_stream(char **pstring, int end_trigger) { struct parse_context ctx; - o_string dest = NULL_O_STRING; int heredoc_cnt; /* Single-quote triggers a bypass of the main loop until its mate is - * found. When recursing, quote state is passed in via dest->o_expflags. + * found. When recursing, quote state is passed in via ctx.word.o_expflags. */ debug_printf_parse("parse_stream entered, end_trigger='%c'\n", end_trigger ? end_trigger : 'X'); debug_enter(); - /* If very first arg is "" or '', dest.data may end up NULL. - * Preventing this: */ - o_addchr(&dest, '\0'); - dest.length = 0; + initialize_context(&ctx); + + /* If very first arg is "" or '', ctx.word.data may end up NULL. + * Preventing this: + */ + o_addchr(&ctx.word, '\0'); + ctx.word.length = 0; /* We used to separate words on $IFS here. This was wrong. * $IFS is used only for word splitting when $var is expanded, * here we should use blank chars as separators, not $IFS */ - if (MAYBE_ASSIGNMENT != 0) - dest.o_assignment = MAYBE_ASSIGNMENT; - initialize_context(&ctx); heredoc_cnt = 0; while (1) { const char *is_blank; @@ -5006,7 +5004,7 @@ static struct pipe *parse_stream(char **pstring, ch = i_getch(input); debug_printf_parse(": ch=%c (%d) escape=%d\n", - ch, ch, !!(dest.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); + ch, ch, !!(ctx.word.o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (ch == EOF) { struct pipe *pi; @@ -5023,10 +5021,10 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } - o_free(&dest); + o_free(&ctx.word); done_pipe(&ctx, PIPE_SEQ); pi = ctx.list_head; /* If we got nothing... */ @@ -5066,8 +5064,8 @@ static struct pipe *parse_stream(char **pstring, SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ if (ctx.command->argv /* word [word]{... - non-special */ - || dest.length /* word{... - non-special */ - || dest.has_quoted_part /* ""{... - non-special */ + || ctx.word.length /* word{... - non-special */ + || ctx.word.has_quoted_part /* ""{... - non-special */ || (next != ';' /* }; - special */ && next != ')' /* }) - special */ && next != '(' /* {( - special */ @@ -5084,14 +5082,14 @@ static struct pipe *parse_stream(char **pstring, if (!is_special && !is_blank) { /* ordinary char */ ordinary_char: - o_addQchr(&dest, ch); - if ((dest.o_assignment == MAYBE_ASSIGNMENT - || dest.o_assignment == WORD_IS_KEYWORD) + o_addQchr(&ctx.word, ch); + if ((ctx.is_assignment == MAYBE_ASSIGNMENT + || ctx.is_assignment == WORD_IS_KEYWORD) && ch == '=' - && is_well_formed_var_name(dest.data, '=') + && is_well_formed_var_name(ctx.word.data, '=') ) { - dest.o_assignment = DEFINITELY_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = DEFINITELY_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); } continue; } @@ -5113,7 +5111,7 @@ static struct pipe *parse_stream(char **pstring, } /* ch == last eaten whitespace char */ #endif - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } if (ch == '\n') { @@ -5123,7 +5121,7 @@ static struct pipe *parse_stream(char **pstring, * "case ... in word) ..." */ if (IS_NULL_CMD(ctx.command) - && dest.length == 0 && !dest.has_quoted_part + && ctx.word.length == 0 && !ctx.word.has_quoted_part ) { /* This newline can be ignored. But... * Without check #1, interactive shell @@ -5158,8 +5156,8 @@ static struct pipe *parse_stream(char **pstring, } heredoc_cnt = 0; } - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); ch = ';'; /* note: if (is_blank) continue; * will still trigger for us */ @@ -5171,8 +5169,8 @@ static struct pipe *parse_stream(char **pstring, * Pathological example: { ""}; } should exec "}" cmd */ if (ch == '}') { - if (dest.length != 0 /* word} */ - || dest.has_quoted_part /* ""} */ + if (ctx.word.length != 0 /* word} */ + || ctx.word.has_quoted_part /* ""} */ ) { goto ordinary_char; } @@ -5201,7 +5199,7 @@ static struct pipe *parse_stream(char **pstring, #if ENABLE_HUSH_CASE && (ch != ')' || ctx.ctx_res_w != RES_MATCH - || (!dest.has_quoted_part && strcmp(dest.data, "esac") == 0) + || (!ctx.word.has_quoted_part && strcmp(ctx.word.data, "esac") == 0) ) #endif ) { @@ -5218,17 +5216,17 @@ static struct pipe *parse_stream(char **pstring, syntax_error_unterm_str("here document"); goto parse_error; } - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } done_pipe(&ctx, PIPE_SEQ); - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); /* Do we sit outside of any if's, loops or case's? */ if (!HAS_KEYWORDS IF_HAS_KEYWORDS(|| (ctx.ctx_res_w == RES_NONE && ctx.old_flag == 0)) ) { - o_free(&dest); + o_free(&ctx.word); #if !BB_MMU debug_printf_parse("as_string2 '%s'\n", ctx.as_string.data); if (pstring) @@ -5257,8 +5255,8 @@ static struct pipe *parse_stream(char **pstring, * an assignment. a=1 2>z b=2: b=2 is still assignment */ switch (ch) { case '>': - redir_fd = redirect_opt_num(&dest); - if (done_word(&dest, &ctx)) { + redir_fd = redirect_opt_num(&ctx.word); + if (done_word(&ctx)) { goto parse_error; } redir_style = REDIRECT_OVERWRITE; @@ -5279,8 +5277,8 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; continue; /* back to top of while (1) */ case '<': - redir_fd = redirect_opt_num(&dest); - if (done_word(&dest, &ctx)) { + redir_fd = redirect_opt_num(&ctx.word); + if (done_word(&ctx)) { goto parse_error; } redir_style = REDIRECT_INPUT; @@ -5307,7 +5305,7 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; continue; /* back to top of while (1) */ case '#': - if (dest.length == 0 && !dest.has_quoted_part) { + if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { /* skip "#comment" */ /* note: we do not add it to &ctx.as_string */ /* TODO: in bash: @@ -5342,14 +5340,14 @@ static struct pipe *parse_stream(char **pstring, break; } - if (dest.o_assignment == MAYBE_ASSIGNMENT + if (ctx.is_assignment == MAYBE_ASSIGNMENT /* check that we are not in word in "a=1 2>word b=1": */ && !ctx.pending_redirect ) { /* ch is a special char and thus this word * cannot be an assignment */ - dest.o_assignment = NOT_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = NOT_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); } /* Note: nommu_addchr(&ctx.as_string, ch) is already done */ @@ -5357,12 +5355,12 @@ static struct pipe *parse_stream(char **pstring, switch (ch) { case SPECIAL_VAR_SYMBOL: /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); /* fall through */ case '#': /* non-comment #: "echo a#b" etc */ - o_addchr(&dest, ch); + o_addchr(&ctx.word, ch); break; case '\\': if (next == EOF) { @@ -5371,29 +5369,29 @@ static struct pipe *parse_stream(char **pstring, } ch = i_getch(input); /* note: ch != '\n' (that case does not reach this place) */ - o_addchr(&dest, '\\'); + o_addchr(&ctx.word, '\\'); /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&dest, ch); + o_addchr(&ctx.word, ch); nommu_addchr(&ctx.as_string, ch); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; break; case '$': - if (!parse_dollar(&ctx.as_string, &dest, input, /*quote_mask:*/ 0)) { + if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " "parse_dollar returned 0 (error)\n"); goto parse_error; } break; case '\'': - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; if (next == '\'' && !ctx.pending_redirect) { insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); i_getch(input); /* eat second ' */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); } else { while (1) { ch = i_getch(input); @@ -5406,38 +5404,38 @@ static struct pipe *parse_stream(char **pstring, break; if (ch == SPECIAL_VAR_SYMBOL) { /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, SPECIAL_VAR_QUOTED_SVS); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); } - o_addqchr(&dest, ch); + o_addqchr(&ctx.word, ch); } } break; case '"': - dest.has_quoted_part = 1; + ctx.word.has_quoted_part = 1; if (next == '"' && !ctx.pending_redirect) goto insert_empty_quoted_str_marker; - if (dest.o_assignment == NOT_ASSIGNMENT) - dest.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; - if (!encode_string(&ctx.as_string, &dest, input, '"', /*process_bkslash:*/ 1)) + if (ctx.is_assignment == NOT_ASSIGNMENT) + ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; + if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) goto parse_error; - dest.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; + ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; break; #if ENABLE_HUSH_TICK case '`': { USE_FOR_NOMMU(unsigned pos;) - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - o_addchr(&dest, '`'); - USE_FOR_NOMMU(pos = dest.length;) - if (!add_till_backquote(&dest, input, /*in_dquote:*/ 0)) + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, '`'); + USE_FOR_NOMMU(pos = ctx.word.length;) + if (!add_till_backquote(&ctx.word, input, /*in_dquote:*/ 0)) goto parse_error; # if !BB_MMU - o_addstr(&ctx.as_string, dest.data + pos); + o_addstr(&ctx.as_string, ctx.word.data + pos); o_addchr(&ctx.as_string, '`'); # endif - o_addchr(&dest, SPECIAL_VAR_SYMBOL); - //debug_printf_subst("SUBST RES3 '%s'\n", dest.data + pos); + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); break; } #endif @@ -5445,7 +5443,7 @@ static struct pipe *parse_stream(char **pstring, #if ENABLE_HUSH_CASE case_semi: #endif - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } done_pipe(&ctx, PIPE_SEQ); @@ -5468,11 +5466,11 @@ static struct pipe *parse_stream(char **pstring, new_cmd: /* We just finished a cmd. New one may start * with an assignment */ - dest.o_assignment = MAYBE_ASSIGNMENT; - debug_printf_parse("dest.o_assignment='%s'\n", assignment_flag[dest.o_assignment]); + ctx.is_assignment = MAYBE_ASSIGNMENT; + debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); break; case '&': - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } if (next == '\\') @@ -5486,7 +5484,7 @@ static struct pipe *parse_stream(char **pstring, } goto new_cmd; case '|': - if (done_word(&dest, &ctx)) { + if (done_word(&ctx)) { goto parse_error; } #if ENABLE_HUSH_CASE @@ -5511,14 +5509,14 @@ static struct pipe *parse_stream(char **pstring, /* "case... in [(]word)..." - skip '(' */ if (ctx.ctx_res_w == RES_MATCH && ctx.command->argv == NULL /* not (word|(... */ - && dest.length == 0 /* not word(... */ - && dest.has_quoted_part == 0 /* not ""(... */ + && ctx.word.length == 0 /* not word(... */ + && ctx.word.has_quoted_part == 0 /* not ""(... */ ) { continue; } #endif case '{': - if (parse_group(&dest, &ctx, input, ch) != 0) { + if (parse_group(&ctx, input, ch) != 0) { goto parse_error; } goto new_cmd; @@ -5575,7 +5573,7 @@ static struct pipe *parse_stream(char **pstring, IF_HAS_KEYWORDS(pctx = p2;) } while (HAS_KEYWORDS && pctx); - o_free(&dest); + o_free(&ctx.word); #if !BB_MMU if (pstring) *pstring = NULL; -- cgit v1.2.3-55-g6feb From 1c57269b5d1891aef5093e7a5824f1adfbb33847 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 13:09:26 +0200 Subject: hush: simplify \ code, part 1 function old new delta parse_stream 2919 2787 -132 Signed-off-by: Denys Vlasenko --- shell/hush.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 0c57803f1..94ab45053 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4913,6 +4913,9 @@ static int encode_string(o_string *as_string, ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (process_bkslash && ch == '\\') { if (next == EOF) { +// TODO: what if in interactive shell a file with +// echo "unterminated string\ +// is sourced? syntax_error("\\"); xfunc_die(); } @@ -5051,12 +5054,14 @@ static struct pipe *parse_stream(char **pstring, next = '\0'; if (ch != '\n') { - next = i_peek(input); - /* Can't use i_peek_and_eat_bkslash_nl(input) here: + /* Do not break this case: * echo '\ * ' - * will break. + * and + * echo z\\ */ + next = (ch == '\'' || ch == '\\') ? i_peek(input) : i_peek_and_eat_bkslash_nl(input); +/// } is_special = "{}<>;&|()#'" /* special outside of "str" */ @@ -5260,8 +5265,6 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } redir_style = REDIRECT_OVERWRITE; - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '>') { redir_style = REDIRECT_APPEND; ch = i_getch(input); @@ -5282,8 +5285,6 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } redir_style = REDIRECT_INPUT; - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '<') { redir_style = REDIRECT_HEREDOC; heredoc_cnt++; @@ -5327,6 +5328,7 @@ static struct pipe *parse_stream(char **pstring, continue; /* back to top of while (1) */ } break; +#if 0 /* looks like we never reach this code */ case '\\': if (next == '\n') { /* It's "\" */ @@ -5338,6 +5340,7 @@ static struct pipe *parse_stream(char **pstring, continue; /* back to top of while (1) */ } break; +#endif } if (ctx.is_assignment == MAYBE_ASSIGNMENT @@ -5364,6 +5367,7 @@ static struct pipe *parse_stream(char **pstring, break; case '\\': if (next == EOF) { +//TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" syntax_error("\\"); xfunc_die(); } @@ -5473,8 +5477,6 @@ static struct pipe *parse_stream(char **pstring, if (done_word(&ctx)) { goto parse_error; } - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '&') { ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); @@ -5491,8 +5493,6 @@ static struct pipe *parse_stream(char **pstring, if (ctx.ctx_res_w == RES_MATCH) break; /* we are in case's "word | word)" */ #endif - if (next == '\\') - next = i_peek_and_eat_bkslash_nl(input); if (next == '|') { /* || */ ch = i_getch(input); nommu_addchr(&ctx.as_string, ch); -- cgit v1.2.3-55-g6feb From e8b1bc0481828d84cea2862eab0ad13a73b0caca Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 13:13:10 +0200 Subject: hush: simplify \ code, part 2 function old new delta parse_stream 2787 2780 -7 Signed-off-by: Denys Vlasenko --- shell/hush.c | 60 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 94ab45053..3c6718648 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5364,7 +5364,7 @@ static struct pipe *parse_stream(char **pstring, case '#': /* non-comment #: "echo a#b" etc */ o_addchr(&ctx.word, ch); - break; + continue; /* get next char */ case '\\': if (next == EOF) { //TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" @@ -5380,51 +5380,51 @@ static struct pipe *parse_stream(char **pstring, /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ ctx.word.has_quoted_part = 1; - break; + continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " "parse_dollar returned 0 (error)\n"); goto parse_error; } - break; + continue; /* get next char */ case '\'': ctx.word.has_quoted_part = 1; - if (next == '\'' && !ctx.pending_redirect) { + if (next == '\'' && !ctx.pending_redirect) + goto insert_empty_quoted_str_marker; + while (1) { + ch = i_getch(input); + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto parse_error; + } + nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') + break; + if (ch == SPECIAL_VAR_SYMBOL) { + /* Convert raw ^C to corresponding special variable reference */ + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); + } + o_addqchr(&ctx.word, ch); + } + continue; /* get next char */ + case '"': + ctx.word.has_quoted_part = 1; + if (next == '"' && !ctx.pending_redirect) { insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); i_getch(input); /* eat second ' */ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - } else { - while (1) { - ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - goto parse_error; - } - nommu_addchr(&ctx.as_string, ch); - if (ch == '\'') - break; - if (ch == SPECIAL_VAR_SYMBOL) { - /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); - } - o_addqchr(&ctx.word, ch); - } + continue; /* get next char */ } - break; - case '"': - ctx.word.has_quoted_part = 1; - if (next == '"' && !ctx.pending_redirect) - goto insert_empty_quoted_str_marker; if (ctx.is_assignment == NOT_ASSIGNMENT) ctx.word.o_expflags |= EXP_FLAG_ESC_GLOB_CHARS; if (!encode_string(&ctx.as_string, &ctx.word, input, '"', /*process_bkslash:*/ 1)) goto parse_error; ctx.word.o_expflags &= ~EXP_FLAG_ESC_GLOB_CHARS; - break; + continue; /* get next char */ #if ENABLE_HUSH_TICK case '`': { USE_FOR_NOMMU(unsigned pos;) @@ -5440,7 +5440,7 @@ static struct pipe *parse_stream(char **pstring, # endif o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); //debug_printf_subst("SUBST RES3 '%s'\n", ctx.word.data + pos); - break; + continue; /* get next char */ } #endif case ';': @@ -5472,7 +5472,7 @@ static struct pipe *parse_stream(char **pstring, * with an assignment */ ctx.is_assignment = MAYBE_ASSIGNMENT; debug_printf_parse("ctx.is_assignment='%s'\n", assignment_flag[ctx.is_assignment]); - break; + continue; /* get next char */ case '&': if (done_word(&ctx)) { goto parse_error; @@ -5512,7 +5512,7 @@ static struct pipe *parse_stream(char **pstring, && ctx.word.length == 0 /* not word(... */ && ctx.word.has_quoted_part == 0 /* not ""(... */ ) { - continue; + continue; /* get next char */ } #endif case '{': -- cgit v1.2.3-55-g6feb From 92a930b4e8dce1b8d884a83d7f38bb139ab8317f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:20:48 +0200 Subject: hush: simplify \ code, part 3 function old new delta parse_stream 2780 2762 -18 Signed-off-by: Denys Vlasenko --- shell/hush.c | 100 +++++++++++++++++++++++++++-------------------------------- 1 file changed, 45 insertions(+), 55 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 3c6718648..6cd85cc4d 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5052,19 +5052,43 @@ static struct pipe *parse_stream(char **pstring, } nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') { + ctx.word.has_quoted_part = 1; + next = i_getch(input); + if (next == '\'' && !ctx.pending_redirect) + goto insert_empty_quoted_str_marker; + + ch = next; + while (1) { + if (ch == EOF) { + syntax_error_unterm_ch('\''); + goto parse_error; + } + nommu_addchr(&ctx.as_string, ch); + if (ch == '\'') + break; + if (ch == SPECIAL_VAR_SYMBOL) { + /* Convert raw ^C to corresponding special variable reference */ + o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); + o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); + } + o_addqchr(&ctx.word, ch); + ch = i_getch(input); + } + continue; /* get next char */ + } + next = '\0'; - if (ch != '\n') { - /* Do not break this case: - * echo '\ - * ' - * and - * echo z\\ + if (ch != '\n' && ch != '\\') { + /* Not on '\': do not break the case of "echo z\\": + * on 2nd '\', i_peek_and_eat_bkslash_nl() + * would stop and try to read next line, + * not letting the command to execute. */ - next = (ch == '\'' || ch == '\\') ? i_peek(input) : i_peek_and_eat_bkslash_nl(input); -/// + next = i_peek_and_eat_bkslash_nl(input); } - is_special = "{}<>;&|()#'" /* special outside of "str" */ + is_special = "{}<>;&|()#" /* special outside of "str" */ "\\$\"" IF_HUSH_TICK("`") /* always special */ SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ @@ -5252,7 +5276,7 @@ static struct pipe *parse_stream(char **pstring, return ctx.list_head; } } - skip_end_trigger: + if (is_blank) continue; @@ -5278,7 +5302,7 @@ static struct pipe *parse_stream(char **pstring, #endif if (parse_redirect(&ctx, redir_fd, redir_style, input)) goto parse_error; - continue; /* back to top of while (1) */ + continue; /* get next char */ case '<': redir_fd = redirect_opt_num(&ctx.word); if (done_word(&ctx)) { @@ -5304,7 +5328,7 @@ static struct pipe *parse_stream(char **pstring, #endif if (parse_redirect(&ctx, redir_fd, redir_style, input)) goto parse_error; - continue; /* back to top of while (1) */ + continue; /* get next char */ case '#': if (ctx.word.length == 0 && !ctx.word.has_quoted_part) { /* skip "#comment" */ @@ -5325,23 +5349,11 @@ static struct pipe *parse_stream(char **pstring, if (ch == EOF) break; } - continue; /* back to top of while (1) */ - } - break; -#if 0 /* looks like we never reach this code */ - case '\\': - if (next == '\n') { - /* It's "\" */ -#if !BB_MMU - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - ch = i_getch(input); /* eat it */ - continue; /* back to top of while (1) */ + continue; /* get next char */ } break; -#endif } + skip_end_trigger: if (ctx.is_assignment == MAYBE_ASSIGNMENT /* check that we are not in word in "a=1 2>word b=1": */ @@ -5366,20 +5378,19 @@ static struct pipe *parse_stream(char **pstring, o_addchr(&ctx.word, ch); continue; /* get next char */ case '\\': - if (next == EOF) { + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); + ch = i_getch(input); + if (ch == EOF) { //TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" syntax_error("\\"); xfunc_die(); } - ch = i_getch(input); - /* note: ch != '\n' (that case does not reach this place) */ - o_addchr(&ctx.word, '\\'); - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, ch); - nommu_addchr(&ctx.as_string, ch); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { @@ -5388,33 +5399,12 @@ static struct pipe *parse_stream(char **pstring, goto parse_error; } continue; /* get next char */ - case '\'': - ctx.word.has_quoted_part = 1; - if (next == '\'' && !ctx.pending_redirect) - goto insert_empty_quoted_str_marker; - while (1) { - ch = i_getch(input); - if (ch == EOF) { - syntax_error_unterm_ch('\''); - goto parse_error; - } - nommu_addchr(&ctx.as_string, ch); - if (ch == '\'') - break; - if (ch == SPECIAL_VAR_SYMBOL) { - /* Convert raw ^C to corresponding special variable reference */ - o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); - o_addchr(&ctx.word, SPECIAL_VAR_QUOTED_SVS); - } - o_addqchr(&ctx.word, ch); - } - continue; /* get next char */ case '"': ctx.word.has_quoted_part = 1; if (next == '"' && !ctx.pending_redirect) { + i_getch(input); /* eat second " */ insert_empty_quoted_str_marker: nommu_addchr(&ctx.as_string, next); - i_getch(input); /* eat second ' */ o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); o_addchr(&ctx.word, SPECIAL_VAR_SYMBOL); continue; /* get next char */ -- cgit v1.2.3-55-g6feb From bcf56114fa7f037cea579cdc8d17ac1a5dab93a3 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:40:23 +0200 Subject: hush: fix eval 'echo ok\' function old new delta parse_stream 2762 2753 -9 Signed-off-by: Denys Vlasenko --- shell/hush.c | 14 +++++++++----- shell/hush_test/hush-parsing/bkslash_eof1.right | 1 + shell/hush_test/hush-parsing/bkslash_eof1.tests | 1 + 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 shell/hush_test/hush-parsing/bkslash_eof1.right create mode 100755 shell/hush_test/hush-parsing/bkslash_eof1.tests diff --git a/shell/hush.c b/shell/hush.c index 6cd85cc4d..5df8d4744 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5379,15 +5379,19 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ case '\\': /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); ch = i_getch(input); if (ch == EOF) { -//TODO: in ". FILE" containing "cmd\" (no newline) bash ignores last "\" - syntax_error("\\"); - xfunc_die(); + /* Ignore this '\'. Testcase: eval 'echo Ok\' */ +#if !BB_MMU + /* Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; +#endif + continue; /* get next char */ } + o_addchr(&ctx.word, '\\'); /* Example: echo Hello \2>file - * we need to know that word 2 is quoted */ + * we need to know that word 2 is quoted + */ ctx.word.has_quoted_part = 1; nommu_addchr(&ctx.as_string, ch); o_addchr(&ctx.word, ch); diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right new file mode 100644 index 000000000..9766475a4 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right @@ -0,0 +1 @@ +ok diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.tests b/shell/hush_test/hush-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof1.tests @@ -0,0 +1 @@ +eval 'echo ok\' -- cgit v1.2.3-55-g6feb From 4709df0f152c477c191f83e18bfecabb2fb2c1f9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 14:49:01 +0200 Subject: hush: fix handling of \ in double-quoted strings function old new delta encode_string 268 250 -18 Signed-off-by: Denys Vlasenko --- shell/hush.c | 11 ++++++----- shell/hush_test/hush-parsing/bkslash_eof2.right | 2 ++ shell/hush_test/hush-parsing/bkslash_eof2.tests | 4 ++++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 shell/hush_test/hush-parsing/bkslash_eof2.right create mode 100755 shell/hush_test/hush-parsing/bkslash_eof2.tests diff --git a/shell/hush.c b/shell/hush.c index 5df8d4744..98ba96e0c 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -4913,11 +4913,12 @@ static int encode_string(o_string *as_string, ch, ch, !!(dest->o_expflags & EXP_FLAG_ESC_GLOB_CHARS)); if (process_bkslash && ch == '\\') { if (next == EOF) { -// TODO: what if in interactive shell a file with -// echo "unterminated string\ -// is sourced? - syntax_error("\\"); - xfunc_die(); + /* Testcase: in interactive shell a file with + * echo "unterminated string\ + * is sourced. + */ + syntax_error_unterm_ch('"'); + return 0; /* error */ } /* bash: * "The backslash retains its special meaning [in "..."] diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.right b/shell/hush_test/hush-parsing/bkslash_eof2.right new file mode 100644 index 000000000..8be75727f --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.right @@ -0,0 +1,2 @@ +hush: syntax error: unterminated " +One:1 diff --git a/shell/hush_test/hush-parsing/bkslash_eof2.tests b/shell/hush_test/hush-parsing/bkslash_eof2.tests new file mode 100755 index 000000000..da1f08db6 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_eof2.tests @@ -0,0 +1,4 @@ +printf 'echo "unterminated string\\' >test.tmp.sh +. ./test.tmp.sh +echo One:$? +rm -f test.tmp.sh -- cgit v1.2.3-55-g6feb From 3632cb15f16a7596a68dccfd66a2ad9496bf9fd9 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 10 Apr 2018 15:25:41 +0200 Subject: shell: add comments about [[, no code changes Signed-off-by: Denys Vlasenko --- coreutils/test.c | 4 ++++ shell/ash.c | 15 ++++++++++++++- shell/hush.c | 12 ++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/coreutils/test.c b/coreutils/test.c index 824ce3b5a..ddb66ddce 100644 --- a/coreutils/test.c +++ b/coreutils/test.c @@ -313,6 +313,9 @@ static const struct operator_t ops_table[] = { { /* "-L" */ FILSYM , UNOP }, { /* "-S" */ FILSOCK , UNOP }, { /* "=" */ STREQ , BINOP }, + /* "==" is bashism, http://pubs.opengroup.org/onlinepubs/9699919799/utilities/test.html + * lists only "=" as comparison operator. + */ { /* "==" */ STREQ , BINOP }, { /* "!=" */ STRNE , BINOP }, { /* "<" */ STRLT , BINOP }, @@ -357,6 +360,7 @@ static const char ops_texts[] ALIGN1 = "-L" "\0" "-S" "\0" "=" "\0" + /* "==" is bashism */ "==" "\0" "!=" "\0" "<" "\0" diff --git a/shell/ash.c b/shell/ash.c index 45c747dbc..713219b6e 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -197,7 +197,20 @@ #define IF_BASH_PATTERN_SUBST IF_ASH_BASH_COMPAT #define BASH_SUBSTR ENABLE_ASH_BASH_COMPAT #define IF_BASH_SUBSTR IF_ASH_BASH_COMPAT -/* [[ EXPR ]] */ +/* BASH_TEST2: [[ EXPR ]] + * Status of [[ support: + * We replace && and || with -a and -o + * TODO: + * singleword+noglob expansion: + * v='a b'; [[ $v = 'a b' ]]; echo 0:$? + * [[ /bin/* ]]; echo 0:$? + * -a/-o are not AND/OR ops! (they are just strings) + * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) + * = is glob match operator, not equality operator: STR = GLOB + * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") + * == same as = + * add =~ regex match operator: STR =~ REGEX + */ #define BASH_TEST2 (ENABLE_ASH_BASH_COMPAT * ENABLE_ASH_TEST) #define BASH_SOURCE ENABLE_ASH_BASH_COMPAT #define BASH_PIPEFAIL ENABLE_ASH_BASH_COMPAT diff --git a/shell/hush.c b/shell/hush.c index 98ba96e0c..3afb70cb0 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -79,6 +79,18 @@ * Some builtins mandated by standards: * newgrp [GRP]: not a builtin in bash but a suid binary * which spawns a new shell with new group ID + * + * Status of [[ support: + * [[ args ]] are CMD_SINGLEWORD_NOGLOB: + * v='a b'; [[ $v = 'a b' ]]; echo 0:$? + * [[ /bin/* ]]; echo 0:$? + * TODO: + * &&/|| are AND/OR ops, -a/-o are not + * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) + * = is glob match operator, not equality operator: STR = GLOB + * (in GLOB, quoting is significant on char-by-char basis: a*cd"*") + * == same as = + * add =~ regex match operator: STR =~ REGEX */ //config:config HUSH //config: bool "hush (64 kb)" -- cgit v1.2.3-55-g6feb From 89e9d5534d0e8879803ed9dbb25dff3989c31202 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 01:15:33 +0200 Subject: hush: do not drop backslash from eval 'echo ok\' newer bash does not drop it, most other shells too function old new delta unbackslash 39 57 +18 parse_stream 2753 2751 -2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 18/-2) Total: 16 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 2 +- shell/ash_test/ash-parsing/bkslash_eof1.right | 1 + shell/ash_test/ash-parsing/bkslash_eof1.tests | 1 + shell/ash_test/ash-redir/redir_exec1.right | 2 +- shell/hush.c | 22 +++++++++++++++++----- shell/hush_test/hush-parsing/bkslash_eof1.right | 2 +- 6 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 shell/ash_test/ash-parsing/bkslash_eof1.right create mode 100755 shell/ash_test/ash-parsing/bkslash_eof1.tests diff --git a/shell/ash.c b/shell/ash.c index 713219b6e..6f8bc9042 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -203,7 +203,7 @@ * TODO: * singleword+noglob expansion: * v='a b'; [[ $v = 'a b' ]]; echo 0:$? - * [[ /bin/* ]]; echo 0:$? + * [[ /bin/n* ]]; echo 0:$? * -a/-o are not AND/OR ops! (they are just strings) * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) * = is glob match operator, not equality operator: STR = GLOB diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.right b/shell/ash_test/ash-parsing/bkslash_eof1.right new file mode 100644 index 000000000..6c6df0b0c --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.right @@ -0,0 +1 @@ +ok\ diff --git a/shell/ash_test/ash-parsing/bkslash_eof1.tests b/shell/ash_test/ash-parsing/bkslash_eof1.tests new file mode 100755 index 000000000..97629cb13 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_eof1.tests @@ -0,0 +1 @@ +eval 'echo ok\' diff --git a/shell/ash_test/ash-redir/redir_exec1.right b/shell/ash_test/ash-redir/redir_exec1.right index d4393d10c..c98455bf5 100644 --- a/shell/ash_test/ash-redir/redir_exec1.right +++ b/shell/ash_test/ash-redir/redir_exec1.right @@ -1,2 +1,2 @@ -redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory +./redir_exec1.tests: line 1: can't create /cant/be/created: nonexistent directory First diff --git a/shell/hush.c b/shell/hush.c index 3afb70cb0..523fc1a31 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -83,7 +83,7 @@ * Status of [[ support: * [[ args ]] are CMD_SINGLEWORD_NOGLOB: * v='a b'; [[ $v = 'a b' ]]; echo 0:$? - * [[ /bin/* ]]; echo 0:$? + * [[ /bin/n* ]]; echo 0:$? * TODO: * &&/|| are AND/OR ops, -a/-o are not * quoting needs to be considered (-f is an operator, "-f" and ""-f are not; etc) @@ -1426,8 +1426,19 @@ static char *unbackslash(char *src) { char *dst = src = strchrnul(src, '\\'); while (1) { - if (*src == '\\') + if (*src == '\\') { src++; + if (*src != '\0') { + /* \x -> x */ + *dst++ = *src++; + continue; + } + /* else: "\". Do not delete this backslash. + * Testcase: eval 'echo ok\' + */ + *dst++ = '\\'; + /* fallthrough */ + } if ((*dst++ = *src++) == '\0') break; } @@ -5392,16 +5403,17 @@ static struct pipe *parse_stream(char **pstring, continue; /* get next char */ case '\\': /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); ch = i_getch(input); if (ch == EOF) { - /* Ignore this '\'. Testcase: eval 'echo Ok\' */ -#if !BB_MMU + /* Testcase: eval 'echo Ok\' */ + +#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ /* Remove trailing '\' from ctx.as_string */ ctx.as_string.data[--ctx.as_string.length] = '\0'; #endif continue; /* get next char */ } - o_addchr(&ctx.word, '\\'); /* Example: echo Hello \2>file * we need to know that word 2 is quoted */ diff --git a/shell/hush_test/hush-parsing/bkslash_eof1.right b/shell/hush_test/hush-parsing/bkslash_eof1.right index 9766475a4..6c6df0b0c 100644 --- a/shell/hush_test/hush-parsing/bkslash_eof1.right +++ b/shell/hush_test/hush-parsing/bkslash_eof1.right @@ -1 +1 @@ -ok +ok\ -- cgit v1.2.3-55-g6feb From 0403bedccc17c8ea3059523e32ea615e5df4bc26 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 01:33:54 +0200 Subject: hush: optimize parse_stream() Since we check for '\' anyway when we determine whether we can look ahead, we can just check for *and handle* it there. function old new delta parse_stream 2751 2740 -11 Signed-off-by: Denys Vlasenko --- shell/hush.c | 59 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/shell/hush.c b/shell/hush.c index 523fc1a31..735fbef27 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5076,6 +5076,14 @@ static struct pipe *parse_stream(char **pstring, } nommu_addchr(&ctx.as_string, ch); + /* Handle "'" and "\" first, as they won't play nice with + * i_peek_and_eat_bkslash_nl() anyway: + * echo z\\ + * and + * echo '\ + * ' + * would break. + */ if (ch == '\'') { ctx.word.has_quoted_part = 1; next = i_getch(input); @@ -5101,19 +5109,34 @@ static struct pipe *parse_stream(char **pstring, } continue; /* get next char */ } + if (ch == '\\') { + /*nommu_addchr(&ctx.as_string, '\\'); - already done */ + o_addchr(&ctx.word, '\\'); + ch = i_getch(input); + if (ch == EOF) { + /* Testcase: eval 'echo Ok\' */ - next = '\0'; - if (ch != '\n' && ch != '\\') { - /* Not on '\': do not break the case of "echo z\\": - * on 2nd '\', i_peek_and_eat_bkslash_nl() - * would stop and try to read next line, - * not letting the command to execute. +#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ + /* Remove trailing '\' from ctx.as_string */ + ctx.as_string.data[--ctx.as_string.length] = '\0'; +#endif + continue; /* get next char */ + } + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted */ - next = i_peek_and_eat_bkslash_nl(input); + ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); + continue; /* get next char */ } + next = '\0'; + if (ch != '\n') + next = i_peek_and_eat_bkslash_nl(input); + is_special = "{}<>;&|()#" /* special outside of "str" */ - "\\$\"" IF_HUSH_TICK("`") /* always special */ + "$\"" IF_HUSH_TICK("`") /* always special */ SPECIAL_VAR_SYMBOL_STR; /* Are { and } special here? */ if (ctx.command->argv /* word [word]{... - non-special */ @@ -5401,26 +5424,6 @@ static struct pipe *parse_stream(char **pstring, /* non-comment #: "echo a#b" etc */ o_addchr(&ctx.word, ch); continue; /* get next char */ - case '\\': - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); - ch = i_getch(input); - if (ch == EOF) { - /* Testcase: eval 'echo Ok\' */ - -#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - continue; /* get next char */ - } - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted - */ - ctx.word.has_quoted_part = 1; - nommu_addchr(&ctx.as_string, ch); - o_addchr(&ctx.word, ch); - continue; /* get next char */ case '$': if (!parse_dollar(&ctx.as_string, &ctx.word, input, /*quote_mask:*/ 0)) { debug_printf_parse("parse_stream parse error: " -- cgit v1.2.3-55-g6feb From 680c3016a2dddc3edb4d79868a728e899638e2c4 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 12:39:18 +0200 Subject: ash: parser: Allow newlines within parameter substitution Upstream commit: Date: Thu, 22 Mar 2018 21:41:24 +0800 parser: Allow newlines within parameter substitution On Fri, Mar 16, 2018 at 11:27:22AM +0800, Herbert Xu wrote: > On Thu, Mar 15, 2018 at 10:49:15PM +0100, Harald van Dijk wrote: > > > > Okay, it can be trivially modified to something that does work in other > > shells (even if it were actually executed), but gets rejected at parse time > > by dash: > > > > if false; then > > : ${$+ > > } > > fi > > That's just a bug in dash's parser with ${} in general, because > it bombs out without the if clause too: > > : ${$+ > } This patch fixes the parsing of newlines with parameter substitution. Signed-off-by: Herbert Xu Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 ++++- shell/ash_test/ash-vars/param_expand_alt2.right | 4 ++++ shell/ash_test/ash-vars/param_expand_alt2.tests | 7 +++++++ shell/hush_test/hush-vars/param_expand_alt2.right | 4 ++++ shell/hush_test/hush-vars/param_expand_alt2.tests | 7 +++++++ 5 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 shell/ash_test/ash-vars/param_expand_alt2.right create mode 100755 shell/ash_test/ash-vars/param_expand_alt2.tests create mode 100644 shell/hush_test/hush-vars/param_expand_alt2.right create mode 100755 shell/hush_test/hush-vars/param_expand_alt2.tests diff --git a/shell/ash.c b/shell/ash.c index 6f8bc9042..40ca82d0b 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -11999,8 +11999,11 @@ readtoken1(int c, int syntax, char *eofmark, int striptabs) CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ switch (SIT(c, synstack->syntax)) { case CNL: /* '\n' */ - if (synstack->syntax == BASESYNTAX) + if (synstack->syntax == BASESYNTAX + && !synstack->varnest + ) { goto endword; /* exit outer loop */ + } USTPUTC(c, out); nlprompt(); c = pgetc(); diff --git a/shell/ash_test/ash-vars/param_expand_alt2.right b/shell/ash_test/ash-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.right @@ -0,0 +1,4 @@ +Unquoted: H H +Quoted: H +H +Ok:0 diff --git a/shell/ash_test/ash-vars/param_expand_alt2.tests b/shell/ash_test/ash-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/ash_test/ash-vars/param_expand_alt2.tests @@ -0,0 +1,7 @@ +echo Unquoted: H${$+ +}H + +echo Quoted: "H${$+ +}H" + +echo Ok:$? diff --git a/shell/hush_test/hush-vars/param_expand_alt2.right b/shell/hush_test/hush-vars/param_expand_alt2.right new file mode 100644 index 000000000..fef5889ca --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.right @@ -0,0 +1,4 @@ +Unquoted: H H +Quoted: H +H +Ok:0 diff --git a/shell/hush_test/hush-vars/param_expand_alt2.tests b/shell/hush_test/hush-vars/param_expand_alt2.tests new file mode 100755 index 000000000..d8abf4c3b --- /dev/null +++ b/shell/hush_test/hush-vars/param_expand_alt2.tests @@ -0,0 +1,7 @@ +echo Unquoted: H${$+ +}H + +echo Quoted: "H${$+ +}H" + +echo Ok:$? -- cgit v1.2.3-55-g6feb From 34179956f96370f5a53e73073d984d62135cd037 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 13:47:59 +0200 Subject: hush: fix "$v" expansion in case patterns when v='[a]' function old new delta run_list 1053 1063 +10 setup_redirects 311 320 +9 encode_then_expand_string 135 142 +7 run_pipe 1784 1789 +5 expand_assignments 81 86 +5 expand_string_to_string 124 125 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 6/0 up/down: 37/0) Total: 37 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-quoting/case_glob1.right | 1 + shell/ash_test/ash-quoting/case_glob1.tests | 8 +++++ shell/hush.c | 50 ++++++++++++++++++--------- shell/hush_test/hush-quoting/case_glob1.right | 1 + shell/hush_test/hush-quoting/case_glob1.tests | 8 +++++ 5 files changed, 52 insertions(+), 16 deletions(-) create mode 100644 shell/ash_test/ash-quoting/case_glob1.right create mode 100755 shell/ash_test/ash-quoting/case_glob1.tests create mode 100644 shell/hush_test/hush-quoting/case_glob1.right create mode 100755 shell/hush_test/hush-quoting/case_glob1.tests diff --git a/shell/ash_test/ash-quoting/case_glob1.right b/shell/ash_test/ash-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.right @@ -0,0 +1 @@ +s diff --git a/shell/ash_test/ash-quoting/case_glob1.tests b/shell/ash_test/ash-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/ash_test/ash-quoting/case_glob1.tests @@ -0,0 +1,8 @@ +g='[3](a)(b)(c)' +s='[3](a)(b)(c)' +case $g in +"$s") echo s + ;; +*) echo "*" + ;; +esac diff --git a/shell/hush.c b/shell/hush.c index 735fbef27..248364be2 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5610,11 +5610,10 @@ static struct pipe *parse_stream(char **pstring, /* Expansion can recurse, need forward decls: */ #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE -/* only ${var/pattern/repl} (its pattern part) needs additional mode */ -#define expand_string_to_string(str, do_unbackslash) \ +#define expand_string_to_string(str, EXP_flags, do_unbackslash) \ expand_string_to_string(str) #endif -static char *expand_string_to_string(const char *str, int do_unbackslash); +static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash); #if ENABLE_HUSH_TICK static int process_command_subs(o_string *dest, const char *s); #endif @@ -5760,7 +5759,10 @@ static char *encode_then_expand_string(const char *str, int process_bkslash, int encode_string(NULL, &dest, &input, EOF, process_bkslash); //TODO: error check (encode_string returns 0 on error)? //bb_error_msg("'%s' -> '%s'", str, dest.data); - exp_str = expand_string_to_string(dest.data, /*unbackslash:*/ do_unbackslash); + exp_str = expand_string_to_string(dest.data, + do_unbackslash ? EXP_FLAG_ESC_GLOB_CHARS : 0, + do_unbackslash + ); //bb_error_msg("'%s' -> '%s'", dest.data, exp_str); o_free_unsafe(&dest); return exp_str; @@ -6393,10 +6395,11 @@ static char **expand_strvec_to_strvec_singleword_noglob(char **argv) * NB: should NOT do globbing! * "export v=/bin/c*; env | grep ^v=" outputs "v=/bin/c*" */ -static char *expand_string_to_string(const char *str, int do_unbackslash) +static char *expand_string_to_string(const char *str, int EXP_flags, int do_unbackslash) { #if !BASH_PATTERN_SUBST && !ENABLE_HUSH_CASE const int do_unbackslash = 1; + const int EXP_flags = EXP_FLAG_ESC_GLOB_CHARS; #endif char *argv[2], **list; @@ -6413,10 +6416,7 @@ static char *expand_string_to_string(const char *str, int do_unbackslash) argv[0] = (char*)str; argv[1] = NULL; - list = expand_variables(argv, do_unbackslash - ? EXP_FLAG_ESC_GLOB_CHARS | EXP_FLAG_SINGLEWORD - : EXP_FLAG_SINGLEWORD - ); + list = expand_variables(argv, EXP_flags | EXP_FLAG_SINGLEWORD); if (HUSH_DEBUG) if (!list[0] || list[1]) bb_error_msg_and_die("BUG in varexp2"); @@ -6460,7 +6460,13 @@ static char **expand_assignments(char **argv, int count) G.expanded_assignments = p = NULL; /* Expand assignments into one string each */ for (i = 0; i < count; i++) { - G.expanded_assignments = p = add_string_to_strings(p, expand_string_to_string(argv[i], /*unbackslash:*/ 1)); + p = add_string_to_strings(p, + expand_string_to_string(argv[i], + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 1 + ) + ); + G.expanded_assignments = p; } G.expanded_assignments = NULL; return p; @@ -7172,7 +7178,8 @@ static int setup_redirects(struct command *prog, struct squirrel **sqp) continue; } mode = redir_table[redir->rd_type].mode; - p = expand_string_to_string(redir->rd_filename, /*unbackslash:*/ 1); + p = expand_string_to_string(redir->rd_filename, + EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); newfd = open_or_warn(p, mode); free(p); if (newfd < 0) { @@ -8370,7 +8377,10 @@ static NOINLINE int run_pipe(struct pipe *pi) bb_putchar_stderr('+'); i = 0; while (i < command->assignment_cnt) { - char *p = expand_string_to_string(argv[i], /*unbackslash:*/ 1); + char *p = expand_string_to_string(argv[i], + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 1 + ); if (G_x_mode) fprintf(stderr, " %s", p); debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p); @@ -8865,7 +8875,8 @@ static int run_list(struct pipe *pi) #if ENABLE_HUSH_CASE if (rword == RES_CASE) { debug_printf_exec("CASE cond_code:%d\n", cond_code); - case_word = expand_string_to_string(pi->cmds->argv[0], 1); + case_word = expand_string_to_string(pi->cmds->argv[0], + EXP_FLAG_ESC_GLOB_CHARS, /*unbackslash:*/ 1); debug_printf_exec("CASE word1:'%s'\n", case_word); //unbackslash(case_word); //debug_printf_exec("CASE word2:'%s'\n", case_word); @@ -8880,12 +8891,19 @@ static int run_list(struct pipe *pi) /* all prev words didn't match, does this one match? */ argv = pi->cmds->argv; while (*argv) { - char *pattern = expand_string_to_string(*argv, /*unbackslash:*/ 0); + char *pattern; + debug_printf_exec("expand_string_to_string('%s')\n", *argv); + pattern = expand_string_to_string(*argv, + EXP_FLAG_ESC_GLOB_CHARS, + /*unbackslash:*/ 0 + ); /* TODO: which FNM_xxx flags to use? */ cond_code = (fnmatch(pattern, case_word, /*flags:*/ 0) != 0); - debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", pattern, case_word, cond_code); + debug_printf_exec("fnmatch(pattern:'%s',str:'%s'):%d\n", + pattern, case_word, cond_code); free(pattern); - if (cond_code == 0) { /* match! we will execute this branch */ + if (cond_code == 0) { + /* match! we will execute this branch */ free(case_word); case_word = NULL; /* make future "word)" stop */ break; diff --git a/shell/hush_test/hush-quoting/case_glob1.right b/shell/hush_test/hush-quoting/case_glob1.right new file mode 100644 index 000000000..b4785957b --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.right @@ -0,0 +1 @@ +s diff --git a/shell/hush_test/hush-quoting/case_glob1.tests b/shell/hush_test/hush-quoting/case_glob1.tests new file mode 100755 index 000000000..8dbbc0fb1 --- /dev/null +++ b/shell/hush_test/hush-quoting/case_glob1.tests @@ -0,0 +1,8 @@ +g='[3](a)(b)(c)' +s='[3](a)(b)(c)' +case $g in +"$s") echo s + ;; +*) echo "*" + ;; +esac -- cgit v1.2.3-55-g6feb From 9678636911b39a7adf9b51d5b625cf4dc7e4ac81 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 16:02:58 +0200 Subject: hush: IFS fixes $ IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done |x| $ IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done |x| || function old new delta run_pipe 1789 1870 +81 expand_on_ifs 310 361 +51 pseudo_exec_argv 588 591 +3 builtin_local 50 53 +3 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 4/0 up/down: 138/0) Total: 138 bytes Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-vars/var_wordsplit_ifs4.right | 5 +++ shell/ash_test/ash-vars/var_wordsplit_ifs4.tests | 4 +++ shell/hush.c | 38 ++++++++++++++++++++-- shell/hush_test/hush-vars/var_wordsplit_ifs4.right | 5 +++ shell/hush_test/hush-vars/var_wordsplit_ifs4.tests | 4 +++ 5 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 shell/ash_test/ash-vars/var_wordsplit_ifs4.right create mode 100755 shell/ash_test/ash-vars/var_wordsplit_ifs4.tests create mode 100644 shell/hush_test/hush-vars/var_wordsplit_ifs4.right create mode 100755 shell/hush_test/hush-vars/var_wordsplit_ifs4.tests diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.right b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.right @@ -0,0 +1,5 @@ +|x| +Ok1:0 +|x| +|| +Ok2:0 diff --git a/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/ash_test/ash-vars/var_wordsplit_ifs4.tests @@ -0,0 +1,4 @@ +IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done +echo Ok1:$? +IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done +echo Ok2:$? diff --git a/shell/hush.c b/shell/hush.c index 248364be2..8e95a26a6 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -930,6 +930,7 @@ struct globals { unsigned getopt_count; #endif const char *ifs; + char *ifs_whitespace; /* = G.ifs or malloced */ const char *cwd; struct variable *top_var; char **expanded_assignments; @@ -5696,10 +5697,20 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha /* We know str here points to at least one IFS char */ last_is_ifs = 1; - str += strspn(str, G.ifs); /* skip IFS chars */ + str += strspn(str, G.ifs_whitespace); /* skip IFS whitespace chars */ if (!*str) /* EOL - do not finalize word */ break; + if (G.ifs_whitespace != G.ifs /* usually false ($IFS is usually all whitespace), */ + && strchr(G.ifs, *str) /* the second check would fail */ + ) { + /* This is a non-whitespace $IFS char */ + /* Skip it and IFS whitespace chars, start new word */ + str++; + str += strspn(str, G.ifs_whitespace); + goto new_word; + } + /* Start new word... but not always! */ /* Case "v=' a'; echo ''$v": we do need to finalize empty word: */ if (output->has_quoted_part @@ -5710,6 +5721,7 @@ static int expand_on_ifs(int *ended_with_ifs, o_string *output, int n, const cha */ || (n > 0 && output->data[output->length - 1]) ) { + new_word: o_addchr(output, '\0'); debug_print_list("expand_on_ifs", output, n); n = o_save_ptr(output, n); @@ -8283,9 +8295,31 @@ static NOINLINE int run_pipe(struct pipe *pi) /* Testcase: set -- q w e; (IFS='' echo "$*"; IFS=''; echo "$*"); echo "$*" * Result should be 3 lines: q w e, qwe, q w e */ + if (G.ifs_whitespace != G.ifs) + free(G.ifs_whitespace); G.ifs = get_local_var_value("IFS"); - if (!G.ifs) + if (G.ifs) { + char *p; + G.ifs_whitespace = (char*)G.ifs; + p = skip_whitespace(G.ifs); + if (*p) { + /* Not all $IFS is whitespace */ + char *d; + int len = p - G.ifs; + p = skip_non_whitespace(p); + G.ifs_whitespace = xmalloc(len + strlen(p) + 1); /* can overestimate */ + d = mempcpy(G.ifs_whitespace, G.ifs, len); + while (*p) { + if (isspace(*p)) + *d++ = *p; + p++; + } + *d = '\0'; + } + } else { G.ifs = defifs; + G.ifs_whitespace = (char*)G.ifs; + } IF_HUSH_JOB(pi->pgrp = -1;) pi->stopped_cmds = 0; diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.right b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right new file mode 100644 index 000000000..c27284c31 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.right @@ -0,0 +1,5 @@ +|x| +Ok1:0 +|x| +|| +Ok2:0 diff --git a/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests new file mode 100755 index 000000000..638bfbb28 --- /dev/null +++ b/shell/hush_test/hush-vars/var_wordsplit_ifs4.tests @@ -0,0 +1,4 @@ +IFS=": "; x=" "; set x $x; for v; do echo "|$v|"; done +echo Ok1:$? +IFS=": "; x=":"; set x $x; for v; do echo "|$v|"; done +echo Ok2:$? -- cgit v1.2.3-55-g6feb From 44257ad5d0790a846423c9ef69a50049366b4578 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 17:18:34 +0200 Subject: hush: fix IFS handling in read $ echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") |X|Y| $ echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") |X|Y| function old new delta shell_builtin_read 1320 1426 +106 Signed-off-by: Denys Vlasenko --- shell/hush_test/hush-read/read_ifs2.right | 9 ++++++++ shell/hush_test/hush-read/read_ifs2.tests | 9 ++++++++ shell/shell_common.c | 37 ++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 shell/hush_test/hush-read/read_ifs2.right create mode 100755 shell/hush_test/hush-read/read_ifs2.tests diff --git a/shell/hush_test/hush-read/read_ifs2.right b/shell/hush_test/hush-read/read_ifs2.right new file mode 100644 index 000000000..797137dae --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.right @@ -0,0 +1,9 @@ +|X|Y:Z:| +|X|Y:Z| +|X|Y| +|X|Y| +|X|| +|X|| +||| +Whitespace should be trimmed too: +|X|Y| diff --git a/shell/hush_test/hush-read/read_ifs2.tests b/shell/hush_test/hush-read/read_ifs2.tests new file mode 100755 index 000000000..f01a68978 --- /dev/null +++ b/shell/hush_test/hush-read/read_ifs2.tests @@ -0,0 +1,9 @@ +echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X" | (IFS=": " read x y; echo "|$x|$y|") +echo "" | (IFS=": " read x y; echo "|$x|$y|") +echo Whitespace should be trimmed too: +echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") diff --git a/shell/shell_common.c b/shell/shell_common.c index 9e58ee4fe..0a07296f3 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -274,9 +274,44 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), if (argv[0]) { /* Remove trailing space $IFS chars */ - while (--bufpos >= 0 && isspace(buffer[bufpos]) && strchr(ifs, buffer[bufpos]) != NULL) + while (--bufpos >= 0 + && isspace(buffer[bufpos]) + && strchr(ifs, buffer[bufpos]) != NULL + ) { continue; + } buffer[bufpos + 1] = '\0'; + + /* Last variable takes the entire remainder with delimiters + * (sans trailing whitespace $IFS), + * but ***only "if there are fewer vars than fields"(c)***! + * The "X:Y:" case below: there are two fields, + * and therefore last delimiter (:) is eaten: + * IFS=": " + * echo "X:Y:Z:" | (read x y; echo "|$x|$y|") # |X|Y:Z:| + * echo "X:Y:Z" | (read x y; echo "|$x|$y|") # |X|Y:Z| + * echo "X:Y:" | (read x y; echo "|$x|$y|") # |X|Y|, not |X|Y:| + * echo "X:Y : " | (read x y; echo "|$x|$y|") # |X|Y| + */ + if (bufpos >= 0 + && strchr(ifs, buffer[bufpos]) != NULL + ) { + /* There _is_ a non-whitespace IFS char */ + /* Skip whitespace IFS char before it */ + while (--bufpos >= 0 + && isspace(buffer[bufpos]) + && strchr(ifs, buffer[bufpos]) != NULL + ) { + continue; + } + /* Are there $IFS chars? */ + if (strcspn(buffer, ifs) >= ++bufpos) { + /* No: last var takes one field, not more */ + /* So, drop trailing IFS delims */ + buffer[bufpos] = '\0'; + } + } + /* Use the remainder as a value for the next variable */ setvar(*argv, buffer); /* Set the rest to "" */ -- cgit v1.2.3-55-g6feb From f693b606b732437bb1265c2ec883d93127f3f38e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 20:00:43 +0200 Subject: hush: fix recent breakage from parse_stream() changes function old new delta parse_stream 3808 3821 +13 Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-parsing/bkslash_newline3.right | 1 + shell/ash_test/ash-parsing/bkslash_newline3.tests | 4 ++ shell/hush.c | 44 +++++++++++----------- .../hush_test/hush-parsing/bkslash_newline3.right | 1 + .../hush_test/hush-parsing/bkslash_newline3.tests | 4 ++ 5 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 shell/ash_test/ash-parsing/bkslash_newline3.right create mode 100755 shell/ash_test/ash-parsing/bkslash_newline3.tests create mode 100644 shell/hush_test/hush-parsing/bkslash_newline3.right create mode 100755 shell/hush_test/hush-parsing/bkslash_newline3.tests diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.right b/shell/ash_test/ash-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.right @@ -0,0 +1 @@ +a:[a] diff --git a/shell/ash_test/ash-parsing/bkslash_newline3.tests b/shell/ash_test/ash-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/ash_test/ash-parsing/bkslash_newline3.tests @@ -0,0 +1,4 @@ +for s in \ +a; do + echo "a:[$s]" +done diff --git a/shell/hush.c b/shell/hush.c index 8e95a26a6..c77700175 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -5075,7 +5075,6 @@ static struct pipe *parse_stream(char **pstring, debug_printf_parse("parse_stream return %p\n", pi); return pi; } - nommu_addchr(&ctx.as_string, ch); /* Handle "'" and "\" first, as they won't play nice with * i_peek_and_eat_bkslash_nl() anyway: @@ -5085,6 +5084,28 @@ static struct pipe *parse_stream(char **pstring, * ' * would break. */ + if (ch == '\\') { + ch = i_getch(input); + if (ch == '\n') + continue; /* drop \, get next char */ + nommu_addchr(&ctx.as_string, '\\'); + o_addchr(&ctx.word, '\\'); + if (ch == EOF) { + /* Testcase: eval 'echo Ok\' */ + /* bash-4.3.43 was removing backslash, + * but 4.4.19 retains it, most other shells too + */ + continue; /* get next char */ + } + /* Example: echo Hello \2>file + * we need to know that word 2 is quoted + */ + ctx.word.has_quoted_part = 1; + nommu_addchr(&ctx.as_string, ch); + o_addchr(&ctx.word, ch); + continue; /* get next char */ + } + nommu_addchr(&ctx.as_string, ch); if (ch == '\'') { ctx.word.has_quoted_part = 1; next = i_getch(input); @@ -5110,27 +5131,6 @@ static struct pipe *parse_stream(char **pstring, } continue; /* get next char */ } - if (ch == '\\') { - /*nommu_addchr(&ctx.as_string, '\\'); - already done */ - o_addchr(&ctx.word, '\\'); - ch = i_getch(input); - if (ch == EOF) { - /* Testcase: eval 'echo Ok\' */ - -#if 0 /* bash-4.3.43 was removing backslash, but 4.4.19 retains it, most other shells too */ - /* Remove trailing '\' from ctx.as_string */ - ctx.as_string.data[--ctx.as_string.length] = '\0'; -#endif - continue; /* get next char */ - } - /* Example: echo Hello \2>file - * we need to know that word 2 is quoted - */ - ctx.word.has_quoted_part = 1; - nommu_addchr(&ctx.as_string, ch); - o_addchr(&ctx.word, ch); - continue; /* get next char */ - } next = '\0'; if (ch != '\n') diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.right b/shell/hush_test/hush-parsing/bkslash_newline3.right new file mode 100644 index 000000000..e635074e5 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.right @@ -0,0 +1 @@ +a:[a] diff --git a/shell/hush_test/hush-parsing/bkslash_newline3.tests b/shell/hush_test/hush-parsing/bkslash_newline3.tests new file mode 100755 index 000000000..2accd4395 --- /dev/null +++ b/shell/hush_test/hush-parsing/bkslash_newline3.tests @@ -0,0 +1,4 @@ +for s in \ +a; do + echo "a:[$s]" +done -- cgit v1.2.3-55-g6feb From 46158dc833ca48a63a2547bb26eee133aa19dccf Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Wed, 11 Apr 2018 20:24:58 +0200 Subject: shell: add 6856 $IFS tests to testsuites Signed-off-by: Denys Vlasenko --- shell/ash_test/ash-z_slow/many_ifs.right | 1 + shell/ash_test/ash-z_slow/many_ifs.tests | 257 +++++++++++++++++++++++++++++ shell/hush_test/hush-z_slow/many_ifs.right | 1 + shell/hush_test/hush-z_slow/many_ifs.tests | 257 +++++++++++++++++++++++++++++ 4 files changed, 516 insertions(+) create mode 100644 shell/ash_test/ash-z_slow/many_ifs.right create mode 100755 shell/ash_test/ash-z_slow/many_ifs.tests create mode 100644 shell/hush_test/hush-z_slow/many_ifs.right create mode 100755 shell/hush_test/hush-z_slow/many_ifs.tests diff --git a/shell/ash_test/ash-z_slow/many_ifs.right b/shell/ash_test/ash-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.right @@ -0,0 +1 @@ +# tests 6856 passed 6856 failed 0 diff --git a/shell/ash_test/ash-z_slow/many_ifs.tests b/shell/ash_test/ash-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/ash_test/ash-z_slow/many_ifs.tests @@ -0,0 +1,257 @@ +# Usage: $SHELL ifs.sh +# +# This script generates 6856 tests for the set(1) and read(1) +# builtins w.r.t. IFS whitespace and non-whitespace characters. +# Each failed test produces one line on the standard output that +# contains the test along with the expected and actual results. +# The last output line contains the test result counts. ordered>0 +# are the number of tests where IFS=": " produced different results +# than IFS=" :". If a test fails the same way for IFS=": " and +# IFS=" :" then the second output line is suppressed. + +TESTS=6856 + +ksh_read=0 +echo 1 | read ksh_read +ksh_arith=0 +eval '((ksh_arith+=1))' 2>/dev/null + +failed=0 +ordered=0 +passed=0 + +split() +{ + i=$1 s=$2 r=$3 S='' R='' + for ifs in ': ' ' :' + do IFS=$ifs + set x $i + shift + IFS=' ' + g="[$#]" + while : + do case $# in + 0) break ;; + esac + g="$g($1)" + shift + done + case $g in + "$s") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$S") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $s in + "$S") ;; + ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; + ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; + ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; + ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; + *) echo TEST ERROR i="'$i'" s="'$s'" ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + esac + case $ksh_read in + 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; + *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; + esac + case $g in + "$r") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$R") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $r in + "$R") ;; + *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + esac + done +} + +for str in \ + '-' \ + 'a' \ + '- -' \ + '- a' \ + 'a -' \ + 'a b' \ + '- - -' \ + '- - a' \ + '- a -' \ + '- a b' \ + 'a - -' \ + 'a - b' \ + 'a b -' \ + 'a b c' \ + +do + IFS=' ' + set x $str + + shift + case $# in + 0) continue ;; + esac + + f1=$1 + case $f1 in + '-') f1='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f1$d1 in + '') split "$d0$f1$d1" "[0]" "()()" ;; + ' ') ;; + *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; + esac + done + done + continue + ;; + esac + f2=$1 + case $f2 in + '-') f2='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ' ' ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; + ' ') ;; + *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; + esac + done + done + done + continue + ;; + esac + f3=$1 + case $f3 in + '-') f3='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + ' ') continue ;; + esac + case ' ' in + $f2$d2|$d2$f3) continue ;; + esac + for d3 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f3$d3 in + '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; + ' ') ;; + *) x=$f2$d2$f3$d3 + x=${x# } #was x=${x#' '} hush needs fixing for this to work + x=${x% } #was x=${x%' '} + split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" + ;; + esac + done + done + done + done + continue + ;; + esac +done +case $ksh_arith in +1) ((tests=passed+failed)) ;; +*) tests=`expr $passed + $failed` ;; +esac +case $ordered in +0) ordered="" ;; +*) ordered=" ordered $ordered" ;; +esac +case $tests in +$TESTS) fatal="" ;; +*) fatal=" -- fundamental IFS error -- $TESTS tests expected" +esac +echo "# tests $tests passed $passed failed $failed$ordered$fatal" diff --git a/shell/hush_test/hush-z_slow/many_ifs.right b/shell/hush_test/hush-z_slow/many_ifs.right new file mode 100644 index 000000000..f3bdccc6c --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.right @@ -0,0 +1 @@ +# tests 6856 passed 6856 failed 0 diff --git a/shell/hush_test/hush-z_slow/many_ifs.tests b/shell/hush_test/hush-z_slow/many_ifs.tests new file mode 100755 index 000000000..1f5b1b3a6 --- /dev/null +++ b/shell/hush_test/hush-z_slow/many_ifs.tests @@ -0,0 +1,257 @@ +# Usage: $SHELL ifs.sh +# +# This script generates 6856 tests for the set(1) and read(1) +# builtins w.r.t. IFS whitespace and non-whitespace characters. +# Each failed test produces one line on the standard output that +# contains the test along with the expected and actual results. +# The last output line contains the test result counts. ordered>0 +# are the number of tests where IFS=": " produced different results +# than IFS=" :". If a test fails the same way for IFS=": " and +# IFS=" :" then the second output line is suppressed. + +TESTS=6856 + +ksh_read=0 +echo 1 | read ksh_read +ksh_arith=0 +eval '((ksh_arith+=1))' 2>/dev/null + +failed=0 +ordered=0 +passed=0 + +split() +{ + i=$1 s=$2 r=$3 S='' R='' + for ifs in ': ' ' :' + do IFS=$ifs + set x $i + shift + IFS=' ' + g="[$#]" + while : + do case $# in + 0) break ;; + esac + g="$g($1)" + shift + done + case $g in + "$s") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$S") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $s in + "$S") ;; + ?0*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#]\" # expected \"$s\" got \"$g\"" ;; + ?1*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)\" # expected \"$s\" got \"$g\"" ;; + ?2*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)\" # expected \"$s\" got \"$g\"" ;; + ?3*) echo "IFS=\"$ifs\"; x=\"$i\"; set x \$x; shift; echo \"[\$#](\$1)(\$2)(\$3)\" # expected \"$s\" got \"$g\"" ;; + *) echo TEST ERROR i="'$i'" s="'$s'" ;; + esac + case $S in + '') S=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + esac + case $ksh_read in + 1) echo "$i" | IFS=$ifs read x y; g="($x)($y)" ;; + *) g=`export ifs; echo "$i" | ( IFS=$ifs; read x y; echo "($x)($y)" )` ;; + esac + case $g in + "$r") case $ksh_arith in + 1) ((passed+=1)) ;; + *) passed=`expr $passed + 1` ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + "$R") case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + ;; + *) case $ksh_arith in + 1) ((failed+=1)) ;; + *) failed=`expr $failed + 1` ;; + esac + case $r in + "$R") ;; + *) echo "echo \"$i\" | ( IFS=\"$ifs\" read x y; echo \"(\$x)(\$y)\" ) # expected \"$r\" got \"$g\"" ;; + esac + case $R in + '') R=$g + ;; + "$g") ;; + *) case $ksh_arith in + 1) ((ordered+=1)) ;; + *) ordered=`expr $ordered + 1` ;; + esac + ;; + esac + ;; + esac + done +} + +for str in \ + '-' \ + 'a' \ + '- -' \ + '- a' \ + 'a -' \ + 'a b' \ + '- - -' \ + '- - a' \ + '- a -' \ + '- a b' \ + 'a - -' \ + 'a - b' \ + 'a b -' \ + 'a b c' \ + +do + IFS=' ' + set x $str + + shift + case $# in + 0) continue ;; + esac + + f1=$1 + case $f1 in + '-') f1='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f1$d1 in + '') split "$d0$f1$d1" "[0]" "()()" ;; + ' ') ;; + *) split "$d0$f1$d1" "[1]($f1)" "($f1)()" ;; + esac + done + done + continue + ;; + esac + f2=$1 + case $f2 in + '-') f2='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ' ' ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + '') split "$d0$f1$d1$f2$d2" "[1]($f1)" "($f1)()" ;; + ' ') ;; + *) split "$d0$f1$d1$f2$d2" "[2]($f1)($f2)" "($f1)($f2)" ;; + esac + done + done + done + continue + ;; + esac + f3=$1 + case $f3 in + '-') f3='' ;; + esac + + shift + case $# in + 0) for d0 in '' ' ' + do + for d1 in ':' ' :' ': ' ' : ' + do + case ' ' in + $f1$d1|$d1$f2) continue ;; + esac + for d2 in ' ' ':' ' :' ': ' ' : ' + do + case $f2$d2 in + ' ') continue ;; + esac + case ' ' in + $f2$d2|$d2$f3) continue ;; + esac + for d3 in '' ' ' ':' ' :' ': ' ' : ' + do + case $f3$d3 in + '') split "$d0$f1$d1$f2$d2$f3$d3" "[2]($f1)($f2)" "($f1)($f2)" ;; + ' ') ;; + *) x=$f2$d2$f3$d3 + x=${x# } #was x=${x#' '} hush needs fixing for this to work + x=${x% } #was x=${x%' '} + split "$d0$f1$d1$f2$d2$f3$d3" "[3]($f1)($f2)($f3)" "($f1)($x)" + ;; + esac + done + done + done + done + continue + ;; + esac +done +case $ksh_arith in +1) ((tests=passed+failed)) ;; +*) tests=`expr $passed + $failed` ;; +esac +case $ordered in +0) ordered="" ;; +*) ordered=" ordered $ordered" ;; +esac +case $tests in +$TESTS) fatal="" ;; +*) fatal=" -- fundamental IFS error -- $TESTS tests expected" +esac +echo "# tests $tests passed $passed failed $failed$ordered$fatal" -- cgit v1.2.3-55-g6feb From 266f6f19732f9b35487a87a346e58c1c3a0af43d Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 13 Apr 2018 13:18:34 +0200 Subject: udhcp: support string user options, closes 10946 function old new delta udhcp_str2optset 536 628 +92 packed_usage 32757 32760 +3 udhcpc_main 2708 2692 -16 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 2/1 up/down: 95/-16) Total: 79 bytes Signed-off-by: Denys Vlasenko --- examples/udhcp/udhcpd.conf | 3 +- networking/udhcp/common.c | 71 ++++++++++++++++++++++++++++----------------- networking/udhcp/d6_dhcpc.c | 11 ++----- networking/udhcp/dhcpc.c | 11 ++----- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/examples/udhcp/udhcpd.conf b/examples/udhcp/udhcpd.conf index eca44c0ab..90714bcdf 100644 --- a/examples/udhcp/udhcpd.conf +++ b/examples/udhcp/udhcpd.conf @@ -70,8 +70,9 @@ option domain local option lease 864000 # default: 10 days option msstaticroutes 10.0.0.0/8 10.127.0.1 # single static route option staticroutes 10.0.0.0/8 10.127.0.1, 10.11.12.0/24 10.11.12.1 -# Arbitrary option in hex form: +# Arbitrary option in hex or string form: option 0x08 01020304 # option 8: "cookie server IP addr: 1.2.3.4" +option 14 "dumpfile" # Currently supported options (for more info, see options.c): #opt lease NUM diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c index d3eea5def..fbf9c6878 100644 --- a/networking/udhcp/common.c +++ b/networking/udhcp/common.c @@ -378,34 +378,24 @@ int FAST_FUNC udhcp_str2nip(const char *str, void *arg) * Called to parse "udhcpc -x OPTNAME:OPTVAL" * and to parse udhcpd.conf's "opt OPTNAME OPTVAL" directives. */ -/* helper for the helper */ -static char *allocate_tempopt_if_needed( +/* helper: add an option to the opt_list */ +static NOINLINE void attach_option( + struct option_set **opt_list, const struct dhcp_optflag *optflag, char *buffer, - int *length_p) + int length) { + struct option_set *existing; char *allocated = NULL; + if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_BIN) { const char *end; allocated = xstrdup(buffer); /* more than enough */ end = hex2bin(allocated, buffer, 255); if (errno) bb_error_msg_and_die("malformed hex string '%s'", buffer); - *length_p = end - allocated; + length = end - allocated; } - return allocated; -} -/* helper: add an option to the opt_list */ -static NOINLINE void attach_option( - struct option_set **opt_list, - const struct dhcp_optflag *optflag, - char *buffer, - int length) -{ - struct option_set *existing; - char *allocated; - - allocated = allocate_tempopt_if_needed(optflag, buffer, &length); #if ENABLE_FEATURE_UDHCP_RFC3397 if ((optflag->flags & OPTION_TYPE_MASK) == OPTION_DNS_STRING) { /* reuse buffer and length for RFC1035-formatted string */ @@ -463,12 +453,12 @@ static NOINLINE void attach_option( int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dhcp_optflag *optflags, const char *option_strings) { struct option_set **opt_list = arg; - char *opt, *val; + char *opt; char *str; const struct dhcp_optflag *optflag; - struct dhcp_optflag bin_optflag; + struct dhcp_optflag userdef_optflag; unsigned optcode; - int retval, length; + int retval; /* IP_PAIR needs 8 bytes, STATIC_ROUTES needs 9 max */ char buffer[9] ALIGNED(4); uint16_t *result_u16 = (uint16_t *) buffer; @@ -476,28 +466,40 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh /* Cheat, the only *const* str possible is "" */ str = (char *) const_str; - opt = strtok(str, " \t="); + opt = strtok(str, " \t=:"); if (!opt) return 0; optcode = bb_strtou(opt, NULL, 0); if (!errno && optcode < 255) { - /* Raw (numeric) option code */ - bin_optflag.flags = OPTION_BIN; - bin_optflag.code = optcode; - optflag = &bin_optflag; + /* Raw (numeric) option code. + * Initially assume binary (hex-str), but if "str" or 'str' + * is seen later, switch to STRING. + */ + userdef_optflag.flags = OPTION_BIN; + userdef_optflag.code = optcode; + optflag = &userdef_optflag; } else { optflag = &optflags[udhcp_option_idx(opt, option_strings)]; } + /* Loop to handle OPTION_LIST case, else execute just once */ retval = 0; do { - val = strtok(NULL, ", \t"); + int length; + char *val; + + if (optflag->flags == OPTION_BIN) + val = trim(strtok(NULL, "")); /* do not split "'q w e'" */ + else + val = strtok(NULL, ", \t"); if (!val) break; + length = dhcp_option_lengths[optflag->flags & OPTION_TYPE_MASK]; retval = 0; opt = buffer; /* new meaning for variable opt */ + switch (optflag->flags & OPTION_TYPE_MASK) { case OPTION_IP: retval = udhcp_str2nip(val, buffer); @@ -510,6 +512,7 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh if (retval) retval = udhcp_str2nip(val, buffer + 4); break; +case_OPTION_STRING: case OPTION_STRING: case OPTION_STRING_HOST: #if ENABLE_FEATURE_UDHCP_RFC3397 @@ -577,12 +580,26 @@ int FAST_FUNC udhcp_str2optset(const char *const_str, void *arg, const struct dh } break; } - case OPTION_BIN: /* handled in attach_option() */ + case OPTION_BIN: + /* Raw (numeric) option code. Is it a string? */ + if (val[0] == '"' || val[0] == '\'') { + char delim = val[0]; + char *end = last_char_is(val + 1, delim); + if (end) { + *end = '\0'; + val++; + userdef_optflag.flags = OPTION_STRING; + goto case_OPTION_STRING; + } + } + /* No: hex-str option, handled in attach_option() */ opt = val; retval = 1; + break; default: break; } + if (retval) attach_option(opt_list, optflag, opt, length); } while (retval && (optflag->flags & OPTION_LIST)); diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c index 289df66ee..85d9da724 100644 --- a/networking/udhcp/d6_dhcpc.c +++ b/networking/udhcp/d6_dhcpc.c @@ -1063,6 +1063,7 @@ static void client_background(void) //usage: "\n -x hostname:bbox - option 12" //usage: "\n -x lease:3600 - option 51 (lease time)" //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" +//usage: "\n -x 14:'\"dumpfile\"' - option 14 (shell-quoted)" //usage: IF_UDHCP_VERBOSE( //usage: "\n -v Verbose" //usage: ) @@ -1155,15 +1156,9 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv) } } while (list_x) { - char *optstr = llist_pop(&list_x); - char *colon = strchr(optstr, ':'); - if (colon) - *colon = ' '; - /* now it looks similar to udhcpd's config file line: - * "optname optval", using the common routine: */ + char *optstr = xstrdup(llist_pop(&list_x)); udhcp_str2optset(optstr, &client_config.options, d6_optflags, d6_option_strings); - if (colon) - *colon = ':'; /* restore it for NOMMU reexec */ + free(optstr); } if (d6_read_interface(client_config.interface, diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c index 90b07bf4b..bd9e8fdc2 100644 --- a/networking/udhcp/dhcpc.c +++ b/networking/udhcp/dhcpc.c @@ -1224,6 +1224,7 @@ static void client_background(void) //usage: "\n -x hostname:bbox - option 12" //usage: "\n -x lease:3600 - option 51 (lease time)" //usage: "\n -x 0x3d:0100BEEFC0FFEE - option 61 (client id)" +//usage: "\n -x 14:'\"dumpfile\"' - option 14 (shell-quoted)" //usage: "\n -F NAME Ask server to update DNS mapping for NAME" //usage: "\n -V VENDOR Vendor identifier (default 'udhcp VERSION')" //usage: "\n -C Don't send MAC as client identifier" @@ -1335,15 +1336,9 @@ int udhcpc_main(int argc UNUSED_PARAM, char **argv) } } while (list_x) { - char *optstr = llist_pop(&list_x); - char *colon = strchr(optstr, ':'); - if (colon) - *colon = ' '; - /* now it looks similar to udhcpd's config file line: - * "optname optval", using the common routine: */ + char *optstr = xstrdup(llist_pop(&list_x)); udhcp_str2optset(optstr, &client_config.options, dhcp_optflags, dhcp_option_strings); - if (colon) - *colon = ':'; /* restore it for NOMMU reexec */ + free(optstr); } if (udhcp_read_interface(client_config.interface, -- cgit v1.2.3-55-g6feb From dd56921e2d404c8fc9484290a36411a13d14df1a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 13 Apr 2018 13:26:33 +0200 Subject: dpkg: fix symlink creation, closes 10941 function old new delta get_header_ar 434 442 +8 Signed-off-by: Denys Vlasenko --- archival/libarchive/get_header_ar.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/archival/libarchive/get_header_ar.c b/archival/libarchive/get_header_ar.c index 93e071c9f..a97970630 100644 --- a/archival/libarchive/get_header_ar.c +++ b/archival/libarchive/get_header_ar.c @@ -127,8 +127,10 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) archive_handle->action_header(typed); #if ENABLE_DPKG || ENABLE_DPKG_DEB if (archive_handle->dpkg__sub_archive) { - while (archive_handle->dpkg__action_data_subarchive(archive_handle->dpkg__sub_archive) == EXIT_SUCCESS) + struct archive_handle_t *sa = archive_handle->dpkg__sub_archive; + while (archive_handle->dpkg__action_data_subarchive(sa) == EXIT_SUCCESS) continue; + create_symlinks_from_list(sa->symlink_placeholders); } else #endif archive_handle->action_data(archive_handle); -- cgit v1.2.3-55-g6feb From 837913fc548fe752e3a1bc13af9b43f50d8f278a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 01:23:40 +0200 Subject: start_stop_daemon: fix normally disabled OLDER_VERSION_OF_X code Signed-off-by: Denys Vlasenko --- debianutils/start_stop_daemon.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c index fa77a7e00..9fa092d66 100644 --- a/debianutils/start_stop_daemon.c +++ b/debianutils/start_stop_daemon.c @@ -157,6 +157,9 @@ struct globals { unsigned execname_sizeof; int user_id; smallint signal_nr; +#ifdef OLDER_VERSION_OF_X + struct stat execstat; +#endif } FIX_ALIASING; #define G (*(struct globals*)bb_common_bufsiz1) #define userspec (G.userspec ) @@ -184,8 +187,8 @@ static int pid_is_exec(pid_t pid) sprintf(buf, "/proc/%u/exe", (unsigned)pid); if (stat(buf, &st) < 0) return 0; - if (st.st_dev == execstat.st_dev - && st.st_ino == execstat.st_ino) + if (st.st_dev == G.execstat.st_dev + && st.st_ino == G.execstat.st_ino) return 1; return 0; } @@ -408,9 +411,6 @@ int start_stop_daemon_main(int argc UNUSED_PARAM, char **argv) char *signame; char *startas; char *chuid; -#ifdef OLDER_VERSION_OF_X - struct stat execstat; -#endif #if ENABLE_FEATURE_START_STOP_DAEMON_FANCY // char *retry_arg = NULL; // int retries = -1; @@ -479,7 +479,7 @@ int start_stop_daemon_main(int argc UNUSED_PARAM, char **argv) #ifdef OLDER_VERSION_OF_X if (execname) - xstat(execname, &execstat); + xstat(execname, &G.execstat); #endif *--argv = startas; -- cgit v1.2.3-55-g6feb From c783cf78af04e898a20f69f28b885f7c0285020c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 01:29:01 +0200 Subject: tart_stop_daemon: another fix to disabled OLDER_VERSION_OF_X code Signed-off-by: Denys Vlasenko --- debianutils/start_stop_daemon.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/debianutils/start_stop_daemon.c b/debianutils/start_stop_daemon.c index 9fa092d66..43b6fca26 100644 --- a/debianutils/start_stop_daemon.c +++ b/debianutils/start_stop_daemon.c @@ -192,8 +192,7 @@ static int pid_is_exec(pid_t pid) return 1; return 0; } -#endif - +#else static int pid_is_exec(pid_t pid) { ssize_t bytes; @@ -217,6 +216,7 @@ static int pid_is_exec(pid_t pid) } return 0; } +#endif static int pid_is_name(pid_t pid) { -- cgit v1.2.3-55-g6feb From 43dd0062229170747dcbee0a2a87b8e5ee2f09d6 Mon Sep 17 00:00:00 2001 From: Michael Olbrich Date: Thu, 12 Apr 2018 10:36:54 +0200 Subject: build system: fix parallel building issue The files generated by the include/config/MARKER target are in the dependency list for applets/applet_tables. If applets/applet_tables is created first during applets_dir then it will be created again later as part of $(busybox-dirs). As a result include/applet_tables.h is created again. This time while other build commands may need it. Let applets_dir depend on include/config/MARKER to avoid this particular race condition and create the header files atomically to ensure that the compiler never sees incomplete files. Signed-off-by: Michael Olbrich Signed-off-by: Denys Vlasenko --- Makefile | 2 +- applets/applet_tables.c | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 03a57adf3..9d7a0a36c 100644 --- a/Makefile +++ b/Makefile @@ -368,7 +368,7 @@ gen_build_files: $(wildcard $(srctree)/*/*.c) $(wildcard $(srctree)/*/*/*.c) # we depend on scripts_basic, since scripts/basic/fixdep # must be built before any other host prog PHONY += applets_dir -applets_dir: scripts_basic gen_build_files +applets_dir: scripts_basic gen_build_files include/config/MARKER $(Q)$(MAKE) $(build)=applets applets/%: applets_dir ; diff --git a/applets/applet_tables.c b/applets/applet_tables.c index ef911a43b..e3d10c83f 100644 --- a/applets/applet_tables.c +++ b/applets/applet_tables.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +62,7 @@ static int str_isalnum_(const char *s) int main(int argc, char **argv) { int i, j; + char tmp1[PATH_MAX], tmp2[PATH_MAX]; // In find_applet_by_name(), before linear search, narrow it down // by looking at N "equidistant" names. With ~350 applets: @@ -84,7 +86,8 @@ int main(int argc, char **argv) if (!argv[1]) return 1; - i = open(argv[1], O_WRONLY | O_TRUNC | O_CREAT, 0666); + snprintf(tmp1, PATH_MAX, "%s.%u.new", argv[1], (int) getpid()); + i = open(tmp1, O_WRONLY | O_TRUNC | O_CREAT, 0666); if (i < 0) return 1; dup2(i, 1); @@ -209,12 +212,21 @@ int main(int argc, char **argv) // fclose(fp); // } // if (strcmp(line_old, line_new) != 0) { - fp = fopen(argv[2], "w"); + snprintf(tmp2, PATH_MAX, "%s.%u.new", argv[2], (int) getpid()); + fp = fopen(tmp2, "w"); if (!fp) return 1; fputs(line_new, fp); + if (fclose(fp)) + return 1; // } } + if (fclose(stdout)) + return 1; + if (rename(tmp1, argv[1])) + return 1; + if (rename(tmp2, argv[2])) + return 1; return 0; } -- cgit v1.2.3-55-g6feb From 0dd3be8c0933d5649152b6019b975876e804e22a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 14:05:45 +0200 Subject: nslookup: add openwrt / lede version Needs work on size reduction function old new delta nslookup_main 114 2363 +2249 parse_reply - 1022 +1022 add_ns - 663 +663 ns_parserr - 486 +486 ns_initparse - 184 +184 ns_skiprr - 117 +117 add_query - 95 +95 qtypes - 80 +80 rcodes - 68 +68 dn_skipname - 58 +58 ns_name_uncompress - 56 +56 ns_get16 - 13 +13 v4_mapped - 12 +12 ns_get32 - 9 +9 res_init 3 - -3 __res_state 6 - -6 xmalloc_sockaddr2hostonly_noport 10 - -10 gai_strerror 47 - -47 set_default_dns 95 - -95 print_host 199 - -199 static.res 512 - -512 ------------------------------------------------------------------------------ (add/remove: 15/10 grow/shrink: 1/0 up/down: 5112/-872) Total: 4240 bytes text data bss dec hex filename 921944 555 6252 928751 e2bef busybox_old 927375 555 5740 933670 e3f26 busybox_unstripped Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 919 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 908 insertions(+), 11 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 45c218e6c..6f8f02fc1 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -1,20 +1,20 @@ /* vi: set sw=4 ts=4: */ -/* - * Mini nslookup implementation for busybox - * - * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu - * Copyright (C) 1999,2000,2001 by John Beppu - * - * Correct default name server display and explicit name server option - * added by Ben Zeckel June 2001 - * - * Licensed under GPLv2 or later, see file LICENSE in this source tree. - */ + //config:config NSLOOKUP //config: bool "nslookup (4.5 kb)" //config: default y //config: help //config: nslookup is a tool to query Internet name servers. +//config: +//config:config NSLOOKUP_BIG +//config: bool "Use internal resolver code instead of libc" +//config: depends on NSLOOKUP +//config: default y +//config: +//config:config FEATURE_NSLOOKUP_LONG_OPTIONS +//config: bool "Enable long options" +//config: default y +//config: depends on NSLOOKUP_BIG && LONG_OPTS //applet:IF_NSLOOKUP(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP)) @@ -35,7 +35,26 @@ //usage: "Address: 127.0.0.1\n" #include +#include /* for IFNAMSIZ */ +//#include +//#include #include "libbb.h" +#include "common_bufsiz.h" + + +#if !ENABLE_NSLOOKUP_BIG + +/* + * Mini nslookup implementation for busybox + * + * Copyright (C) 1999,2000 by Lineo, inc. and John Beppu + * Copyright (C) 1999,2000,2001 by John Beppu + * + * Correct default name server display and explicit name server option + * added by Ben Zeckel June 2001 + * + * Licensed under GPLv2 or later, see file LICENSE in this source tree. + */ /* * I'm only implementing non-interactive mode; @@ -207,3 +226,881 @@ int nslookup_main(int argc, char **argv) return print_host(argv[1], "Name:"); } + + +#else /****** A version from LEDE / OpenWRT ******/ + +/* + * musl compatible nslookup + * + * Copyright (C) 2017 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if 0 +# define dbg(...) fprintf(stderr, __VA_ARGS__) +#else +# define dbg(...) ((void)0) +#endif + +struct ns { + const char *name; + len_and_sockaddr addr; + int failures; + int replies; +}; + +struct query { + const char *name; + size_t qlen, rlen; + unsigned char query[512], reply[512]; + unsigned long latency; + int rcode, n_ns; +}; + +static const struct { + int type; + const char *name; +} qtypes[] = { + { ns_t_soa, "SOA" }, + { ns_t_ns, "NS" }, + { ns_t_a, "A" }, +#if ENABLE_FEATURE_IPV6 + { ns_t_aaaa, "AAAA" }, +#endif + { ns_t_cname, "CNAME" }, + { ns_t_mx, "MX" }, + { ns_t_txt, "TXT" }, + { ns_t_ptr, "PTR" }, + { ns_t_any, "ANY" }, + { } +}; + +static const char *const rcodes[] = { + "NOERROR", + "FORMERR", + "SERVFAIL", + "NXDOMAIN", + "NOTIMP", + "REFUSED", + "YXDOMAIN", + "YXRRSET", + "NXRRSET", + "NOTAUTH", + "NOTZONE", + "RESERVED11", + "RESERVED12", + "RESERVED13", + "RESERVED14", + "RESERVED15", + "BADVERS" +}; + +#if ENABLE_FEATURE_IPV6 +static const char v4_mapped[] = { 0,0,0,0,0,0,0,0,0,0,0xff,0xff }; +#endif + +struct globals { + unsigned default_port; + unsigned default_retry; + unsigned default_timeout; +} FIX_ALIASING; +#define G (*(struct globals*)bb_common_bufsiz1) +#define INIT_G() do { \ + setup_common_bufsiz(); \ + G.default_port = 53; \ + G.default_retry = 2; \ + G.default_timeout = 5; \ +} while (0) + +static int +parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) +{ + ns_msg handle; + ns_rr rr; + int i, n, rdlen; + const char *format = NULL; + char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; + const unsigned char *cp; + + if (ns_initparse(msg, len, &handle) != 0) { + //fprintf(stderr, "Unable to parse reply: %s\n", strerror(errno)); + return -1; + } + + for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { + if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { + //fprintf(stderr, "Unable to parse resource record: %s\n", strerror(errno)); + return -1; + } + + if (bb_style_counter && *bb_style_counter == 1) + printf("Name: %s\n", ns_rr_name(rr)); + + rdlen = ns_rr_rdlen(rr); + + switch (ns_rr_type(rr)) + { + case ns_t_a: + if (rdlen != 4) { + //fprintf(stderr, "Unexpected A record length\n"); + return -1; + } + inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); + if (bb_style_counter) + printf("Address %d: %s\n", (*bb_style_counter)++, astr); + else + printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); + break; + +#if ENABLE_FEATURE_IPV6 + case ns_t_aaaa: + if (rdlen != 16) { + //fprintf(stderr, "Unexpected AAAA record length\n"); + return -1; + } + inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); + if (bb_style_counter) + printf("Address %d: %s\n", (*bb_style_counter)++, astr); + else + printf("%s\thas AAAA address %s\n", ns_rr_name(rr), astr); + break; +#endif + + case ns_t_ns: + if (!format) + format = "%s\tnameserver = %s\n"; + /* fall through */ + + case ns_t_cname: + if (!format) + format = "%s\tcanonical name = %s\n"; + /* fall through */ + + case ns_t_ptr: + if (!format) + format = "%s\tname = %s\n"; + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), + ns_rr_rdata(rr), dname, sizeof(dname)) < 0) { + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + return -1; + } + printf(format, ns_rr_name(rr), dname); + break; + + case ns_t_mx: + if (rdlen < 2) { + fprintf(stderr, "MX record too short\n"); + return -1; + } + n = ns_get16(ns_rr_rdata(rr)); + if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), + ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) { + //fprintf(stderr, "Cannot uncompress MX domain: %s\n", strerror(errno)); + return -1; + } + printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname); + break; + + case ns_t_txt: + if (rdlen < 1) { + //fprintf(stderr, "TXT record too short\n"); + return -1; + } + n = *(unsigned char *)ns_rr_rdata(rr); + if (n > 0) { + memset(dname, 0, sizeof(dname)); + memcpy(dname, ns_rr_rdata(rr) + 1, n); + printf("%s\ttext = \"%s\"\n", ns_rr_name(rr), dname); + } + break; + + case ns_t_soa: + if (rdlen < 20) { + //fprintf(stderr, "SOA record too short\n"); + return -1; + } + + printf("%s\n", ns_rr_name(rr)); + + cp = ns_rr_rdata(rr); + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), + cp, dname, sizeof(dname)); + + if (n < 0) { + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + return -1; + } + + printf("\torigin = %s\n", dname); + cp += n; + + n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), + cp, dname, sizeof(dname)); + + if (n < 0) { + //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + return -1; + } + + printf("\tmail addr = %s\n", dname); + cp += n; + + printf("\tserial = %lu\n", ns_get32(cp)); + cp += 4; + + printf("\trefresh = %lu\n", ns_get32(cp)); + cp += 4; + + printf("\tretry = %lu\n", ns_get32(cp)); + cp += 4; + + printf("\texpire = %lu\n", ns_get32(cp)); + cp += 4; + + printf("\tminimum = %lu\n", ns_get32(cp)); + break; + + default: + break; + } + } + + return i; +} + +static int parse_nsaddr(const char *addrstr, len_and_sockaddr *lsa) +{ + char *hash; + unsigned port = G.default_port; + IF_FEATURE_IPV6(unsigned scope;) + + dbg("%s: addrstr:'%s'\n", __func__, addrstr); + + hash = strchr(addrstr, '#'); + + if (hash) { + *hash++ = '\0'; + port = bb_strtou(hash, NULL, 10); + if (errno || port > 65535) { + errno = EINVAL; + return -1; + } + } + +#if ENABLE_FEATURE_IPV6 + scope = 0; + hash = strchr(addrstr, '%'); + if (hash) { + char ifname[IFNAMSIZ]; + int i; + + hash++; + for (i = 0; hash[i] != '\0' && hash[i] != '#'; i++) { + if (i >= IFNAMSIZ) { + errno = ENODEV; + return -1; + } + ifname[i] = hash[i]; + } + ifname[i] = '\0'; + + scope = if_nametoindex(ifname); + if (scope == 0) { + errno = ENODEV; + return -1; + } + } + + if (inet_pton(AF_INET6, addrstr, &lsa->u.sin6.sin6_addr)) { + lsa->u.sin6.sin6_family = AF_INET6; + lsa->u.sin6.sin6_port = htons(port); + lsa->u.sin6.sin6_scope_id = scope; + lsa->len = sizeof(lsa->u.sin6); + return 0; + } +#endif + + if (inet_pton(AF_INET, addrstr, &lsa->u.sin.sin_addr)) { + lsa->u.sin.sin_family = AF_INET; + lsa->u.sin.sin_port = htons(port); + lsa->len = sizeof(lsa->u.sin); + return 0; + } + + errno = EINVAL; + return -1; +} + +static char *make_ptr(char resbuf[80], const char *addrstr) +{ + unsigned char addr[16]; + int i; + +#if ENABLE_FEATURE_IPV6 + if (inet_pton(AF_INET6, addrstr, addr)) { + if (memcmp(addr, v4_mapped, 12) != 0) { + char *ptr = resbuf; + for (i = 0; i < 16; i++) { + *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] & 0xf]; + *ptr++ = '.'; + *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] >> 4]; + *ptr++ = '.'; + } + strcpy(ptr, "ip6.arpa"); + } + else { + sprintf(resbuf, "%u.%u.%u.%u.in-addr.arpa", + addr[15], addr[14], addr[13], addr[12]); + } + return resbuf; + } +#endif + + if (inet_pton(AF_INET, addrstr, addr)) { + sprintf(resbuf, "%u.%u.%u.%u.in-addr.arpa", + addr[3], addr[2], addr[1], addr[0]); + return resbuf; + } + + return NULL; +} + +#if ENABLE_FEATURE_IPV6 +static void to_v4_mapped(len_and_sockaddr *a) +{ + if (a->u.sa.sa_family != AF_INET) + return; + + /* Order is important */ +//FIXME: port? + memcpy(a->u.sin6.sin6_addr.s6_addr + 12, &a->u.sin.sin_addr, 4); + memcpy(a->u.sin6.sin6_addr.s6_addr, v4_mapped, 12); + + a->u.sin6.sin6_family = AF_INET6; + a->u.sin6.sin6_flowinfo = 0; + a->u.sin6.sin6_scope_id = 0; + a->len = sizeof(a->u.sin6); +} +#endif + +/* + * Function logic borrowed & modified from musl libc, res_msend.c + */ +static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_queries) +{ + int fd; + int timeout = G.default_timeout * 1000, retry_interval, servfail_retry = 0; + len_and_sockaddr from = { }; + int recvlen = 0; + int n_replies = 0; + struct pollfd pfd; + unsigned long t0, t1, t2; + int nn, qn, next_query = 0; + + from.u.sa.sa_family = AF_INET; + from.len = sizeof(from.u.sin); +#if ENABLE_FEATURE_IPV6 + for (nn = 0; nn < n_ns; nn++) { + if (ns[nn].addr.u.sa.sa_family == AF_INET6) { + from.u.sa.sa_family = AF_INET6; + from.len = sizeof(from.u.sin6); + break; + } + } +#endif + + /* Get local address and open/bind a socket */ +#if ENABLE_FEATURE_IPV6 + fd = socket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + /* Handle case where system lacks IPv6 support */ + if (fd < 0) { + if (from.u.sa.sa_family != AF_INET6 || errno != EAFNOSUPPORT) + bb_perror_msg_and_die("socket"); + fd = xsocket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + from.u.sa.sa_family = AF_INET; + } +#else + fd = xsocket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); +#endif + xbind(fd, &from.u.sa, from.len); + +#if ENABLE_FEATURE_IPV6 + /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ + if (from.u.sa.sa_family == AF_INET6) { + setsockopt_1(fd, IPPROTO_IPV6, IPV6_V6ONLY); + for (nn = 0; nn < n_ns; nn++) + to_v4_mapped(&ns[nn].addr); + } +#endif + + pfd.fd = fd; + pfd.events = POLLIN; + retry_interval = timeout / G.default_retry; + t0 = t2 = monotonic_ms(); + t1 = t2 - retry_interval; + + for (; t2 - t0 < timeout; t2 = monotonic_ms()) { + if (t2 - t1 >= retry_interval) { + for (qn = 0; qn < n_queries; qn++) { + if (queries[qn].rlen) + continue; + + for (nn = 0; nn < n_ns; nn++) { + sendto(fd, queries[qn].query, queries[qn].qlen, + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); + } + } + + t1 = t2; + servfail_retry = 2 * n_queries; + } + + /* Wait for a response, or until time to retry */ + if (poll(&pfd, 1, t1 + retry_interval - t2) <= 0) + continue; + + while (1) { + recvlen = recvfrom(fd, queries[next_query].reply, + sizeof(queries[next_query].reply), 0, + &from.u.sa, &from.len + ); + + /* read error */ + if (recvlen < 0) + break; + + /* Ignore non-identifiable packets */ + if (recvlen < 4) + continue; + + /* Ignore replies from addresses we didn't send to */ + for (nn = 0; nn < n_ns; nn++) + if (memcmp(&from.u.sa, &ns[nn].addr.u.sa, from.len) == 0) + break; + + if (nn >= n_ns) + continue; + + /* Find which query this answer goes with, if any */ + for (qn = next_query; qn < n_queries; qn++) + if (memcmp(queries[next_query].reply, queries[qn].query, 2) == 0) + break; + + if (qn >= n_queries || queries[qn].rlen) + continue; + + queries[qn].rcode = queries[next_query].reply[3] & 15; + queries[qn].latency = monotonic_ms() - t0; + queries[qn].n_ns = nn; + + ns[nn].replies++; + + /* Only accept positive or negative responses; + * retry immediately on server failure, and ignore + * all other codes such as refusal. */ + switch (queries[qn].rcode) { + case 0: + case 3: + break; + + case 2: + if (servfail_retry && servfail_retry--) { + ns[nn].failures++; + sendto(fd, queries[qn].query, queries[qn].qlen, + MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); + } + /* fall through */ + + default: + continue; + } + + /* Store answer */ + n_replies++; + + queries[qn].rlen = recvlen; + + if (qn == next_query) { + while (next_query < n_queries) { + if (!queries[next_query].rlen) + break; + + next_query++; + } + } + else { + memcpy(queries[qn].reply, queries[next_query].reply, recvlen); + } + + if (next_query >= n_queries) + return n_replies; + } + } + + return n_replies; +} + +///FIXME: use standard lsa = xhost2sockaddr(host, port) instead + +static struct ns *add_ns(struct ns **ns, int *n_ns, const char *addr) +{ + char portstr[sizeof("65535")], *p; + len_and_sockaddr a = { }; + struct ns *tmp; + struct addrinfo *ai, *aip, hints = { + .ai_flags = AI_NUMERICSERV, + .ai_socktype = SOCK_DGRAM + }; + + dbg("%s: addr:'%s'\n", __func__, addr); + + if (parse_nsaddr(addr, &a)) { + /* Maybe we got a domain name, attempt to resolve it using the standard + * resolver routines */ + + p = strchr(addr, '#'); + snprintf(portstr, sizeof(portstr), "%hu", + (unsigned short)(p ? strtoul(p, NULL, 10) : G.default_port)); + + if (!getaddrinfo(addr, portstr, &hints, &ai)) { + for (aip = ai; aip; aip = aip->ai_next) { + if (aip->ai_addr->sa_family != AF_INET && + aip->ai_addr->sa_family != AF_INET6) + continue; + +#if ! ENABLE_FEATURE_IPV6 + if (aip->ai_addr->sa_family != AF_INET) + continue; +#endif + + tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1)); + + if (!tmp) + return NULL; + + *ns = tmp; + + (*ns)[*n_ns].name = addr; + (*ns)[*n_ns].replies = 0; + (*ns)[*n_ns].failures = 0; + (*ns)[*n_ns].addr.len = aip->ai_addrlen; + + memcpy(&(*ns)[*n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); + + (*n_ns)++; + } + + freeaddrinfo(ai); + + return &(*ns)[*n_ns]; + } + + return NULL; + } + + tmp = xrealloc(*ns, sizeof(**ns) * (*n_ns + 1)); + *ns = tmp; + + (*ns)[*n_ns].addr = a; + (*ns)[*n_ns].name = addr; + (*ns)[*n_ns].replies = 0; + (*ns)[*n_ns].failures = 0; + + return &(*ns)[(*n_ns)++]; +} + +static int parse_resolvconf(struct ns **ns, int *n_ns) +{ + int prev_n_ns = *n_ns; + FILE *resolv; + + resolv = fopen("/etc/resolv.conf", "r"); + if (resolv) { + char line[128], *p; + + while (fgets(line, sizeof(line), resolv)) { + p = strtok(line, " \t\n"); + + if (!p || strcmp(p, "nameserver") != 0) + continue; + + p = strtok(NULL, " \t\n"); + + if (!p) + continue; + + if (!add_ns(ns, n_ns, xstrdup(p))) { + free(p); + break; + } + } + + fclose(resolv); + } + + return *n_ns - prev_n_ns; +} + +static struct query *add_query(struct query **queries, int *n_queries, + int type, const char *dname) +{ + struct query *new_q; + int pos; + ssize_t qlen; + + pos = *n_queries; + *n_queries = pos + 1; + *queries = new_q = xrealloc(*queries, sizeof(**queries) * (pos + 1)); + + dbg("new query#%u type %u for '%s'\n", pos, type, dname); + new_q += pos; + memset(new_q, 0, sizeof(*new_q)); + + qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, + new_q->query, sizeof(new_q->query)); + + new_q->qlen = qlen; + new_q->name = dname; + + return new_q; +} + +//FIXME: use xmalloc_sockaddr2dotted[_noport]() instead of sal2str() + +#define SIZEOF_SAL2STR_BUF (INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1 + 5 + 1) +static char *sal2str(char buf[SIZEOF_SAL2STR_BUF], len_and_sockaddr *a) +{ + char *p = buf; + +#if ENABLE_FEATURE_IPV6 + if (a->u.sa.sa_family == AF_INET6) { + inet_ntop(AF_INET6, &a->u.sin6.sin6_addr, buf, SIZEOF_SAL2STR_BUF); + p += strlen(p); + + if (a->u.sin6.sin6_scope_id) { + if (if_indextoname(a->u.sin6.sin6_scope_id, p + 1)) { + *p++ = '%'; + p += strlen(p); + } + } + } else +#endif + { + inet_ntop(AF_INET, &a->u.sin.sin_addr, buf, SIZEOF_SAL2STR_BUF); + p += strlen(p); + } + + sprintf(p, "#%hu", ntohs(a->u.sin.sin_port)); + + return buf; +} + +int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int nslookup_main(int argc UNUSED_PARAM, char **argv) +{ + struct ns *ns; + struct query *queries; + llist_t *type_strings; + int n_ns, n_queries; + int bb_style_counter = 0; + unsigned types; + int rc; + int opts; + enum { + OPT_stats = (1 << 4), + }; + + INIT_G(); + + type_strings = NULL; +#if ENABLE_FEATURE_NSLOOKUP_LONG_OPTIONS + opts = getopt32long(argv, "^" + "+" /* '+': stop at first non-option (why?) */ + "q:*p:+r:+t:+s" + "\0" + "-1:q::", /* minimum 1 arg, -q is a list */ + "type\0" Required_argument "q" + "querytype\0" Required_argument "q" + "port\0" Required_argument "p" + "retry\0" Required_argument "r" + "timeout\0" Required_argument "t" + "stats\0" No_argument "s", + &type_strings, &G.default_port, + &G.default_retry, &G.default_timeout + ); +#else + opts = getopt32(argv, "^" + "+" /* '+': stop at first non-option (why?) */ + "q:*p:+r:+t:+s" + "\0" + "-1:q::", /* minimum 1 arg, -q is a list */ + &type_strings, &G.default_port, + &G.default_retry, &G.default_timeout + ); +#endif + if (G.default_port > 65535) + bb_error_msg_and_die("invalid server port"); + if (G.default_retry == 0) + bb_error_msg_and_die("invalid retry value"); + if (G.default_timeout == 0) + bb_error_msg_and_die("invalid timeout value"); + + types = 0; + while (type_strings) { + int c; + char *ptr, *chr; + + ptr = llist_pop(&type_strings); + + /* skip leading text, e.g. when invoked with -querytype=AAAA */ + chr = strchr(ptr, '='); + if (chr) + ptr = chr + 1; + + for (c = 0;; c++) { + if (!qtypes[c].name) + bb_error_msg_and_die("invalid query type \"%s\"", ptr); + if (strcmp(qtypes[c].name, ptr) == 0) + break; + } + + types |= (1 << c); + } + + argv += optind; + + n_queries = 0; + queries = NULL; + do { + /* No explicit type given, guess query type. + * If we can convert the domain argument into a ptr (means that + * inet_pton() could read it) we assume a PTR request, else + * we issue A+AAAA queries and switch to an output format + * mimicking the one of the traditional nslookup applet. */ + if (types == 0) { + char *ptr; + char buf80[80]; + + ptr = make_ptr(buf80, *argv); + + if (ptr) { + add_query(&queries, &n_queries, T_PTR, ptr); + } + else { + bb_style_counter = 1; + add_query(&queries, &n_queries, T_A, *argv); +#if ENABLE_FEATURE_IPV6 + add_query(&queries, &n_queries, T_AAAA, *argv); +#endif + } + } + else { + int c; + for (c = 0; qtypes[c].name; c++) { + if (types & (1 << c)) + add_query(&queries, &n_queries, qtypes[c].type, + *argv); + } + } + argv++; + } while (argv[0] && argv[1]); + + /* Use given DNS server if present */ + n_ns = 0; + ns = NULL; + if (argv[0]) { + if (!add_ns(&ns, &n_ns, argv[0])) + bb_error_msg_and_die("invalid NS server address \"%s\"", argv[0]); + } else { + parse_resolvconf(&ns, &n_ns); + /* Fall back to localhost if we could not find NS in resolv.conf */ + if (n_ns == 0) + add_ns(&ns, &n_ns, "127.0.0.1"); + } + + for (rc = 0; rc < n_ns; rc++) { + int c = send_queries(&ns[rc], 1, queries, n_queries); + if (c < 0) + bb_perror_msg_and_die("can't send queries"); + if (c > 0) + break; + rc++; + if (rc >= n_ns) { + fprintf(stderr, + ";; connection timed out; no servers could be reached\n\n"); + return EXIT_FAILURE; + } + } + + printf("Server:\t\t%s\n", ns[rc].name); + { + char buf[SIZEOF_SAL2STR_BUF]; + printf("Address:\t%s\n", sal2str(buf, &ns[rc].addr)); + } + if (opts & OPT_stats) { + printf("Replies:\t%d\n", ns[rc].replies); + printf("Failures:\t%d\n", ns[rc].failures); + } + printf("\n"); + + for (rc = 0; rc < n_queries; rc++) { + int c; + + if (opts & OPT_stats) { + printf("Query #%d completed in %lums:\n", rc, queries[rc].latency); + } + + if (queries[rc].rcode != 0) { + printf("** server can't find %s: %s\n", queries[rc].name, + rcodes[queries[rc].rcode]); + continue; + } + + c = 0; + + if (queries[rc].rlen) { + HEADER *header; + + if (!bb_style_counter) { + header = (HEADER *)queries[rc].reply; + if (!header->aa) + printf("Non-authoritative answer:\n"); + c = parse_reply(queries[rc].reply, queries[rc].rlen, NULL); + } + else { + c = parse_reply(queries[rc].reply, queries[rc].rlen, + &bb_style_counter); + } + } + + if (c == 0) + printf("*** Can't find %s: No answer\n", queries[rc].name); + else if (c < 0) + printf("*** Can't find %s: Parse error\n", queries[rc].name); + + if (!bb_style_counter) + printf("\n"); + } + + if (ENABLE_FEATURE_CLEAN_UP) { + free(ns); + if (n_queries) + free(queries); + } + + return EXIT_SUCCESS; +} + +#endif -- cgit v1.2.3-55-g6feb From d5f5045b43bd7a55402dfcecde511a2c207d07b7 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 14:50:47 +0200 Subject: ash: expand: Fix buffer overflow in expandmeta Upstream commit: Date: Sun, 25 Mar 2018 16:38:00 +0800 expand: Fix buffer overflow in expandmeta The native version of expandmeta allocates a buffer that may be overrun for two reasons. First of all the size is 1 byte too small but this is normally hidden because the minimum size is rounded up to 2048 bytes. Secondly, if the directory level is deep enough, any buffer can be overrun. This patch fixes both problems by calling realloc when necessary. Signed-off-by: Herbert Xu function old new delta expmeta 517 635 +118 expandarg 990 996 +6 mklocal 288 290 +2 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 126/0) Total: 126 bytes Signed-off-by: Denys Vlasenko --- shell/ash.c | 67 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 42 insertions(+), 25 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 40ca82d0b..051cc671f 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -7575,9 +7575,16 @@ expandmeta(struct strlist *str /*, int flag*/) /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ +typedef struct exp_t { + char *dir; + unsigned dir_max; +} exp_t; static void -expmeta(char *expdir, char *enddir, char *name) +expmeta(exp_t *exp, char *name, unsigned name_len, unsigned expdir_len) { +#define expdir exp->dir +#define expdir_max exp->dir_max + char *enddir = expdir + expdir_len; char *p; const char *cp; char *start; @@ -7620,15 +7627,15 @@ expmeta(char *expdir, char *enddir, char *name) } } if (metaflag == 0) { /* we've reached the end of the file name */ - if (enddir != expdir) - metaflag++; + if (!expdir_len) + return; p = name; do { if (*p == '\\') p++; *enddir++ = *p; } while (*p++); - if (metaflag == 0 || lstat(expdir, &statb) >= 0) + if (lstat(expdir, &statb) == 0) addfname(expdir); return; } @@ -7641,19 +7648,14 @@ expmeta(char *expdir, char *enddir, char *name) *enddir++ = *p++; } while (p < start); } - if (enddir == expdir) { + *enddir = '\0'; + cp = expdir; + expdir_len = enddir - cp; + if (!expdir_len) cp = "."; - } else if (enddir == expdir + 1 && *expdir == '/') { - cp = "/"; - } else { - cp = expdir; - enddir[-1] = '\0'; - } dirp = opendir(cp); if (dirp == NULL) return; - if (enddir != expdir) - enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { @@ -7661,6 +7663,7 @@ expmeta(char *expdir, char *enddir, char *name) *endname = '\0'; endname += esc + 1; } + name_len -= endname - name; matchdot = 0; p = start; if (*p == '\\') @@ -7675,16 +7678,30 @@ expmeta(char *expdir, char *enddir, char *name) strcpy(enddir, dp->d_name); addfname(expdir); } else { - for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) - continue; - p[-1] = '/'; - expmeta(expdir, p, endname); + unsigned offset; + unsigned len; + + p = stpcpy(enddir, dp->d_name); + *p = '/'; + + offset = p - expdir + 1; + len = offset + name_len + NAME_MAX; + if (len > expdir_max) { + len += PATH_MAX; + expdir = ckrealloc(expdir, len); + expdir_max = len; + } + + expmeta(exp, endname, name_len, offset); + enddir = expdir + expdir_len; } } } closedir(dirp); if (!atend) endname[-esc - 1] = esc ? '\\' : '/'; +#undef expdir +#undef expdir_max } static struct strlist * @@ -7757,10 +7774,11 @@ expandmeta(struct strlist *str /*, int flag*/) /* TODO - EXP_REDIR */ while (str) { - char *expdir; + exp_t exp; struct strlist **savelastp; struct strlist *sp; char *p; + unsigned len; if (fflag) goto nometa; @@ -7770,13 +7788,12 @@ expandmeta(struct strlist *str /*, int flag*/) INT_OFF; p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); - { - int i = strlen(str->text); -//BUGGY estimation of how long expanded name can be - expdir = ckmalloc(i < 2048 ? 2048 : i+1); - } - expmeta(expdir, expdir, p); - free(expdir); + len = strlen(p); + exp.dir_max = len + PATH_MAX; + exp.dir = ckmalloc(exp.dir_max); + + expmeta(&exp, p, len, 0); + free(exp.dir); if (p != str->text) free(p); INT_ON; -- cgit v1.2.3-55-g6feb From 75568354f6e2ab08302085bbcb32c3368c49b86f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 16:05:26 +0200 Subject: patch: implement --dry-run function old new delta static.patch_longopts - 137 +137 patch_main 2053 2135 +82 fail_hunk 132 139 +7 finish_oldfile 119 124 +5 packed_usage 32807 32787 -20 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 3/1 up/down: 231/-20) Total: 211 bytes Signed-off-by: Denys Vlasenko --- editors/patch.c | 129 +++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 43 deletions(-) diff --git a/editors/patch.c b/editors/patch.c index a51b7a502..eca6bc5f6 100644 --- a/editors/patch.c +++ b/editors/patch.c @@ -15,7 +15,6 @@ * -D define wrap #ifdef and #ifndef around changes * -o outfile output here instead of in place * -r rejectfile write rejected hunks to this file - * --dry-run (regression!) * * -f force (no questions asked) * -F fuzz (number, default 2) @@ -34,23 +33,15 @@ //usage:#define patch_trivial_usage //usage: "[OPTIONS] [ORIGFILE [PATCHFILE]]" //usage:#define patch_full_usage "\n\n" -//usage: IF_LONG_OPTS( -//usage: " -p,--strip N Strip N leading components from file names" -//usage: "\n -i,--input DIFF Read DIFF instead of stdin" -//usage: "\n -R,--reverse Reverse patch" -//usage: "\n -N,--forward Ignore already applied patches" -/*usage: "\n --dry-run Don't actually change files" - TODO */ -//usage: "\n -E,--remove-empty-files Remove output files if they become empty" -//usage: ) -//usage: IF_NOT_LONG_OPTS( //usage: " -p N Strip N leading components from file names" //usage: "\n -i DIFF Read DIFF instead of stdin" //usage: "\n -R Reverse patch" //usage: "\n -N Ignore already applied patches" //usage: "\n -E Remove output files if they become empty" +//usage: IF_LONG_OPTS( +//usage: "\n --dry-run Don't actually change files" //usage: ) /* -u "interpret as unified diff" is supported but not documented: this info is not useful for --help */ -/* -x "debug" is supported but does nothing */ //usage: //usage:#define patch_example_usage //usage: "$ patch -p1 < example.diff\n" @@ -58,6 +49,7 @@ #include "libbb.h" +#define PATCH_DEBUG 0 // libbb candidate? @@ -122,16 +114,18 @@ struct globals { } while (0) -#define FLAG_STR "Rup:i:NEx" +#define FLAG_STR "Rup:i:NEfg" /* FLAG_REVERSE must be == 1! Code uses this fact. */ -#define FLAG_REVERSE (1 << 0) -#define FLAG_u (1 << 1) -#define FLAG_PATHLEN (1 << 2) -#define FLAG_INPUT (1 << 3) -#define FLAG_IGNORE (1 << 4) -#define FLAG_RMEMPTY (1 << 5) -/* Enable this bit and use -x for debug output: */ -#define FLAG_DEBUG (0 << 6) +#define FLAG_REVERSE (1 << 0) +#define FLAG_u (1 << 1) +#define FLAG_PATHLEN (1 << 2) +#define FLAG_INPUT (1 << 3) +#define FLAG_IGNORE (1 << 4) +#define FLAG_RMEMPTY (1 << 5) +#define FLAG_f_unused (1 << 6) +#define FLAG_g_unused (1 << 7) +#define FLAG_dry_run ((1 << 8) * ENABLE_LONG_OPTS) + // Dispose of a line of input, either by writing it out or discarding it. @@ -140,8 +134,6 @@ struct globals { // state = 3: write whole line to fileout // state > 3: write line+1 to fileout when *line != state -#define PATCH_DEBUG (option_mask32 & FLAG_DEBUG) - static void do_line(void *data) { struct double_list *dlist = data; @@ -168,12 +160,14 @@ static void finish_oldfile(void) } xclose(TT.fileout); - temp = xstrdup(TT.tempname); - temp[strlen(temp) - 6] = '\0'; - rename(TT.tempname, temp); - free(temp); + if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */ + temp = xstrdup(TT.tempname); + temp[strlen(temp) - 6] = '\0'; + rename(TT.tempname, temp); + free(temp); + free(TT.tempname); + } - free(TT.tempname); TT.tempname = NULL; } TT.fileout = TT.filein = -1; @@ -197,8 +191,10 @@ static void fail_hunk(void) // Abort the copy and delete the temporary file. close(TT.filein); close(TT.fileout); - unlink(TT.tempname); - free(TT.tempname); + if (!ENABLE_LONG_OPTS || TT.tempname[0]) { /* not --dry-run? */ + unlink(TT.tempname); + free(TT.tempname); + } TT.tempname = NULL; TT.state = 0; @@ -239,6 +235,7 @@ static int apply_one_hunk(void) plist = TT.current_hunk; buf = NULL; if (reverse ? TT.oldlen : TT.newlen) for (;;) { +//FIXME: this performs 1-byte reads: char *data = xmalloc_reads(TT.filein, NULL); TT.linenum++; @@ -369,9 +366,45 @@ int patch_main(int argc UNUSED_PARAM, char **argv) long oldlen = oldlen; /* for compiler */ long newlen = newlen; /* for compiler */ +#if ENABLE_LONG_OPTS + static const char patch_longopts[] ALIGN1 = + "reverse\0" No_argument "R" + "unified\0" No_argument "u" + "strip\0" Required_argument "p" + "input\0" Required_argument "i" + "forward\0" No_argument "N" +# if ENABLE_DESKTOP + "remove-empty-files\0" No_argument "E" /*ignored*/ + /* "debug" Required_argument "x" */ +# endif + /* "Assume user knows what [s]he is doing, do not ask any questions": */ + "force\0" No_argument "f" /*ignored*/ +# if ENABLE_DESKTOP + /* "Controls actions when a file is under RCS or SCCS control, + * and does not exist or is read-only and matches the default version, + * or when a file is under ClearCase control and does not exist..." + * IOW: rather obscure option. + * But Gentoo's portage does use -g0 + */ + "get\0" Required_argument "g" /*ignored*/ +# endif + "dry-run\0" No_argument "\xfd" +# if ENABLE_DESKTOP + "backup-if-mismatch\0" No_argument "\xfe" /*ignored*/ + "no-backup-if-mismatch\0" No_argument "\xff" /*ignored*/ +# endif + ; +#endif + INIT_TT(); +#if ENABLE_LONG_OPTS + opts = getopt32long(argv, FLAG_STR, patch_longopts, &opt_p, &opt_i); +#else opts = getopt32(argv, FLAG_STR, &opt_p, &opt_i); +#endif + //bb_error_msg_and_die("opts:%x", opts); + argv += optind; reverse = opts & FLAG_REVERSE; TT.prefix = (opts & FLAG_PATHLEN) ? xatoi(opt_p) : 0; // can be negative! @@ -517,10 +550,12 @@ int patch_main(int argc UNUSED_PARAM, char **argv) if (option_mask32 & FLAG_RMEMPTY) { // If flag -E or --remove-empty-files is set printf("removing %s\n", name); - xunlink(name); + if (!(opts & FLAG_dry_run)) + xunlink(name); } else { printf("patching file %s\n", name); - xclose(xopen(name, O_WRONLY | O_TRUNC)); + if (!(opts & FLAG_dry_run)) + xclose(xopen(name, O_WRONLY | O_TRUNC)); } // If we've got a file to open, do so. } else if (!(option_mask32 & FLAG_PATHLEN) || i <= TT.prefix) { @@ -529,24 +564,32 @@ int patch_main(int argc UNUSED_PARAM, char **argv) // If the old file was null, we're creating a new one. if (strcmp(oldname, "/dev/null") == 0 || !oldsum) { printf("creating %s\n", name); - s = strrchr(name, '/'); - if (s) { - *s = 0; - bb_make_directory(name, -1, FILEUTILS_RECUR); - *s = '/'; + if (!(opts & FLAG_dry_run)) { + s = strrchr(name, '/'); + if (s) { + *s = '\0'; + bb_make_directory(name, -1, FILEUTILS_RECUR); + *s = '/'; + } + TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR); + } else { + TT.filein = xopen("/dev/null", O_RDONLY); } - TT.filein = xopen(name, O_CREAT|O_EXCL|O_RDWR); } else { printf("patching file %s\n", name); TT.filein = xopen(name, O_RDONLY); } - TT.tempname = xasprintf("%sXXXXXX", name); - TT.fileout = xmkstemp(TT.tempname); - // Set permissions of output file - fstat(TT.filein, &statbuf); - fchmod(TT.fileout, statbuf.st_mode); - + if (!(opts & FLAG_dry_run)) { + TT.tempname = xasprintf("%sXXXXXX", name); + TT.fileout = xmkstemp(TT.tempname); + // Set permissions of output file + fstat(TT.filein, &statbuf); + fchmod(TT.fileout, statbuf.st_mode); + } else { + TT.tempname = (char*)""; + TT.fileout = xopen("/dev/null", O_WRONLY); + } TT.linenum = 0; TT.hunknum = 0; } -- cgit v1.2.3-55-g6feb From 55bc8e8826df8cf5c342f57e513d10ee6c632838 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 17:54:24 +0200 Subject: nslookup: usee bbox network functions instead of opne-coded mess function old new delta nslookup_main 2363 2091 -272 add_ns 663 65 -598 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-870) Total: -870 bytes Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 310 ++++++++++++-------------------------------------- 1 file changed, 70 insertions(+), 240 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 6f8f02fc1..50d91abbc 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -256,7 +256,7 @@ int nslookup_main(int argc, char **argv) struct ns { const char *name; - len_and_sockaddr addr; + len_and_sockaddr *lsa; int failures; int replies; }; @@ -266,7 +266,7 @@ struct query { size_t qlen, rlen; unsigned char query[512], reply[512]; unsigned long latency; - int rcode, n_ns; + int rcode; }; static const struct { @@ -315,6 +315,8 @@ struct globals { unsigned default_port; unsigned default_retry; unsigned default_timeout; + unsigned serv_count; + struct ns *server; } FIX_ALIASING; #define G (*(struct globals*)bb_common_bufsiz1) #define INIT_G() do { \ @@ -480,69 +482,6 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) return i; } -static int parse_nsaddr(const char *addrstr, len_and_sockaddr *lsa) -{ - char *hash; - unsigned port = G.default_port; - IF_FEATURE_IPV6(unsigned scope;) - - dbg("%s: addrstr:'%s'\n", __func__, addrstr); - - hash = strchr(addrstr, '#'); - - if (hash) { - *hash++ = '\0'; - port = bb_strtou(hash, NULL, 10); - if (errno || port > 65535) { - errno = EINVAL; - return -1; - } - } - -#if ENABLE_FEATURE_IPV6 - scope = 0; - hash = strchr(addrstr, '%'); - if (hash) { - char ifname[IFNAMSIZ]; - int i; - - hash++; - for (i = 0; hash[i] != '\0' && hash[i] != '#'; i++) { - if (i >= IFNAMSIZ) { - errno = ENODEV; - return -1; - } - ifname[i] = hash[i]; - } - ifname[i] = '\0'; - - scope = if_nametoindex(ifname); - if (scope == 0) { - errno = ENODEV; - return -1; - } - } - - if (inet_pton(AF_INET6, addrstr, &lsa->u.sin6.sin6_addr)) { - lsa->u.sin6.sin6_family = AF_INET6; - lsa->u.sin6.sin6_port = htons(port); - lsa->u.sin6.sin6_scope_id = scope; - lsa->len = sizeof(lsa->u.sin6); - return 0; - } -#endif - - if (inet_pton(AF_INET, addrstr, &lsa->u.sin.sin_addr)) { - lsa->u.sin.sin_family = AF_INET; - lsa->u.sin.sin_port = htons(port); - lsa->len = sizeof(lsa->u.sin); - return 0; - } - - errno = EINVAL; - return -1; -} - static char *make_ptr(char resbuf[80], const char *addrstr) { unsigned char addr[16]; @@ -577,104 +516,65 @@ static char *make_ptr(char resbuf[80], const char *addrstr) return NULL; } -#if ENABLE_FEATURE_IPV6 -static void to_v4_mapped(len_and_sockaddr *a) -{ - if (a->u.sa.sa_family != AF_INET) - return; - - /* Order is important */ -//FIXME: port? - memcpy(a->u.sin6.sin6_addr.s6_addr + 12, &a->u.sin.sin_addr, 4); - memcpy(a->u.sin6.sin6_addr.s6_addr, v4_mapped, 12); - - a->u.sin6.sin6_family = AF_INET6; - a->u.sin6.sin6_flowinfo = 0; - a->u.sin6.sin6_scope_id = 0; - a->len = sizeof(a->u.sin6); -} -#endif - /* * Function logic borrowed & modified from musl libc, res_msend.c */ -static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_queries) +static int send_queries(struct ns *ns, struct query *queries, int n_queries) { - int fd; - int timeout = G.default_timeout * 1000, retry_interval, servfail_retry = 0; - len_and_sockaddr from = { }; - int recvlen = 0; - int n_replies = 0; struct pollfd pfd; - unsigned long t0, t1, t2; - int nn, qn, next_query = 0; - - from.u.sa.sa_family = AF_INET; - from.len = sizeof(from.u.sin); -#if ENABLE_FEATURE_IPV6 - for (nn = 0; nn < n_ns; nn++) { - if (ns[nn].addr.u.sa.sa_family == AF_INET6) { - from.u.sa.sa_family = AF_INET6; - from.len = sizeof(from.u.sin6); - break; - } - } -#endif - - /* Get local address and open/bind a socket */ -#if ENABLE_FEATURE_IPV6 - fd = socket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - /* Handle case where system lacks IPv6 support */ - if (fd < 0) { - if (from.u.sa.sa_family != AF_INET6 || errno != EAFNOSUPPORT) - bb_perror_msg_and_die("socket"); - fd = xsocket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - from.u.sa.sa_family = AF_INET; - } -#else - fd = xsocket(from.u.sa.sa_family, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); -#endif - xbind(fd, &from.u.sa, from.len); - -#if ENABLE_FEATURE_IPV6 - /* Convert any IPv4 addresses in a mixed environment to v4-mapped */ - if (from.u.sa.sa_family == AF_INET6) { - setsockopt_1(fd, IPPROTO_IPV6, IPV6_V6ONLY); - for (nn = 0; nn < n_ns; nn++) - to_v4_mapped(&ns[nn].addr); - } -#endif + int servfail_retry = 0; + int n_replies = 0; + int next_query = 0; + unsigned retry_interval; + unsigned timeout = G.default_timeout * 1000; + unsigned t0, t1, t2; - pfd.fd = fd; + pfd.fd = -1; pfd.events = POLLIN; + retry_interval = timeout / G.default_retry; t0 = t2 = monotonic_ms(); t1 = t2 - retry_interval; for (; t2 - t0 < timeout; t2 = monotonic_ms()) { if (t2 - t1 >= retry_interval) { + int qn; for (qn = 0; qn < n_queries; qn++) { if (queries[qn].rlen) continue; - - for (nn = 0; nn < n_ns; nn++) { - sendto(fd, queries[qn].query, queries[qn].qlen, - MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); + if (pfd.fd < 0) { + len_and_sockaddr *local_lsa; + pfd.fd = xsocket_type(&local_lsa, ns->lsa->u.sa.sa_family, SOCK_DGRAM); + /* + * local_lsa has "null" address and port 0 now. + * bind() ensures we have a *particular port* selected by kernel + * and remembered in fd, thus later recv(fd) + * receives only packets sent to this port. + */ + xbind(pfd.fd, &local_lsa->u.sa, local_lsa->len); + free(local_lsa); + /* Make read/writes know the destination */ + xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len); + ndelay_on(pfd.fd); } + write(pfd.fd, queries[qn].query, queries[qn].qlen); } t1 = t2; servfail_retry = 2 * n_queries; } - + poll_more: /* Wait for a response, or until time to retry */ if (poll(&pfd, 1, t1 + retry_interval - t2) <= 0) continue; while (1) { - recvlen = recvfrom(fd, queries[next_query].reply, - sizeof(queries[next_query].reply), 0, - &from.u.sa, &from.len + int qn; + int recvlen; + + recvlen = read(pfd.fd, + queries[next_query].reply, + sizeof(queries[next_query].reply) ); /* read error */ @@ -683,15 +583,7 @@ static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_qu /* Ignore non-identifiable packets */ if (recvlen < 4) - continue; - - /* Ignore replies from addresses we didn't send to */ - for (nn = 0; nn < n_ns; nn++) - if (memcmp(&from.u.sa, &ns[nn].addr.u.sa, from.len) == 0) - break; - - if (nn >= n_ns) - continue; + goto poll_more; /* Find which query this answer goes with, if any */ for (qn = next_query; qn < n_queries; qn++) @@ -699,13 +591,12 @@ static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_qu break; if (qn >= n_queries || queries[qn].rlen) - continue; + goto poll_more; queries[qn].rcode = queries[next_query].reply[3] & 15; queries[qn].latency = monotonic_ms() - t0; - queries[qn].n_ns = nn; - ns[nn].replies++; + ns->replies++; /* Only accept positive or negative responses; * retry immediately on server failure, and ignore @@ -717,9 +608,8 @@ static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_qu case 2: if (servfail_retry && servfail_retry--) { - ns[nn].failures++; - sendto(fd, queries[qn].query, queries[qn].qlen, - MSG_NOSIGNAL, &ns[nn].addr.u.sa, ns[nn].addr.len); + ns->failures++; + write(pfd.fd, queries[qn].query, queries[qn].qlen); } /* fall through */ @@ -736,94 +626,42 @@ static int send_queries(struct ns *ns, int n_ns, struct query *queries, int n_qu while (next_query < n_queries) { if (!queries[next_query].rlen) break; - next_query++; } - } - else { + } else { memcpy(queries[qn].reply, queries[next_query].reply, recvlen); } if (next_query >= n_queries) - return n_replies; + goto ret; } } + ret: + if (pfd.fd >= 0) + close(pfd.fd); return n_replies; } -///FIXME: use standard lsa = xhost2sockaddr(host, port) instead - -static struct ns *add_ns(struct ns **ns, int *n_ns, const char *addr) +static void add_ns(const char *addr) { - char portstr[sizeof("65535")], *p; - len_and_sockaddr a = { }; - struct ns *tmp; - struct addrinfo *ai, *aip, hints = { - .ai_flags = AI_NUMERICSERV, - .ai_socktype = SOCK_DGRAM - }; + struct ns *ns; + unsigned count; dbg("%s: addr:'%s'\n", __func__, addr); - if (parse_nsaddr(addr, &a)) { - /* Maybe we got a domain name, attempt to resolve it using the standard - * resolver routines */ - - p = strchr(addr, '#'); - snprintf(portstr, sizeof(portstr), "%hu", - (unsigned short)(p ? strtoul(p, NULL, 10) : G.default_port)); + count = G.serv_count++; - if (!getaddrinfo(addr, portstr, &hints, &ai)) { - for (aip = ai; aip; aip = aip->ai_next) { - if (aip->ai_addr->sa_family != AF_INET && - aip->ai_addr->sa_family != AF_INET6) - continue; - -#if ! ENABLE_FEATURE_IPV6 - if (aip->ai_addr->sa_family != AF_INET) - continue; -#endif - - tmp = realloc(*ns, sizeof(**ns) * (*n_ns + 1)); - - if (!tmp) - return NULL; - - *ns = tmp; - - (*ns)[*n_ns].name = addr; - (*ns)[*n_ns].replies = 0; - (*ns)[*n_ns].failures = 0; - (*ns)[*n_ns].addr.len = aip->ai_addrlen; - - memcpy(&(*ns)[*n_ns].addr.u.sa, aip->ai_addr, aip->ai_addrlen); - - (*n_ns)++; - } - - freeaddrinfo(ai); - - return &(*ns)[*n_ns]; - } - - return NULL; - } - - tmp = xrealloc(*ns, sizeof(**ns) * (*n_ns + 1)); - *ns = tmp; - - (*ns)[*n_ns].addr = a; - (*ns)[*n_ns].name = addr; - (*ns)[*n_ns].replies = 0; - (*ns)[*n_ns].failures = 0; - - return &(*ns)[(*n_ns)++]; + G.server = xrealloc_vector(G.server, /*8=2^3:*/ 3, count); + ns = &G.server[count]; + ns->name = addr; + ns->lsa = xhost2sockaddr(addr, 53); + /*ns->replies = 0; - already is */ + /*ns->failures = 0; - already is */ } -static int parse_resolvconf(struct ns **ns, int *n_ns) +static void parse_resolvconf(void) { - int prev_n_ns = *n_ns; FILE *resolv; resolv = fopen("/etc/resolv.conf", "r"); @@ -841,16 +679,11 @@ static int parse_resolvconf(struct ns **ns, int *n_ns) if (!p) continue; - if (!add_ns(ns, n_ns, xstrdup(p))) { - free(p); - break; - } + add_ns(xstrdup(p)); } fclose(resolv); } - - return *n_ns - prev_n_ns; } static struct query *add_query(struct query **queries, int *n_queries, @@ -913,7 +746,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) struct ns *ns; struct query *queries; llist_t *type_strings; - int n_ns, n_queries; + int n_queries; int bb_style_counter = 0; unsigned types; int rc; @@ -1018,40 +851,37 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } while (argv[0] && argv[1]); /* Use given DNS server if present */ - n_ns = 0; - ns = NULL; if (argv[0]) { - if (!add_ns(&ns, &n_ns, argv[0])) - bb_error_msg_and_die("invalid NS server address \"%s\"", argv[0]); + add_ns(argv[0]); } else { - parse_resolvconf(&ns, &n_ns); + parse_resolvconf(); /* Fall back to localhost if we could not find NS in resolv.conf */ - if (n_ns == 0) - add_ns(&ns, &n_ns, "127.0.0.1"); + if (G.serv_count == 0) + add_ns("127.0.0.1"); } - for (rc = 0; rc < n_ns; rc++) { - int c = send_queries(&ns[rc], 1, queries, n_queries); + for (rc = 0; rc < G.serv_count; rc++) { + int c = send_queries(&G.server[rc], queries, n_queries); if (c < 0) bb_perror_msg_and_die("can't send queries"); if (c > 0) break; rc++; - if (rc >= n_ns) { + if (rc >= G.serv_count) { fprintf(stderr, ";; connection timed out; no servers could be reached\n\n"); return EXIT_FAILURE; } } - printf("Server:\t\t%s\n", ns[rc].name); + printf("Server:\t\t%s\n", G.server[rc].name); { char buf[SIZEOF_SAL2STR_BUF]; - printf("Address:\t%s\n", sal2str(buf, &ns[rc].addr)); + printf("Address:\t%s\n", sal2str(buf, G.server[rc].lsa)); } if (opts & OPT_stats) { - printf("Replies:\t%d\n", ns[rc].replies); - printf("Failures:\t%d\n", ns[rc].failures); + printf("Replies:\t%d\n", G.server[rc].replies); + printf("Failures:\t%d\n", G.server[rc].failures); } printf("\n"); -- cgit v1.2.3-55-g6feb From db93b21ec9c7923dc2c419c81650b070b9ca751a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 18:11:35 +0200 Subject: nslookup: use xmalloc_sockaddr2dotted() instead of homegrown function function old new delta nslookup_main 2091 2007 -84 Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 48 +++++++++++------------------------------------- 1 file changed, 11 insertions(+), 37 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 50d91abbc..2e6569497 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -557,7 +557,11 @@ static int send_queries(struct ns *ns, struct query *queries, int n_queries) xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len); ndelay_on(pfd.fd); } - write(pfd.fd, queries[qn].query, queries[qn].qlen); + if (write(pfd.fd, queries[qn].query, queries[qn].qlen) < 0) { + bb_perror_msg("write to '%s'", ns->name); + close(pfd.fd); + return -1; /* "no go, try next server" */ + } } t1 = t2; @@ -710,36 +714,6 @@ static struct query *add_query(struct query **queries, int *n_queries, return new_q; } -//FIXME: use xmalloc_sockaddr2dotted[_noport]() instead of sal2str() - -#define SIZEOF_SAL2STR_BUF (INET6_ADDRSTRLEN + 1 + IFNAMSIZ + 1 + 5 + 1) -static char *sal2str(char buf[SIZEOF_SAL2STR_BUF], len_and_sockaddr *a) -{ - char *p = buf; - -#if ENABLE_FEATURE_IPV6 - if (a->u.sa.sa_family == AF_INET6) { - inet_ntop(AF_INET6, &a->u.sin6.sin6_addr, buf, SIZEOF_SAL2STR_BUF); - p += strlen(p); - - if (a->u.sin6.sin6_scope_id) { - if (if_indextoname(a->u.sin6.sin6_scope_id, p + 1)) { - *p++ = '%'; - p += strlen(p); - } - } - } else -#endif - { - inet_ntop(AF_INET, &a->u.sin.sin_addr, buf, SIZEOF_SAL2STR_BUF); - p += strlen(p); - } - - sprintf(p, "#%hu", ntohs(a->u.sin.sin_port)); - - return buf; -} - int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int nslookup_main(int argc UNUSED_PARAM, char **argv) { @@ -862,10 +836,10 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) for (rc = 0; rc < G.serv_count; rc++) { int c = send_queries(&G.server[rc], queries, n_queries); - if (c < 0) - bb_perror_msg_and_die("can't send queries"); if (c > 0) break; + /* c = 0: timed out waiting for replies */ + /* c < 0: error (message already printed) */ rc++; if (rc >= G.serv_count) { fprintf(stderr, @@ -875,10 +849,10 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } printf("Server:\t\t%s\n", G.server[rc].name); - { - char buf[SIZEOF_SAL2STR_BUF]; - printf("Address:\t%s\n", sal2str(buf, G.server[rc].lsa)); - } + printf("Address:\t%s\n", xmalloc_sockaddr2dotted(&G.server[rc].lsa->u.sa)); + /* In "Address", bind-utils-9.11.3 show port after a hash: "1.2.3.4#53" */ + /* Should we do the same? */ + if (opts & OPT_stats) { printf("Replies:\t%d\n", G.server[rc].replies); printf("Failures:\t%d\n", G.server[rc].failures); -- cgit v1.2.3-55-g6feb From 71e016d806f0f2c9168e9096546c15996e6a8e20 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 22:53:39 +0200 Subject: nslookup: shrink send_queries() function old new delta rcodes 68 64 -4 nslookup_main 2007 1880 -127 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-131) Total: -131 bytes text data bss dec hex filename 926735 555 5740 933030 e3ca6 busybox_old 926525 555 5740 932820 e3bd4 busybox_unstripped Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 241 ++++++++++++++++++++++++++------------------------ 1 file changed, 123 insertions(+), 118 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 2e6569497..23ab97585 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -263,10 +263,10 @@ struct ns { struct query { const char *name; - size_t qlen, rlen; + unsigned qlen, rlen; + unsigned latency; + uint8_t rcode; unsigned char query[512], reply[512]; - unsigned long latency; - int rcode; }; static const struct { @@ -288,23 +288,22 @@ static const struct { }; static const char *const rcodes[] = { - "NOERROR", - "FORMERR", - "SERVFAIL", - "NXDOMAIN", - "NOTIMP", - "REFUSED", - "YXDOMAIN", - "YXRRSET", - "NXRRSET", - "NOTAUTH", - "NOTZONE", - "RESERVED11", - "RESERVED12", - "RESERVED13", - "RESERVED14", - "RESERVED15", - "BADVERS" + "NOERROR", // 0 + "FORMERR", // 1 + "SERVFAIL", // 2 + "NXDOMAIN", // 3 + "NOTIMP", // 4 + "REFUSED", // 5 + "YXDOMAIN", // 6 + "YXRRSET", // 7 + "NXRRSET", // 8 + "NOTAUTH", // 9 + "NOTZONE", // 10 + "11", // 11 not assigned + "12", // 12 not assigned + "13", // 13 not assigned + "14", // 14 not assigned + "15", // 15 not assigned }; #if ENABLE_FEATURE_IPV6 @@ -518,131 +517,136 @@ static char *make_ptr(char resbuf[80], const char *addrstr) /* * Function logic borrowed & modified from musl libc, res_msend.c + * n_queries is always > 0. */ -static int send_queries(struct ns *ns, struct query *queries, int n_queries) +static int send_queries(struct ns *ns, struct query *query, int n_queries) { + len_and_sockaddr *local_lsa; struct pollfd pfd; int servfail_retry = 0; int n_replies = 0; - int next_query = 0; + int save_idx = 0; unsigned retry_interval; unsigned timeout = G.default_timeout * 1000; - unsigned t0, t1, t2; + unsigned tstart, tsent, tcur; - pfd.fd = -1; pfd.events = POLLIN; + pfd.fd = xsocket_type(&local_lsa, ns->lsa->u.sa.sa_family, SOCK_DGRAM); + /* + * local_lsa has "null" address and port 0 now. + * bind() ensures we have a *particular port* selected by kernel + * and remembered in fd, thus later recv(fd) + * receives only packets sent to this port. + */ + xbind(pfd.fd, &local_lsa->u.sa, local_lsa->len); + free(local_lsa); + /* Make read/writes know the destination */ + xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len); + ndelay_on(pfd.fd); retry_interval = timeout / G.default_retry; - t0 = t2 = monotonic_ms(); - t1 = t2 - retry_interval; + tstart = tcur = monotonic_ms(); + goto send; + + while (tcur - tstart < timeout) { + int qn; + int recvlen; - for (; t2 - t0 < timeout; t2 = monotonic_ms()) { - if (t2 - t1 >= retry_interval) { - int qn; + if (tcur - tsent >= retry_interval) { + send: for (qn = 0; qn < n_queries; qn++) { - if (queries[qn].rlen) + if (query[qn].rlen) continue; - if (pfd.fd < 0) { - len_and_sockaddr *local_lsa; - pfd.fd = xsocket_type(&local_lsa, ns->lsa->u.sa.sa_family, SOCK_DGRAM); - /* - * local_lsa has "null" address and port 0 now. - * bind() ensures we have a *particular port* selected by kernel - * and remembered in fd, thus later recv(fd) - * receives only packets sent to this port. - */ - xbind(pfd.fd, &local_lsa->u.sa, local_lsa->len); - free(local_lsa); - /* Make read/writes know the destination */ - xconnect(pfd.fd, &ns->lsa->u.sa, ns->lsa->len); - ndelay_on(pfd.fd); - } - if (write(pfd.fd, queries[qn].query, queries[qn].qlen) < 0) { + if (write(pfd.fd, query[qn].query, query[qn].qlen) < 0) { bb_perror_msg("write to '%s'", ns->name); - close(pfd.fd); - return -1; /* "no go, try next server" */ + n_replies = -1; /* "no go, try next server" */ + goto ret; } + dbg("query %u sent\n", qn); } - - t1 = t2; + tsent = tcur; servfail_retry = 2 * n_queries; } - poll_more: - /* Wait for a response, or until time to retry */ - if (poll(&pfd, 1, t1 + retry_interval - t2) <= 0) - continue; - - while (1) { - int qn; - int recvlen; - - recvlen = read(pfd.fd, - queries[next_query].reply, - sizeof(queries[next_query].reply) - ); - /* read error */ - if (recvlen < 0) - break; - - /* Ignore non-identifiable packets */ - if (recvlen < 4) - goto poll_more; - - /* Find which query this answer goes with, if any */ - for (qn = next_query; qn < n_queries; qn++) - if (memcmp(queries[next_query].reply, queries[qn].query, 2) == 0) - break; - - if (qn >= n_queries || queries[qn].rlen) - goto poll_more; + /* Wait for a response, or until time to retry */ + if (poll(&pfd, 1, retry_interval - (tcur - tsent)) <= 0) + goto next; - queries[qn].rcode = queries[next_query].reply[3] & 15; - queries[qn].latency = monotonic_ms() - t0; + recvlen = read(pfd.fd, query[save_idx].reply, sizeof(query[0].reply)); - ns->replies++; + /* Error/non-identifiable packet */ + if (recvlen < 4) { + dbg("read is too short:%d\n", recvlen); + goto next; + } - /* Only accept positive or negative responses; - * retry immediately on server failure, and ignore - * all other codes such as refusal. */ - switch (queries[qn].rcode) { - case 0: - case 3: + /* Find which query this answer goes with, if any */ + qn = save_idx; + for (;;) { + if (memcmp(query[save_idx].reply, query[qn].query, 2) == 0) { + dbg("response matches query %u\n", qn); break; - - case 2: - if (servfail_retry && servfail_retry--) { - ns->failures++; - write(pfd.fd, queries[qn].query, queries[qn].qlen); - } - /* fall through */ - - default: - continue; } + if (++qn >= n_queries) { + dbg("response does not match any query\n"); + goto next; + } + } - /* Store answer */ - n_replies++; + if (query[qn].rlen) { + dbg("dropped duplicate response to query %u\n", qn); + goto next; + } - queries[qn].rlen = recvlen; + ns->replies++; - if (qn == next_query) { - while (next_query < n_queries) { - if (!queries[next_query].rlen) - break; - next_query++; - } - } else { - memcpy(queries[qn].reply, queries[next_query].reply, recvlen); + query[qn].rcode = query[save_idx].reply[3] & 15; + dbg("query %u rcode:%s\n", qn, rcodes[query[qn].rcode]); + + /* Only accept positive or negative responses; + * retry immediately on server failure, and ignore + * all other codes such as refusal. + */ + switch (query[qn].rcode) { + case 0: + case 3: + break; + case 2: + if (servfail_retry) { + servfail_retry--; + ns->failures++; + write(pfd.fd, query[qn].query, query[qn].qlen); + dbg("query %u resent\n", qn); } + /* fall through */ + default: + next: + tcur = monotonic_ms(); + continue; + } - if (next_query >= n_queries) - goto ret; + /* Store answer */ + n_replies++; + query[qn].rlen = recvlen; + tcur = monotonic_ms(); + query[qn].latency = tcur - tstart; + if (qn != save_idx) { + /* "wrong" receive buffer, move to correct one */ + memcpy(query[qn].reply, query[save_idx].reply, recvlen); + continue; } - } + /* query[0..save_idx] have replies, move to next one, if exists */ + for (;;) { + save_idx++; + if (save_idx >= n_queries) + goto ret; /* all are full: we have all results */ + if (!query[save_idx].rlen) + break; /* this one is empty */ + } + } /* while() */ + ret: - if (pfd.fd >= 0) - close(pfd.fd); + close(pfd.fd); return n_replies; } @@ -791,12 +795,13 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) n_queries = 0; queries = NULL; do { - /* No explicit type given, guess query type. - * If we can convert the domain argument into a ptr (means that - * inet_pton() could read it) we assume a PTR request, else - * we issue A+AAAA queries and switch to an output format - * mimicking the one of the traditional nslookup applet. */ if (types == 0) { + /* No explicit type given, guess query type. + * If we can convert the domain argument into a ptr (means that + * inet_pton() could read it) we assume a PTR request, else + * we issue A+AAAA queries and switch to an output format + * mimicking the one of the traditional nslookup applet. + */ char *ptr; char buf80[80]; -- cgit v1.2.3-55-g6feb From cf950cd3eae0c8d1f711163ea1d99d5891f4f392 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 23:08:23 +0200 Subject: nslookup: more closely resemble output format of bind-utils-9.11.3 function old new delta nslookup_main 1880 1832 -48 parse_reply 1022 852 -170 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-218) Total: -218 bytes Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 52 +++++++++++++++++---------------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 23ab97585..feeec15aa 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -326,7 +326,7 @@ struct globals { } while (0) static int -parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) +parse_reply(const unsigned char *msg, size_t len) { ns_msg handle; ns_rr rr; @@ -346,36 +346,28 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) return -1; } - if (bb_style_counter && *bb_style_counter == 1) - printf("Name: %s\n", ns_rr_name(rr)); - rdlen = ns_rr_rdlen(rr); switch (ns_rr_type(rr)) { case ns_t_a: if (rdlen != 4) { - //fprintf(stderr, "Unexpected A record length\n"); + dbg("unexpected A record length %d\n", rdlen); return -1; } inet_ntop(AF_INET, ns_rr_rdata(rr), astr, sizeof(astr)); - if (bb_style_counter) - printf("Address %d: %s\n", (*bb_style_counter)++, astr); - else - printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); + printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); break; #if ENABLE_FEATURE_IPV6 case ns_t_aaaa: if (rdlen != 16) { - //fprintf(stderr, "Unexpected AAAA record length\n"); + dbg("unexpected AAAA record length %d\n", rdlen); return -1; } inet_ntop(AF_INET6, ns_rr_rdata(rr), astr, sizeof(astr)); - if (bb_style_counter) - printf("Address %d: %s\n", (*bb_style_counter)++, astr); - else - printf("%s\thas AAAA address %s\n", ns_rr_name(rr), astr); + /* bind-utils-9.11.3 uses the same format for A and AAAA answers */ + printf("Name:\t%s\nAddress: %s\n", ns_rr_name(rr), astr); break; #endif @@ -393,7 +385,8 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) if (!format) format = "%s\tname = %s\n"; if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), - ns_rr_rdata(rr), dname, sizeof(dname)) < 0) { + ns_rr_rdata(rr), dname, sizeof(dname)) < 0 + ) { //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); return -1; } @@ -407,7 +400,8 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) } n = ns_get16(ns_rr_rdata(rr)); if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), - ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0) { + ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0 + ) { //fprintf(stderr, "Cannot uncompress MX domain: %s\n", strerror(errno)); return -1; } @@ -429,7 +423,7 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) case ns_t_soa: if (rdlen < 20) { - //fprintf(stderr, "SOA record too short\n"); + dbg("SOA record too short:%d\n", rdlen); return -1; } @@ -438,7 +432,6 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) cp = ns_rr_rdata(rr); n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), cp, dname, sizeof(dname)); - if (n < 0) { //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); return -1; @@ -449,7 +442,6 @@ parse_reply(const unsigned char *msg, size_t len, int *bb_style_counter) n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), cp, dname, sizeof(dname)); - if (n < 0) { //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); return -1; @@ -725,7 +717,6 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) struct query *queries; llist_t *type_strings; int n_queries; - int bb_style_counter = 0; unsigned types; int rc; int opts; @@ -811,7 +802,6 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) add_query(&queries, &n_queries, T_PTR, ptr); } else { - bb_style_counter = 1; add_query(&queries, &n_queries, T_A, *argv); #if ENABLE_FEATURE_IPV6 add_query(&queries, &n_queries, T_AAAA, *argv); @@ -868,7 +858,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) int c; if (opts & OPT_stats) { - printf("Query #%d completed in %lums:\n", rc, queries[rc].latency); + printf("Query #%d completed in %ums:\n", rc, queries[rc].latency); } if (queries[rc].rcode != 0) { @@ -878,20 +868,13 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } c = 0; - if (queries[rc].rlen) { HEADER *header; - if (!bb_style_counter) { - header = (HEADER *)queries[rc].reply; - if (!header->aa) - printf("Non-authoritative answer:\n"); - c = parse_reply(queries[rc].reply, queries[rc].rlen, NULL); - } - else { - c = parse_reply(queries[rc].reply, queries[rc].rlen, - &bb_style_counter); - } + header = (HEADER *)queries[rc].reply; + if (!header->aa) + printf("Non-authoritative answer:\n"); + c = parse_reply(queries[rc].reply, queries[rc].rlen); } if (c == 0) @@ -899,8 +882,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) else if (c < 0) printf("*** Can't find %s: Parse error\n", queries[rc].name); - if (!bb_style_counter) - printf("\n"); + bb_putchar('\n'); } if (ENABLE_FEATURE_CLEAN_UP) { -- cgit v1.2.3-55-g6feb From 4e73c0f659738f141583bbf92b3df5346d5fb3c0 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sat, 14 Apr 2018 23:18:34 +0200 Subject: nslookup: fix output corruption for "nslookup 1.2.3.4" function old new delta nslookup_main 1832 1837 +5 Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index feeec15aa..92e07e8b1 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -797,11 +797,9 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) char buf80[80]; ptr = make_ptr(buf80, *argv); - if (ptr) { - add_query(&queries, &n_queries, T_PTR, ptr); - } - else { + add_query(&queries, &n_queries, T_PTR, xstrdup(ptr)); + } else { add_query(&queries, &n_queries, T_A, *argv); #if ENABLE_FEATURE_IPV6 add_query(&queries, &n_queries, T_AAAA, *argv); -- cgit v1.2.3-55-g6feb From 2cf75b3c8113c59db0bf9290bd0fdfc77cd2237b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 10:46:44 +0200 Subject: nslookup: process replies immediately, do not store them function old new delta nslookup_main 1837 2708 +871 parse_reply 852 - -852 ------------------------------------------------------------------------------ (add/remove: 0/1 grow/shrink: 1/0 up/down: 871/-852) Total: 19 bytes Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 196 ++++++++++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 86 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 92e07e8b1..d31801e96 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -264,9 +264,10 @@ struct ns { struct query { const char *name; unsigned qlen, rlen; - unsigned latency; - uint8_t rcode; - unsigned char query[512], reply[512]; +// unsigned latency; +// uint8_t rcode; + unsigned char query[512]; +// unsigned char reply[512]; }; static const struct { @@ -325,9 +326,14 @@ struct globals { G.default_timeout = 5; \ } while (0) -static int -parse_reply(const unsigned char *msg, size_t len) +enum { + OPT_stats = (1 << 4), +}; + +static int parse_reply(const unsigned char *msg, size_t len) { + HEADER *header; + ns_msg handle; ns_rr rr; int i, n, rdlen; @@ -335,14 +341,18 @@ parse_reply(const unsigned char *msg, size_t len) char astr[INET6_ADDRSTRLEN], dname[MAXDNAME]; const unsigned char *cp; + header = (HEADER *)msg; + if (!header->aa) + printf("Non-authoritative answer:\n"); + if (ns_initparse(msg, len, &handle) != 0) { - //fprintf(stderr, "Unable to parse reply: %s\n", strerror(errno)); + //printf("Unable to parse reply: %s\n", strerror(errno)); return -1; } for (i = 0; i < ns_msg_count(handle, ns_s_an); i++) { if (ns_parserr(&handle, ns_s_an, i, &rr) != 0) { - //fprintf(stderr, "Unable to parse resource record: %s\n", strerror(errno)); + //printf("Unable to parse resource record: %s\n", strerror(errno)); return -1; } @@ -387,7 +397,7 @@ parse_reply(const unsigned char *msg, size_t len) if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), ns_rr_rdata(rr), dname, sizeof(dname)) < 0 ) { - //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + //printf("Unable to uncompress domain: %s\n", strerror(errno)); return -1; } printf(format, ns_rr_name(rr), dname); @@ -395,14 +405,14 @@ parse_reply(const unsigned char *msg, size_t len) case ns_t_mx: if (rdlen < 2) { - fprintf(stderr, "MX record too short\n"); + printf("MX record too short\n"); return -1; } n = ns_get16(ns_rr_rdata(rr)); if (ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), ns_rr_rdata(rr) + 2, dname, sizeof(dname)) < 0 ) { - //fprintf(stderr, "Cannot uncompress MX domain: %s\n", strerror(errno)); + //printf("Cannot uncompress MX domain: %s\n", strerror(errno)); return -1; } printf("%s\tmail exchanger = %d %s\n", ns_rr_name(rr), n, dname); @@ -410,7 +420,7 @@ parse_reply(const unsigned char *msg, size_t len) case ns_t_txt: if (rdlen < 1) { - //fprintf(stderr, "TXT record too short\n"); + //printf("TXT record too short\n"); return -1; } n = *(unsigned char *)ns_rr_rdata(rr); @@ -433,7 +443,7 @@ parse_reply(const unsigned char *msg, size_t len) n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), cp, dname, sizeof(dname)); if (n < 0) { - //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + //printf("Unable to uncompress domain: %s\n", strerror(errno)); return -1; } @@ -443,7 +453,7 @@ parse_reply(const unsigned char *msg, size_t len) n = ns_name_uncompress(ns_msg_base(handle), ns_msg_end(handle), cp, dname, sizeof(dname)); if (n < 0) { - //fprintf(stderr, "Unable to uncompress domain: %s\n", strerror(errno)); + //printf("Unable to uncompress domain: %s\n", strerror(errno)); return -1; } @@ -513,11 +523,13 @@ static char *make_ptr(char resbuf[80], const char *addrstr) */ static int send_queries(struct ns *ns, struct query *query, int n_queries) { + unsigned char reply[512]; + uint8_t rcode; len_and_sockaddr *local_lsa; struct pollfd pfd; int servfail_retry = 0; int n_replies = 0; - int save_idx = 0; +// int save_idx = 0; unsigned retry_interval; unsigned timeout = G.default_timeout * 1000; unsigned tstart, tsent, tcur; @@ -564,18 +576,34 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) if (poll(&pfd, 1, retry_interval - (tcur - tsent)) <= 0) goto next; - recvlen = read(pfd.fd, query[save_idx].reply, sizeof(query[0].reply)); + recvlen = read(pfd.fd, reply, sizeof(reply)); + if (recvlen < 0) { + bb_perror_msg("read"); + next: + tcur = monotonic_ms(); + continue; + } - /* Error/non-identifiable packet */ + if (ns->replies++ == 0) { + printf("Server:\t\t%s\n", ns->name); + printf("Address:\t%s\n\n", + auto_string(xmalloc_sockaddr2dotted(&ns->lsa->u.sa)) + ); + /* In "Address", bind-utils-9.11.3 show port after a hash: "1.2.3.4#53" */ + /* Should we do the same? */ + } + + /* Non-identifiable packet */ if (recvlen < 4) { dbg("read is too short:%d\n", recvlen); goto next; } /* Find which query this answer goes with, if any */ - qn = save_idx; +// qn = save_idx; + qn = 0; for (;;) { - if (memcmp(query[save_idx].reply, query[qn].query, 2) == 0) { + if (memcmp(reply, query[qn].query, 2) == 0) { dbg("response matches query %u\n", qn); break; } @@ -590,38 +618,42 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) goto next; } - ns->replies++; - - query[qn].rcode = query[save_idx].reply[3] & 15; - dbg("query %u rcode:%s\n", qn, rcodes[query[qn].rcode]); + rcode = reply[3] & 0x0f; + dbg("query %u rcode:%s\n", qn, rcodes[rcode]); - /* Only accept positive or negative responses; - * retry immediately on server failure, and ignore - * all other codes such as refusal. - */ - switch (query[qn].rcode) { - case 0: - case 3: - break; - case 2: + /* Retry immediately on SERVFAIL */ + if (rcode == 2) { + ns->failures++; if (servfail_retry) { servfail_retry--; - ns->failures++; write(pfd.fd, query[qn].query, query[qn].qlen); dbg("query %u resent\n", qn); + goto next; } - /* fall through */ - default: - next: - tcur = monotonic_ms(); - continue; } - /* Store answer */ - n_replies++; + /* Process reply */ query[qn].rlen = recvlen; tcur = monotonic_ms(); +#if 1 + if (option_mask32 & OPT_stats) { + printf("Query #%d completed in %ums:\n", qn, tcur - tstart); + } + if (rcode != 0) { + printf("** server can't find %s: %s\n", + query[qn].name, rcodes[rcode]); + } else { + if (parse_reply(reply, recvlen) < 0) + printf("*** Can't find %s: Parse error\n", query[qn].name); + } + bb_putchar('\n'); + n_replies++; + if (n_replies >= n_queries) + goto ret; +#else +//used to store replies and process them later query[qn].latency = tcur - tstart; + n_replies++; if (qn != save_idx) { /* "wrong" receive buffer, move to correct one */ memcpy(query[qn].reply, query[save_idx].reply, recvlen); @@ -635,6 +667,7 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) if (!query[save_idx].rlen) break; /* this one is empty */ } +#endif } /* while() */ ret: @@ -718,11 +751,9 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) llist_t *type_strings; int n_queries; unsigned types; - int rc; int opts; - enum { - OPT_stats = (1 << 4), - }; + int rc; + int err; INIT_G(); @@ -827,61 +858,54 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) add_ns("127.0.0.1"); } - for (rc = 0; rc < G.serv_count; rc++) { - int c = send_queries(&G.server[rc], queries, n_queries); - if (c > 0) + for (rc = 0; rc < G.serv_count;) { + int c; + + c = send_queries(&G.server[rc], queries, n_queries); + if (c > 0) { + /* more than zero replies received */ + if (opts & OPT_stats) { + printf("Replies:\t%d\n", G.server[rc].replies); + printf("Failures:\t%d\n\n", G.server[rc].failures); + } break; +//FIXME: we "break" even though some queries may still be not answered, and other servers may know them? + } /* c = 0: timed out waiting for replies */ /* c < 0: error (message already printed) */ rc++; if (rc >= G.serv_count) { - fprintf(stderr, - ";; connection timed out; no servers could be reached\n\n"); +// +// NB: bind-utils-9.11.3 behavior (all to stdout, not stderr): +// +// $ nslookup gmail.com 8.8.8.8 +// ;; connection timed out; no servers could be reached +// +// $ nslookup -s gmail.com 8.8.8.8; echo EXITCODE:$? +// <~10 sec> +// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. +// <~10 sec> +// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. +// <~10 sec> +// ;; connection timed out; no servers could be reached +// ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. +// +// EXITCODE:1 +// $ _ + printf(";; connection timed out; no servers could be reached\n\n"); return EXIT_FAILURE; } } - printf("Server:\t\t%s\n", G.server[rc].name); - printf("Address:\t%s\n", xmalloc_sockaddr2dotted(&G.server[rc].lsa->u.sa)); - /* In "Address", bind-utils-9.11.3 show port after a hash: "1.2.3.4#53" */ - /* Should we do the same? */ - - if (opts & OPT_stats) { - printf("Replies:\t%d\n", G.server[rc].replies); - printf("Failures:\t%d\n", G.server[rc].failures); - } - printf("\n"); - + err = 0; for (rc = 0; rc < n_queries; rc++) { - int c; - - if (opts & OPT_stats) { - printf("Query #%d completed in %ums:\n", rc, queries[rc].latency); - } - - if (queries[rc].rcode != 0) { - printf("** server can't find %s: %s\n", queries[rc].name, - rcodes[queries[rc].rcode]); - continue; - } - - c = 0; - if (queries[rc].rlen) { - HEADER *header; - - header = (HEADER *)queries[rc].reply; - if (!header->aa) - printf("Non-authoritative answer:\n"); - c = parse_reply(queries[rc].reply, queries[rc].rlen); - } - - if (c == 0) + if (queries[rc].rlen == 0) { printf("*** Can't find %s: No answer\n", queries[rc].name); - else if (c < 0) - printf("*** Can't find %s: Parse error\n", queries[rc].name); - - bb_putchar('\n'); + err = 1; + } } + if (err) + bb_putchar('\n'); /* should this affect exicode too? */ if (ENABLE_FEATURE_CLEAN_UP) { free(ns); -- cgit v1.2.3-55-g6feb From a980109c6aa36995434eae4993e77d346d2f227a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 10:52:11 +0200 Subject: nslookup: smaller qtypes[] array function old new delta nslookup_main 2708 2715 +7 qtypes 80 72 -8 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 7/-8) Total: -1 bytes text data bss dec hex filename 926277 555 5740 932572 e3adc busybox_old 926262 555 5740 932557 e3acd busybox_unstripped Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index d31801e96..c6f431347 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -271,8 +271,8 @@ struct query { }; static const struct { - int type; - const char *name; + unsigned char type; + char name[7]; } qtypes[] = { { ns_t_soa, "SOA" }, { ns_t_ns, "NS" }, @@ -285,7 +285,6 @@ static const struct { { ns_t_txt, "TXT" }, { ns_t_ptr, "PTR" }, { ns_t_any, "ANY" }, - { } }; static const char *const rcodes[] = { @@ -803,7 +802,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) ptr = chr + 1; for (c = 0;; c++) { - if (!qtypes[c].name) + if (c == ARRAY_SIZE(qtypes)) bb_error_msg_and_die("invalid query type \"%s\"", ptr); if (strcmp(qtypes[c].name, ptr) == 0) break; @@ -836,13 +835,11 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) add_query(&queries, &n_queries, T_AAAA, *argv); #endif } - } - else { + } else { int c; - for (c = 0; qtypes[c].name; c++) { + for (c = 0; c < ARRAY_SIZE(qtypes); c++) { if (types & (1 << c)) - add_query(&queries, &n_queries, qtypes[c].type, - *argv); + add_query(&queries, &n_queries, qtypes[c].type, *argv); } } argv++; -- cgit v1.2.3-55-g6feb From d4461ef9fb09a48b442c28ddf42314ad146b6e76 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 12:01:46 +0200 Subject: nslookup: rework option parsing function old new delta nslookup_main 2715 2754 +39 packed_usage 32179 32211 +32 add_ns 65 66 +1 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 72/0) Total: 72 bytes text data bss dec hex filename 926262 555 5740 932557 e3acd busybox_old 926239 555 5740 932534 e3ab6 busybox_unstripped Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 204 ++++++++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 91 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index c6f431347..112e8763c 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -6,7 +6,7 @@ //config: help //config: nslookup is a tool to query Internet name servers. //config: -//config:config NSLOOKUP_BIG +//config:config FEATURE_NSLOOKUP_BIG //config: bool "Use internal resolver code instead of libc" //config: depends on NSLOOKUP //config: default y @@ -14,17 +14,16 @@ //config:config FEATURE_NSLOOKUP_LONG_OPTIONS //config: bool "Enable long options" //config: default y -//config: depends on NSLOOKUP_BIG && LONG_OPTS +//config: depends on FEATURE_NSLOOKUP_BIG && LONG_OPTS //applet:IF_NSLOOKUP(APPLET(nslookup, BB_DIR_USR_BIN, BB_SUID_DROP)) //kbuild:lib-$(CONFIG_NSLOOKUP) += nslookup.o //usage:#define nslookup_trivial_usage -//usage: "[HOST] [SERVER]" +//usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] ") "HOST [DNS_SERVER]" //usage:#define nslookup_full_usage "\n\n" -//usage: "Query the nameserver for the IP address of the given HOST\n" -//usage: "optionally using a specified DNS server" +//usage: "Query DNS about HOST" //usage: //usage:#define nslookup_example_usage //usage: "$ nslookup localhost\n" @@ -42,7 +41,7 @@ #include "common_bufsiz.h" -#if !ENABLE_NSLOOKUP_BIG +#if !ENABLE_FEATURE_NSLOOKUP_BIG /* * Mini nslookup implementation for busybox @@ -326,7 +325,7 @@ struct globals { } while (0) enum { - OPT_stats = (1 << 4), + OPT_stats = (1 << 0), }; static int parse_reply(const unsigned char *msg, size_t len) @@ -687,7 +686,7 @@ static void add_ns(const char *addr) G.server = xrealloc_vector(G.server, /*8=2^3:*/ 3, count); ns = &G.server[count]; ns->name = addr; - ns->lsa = xhost2sockaddr(addr, 53); + ns->lsa = xhost2sockaddr(addr, G.default_port); /*ns->replies = 0; - already is */ /*ns->failures = 0; - already is */ } @@ -747,107 +746,130 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) { struct ns *ns; struct query *queries; - llist_t *type_strings; int n_queries; unsigned types; - int opts; int rc; int err; INIT_G(); - type_strings = NULL; -#if ENABLE_FEATURE_NSLOOKUP_LONG_OPTIONS - opts = getopt32long(argv, "^" - "+" /* '+': stop at first non-option (why?) */ - "q:*p:+r:+t:+s" - "\0" - "-1:q::", /* minimum 1 arg, -q is a list */ - "type\0" Required_argument "q" - "querytype\0" Required_argument "q" - "port\0" Required_argument "p" - "retry\0" Required_argument "r" - "timeout\0" Required_argument "t" - "stats\0" No_argument "s", - &type_strings, &G.default_port, - &G.default_retry, &G.default_timeout - ); -#else - opts = getopt32(argv, "^" - "+" /* '+': stop at first non-option (why?) */ - "q:*p:+r:+t:+s" - "\0" - "-1:q::", /* minimum 1 arg, -q is a list */ - &type_strings, &G.default_port, - &G.default_retry, &G.default_timeout - ); -#endif - if (G.default_port > 65535) - bb_error_msg_and_die("invalid server port"); - if (G.default_retry == 0) - bb_error_msg_and_die("invalid retry value"); - if (G.default_timeout == 0) - bb_error_msg_and_die("invalid timeout value"); - + /* manpage: "Options can also be specified on the command line + * if they precede the arguments and are prefixed with a hyphen." + */ types = 0; - while (type_strings) { - int c; - char *ptr, *chr; - - ptr = llist_pop(&type_strings); - - /* skip leading text, e.g. when invoked with -querytype=AAAA */ - chr = strchr(ptr, '='); - if (chr) - ptr = chr + 1; + argv++; + for (;;) { + const char *options = +// bind-utils-9.11.3 accept these: +// class= cl= +// type= ty= querytype= query= qu= q= +// domain= do= +// port= po= +// timeout= t= +// retry= ret= +// ndots= +// recurse +// norecurse +// defname +// nodefname +// vc +// novc +// debug +// nodebug +// d2 +// nod2 +// search +// nosearch +// sil +// fail +// nofail +// ver (prints version and exits) + "type\0" /* 0 */ + "querytype\0" /* 1 */ + "port\0" /* 2 */ + "retry\0" /* 3 */ + "stats\0" /* 4 */ + "t\0" /* disambiguate with "type": else -t=2 fails */ + "timeout\0" /* 6 */ + ""; + int i; + char *arg; + char *val; + + if (!*argv) + bb_show_usage(); + if (argv[0][0] != '-') + break; - for (c = 0;; c++) { - if (c == ARRAY_SIZE(qtypes)) - bb_error_msg_and_die("invalid query type \"%s\"", ptr); - if (strcmp(qtypes[c].name, ptr) == 0) - break; + /* Separate out "=val" part */ + arg = (*argv++) + 1; + val = strchrnul(arg, '='); + if (*val) + *val++ = '\0'; + + i = index_in_substrings(options, arg); + //bb_error_msg("i:%d arg:'%s' val:'%s'", i, arg, val); + if (i < 0) + bb_show_usage(); + + if (i <= 1) { + for (i = 0;; i++) { + if (i == ARRAY_SIZE(qtypes)) + bb_error_msg_and_die("invalid query type \"%s\"", val); + if (strcmp(qtypes[i].name, val) == 0) + break; + } + types |= (1 << i); + continue; + } + if (i == 2) { + G.default_port = xatou_range(val, 1, 0xffff); + } + if (i == 3) { + G.default_retry = xatou_range(val, 1, INT_MAX); + } + if (i == 4) { + option_mask32 |= OPT_stats; + } + if (i > 4) { + G.default_timeout = xatou_range(val, 1, INT_MAX / 1000); } - - types |= (1 << c); } - argv += optind; - n_queries = 0; queries = NULL; - do { - if (types == 0) { - /* No explicit type given, guess query type. - * If we can convert the domain argument into a ptr (means that - * inet_pton() could read it) we assume a PTR request, else - * we issue A+AAAA queries and switch to an output format - * mimicking the one of the traditional nslookup applet. - */ - char *ptr; - char buf80[80]; - - ptr = make_ptr(buf80, *argv); - if (ptr) { - add_query(&queries, &n_queries, T_PTR, xstrdup(ptr)); - } else { - add_query(&queries, &n_queries, T_A, *argv); + if (types == 0) { + /* No explicit type given, guess query type. + * If we can convert the domain argument into a ptr (means that + * inet_pton() could read it) we assume a PTR request, else + * we issue A+AAAA queries and switch to an output format + * mimicking the one of the traditional nslookup applet. + */ + char *ptr; + char buf80[80]; + + ptr = make_ptr(buf80, argv[0]); + if (ptr) { + add_query(&queries, &n_queries, T_PTR, xstrdup(ptr)); + } else { + add_query(&queries, &n_queries, T_A, argv[0]); #if ENABLE_FEATURE_IPV6 - add_query(&queries, &n_queries, T_AAAA, *argv); + add_query(&queries, &n_queries, T_AAAA, argv[0]); #endif - } - } else { - int c; - for (c = 0; c < ARRAY_SIZE(qtypes); c++) { - if (types & (1 << c)) - add_query(&queries, &n_queries, qtypes[c].type, *argv); - } } - argv++; - } while (argv[0] && argv[1]); + } else { + int c; + for (c = 0; c < ARRAY_SIZE(qtypes); c++) { + if (types & (1 << c)) + add_query(&queries, &n_queries, qtypes[c].type, argv[0]); + } + } /* Use given DNS server if present */ - if (argv[0]) { - add_ns(argv[0]); + if (argv[1]) { + if (argv[2]) + bb_show_usage(); + add_ns(argv[1]); } else { parse_resolvconf(); /* Fall back to localhost if we could not find NS in resolv.conf */ @@ -861,7 +883,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) c = send_queries(&G.server[rc], queries, n_queries); if (c > 0) { /* more than zero replies received */ - if (opts & OPT_stats) { + if (option_mask32 & OPT_stats) { printf("Replies:\t%d\n", G.server[rc].replies); printf("Failures:\t%d\n\n", G.server[rc].failures); } -- cgit v1.2.3-55-g6feb From 296381ff4f69715ed880adcce7d5ce608153e767 Mon Sep 17 00:00:00 2001 From: "Yann E. MORIN" Date: Sun, 15 Apr 2018 10:55:30 +0200 Subject: applets/install: don't try to install nothing Commit 952d5a6024e7 (applets/install: accept more than one install option) changed the way we handle install options: before that commit, a missing install type would mean to install nothing; after, we would iterate over options, so we would never notice there was a mising option. Fix that by introducing an explicit --none option to specify to install nothing. Reported-by: Aaro Koskinen Cc: Aaro Koskinen Cc: Denys Vlasenko Signed-off-by: Yann E. MORIN Signed-off-by: Denys Vlasenko --- Makefile.custom | 3 +++ applets/install.sh | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile.custom b/Makefile.custom index 28d0ef7bc..6f679c4e1 100644 --- a/Makefile.custom +++ b/Makefile.custom @@ -11,6 +11,9 @@ busybox.cfg.nosuid: $(srctree)/applets/busybox.mksuid $(objtree)/include/autocon $(Q)-SUID="DROP" $(SHELL) $^ > $@ .PHONY: install +ifeq ($(CONFIG_INSTALL_APPLET_DONT),y) +INSTALL_OPTS:= --none +endif ifeq ($(CONFIG_INSTALL_APPLET_SYMLINKS),y) INSTALL_OPTS:= --symlinks endif diff --git a/applets/install.sh b/applets/install.sh index c75a78e9d..9aede0f53 100755 --- a/applets/install.sh +++ b/applets/install.sh @@ -5,7 +5,9 @@ export LC_CTYPE=POSIX prefix=$1 if [ -z "$prefix" ]; then - echo "usage: applets/install.sh DESTINATION [--symlinks/--hardlinks/--binaries/--scriptwrapper]" + echo "usage: applets/install.sh DESTINATION TYPE [OPTS ...]" + echo " TYPE is one of: --symlinks --hardlinks --binaries --scriptwrapper --none" + echo " OPTS is one or more of: --cleanup --noclobber" exit 1 fi shift # Keep only remaining options @@ -32,7 +34,7 @@ while [ ${#} -gt 0 ]; do --sw-sh-sym) scriptwrapper="y"; linkopts="-fs";; --cleanup) cleanup="1";; --noclobber) noclobber="1";; - "") h="";; + --none) h="";; *) echo "Unknown install option: $1"; exit 1;; esac shift -- cgit v1.2.3-55-g6feb From 6cdc3195a6d174363a813d58b8372f0f5cd546b1 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 12:49:11 +0200 Subject: nslookup: change -stats to -debug (it's a bug in bind that it accepts -s) function old new delta packed_usage 32211 32189 -22 nslookup_main 2754 2692 -62 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-84) Total: -84 bytes Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 112e8763c..63bb46b55 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -21,7 +21,7 @@ //kbuild:lib-$(CONFIG_NSLOOKUP) += nslookup.o //usage:#define nslookup_trivial_usage -//usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] ") "HOST [DNS_SERVER]" +//usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] [-debug] ") "HOST [DNS_SERVER]" //usage:#define nslookup_full_usage "\n\n" //usage: "Query DNS about HOST" //usage: @@ -306,7 +306,7 @@ static const char *const rcodes[] = { }; #if ENABLE_FEATURE_IPV6 -static const char v4_mapped[] = { 0,0,0,0,0,0,0,0,0,0,0xff,0xff }; +static const char v4_mapped[12] = { 0,0,0,0, 0,0,0,0, 0,0,0xff,0xff }; #endif struct globals { @@ -325,7 +325,7 @@ struct globals { } while (0) enum { - OPT_stats = (1 << 0), + OPT_debug = (1 << 0), }; static int parse_reply(const unsigned char *msg, size_t len) @@ -634,7 +634,7 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) query[qn].rlen = recvlen; tcur = monotonic_ms(); #if 1 - if (option_mask32 & OPT_stats) { + if (option_mask32 & OPT_debug) { printf("Query #%d completed in %ums:\n", qn, tcur - tstart); } if (rcode != 0) { @@ -788,7 +788,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) "querytype\0" /* 1 */ "port\0" /* 2 */ "retry\0" /* 3 */ - "stats\0" /* 4 */ + "debug\0" /* 4 */ "t\0" /* disambiguate with "type": else -t=2 fails */ "timeout\0" /* 6 */ ""; @@ -829,7 +829,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) G.default_retry = xatou_range(val, 1, INT_MAX); } if (i == 4) { - option_mask32 |= OPT_stats; + option_mask32 |= OPT_debug; } if (i > 4) { G.default_timeout = xatou_range(val, 1, INT_MAX / 1000); @@ -883,10 +883,12 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) c = send_queries(&G.server[rc], queries, n_queries); if (c > 0) { /* more than zero replies received */ - if (option_mask32 & OPT_stats) { +#if 0 /* which version does this? */ + if (option_mask32 & OPT_debug) { printf("Replies:\t%d\n", G.server[rc].replies); printf("Failures:\t%d\n\n", G.server[rc].failures); } +#endif break; //FIXME: we "break" even though some queries may still be not answered, and other servers may know them? } @@ -900,7 +902,8 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) // $ nslookup gmail.com 8.8.8.8 // ;; connection timed out; no servers could be reached // -// $ nslookup -s gmail.com 8.8.8.8; echo EXITCODE:$? +// Using TCP mode: +// $ nslookup -vc gmail.com 8.8.8.8; echo EXITCODE:$? // <~10 sec> // ;; Connection to 8.8.8.8#53(8.8.8.8) for gmail.com failed: timed out. // <~10 sec> -- cgit v1.2.3-55-g6feb From 4b6091f92c1c5abfe4597af4f3f6fa266ac46a5a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 12:57:32 +0200 Subject: nslookup: accept lowercase -type=soa, document query types Usage: nslookup [-type=QUERY_TYPE] [-debug] HOST [DNS_SERVER] Query DNS about HOST QUERY_TYPE: soa,ns,a,aaaa,cname,mx,txt,ptr,any function old new delta packed_usage 32189 32258 +69 Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 63bb46b55..88a638a79 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -24,7 +24,8 @@ //usage: IF_FEATURE_NSLOOKUP_BIG("[-type=QUERY_TYPE] [-debug] ") "HOST [DNS_SERVER]" //usage:#define nslookup_full_usage "\n\n" //usage: "Query DNS about HOST" -//usage: +//usage: IF_FEATURE_NSLOOKUP_BIG("\n") +//usage: IF_FEATURE_NSLOOKUP_BIG("\nQUERY_TYPE: soa,ns,a,"IF_FEATURE_IPV6("aaaa,")"cname,mx,txt,ptr,any") //usage:#define nslookup_example_usage //usage: "$ nslookup localhost\n" //usage: "Server: default\n" @@ -816,7 +817,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) for (i = 0;; i++) { if (i == ARRAY_SIZE(qtypes)) bb_error_msg_and_die("invalid query type \"%s\"", val); - if (strcmp(qtypes[i].name, val) == 0) + if (strcasecmp(qtypes[i].name, val) == 0) break; } types |= (1 << i); -- cgit v1.2.3-55-g6feb From 36941503bd8e28a1fac13edffc3ffc84346b32c5 Mon Sep 17 00:00:00 2001 From: Aaro Koskinen Date: Sun, 15 Apr 2018 01:24:24 +0300 Subject: less: implement -F Implement -F option: Exit if entire file fits on first screen. function old new delta buffer_print 622 633 +11 less_main 2446 2449 +3 buffer_fill_and_print 169 172 +3 packed_usage 32258 32236 -22 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/1 up/down: 17/-22) Total: -5 bytes Signed-off-by: Aaro Koskinen Signed-off-by: Denys Vlasenko --- miscutils/less.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/miscutils/less.c b/miscutils/less.c index 51ef2a59d..3bce93247 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -121,11 +121,12 @@ //kbuild:lib-$(CONFIG_LESS) += less.o //usage:#define less_trivial_usage -//usage: "[-E" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") +//usage: "[-EF" IF_FEATURE_LESS_REGEXP("I")IF_FEATURE_LESS_FLAGS("Mm") //usage: "N" IF_FEATURE_LESS_TRUNCATE("S") IF_FEATURE_LESS_RAW("R") "h~] [FILE]..." //usage:#define less_full_usage "\n\n" //usage: "View FILE (or stdin) one screenful at a time\n" //usage: "\n -E Quit once the end of a file is reached" +//usage: "\n -F Quit if entire file fits on first screen" //usage: IF_FEATURE_LESS_REGEXP( //usage: "\n -I Ignore case in all searches" //usage: ) @@ -175,8 +176,9 @@ enum { FLAG_N = 1 << 3, FLAG_TILDE = 1 << 4, FLAG_I = 1 << 5, - FLAG_S = (1 << 6) * ENABLE_FEATURE_LESS_TRUNCATE, - FLAG_R = (1 << 7) * ENABLE_FEATURE_LESS_RAW, + FLAG_F = 1 << 6, + FLAG_S = (1 << 7) * ENABLE_FEATURE_LESS_TRUNCATE, + FLAG_R = (1 << 8) * ENABLE_FEATURE_LESS_RAW, /* hijack command line options variable for internal state vars */ LESS_STATE_MATCH_BACKWARDS = 1 << 15, }; @@ -906,11 +908,12 @@ static void buffer_print(void) else print_ascii(buffer[i]); } - if ((option_mask32 & FLAG_E) + if ((option_mask32 & (FLAG_E|FLAG_F)) && eof_error <= 0 - && (max_fline - cur_fline) <= max_displayed_line ) { - less_exit(EXIT_SUCCESS); + i = option_mask32 & FLAG_F ? 0 : cur_fline; + if (max_fline - i <= max_displayed_line) + less_exit(EXIT_SUCCESS); } status_print(); } @@ -1814,7 +1817,7 @@ int less_main(int argc, char **argv) * -s: condense many empty lines to one * (used by some setups for manpage display) */ - getopt32(argv, "EMmN~I" + getopt32(argv, "EMmN~IF" IF_FEATURE_LESS_TRUNCATE("S") IF_FEATURE_LESS_RAW("R") /*ignored:*/"s" @@ -1828,6 +1831,9 @@ int less_main(int argc, char **argv) if (ENABLE_FEATURE_LESS_ENV) { char *c = getenv("LESS"); if (c) while (*c) switch (*c++) { + case 'F': + option_mask32 |= FLAG_F; + break; case 'M': option_mask32 |= FLAG_M; break; -- cgit v1.2.3-55-g6feb From 50aea2786b275c1f1d5f1b2df85b519dbaef6188 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 13:14:51 +0200 Subject: less: remove unnecessary message Signed-off-by: Denys Vlasenko --- miscutils/less.c | 1 - 1 file changed, 1 deletion(-) diff --git a/miscutils/less.c b/miscutils/less.c index 3bce93247..6029b6809 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -1856,7 +1856,6 @@ int less_main(int argc, char **argv) if (!num_files) { if (isatty(STDIN_FILENO)) { /* Just "less"? No args and no redirection? */ - bb_error_msg("missing filename"); bb_show_usage(); } } else { -- cgit v1.2.3-55-g6feb From 58e43a4c40f56a463a475bec5ef915612fc5ed8c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 14:10:45 +0200 Subject: nslookup: move array of queries to "globals" function old new delta add_query 95 89 -6 nslookup_main 2692 2641 -51 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/2 up/down: 0/-57) Total: -57 bytes Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 86 +++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 48 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 88a638a79..d1697f2fd 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -314,8 +314,10 @@ struct globals { unsigned default_port; unsigned default_retry; unsigned default_timeout; + unsigned query_count; unsigned serv_count; struct ns *server; + struct query *query; } FIX_ALIASING; #define G (*(struct globals*)bb_common_bufsiz1) #define INIT_G() do { \ @@ -518,9 +520,9 @@ static char *make_ptr(char resbuf[80], const char *addrstr) /* * Function logic borrowed & modified from musl libc, res_msend.c - * n_queries is always > 0. + * G.query_count is always > 0. */ -static int send_queries(struct ns *ns, struct query *query, int n_queries) +static int send_queries(struct ns *ns) { unsigned char reply[512]; uint8_t rcode; @@ -557,10 +559,10 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) if (tcur - tsent >= retry_interval) { send: - for (qn = 0; qn < n_queries; qn++) { - if (query[qn].rlen) + for (qn = 0; qn < G.query_count; qn++) { + if (G.query[qn].rlen) continue; - if (write(pfd.fd, query[qn].query, query[qn].qlen) < 0) { + if (write(pfd.fd, G.query[qn].query, G.query[qn].qlen) < 0) { bb_perror_msg("write to '%s'", ns->name); n_replies = -1; /* "no go, try next server" */ goto ret; @@ -568,7 +570,7 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) dbg("query %u sent\n", qn); } tsent = tcur; - servfail_retry = 2 * n_queries; + servfail_retry = 2 * G.query_count; } /* Wait for a response, or until time to retry */ @@ -602,17 +604,17 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) // qn = save_idx; qn = 0; for (;;) { - if (memcmp(reply, query[qn].query, 2) == 0) { + if (memcmp(reply, G.query[qn].query, 2) == 0) { dbg("response matches query %u\n", qn); break; } - if (++qn >= n_queries) { + if (++qn >= G.query_count) { dbg("response does not match any query\n"); goto next; } } - if (query[qn].rlen) { + if (G.query[qn].rlen) { dbg("dropped duplicate response to query %u\n", qn); goto next; } @@ -625,14 +627,14 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) ns->failures++; if (servfail_retry) { servfail_retry--; - write(pfd.fd, query[qn].query, query[qn].qlen); + write(pfd.fd, G.query[qn].query, G.query[qn].qlen); dbg("query %u resent\n", qn); goto next; } } /* Process reply */ - query[qn].rlen = recvlen; + G.query[qn].rlen = recvlen; tcur = monotonic_ms(); #if 1 if (option_mask32 & OPT_debug) { @@ -640,30 +642,30 @@ static int send_queries(struct ns *ns, struct query *query, int n_queries) } if (rcode != 0) { printf("** server can't find %s: %s\n", - query[qn].name, rcodes[rcode]); + G.query[qn].name, rcodes[rcode]); } else { if (parse_reply(reply, recvlen) < 0) - printf("*** Can't find %s: Parse error\n", query[qn].name); + printf("*** Can't find %s: Parse error\n", G.query[qn].name); } bb_putchar('\n'); n_replies++; - if (n_replies >= n_queries) + if (n_replies >= G.query_count) goto ret; #else //used to store replies and process them later - query[qn].latency = tcur - tstart; + G.query[qn].latency = tcur - tstart; n_replies++; if (qn != save_idx) { /* "wrong" receive buffer, move to correct one */ - memcpy(query[qn].reply, query[save_idx].reply, recvlen); + memcpy(G.query[qn].reply, G.query[save_idx].reply, recvlen); continue; } - /* query[0..save_idx] have replies, move to next one, if exists */ + /* G.query[0..save_idx] have replies, move to next one, if exists */ for (;;) { save_idx++; - if (save_idx >= n_queries) + if (save_idx >= G.query_count) goto ret; /* all are full: we have all results */ - if (!query[save_idx].rlen) + if (!G.query[save_idx].rlen) break; /* this one is empty */ } #endif @@ -718,36 +720,27 @@ static void parse_resolvconf(void) } } -static struct query *add_query(struct query **queries, int *n_queries, - int type, const char *dname) +static void add_query(int type, const char *dname) { struct query *new_q; - int pos; + unsigned count; ssize_t qlen; - pos = *n_queries; - *n_queries = pos + 1; - *queries = new_q = xrealloc(*queries, sizeof(**queries) * (pos + 1)); + count = G.query_count++; - dbg("new query#%u type %u for '%s'\n", pos, type, dname); - new_q += pos; - memset(new_q, 0, sizeof(*new_q)); + G.query = xrealloc_vector(G.query, /*2=2^1:*/ 1, count); + new_q = &G.query[count]; + dbg("new query#%u type %u for '%s'\n", count, type, dname); + new_q->name = dname; qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, new_q->query, sizeof(new_q->query)); - new_q->qlen = qlen; - new_q->name = dname; - - return new_q; } int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int nslookup_main(int argc UNUSED_PARAM, char **argv) { - struct ns *ns; - struct query *queries; - int n_queries; unsigned types; int rc; int err; @@ -837,8 +830,6 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } } - n_queries = 0; - queries = NULL; if (types == 0) { /* No explicit type given, guess query type. * If we can convert the domain argument into a ptr (means that @@ -851,18 +842,18 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) ptr = make_ptr(buf80, argv[0]); if (ptr) { - add_query(&queries, &n_queries, T_PTR, xstrdup(ptr)); + add_query(T_PTR, xstrdup(ptr)); } else { - add_query(&queries, &n_queries, T_A, argv[0]); + add_query(T_A, argv[0]); #if ENABLE_FEATURE_IPV6 - add_query(&queries, &n_queries, T_AAAA, argv[0]); + add_query(T_AAAA, argv[0]); #endif } } else { int c; for (c = 0; c < ARRAY_SIZE(qtypes); c++) { if (types & (1 << c)) - add_query(&queries, &n_queries, qtypes[c].type, argv[0]); + add_query(qtypes[c].type, argv[0]); } } @@ -881,7 +872,7 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) for (rc = 0; rc < G.serv_count;) { int c; - c = send_queries(&G.server[rc], queries, n_queries); + c = send_queries(&G.server[rc]); if (c > 0) { /* more than zero replies received */ #if 0 /* which version does this? */ @@ -921,9 +912,9 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) } err = 0; - for (rc = 0; rc < n_queries; rc++) { - if (queries[rc].rlen == 0) { - printf("*** Can't find %s: No answer\n", queries[rc].name); + for (rc = 0; rc < G.query_count; rc++) { + if (G.query[rc].rlen == 0) { + printf("*** Can't find %s: No answer\n", G.query[rc].name); err = 1; } } @@ -931,9 +922,8 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) bb_putchar('\n'); /* should this affect exicode too? */ if (ENABLE_FEATURE_CLEAN_UP) { - free(ns); - if (n_queries) - free(queries); + free(G.server); + free(G.query); } return EXIT_SUCCESS; -- cgit v1.2.3-55-g6feb From b74e490629a75cb8c2cb798f0feadc2eb4dc5471 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 14:18:16 +0200 Subject: ar: stop using static data function old new delta static.ar_long_names 4 - -4 static.ar_long_name_size 4 - -4 get_header_ar 546 532 -14 ------------------------------------------------------------------------------ (add/remove: 0/2 grow/shrink: 0/1 up/down: 0/-22) Total: -22 bytes Signed-off-by: Denys Vlasenko --- archival/libarchive/get_header_ar.c | 18 +++++++----------- include/bb_archive.h | 4 ++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/archival/libarchive/get_header_ar.c b/archival/libarchive/get_header_ar.c index a97970630..adcde46d5 100644 --- a/archival/libarchive/get_header_ar.c +++ b/archival/libarchive/get_header_ar.c @@ -34,10 +34,6 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) char raw[60]; struct ar_header formatted; } ar; -#if ENABLE_FEATURE_AR_LONG_FILENAMES - static char *ar_long_names; - static unsigned ar_long_name_size; -#endif /* dont use xread as we want to handle the error ourself */ if (read(archive_handle->src_fd, ar.raw, 60) != 60) { @@ -81,10 +77,10 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) * stores long filename for multiple entries, they are stored * in static variable long_names for use in future entries */ - ar_long_name_size = size; - free(ar_long_names); - ar_long_names = xzalloc(size + 1); - xread(archive_handle->src_fd, ar_long_names, size); + archive_handle->ar__long_name_size = size; + free(archive_handle->ar__long_names); + archive_handle->ar__long_names = xzalloc(size + 1); + xread(archive_handle->src_fd, archive_handle->ar__long_names, size); archive_handle->offset += size; /* Return next header */ return get_header_ar(archive_handle); @@ -107,13 +103,13 @@ char FAST_FUNC get_header_ar(archive_handle_t *archive_handle) unsigned long_offset; /* The number after the '/' indicates the offset in the ar data section - * (saved in ar_long_names) that contains the real filename */ + * (saved in ar__long_names) that contains the real filename */ long_offset = read_num(&ar.formatted.name[1], 10, sizeof(ar.formatted.name) - 1); - if (long_offset >= ar_long_name_size) { + if (long_offset >= archive_handle->ar__long_name_size) { bb_error_msg_and_die("can't resolve long filename"); } - typed->name = xstrdup(ar_long_names + long_offset); + typed->name = xstrdup(archive_handle->ar__long_names + long_offset); } else #endif { diff --git a/include/bb_archive.h b/include/bb_archive.h index b437f1920..0252488bf 100644 --- a/include/bb_archive.h +++ b/include/bb_archive.h @@ -116,6 +116,10 @@ typedef struct archive_handle_t { #if ENABLE_FEATURE_AR_CREATE const char *ar__name; struct archive_handle_t *ar__out; +# if ENABLE_FEATURE_AR_LONG_FILENAMES + char *ar__long_names; + unsigned ar__long_name_size; +# endif #endif } archive_handle_t; /* bits in ah_flags */ -- cgit v1.2.3-55-g6feb From 71e4b3f4824f94e6e9aae25222c41f8789d80fd6 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 19:51:42 +0200 Subject: nslookup: get rid of query::rlen field Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index d1697f2fd..99f781e1b 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -263,7 +263,7 @@ struct ns { struct query { const char *name; - unsigned qlen, rlen; + unsigned qlen; // unsigned latency; // uint8_t rcode; unsigned char query[512]; @@ -560,8 +560,9 @@ static int send_queries(struct ns *ns) if (tcur - tsent >= retry_interval) { send: for (qn = 0; qn < G.query_count; qn++) { - if (G.query[qn].rlen) - continue; + if (G.query[qn].qlen == 0) + continue; /* this one was replied already */ + if (write(pfd.fd, G.query[qn].query, G.query[qn].qlen) < 0) { bb_perror_msg("write to '%s'", ns->name); n_replies = -1; /* "no go, try next server" */ @@ -614,7 +615,7 @@ static int send_queries(struct ns *ns) } } - if (G.query[qn].rlen) { + if (G.query[qn].qlen == 0) { dbg("dropped duplicate response to query %u\n", qn); goto next; } @@ -634,7 +635,7 @@ static int send_queries(struct ns *ns) } /* Process reply */ - G.query[qn].rlen = recvlen; + G.query[qn].qlen = 0; /* flag: "reply received" */ tcur = monotonic_ms(); #if 1 if (option_mask32 & OPT_debug) { @@ -733,8 +734,12 @@ static void add_query(int type, const char *dname) dbg("new query#%u type %u for '%s'\n", count, type, dname); new_q->name = dname; - qlen = res_mkquery(QUERY, dname, C_IN, type, NULL, 0, NULL, - new_q->query, sizeof(new_q->query)); + + qlen = res_mkquery(QUERY, dname, C_IN, type, + /*data:*/ NULL, /*datalen:*/ 0, + /*newrr:*/ NULL, + new_q->query, sizeof(new_q->query) + ); new_q->qlen = qlen; } @@ -913,13 +918,13 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) err = 0; for (rc = 0; rc < G.query_count; rc++) { - if (G.query[rc].rlen == 0) { + if (G.query[rc].qlen) { printf("*** Can't find %s: No answer\n", G.query[rc].name); err = 1; } } - if (err) - bb_putchar('\n'); /* should this affect exicode too? */ + if (err) /* should this affect exicode too? */ + bb_putchar('\n'); if (ENABLE_FEATURE_CLEAN_UP) { free(G.server); -- cgit v1.2.3-55-g6feb From c72499584abbf32b3757024bb0cd53f23d5d0d72 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 15 Apr 2018 20:04:57 +0200 Subject: nslookup: simplify make_ptr function old new delta nslookup_main 2644 2611 -33 Signed-off-by: Denys Vlasenko --- networking/nslookup.c | 71 ++++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/networking/nslookup.c b/networking/nslookup.c index 99f781e1b..fd241a5ca 100644 --- a/networking/nslookup.c +++ b/networking/nslookup.c @@ -484,40 +484,6 @@ static int parse_reply(const unsigned char *msg, size_t len) return i; } -static char *make_ptr(char resbuf[80], const char *addrstr) -{ - unsigned char addr[16]; - int i; - -#if ENABLE_FEATURE_IPV6 - if (inet_pton(AF_INET6, addrstr, addr)) { - if (memcmp(addr, v4_mapped, 12) != 0) { - char *ptr = resbuf; - for (i = 0; i < 16; i++) { - *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] & 0xf]; - *ptr++ = '.'; - *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] >> 4]; - *ptr++ = '.'; - } - strcpy(ptr, "ip6.arpa"); - } - else { - sprintf(resbuf, "%u.%u.%u.%u.in-addr.arpa", - addr[15], addr[14], addr[13], addr[12]); - } - return resbuf; - } -#endif - - if (inet_pton(AF_INET, addrstr, addr)) { - sprintf(resbuf, "%u.%u.%u.%u.in-addr.arpa", - addr[3], addr[2], addr[1], addr[0]); - return resbuf; - } - - return NULL; -} - /* * Function logic borrowed & modified from musl libc, res_msend.c * G.query_count is always > 0. @@ -743,6 +709,38 @@ static void add_query(int type, const char *dname) new_q->qlen = qlen; } +static char *make_ptr(const char *addrstr) +{ + unsigned char addr[16]; + int i; + +#if ENABLE_FEATURE_IPV6 + if (inet_pton(AF_INET6, addrstr, addr)) { + if (memcmp(addr, v4_mapped, 12) != 0) { + char resbuf[80]; + char *ptr = resbuf; + for (i = 0; i < 16; i++) { + *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] & 0xf]; + *ptr++ = '.'; + *ptr++ = 0x20 | bb_hexdigits_upcase[(unsigned char)addr[15 - i] >> 4]; + *ptr++ = '.'; + } + strcpy(ptr, "ip6.arpa"); + return xstrdup(resbuf); + } + return xasprintf("%u.%u.%u.%u.in-addr.arpa", + addr[15], addr[14], addr[13], addr[12]); + } +#endif + + if (inet_pton(AF_INET, addrstr, addr)) { + return xasprintf("%u.%u.%u.%u.in-addr.arpa", + addr[3], addr[2], addr[1], addr[0]); + } + + return NULL; +} + int nslookup_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int nslookup_main(int argc UNUSED_PARAM, char **argv) { @@ -843,11 +841,10 @@ int nslookup_main(int argc UNUSED_PARAM, char **argv) * mimicking the one of the traditional nslookup applet. */ char *ptr; - char buf80[80]; - ptr = make_ptr(buf80, argv[0]); + ptr = make_ptr(argv[0]); if (ptr) { - add_query(T_PTR, xstrdup(ptr)); + add_query(T_PTR, ptr); } else { add_query(T_A, argv[0]); #if ENABLE_FEATURE_IPV6 -- cgit v1.2.3-55-g6feb From 058a153b6970660f3c284ac259986ae87507df9a Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 16 Apr 2018 10:24:48 +0200 Subject: less: fix fallout from "use common routine to set raw termios" Testcase: (sleep 10; ls) | busybox less [...] ~ LICENSE ~ Makefile ~ Makefile.custom ~ Makefile.flags [...] less did not want this part: + /* dont convert NL to CR+NL on output */ + newterm->c_oflag &= ~(ONLCR); function old new delta get_termios_and_make_raw 108 115 +7 Signed-off-by: Denys Vlasenko --- include/libbb.h | 8 +++++--- libbb/xfuncs.c | 13 +++++++++---- miscutils/less.c | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/include/libbb.h b/include/libbb.h index c7bf33ef8..646c58bf2 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1597,9 +1597,11 @@ int get_terminal_width_height(int fd, unsigned *width, unsigned *height) FAST_FU int get_terminal_width(int fd) FAST_FUNC; int tcsetattr_stdin_TCSANOW(const struct termios *tp) FAST_FUNC; -#define TERMIOS_CLEAR_ISIG (1 << 0) -#define TERMIOS_RAW_CRNL (1 << 1) -#define TERMIOS_RAW_INPUT (1 << 2) +#define TERMIOS_CLEAR_ISIG (1 << 0) +#define TERMIOS_RAW_CRNL_INPUT (1 << 1) +#define TERMIOS_RAW_CRNL_OUTPUT (1 << 2) +#define TERMIOS_RAW_CRNL (TERMIOS_RAW_CRNL_INPUT|TERMIOS_RAW_CRNL_OUTPUT) +#define TERMIOS_RAW_INPUT (1 << 3) int get_termios_and_make_raw(int fd, struct termios *newterm, struct termios *oldterm, int flags) FAST_FUNC; int set_termios_to_raw(int fd, struct termios *oldterm, int flags) FAST_FUNC; diff --git a/libbb/xfuncs.c b/libbb/xfuncs.c index e8c027f17..b4d512bd6 100644 --- a/libbb/xfuncs.c +++ b/libbb/xfuncs.c @@ -330,7 +330,6 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t newterm->c_cc[VMIN] = 1; /* no timeout (reads block forever) */ newterm->c_cc[VTIME] = 0; - if (flags & TERMIOS_RAW_CRNL) { /* IXON, IXOFF, and IXANY: * IXOFF=1: sw flow control is enabled on input queue: * tty transmits a STOP char when input queue is close to full @@ -340,9 +339,12 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t * and resume sending if START is received, or if any char * is received and IXANY=1. */ + if (flags & TERMIOS_RAW_CRNL_INPUT) { /* IXON=0: XON/XOFF chars are treated as normal chars (why we do this?) */ /* dont convert CR to NL on input */ newterm->c_iflag &= ~(IXON | ICRNL); + } + if (flags & TERMIOS_RAW_CRNL_OUTPUT) { /* dont convert NL to CR+NL on output */ newterm->c_oflag &= ~(ONLCR); /* Maybe clear more c_oflag bits? Usually, only OPOST and ONLCR are set. @@ -363,9 +365,12 @@ int FAST_FUNC get_termios_and_make_raw(int fd, struct termios *newterm, struct t #ifndef IXANY # define IXANY 0 #endif - /* IXOFF=0: disable sending XON/XOFF if input buf is full */ - /* IXON=0: input XON/XOFF chars are not special */ - /* dont convert anything on input */ + /* IXOFF=0: disable sending XON/XOFF if input buf is full + * IXON=0: input XON/XOFF chars are not special + * BRKINT=0: dont send SIGINT on break + * IMAXBEL=0: dont echo BEL on input line too long + * INLCR,ICRNL,IUCLC: dont convert anything on input + */ newterm->c_iflag &= ~(IXOFF|IXON|IXANY|BRKINT|INLCR|ICRNL|IUCLC|IMAXBEL); } return r; diff --git a/miscutils/less.c b/miscutils/less.c index 6029b6809..938d9842f 100644 --- a/miscutils/less.c +++ b/miscutils/less.c @@ -1891,7 +1891,7 @@ int less_main(int argc, char **argv) G.kbd_fd_orig_flags = ndelay_on(tty_fd); kbd_fd = tty_fd; /* save in a global */ - get_termios_and_make_raw(tty_fd, &term_less, &term_orig, TERMIOS_RAW_CRNL); + get_termios_and_make_raw(tty_fd, &term_less, &term_orig, TERMIOS_RAW_CRNL_INPUT); IF_FEATURE_LESS_ASK_TERMINAL(G.winsize_err =) get_terminal_width_height(tty_fd, &width, &max_displayed_line); /* 20: two tabstops + 4 */ -- cgit v1.2.3-55-g6feb From fa87abe807530daeb46f6730d4231489a4e2782b Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 16 Apr 2018 11:04:03 +0200 Subject: build system: FEATURE_NSLOOKUP_BIG needs -lresolv Signed-off-by: Denys Vlasenko --- Makefile.flags | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile.flags b/Makefile.flags index f3c897b06..6f6142cc5 100644 --- a/Makefile.flags +++ b/Makefile.flags @@ -156,6 +156,10 @@ CPPFLAGS += $(SELINUX_CFLAGS) LDLIBS += $(if $(SELINUX_LIBS),$(SELINUX_LIBS:-l%=%),$(SELINUX_PC_MODULES:lib%=%)) endif +ifeq ($(CONFIG_FEATURE_NSLOOKUP_BIG),y) +LDLIBS += resolv +endif + ifeq ($(CONFIG_EFENCE),y) LDLIBS += efence endif -- cgit v1.2.3-55-g6feb From 816d8d7a668b541cee99469edb90e4917ea11c3e Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 16 Apr 2018 21:48:32 +0200 Subject: setlogcons: open /dev/ttyN for "setlogcons N", not /dev/tty1 Signed-off-by: Denys Vlasenko --- console-tools/setlogcons.c | 10 +++++++++- examples/var_service/tftpd/run | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/console-tools/setlogcons.c b/console-tools/setlogcons.c index 6778a4d2b..0fad6600a 100644 --- a/console-tools/setlogcons.c +++ b/console-tools/setlogcons.c @@ -41,6 +41,7 @@ int setlogcons_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int setlogcons_main(int argc UNUSED_PARAM, char **argv) { + char *devname; struct { char fn; char subarg; @@ -52,7 +53,14 @@ int setlogcons_main(int argc UNUSED_PARAM, char **argv) if (argv[1]) arg.subarg = xatou_range(argv[1], 0, 63); - xioctl(xopen(VC_1, O_RDONLY), TIOCLINUX, &arg); + /* Can just call it on "/dev/tty1" always, but... + * in my testing, inactive (never opened) VTs are not + * redirected to, despite ioctl not failing. + * + * By using "/dev/ttyN", ensure it is activated. + */ + devname = xasprintf("/dev/tty%u", arg.subarg); + xioctl(xopen(devname, O_RDONLY), TIOCLINUX, &arg); return EXIT_SUCCESS; } diff --git a/examples/var_service/tftpd/run b/examples/var_service/tftpd/run index e492d8453..ceb2be555 100755 --- a/examples/var_service/tftpd/run +++ b/examples/var_service/tftpd/run @@ -7,7 +7,7 @@ exec Date: Tue, 17 Apr 2018 12:43:54 +0200 Subject: libbb: new option FEATURE_ETC_SERVICES: if off, /etc/services reads often avoided In practice, "wget http://host.com/" always uses port 80. People explicitly set non-standard ports via options or parameters ("telnet 1.2.3.4 567" or "telnet 1.2.3.4 ftp") instead of modifying /etc/services. function old new delta telnet_main 1466 1464 -2 rdate_main 215 198 -17 fakeidentd_main 269 252 -17 parse_url 459 392 -67 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 0/4 up/down: 0/-103) Total: -103 bytes Signed-off-by: Denys Vlasenko --- include/libbb.h | 5 +++++ libbb/Config.src | 12 ++++++++++++ networking/isrv_identd.c | 2 +- networking/telnet.c | 3 ++- networking/wget.c | 8 ++++---- util-linux/rdate.c | 2 +- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/include/libbb.h b/include/libbb.h index 646c58bf2..a605c7f03 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -640,6 +640,11 @@ int setsockopt_bindtodevice(int fd, const char *iface) FAST_FUNC; int bb_getsockname(int sockfd, void *addr, socklen_t addrlen) FAST_FUNC; /* NB: returns port in host byte order */ unsigned bb_lookup_port(const char *port, const char *protocol, unsigned default_port) FAST_FUNC; +#if ENABLE_FEATURE_ETC_SERVICES +# define bb_lookup_std_port(portstr, protocol, portnum) bb_lookup_port(portstr, protocol, portnum) +#else +# define bb_lookup_std_port(portstr, protocol, portnum) (portnum) +#endif typedef struct len_and_sockaddr { socklen_t len; union { diff --git a/libbb/Config.src b/libbb/Config.src index fdf8bbb28..16e16480b 100644 --- a/libbb/Config.src +++ b/libbb/Config.src @@ -76,6 +76,18 @@ config FEATURE_ETC_NETWORKS a rarely used feature which allows you to use names instead of IP/mask pairs in route command. +config FEATURE_ETC_SERVICES + bool "Consult /etc/services even for well-known ports" + default n + help + Look up e.g. "telnet" and "http" in /etc/services file + instead of assuming ports 23 and 80. + This is almost never necessary (everybody uses standard ports), + and it makes sense to avoid reading this file. + If you disable this option, in the cases where port is explicitly + specified as a service name (e.g. "telnet HOST PORTNAME"), + it will still be looked up in /etc/services. + config FEATURE_EDITING bool "Command line editing" default y diff --git a/networking/isrv_identd.c b/networking/isrv_identd.c index 133d62a65..0c33dde4f 100644 --- a/networking/isrv_identd.c +++ b/networking/isrv_identd.c @@ -159,7 +159,7 @@ int fakeidentd_main(int argc UNUSED_PARAM, char **argv) fd = 0; if (!(opt & OPT_inetdwait)) { fd = create_and_bind_stream_or_die(bind_address, - bb_lookup_port("identd", "tcp", 113)); + bb_lookup_std_port("identd", "tcp", 113)); xlisten(fd, 5); } diff --git a/networking/telnet.c b/networking/telnet.c index 15d6a08d8..1e6be85bd 100644 --- a/networking/telnet.c +++ b/networking/telnet.c @@ -643,7 +643,8 @@ int telnet_main(int argc UNUSED_PARAM, char **argv) if (!*argv) bb_show_usage(); host = *argv++; - port = bb_lookup_port(*argv ? *argv++ : "telnet", "tcp", 23); + port = *argv ? bb_lookup_port(*argv++, "tcp", 23) + : bb_lookup_std_port("telnet", "tcp", 23); if (*argv) /* extra params?? */ bb_show_usage(); diff --git a/networking/wget.c b/networking/wget.c index 2650b5384..b6c76e9dc 100644 --- a/networking/wget.c +++ b/networking/wget.c @@ -505,23 +505,23 @@ static void parse_url(const char *src_url, struct host_info *h) *p = '\0'; h->host = p + 3; if (strcmp(url, P_FTP) == 0) { - h->port = bb_lookup_port(P_FTP, "tcp", 21); + h->port = bb_lookup_std_port(P_FTP, "tcp", 21); } else #if SSL_SUPPORTED # if ENABLE_FEATURE_WGET_HTTPS if (strcmp(url, P_FTPS) == 0) { - h->port = bb_lookup_port(P_FTPS, "tcp", 990); + h->port = bb_lookup_std_port(P_FTPS, "tcp", 990); h->protocol = P_FTPS; } else # endif if (strcmp(url, P_HTTPS) == 0) { - h->port = bb_lookup_port(P_HTTPS, "tcp", 443); + h->port = bb_lookup_std_port(P_HTTPS, "tcp", 443); h->protocol = P_HTTPS; } else #endif if (strcmp(url, P_HTTP) == 0) { http: - h->port = bb_lookup_port(P_HTTP, "tcp", 80); + h->port = bb_lookup_std_port(P_HTTP, "tcp", 80); h->protocol = P_HTTP; } else { *p = ':'; diff --git a/util-linux/rdate.c b/util-linux/rdate.c index f27294e25..5ec795208 100644 --- a/util-linux/rdate.c +++ b/util-linux/rdate.c @@ -45,7 +45,7 @@ static time_t askremotedate(const char *host) alarm(10); signal(SIGALRM, socket_timeout); - fd = create_and_connect_stream_or_die(host, bb_lookup_port("time", "tcp", 37)); + fd = create_and_connect_stream_or_die(host, bb_lookup_std_port("time", "tcp", 37)); if (safe_read(fd, &nett, 4) != 4) /* read time from server */ bb_error_msg_and_die("%s: %s", host, "short read"); -- cgit v1.2.3-55-g6feb From e09c426456cfd030cc868d93bbcb2e0a6933cabb Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Thu, 19 Apr 2018 19:29:49 +0200 Subject: unlzma: fix another SEGV case function old new delta unpack_lzma_stream 1705 1717 +12 Signed-off-by: Denys Vlasenko --- archival/libarchive/decompress_unlzma.c | 9 +++++++-- testsuite/unzip.tests | 15 +++++++++++++-- testsuite/unzip_bad_lzma_1.zip | Bin 0 -> 229 bytes 3 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 testsuite/unzip_bad_lzma_1.zip diff --git a/archival/libarchive/decompress_unlzma.c b/archival/libarchive/decompress_unlzma.c index 80a453806..42efd5aa7 100644 --- a/archival/libarchive/decompress_unlzma.c +++ b/archival/libarchive/decompress_unlzma.c @@ -224,6 +224,7 @@ unpack_lzma_stream(transformer_state_t *xstate) rc_t *rc; int i; uint8_t *buffer; + uint32_t buffer_size; uint8_t previous_byte = 0; size_t buffer_pos = 0, global_pos = 0; int len = 0; @@ -253,7 +254,8 @@ unpack_lzma_stream(transformer_state_t *xstate) if (header.dict_size == 0) header.dict_size++; - buffer = xmalloc(MIN(header.dst_size, header.dict_size)); + buffer_size = MIN(header.dst_size, header.dict_size); + buffer = xmalloc(buffer_size); { int num_probs; @@ -464,7 +466,10 @@ unpack_lzma_stream(transformer_state_t *xstate) if ((int32_t)pos < 0) { pos += header.dict_size; /* bug 10436 has an example file where this triggers: */ - if ((int32_t)pos < 0) + //if ((int32_t)pos < 0) + // goto bad; + /* more stringent test (see unzip_bad_lzma_1.zip): */ + if (pos >= buffer_size) goto bad; } previous_byte = buffer[pos]; diff --git a/testsuite/unzip.tests b/testsuite/unzip.tests index 2e4becdb8..6bcb6b3a2 100755 --- a/testsuite/unzip.tests +++ b/testsuite/unzip.tests @@ -14,7 +14,7 @@ # Create a scratch directory mkdir temp -cd temp +cd temp || exit 90 # Create test file to work with. @@ -52,7 +52,18 @@ NzITNFBLBQUKAC4JAA04Cw0EOhZQSwUGAQAABAIAAgCZAAAAeQAAAAIALhM= " SKIP= -rm * +rm -f * + +optional CONFIG_FEATURE_UNZIP_LZMA +testing "unzip (archive with corrupted lzma)" "unzip -p ../unzip_bad_lzma_1.zip 2>&1; echo \$?" \ +"unzip: removing leading '/' from member names +unzip: inflate error +1 +" \ +"" "" +SKIP= + +rm -f * # Clean up scratch directory. diff --git a/testsuite/unzip_bad_lzma_1.zip b/testsuite/unzip_bad_lzma_1.zip new file mode 100644 index 000000000..1335c96d7 Binary files /dev/null and b/testsuite/unzip_bad_lzma_1.zip differ -- cgit v1.2.3-55-g6feb From c21dfaf836cf0eb5317035bc20395c751a205934 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Fri, 20 Apr 2018 15:12:52 +0200 Subject: examples/shutdown-1.0: an example of reboot which does not signal init For one, my inits know nothing about the concept of "shutting down the system". Signed-off-by: Denys Vlasenko --- examples/shutdown-1.0/README | 30 ++++ examples/shutdown-1.0/script/do_shutdown | 54 +++++++ examples/shutdown-1.0/script/hardshutdown.c | 168 ++++++++++++++++++++++ examples/shutdown-1.0/script/hardshutdown.make.sh | 8 ++ examples/shutdown-1.0/script/shutdown | 64 +++++++++ examples/shutdown-1.0/script/stop_storage | 81 +++++++++++ examples/shutdown-1.0/script/stop_tasks | 70 +++++++++ 7 files changed, 475 insertions(+) create mode 100644 examples/shutdown-1.0/README create mode 100755 examples/shutdown-1.0/script/do_shutdown create mode 100644 examples/shutdown-1.0/script/hardshutdown.c create mode 100755 examples/shutdown-1.0/script/hardshutdown.make.sh create mode 100755 examples/shutdown-1.0/script/shutdown create mode 100755 examples/shutdown-1.0/script/stop_storage create mode 100755 examples/shutdown-1.0/script/stop_tasks diff --git a/examples/shutdown-1.0/README b/examples/shutdown-1.0/README new file mode 100644 index 000000000..40fe0ebed --- /dev/null +++ b/examples/shutdown-1.0/README @@ -0,0 +1,30 @@ +# Replaces traditional overdesigned shutdown mechanism. +# +# No communication with init is necessary: +# just ask all processes to exit. +# Then unmount everything. Then reboot or power off. + +# Install /sbin/ symlinks named halt, reboot, poweroff +# (and also possibly shutdown) to e.g. +# /app/shutdown-1.0/script/shutdown: +# +ln -s /app/shutdown-1.0/script/shutdown /sbin/halt +ln -s /app/shutdown-1.0/script/shutdown /sbin/reboot +ln -s /app/shutdown-1.0/script/shutdown /sbin/poweroff +# +# shutdown spawns do_shutdown in new session, redirected to /dev/null, +# tells user that shutdown is in progress, and sleeps +# (for cosmetic reasons: do not confuse user by letting him +# type more commands in this terminal). +# +# do_shutdown tries to switch to a VT console. +# Then, (only if -r) it spawns a hardshutdown child, to reboot +# unconditionally in 30 seconds if something later goes seriously bad. +# Then it runs stop_tasks, writing to /var/log/reboot/YYYYMMDDhhmmss.log, +# then it runs stop_storage. +# Then it commands kernel to halt/reboot/poweroff, if requested. +# Then it sleeps forever. +# +# Build the hardshutdown binary: +# +cd script && ./hardshutdown.make.sh diff --git a/examples/shutdown-1.0/script/do_shutdown b/examples/shutdown-1.0/script/do_shutdown new file mode 100755 index 000000000..0c1e0dce8 --- /dev/null +++ b/examples/shutdown-1.0/script/do_shutdown @@ -0,0 +1,54 @@ +#!/bin/sh +# We are called with stdin/out/err = /dev/null + +resetgracetime=60 + +logfile="/var/log/reboot/`date '+%Y%m%d%H%M%S'`.log" +mkdir -p /var/log/reboot + +PATH=/sbin:/bin + +say() { + printf "\r%s\n\r" "$*" +} + +# Since there is a potential for various fuckups during umount, +# we start delayed hard reboot here which will forcibly +# reboot hung box in a remote datacenter a thousand miles away ;) +if test "$1" = "-r"; then + ./hardshutdown -r "$resetgracetime" & +fi + +# Now, (try to) switch away from X and open a console. I've seen reboots +# hung on open("/dev/console"), therefore we do it _after_ hardshutdown +exec >/dev/console 2>&1 + +if test "$1" = "-r"; then + say "* `date '+%H:%M:%S'` Scheduled hard reboot in $resetgracetime seconds" +fi + +say "* `date '+%H:%M:%S'` Stopping tasks (see /var/log/reboot/* files)" +# log reboot event to file. %Y%m%d%H%M%S: YYYYMMDDHHMMSS +./stop_tasks >"$logfile" 2>&1 + +# Dying X tends to leave us at semi-random vt. Try to fix that, +# but if it doesn't work, proceed anyway. +exec >/dev/null 2>&1 +chvt 1 & sleep 1 +exec >/dev/console 2>&1 + +command -v ctrlaltdel >/dev/null && { + say "* `date '+%H:%M:%S'` Setting Ctrl-Alt-Del to 'hard'" + ctrlaltdel hard +} + +say "* `date '+%H:%M:%S'` Stopping storage devices" +# we can't log this: we are about to unmount everything! +./stop_storage "$@" + +# If we have cmdline params, start hardshutdown with them +test "$*" && ./hardshutdown "$@" + +# Just sleep endlessly... +say "* `date '+%H:%M:%S'` You may now power off or press Ctrl-Alt-Del to reboot" +while true; do sleep 32000; done diff --git a/examples/shutdown-1.0/script/hardshutdown.c b/examples/shutdown-1.0/script/hardshutdown.c new file mode 100644 index 000000000..c21ddad58 --- /dev/null +++ b/examples/shutdown-1.0/script/hardshutdown.c @@ -0,0 +1,168 @@ +/* Including makes sure that on a glibc system + * is included, which again defines __GLIBC__ + */ + +#include +#include /* puts */ +#include /* nanosleep */ +#include +#include +#include + + +/* + * Magic values required to use _reboot() system call. + */ +#define LINUX_REBOOT_MAGIC1 0xfee1dead +#define LINUX_REBOOT_MAGIC2 672274793 +#define LINUX_REBOOT_MAGIC2A 85072278 +#define LINUX_REBOOT_MAGIC2B 369367448 +/* + * Commands accepted by the _reboot() system call. + * + * RESTART Restart system using default command and mode. + * HALT Stop OS and give system control to ROM monitor, if any. + * CAD_ON Ctrl-Alt-Del sequence causes RESTART command. + * CAD_OFF Ctrl-Alt-Del sequence sends SIGINT to init task. + * POWER_OFF Stop OS and remove all power from system, if possible. + * RESTART2 Restart system using given command string. + */ +#define LINUX_REBOOT_CMD_RESTART 0x01234567 +#define LINUX_REBOOT_CMD_HALT 0xCDEF0123 +#define LINUX_REBOOT_CMD_CAD_ON 0x89ABCDEF +#define LINUX_REBOOT_CMD_CAD_OFF 0x00000000 +#define LINUX_REBOOT_CMD_POWER_OFF 0x4321FEDC +#define LINUX_REBOOT_CMD_RESTART2 0xA1B2C3D4 + + +#define USE_LIBC + +#ifdef USE_LIBC + +/* libc version */ +#if defined __GLIBC__ && __GLIBC__ >= 2 +# include +# define REBOOT(cmd) reboot(cmd) +#else +extern int reboot(int, int, int); +# define REBOOT(cmd) reboot(LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,(cmd)) +#endif + +static int my_reboot(int cmd) +{ + return REBOOT(cmd); +} + +#else /* no USE_LIBC */ + +/* direct syscall version */ +#include + +#ifdef _syscall3 +_syscall3(int, reboot, int, magic, int, magic_too, int, cmd); +#else +/* Let us hope we have a 3-argument reboot here */ +extern int reboot(int, int, int); +#endif + +static int my_reboot(int cmd) +{ + return reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, cmd); +} + +#endif + + +static void do_reboot(void) +{ + my_reboot(LINUX_REBOOT_CMD_RESTART); +} +static void do_poweroff(void) +{ + my_reboot(LINUX_REBOOT_CMD_POWER_OFF); +} +static void do_halt(void) +{ + my_reboot(LINUX_REBOOT_CMD_HALT); +} + +static void usage(void) +{ + puts( + "Usage: hardshutdown -h|-r|-p [NN]\n" + " NN - seconds to sleep before requested action" + ); + exit(1); +} + +enum action_t { + SHUTDOWN, // do nothing + HALT, + POWEROFF, + REBOOT +}; + +int main(int argc, char *argv[]) +{ + struct timespec t = {0,0}; + enum action_t action = SHUTDOWN; + int c, i; + char *prog, *ptr; + + //if (*argv[0] == '-') argv[0]++; /* allow shutdown as login shell */ + prog = argv[0]; + ptr = strrchr(prog,'/'); + if (ptr) + prog = ptr+1; + + for (c=1; c < argc; c++) { + if (argv[c][0] >= '0' && argv[c][0] <= '9') { + t.tv_sec = strtol(argv[c], NULL, 10); + continue; + } + if (argv[c][0] != '-') { + usage(); + return 1; + } + for (i=1; argv[c][i]; i++) { + switch (argv[c][i]) { + case 'h': + action = HALT; + break; + case 'p': + action = POWEROFF; + break; + case 'r': + action = REBOOT; + break; + default: + usage(); + return 1; + } + } + } + + if (action==SHUTDOWN) { + usage(); + return 1; + } + + chdir("/"); + while (nanosleep(&t,&t)<0) + if (errno!=EINTR) break; + + switch (action) { + case HALT: + do_halt(); + break; + case POWEROFF: + do_poweroff(); + break; + case REBOOT: + do_reboot(); + break; + default: /* SHUTDOWN */ + break; + } + return 1; +} diff --git a/examples/shutdown-1.0/script/hardshutdown.make.sh b/examples/shutdown-1.0/script/hardshutdown.make.sh new file mode 100755 index 000000000..90f8c5456 --- /dev/null +++ b/examples/shutdown-1.0/script/hardshutdown.make.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +gcc -Wall -Os -o hardshutdown hardshutdown.c +strip hardshutdown + +#or: +#diet gcc -Wall -o hardshutdown hardshutdown.c +#elftrunc hardshutdown diff --git a/examples/shutdown-1.0/script/shutdown b/examples/shutdown-1.0/script/shutdown new file mode 100755 index 000000000..dbab9d81e --- /dev/null +++ b/examples/shutdown-1.0/script/shutdown @@ -0,0 +1,64 @@ +#!/bin/sh + +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +# Usually, /sbin/ has symlinks named halt, reboot, poweroff +# (and also possibly shutdown) to e.g. +# /app/shutdown-1.0/script/shutdown (this file). +cd /app/shutdown-1.0/script || exit 1 +test -x ./do_shutdown || exit 1 +test -x ./hardshutdown || exit 1 + +# "reboot -f" -> "shutdown -f -r" -> "hardshutdown -r" -> immediate reboot +# "reboot" -> "shutdown -r" -> "do_shutdown -r" +# ^^^^^^^^^^^^^^^^^^ similarly for halt, poweroff. +# "shutdown" -> "do_shutdown" (everything killed/unmounted, but kernel not asked to do any poweroff etc) +force="" +test x"$1" = x"-f" && { + force="-f" + shift +} +test ! "$*" && test x"${0##*/}" = x"halt" && exec "$0" $force -h +test ! "$*" && test x"${0##*/}" = x"reboot" && exec "$0" $force -r +test ! "$*" && test x"${0##*/}" = x"poweroff" && exec "$0" $force -p +# We have something else than allowed parameters? +test x"$*" = x"" || test x"$*" = x"-h" || test x"$*" = x"-r" || test x"$*" = x"-p" || { + echo "Syntax: $0 [-f] [-h/-r/-p]" + exit 1 +} + +# Emergency shutdown? +test "$force" && { + exec ./hardshutdown "$@" + exit 1 +} + +# Normal shutdown + +# We must have these executables on root fs +# (mount/umount aren't checked, all systems are ok versus that): +test -x /bin/killall5 -o -x /sbin/killall5 || exit 1 +test -x /bin/ps -o -x /sbin/ps || exit 1 +test -x /bin/date -o -x /sbin/date || exit 1 +test -x /bin/xargs -o -x /sbin/xargs || exit 1 +test -x /bin/wc -o -x /sbin/wc || exit 1 +test -x /bin/cat -o -x /sbin/cat || exit 1 +test -x /bin/sort -o -x /sbin/sort || exit 1 + +i="`ulimit -n`" +echo -n "Closing file descriptors $i-3... " +while test "$i" -ge 3; do + eval "exec $i>&-" + i=$((i-1)) +done + +echo "Shutting down. Please stand by..." + +# setsid & /dev/null: +# make it a process leader & detach it from current tty. +# Why /dev/null and not /dev/console? +# I have seen a system which locked up while opening /dev/console +# due to the bug (?) in keyboard driver. +setsid env - PATH="$PATH" ./do_shutdown "$@" /dev/null 2>&1 & + +while true; do read junk; done diff --git a/examples/shutdown-1.0/script/stop_storage b/examples/shutdown-1.0/script/stop_storage new file mode 100755 index 000000000..1be5f735b --- /dev/null +++ b/examples/shutdown-1.0/script/stop_storage @@ -0,0 +1,81 @@ +#!/bin/sh +# Do unmount/remount-ro. Wait. +# KILL everybody. Wait. +# Repeat. + +umountcnt=2 +writeout=0 # increase if your kernel doesn ot guarantee writes to complete + +# No /usr - we are expecting all binaries to be accessible +# from root fs alone +PATH=/sbin:/bin + +say() { + printf "\r%s\n\r" "$*" +} + +showps() { + # sleep 1 ensures that xargs will have time to start up + # this makes pslist less prone to random jitter + pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs` + pscnt=$(( `say "$pslist" | wc -w` + 0 )) + if test x"$VERBOSE" = x; then + say "* `date '+%H:%M:%S'` $pscnt processes" + else + say "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist" + fi +} + +say "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'" + +showps + +i="$umountcnt" +while test "$i" -gt 0; do + say "* `date '+%H:%M:%S'` Unmounting filesystems" + umount -a -n -r -f + # In case we unmounted proc... + test -e /proc/version || mount -t proc none /proc + # Remounting / RO isn't necessary when /etc/mtab is linked to /proc/mounts: + # already done. But let's be more paranoid here... + say "* `date '+%H:%M:%S'` Remounting root filesystem read-only" + mount -n -o remount,ro / + say "* `date '+%H:%M:%S'` Freeing loop devices" + for a in /dev/loop*; do + test -b "$a" && losetup -d "$a" + done + say "* `date '+%H:%M:%S'` Syncing" + sync + say "* `date '+%H:%M:%S'` Executing: killall5 -KILL" + killall5 -9 + showps + i=$((i-1)) +done + +say "* `date '+%H:%M:%S'` Filesystem status (/proc/mounts)" +cat /proc/mounts \ +| { + bad=false + while read dev mntpoint fstype opt n1 n2; do + case "$fstype" in + ( proc | sysfs | usbfs | devpts | rpc_pipefs | binfmt_misc | autofs | rootfs | tmpfs | ramfs ) + say "$dev $mntpoint $fstype $opt $n1 $n2" + continue + ;; + esac + if test "${opt:0:2}" = "rw"; then + say "$dev $mntpoint $fstype $opt $n1 $n2 - RW!" + bad=true + else + say "$dev $mntpoint $fstype $opt $n1 $n2" + fi + done + if $bad; then + say "ERROR: we have filesystems mounted RW! Press (^J)..." + read junk &0 2>&0 # debug + fi +} + +# Disk cache writeout +sleep "$writeout" diff --git a/examples/shutdown-1.0/script/stop_tasks b/examples/shutdown-1.0/script/stop_tasks new file mode 100755 index 000000000..2d752a3da --- /dev/null +++ b/examples/shutdown-1.0/script/stop_tasks @@ -0,0 +1,70 @@ +#!/bin/sh +# We are trying to be nice. +# TERM everybody. Give them some time to die. +# KILL might make some filesystems non-unmountable, +# so we'll do it in stop_storage instead. + +killcnt=30 + +PATH=/sbin:/usr/sbin:/bin:/usr/bin + +echo "<*> `date '+%Y-%m-%d %H:%M:%S'` Executing '$0 $*'" + +showps() { + # sleep 1 ensures that xargs will have time to start up. + # This makes pslist less prone to random jitter. + pslist=`{ sleep 1; ps -A -o comm=; } | sort | xargs` + pscnt=$(( `echo "$pslist" | wc -w` + 0 )) + if test x"$VERBOSE" = x; then + echo "* `date '+%H:%M:%S'` $pscnt processes" + else + echo "* `date '+%H:%M:%S'` Processes ($pscnt): $pslist" + fi +} + +# Sync. +# Rationale: sometimes buggy root processes can +# hang the system when killed (X for example may have problems +# with restoring text mode on a poorly supported hardware). +# These are bugs and must be fixed, but until then users will lose +# dirty data on shutdown! Let's make that less likely. +sync & + +# Send SIGTERMs. If list of processes changes, proceed slower. +# If it has stabilised (all who wanted to, exited), proceed faster. +showps +i="$killcnt" +while test "$i" -gt 0; do + echo "* `date '+%H:%M:%S'` Sending CONT, TERM" #, HUP" + # I've seen "killall5 2.86" which doesn't grok signal names! + killall5 -18 + killall5 -15 + #killall5 -1 # HUP: because interactive bash does not die on TERM... + # but init will reread /etc/inittab on HUP and my /etc is on non root fs! + # -> umounts will complain. + oldpslist="$pslist" + showps + if test x"$pslist" = x"$oldpslist"; then + i=$((i-8)) + fi + i=$((i-2)) +done + +echo "* `date '+%H:%M:%S'` Turning off swap" +swapoff -a +cat /proc/swaps | grep -v ^Filename | cut -d ' ' -f1 \ +| while read -r line; do + test "$line" && { + echo swapoff "$line" + swapoff "$line" + } +done + +echo "* /proc/swaps:" +cat /proc/swaps +echo "* /proc/mounts:" +cat /proc/mounts +echo "* ps -A e:" +ps -A e +echo "* top -bn1:" +top -bn1 -- cgit v1.2.3-55-g6feb From 2454e678cb7d241097ee9ce4b2114a136f1b57ec Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Mon, 23 Apr 2018 10:53:18 +0200 Subject: awk: do not allow $(-1) function old new delta EMSG_NEGATIVE_FIELD - 25 +25 evaluate 3390 3403 +13 ------------------------------------------------------------------------------ (add/remove: 1/0 grow/shrink: 1/0 up/down: 38/0) Total: 38 bytes Signed-off-by: Denys Vlasenko --- editors/awk.c | 3 +++ testsuite/awk.tests | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/editors/awk.c b/editors/awk.c index d54249bfd..bafc9ba1d 100644 --- a/editors/awk.c +++ b/editors/awk.c @@ -598,6 +598,7 @@ static const char EMSG_NOT_ARRAY[] ALIGN1 = "Not an array"; static const char EMSG_POSSIBLE_ERROR[] ALIGN1 = "Possible syntax error"; static const char EMSG_UNDEF_FUNC[] ALIGN1 = "Call to undefined function"; static const char EMSG_NO_MATH[] ALIGN1 = "Math support is not compiled in"; +static const char EMSG_NEGATIVE_FIELD[] ALIGN1 = "Access to negative field"; static void zero_out_var(var *vp) { @@ -2949,6 +2950,8 @@ static var *evaluate(node *op, var *res) case XC( OC_FIELD ): { int i = (int)getvar_i(R.v); + if (i < 0) + syntax_error(EMSG_NEGATIVE_FIELD); if (i == 0) { res = intvar[F0]; } else { diff --git a/testsuite/awk.tests b/testsuite/awk.tests index ad0583afb..3933fefc9 100755 --- a/testsuite/awk.tests +++ b/testsuite/awk.tests @@ -339,5 +339,11 @@ testing "awk handles invalid for loop" \ "awk '{ for() }' 2>&1" "awk: cmd. line:1: Unexpected token\n" "" "" # testing "description" "command" "result" "infile" "stdin" +testing 'awk negative field access' \ + 'awk 2>&1 -- '\''{ $(-1) }'\' \ + "awk: cmd. line:1: Access to negative field\n" \ + '' \ + 'anything' + exit $FAILCOUNT -- cgit v1.2.3-55-g6feb From f4eaccbfaee58d00f53fe106d018f4e9687b2e27 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 24 Apr 2018 10:13:45 +0200 Subject: free: improve --help for type option Patch by Fred Friedrich. Signed-off-by: Denys Vlasenko --- findutils/find.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/findutils/find.c b/findutils/find.c index d9a42b3c6..07321c81a 100644 --- a/findutils/find.c +++ b/findutils/find.c @@ -263,7 +263,7 @@ //usage: "\n -regex PATTERN Match path to regex PATTERN" //usage: ) //usage: IF_FEATURE_FIND_TYPE( -//usage: "\n -type X File type is X (one of: f,d,l,b,c,...)" +//usage: "\n -type X File type is X (one of: f,d,l,b,c,s,p)" //usage: ) //usage: IF_FEATURE_FIND_PERM( //usage: "\n -perm MASK At least one mask bit (+MASK), all bits (-MASK)," -- cgit v1.2.3-55-g6feb From df45eb49acaeb64574e14f78c6bb5b95f1157058 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 24 Apr 2018 13:35:32 +0200 Subject: wget: handle URLs with @ or hash differently If server replied with 302 and Location: ?foo we used to underflow the allocated space while trying to form the "@foo" filename. Switch to forming "foo" filename. function old new delta packed_usage 32795 32799 +4 parse_url 387 352 -35 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 1/1 up/down: 4/-35) Total: -31 bytes Signed-off-by: Denys Vlasenko --- networking/wget.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/networking/wget.c b/networking/wget.c index b6c76e9dc..30c339244 100644 --- a/networking/wget.c +++ b/networking/wget.c @@ -539,7 +539,7 @@ static void parse_url(const char *src_url, struct host_info *h) // and saves 'index.html?var=a%2Fb' (we save 'b') // wget 'http://busybox.net?login=john@doe': // request: 'GET /?login=john@doe HTTP/1.0' - // saves: 'index.html?login=john@doe' (we save '?login=john@doe') + // saves: 'index.html?login=john@doe' (we save 'login=john@doe') // wget 'http://busybox.net#test/test': // request: 'GET / HTTP/1.0' // saves: 'index.html' (we save 'test') @@ -553,13 +553,13 @@ static void parse_url(const char *src_url, struct host_info *h) } else if (*sp == '/') { *sp = '\0'; h->path = sp + 1; - } else { // '#' or '?' + } else { + // sp points to '#' or '?' + // Note: // http://busybox.net?login=john@doe is a valid URL - // memmove converts to: - // http:/busybox.nett?login=john@doe... - memmove(h->host - 1, h->host, sp - h->host); - h->host--; - sp[-1] = '\0'; + // (without '/' between ".net" and "?"), + // can't store NUL at sp[-1] - this destroys hostname. + *sp++ = '\0'; h->path = sp; } -- cgit v1.2.3-55-g6feb From 77bf05dfe39c507eafc6421ac66b4613aab5509f Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 24 Apr 2018 13:49:12 +0200 Subject: unlzma: do emit the error message on bad input, when we exit with 1 Signed-off-by: Denys Vlasenko --- archival/libarchive/decompress_unlzma.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/archival/libarchive/decompress_unlzma.c b/archival/libarchive/decompress_unlzma.c index 42efd5aa7..446319e7b 100644 --- a/archival/libarchive/decompress_unlzma.c +++ b/archival/libarchive/decompress_unlzma.c @@ -498,6 +498,12 @@ unpack_lzma_stream(transformer_state_t *xstate) IF_DESKTOP(total_written += buffer_pos;) if (transformer_write(xstate, buffer, buffer_pos) != (ssize_t)buffer_pos) { bad: + /* One of our users, bbunpack(), expects _us_ to emit + * the error message (since it's the best place to give + * potentially more detailed information). + * Do not fail silently. + */ + bb_error_msg("corrupted data"); total_written = -1; /* failure */ } rc_free(rc); -- cgit v1.2.3-55-g6feb From bc2e70b4a25e58264cdc236803c32f5a9a0c7e4c Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 29 Apr 2018 13:46:49 +0200 Subject: ifplugd: close signal race function old new delta ifplugd_main 1109 1117 +8 Signed-off-by: Denys Vlasenko --- networking/ifplugd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/networking/ifplugd.c b/networking/ifplugd.c index 5059eaf73..9a67d24d8 100644 --- a/networking/ifplugd.c +++ b/networking/ifplugd.c @@ -686,6 +686,8 @@ int ifplugd_main(int argc UNUSED_PARAM, char **argv) goto exiting; default: bb_got_signal = 0; + /* do not clear bb_got_signal if already 0, this can lose signals */ + case 0: break; } -- cgit v1.2.3-55-g6feb From d80eecb86812c1fbda652f9b995060c26ba0b155 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Sun, 29 Apr 2018 14:05:43 +0200 Subject: cat: fix cat -e and cat -v erroneously numbering 1st line function old new delta cat_main 418 421 +3 Signed-off-by: Denys Vlasenko --- coreutils/cat.c | 6 +++--- testsuite/cat.tests | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) create mode 100755 testsuite/cat.tests diff --git a/coreutils/cat.c b/coreutils/cat.c index 5f02233ca..fb735f994 100644 --- a/coreutils/cat.c +++ b/coreutils/cat.c @@ -112,10 +112,10 @@ static int catv(unsigned opts, char **argv) int retval = EXIT_SUCCESS; int fd; #if ENABLE_FEATURE_CATN - unsigned lineno = 0; - unsigned eol_char = (opts & (CAT_OPT_n|CAT_OPT_b)) ? '\n' : 0x100; + bool eol_seen = (opts & (CAT_OPT_n|CAT_OPT_b)); + unsigned eol_char = (eol_seen ? '\n' : 0x100); unsigned skip_num_on = (opts & CAT_OPT_b) ? '\n' : 0x100; - bool eol_seen = 1; + unsigned lineno = 0; #endif BUILD_BUG_ON(CAT_OPT_e != VISIBLE_ENDLINE); diff --git a/testsuite/cat.tests b/testsuite/cat.tests new file mode 100755 index 000000000..404ebedeb --- /dev/null +++ b/testsuite/cat.tests @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright 2018 by Denys Vlasenko +# Licensed under GPLv2, see file LICENSE in this source tree. + +. ./testing.sh + +# testing "description" "command" "result" "infile" "stdin" +testing 'cat -e' \ + 'cat -e' \ + 'foo$\n' \ + '' \ + 'foo\n' + +testing 'cat -v' \ + 'cat -v' \ + 'foo\n' \ + '' \ + 'foo\n' + +exit $FAILCOUNT -- cgit v1.2.3-55-g6feb