1
0
Fork 0
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:
Johnothan King 2021-04-14 19:37:57 -07:00 committed by GitHub
parent df47731d7d
commit 2c38fb93fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 254 additions and 43 deletions

View file

@ -769,6 +769,8 @@ Pathcomp_t *path_absolute(Shell_t *shp,register const char *name, Pathcomp_t *pp
{
sh_sigcheck(shp);
shp->bltin_dir = 0;
/* In this loop, oldpp is the current pointer.
pp is the next pointer. */
while(oldpp=pp)
{
pp = path_nextcomp(shp,pp,name,0);
@ -901,10 +903,10 @@ Pathcomp_t *path_absolute(Shell_t *shp,register const char *name, Pathcomp_t *pp
np->nvflag = n;
}
}
if(f<0 && errno!=ENOENT)
noexec = errno;
if(!pp || f>=0)
break;
if(errno!=ENOENT)
noexec = errno;
}
if(f<0)
{
@ -1007,7 +1009,8 @@ noreturn void path_exec(Shell_t *shp,register const char *arg0,register char *ar
char **envp;
const char *opath;
Pathcomp_t *libpath, *pp=0;
int slash=0;
int slash=0, not_executable=0;
pid_t spawnpid;
nv_setlist(local,NV_EXPORT|NV_IDENT|NV_ASSIGN,0);
envp = sh_envgen();
if(strchr(arg0,'/'))
@ -1038,18 +1041,40 @@ noreturn void path_exec(Shell_t *shp,register const char *arg0,register char *ar
}
else
opath = arg0;
path_spawn(shp,opath,argv,envp,libpath,0);
spawnpid = path_spawn(shp,opath,argv,envp,libpath,0);
if(spawnpid==-1 && shp->path_err!=ENOENT)
{
/*
* A command was found but it couldn't be executed.
* POSIX specifies that the shell should continue to search for the
* command in PATH and return 126 only when it can't find an executable
* file in other elements of PATH.
*/
not_executable = shp->path_err;
}
while(pp && (pp->flags&PATH_FPATH))
pp = path_nextcomp(shp,pp,arg0,0);
}
while(pp);
/* force an exit */
((struct checkpt*)shp->jmplist)->mode = SH_JMPEXIT;
if((errno=shp->path_err)==ENOENT)
errno = not_executable ? not_executable : shp->path_err;
switch(errno)
{
/* the first two cases return exit status 127 (the command wasn't in the PATH) */
case ENOENT:
errormsg(SH_DICT,ERROR_exit(ERROR_NOENT),e_found,arg0);
else
UNREACHABLE();
#ifdef ENAMETOOLONG
case ENAMETOOLONG:
errormsg(SH_DICT,ERROR_exit(ERROR_NOENT),e_toolong,arg0);
UNREACHABLE();
#endif
/* other cases return exit status 126 (the command was found, but wasn't executable) */
default:
errormsg(SH_DICT,ERROR_system(ERROR_NOEXEC),e_exec,arg0);
UNREACHABLE();
UNREACHABLE();
}
}
pid_t path_spawn(Shell_t *shp,const char *opath,register char **argv, char **envp, Pathcomp_t *libpath, int spawn)
@ -1187,6 +1212,8 @@ pid_t path_spawn(Shell_t *shp,const char *opath,register char **argv, char **env
return(pid);
switch(shp->path_err = errno)
{
case EISDIR:
return -1;
case ENOEXEC:
#if SHOPT_SUID_EXEC
case EPERM: