shell: optional support for read -t N.NNN, closes 10101

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

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2017-07-20 16:09:31 +02:00
parent 82dcc3bff9
commit eae12688c9
6 changed files with 97 additions and 34 deletions

View File

@ -145,6 +145,13 @@ config FEATURE_SH_NOFORK
This feature is relatively new. Use with care. Report bugs This feature is relatively new. Use with care. Report bugs
to project mailing list. 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 config FEATURE_SH_HISTFILESIZE
bool "Use $HISTFILESIZE" bool "Use $HISTFILESIZE"
default y default y

View File

@ -0,0 +1,3 @@
><[0]
><[0]
><[1]

View File

@ -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<[$?]"; }

View File

@ -0,0 +1,3 @@
><[0]
><[0]
><[1]

View File

@ -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<[$?]"; }

View File

@ -57,9 +57,10 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
const char *opt_u const char *opt_u
) )
{ {
struct pollfd pfd[1];
#define fd (pfd[0].fd) /* -u FD */
unsigned err; unsigned err;
unsigned end_ms; /* -t TIMEOUT */ unsigned end_ms; /* -t TIMEOUT */
int fd; /* -u FD */
int nchars; /* -n NUM */ int nchars; /* -n NUM */
char **pp; char **pp;
char *buffer; char *buffer;
@ -88,38 +89,43 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val),
return "invalid count"; return "invalid count";
/* note: "-n 0": off (bash 3.2 does this too) */ /* note: "-n 0": off (bash 3.2 does this too) */
} }
end_ms = 0; end_ms = 0;
if (opt_t) { if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) {
end_ms = bb_strtou(opt_t, NULL, 10); end_ms = bb_strtou(opt_t, NULL, 10);
if (errno || end_ms > UINT_MAX / 2048) if (errno)
return "invalid timeout"; return "invalid timeout";
if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */
end_ms = UINT_MAX / 2048;
end_ms *= 1000; 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; fd = STDIN_FILENO;
if (opt_u) { if (opt_u) {
fd = bb_strtou(opt_u, NULL, 10); 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"; 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)) { if (opt_p && isatty(fd)) {
fputs(opt_p, stderr); fputs(opt_p, stderr);
fflush_all(); 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; retval = (const char *)(uintptr_t)0;
startword = 1; startword = 1;
backslash = 0; backslash = 0;
if (end_ms) /* NB: end_ms stays nonzero: */ if (opt_t)
end_ms = ((unsigned)monotonic_ms() + end_ms) | 1; end_ms += (unsigned)monotonic_ms();
buffer = NULL; buffer = NULL;
bufpos = 0; bufpos = 0;
do { do {
char c; char c;
struct pollfd pfd[1];
int timeout; int timeout;
if ((bufpos & 0xff) == 0) if ((bufpos & 0xff) == 0)
buffer = xrealloc(buffer, bufpos + 0x101); buffer = xrealloc(buffer, bufpos + 0x101);
timeout = -1; timeout = -1;
if (end_ms) { if (opt_t) {
timeout = end_ms - (unsigned)monotonic_ms(); 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? */ if (timeout <= 0) { /* already late? */
retval = (const char *)(uintptr_t)1; retval = (const char *)(uintptr_t)1;
goto ret; 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! * regardless of SA_RESTART-ness of that signal!
*/ */
errno = 0; errno = 0;
pfd[0].fd = fd;
pfd[0].events = POLLIN; pfd[0].events = POLLIN;
if (poll(pfd, 1, timeout) != 1) { if (poll(pfd, 1, timeout) <= 0) {
/* timed out, or EINTR */ /* timed out, or EINTR */
err = errno; err = errno;
retval = (const char *)(uintptr_t)1; 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; errno = err;
return retval; return retval;
#undef fd
} }
/* ulimit builtin */ /* ulimit builtin */