(root)/
make-4.4/
lib/
findprog-in.c
       1  /* Locating a program in a given path.
       2     Copyright (C) 2001-2004, 2006-2022 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 *
      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  }