shell: optional support for read -t N.NNN, closes 10101
authorDenys Vlasenko <vda.linux@googlemail.com>
Thu, 20 Jul 2017 14:09:31 +0000 (16:09 +0200)
committerDenys Vlasenko <vda.linux@googlemail.com>
Thu, 20 Jul 2017 14:09:31 +0000 (16:09 +0200)
function                                             old     new   delta
shell_builtin_read                                  1097    1277    +180
dump_procs                                           353     359      +6

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
shell/Config.src
shell/ash_test/ash-read/read_t0.right [new file with mode: 0644]
shell/ash_test/ash-read/read_t0.tests [new file with mode: 0755]
shell/hush_test/hush-read/read_t0.right [new file with mode: 0644]
shell/hush_test/hush-read/read_t0.tests [new file with mode: 0755]
shell/shell_common.c

index ccb1b15fe81c76bc8fce73cad4171450bd594e0b..0dbf304aed85eabc9bbe5b5a1d9e76dfbf5ae25e 100644 (file)
@@ -145,6 +145,13 @@ config FEATURE_SH_NOFORK
          This feature is relatively new. Use with care. Report bugs
          to project mailing list.
 
+config FEATURE_SH_READ_FRAC
+       bool "read -t N.NNN support (+110 bytes)"
+       default y
+       depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH
+       help
+         Enable support for fractional second timeout in read builtin.
+
 config FEATURE_SH_HISTFILESIZE
        bool "Use $HISTFILESIZE"
        default y
diff --git a/shell/ash_test/ash-read/read_t0.right b/shell/ash_test/ash-read/read_t0.right
new file mode 100644 (file)
index 0000000..f021059
--- /dev/null
@@ -0,0 +1,3 @@
+><[0]
+><[0]
+><[1]
diff --git a/shell/ash_test/ash-read/read_t0.tests b/shell/ash_test/ash-read/read_t0.tests
new file mode 100755 (executable)
index 0000000..6b7bc21
--- /dev/null
@@ -0,0 +1,14 @@
+# ><[0]
+echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
+
+# This would not be deterministic: returns 0 "data exists" if EOF is seen
+# (true terminated) - because EOF is considered to be data (read will not block),
+# else returns 1 "no data".
+## ><[????]
+#true | { read -t 0 reply; echo ">$reply<[$?]"; }
+
+# ><[0]
+true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
+
+# ><[1]
+sleep 0.2 | { read -p IGNORED_PROMPT -t 0 reply; echo ">$reply<[$?]"; }
diff --git a/shell/hush_test/hush-read/read_t0.right b/shell/hush_test/hush-read/read_t0.right
new file mode 100644 (file)
index 0000000..f021059
--- /dev/null
@@ -0,0 +1,3 @@
+><[0]
+><[0]
+><[1]
diff --git a/shell/hush_test/hush-read/read_t0.tests b/shell/hush_test/hush-read/read_t0.tests
new file mode 100755 (executable)
index 0000000..6b7bc21
--- /dev/null
@@ -0,0 +1,14 @@
+# ><[0]
+echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
+
+# This would not be deterministic: returns 0 "data exists" if EOF is seen
+# (true terminated) - because EOF is considered to be data (read will not block),
+# else returns 1 "no data".
+## ><[????]
+#true | { read -t 0 reply; echo ">$reply<[$?]"; }
+
+# ><[0]
+true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; }
+
+# ><[1]
+sleep 0.2 | { read -p IGNORED_PROMPT -t 0 reply; echo ">$reply<[$?]"; }
index bf56f3d78b9e4c121d3c7b8d48c879ed4b5bd18f..a9f8d84130d509a393ca40cf8f35457ec1fb4b4a 100644 (file)
@@ -57,9 +57,10 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        const char *opt_u
 )
 {
+       struct pollfd pfd[1];
+#define fd (pfd[0].fd) /* -u FD */
        unsigned err;
        unsigned end_ms; /* -t TIMEOUT */
-       int fd; /* -u FD */
        int nchars; /* -n NUM */
        char **pp;
        char *buffer;
@@ -88,38 +89,43 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                        return "invalid count";
                /* note: "-n 0": off (bash 3.2 does this too) */
        }
