Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 4 Sep 1997 11:20:48 +1000
From:      Bruce Evans <bde@zeta.org.au>
To:        bde@zeta.org.au, bugs@FreeBSD.ORG
Subject:   Re: fixes for execlp() and execvp()
Message-ID:  <199709040120.LAA17438@godzilla.zeta.org.au>

next in thread | raw e-mail | index | archive | help
I wrote:
>Log:
>Handle execve() failure in execlp() and execvp() the same (*) as in sh(1):
>- don't retry for ETXTBSY.
>- continue with the next component of the search path after most errors.
>  Previously we returned after most errors.  This change is significant
>  for at least the following errors:
>...
>It is bogus to keep searching after finding an accessible executable, but
>that's what sh(1)  does and what POSIX.2 seems to require.

On more careful reading, POSIX.2 seems to say that the search stops after
finding "the name with the execute permissions set that would allow the
process to execute it".  This seems to require an implementation that
stats each file until it finds one with suitable execute permissions bits
set, and then attempts to exec just once.  The usual implementation which
attempts to exec each file (to avoid races) is wrong because it is too
sophisticated - it notices things that stat() can't notice:
1. files that have suitable permissions but are on a MNT_NOEXEC file system
   are not executable
2. special files are not executable (POSIX.2 may mention this, but I can't
   see where)
3. files with bogus interpreters, e.g., one containing "#!/bin/zz".  execve()
   returns ENOENT for this (if its permissions allow execution).

>(*) sh(1) currently sets $? wrong after finding an inaccessible executable.
>EACCES should be sticky like it already was for execlp().

This is wrong too.  sh(1)'s handling of errno is close to what is required
for setting the status POSIXly (127 = command not found; 126 = command found,
but can not be executed due to non-accessibility or error exec'ing it).

Here is another attempt at fixing execlp() and execvp().  It follows
historical practice again except for not retrying ETXTBSY, being more
careful about ambiguity of EACCES and some other errnos, and not returning
spurious errnos that may occur during the search (if nothing is found,
just return ENOENT).  All documented, unambiguous errnos are handled
explicitly to avoid the new stat() call.

Bruce

diff -c2 exec.c~ exec.c
*** exec.c~	Wed Nov 20 02:07:18 1996
--- exec.c	Thu Sep  4 10:38:33 1997
***************
*** 38,41 ****
--- 38,42 ----
  #include <sys/param.h>
  #include <sys/types.h>
+ #include <sys/stat.h>
  #include <errno.h>
  #include <unistd.h>
***************
*** 188,195 ****
  	register int cnt, lp, ln;
  	register char *p;
! 	int eacces, etxtbsy;
  	char *bp, *cur, *path, buf[MAXPATHLEN];
  
! 	eacces = etxtbsy = 0;
  
  	/* If it's an absolute or relative path name, it's easy. */
--- 189,197 ----
  	register int cnt, lp, ln;
  	register char *p;
! 	int eacces, save_errno;
  	char *bp, *cur, *path, buf[MAXPATHLEN];
+ 	struct stat sb;
  
! 	eacces = 0;
  
  	/* If it's an absolute or relative path name, it's easy. */
***************
*** 242,248 ****
  retry:		(void)execve(bp, argv, environ);
  		switch(errno) {
! 		case EACCES:
! 			eacces = 1;
! 			break;
  		case ENOENT:
  			break;
--- 244,251 ----
  retry:		(void)execve(bp, argv, environ);
  		switch(errno) {
! 		case E2BIG:
! 			goto done;
! 		case ELOOP:
! 		case ENAMETOOLONG:
  		case ENOENT:
  			break;
***************
*** 259,267 ****
  			free(memp);
  			goto done;
  		case ETXTBSY:
! 			if (etxtbsy < 3)
! 				(void)sleep(++etxtbsy);
! 			goto retry;
  		default:
  			goto done;
  		}
--- 262,290 ----
  			free(memp);
  			goto done;
+ 		case ENOMEM:
+ 			goto done;
+ 		case ENOTDIR:
+ 			break;
  		case ETXTBSY:
! 			/*
! 			 * We used to retry here, but sh(1) doesn't.
! 			 */
! 			goto done;
  		default:
+ 			/*
+ 			 * EACCES may be for an inaccessible directory or
+ 			 * a non-executable file.  Call stat() to decide
+ 			 * which.  This also handles ambiguities for EFAULT
+ 			 * and EIO, and undocumented errors like ESTALE.
+ 			 * We hope that the race for a stat() is unimportant.
+ 			 */
+ 			save_errno = errno;
+ 			if (stat(argv[0], &sb) != 0)
+ 				break;
+ 			if (save_errno == EACCES) {
+ 				eacces = 1;
+ 				continue;
+ 			}
+ 			errno = save_errno;
  			goto done;
  		}
***************
*** 269,273 ****
  	if (eacces)
  		errno = EACCES;
! 	else if (!errno)
  		errno = ENOENT;
  done:	if (path)
--- 292,296 ----
  	if (eacces)
  		errno = EACCES;
! 	else
  		errno = ENOENT;
  done:	if (path)



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199709040120.LAA17438>