hush: make "set -x" output don't redirectable when fd#2 redirected
authorDenys Vlasenko <vda.linux@googlemail.com>
Sat, 28 Jul 2018 10:13:58 +0000 (12:13 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Sat, 28 Jul 2018 10:13:58 +0000 (12:13 +0200)
function                                             old     new   delta
x_mode_print_optionally_squoted                        -     120    +120
x_mode_flush                                           -      68     +68
save_fd_on_redirect                                  208     243     +35
x_mode_prefix                                          -      27     +27
x_mode_addblock                                        -      23     +23
x_mode_addchr                                          -      17     +17
dump_cmd_in_x_mode                                   110      85     -25
run_pipe                                            1919    1890     -29
print_optionally_squoted                             145       -    -145
------------------------------------------------------------------------------
(add/remove: 5/1 grow/shrink: 1/2 up/down: 290/-199)           Total: 91 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/hush.c

index ac8467fb44a8852d6088283679b25a40c5e19b9d..9676819fa1cf7b5c674032c3cfb0b31893ad8e0c 100644 (file)
@@ -954,9 +954,6 @@ struct globals {
        unsigned func_nest_level; /* solely to prevent "local v" in non-functions */
 # endif
        struct function *top_func;
-#endif
-#if ENABLE_HUSH_MODE_X
-       unsigned x_mode_depth;
 #endif
        /* Signal and trap handling */
 #if ENABLE_HUSH_FAST
@@ -993,6 +990,15 @@ struct globals {
 #if ENABLE_HUSH_MEMLEAK
        unsigned long memleak_value;
 #endif
+#if ENABLE_HUSH_MODE_X
+       unsigned x_mode_depth;
+       /* "set -x" output should not be redirectable with subsequent 2>FILE.
+        * We dup fd#2 to x_mode_fd when "set -x" is executed, and use it
+        * for all subsequent output.
+        */
+       int x_mode_fd;
+       o_string x_mode_buf;
+#endif
 #if HUSH_DEBUG
        int debug_indent;
 #endif
@@ -1660,6 +1666,12 @@ static int move_HFILEs_on_redirect(int fd, int avoid_fd)
                }
                fl = fl->next_hfile;
        }
+#if ENABLE_HUSH_MODE_X
+       if (G.x_mode_fd > 0 && fd == G.x_mode_fd) {
+               G.x_mode_fd = xdup_CLOEXEC_and_close(fd, avoid_fd);
+               return 1; /* "found and moved" */
+       }
+#endif
        return 0; /* "not in the list" */
 }
 #if ENABLE_FEATURE_SH_STANDALONE && BB_MMU
@@ -2903,6 +2915,11 @@ static void o_addstr(o_string *o, const char *str)
        o_addblock(o, str, strlen(str));
 }
 
+static void o_addstr_with_NUL(o_string *o, const char *str)
+{
+       o_addblock(o, str, strlen(str) + 1);
+}
+
 #if !BB_MMU
 static void nommu_addchr(o_string *o, int ch)
 {
@@ -2913,10 +2930,36 @@ static void nommu_addchr(o_string *o, int ch)
 # define nommu_addchr(o, str) ((void)0)
 #endif
 
-static void o_addstr_with_NUL(o_string *o, const char *str)
+#if ENABLE_HUSH_MODE_X
+static void x_mode_addchr(int ch)
 {
-       o_addblock(o, str, strlen(str) + 1);
+       o_addchr(&G.x_mode_buf, ch);
 }
+static void x_mode_addstr(const char *str)
+{
+       o_addstr(&G.x_mode_buf, str);
+}
+static void x_mode_addblock(const char *str, int len)
+{
+       o_addblock(&G.x_mode_buf, str, len);
+}
+static void x_mode_prefix(void)
+{
+       int n = G.x_mode_depth;
+       do x_mode_addchr('+'); while (--n >= 0);
+}
+static void x_mode_flush(void)
+{
+       int len = G.x_mode_buf.length;
+       if (len <= 0)
+               return;
+       if (G.x_mode_fd > 0) {
+               G.x_mode_buf.data[len] = '\n';
+               full_write(G.x_mode_fd, G.x_mode_buf.data, len + 1);
+       }
+       G.x_mode_buf.length = 0;
+}
+#endif
 
 /*
  * HUSH_BRACE_EXPANSION code needs corresponding quoting on variable expansion side.
@@ -8030,31 +8073,26 @@ static void execvp_or_die(char **argv)
 }
 
 #if ENABLE_HUSH_MODE_X
-static void print_optionally_squoted(FILE *fp, const char *str)
+static void x_mode_print_optionally_squoted(const char *str)
 {
        unsigned len;
        const char *cp;
 
        cp = str;
-       if (str[0] != '{' && str[0] != '(') for (;;) {
-               if (!*cp) {
-                       /* string has no special chars */
-                       fputs(str, fp);
-                       return;
-               }
-               if (*cp == '\\') break;
-               if (*cp == '\'') break;
-               if (*cp == '"') break;
-               if (*cp == '$') break;
-               if (*cp == '!') break;
-               if (*cp == '*') break;
-               if (*cp == '[') break;
-               if (*cp == ']') break;
-#if ENABLE_HUSH_TICK
-               if (*cp == '`') break;
-#endif
-               if (isspace(*cp)) break;
-               cp++;
+
+       /* the set of chars which-cause-string-to-be-squoted mimics bash */
+       /* test a char with: bash -c 'set -x; echo "CH"' */
+       if (str[strcspn(str, "\\\"'`$(){}[]<>;#&|~*?!^"
+                       " " "\001\002\003\004\005\006\007"
+                       "\010\011\012\013\014\015\016\017"
+                       "\020\021\022\023\024\025\026\027"
+                       "\030\031\032\033\034\035\036\037"
+                       )
+               ] == '\0'
+       ) {
+               /* string has no special chars */
+               x_mode_addstr(str);
+               return;
        }
 
        cp = str;
