1 /* Locating a program in a given path.
2 Copyright (C) 2001-2004, 2006-2025 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/>. */
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 '\\'
38 # define NATIVE_SLASH '/'
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 ';'
47 # define PATH_SEPARATOR ':'
50 /* The list of suffixes that the execlp/execvp function tries when searching
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__
66 #elif defined __DJGPP__
67 "", ".com", ".exe", ".bat"
74 find_in_given_path (const char *progname
, const char *path
,
75 const char *directory
, bool optimize_for_exec
)
78 bool has_slash
= false;
82 for (p
= progname
; *p
!= '\0'; p
++)
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. */
99 /* Try the various suffixes and see whether one of the files
100 with such a suffix is actually executable. */
104 const char *directory_as_prefix
=
105 (directory
!= NULL
&& IS_RELATIVE_FILE_NAME (progname
)
109 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
110 const char *progbasename
;
115 progbasename
= progname
;
116 for (p
= progname
; *p
!= '\0'; p
++)
118 progbasename
= p
+ 1;
121 bool progbasename_has_dot
= (strchr (progbasename
, '.') != NULL
);
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
)
136 /* Concatenate directory_as_prefix, progname, suffix. */
138 concatenated_filename (directory_as_prefix
, progname
,
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
154 if (stat (progpathname
, &statbuf
) >= 0)
156 if (! S_ISDIR (statbuf
.st_mode
))
159 if (strcmp (progpathname
, progname
) == 0)
173 failure_errno
= errno
;
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. */
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)
194 if (stat (progpathname
, &statbuf
) >= 0)
196 if (! S_ISDIR (statbuf
.st_mode
))
203 failure_errno
= errno
;
209 errno
= failure_errno
;
216 /* If PATH is not set, the default search path is implementation dependent.
217 In practice, it is treated like an empty 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 */
230 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
231 bool progname_has_dot
= (strchr (progname
, '.') != NULL
);
234 failure_errno
= ENOENT
;
235 for (path_rest
= path_copy
; ; path_rest
= cp
+ 1)
239 char *dir_as_prefix_to_free
;
240 const char *dir_as_prefix
;
243 /* Extract next directory in PATH. */
245 for (cp
= path_rest
; *cp
!= '\0' && *cp
!= PATH_SEPARATOR
; cp
++)
247 last
= (*cp
== '\0');
250 /* Empty PATH components designate the current directory. */
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
;
265 dir_as_prefix
= dir_as_prefix_to_free
;
269 dir_as_prefix_to_free
= NULL
;
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
)
284 /* Concatenate dir_as_prefix, progname, and suffix. */
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
);
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
306 if (stat (progpathname
, &statbuf
) >= 0)
308 if (! S_ISDIR (statbuf
.st_mode
))
311 if (strcmp (progpathname
, progname
) == 0)
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. */
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
);
328 progpathname
[0] = '.';
329 progpathname
[1] = NATIVE_SLASH
;
330 memcpy (progpathname
+ 2, progname
,
331 strlen (progname
) + 1);
334 free (dir_as_prefix_to_free
);
344 failure_errno
= errno
;
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. */
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
);
366 if (eaccess (progpathname
, X_OK
) == 0)
370 if (stat (progpathname
, &statbuf
) >= 0)
372 if (! S_ISDIR (statbuf
.st_mode
))
379 failure_errno
= errno
;
385 free (dir_as_prefix_to_free
);
392 /* Not found in PATH. */
395 errno
= failure_errno
;