123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /* Locating a program in a given path.
- Copyright (C) 2001-2004, 2006-2023 Free Software Foundation, Inc.
- Written by Bruno Haible <haible@clisp.cons.org>, 2001, 2019.
- This file is free software: you can redistribute it and/or modify
- it under the terms of the GNU Lesser General Public License as
- published by the Free Software Foundation; either version 2.1 of the
- License, or (at your option) any later version.
- This file is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>. */
- #include <config.h>
- /* Specification. */
- #include "findprog.h"
- #include <errno.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/stat.h>
- #include "filename.h"
- #include "concat-filename.h"
- #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
- /* Native Windows, OS/2, DOS */
- # define NATIVE_SLASH '\\'
- #else
- /* Unix */
- # define NATIVE_SLASH '/'
- #endif
- /* Separator in PATH like lists of pathnames. */
- #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
- /* Native Windows, OS/2, DOS */
- # define PATH_SEPARATOR ';'
- #else
- /* Unix */
- # define PATH_SEPARATOR ':'
- #endif
- /* The list of suffixes that the execlp/execvp function tries when searching
- for the program. */
- static const char * const suffixes[] =
- {
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- "", ".com", ".exe", ".bat", ".cmd"
- /* Note: Files without any suffix are not considered executable. */
- /* Note: The cmd.exe program does a different lookup: It searches according
- to the PATHEXT environment variable.
- See <https://stackoverflow.com/questions/7839150/>.
- Also, it executes files ending in .bat and .cmd directly without letting
- the kernel interpret the program file. */
- #elif defined __CYGWIN__
- "", ".exe", ".com"
- #elif defined __EMX__
- "", ".exe"
- #elif defined __DJGPP__
- "", ".com", ".exe", ".bat"
- #else /* Unix */
- ""
- #endif
- };
- const char *
- find_in_given_path (const char *progname, const char *path,
- const char *directory, bool optimize_for_exec)
- {
- {
- bool has_slash = false;
- {
- const char *p;
- for (p = progname; *p != '\0'; p++)
- if (ISSLASH (*p))
- {
- has_slash = true;
- break;
- }
- }
- if (has_slash)
- {
- /* If progname contains a slash, it is either absolute or relative to
- the current directory. PATH is not used. */
- if (optimize_for_exec)
- /* The execl/execv/execlp/execvp functions will try the various
- suffixes anyway and fail if no executable is found. */
- return progname;
- else
- {
- /* Try the various suffixes and see whether one of the files
- with such a suffix is actually executable. */
- int failure_errno;
- size_t i;
- const char *directory_as_prefix =
- (directory != NULL && IS_RELATIVE_FILE_NAME (progname)
- ? directory
- : "");
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- const char *progbasename;
- {
- const char *p;
- progbasename = progname;
- for (p = progname; *p != '\0'; p++)
- if (ISSLASH (*p))
- progbasename = p + 1;
- }
- bool progbasename_has_dot = (strchr (progbasename, '.') != NULL);
- #endif
- /* Try all platform-dependent suffixes. */
- failure_errno = ENOENT;
- for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
- {
- const char *suffix = suffixes[i];
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- /* File names without a '.' are not considered executable, and
- for file names with a '.' no additional suffix is tried. */
- if ((*suffix != '\0') != progbasename_has_dot)
- #endif
- {
- /* Concatenate directory_as_prefix, progname, suffix. */
- char *progpathname =
- concatenated_filename (directory_as_prefix, progname,
- suffix);
- if (progpathname == NULL)
- return NULL; /* errno is set here */
- /* On systems which have the eaccess() system call, let's
- use it. On other systems, let's hope that this program
- is not installed setuid or setgid, so that it is ok to
- call access() despite its design flaw. */
- if (eaccess (progpathname, X_OK) == 0)
- {
- /* Check that the progpathname does not point to a
- directory. */
- struct stat statbuf;
- if (stat (progpathname, &statbuf) >= 0)
- {
- if (! S_ISDIR (statbuf.st_mode))
- {
- /* Found! */
- if (strcmp (progpathname, progname) == 0)
- {
- free (progpathname);
- return progname;
- }
- else
- return progpathname;
- }
- errno = EACCES;
- }
- }
- if (errno != ENOENT)
- failure_errno = errno;
- free (progpathname);
- }
- }
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- if (failure_errno == ENOENT && !progbasename_has_dot)
- {
- /* In the loop above, we skipped suffix = "". Do this loop
- round now, merely to provide a better errno than ENOENT. */
- char *progpathname =
- concatenated_filename (directory_as_prefix, progname, "");
- if (progpathname == NULL)
- return NULL; /* errno is set here */
- if (eaccess (progpathname, X_OK) == 0)
- {
- struct stat statbuf;
- if (stat (progpathname, &statbuf) >= 0)
- {
- if (! S_ISDIR (statbuf.st_mode))
- errno = ENOEXEC;
- else
- errno = EACCES;
- }
- }
- failure_errno = errno;
- free (progpathname);
- }
- #endif
- errno = failure_errno;
- return NULL;
- }
- }
- }
- if (path == NULL)
- /* If PATH is not set, the default search path is implementation dependent.
- In practice, it is treated like an empty PATH. */
- path = "";
- {
- /* Make a copy, to prepare for destructive modifications. */
- char *path_copy = strdup (path);
- if (path_copy == NULL)
- return NULL; /* errno is set here */
- int failure_errno;
- char *path_rest;
- char *cp;
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- bool progname_has_dot = (strchr (progname, '.') != NULL);
- #endif
- failure_errno = ENOENT;
- for (path_rest = path_copy; ; path_rest = cp + 1)
- {
- const char *dir;
- bool last;
- char *dir_as_prefix_to_free;
- const char *dir_as_prefix;
- size_t i;
- /* Extract next directory in PATH. */
- dir = path_rest;
- for (cp = path_rest; *cp != '\0' && *cp != PATH_SEPARATOR; cp++)
- ;
- last = (*cp == '\0');
- *cp = '\0';
- /* Empty PATH components designate the current directory. */
- if (dir == cp)
- dir = ".";
- /* Concatenate directory and dir. */
- if (directory != NULL && IS_RELATIVE_FILE_NAME (dir))
- {
- dir_as_prefix_to_free =
- concatenated_filename (directory, dir, NULL);
- if (dir_as_prefix_to_free == NULL)
- {
- /* errno is set here. */
- failure_errno = errno;
- goto failed;
- }
- dir_as_prefix = dir_as_prefix_to_free;
- }
- else
- {
- dir_as_prefix_to_free = NULL;
- dir_as_prefix = dir;
- }
- /* Try all platform-dependent suffixes. */
- for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
- {
- const char *suffix = suffixes[i];
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- /* File names without a '.' are not considered executable, and
- for file names with a '.' no additional suffix is tried. */
- if ((*suffix != '\0') != progname_has_dot)
- #endif
- {
- /* Concatenate dir_as_prefix, progname, and suffix. */
- char *progpathname =
- concatenated_filename (dir_as_prefix, progname, suffix);
- if (progpathname == NULL)
- {
- /* errno is set here. */
- failure_errno = errno;
- free (dir_as_prefix_to_free);
- goto failed;
- }
- /* On systems which have the eaccess() system call, let's
- use it. On other systems, let's hope that this program
- is not installed setuid or setgid, so that it is ok to
- call access() despite its design flaw. */
- if (eaccess (progpathname, X_OK) == 0)
- {
- /* Check that the progpathname does not point to a
- directory. */
- struct stat statbuf;
- if (stat (progpathname, &statbuf) >= 0)
- {
- if (! S_ISDIR (statbuf.st_mode))
- {
- /* Found! */
- if (strcmp (progpathname, progname) == 0)
- {
- free (progpathname);
- /* Add the "./" prefix for real, that
- concatenated_filename() optimized away.
- This avoids a second PATH search when the
- caller uses execl/execv/execlp/execvp. */
- progpathname =
- (char *) malloc (2 + strlen (progname) + 1);
- if (progpathname == NULL)
- {
- /* errno is set here. */
- failure_errno = errno;
- free (dir_as_prefix_to_free);
- goto failed;
- }
- progpathname[0] = '.';
- progpathname[1] = NATIVE_SLASH;
- memcpy (progpathname + 2, progname,
- strlen (progname) + 1);
- }
- free (dir_as_prefix_to_free);
- free (path_copy);
- return progpathname;
- }
- errno = EACCES;
- }
- }
- if (errno != ENOENT)
- failure_errno = errno;
- free (progpathname);
- }
- }
- #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
- if (failure_errno == ENOENT && !progname_has_dot)
- {
- /* In the loop above, we skipped suffix = "". Do this loop
- round now, merely to provide a better errno than ENOENT. */
- char *progpathname =
- concatenated_filename (dir_as_prefix, progname, "");
- if (progpathname == NULL)
- {
- /* errno is set here. */
- failure_errno = errno;
- free (dir_as_prefix_to_free);
- goto failed;
- }
- if (eaccess (progpathname, X_OK) == 0)
- {
- struct stat statbuf;
- if (stat (progpathname, &statbuf) >= 0)
- {
- if (! S_ISDIR (statbuf.st_mode))
- errno = ENOEXEC;
- else
- errno = EACCES;
- }
- }
- failure_errno = errno;
- free (progpathname);
- }
- #endif
- free (dir_as_prefix_to_free);
- if (last)
- break;
- }
- failed:
- /* Not found in PATH. */
- free (path_copy);
- errno = failure_errno;
- return NULL;
- }
- }
|