From dcd8df258a402658ad1fdc018a245b06a610ca8d Mon Sep 17 00:00:00 2001 From: Ron Yorston Date: Tue, 1 Jul 2025 09:47:14 +0100 Subject: shell: improve bash compatibility of read built-in Make the read built-in more compatible with bash: - Return an exit code of 142 on timeout. - When the timeout expires before a newline is detected in the input bash captures the partial input. This behaviour is new since bash version 4.4. BusyBox shells had the pre-4.4 behaviour where the input was lost. Update the tests to suit and fix a couple of compiler errors in the testsuite. function old new delta builtin_read 154 174 +20 readcmd 213 228 +15 shell_builtin_read 1364 1370 +6 ------------------------------------------------------------------------------ (add/remove: 0/0 grow/shrink: 3/0 up/down: 41/0) Total: 41 bytes Signed-off-by: Ron Yorston Signed-off-by: Denys Vlasenko --- shell/ash.c | 5 +++++ shell/ash_test/ash-read/read_ifs2.right | 9 +++++++++ shell/ash_test/ash-read/read_ifs2.tests | 9 +++++++++ shell/ash_test/ash-read/read_t.right | 8 ++++---- shell/ash_test/ash-read/read_t.tests | 18 +++++++++--------- shell/ash_test/printenv.c | 4 +--- shell/ash_test/recho.c | 2 +- shell/hush.c | 5 +++++ shell/hush_test/hush-read/read_t.right | 8 ++++---- shell/hush_test/hush-read/read_t.tests | 18 +++++++++--------- shell/shell_common.c | 10 +++++++--- 11 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 shell/ash_test/ash-read/read_ifs2.right create mode 100755 shell/ash_test/ash-read/read_ifs2.tests (limited to 'shell') diff --git a/shell/ash.c b/shell/ash.c index 9173b8608..92b1df5f8 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -14395,6 +14395,11 @@ readcmd(int argc UNUSED_PARAM, char **argv UNUSED_PARAM) goto again; } + if ((uintptr_t)r == 2) /* -t SEC timeout? */ + /* bash: "The exit status is greater than 128 if the timeout is exceeded." */ + /* The actual value observed with bash 5.2.15: */ + return 128 + SIGALRM; + if ((uintptr_t)r > 1) ash_msg_and_raise_error(r); diff --git a/shell/ash_test/ash-read/read_ifs2.right b/shell/ash_test/ash-read/read_ifs2.right new file mode 100644 index 000000000..797137dae --- /dev/null +++ b/shell/ash_test/ash-read/read_ifs2.right @@ -0,0 +1,9 @@ +|X|Y:Z:| +|X|Y:Z| +|X|Y| +|X|Y| +|X|| +|X|| +||| +Whitespace should be trimmed too: +|X|Y| diff --git a/shell/ash_test/ash-read/read_ifs2.tests b/shell/ash_test/ash-read/read_ifs2.tests new file mode 100755 index 000000000..f01a68978 --- /dev/null +++ b/shell/ash_test/ash-read/read_ifs2.tests @@ -0,0 +1,9 @@ +echo "X:Y:Z:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:Z" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:Y" | (IFS=": " read x y; echo "|$x|$y|") +echo "X:" | (IFS=": " read x y; echo "|$x|$y|") +echo "X" | (IFS=": " read x y; echo "|$x|$y|") +echo "" | (IFS=": " read x y; echo "|$x|$y|") +echo Whitespace should be trimmed too: +echo "X:Y : " | (IFS=": " read x y; echo "|$x|$y|") diff --git a/shell/ash_test/ash-read/read_t.right b/shell/ash_test/ash-read/read_t.right index 04126cbe6..3eedae275 100644 --- a/shell/ash_test/ash-read/read_t.right +++ b/shell/ash_test/ash-read/read_t.right @@ -1,4 +1,4 @@ ->< ->< ->test< ->test< +>te:142< +>:142< +>test:0< +>test:0< diff --git a/shell/ash_test/ash-read/read_t.tests b/shell/ash_test/ash-read/read_t.tests index d65f1aeaa..9fbeec517 100755 --- a/shell/ash_test/ash-read/read_t.tests +++ b/shell/ash_test/ash-read/read_t.tests @@ -1,10 +1,10 @@ -# bash 3.2 outputs: +# bash 5.2 outputs: -# >< -{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply<") -# >< -{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<") -# >test< -{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply<") -# >test< -{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<") +# >te:142< +{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply:$?<") +# >:142< +{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply:$?<") +# >test:0< +{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply:$?<") +# >test:0< +{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply:$?<") diff --git a/shell/ash_test/printenv.c b/shell/ash_test/printenv.c index c86308d3b..f0f41984d 100644 --- a/shell/ash_test/printenv.c +++ b/shell/ash_test/printenv.c @@ -31,9 +31,7 @@ extern char **environ; int -main (argc, argv) - int argc; - char **argv; +main (int argc, char **argv) { register char **envp, *eval; int len; diff --git a/shell/ash_test/recho.c b/shell/ash_test/recho.c index 42a5feafd..7e96b14cc 100644 --- a/shell/ash_test/recho.c +++ b/shell/ash_test/recho.c @@ -27,7 +27,7 @@ #include #include -void strprint(); +void strprint(char *); int main(int argc, char **argv) { diff --git a/shell/hush.c b/shell/hush.c index 4a97293cc..37cfecc08 100644 --- a/shell/hush.c +++ b/shell/hush.c @@ -11175,6 +11175,11 @@ static int FAST_FUNC builtin_read(char **argv) goto again; } + if ((uintptr_t)r == 2) /* -t SEC timeout? */ + /* bash: "The exit status is greater than 128 if the timeout is exceeded." */ + /* The actual value observed with bash 5.2.15: */ + return 128 + SIGALRM; + if ((uintptr_t)r > 1) { bb_simple_error_msg(r); r = (char*)(uintptr_t)1; diff --git a/shell/hush_test/hush-read/read_t.right b/shell/hush_test/hush-read/read_t.right index 04126cbe6..3eedae275 100644 --- a/shell/hush_test/hush-read/read_t.right +++ b/shell/hush_test/hush-read/read_t.right @@ -1,4 +1,4 @@ ->< ->< ->test< ->test< +>te:142< +>:142< +>test:0< +>test:0< diff --git a/shell/hush_test/hush-read/read_t.tests b/shell/hush_test/hush-read/read_t.tests index d65f1aeaa..9fbeec517 100755 --- a/shell/hush_test/hush-read/read_t.tests +++ b/shell/hush_test/hush-read/read_t.tests @@ -1,10 +1,10 @@ -# bash 3.2 outputs: +# bash 5.2 outputs: -# >< -{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply<") -# >< -{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply<") -# >test< -{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply<") -# >test< -{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply<") +# >te:142< +{ echo -n 'te'; sleep 2; echo 'st'; } | (read -t 1 reply; echo ">$reply:$?<") +# >:142< +{ sleep 2; echo 'test'; } | (read -t 1 reply; echo ">$reply:$?<") +# >test:0< +{ echo -n 'te'; sleep 1; echo 'st'; } | (read -t 2 reply; echo ">$reply:$?<") +# >test:0< +{ sleep 1; echo 'test'; } | (read -t 2 reply; echo ">$reply:$?<") diff --git a/shell/shell_common.c b/shell/shell_common.c index e5c2cefb3..9a03f7265 100644 --- a/shell/shell_common.c +++ b/shell/shell_common.c @@ -204,8 +204,8 @@ shell_builtin_read(struct builtin_read_params *params) * 32-bit unix time wrapped (year 2038+). */ if (timeout <= 0) { /* already late? */ - retval = (const char *)(uintptr_t)1; - goto ret; + retval = (const char *)(uintptr_t)2; + break; } } @@ -217,8 +217,12 @@ shell_builtin_read(struct builtin_read_params *params) pfd[0].events = POLLIN; //TODO race with a signal arriving just before the poll! if (poll(pfd, 1, timeout) <= 0) { - /* timed out, or EINTR */ + /* timed out, or some error */ err = errno; + if (!err) { + retval = (const char *)(uintptr_t)2; + break; + } retval = (const char *)(uintptr_t)1; goto ret; } -- cgit v1.2.3-55-g6feb