1
0
Fork 0
mirror of git://git.code.sf.net/p/cdesktopenv/code synced 2025-03-09 15:50:02 +00:00
cde/src/cmd/ksh93/include/shell.h
Johnothan King 2c38fb93fd
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.
2021-04-15 03:37:57 +01:00

312 lines
9.3 KiB
C

/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2012 AT&T Intellectual Property *
* and is licensed under the *
* Eclipse Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* http://www.eclipse.org/org/documents/epl-v10.html *
* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
#ifndef SH_INTERACTIVE
/*
* David Korn
* AT&T Labs
*
* Interface definitions for shell command language
*
*/
#define SH_VERSION 20071012
#include <ast.h>
#include <cdt.h>
#ifdef _SH_PRIVATE
# include "name.h"
#else
# include <nval.h>
#endif /* _SH_PRIVATE */
/* options */
typedef struct
{
unsigned long v[4];
}
Shopt_t;
typedef struct Shell_s Shell_t;
#include <shcmd.h>
typedef void (*Shinit_f)(Shell_t*, int);
#ifndef SH_wait_f_defined
typedef int (*Shwait_f)(int, long, int);
# define SH_wait_f_defined
#endif
union Shnode_u;
typedef union Shnode_u Shnode_t;
/*
* Shell state flags. Used with sh_isstate(), sh_onstate(), sh_offstate().
* See also shell options below. States 0-5 are also used as shell options.
*/
#define SH_NOFORK 0 /* set when fork not necessary */
#define SH_FORKED 7 /* set when process has been forked */
#define SH_PROFILE 8 /* set when processing profiles */
#define SH_NOALIAS 9 /* do not expand non-exported aliases */
#define SH_NOTRACK 10 /* set to disable sftrack() function */
#define SH_STOPOK 11 /* set for stopable builtins */
#define SH_GRACE 12 /* set for timeout grace period */
#define SH_TIMING 13 /* set while timing pipelines */
#define SH_DEFPATH 14 /* set when using default path */
#define SH_INIT 15 /* set when initializing the shell */
#define SH_TTYWAIT 16 /* waiting for keyboard input */
#define SH_FCOMPLETE 17 /* set for filename completion */
#define SH_PREINIT 18 /* set with SH_INIT before parsing options */
#define SH_COMPLETE 19 /* set for command completion */
#define SH_INTESTCMD 20 /* set while test/[ command is being run */
#define SH_XARG 21 /* set while in xarg (command -x) mode */
/*
* Shell options (set -o). Used with sh_isoption(), sh_onoption(), sh_offoption().
* There can be a maximum of 256 (0..0xFF) shell options.
* The short option letters are defined in optksh[] and flagval[] in sh/args.c.
* The long option names are defined in shtab_options[] in data/options.c.
*/
#define SH_CFLAG 0
#define SH_HISTORY 1 /* used also as a state */
#define SH_ERREXIT 2 /* used also as a state */
#define SH_VERBOSE 3 /* used also as a state */
#define SH_MONITOR 4 /* used also as a state */
#define SH_INTERACTIVE 5 /* used also as a state */
#define SH_RESTRICTED 6
#define SH_XTRACE 7
#define SH_KEYWORD 8
#define SH_NOUNSET 9
#define SH_NOGLOB 10
#define SH_ALLEXPORT 11
#if SHOPT_PFSH
#define SH_PFSH 12
#endif
#define SH_IGNOREEOF 13
#define SH_NOCLOBBER 14
#define SH_MARKDIRS 15
#define SH_BGNICE 16
#if SHOPT_VSH
#define SH_VI 17
#define SH_VIRAW 18
#endif
#define SH_TFLAG 19
#define SH_TRACKALL 20
#define SH_SFLAG 21
#define SH_NOEXEC 22
#if SHOPT_ESH
#define SH_GMACS 24
#define SH_EMACS 25
#endif
#define SH_PRIVILEGED 26
#define SH_NOLOG 28
#define SH_NOTIFY 29
#define SH_DICTIONARY 30
#define SH_PIPEFAIL 32
#define SH_GLOBSTARS 33
#if SHOPT_GLOBCASEDET
#define SH_GLOBCASEDET 34
#endif
#define SH_RC 35
#define SH_SHOWME 36
#define SH_LETOCTAL 37
#if SHOPT_BRACEPAT
#define SH_BRACEEXPAND 42
#endif
#define SH_POSIX 46
#define SH_MULTILINE 47
#define SH_NOBACKSLCTRL 48
#define SH_LOGIN_SHELL 67
#define SH_NOUSRPROFILE 79 /* internal use only */
#define SH_COMMANDLINE 0x100 /* bit flag for invocation-only options ('set -o' cannot change them) */
/*
* passed as flags to builtins in Nambltin_t struct when BLT_OPTIM is on
*/
#define SH_BEGIN_OPTIM 0x1
#define SH_END_OPTIM 0x2
/* The following type is used for error messages */
/* error messages */
extern const char e_found[];
#ifdef ENAMETOOLONG
extern const char e_toolong[];
#endif
extern const char e_format[];
extern const char e_number[];
extern const char e_restricted[];
extern const char e_recursive[];
extern char e_version[];
typedef struct sh_scope
{
struct sh_scope *par_scope;
int argc;
char **argv;
char *cmdname;
char *filename;
char *funname;
int lineno;
Dt_t *var_tree;
struct sh_scope *self;
} Shscope_t;
/*
* Saves the state of the shell
*/
struct Shell_s
{
Shopt_t options; /* set -o options */
Dt_t *var_tree; /* for shell variables */
Dt_t *fun_tree; /* for shell functions */
Dt_t *alias_tree; /* for alias names */
Dt_t *bltin_tree; /* for builtin commands */
Shscope_t *topscope; /* pointer to top-level scope */
int inlineno; /* line number of current input file */
int exitval; /* exit status of the command currently being run */
int savexit; /* $? == exit status of the last command executed */
unsigned char trapnote; /* set when trap/signal is pending */
char shcomp; /* set when running shcomp */
unsigned int subshell; /* set for virtual subshell */
#ifdef _SH_PRIVATE
_SH_PRIVATE
#endif /* _SH_PRIVATE */
};
/* used for builtins */
typedef struct Libcomp_s
{
void* dll;
char* lib;
dev_t dev;
ino_t ino;
unsigned int attr;
} Libcomp_t;
extern Libcomp_t *liblist;
/* flags for sh_parse */
#define SH_NL 1 /* Treat new-lines as ; */
#define SH_EOF 2 /* EOF causes syntax error */
/* symbolic values for sh_iogetiop */
#define SH_IOCOPROCESS (-2)
#define SH_IOHISTFILE (-3)
#include <cmd.h>
/* symbolic value for sh_fdnotify */
#define SH_FDCLOSE (-1)
#undef getenv /* -lshell provides its own */
#if defined(__EXPORT__) && defined(_DLL)
# ifdef _BLD_shell
# define extern __EXPORT__
# endif /* _BLD_shell */
#endif /* _DLL */
extern Dt_t *sh_bltin_tree(void);
extern void sh_subfork(void);
extern Shell_t *sh_init(int,char*[],Shinit_f);
extern int sh_reinit(char*[]);
extern int sh_eval(Sfio_t*,int);
extern void sh_delay(double,int);
extern void *sh_parse(Shell_t*, Sfio_t*,int);
extern int sh_trap(const char*,int);
extern int sh_fun(Namval_t*,Namval_t*, char*[]);
extern int sh_funscope(int,char*[],int(*)(void*),void*,int);
extern Sfio_t *sh_iogetiop(int,int);
extern int sh_main(int, char*[], Shinit_f);
extern int sh_run(int, char*[]);
extern void sh_menu(Sfio_t*, int, char*[]);
extern Namval_t *sh_addbuiltin(const char*, int(*)(int, char*[],Shbltin_t*), void*);
extern char *sh_fmtq(const char*);
extern char *sh_fmtqf(const char*, int, int);
extern Sfdouble_t sh_strnum(const char*, char**, int);
extern int sh_access(const char*,int);
extern int sh_close(int);
extern int sh_chdir(const char*);
extern int sh_dup(int);
extern void sh_exit(int);
extern int sh_fchdir(int);
extern int sh_fcntl(int, int, ...);
extern Sfio_t *sh_fd2sfio(int);
extern int (*sh_fdnotify(int(*)(int,int)))(int,int);
extern Shell_t *sh_getinterp(void);
extern int sh_open(const char*, int, ...);
extern int sh_openmax(void);
extern Sfio_t *sh_pathopen(const char*);
extern ssize_t sh_read(int, void*, size_t);
extern ssize_t sh_write(int, const void*, size_t);
extern off_t sh_seek(int, off_t, int);
extern int sh_pipe(int[]);
extern mode_t sh_umask(mode_t);
extern void *sh_waitnotify(Shwait_f);
extern Shscope_t *sh_getscope(int,int);
extern Shscope_t *sh_setscope(Shscope_t*);
extern void sh_sigcheck(Shell_t*);
extern unsigned long sh_isoption(int);
extern unsigned long sh_onoption(int);
extern unsigned long sh_offoption(int);
extern int sh_waitsafe(void);
extern int sh_exec(const Shnode_t*,int);
/*
* direct access to sh is obsolete, use sh_getinterp() instead
*/
#if !defined(_SH_PRIVATE) && defined(__IMPORT__) && !defined(_BLD_shell)
extern __IMPORT__ Shell_t sh;
#else
extern Shell_t sh;
#endif
#ifdef _DLL
# undef extern
#endif /* _DLL */
#define chdir(a) sh_chdir(a)
#define fchdir(a) sh_fchdir(a)
#ifndef _SH_PRIVATE
# define access(a,b) sh_access(a,b)
# define close(a) sh_close(a)
# define exit(a) sh_exit(a)
# define fcntl(a,b,c) sh_fcntl(a,b,c)
# define pipe(a) sh_pipe(a)
# define read(a,b,c) sh_read(a,b,c)
# define write(a,b,c) sh_write(a,b,c)
# define umask(a) sh_umask(a)
# define dup sh_dup
# if _lib_lseek64
# define open64 sh_open
# define lseek64 sh_seek
# else
# define open sh_open
# define lseek sh_seek
# endif
#endif /* !_SH_PRIVATE */
#define SH_SIGSET 4
#define SH_EXITSIG 0400 /* signal exit bit */
#define SH_EXITMASK (SH_EXITSIG-1) /* normal exit status bits */
#define SH_RUNPROG -1022 /* needs to be negative and < 256 */
#endif /* SH_INTERACTIVE */