(root)/
gettext-0.22.4/
gettext-tools/
gnulib-lib/
findprog-in.c
       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  }