diff --git a/shell/Config.src b/shell/Config.src index ccb1b15fe..0dbf304ae 100644 --- a/shell/Config.src +++ b/shell/Config.src @@ -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 index 000000000..f02105961 --- /dev/null +++ b/shell/ash_test/ash-read/read_t0.right @@ -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 index 000000000..6b7bc217b --- /dev/null +++ b/shell/ash_test/ash-read/read_t0.tests @@ -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 index 000000000..f02105961 --- /dev/null +++ b/shell/hush_test/hush-read/read_t0.right @@ -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 index 000000000..6b7bc217b --- /dev/null +++ b/shell/hush_test/hush-read/read_t0.tests @@ -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/shell_common.c b/shell/shell_common.c index bf56f3d78..a9f8d8413 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -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) - 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"; - } -#endif /* if 0 */ } + 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 (--frac_digits > 0) { + end_ms *= 10; + } + } + 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 */