[git commit] shell: optional support for read -t N.NNN, closes 10101

Denys Vlasenko vda.linux at googlemail.com
Thu Jul 20 14:09:31 UTC 2017


commit: https://git.busybox.net/busybox/commit/?id=eae12688c9bb056b21e4e62fc295be5ebdcb8b7b
branch: https://git.busybox.net/busybox/commit/?id=refs/heads/master

function                                             old     new   delta
shell_builtin_read                                  1097    1277    +180
dump_procs                                           353     359      +6

Signed-off-by: Denys Vlasenko <vda.linux at googlemail.com>
---
 shell/Config.src                        |  7 +++
 shell/ash_test/ash-read/read_t0.right   |  3 ++
 shell/ash_test/ash-read/read_t0.tests   | 14 ++++++
 shell/hush_test/hush-read/read_t0.right |  3 ++
 shell/hush_test/hush-read/read_t0.tests | 14 ++++++
 shell/shell_common.c                    | 82 +++++++++++++++++++++------------
 6 files changed, 93 insertions(+), 30 deletions(-)

diff --git a/shell/Config.src b/shell/Config.src
index ccb1b15..0dbf304 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 0000000..f021059
--- /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 0000000..6b7bc21
--- /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 0000000..f021059
--- /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 0000000..6b7bc21
--- /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 bf56f3d..a9f8d84 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 */


More information about the busybox-cvs mailing list