+
        end_ms = 0;
-       if (opt_t) {
+       if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
                end_ms = bb_strtou(opt_t, NULL, 10);
-               if (errno || end_ms > UINT_MAX / 2048)
+               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 0 /* even bash has no -t N.NNN support */
-               ts.tv_sec = bb_strtou(opt_t, &p, 10);
-               ts.tv_usec = 0;
-               /* EINVAL means number is ok, but not terminated by NUL */
-               if (*p == '.' && errno == EINVAL) {
-                       char *p2;
-                       if (*++p) {
-                               int scale;
-                               ts.tv_usec = bb_strtou(p, &p2, 10);
-                               if (errno)
-                                       return "invalid timeout";
-                               scale = p2 - p;
-                               /* normalize to usec */
-                               if (scale > 6)
+       }
+       if (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);
+               if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
+                       end_ms = UINT_MAX / 2048;
+
+               if (errno) {
+                       /* EINVAL = number is ok, but not NUL terminated */
+                       if (errno != EINVAL || *p != '.')
+                               return "invalid timeout";
+                       /* Do not check the rest: bash allows "0.123456xyz" */
+                       while (*++p && --frac_digits) {
+                               end_ms *= 10;
+                               end_ms += (*p - '0');
+                               if ((unsigned char)(*p - '0') > 9)
                                        return "invalid timeout";
-                               while (scale++ < 6)
-                                       ts.tv_usec *= 10;
                        }
-               } else if (ts.tv_sec < 0 || errno) {
-                       return "invalid timeout";
                }
-               if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */
-                       return "invalid timeout";
+               while (--frac_digits > 0) {
+                       end_ms *= 10;
                }
-#endif /* if 0 */
        }
+
        fd = STDIN_FILENO;
        if (opt_u) {
                fd = bb_strtou(opt_u, NULL, 10);
@@ -127,6 +133,19 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                        return "invalid file descriptor";
        }
 
+       if (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."
+                * bash seems to ignore -p PROMPT for this use case.
+                */
+               int r;
+               pfd[0].events = POLLIN;
+               r = poll(pfd, 1, /*timeout:*/ 0);
+               /* Return 0 only if poll returns 1 ("one fd ready"), else return 1: */
+               return (const char *)(uintptr_t)(r <= 0);
+       }
+
        if (opt_p && isatty(fd)) {
                fputs(opt_p, stderr);
                fflush_all();
@@ -161,21 +180,24 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
        retval = (const char *)(uintptr_t)0;
        startword = 1;
        backslash = 0;
-       if (end_ms) /* NB: end_ms stays nonzero: */
-               end_ms = ((unsigned)monotonic_ms() + end_ms) | 1;
+       if (opt_t)
+               end_ms += (unsigned)monotonic_ms();
        buffer = NULL;
        bufpos = 0;
        do {
                char c;
-               struct pollfd pfd[1];
                int timeout;
 
                if ((bufpos & 0xff) == 0)
                        buffer = xrealloc(buffer, bufpos + 0x101);
 
                timeout = -1;
-               if (end_ms) {
+               if (opt_t) {
                        timeout = end_ms - (unsigned)monotonic_ms();
+                       /* ^^^^^^^^^^^^^ all values are unsigned,
+                        * wrapping math is used here, good even if
+                        * 32-bit unix time wrapped (year 2038+).
+                        */
                        if (timeout <= 0) { /* already late? */
                                retval = (const char *)(uintptr_t)1;
                                goto ret;
@@ -187,9 +209,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
                 * regardless of SA_RESTART-ness of that signal!
                 */
                errno = 0;
-               pfd[0].fd = fd;
                pfd[0].events = POLLIN;
-               if (poll(pfd, 1, timeout) != 1) {
+               if (poll(pfd, 1, timeout) <= 0) {
                        /* timed out, or EINTR */
                        err = errno;
                        retval = (const char *)(uintptr_t)1;
@@ -272,6 +293,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
 
        errno = err;
        return retval;
+#undef fd
 }
 
 /* ulimit builtin */