exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / findprog-in.c
blob5c71c309ee6b18fa6a9ca702469b06445dad7e74
1 /* Locating a program in a given path.
2 Copyright (C) 2001-2004, 2006-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001, 2019.
5 This file is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as
7 published by the Free Software Foundation; either version 2.1 of the
8 License, or (at your option) any later version.
10 This file 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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 <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
30 #include "filename.h"
31 #include "concat-filename.h"
33 #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
34 /* Native Windows, OS/2, DOS */
35 # define NATIVE_SLASH '\\'
36 #else
37 /* Unix */
38 # define NATIVE_SLASH '/'
39 #endif
41 /* Separator in PATH like lists of pathnames. */
42 #if (defined _WIN32 && !defined __CYGWIN__) || defined __EMX__ || defined __DJGPP__
43 /* Native Windows, OS/2, DOS */
44 # define PATH_SEPARATOR ';'
45 #else
46 /* Unix */
47 # define PATH_SEPARATOR ':'
48 #endif
50 /* The list of suffixes that the execlp/execvp function tries when searching
51 for the program. */
52 static const char * const suffixes[] =
54 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
55 "", ".com", ".exe", ".bat", ".cmd"
56 /* Note: Files without any suffix are not considered executable. */
57 /* Note: The cmd.exe program does a different lookup: It searches according
58 to the PATHEXT environment variable.
59 See <https://stackoverflow.com/questions/7839150/>.
60 Also, it executes files ending in .bat and .cmd directly without letting
61 the kernel interpret the program file. */
62 #elif defined __CYGWIN__
63 "", ".exe", ".com"
64 #elif defined __EMX__
65 "", ".exe"
66 #elif defined __DJGPP__
67 "", ".com", ".exe", ".bat"
68 #else /* Unix */
70 #endif
73 const char *
74 find_in_given_path (const char *progname, const char *path,
75 const char *directory, bool optimize_for_exec)
78 bool has_slash = false;
80 const char *p;
82 for (p = progname; *p != '\0'; p++)
83 if (ISSLASH (*p))
85 has_slash = true;
86 break;
89 if (has_slash)
91 /* If progname contains a slash, it is either absolute or relative to
92 the current directory. PATH is not used. */
93 if (optimize_for_exec)
94 /* The execl/execv/execlp/execvp functions will try the various
95 suffixes anyway and fail if no executable is found. */
96 return progname;
97 else
99 /* Try the various suffixes and see whether one of the files
100 with such a suffix is actually executable. */
101 int failure_errno;
102 size_t i;
104 const char *directory_as_prefix =
105 (directory != NULL && IS_RELATIVE_FILE_NAME (progname)
106 ? directory
107 : "");
109 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
110 const char *progbasename;
113 const char *p;
115 progbasename = progname;
116 for (p = progname; *p != '\0'; p++)
117 if (ISSLASH (*p))
118 progbasename = p + 1;
121 bool progbasename_has_dot = (strchr (progbasename, '.') != NULL);
122 #endif
124 /* Try all platform-dependent suffixes. */
125 failure_errno = ENOENT;
126 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
128 const char *suffix = suffixes[i];
130 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
131 /* File names without a '.' are not considered executable, and
132 for file names with a '.' no additional suffix is tried. */
133 if ((*suffix != '\0') != progbasename_has_dot)
134 #endif
136 /* Concatenate directory_as_prefix, progname, suffix. */
137 char *progpathname =
138 concatenated_filename (directory_as_prefix, progname,
139 suffix);
141 if (progpathname == NULL)
142 return NULL; /* errno is set here */
144 /* On systems which have the eaccess() system call, let's
145 use it. On other systems, let's hope that this program
146 is not installed setuid or setgid, so that it is ok to
147 call access() despite its design flaw. */
148 if (eaccess (progpathname, X_OK) == 0)
150 /* Check that the progpathname does not point to a
151 directory. */
152 struct stat statbuf;
154 if (stat (progpathname, &statbuf) >= 0)
156 if (! S_ISDIR (statbuf.st_mode))
158 /* Found! */
159 if (strcmp (progpathname, progname) == 0)
161 free (progpathname);
162 return progname;
164 else
165 return progpathname;
168 errno = EACCES;
172 if (errno != ENOENT)
173 failure_errno = errno;
175 free (progpathname);
178 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
179 if (failure_errno == ENOENT && !progbasename_has_dot)
181 /* In the loop above, we skipped suffix = "". Do this loop
182 round now, merely to provide a better errno than ENOENT. */
184 char *progpathname =
185 concatenated_filename (directory_as_prefix, progname, "");
187 if (progpathname == NULL)
188 return NULL; /* errno is set here */
190 if (eaccess (progpathname, X_OK) == 0)
192 struct stat statbuf;
194 if (stat (progpathname, &statbuf) >= 0)
196 if (! S_ISDIR (statbuf.st_mode))
197 errno = ENOEXEC;
198 else
199 errno = EACCES;
203 failure_errno = errno;
205 free (progpathname);
207 #endif
209 errno = failure_errno;
210 return NULL;
215 if (path == NULL)
216 /* If PATH is not set, the default search path is implementation dependent.
217 In practice, it is treated like an empty PATH. */
218 path = "";
221 /* Make a copy, to prepare for destructive modifications. */
222 char *path_copy = strdup (path);
223 if (path_copy == NULL)
224 return NULL; /* errno is set here */
226 int failure_errno;
227 char *path_rest;
228 char *cp;
230 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
231 bool progname_has_dot = (strchr (progname, '.') != NULL);
232 #endif
234 failure_errno = ENOENT;
235 for (path_rest = path_copy; ; path_rest = cp + 1)
237 const char *dir;
238 bool last;
239 char *dir_as_prefix_to_free;
240 const char *dir_as_prefix;
241 size_t i;
243 /* Extract next directory in PATH. */
244 dir = path_rest;
245 for (cp = path_rest; *cp != '\0' && *cp != PATH_SEPARATOR; cp++)
247 last = (*cp == '\0');
248 *cp = '\0';
250 /* Empty PATH components designate the current directory. */
251 if (dir == cp)
252 dir = ".";
254 /* Concatenate directory and dir. */
255 if (directory != NULL && IS_RELATIVE_FILE_NAME (dir))
257 dir_as_prefix_to_free =
258 concatenated_filename (directory, dir, NULL);
259 if (dir_as_prefix_to_free == NULL)
261 /* errno is set here. */
262 failure_errno = errno;
263 goto failed;
265 dir_as_prefix = dir_as_prefix_to_free;
267 else
269 dir_as_prefix_to_free = NULL;
270 dir_as_prefix = dir;
273 /* Try all platform-dependent suffixes. */
274 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
276 const char *suffix = suffixes[i];
278 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
279 /* File names without a '.' are not considered executable, and
280 for file names with a '.' no additional suffix is tried. */
281 if ((*suffix != '\0') != progname_has_dot)
282 #endif
284 /* Concatenate dir_as_prefix, progname, and suffix. */
285 char *progpathname =
286 concatenated_filename (dir_as_prefix, progname, suffix);
288 if (progpathname == NULL)
290 /* errno is set here. */
291 failure_errno = errno;
292 free (dir_as_prefix_to_free);
293 goto failed;
296 /* On systems which have the eaccess() system call, let's
297 use it. On other systems, let's hope that this program
298 is not installed setuid or setgid, so that it is ok to
299 call access() despite its design flaw. */
300 if (eaccess (progpathname, X_OK) == 0)
302 /* Check that the progpathname does not point to a
303 directory. */
304 struct stat statbuf;
306 if (stat (progpathname, &statbuf) >= 0)
308 if (! S_ISDIR (statbuf.st_mode))
310 /* Found! */
311 if (strcmp (progpathname, progname) == 0)
313 free (progpathname);
315 /* Add the "./" prefix for real, that
316 concatenated_filename() optimized away.
317 This avoids a second PATH search when the
318 caller uses execl/execv/execlp/execvp. */
319 progpathname =
320 (char *) malloc (2 + strlen (progname) + 1);
321 if (progpathname == NULL)
323 /* errno is set here. */
324 failure_errno = errno;
325 free (dir_as_prefix_to_free);
326 goto failed;
328 progpathname[0] = '.';
329 progpathname[1] = NATIVE_SLASH;
330 memcpy (progpathname + 2, progname,
331 strlen (progname) + 1);
334 free (dir_as_prefix_to_free);
335 free (path_copy);
336 return progpathname;
339 errno = EACCES;
343 if (errno != ENOENT)
344 failure_errno = errno;
346 free (progpathname);
349 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
350 if (failure_errno == ENOENT && !progname_has_dot)
352 /* In the loop above, we skipped suffix = "". Do this loop
353 round now, merely to provide a better errno than ENOENT. */
355 char *progpathname =
356 concatenated_filename (dir_as_prefix, progname, "");
358 if (progpathname == NULL)
360 /* errno is set here. */
361 failure_errno = errno;
362 free (dir_as_prefix_to_free);
363 goto failed;
366 if (eaccess (progpathname, X_OK) == 0)
368 struct stat statbuf;
370 if (stat (progpathname, &statbuf) >= 0)
372 if (! S_ISDIR (statbuf.st_mode))
373 errno = ENOEXEC;
374 else
375 errno = EACCES;
379 failure_errno = errno;
381 free (progpathname);
383 #endif
385 free (dir_as_prefix_to_free);
387 if (last)
388 break;
391 failed:
392 /* Not found in PATH. */
393 free (path_copy);
395 errno = failure_errno;
396 return NULL;