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