Blame posix/execvpe.c

Packit 6c4009
/* Copyright (C) 1991-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
#include <unistd.h>
Packit 6c4009
#include <stdarg.h>
Packit 6c4009
#include <stdbool.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <string.h>
Packit 6c4009
#include <errno.h>
Packit 6c4009
#include <paths.h>
Packit 6c4009
#include <confstr.h>
Packit 6c4009
#include <sys/param.h>
Packit 6c4009
Packit 6c4009
#ifndef PATH_MAX
Packit 6c4009
# ifdef MAXPATHLEN
Packit 6c4009
#  define PATH_MAX MAXPATHLEN
Packit 6c4009
# else
Packit 6c4009
#  define PATH_MAX 1024
Packit 6c4009
# endif
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
/* The file is accessible but it is not an executable file.  Invoke
Packit 6c4009
   the shell to interpret it as a script.  */
Packit 6c4009
static void
Packit 6c4009
maybe_script_execute (const char *file, char *const argv[], char *const envp[])
Packit 6c4009
{
Packit 6c4009
  ptrdiff_t argc;
Packit 6c4009
  for (argc = 0; argv[argc] != NULL; argc++)
Packit 6c4009
    {
Packit 6c4009
      if (argc == INT_MAX - 1)
Packit 6c4009
	{
Packit 6c4009
	  errno = E2BIG;
Packit 6c4009
	  return;
Packit 6c4009
	}
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Construct an argument list for the shell based on original arguments:
Packit 6c4009
     1. Empty list (argv = { NULL }, argc = 1 }: new argv will contain 3
Packit 6c4009
	arguments - default shell, script to execute, and ending NULL.
Packit 6c4009
     2. Non empty argument list (argc = { ..., NULL }, argc > 1}: new argv
Packit 6c4009
	will contain also the default shell and the script to execute.  It
Packit 6c4009
	will also skip the script name in arguments and only copy script
Packit 6c4009
	arguments.  */
Packit 6c4009
  char *new_argv[argc > 1 ? 2 + argc : 3];
Packit 6c4009
  new_argv[0] = (char *) _PATH_BSHELL;
Packit 6c4009
  new_argv[1] = (char *) file;
Packit 6c4009
  if (argc > 1)
Packit 6c4009
    memcpy (new_argv + 2, argv + 1, argc * sizeof(char *));
Packit 6c4009
  else
Packit 6c4009
    new_argv[2] = NULL;
Packit 6c4009
Packit 6c4009
  /* Execute the shell.  */
Packit 6c4009
  __execve (new_argv[0], new_argv, envp);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
static int
Packit 6c4009
__execvpe_common (const char *file, char *const argv[], char *const envp[],
Packit 6c4009
	          bool exec_script)
Packit 6c4009
{
Packit 6c4009
  /* We check the simple case first. */
Packit 6c4009
  if (*file == '\0')
Packit 6c4009
    {
Packit 6c4009
      __set_errno (ENOENT);
Packit 6c4009
      return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* Don't search when it contains a slash.  */
Packit 6c4009
  if (strchr (file, '/') != NULL)
Packit 6c4009
    {
Packit 6c4009
      __execve (file, argv, envp);
Packit 6c4009
Packit 6c4009
      if (errno == ENOEXEC && exec_script)
Packit 6c4009
        maybe_script_execute (file, argv, envp);
Packit 6c4009
Packit 6c4009
      return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  const char *path = getenv ("PATH");
Packit 6c4009
  if (!path)
Packit 6c4009
    path = CS_PATH;
Packit 6c4009
  /* Although GLIBC does not enforce NAME_MAX, we set it as the maximum
Packit 6c4009
     size to avoid unbounded stack allocation.  Same applies for
Packit 6c4009
     PATH_MAX.  */
Packit 6c4009
  size_t file_len = __strnlen (file, NAME_MAX) + 1;
Packit 6c4009
  size_t path_len = __strnlen (path, PATH_MAX - 1) + 1;
Packit 6c4009
Packit 6c4009
  /* NAME_MAX does not include the terminating null character.  */
Packit 6c4009
  if ((file_len - 1 > NAME_MAX)
Packit 6c4009
      || !__libc_alloca_cutoff (path_len + file_len + 1))
Packit 6c4009
    {
Packit 6c4009
      errno = ENAMETOOLONG;
Packit 6c4009
      return -1;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  const char *subp;
Packit 6c4009
  bool got_eacces = false;
Packit 6c4009
  /* The resulting string maximum size would be potentially a entry
Packit 6c4009
     in PATH plus '/' (path_len + 1) and then the the resulting file name
Packit 6c4009
     plus '\0' (file_len since it already accounts for the '\0').  */
Packit 6c4009
  char buffer[path_len + file_len + 1];
Packit 6c4009
  for (const char *p = path; ; p = subp)
Packit 6c4009
    {
Packit 6c4009
      subp = __strchrnul (p, ':');
Packit 6c4009
Packit 6c4009
      /* PATH is larger than PATH_MAX and thus potentially larger than
Packit 6c4009
	 the stack allocation.  */
Packit 6c4009
      if (subp - p >= path_len)
Packit 6c4009
	{
Packit 6c4009
          /* If there is only one path, bail out.  */
Packit 6c4009
	  if (*subp == '\0')
Packit 6c4009
	    break;
Packit 6c4009
	  /* Otherwise skip to next one.  */
Packit 6c4009
	  continue;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      /* Use the current path entry, plus a '/' if nonempty, plus the file to
Packit 6c4009
         execute.  */
Packit 6c4009
      char *pend = mempcpy (buffer, p, subp - p);
Packit 6c4009
      *pend = '/';
Packit 6c4009
      memcpy (pend + (p < subp), file, file_len);
Packit 6c4009
Packit 6c4009
      __execve (buffer, argv, envp);
Packit 6c4009
Packit 6c4009
      if (errno == ENOEXEC && exec_script)
Packit 6c4009
        /* This has O(P*C) behavior, where P is the length of the path and C
Packit 6c4009
           is the argument count.  A better strategy would be allocate the
Packit 6c4009
           substitute argv and reuse it each time through the loop (so it
Packit 6c4009
           behaves as O(P+C) instead.  */
Packit 6c4009
        maybe_script_execute (buffer, argv, envp);
Packit 6c4009
Packit 6c4009
      switch (errno)
Packit 6c4009
	{
Packit 6c4009
	  case EACCES:
Packit 6c4009
	  /* Record that we got a 'Permission denied' error.  If we end
Packit 6c4009
	     up finding no executable we can use, we want to diagnose
Packit 6c4009
	     that we did find one but were denied access.  */
Packit 6c4009
	    got_eacces = true;
Packit 6c4009
	  case ENOENT:
Packit 6c4009
	  case ESTALE:
Packit 6c4009
	  case ENOTDIR:
Packit 6c4009
	  /* Those errors indicate the file is missing or not executable
Packit 6c4009
	     by us, in which case we want to just try the next path
Packit 6c4009
	     directory.  */
Packit 6c4009
	  case ENODEV:
Packit 6c4009
	  case ETIMEDOUT:
Packit 6c4009
	  /* Some strange filesystems like AFS return even
Packit 6c4009
	     stranger error numbers.  They cannot reasonably mean
Packit 6c4009
	     anything else so ignore those, too.  */
Packit 6c4009
	    break;
Packit 6c4009
Packit 6c4009
          default:
Packit 6c4009
	  /* Some other error means we found an executable file, but
Packit 6c4009
	     something went wrong executing it; return the error to our
Packit 6c4009
	     caller.  */
Packit 6c4009
	    return -1;
Packit 6c4009
	}
Packit 6c4009
Packit 6c4009
      if (*subp++ == '\0')
Packit 6c4009
	break;
Packit 6c4009
    }
Packit 6c4009
Packit 6c4009
  /* We tried every element and none of them worked.  */
Packit 6c4009
  if (got_eacces)
Packit 6c4009
    /* At least one failure was due to permissions, so report that
Packit 6c4009
       error.  */
Packit 6c4009
    __set_errno (EACCES);
Packit 6c4009
Packit 6c4009
  return -1;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Execute FILE, searching in the `PATH' environment variable if it contains
Packit 6c4009
   no slashes, with arguments ARGV and environment from ENVP.  */
Packit 6c4009
int
Packit 6c4009
__execvpe (const char *file, char *const argv[], char *const envp[])
Packit 6c4009
{
Packit 6c4009
  return __execvpe_common (file, argv, envp, true);
Packit 6c4009
}
Packit 6c4009
weak_alias (__execvpe, execvpe)
Packit 6c4009
Packit 6c4009
/* Same as __EXECVPE, but does not try to execute NOEXEC files.  */
Packit 6c4009
int
Packit 6c4009
__execvpex (const char *file, char *const argv[], char *const envp[])
Packit 6c4009
{
Packit 6c4009
  return __execvpe_common (file, argv, envp, false);
Packit 6c4009
}