diff options
| author | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-20 16:09:31 +0200 |
|---|---|---|
| committer | Denys Vlasenko <vda.linux@googlemail.com> | 2017-07-20 16:09:31 +0200 |
| commit | eae12688c9bb056b21e4e62fc295be5ebdcb8b7b (patch) | |
| tree | 785eeede5f3c288a510bbd74fe52e1c216155284 /shell | |
| parent | 82dcc3bff97b08db227610596a54574783f69631 (diff) | |
| download | busybox-w32-eae12688c9bb056b21e4e62fc295be5ebdcb8b7b.tar.gz busybox-w32-eae12688c9bb056b21e4e62fc295be5ebdcb8b7b.tar.bz2 busybox-w32-eae12688c9bb056b21e4e62fc295be5ebdcb8b7b.zip | |
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>
Diffstat (limited to 'shell')
| -rw-r--r-- | shell/Config.src | 7 | ||||
| -rw-r--r-- | shell/ash_test/ash-read/read_t0.right | 3 | ||||
| -rwxr-xr-x | shell/ash_test/ash-read/read_t0.tests | 14 | ||||
| -rw-r--r-- | shell/hush_test/hush-read/read_t0.right | 3 | ||||
| -rwxr-xr-x | shell/hush_test/hush-read/read_t0.tests | 14 | ||||
| -rw-r--r-- | shell/shell_common.c | 82 |
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 | |||
| 145 | This feature is relatively new. Use with care. Report bugs | 145 | This feature is relatively new. Use with care. Report bugs |
| 146 | to project mailing list. | 146 | to project mailing list. |
| 147 | 147 | ||
| 148 | config FEATURE_SH_READ_FRAC | ||
| 149 | bool "read -t N.NNN support (+110 bytes)" | ||
| 150 | default y | ||
| 151 | depends on ASH || HUSH || SH_IS_ASH || BASH_IS_ASH || SH_IS_HUSH || BASH_IS_HUSH | ||
| 152 | help | ||
| 153 | Enable support for fractional second timeout in read builtin. | ||
| 154 | |||
| 148 | config FEATURE_SH_HISTFILESIZE | 155 | config FEATURE_SH_HISTFILESIZE |
| 149 | bool "Use $HISTFILESIZE" | 156 | bool "Use $HISTFILESIZE" |
| 150 | default y | 157 | 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 @@ | |||
| 1 | ><[0] | ||
| 2 | ><[0] | ||
| 3 | ><[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 @@ | |||
| 1 | # ><[0] | ||
| 2 | echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 3 | |||
| 4 | # This would not be deterministic: returns 0 "data exists" if EOF is seen | ||
| 5 | # (true terminated) - because EOF is considered to be data (read will not block), | ||
| 6 | # else returns 1 "no data". | ||
| 7 | ## ><[????] | ||
| 8 | #true | { read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 9 | |||
| 10 | # ><[0] | ||
| 11 | true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 12 | |||
| 13 | # ><[1] | ||
| 14 | 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 @@ | |||
| 1 | ><[0] | ||
| 2 | ><[0] | ||
| 3 | ><[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 @@ | |||
| 1 | # ><[0] | ||
| 2 | echo Ok | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 3 | |||
| 4 | # This would not be deterministic: returns 0 "data exists" if EOF is seen | ||
| 5 | # (true terminated) - because EOF is considered to be data (read will not block), | ||
| 6 | # else returns 1 "no data". | ||
| 7 | ## ><[????] | ||
| 8 | #true | { read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 9 | |||
| 10 | # ><[0] | ||
| 11 | true | { sleep 0.1; read -t 0 reply; echo ">$reply<[$?]"; } | ||
| 12 | |||
| 13 | # ><[1] | ||
| 14 | 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), | |||
| 57 | const char *opt_u | 57 | const char *opt_u |
| 58 | ) | 58 | ) |
| 59 | { | 59 | { |
| 60 | struct pollfd pfd[1]; | ||
| 61 | #define fd (pfd[0].fd) /* -u FD */ | ||
| 60 | unsigned err; | 62 | unsigned err; |
| 61 | unsigned end_ms; /* -t TIMEOUT */ | 63 | unsigned end_ms; /* -t TIMEOUT */ |
| 62 | int fd; /* -u FD */ | ||
| 63 | int nchars; /* -n NUM */ | 64 | int nchars; /* -n NUM */ |
| 64 | char **pp; | 65 | char **pp; |
| 65 | char *buffer; | 66 | char *buffer; |
| @@ -88,38 +89,43 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 88 | return "invalid count"; | 89 | return "invalid count"; |
| 89 | /* note: "-n 0": off (bash 3.2 does this too) */ | 90 | /* note: "-n 0": off (bash 3.2 does this too) */ |
| 90 | } | 91 | } |
| 92 | |||
| 91 | end_ms = 0; | 93 | end_ms = 0; |
| 92 | if (opt_t) { | 94 | if (opt_t && !ENABLE_FEATURE_SH_READ_FRAC) { |
| 93 | end_ms = bb_strtou(opt_t, NULL, 10); | 95 | end_ms = bb_strtou(opt_t, NULL, 10); |
| 94 | if (errno || end_ms > UINT_MAX / 2048) | 96 | if (errno) |
| 95 | return "invalid timeout"; | 97 | return "invalid timeout"; |
| 98 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ | ||
| 99 | end_ms = UINT_MAX / 2048; | ||
| 96 | end_ms *= 1000; | 100 | end_ms *= 1000; |
| 97 | #if 0 /* even bash has no -t N.NNN support */ | 101 | } |
| 98 | ts.tv_sec = bb_strtou(opt_t, &p, 10); | 102 | if (opt_t && ENABLE_FEATURE_SH_READ_FRAC) { |
| 99 | ts.tv_usec = 0; | 103 | /* bash 4.3 (maybe earlier) supports -t N.NNNNNN */ |
| 100 | /* EINVAL means number is ok, but not terminated by NUL */ | 104 | char *p; |
| 101 | if (*p == '.' && errno == EINVAL) { | 105 | /* Eat up to three fractional digits */ |
| 102 | char *p2; | 106 | int frac_digits = 3 + 1; |
| 103 | if (*++p) { | 107 | |
| 104 | int scale; | 108 | end_ms = bb_strtou(opt_t, &p, 10); |
| 105 | ts.tv_usec = bb_strtou(p, &p2, 10); | 109 | if (end_ms > UINT_MAX / 2048) /* be safely away from overflow */ |
| 106 | if (errno) | 110 | end_ms = UINT_MAX / 2048; |
| 107 | return "invalid timeout"; | 111 | |
| 108 | scale = p2 - p; | 112 | if (errno) { |
| 109 | /* normalize to usec */ | 113 | /* EINVAL = number is ok, but not NUL terminated */ |
| 110 | if (scale > 6) | 114 | if (errno != EINVAL || *p != '.') |
| 115 | return "invalid timeout"; | ||
| 116 | /* Do not check the rest: bash allows "0.123456xyz" */ | ||
| 117 | while (*++p && --frac_digits) { | ||
| 118 | end_ms *= 10; | ||
| 119 | end_ms += (*p - '0'); | ||
| 120 | if ((unsigned char)(*p - '0') > 9) | ||
| 111 | return "invalid timeout"; | 121 | return "invalid timeout"; |
| 112 | while (scale++ < 6) | ||
| 113 | ts.tv_usec *= 10; | ||
| 114 | } | 122 | } |
| 115 | } else if (ts.tv_sec < 0 || errno) { | ||
| 116 | return "invalid timeout"; | ||
| 117 | } | 123 | } |
| 118 | if (!(ts.tv_sec | ts.tv_usec)) { /* both are 0? */ | 124 | while (--frac_digits > 0) { |
| 119 | return "invalid timeout"; | 125 | end_ms *= 10; |
| 120 | } | 126 | } |
| 121 | #endif /* if 0 */ | ||
| 122 | } | 127 | } |
| 128 | |||
| 123 | fd = STDIN_FILENO; | 129 | fd = STDIN_FILENO; |
| 124 | if (opt_u) { | 130 | if (opt_u) { |
| 125 | fd = bb_strtou(opt_u, NULL, 10); | 131 | fd = bb_strtou(opt_u, NULL, 10); |
| @@ -127,6 +133,19 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 127 | return "invalid file descriptor"; | 133 | return "invalid file descriptor"; |
| 128 | } | 134 | } |
| 129 | 135 | ||
| 136 | if (opt_t && end_ms == 0) { | ||
| 137 | /* "If timeout is 0, read returns immediately, without trying | ||
| 138 | * to read any data. The exit status is 0 if input is available | ||
| 139 | * on the specified file descriptor, non-zero otherwise." | ||
| 140 | * bash seems to ignore -p PROMPT for this use case. | ||
| 141 | */ | ||
| 142 | int r; | ||
| 143 | pfd[0].events = POLLIN; | ||
| 144 | r = poll(pfd, 1, /*timeout:*/ 0); | ||
| 145 | /* Return 0 only if poll returns 1 ("one fd ready"), else return 1: */ | ||
| 146 | return (const char *)(uintptr_t)(r <= 0); | ||
| 147 | } | ||
| 148 | |||
| 130 | if (opt_p && isatty(fd)) { | 149 | if (opt_p && isatty(fd)) { |
| 131 | fputs(opt_p, stderr); | 150 | fputs(opt_p, stderr); |
| 132 | fflush_all(); | 151 | fflush_all(); |
| @@ -161,21 +180,24 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 161 | retval = (const char *)(uintptr_t)0; | 180 | retval = (const char *)(uintptr_t)0; |
| 162 | startword = 1; | 181 | startword = 1; |
| 163 | backslash = 0; | 182 | backslash = 0; |
| 164 | if (end_ms) /* NB: end_ms stays nonzero: */ | 183 | if (opt_t) |
| 165 | end_ms = ((unsigned)monotonic_ms() + end_ms) | 1; | 184 | end_ms += (unsigned)monotonic_ms(); |
| 166 | buffer = NULL; | 185 | buffer = NULL; |
| 167 | bufpos = 0; | 186 | bufpos = 0; |
| 168 | do { | 187 | do { |
| 169 | char c; | 188 | char c; |
| 170 | struct pollfd pfd[1]; | ||
| 171 | int timeout; | 189 | int timeout; |
| 172 | 190 | ||
| 173 | if ((bufpos & 0xff) == 0) | 191 | if ((bufpos & 0xff) == 0) |
| 174 | buffer = xrealloc(buffer, bufpos + 0x101); | 192 | buffer = xrealloc(buffer, bufpos + 0x101); |
| 175 | 193 | ||
| 176 | timeout = -1; | 194 | timeout = -1; |
| 177 | if (end_ms) { | 195 | if (opt_t) { |
| 178 | timeout = end_ms - (unsigned)monotonic_ms(); | 196 | timeout = end_ms - (unsigned)monotonic_ms(); |
| 197 | /* ^^^^^^^^^^^^^ all values are unsigned, | ||
| 198 | * wrapping math is used here, good even if | ||
| 199 | * 32-bit unix time wrapped (year 2038+). | ||
| 200 | */ | ||
| 179 | if (timeout <= 0) { /* already late? */ | 201 | if (timeout <= 0) { /* already late? */ |
| 180 | retval = (const char *)(uintptr_t)1; | 202 | retval = (const char *)(uintptr_t)1; |
| 181 | goto ret; | 203 | goto ret; |
| @@ -187,9 +209,8 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 187 | * regardless of SA_RESTART-ness of that signal! | 209 | * regardless of SA_RESTART-ness of that signal! |
| 188 | */ | 210 | */ |
| 189 | errno = 0; | 211 | errno = 0; |
| 190 | pfd[0].fd = fd; | ||
| 191 | pfd[0].events = POLLIN; | 212 | pfd[0].events = POLLIN; |
| 192 | if (poll(pfd, 1, timeout) != 1) { | 213 | if (poll(pfd, 1, timeout) <= 0) { |
| 193 | /* timed out, or EINTR */ | 214 | /* timed out, or EINTR */ |
| 194 | err = errno; | 215 | err = errno; |
| 195 | retval = (const char *)(uintptr_t)1; | 216 | retval = (const char *)(uintptr_t)1; |
| @@ -272,6 +293,7 @@ shell_builtin_read(void FAST_FUNC (*setvar)(const char *name, const char *val), | |||
| 272 | 293 | ||
| 273 | errno = err; | 294 | errno = err; |
| 274 | return retval; | 295 | return retval; |
| 296 | #undef fd | ||
| 275 | } | 297 | } |
| 276 | 298 | ||
| 277 | /* ulimit builtin */ | 299 | /* ulimit builtin */ |
