Hi Paul, Here's the implementation I'm committing.
I feel that merging the two functions in a single file would add more contortions than benefit. 2019-09-08 Bruno Haible <br...@clisp.org> findprog-in: New module. Suggested by Paul Smith <psm...@gnu.org>. * lib/findprog.h (find_in_given_path): New declaration. * lib/findprog-in.c: New file, based on lib/findprog.c. * m4/findprog-in.m4: New file, based on m4/findprog.m4. * modules/findprog-in: New file. diff --git a/lib/findprog.h b/lib/findprog.h index a354f67..9bc8a60 100644 --- a/lib/findprog.h +++ b/lib/findprog.h @@ -21,16 +21,29 @@ extern "C" { #endif -/* Look up a program in the PATH. - Attempt to determine the pathname that would be called by execlp/execvp - of PROGNAME. If successful, return a pathname containing a slash - (either absolute or relative to the current directory). Otherwise, - return PROGNAME unmodified. +/* Looks up a program in the PATH. + Attempts to determine the pathname that would be called by execlp/execvp + of PROGNAME. If successful, it returns a pathname containing a slash + (either absolute or relative to the current directory). Otherwise, it + returns PROGNAME unmodified. Because of the latter case, callers should use execlp/execvp, not execl/execv on the returned pathname. The returned string is freshly malloc()ed if it is != PROGNAME. */ extern const char *find_in_path (const char *progname); +/* Looks up a program in the given PATH-like string. + The PATH argument consists of a list of directories, separated by ':' or + (on native Windows) by ';'. An empty PATH element designates the current + directory. A null PATH is equivalent to an empty PATH, that is, to the + singleton list that contains only the current directory. + Determines the pathname that would be called by execlp/execvp of PROGNAME. + - If successful, it returns a pathname containing a slash (either absolute + or relative to the current directory). The returned string can be used + with either execl/execv or execlp/execvp. It is freshly malloc()ed if it + is != PROGNAME. + - Otherwise, it returns NULL. */ +extern const char *find_in_given_path (const char *progname, const char *path); + #ifdef __cplusplus } diff --git a/lib/findprog-in.c b/lib/findprog-in.c new file mode 100644 index 0000000..3d70b7b --- /dev/null +++ b/lib/findprog-in.c @@ -0,0 +1,178 @@ +/* Locating a program in a given path. + Copyright (C) 2001-2004, 2006-2019 Free Software Foundation, Inc. + Written by Bruno Haible <hai...@clisp.cons.org>, 2001, 2019. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + + +#include <config.h> + +/* Specification. */ +#include "findprog.h" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "filename.h" +#include "concat-filename.h" +#include "xalloc.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 .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) +{ + { + 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. + We could try the various suffixes and see whether one of the files + with such a suffix is actually executable. But this is not needed, + since the execl/execv/execlp/execvp functions will do these tests + anyway. */ + return progname; + } + + 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 = xstrdup (path); + char *path_rest; + char *cp; + + for (path_rest = path_copy; ; path_rest = cp + 1) + { + const char *dir; + bool last; + 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 = "."; + + /* 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. */ + if (*suffix != '\0' || strchr (progname, '.') != NULL) + #endif + { + /* Concatenate dir and progname. */ + char *progpathname = + xconcatenated_filename (dir, progname, suffix); + + /* 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) + { + /* Found! */ + if (strcmp (progpathname, progname) == 0) + { + free (progpathname); + + /* Add the "./" prefix for real, that + xconcatenated_filename() optimized away. This + avoids a second PATH search when the caller uses + execl/execv/execlp/execvp. */ + progpathname = + XNMALLOC (2 + strlen (progname) + 1, char); + progpathname[0] = '.'; + progpathname[1] = NATIVE_SLASH; + memcpy (progpathname + 2, progname, + strlen (progname) + 1); + } + + free (path_copy); + return progpathname; + } + + free (progpathname); + } + } + + if (last) + break; + } + + /* Not found in PATH. */ + free (path_copy); + } + + return NULL; +} diff --git a/m4/findprog-in.m4 b/m4/findprog-in.m4 new file mode 100644 index 0000000..baa7e6b --- /dev/null +++ b/m4/findprog-in.m4 @@ -0,0 +1,11 @@ +# findprog-in.m4 serial 1 +dnl Copyright (C) 2003, 2009-2019 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +AC_DEFUN([gl_FINDPROG_IN], +[ + dnl Prerequisites of lib/findprog-in.c. + AC_REQUIRE([gl_FUNC_EACCESS]) +]) diff --git a/modules/findprog-in b/modules/findprog-in new file mode 100644 index 0000000..ce7faa5 --- /dev/null +++ b/modules/findprog-in @@ -0,0 +1,30 @@ +Description: +Locating a program in a given path. + +Files: +lib/findprog.h +lib/findprog-in.c +m4/findprog-in.m4 +m4/eaccess.m4 + +Depends-on: +stdbool +filename +xalloc +xconcat-filename +unistd + +configure.ac: +gl_FINDPROG_IN + +Makefile.am: +lib_SOURCES += findprog.h findprog-in.c + +Include: +"findprog.h" + +License: +GPL + +Maintainer: +all