havelib: Fix for Solaris 11 OpenIndiana and Solaris 11 OmniOS.
[gnulib.git] / lib / findprog-in.c
blobd0505fb0df534022cf674edb437fb291ce1b774a
1 /* Locating a program in a given path.
2 Copyright (C) 2001-2004, 2006-2020 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001, 2019.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
19 #include <config.h>
21 /* Specification. */
22 #include "findprog.h"
24 #include <errno.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <sys/stat.h>
31 #include "filename.h"
32 #include "concat-filename.h"
34 #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
35 /* Native Windows, OS/2, DOS */
36 # define NATIVE_SLASH '\\'
37 #else
38 /* Unix */
39 # define NATIVE_SLASH '/'
40 #endif
42 /* Separator in PATH like lists of pathnames. */
43 #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
44 /* Native Windows, OS/2, DOS */
45 # define PATH_SEPARATOR ';'
46 #else
47 /* Unix */
48 # define PATH_SEPARATOR ':'
49 #endif
51 /* The list of suffixes that the execlp/execvp function tries when searching
52 for the program. */
53 static const char * const suffixes[] =
55 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
56 "", ".com", ".exe", ".bat", ".cmd"
57 /* Note: Files without any suffix are not considered executable. */
58 /* Note: The cmd.exe program does a different lookup: It searches according
59 to the PATHEXT environment variable.
60 See <https://stackoverflow.com/questions/7839150/>.
61 Also, it executes files ending in .bat and .cmd directly without letting
62 the kernel interpret the program file. */
63 #elif defined __CYGWIN__
64 "", ".exe", ".com"
65 #elif defined __EMX__
66 "", ".exe"
67 #elif defined __DJGPP__
68 "", ".com", ".exe", ".bat"
69 #else /* Unix */
71 #endif
74 const char *
75 find_in_given_path (const char *progname, const char *path,
76 const char *directory, bool optimize_for_exec)
79 bool has_slash = false;
81 const char *p;
83 for (p = progname; *p != '\0'; p++)
84 if (ISSLASH (*p))
86 has_slash = true;
87 break;
90 if (has_slash)
92 /* If progname contains a slash, it is either absolute or relative to
93 the current directory. PATH is not used. */
94 if (optimize_for_exec)
95 /* The execl/execv/execlp/execvp functions will try the various
96 suffixes anyway and fail if no executable is found. */
97 return progname;
98 else
100 /* Try the various suffixes and see whether one of the files
101 with such a suffix is actually executable. */
102 int failure_errno;
103 size_t i;
105 const char *directory_as_prefix =
106 (directory != NULL && IS_RELATIVE_FILE_NAME (progname)
107 ? directory
108 : "");
110 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
111 const char *progbasename;
114 const char *p;
116 progbasename = progname;
117 for (p = progname; *p != '\0'; p++)
118 if (ISSLASH (*p))
119 progbasename = p + 1;
122 bool progbasename_has_dot = (strchr (progbasename, '.') != NULL);
123 #endif
125 /* Try all platform-dependent suffixes. */
126 failure_errno = ENOENT;
127 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
129 const char *suffix = suffixes[i];
131 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
132 /* File names without a '.' are not considered executable, and
133 for file names with a '.' no additional suffix is tried. */
134 if ((*suffix != '\0') != progbasename_has_dot)
135 #endif
137 /* Concatenate directory_as_prefix, progname, suffix. */
138 char *progpathname =
139 concatenated_filename (directory_as_prefix, progname,
140 suffix);
142 if (progpathname == NULL)
143 return NULL; /* errno is set here */
145 /* On systems which have the eaccess() system call, let's
146 use it. On other systems, let's hope that this program
147 is not installed setuid or setgid, so that it is ok to
148 call access() despite its design flaw. */
149 if (eaccess (progpathname, X_OK) == 0)
151 /* Check that the progpathname does not point to a
152 directory. */
153 struct stat statbuf;
155 if (stat (progpathname, &statbuf) >= 0)
157 if (! S_ISDIR (statbuf.st_mode))
159 /* Found! */
160 if (strcmp (progpathname, progname) == 0)
162 free (progpathname);
163 return progname;
165 else
166 return progpathname;
169 errno = EACCES;
173 if (errno != ENOENT)
174 failure_errno = errno;
176 free (progpathname);
179 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
180 if (failure_errno == ENOENT && !progbasename_has_dot)
182 /* In the loop above, we skipped suffix = "". Do this loop
183 round now, merely to provide a better errno than ENOENT. */
185 char *progpathname =
186 concatenated_filename (directory_as_prefix, progname, "");
188 if (progpathname == NULL)
189 return NULL; /* errno is set here */
191 if (eaccess (progpathname, X_OK) == 0)
193 struct stat statbuf;
195 if (stat (progpathname, &statbuf) >= 0)
197 if (! S_ISDIR (statbuf.st_mode))
198 errno = ENOEXEC;
199 else
200 errno = EACCES;
204 failure_errno = errno;
206 free (progpathname);
208 #endif
210 errno = failure_errno;
211 return NULL;
216 if (path == NULL)
217 /* If PATH is not set, the default search path is implementation dependent.
218 In practice, it is treated like an empty PATH. */
219 path = "";
222 /* Make a copy, to prepare for destructive modifications. */
223 char *path_copy = strdup (path);
224 if (path_copy == NULL)
225 return NULL; /* errno is set here */
227 int failure_errno;
228 char *path_rest;
229 char *cp;
231 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
232 bool progname_has_dot = (strchr (progname, '.') != NULL);
233 #endif
235 failure_errno = ENOENT;
236 for (path_rest = path_copy; ; path_rest = cp + 1)
238 const char *dir;
239 bool last;
240 char *dir_as_prefix_to_free;
241 const char *dir_as_prefix;
242 size_t i;
244 /* Extract next directory in PATH. */
245 dir = path_rest;
246 for (cp = path_rest; *cp != '\0' && *cp != PATH_SEPARATOR; cp++)
248 last = (*cp == '\0');
249 *cp = '\0';
251 /* Empty PATH components designate the current directory. */
252 if (dir == cp)
253 dir = ".";
255 /* Concatenate directory and dir. */
256 if (directory != NULL && IS_RELATIVE_FILE_NAME (dir))
258 dir_as_prefix_to_free =
259 concatenated_filename (directory, dir, NULL);
260 if (dir_as_prefix_to_free == NULL)
262 /* errno is set here. */
263 failure_errno = errno;
264 goto failed;
266 dir_as_prefix = dir_as_prefix_to_free;
268 else
270 dir_as_prefix_to_free = NULL;
271 dir_as_prefix = dir;
274 /* Try all platform-dependent suffixes. */
275 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
277 const char *suffix = suffixes[i];
279 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
280 /* File names without a '.' are not considered executable, and
281 for file names with a '.' no additional suffix is tried. */
282 if ((*suffix != '\0') != progname_has_dot)
283 #endif
285 /* Concatenate dir_as_prefix, progname, and suffix. */
286 char *progpathname =
287 concatenated_filename (dir_as_prefix, progname, suffix);
289 if (progpathname == NULL)
291 /* errno is set here. */
292 failure_errno = errno;
293 free (dir_as_prefix_to_free);
294 goto failed;
297 /* On systems which have the eaccess() system call, let's
298 use it. On other systems, let's hope that this program
299 is not installed setuid or setgid, so that it is ok to
300 call access() despite its design flaw. */
301 if (eaccess (progpathname, X_OK) == 0)
303 /* Check that the progpathname does not point to a
304 directory. */
305 struct stat statbuf;
307 if (stat (progpathname, &statbuf) >= 0)
309 if (! S_ISDIR (statbuf.st_mode))
311 /* Found! */
312 if (strcmp (progpathname, progname) == 0)
314 free (progpathname);
316 /* Add the "./" prefix for real, that
317 concatenated_filename() optimized away.
318 This avoids a second PATH search when the
319 caller uses execl/execv/execlp/execvp. */
320 progpathname =
321 (char *) malloc (2 + strlen (progname) + 1);
322 if (progpathname == NULL)
324 /* errno is set here. */
325 failure_errno = errno;
326 free (dir_as_prefix_to_free);
327 goto failed;
329 progpathname[0] = '.';
330 progpathname[1] = NATIVE_SLASH;
331 memcpy (progpathname + 2, progname,
332 strlen (progname) + 1);
335 free (dir_as_prefix_to_free);
336 free (path_copy);
337 return progpathname;
340 errno = EACCES;
344 if (errno != ENOENT)
345 failure_errno = errno;
347 free (progpathname);
350 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
351 if (failure_errno == ENOENT && !progname_has_dot)
353 /* In the loop above, we skipped suffix = "". Do this loop
354 round now, merely to provide a better errno than ENOENT. */
356 char *progpathname =
357 concatenated_filename (dir_as_prefix, progname, "");
359 if (progpathname == NULL)
361 /* errno is set here. */
362 failure_errno = errno;
363 free (dir_as_prefix_to_free);
364 goto failed;
367 if (eaccess (progpathname, X_OK) == 0)
369 struct stat statbuf;
371 if (stat (progpathname, &statbuf) >= 0)
373 if (! S_ISDIR (statbuf.st_mode))
374 errno = ENOEXEC;
375 else
376 errno = EACCES;
380 failure_errno = errno;
382 free (progpathname);
384 #endif
386 free (dir_as_prefix_to_free);
388 if (last)
389 break;
392 failed:
393 /* Not found in PATH. */
394 free (path_copy);
396 errno = failure_errno;
397 return NULL;