1 /* Locating a program in a given path.
2 Copyright (C) 2001-2004, 2006-2023 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001, 2019.
4
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.
9
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.
14
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/>. */
17
18
19 #include <config.h>
20
21 /* Specification. */
22 #include "findprog.h"
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28 #include <sys/stat.h>
29
30 #include "filename.h"
31 #include "concat-filename.h"
32
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
40
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
49
50 /* The list of suffixes that the execlp/execvp function tries when searching
51 for the program. */
52 static const char * const suffixes[] =
53 {
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 */
69 ""
70 #endif
71 };
72
73 const char *
74 find_in_given_path (const char *progname, const char *path,
75 const char *directory, bool optimize_for_exec)
76 {
77 {
78 bool has_slash = false;
79 {
80 const char *p;
81
82 for (p = progname; *p != '\0'; p++)
83 if (ISSLASH (*p))
84 {
85 has_slash = true;
86 break;
87 }
88 }
89 if (has_slash)
90 {
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
98 {
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;
103
104 const char *directory_as_prefix =
105 (directory != NULL && IS_RELATIVE_FILE_NAME (progname)
106 ? directory
107 : "");
108
109 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
110 const char *progbasename;
111
112 {
113 const char *p;
114
115 progbasename = progname;
116 for (p = progname; *p != '\0'; p++)
117 if (ISSLASH (*p))
118 progbasename = p + 1;
119 }
120
121 bool progbasename_has_dot = (strchr (progbasename, '.') != NULL);
122 #endif
123
124 /* Try all platform-dependent suffixes. */
125 failure_errno = ENOENT;
126 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
127 {
128 const char *suffix = suffixes[i];
129
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
135 {
136 /* Concatenate directory_as_prefix, progname, suffix. */
137 char *progpathname =
138 concatenated_filename (directory_as_prefix, progname,
139 suffix);
140
141 if (progpathname == NULL)
142 return NULL; /* errno is set here */
143
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)
149 {
150 /* Check that the progpathname does not point to a
151 directory. */
152 struct stat statbuf;
153
154 if (stat (progpathname, &statbuf) >= 0)
155 {
156 if (! S_ISDIR (statbuf.st_mode))
157 {
158 /* Found! */
159 if (strcmp (progpathname, progname) == 0)
160 {
161 free (progpathname);
162 return progname;
163 }
164 else
165 return progpathname;
166 }
167
168 errno = EACCES;
169 }
170 }
171
172 if (errno != ENOENT)
173 failure_errno = errno;
174
175 free (progpathname);
176 }
177 }
178 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
179 if (failure_errno == ENOENT && !progbasename_has_dot)
180 {
181 /* In the loop above, we skipped suffix = "". Do this loop
182 round now, merely to provide a better errno than ENOENT. */
183
184 char *progpathname =
185 concatenated_filename (directory_as_prefix, progname, "");
186
187 if (progpathname == NULL)
188 return NULL; /* errno is set here */
189
190 if (eaccess (progpathname, X_OK) == 0)
191 {
192 struct stat statbuf;
193
194 if (stat (progpathname, &statbuf) >= 0)
195 {
196 if (! S_ISDIR (statbuf.st_mode))
197 errno = ENOEXEC;
198 else
199 errno = EACCES;
200 }
201 }
202
203 failure_errno = errno;
204
205 free (progpathname);
206 }
207 #endif
208
209 errno = failure_errno;
210 return NULL;
211 }
212 }
213 }
214
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 = "";
219
220 {
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 */
225
226 int failure_errno;
227 char *path_rest;
228 char *cp;
229
230 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
231 bool progname_has_dot = (strchr (progname, '.') != NULL);
232 #endif
233
234 failure_errno = ENOENT;
235 for (path_rest = path_copy; ; path_rest = cp + 1)
236 {
237 const char *dir;
238 bool last;
239 char *dir_as_prefix_to_free;
240 const char *dir_as_prefix;
241 size_t i;
242
243 /* Extract next directory in PATH. */
244 dir = path_rest;
245 for (cp = path_rest; *cp != '\0' && *cp != PATH_SEPARATOR; cp++)
246 ;
247 last = (*cp == '\0');
248 *cp = '\0';
249
250 /* Empty PATH components designate the current directory. */
251 if (dir == cp)
252 dir = ".";
253
254 /* Concatenate directory and dir. */
255 if (directory != NULL && IS_RELATIVE_FILE_NAME (dir))
256 {
257 dir_as_prefix_to_free =
258 concatenated_filename (directory, dir, NULL);
259 if (dir_as_prefix_to_free == NULL)
260 {
261 /* errno is set here. */
262 failure_errno = errno;
263 goto failed;
264 }
265 dir_as_prefix = dir_as_prefix_to_free;
266 }
267 else
268 {
269 dir_as_prefix_to_free = NULL;
270 dir_as_prefix = dir;
271 }
272
273 /* Try all platform-dependent suffixes. */
274 for (i = 0; i < sizeof (suffixes) / sizeof (suffixes[0]); i++)
275 {
276 const char *suffix = suffixes[i];
277
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
283 {
284 /* Concatenate dir_as_prefix, progname, and suffix. */
285 char *progpathname =
286 concatenated_filename (dir_as_prefix, progname, suffix);
287
288 if (progpathname == NULL)
289 {
290 /* errno is set here. */
291 failure_errno = errno;
292 free (dir_as_prefix_to_free);
293 goto failed;
294 }
295
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)
301 {
302 /* Check that the progpathname does not point to a
303 directory. */
304 struct stat statbuf;
305
306 if (stat (progpathname, &statbuf) >= 0)
307 {
308 if (! S_ISDIR (statbuf.st_mode))
309 {
310 /* Found! */
311 if (strcmp (progpathname, progname) == 0)
312 {
313 free (progpathname);
314
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)
322 {
323 /* errno is set here. */
324 failure_errno = errno;
325 free (dir_as_prefix_to_free);
326 goto failed;
327 }
328 progpathname[0] = '.';
329 progpathname[1] = NATIVE_SLASH;
330 memcpy (progpathname + 2, progname,
331 strlen (progname) + 1);
332 }
333
334 free (dir_as_prefix_to_free);
335 free (path_copy);
336 return progpathname;
337 }
338
339 errno = EACCES;
340 }
341 }
342
343 if (errno != ENOENT)
344 failure_errno = errno;
345
346 free (progpathname);
347 }
348 }
349 #if defined _WIN32 && !defined __CYGWIN__ /* Native Windows */
350 if (failure_errno == ENOENT && !progname_has_dot)
351 {
352 /* In the loop above, we skipped suffix = "". Do this loop
353 round now, merely to provide a better errno than ENOENT. */
354
355 char *progpathname =
356 concatenated_filename (dir_as_prefix, progname, "");
357
358 if (progpathname == NULL)
359 {
360 /* errno is set here. */
361 failure_errno = errno;
362 free (dir_as_prefix_to_free);
363 goto failed;
364 }
365
366 if (eaccess (progpathname, X_OK) == 0)
367 {
368 struct stat statbuf;
369
370 if (stat (progpathname, &statbuf) >= 0)
371 {
372 if (! S_ISDIR (statbuf.st_mode))
373 errno = ENOEXEC;
374 else
375 errno = EACCES;
376 }
377 }
378
379 failure_errno = errno;
380
381 free (progpathname);
382 }
383 #endif
384
385 free (dir_as_prefix_to_free);
386
387 if (last)
388 break;
389 }
390
391 failed:
392 /* Not found in PATH. */
393 free (path_copy);
394
395 errno = failure_errno;
396 return NULL;
397 }
398 }