mirror of
git://git.code.sf.net/p/cdesktopenv/code
synced 2025-03-09 15:50:02 +00:00
Fix the exit status returned when a command isn't executable (#273)
Previous discussion: https://github.com/att/ast/issues/485 If ksh attempts to execute a non-executable command found in the PATH, in some instances the error message and return status are incorrect. In the example below, ksh returns with exit status 126 when using the -c execve(2) optimization or when using fork(2) in an interactive shell. However, using posix_spawn(3) causes the exit status to change: $ echo 'print cannot execute' > /tmp/x # Runs command with spawnveg (i.e., posix_spawn or vfork) $ ksh -c 'PATH=/tmp; x; echo $?' ksh: x: not found 127 # Runs command with execve $ ksh -c 'PATH=/tmp; x'; echo $? ksh: x: cannot execute [Permission denied] 126 # Runs command with fork $ ksh -ic 'PATH=/tmp; x; echo $?' ksh: x: cannot execute [Permission denied] 126 Since 'x' is in the PATH but can't be executed, the correct exit status is 126, not 127. It's worth noting this bug doesn't cause the regression tests to fail with ksh93u+m, but it does cause one test to fail when run under dtksh: path.sh[706]: Long nonexistent command name: got status 126, '' This commit backports various fixes for this bug from ksh2020, with additional fixes applied (since there were still some additional issues the ksh2020 patch didn't fix). The lacking regression test for exit status 126 in path.sh has been rewritten to test for more scenarios where ksh failed to return the correct error message and/or exit status. I can also confirm with this patch applied the path.sh regression tests now pass when run under dtksh. src/cmd/ksh93/sh/path.c: - Add a comment to path_absolute() describing 'oldpp' is the current pointer in the while loop and 'pp' is the next pointer. Backported from:a6cad450- The patch from ksh2020 didn't fix this bug in the SHOPT_SPAWN code (because ksh2020 prefers fork(2)), so issues with the exit status could still occur when using spawnveg. To fix this, always set 'noexec' to the value of errno if can_execute fails. Before this fix, errno was discarded if 'pp' was a null pointer and can_execute failed. - If a command couldn't be executed and the error wasn't ENOENT, save errno in a 'not_executable' variable. If an executable command couldn't be found in the PATH, exit with status 126 and set errno to the saved value. This was based on a ksh2020 bugfix, but it has been reworked a little bit to fix a bug that caused a mismatch between the error message shown and errno. Example with a non-executable file in PATH: $ nonexec ksh2020: nonexec: cannot execute [No such file or directory] The ksh2020 patch: <https://github.com/att/ast/pull/493> - Backport a ksh2020 bugfix for directories in the PATH when running one of the added regression tests on OpenBSD: https://github.com/att/ast/pull/767 src/cmd/ksh93/data/msg.c, src/cmd/ksh93/include/shell.h, src/cmd/ksh93/sh/{path,xec}.c: - If a command name is too long (ENAMETOOLONG), then it wasn't found in the PATH. For that case return exit status 127, like for ENOENT. src/cmd/ksh93/tests/path.sh: - Replace the old test with a new set of more extensive tests. These tests check the error message and exit status when ksh attempts to run a command using any of the following: - execve(2), used with the last command run with -c (*A tests). - posix_spawn(3)/vfork(2), used in noninteractive scripts (*B tests). - fork(2), used in interactive shells with job control (*C tests). - command -x (*D tests). - exec(1) (*E tests). - Add a regression test from ksh2020 for attempting to execute a directory: https://github.com/att/ast/pull/758 src/lib/libast/include/ast.h, src/lib/libast/include/wait.h: - Avoid bitshifts in macros for static error codes. The return values of command not found and exec related errors are static values and should not require any macro magic for calculation. Backported from:c073b102- Simplify EXIT_* and W* macros to use 8 bits.
This commit is contained in:
parent
df47731d7d
commit
2c38fb93fd
9 changed files with 254 additions and 43 deletions
|
|
@ -180,18 +180,7 @@ builtin -d date 2> /dev/null
|
|||
if [[ $(PATH=:/usr/bin; date) != 'hello' ]]
|
||||
then err_exit "leading : in path not working"
|
||||
fi
|
||||
(
|
||||
PATH=$PWD:
|
||||
builtin chmod
|
||||
print 'print cannot execute' > noexec
|
||||
chmod 644 noexec
|
||||
if [[ ! -x noexec ]]
|
||||
then noexec > /dev/null 2>&1
|
||||
else exit 126
|
||||
fi
|
||||
)
|
||||
status=$?
|
||||
[[ $status == 126 ]] || err_exit "exit status of non-executable is $status -- 126 expected"
|
||||
|
||||
builtin -d rm 2> /dev/null
|
||||
chmod=$(whence chmod)
|
||||
rm=$(whence rm)
|
||||
|
|
@ -734,5 +723,185 @@ PATH=$savePATH
|
|||
[[ -z $got ]] || err_exit "PATH search inconsistent after changing PATH in subshare (got $(printf %q "$got"))"
|
||||
|
||||
# ======
|
||||
exit $((Errors<125?Errors:125))
|
||||
# POSIX: If a command is found but isn't executable, the exit status should be 126.
|
||||
# The tests are arranged as follows:
|
||||
# Test *A runs commands with the -c execve(2) optimization.
|
||||
# Test *B runs commands with spawnveg (i.e., with posix_spawn(3) or vfork(2)).
|
||||
# Test *C runs commands with fork(2) in an interactive shell.
|
||||
# Test *D runs commands with 'command -x'.
|
||||
# Test *E runs commands with 'exec'.
|
||||
# https://github.com/att/ast/issues/485
|
||||
rm -rf noexecute
|
||||
print 'print cannot execute' > noexecute
|
||||
mkdir emptydir cmddir
|
||||
exp=126
|
||||
PATH=$PWD $SHELL -c 'noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 1A: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 1B: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -ic 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 1C: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'command -x noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 1D: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'exec noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 1E: exit status of exec'd non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# Add an empty directory where the command isn't found.
|
||||
PATH=$PWD:$PWD/emptydir $SHELL -c 'noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 2A: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/emptydir $SHELL -c 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 2B: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/emptydir $SHELL -ic 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 2C: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/emptydir $SHELL -c 'command -x noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 2D: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/emptydir $SHELL -c 'exec noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 2E: exit status of exec'd non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# If an executable command is found after a non-executable command, skip the non-executable one.
|
||||
print 'true' > cmddir/noexecute
|
||||
chmod +x cmddir/noexecute
|
||||
exp=0
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'noexecute'
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 3A: failed to run executable command after encountering non-executable command" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'noexecute; exit $?'
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 3B: failed to run executable command after encountering non-executable command" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -ic 'noexecute; exit $?'
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 3C: failed to run executable command after encountering non-executable command" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'command -x noexecute; exit $?'
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 3D: failed to run executable command after encountering non-executable command" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'exec noexecute'
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 3E: failed to run exec'd executable command after encountering non-executable command" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# Same test as above, but with a directory of the same name in the PATH.
|
||||
rm "$PWD/noexecute"
|
||||
mkdir "$PWD/noexecute"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 4A: failed to run executable command after encountering directory with same name in PATH" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 4B: failed to run executable command after encountering directory with same name in PATH" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -ic 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 4C: failed to run executable command after encountering directory with same name in PATH" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'command -x noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 4D: failed to run executable command after encountering directory with same name in PATH" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD:$PWD/cmddir $SHELL -c 'exec noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 4E: failed to run exec'd executable command after encountering directory with same name in PATH" \
|
||||
"(expected $exp, got $got)"
|
||||
# Don't treat directories as commands.
|
||||
# https://github.com/att/ast/issues/757
|
||||
mkdir cat
|
||||
PATH=".:$PATH" cat < /dev/null || err_exit "Test 4F: directories should not be treated as executables"
|
||||
|
||||
# Test attempts to run directories located in the PATH.
|
||||
exp=126
|
||||
PATH=$PWD $SHELL -c 'noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 5A: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 5B: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -ic 'noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 5C: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'command -x noexecute; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 5D: exit status of non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c 'exec noexecute' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 5E: exit status of exec'd non-executable command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# Tests for attempting to run a non-existent command.
|
||||
exp=127
|
||||
PATH=/dev/null $SHELL -c 'nonexist' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 6A: exit status of non-existent command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=/dev/null $SHELL -c 'nonexist; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 6B: exit status of non-existent command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=/dev/null $SHELL -ic 'nonexist; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 6C: exit status of non-existent command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=/dev/null $SHELL -c 'command -x nonexist; exit $?' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 6D: exit status of non-existent command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=/dev/null $SHELL -c 'exec nonexist' > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 6E: exit status of exec'd non-existent command wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# Tests for attempting to use a command name that's too long.
|
||||
# To make the error messages readable, the long string is replaced
|
||||
# with 'LONG_CMD_NAME' in the err_exit output.
|
||||
long_cmd=$(awk -v ORS= 'BEGIN { for(i=0;i<500;i++) print "xxxxxxxxxx"; }')
|
||||
exp=127
|
||||
PATH=$PWD $SHELL -c "$long_cmd" > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 7A: exit status or error message for command with long name wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c "$long_cmd; exit \$?" > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 7B: exit status or error message for command with long name wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -ic "$long_cmd; exit \$?" > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 7C: exit status or error message for command with long name wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c "command -x $long_cmd; exit \$?" > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 7D: exit status or error message for command with long name wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
PATH=$PWD $SHELL -c "exec $long_cmd" > /dev/null 2>&1
|
||||
got=$?
|
||||
[[ $exp == $got ]] || err_exit "Test 7E: exit status or error message for exec'd command with long name wrong" \
|
||||
"(expected $exp, got $got)"
|
||||
|
||||
# ======
|
||||
exit $((Errors<125?Errors:125))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue