tls: code shrink
[oweals/busybox.git] / shell / shell_common.c
index 7a0799ed590938f102069d8dec8b7902ece1bf47..f2bf5ab65457c2145825f104e2c51ced41dcf4c9 100644 (file)
@@ -18,7 +18,6 @@
  */
 #include "libbb.h"
 #include "shell_common.h"
-#include <sys/resource.h> /* getrlimit */
 
 const char defifsvar[] ALIGN1 = "IFS= \t\n";
 const char defoptindvar[] ALIGN1 = "OPTIND=1";
@@ -47,16 +46,7 @@ int FAST_FUNC is_well_formed_var_name(const char *s, char terminator)
 //Here we can simply store "VAR=" at buffer start and store read data directly
 //after "=", then pass buffer to setvar() to consume.
 const char* FAST_FUNC
-shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
-       char       **argv,
-       const char *ifs,
-       int        read_flags,
-       const char *opt_n,
-       const char *opt_p,
-       const char *opt_t,
-       const char *opt_u,
-       const char *opt_d
-)
+shell_builtin_read(struct builtin_read_params *params)
 {
        struct pollfd pfd[1];
 #define fd (pfd[0].fd) /* -u FD */
@@ -71,9 +61,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        int bufpos; /* need to be able to hold -1 */
        int startword;
        smallint backslash;
+       char **argv;
+       const char *ifs;
+       int read_flags;
 
        errno = err = 0;
 
+       argv = params->argv;
        pp = argv;
        while (*pp) {
                if (!is_well_formed_var_name(*pp, '\0')) {
@@ -85,29 +79,29 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        }
 
        nchars = 0; /* if != 0, -n is in effect */
-       if (opt_n) {
-               nchars = bb_strtou(opt_n, NULL, 10);
+       if (params->opt_n) {
+               nchars = bb_strtou(params->opt_n, NULL, 10);
                if (nchars < 0 || errno)
                        return "invalid count";
                /* note: "-n 0": off (bash 3.2 does this too) */
        }
 
        end_ms = 0;
-       if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
-               end_ms = bb_strtou(opt_t, NULL, 10);
+       if (params->opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
+               end_ms = bb_strtou(params->opt_t, NULL, 10);
                if (errno)
                        return "invalid timeout";
                if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
                        end_ms = UINT_MAX / 2048;
                end_ms *= 1000;
        }
-       if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
+       if (params->opt_t && ENABLE_FEATURE_SH_READ_FRAC) {
                /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */
                char *p;
                /* Eat up to three fractional digits */
                int frac_digits = 3 + 1;
 
-               end_ms = bb_strtou(opt_t, &p, 10);
+               end_ms = bb_strtou(params->opt_t, &p, 10);
                if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
                        end_ms = UINT_MAX / 2048;
 
@@ -129,13 +123,13 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        }
 
        fd = STDIN_FILENO;
-       if (opt_u) {
-               fd = bb_strtou(opt_u, NULL, 10);
+       if (params->opt_u) {
+               fd = bb_strtou(params->opt_u, NULL, 10);
                if (fd < 0 || errno)
                        return "invalid file descriptor";
        }
 
-       if (opt_t && end_ms == 0) {
+       if (params->opt_t && end_ms == 0) {
                /* "If timeout is 0, read returns immediately, without trying
                 * to read any data. The exit status is 0 if input is available
                 * on the specified file descriptor, non-zero otherwise."
@@ -148,14 +142,16 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                return (const char *)(uintptr_t)(r <= 0);
        }
 
-       if (opt_p && isatty(fd)) {
-               fputs(opt_p, stderr);
+       if (params->opt_p && isatty(fd)) {
+               fputs(params->opt_p, stderr);
                fflush_all();
        }
 
+       ifs = params->ifs;
        if (ifs == NULL)
                ifs = defifs;
 
+       read_flags = params->read_flags;
        if (nchars || (read_flags & BUILTIN_READ_SILENT)) {
                tcgetattr(fd, &tty);
                old_tty = tty;
@@ -182,11 +178,11 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        retval = (const char *)(uintptr_t)0;
        startword = 1;
        backslash = 0;
-       if (opt_t)
+       if (params->opt_t)
                end_ms += (unsigned)monotonic_ms();
        buffer = NULL;
        bufpos = 0;
-       delim = opt_d ? *opt_d : '\n';
+       delim = params->opt_d ? params->opt_d[0] : '\n';
        do {
                char c;
                int timeout;
@@ -195,7 +191,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                        buffer = xrealloc(buffer, bufpos + 0x101);
 
                timeout = -1;
-               if (opt_t) {
+               if (params->opt_t) {
                        timeout = end_ms - (unsigned)monotonic_ms();
                        /* ^^^^^^^^^^^^^ all values are unsigned,
                         * wrapping math is used here, good even if
@@ -247,7 +243,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                 * without variable names (bash compat).
                 * Thus, "read" and "read REPLY" are not the same.
                 */
-               if (!opt_d && argv[0]) {
+               if (!params->opt_d && argv[0]) {
 /* http://www.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 */
                        const char *is_ifs = strchr(ifs, c);
                        if (startword && is_ifs) {
@@ -262,7 +258,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                        if (argv[1] != NULL && is_ifs) {
                                buffer[bufpos] = '\0';
                                bufpos = 0;
-                               setvar(*argv, buffer);
+                               params->setvar(*argv, buffer);
                                argv++;
                                /* can we skip one non-space ifs char? (2: yes) */
                                startword = isspace(c) ? 2 : 1;
@@ -275,18 +271,53 @@ 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);
+               params->setvar(*argv, buffer);
                /* Set the rest to "" */
                while (*++argv)
-                       setvar(*argv, "");
+                       params->setvar(*argv, "");
        } else {
                /* Note: no $IFS removal */
                buffer[bufpos] = '\0';
-               setvar("REPLY", buffer);
+               params->setvar("REPLY", buffer);
        }
 
  ret:
@@ -425,8 +456,8 @@ shell_builtin_ulimit(char **argv)
         * ulimit 123 -c2 -l 456
         */
 
-       /* In case getopt was already called:
-        * reset the libc getopt() function, which keeps internal state.
+       /* In case getopt() was already called:
+        * reset libc getopt() internal state.
         */
        GETOPT_RESET();