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