@@ -8062,13 +8100,16 @@ static void print_optionally_squoted(FILE *fp, const char *str)
                /* print '....' up to EOL or first squote */
                len = (int)(strchrnul(cp, '\'') - cp);
                if (len != 0) {
-                       fprintf(fp, "'%.*s'", len, cp);
+                       x_mode_addchr('\'');
+                       x_mode_addblock(cp, len);
+                       x_mode_addchr('\'');
                        cp += len;
                }
                if (*cp == '\0')
                        break;
                /* string contains squote(s), print them as \' */
-               fprintf(fp, "\\'");
+               x_mode_addchr('\\');
+               x_mode_addchr('\'');
                cp++;
        }
 }
@@ -8078,19 +8119,19 @@ static void dump_cmd_in_x_mode(char **argv)
                unsigned n;
 
                /* "+[+++...][ cmd...]\n\0" */
-               n = G.x_mode_depth;
-               do bb_putchar_stderr('+'); while ((int)(--n) >= 0);
+               x_mode_prefix();
                n = 0;
                while (argv[n]) {
-                       if (argv[n][0] == '\0')
-                               fputs(" ''", stderr);
-                       else {
-                               bb_putchar_stderr(' ');
-                               print_optionally_squoted(stderr, argv[n]);
+                       x_mode_addchr(' ');
+                       if (argv[n][0] == '\0') {
+                               x_mode_addchr('\'');
+                               x_mode_addchr('\'');
+                       } else {
+                               x_mode_print_optionally_squoted(argv[n]);
                        }
                        n++;
                }
-               bb_putchar_stderr('\n');
+               x_mode_flush();
        }
 }
 #else
@@ -8885,17 +8926,14 @@ static NOINLINE int run_pipe(struct pipe *pi)
 #if ENABLE_HUSH_MODE_X
                                if (G_x_mode) {
                                        char *eq;
-                                       if (i == 0) {
-                                               unsigned n = G.x_mode_depth;
-                                               do
-                                                       bb_putchar_stderr('+');
-                                               while ((int)(--n) >= 0);
-                                       }
+                                       if (i == 0)
+                                               x_mode_prefix();
+                                       x_mode_addchr(' ');
                                        eq = strchrnul(p, '=');
-                                       fprintf(stderr, " %.*s=", (int)(eq - p), p);
-                                       if (*eq)
-                                               print_optionally_squoted(stderr, eq + 1);
-                                       bb_putchar_stderr('\n');
+                                       if (*eq) eq++;
+                                       x_mode_addblock(p, (eq - p));
+                                       x_mode_print_optionally_squoted(eq);
+                                       x_mode_flush();
                                }
 #endif
                                debug_printf_env("set shell var:'%s'->'%s'\n", *argv, p);
@@ -9691,6 +9729,7 @@ static int set_mode(int state, char mode, const char *o_opt)
                break;
        case 'x':
                IF_HUSH_MODE_X(G_x_mode = state;)
+               IF_HUSH_MODE_X(if (G.x_mode_fd <= 0) G.x_mode_fd = dup_CLOEXEC(2, 10);)
                break;
        case 'o':
                if (!o_opt) {