xargs: fix handling of quoted arguments, closes 11441
authorRon Yorston <rmy@pobox.com>
Fri, 24 Jan 2020 13:16:45 +0000 (13:16 +0000)
committerDenys Vlasenko <vda.linux@googlemail.com>
Wed, 29 Jan 2020 13:39:13 +0000 (14:39 +0100)
As reported in bug 11441 when presented with a large number of quoted
arguments xargs can return 'argument line too long':

   seq 10000 29999 | sed -e 's/^/"/' -e 's/$/"/' | busybox xargs echo

This happens because the variant of process_stdin() which handles quoted
arguments doesn't preserve state between calls.  If the allowed number
of characters is exceeded part way through a quoted argument the next
call to process_stdin() incorrectly treats the terminating quote as a
starting quote, thus quoting all of the argument separators.

function                                             old     new   delta
process_stdin                                        274     303     +29
xargs_main                                           731     745     +14
------------------------------------------------------------------------------
(add/remove: 0/0 grow/shrink: 2/0 up/down: 43/0)               Total: 43 bytes

Signed-off-by: Ron Yorston <rmy@pobox.com>
Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
findutils/xargs.c
testsuite/xargs.tests

index 72631580323e1858a52334b7baa8415c1f1c1df4..4fb306bb879b8fb0a2f30e6fa5c8c2aba33eac25 100644 (file)
@@ -114,17 +114,28 @@ struct globals {
        int max_procs;
 #endif
        smalluint xargs_exitcode;
+#if ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
+#define NORM      0
+#define QUOTE     1
+#define BACKSLASH 2
+#define SPACE     4
+       smalluint process_stdin__state;
+       char process_stdin__q;
+#endif
 } FIX_ALIASING;
 #define G (*(struct globals*)bb_common_bufsiz1)
 #define INIT_G() do { \
        setup_common_bufsiz(); \
-       G.eof_str = NULL; /* need to clear by hand because we are NOEXEC applet */ \
+       IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.repl_str = "{}";) \
+       IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.eol_ch = '\n';) \
+       /* Even zero values are set because we are NOEXEC applet */ \
+       G.eof_str = NULL; \
        G.idx = 0; \
        IF_FEATURE_XARGS_SUPPORT_PARALLEL(G.running_procs = 0;) \
        IF_FEATURE_XARGS_SUPPORT_PARALLEL(G.max_procs = 1;) \
        G.xargs_exitcode = 0; \
-       IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.repl_str = "{}";) \
-       IF_FEATURE_XARGS_SUPPORT_REPL_STR(G.eol_ch = '\n';) \
+       IF_FEATURE_XARGS_SUPPORT_QUOTES(G.process_stdin__state = NORM;) \
+       IF_FEATURE_XARGS_SUPPORT_QUOTES(G.process_stdin__q = '\0';) \
 } while (0)
 
 
@@ -257,12 +268,8 @@ static void store_param(char *s)
 #if ENABLE_FEATURE_XARGS_SUPPORT_QUOTES
 static char* FAST_FUNC process_stdin(int n_max_chars, int n_max_arg, char *buf)
 {
-#define NORM      0
-#define QUOTE     1
-#define BACKSLASH 2
-#define SPACE     4
-       char q = '\0';             /* quote char */
-       char state = NORM;
+#define q     G.process_stdin__q
+#define state G.process_stdin__state
        char *s = buf;             /* start of the word */
        char *p = s + strlen(buf); /* end of the word */
 
@@ -339,6 +346,8 @@ static char* FAST_FUNC process_stdin(int n_max_chars, int n_max_arg, char *buf)
        /* store_param(NULL) - caller will do it */
        dbg_msg("return:'%s'", s);
        return s;
+#undef q
+#undef state
 }
 #else
 /* The variant does not support single quotes, double quotes or backslash */
index 2d0a201b784572d5d84003161322587db9f3ab96..855b33bc2664f8da383b8939c67739eb5c216bff 100755 (executable)
@@ -41,4 +41,13 @@ testing "xargs -sNUM test 2" \
        "echo 1 2 3 4 5 6 7 8 9 0\n""echo 1 2 3 4 5 6 7 8 9\n""echo 1 00\n" \
        "" "2 3 4 5 6 7 8 9 0 2 3 4 5 6 7 8 9 00\n"
 
+# see that we don't get "argument line too long",
+# but do see the last word, 99999, instead
+optional FEATURE_XARGS_SUPPORT_QUOTES
+testing "xargs argument line too long" \
+       "seq 10000 99999 | sed -e 's/^/\"/' -e 's/$/\"/' | xargs echo | grep -o 99999; echo \$?" \
+       "99999\n0\n" \
+       "" ""
+SKIP=
+
 exit $FAILCOUNT