aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--shell/Config.src7
-rw-r--r--shell/ash_test/ash-read/read_t0.right3
-rwxr-xr-xshell/ash_test/ash-read/read_t0.tests14
-rw-r--r--shell/hush_test/hush-read/read_t0.right3
-rwxr-xr-xshell/hush_test/hush-read/read_t0.tests14
-rw-r--r--shell/shell_common.c82
6 files changed, 93 insertions, 30 deletions
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)
+ }
+ 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 */