]> git.ipfire.org Git - thirdparty/bash.git/blobdiff - findcmd.c
fix for SIGINT in sourced script
[thirdparty/bash.git] / findcmd.c
index 4c47c042ac7ca7b12212ac68fb1fc75008a842d2..7ff349ce844cdd68dc5256c369063f3ceab03089 100644 (file)
--- a/findcmd.c
+++ b/findcmd.c
@@ -1,30 +1,29 @@
 /* findcmd.c -- Functions to search for commands by name. */
 
-/* Copyright (C) 1997 Free Software Foundation, Inc.
+/* Copyright (C) 1997-2015 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
-   Bash is free software; you can redistribute it and/or modify it
-   under the terms of the GNU General Public License as published by
-   the Free Software Foundation; either version 2, or (at your option)
-   any later version.
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
 
-   Bash is distributed in the hope that it will be useful, but WITHOUT
-   ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
-   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
-   License for more details.
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
 
    You should have received a copy of the GNU General Public License
-   along with Bash; see the file COPYING.  If not, write to the
-   Free Software Foundation Inc.,
-   59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
 
 #include "config.h"
 
 #include <stdio.h>
-#include <ctype.h>
+#include "chartypes.h"
 #include "bashtypes.h"
-#ifndef _MINIX
+#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
 #  include <sys/file.h>
 #endif
 #include "filecntl.h"
 #if defined (HAVE_UNISTD_H)
 #  include <unistd.h>
 #endif
-
-#if defined (HAVE_LIMITS_H)
-#  include <limits.h>
-#endif
+#include <errno.h>
 
 #include "bashansi.h"
 
 #include "hashlib.h"
 #include "pathexp.h"
 #include "hashcmd.h"
+#include "findcmd.h"   /* matching prototypes and declarations */
+
+#include <glob/strmatch.h>
+
+#if !defined (errno)
+extern int errno;
+#endif
 
 extern int posixly_correct;
+extern int last_command_exit_value;
 
 /* Static functions defined and used in this file. */
-static char *find_user_command_internal (), *find_user_command_in_path ();
-static char *find_in_path_element (), *find_absolute_program ();
+static char *_find_user_command_internal __P((const char *, int));
+static char *find_user_command_internal __P((const char *, int));
+static char *find_user_command_in_path __P((const char *, char *, int));
+static char *find_in_path_element __P((const char *, char *, int, int, struct stat *));
+static char *find_absolute_program __P((const char *, int));
+
+static char *get_next_path_element __P((char *, int *));
 
 /* The file name which we would try to execute, except that it isn't
    possible to execute it.  This is the first file that matches the
@@ -62,7 +71,7 @@ static char *file_to_lose_on;
 
 /* Non-zero if we should stat every command found in the hash table to
    make sure it still exists. */
-int check_hashed_filenames;
+int check_hashed_filenames = CHECKHASH_DEFAULT;
 
 /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command ()
    encounters a `.' as the directory pathname while scanning the
@@ -70,10 +79,35 @@ int check_hashed_filenames;
    containing the file of interest. */
 int dot_found_in_search = 0;
 
-#define u_mode_bits(x) (((x) & 0000700) >> 6)
-#define g_mode_bits(x) (((x) & 0000070) >> 3)
-#define o_mode_bits(x) (((x) & 0000007) >> 0)
-#define X_BIT(x) ((x) & 1)
+/* Set up EXECIGNORE; a blacklist of patterns that executable files should not
+   match. */
+static struct ignorevar execignore =
+{
+  "EXECIGNORE",
+  NULL,
+  0,
+  NULL,
+  NULL
+};
+
+void
+setup_exec_ignore (varname)
+     char *varname;
+{
+  setup_ignore_patterns (&execignore);
+}
+
+static int
+exec_name_should_ignore (name)
+     const char *name;
+{
+  struct ign *p;
+
+  for (p = execignore.ignores; p && p->val; p++)
+    if (strmatch (p->val, (char *)name, FNMATCH_EXTFLAG|FNM_CASEFOLD) != FNM_NOMATCH)
+      return 1;
+  return 0;
+}
 
 /* Return some flags based on information about this file.
    The EXISTS bit is non-zero if the file is found.
@@ -81,9 +115,10 @@ int dot_found_in_search = 0;
    Zero is returned if the file is not found. */
 int
 file_status (name)
-     char *name;
+     const char *name;
 {
   struct stat finfo;
+  int r;
 
   /* Determine whether this file exists or not. */
   if (stat (name, &finfo) < 0)
@@ -94,48 +129,73 @@ file_status (name)
   if (S_ISDIR (finfo.st_mode))
     return (FS_EXISTS|FS_DIRECTORY);
 
-#if defined (AFS)
+  r = FS_EXISTS;
+
+#if defined (HAVE_EACCESS)
+  /* Use eaccess(2) if we have it to take things like ACLs and other
+     file access mechanisms into account.  eaccess uses the effective
+     user and group IDs, not the real ones.  We could use sh_eaccess,
+     but we don't want any special treatment for /dev/fd. */
+  if (exec_name_should_ignore (name) == 0 && eaccess (name, X_OK) == 0)
+    r |= FS_EXECABLE;
+  if (eaccess (name, R_OK) == 0)
+    r |= FS_READABLE;
+
+  return r;
+#elif defined (AFS)
   /* We have to use access(2) to determine access because AFS does not
      support Unix file system semantics.  This may produce wrong
      answers for non-AFS files when ruid != euid.  I hate AFS. */
-  if (access (name, X_OK) == 0)
-    return (FS_EXISTS | FS_EXECABLE);
-  else
-    return (FS_EXISTS);
-#else /* !AFS */
+  if (exec_name_should_ignore (name) == 0 && access (name, X_OK) == 0)
+    r |= FS_EXECABLE;
+  if (access (name, R_OK) == 0)
+    r |= FS_READABLE;
+
+  return r;
+#else /* !HAVE_EACCESS && !AFS */
 
   /* Find out if the file is actually executable.  By definition, the
      only other criteria is that the file has an execute bit set that
-     we can use. */
+     we can use.  The same with whether or not a file is readable. */
 
   /* Root only requires execute permission for any of owner, group or
-     others to be able to exec a file. */
+     others to be able to exec a file, and can read any file. */
   if (current_user.euid == (uid_t)0)
     {
-      int bits;
-
-      bits = (u_mode_bits (finfo.st_mode) |
-             g_mode_bits (finfo.st_mode) |
-             o_mode_bits (finfo.st_mode));
-
-      if (X_BIT (bits))
-       return (FS_EXISTS | FS_EXECABLE);
+      r |= FS_READABLE;
+      if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUGO))
+       r |= FS_EXECABLE;
+      return r;
     }
 
-  /* If we are the owner of the file, the owner execute bit applies. */
-  if (current_user.euid == finfo.st_uid && X_BIT (u_mode_bits (finfo.st_mode)))
-    return (FS_EXISTS | FS_EXECABLE);
+  /* If we are the owner of the file, the owner bits apply. */
+  if (current_user.euid == finfo.st_uid)
+    {
+      if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUSR))
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IRUSR)
+       r |= FS_READABLE;
+    }
 
   /* If we are in the owning group, the group permissions apply. */
-  if (group_member (finfo.st_gid) && X_BIT (g_mode_bits (finfo.st_mode)))
-    return (FS_EXISTS | FS_EXECABLE);
+  else if (group_member (finfo.st_gid))
+    {
+      if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXGRP))
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IRGRP)
+       r |= FS_READABLE;
+    }
 
-  /* If `others' have execute permission to the file, then so do we,
-     since we are also `others'. */
-  if (X_BIT (o_mode_bits (finfo.st_mode)))
-    return (FS_EXISTS | FS_EXECABLE);
+  /* Else we check whether `others' have permission to execute the file */
+  else
+    {
+      if (exec_name_should_ignore (name) == 0 && finfo.st_mode & S_IXOTH)
+       r |= FS_EXECABLE;
+      if (finfo.st_mode & S_IROTH)
+       r |= FS_READABLE;
+    }
 
-  return (FS_EXISTS);
+  return r;
 #endif /* !AFS */
 }
 
@@ -145,24 +205,28 @@ file_status (name)
    what an executable file is. */
 int
 executable_file (file)
-     char *file;
+     const char *file;
 {
   int s;
 
   s = file_status (file);
+#if defined (EISDIR)
+  if (s & FS_DIRECTORY)
+    errno = EISDIR;    /* let's see if we can improve error messages */
+#endif
   return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0));
 }
 
 int
 is_directory (file)
-     char *file;
+     const char *file;
 {
   return (file_status (file) & FS_DIRECTORY);
 }
 
 int
 executable_or_directory (file)
-     char *file;
+     const char *file;
 {
   int s;
 
@@ -177,7 +241,7 @@ executable_or_directory (file)
    and that is the only match, then return that. */
 char *
 find_user_command (name)
-     char *name;
+     const char *name;
 {
   return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS));
 }
@@ -185,25 +249,26 @@ find_user_command (name)
 /* Locate the file referenced by NAME, searching along the contents
    of the shell PATH variable.  Return a new string which is the full
    pathname to the file, or NULL if the file couldn't be found.  This
-   returns the first file found. */
+   returns the first readable file found; designed to be used to look
+   for shell scripts or files to source. */
 char *
 find_path_file (name)
-     char *name;
+     const char *name;
 {
-  return (find_user_command_internal (name, FS_EXISTS));
+  return (find_user_command_internal (name, FS_READABLE));
 }
 
 static char *
 _find_user_command_internal (name, flags)
-     char *name;
+     const char *name;
      int flags;
 {
   char *path_list, *cmd;
   SHELL_VAR *var;
 
-  /* Search for the value of PATH in both the temporary environment, and
+  /* Search for the value of PATH in both the temporary environments and
      in the regular list of variables. */
-  if (var = find_variable_internal ("PATH", 1))        /* XXX could be array? */
+  if (var = find_variable_tempenv ("PATH"))    /* XXX could be array? */
     path_list = value_cell (var);
   else
     path_list = (char *)NULL;
@@ -213,21 +278,18 @@ _find_user_command_internal (name, flags)
 
   cmd = find_user_command_in_path (name, path_list, flags);
 
-  if (var && tempvar_p (var))
-    dispose_variable (var);
-
   return (cmd);
 }
 
 static char *
 find_user_command_internal (name, flags)
-     char *name;
+     const char *name;
      int flags;
 {
 #ifdef __WIN32__
   char *res, *dotexe;
 
-  dotexe = xmalloc (strlen (name) + 5);
+  dotexe = (char *)xmalloc (strlen (name) + 5);
   strcpy (dotexe, name);
   strcat (dotexe, ".exe");
   res = _find_user_command_internal (dotexe, flags);
@@ -267,12 +329,17 @@ get_next_path_element (path_list, path_index_pointer)
 
 /* Look for PATHNAME in $PATH.  Returns either the hashed command
    corresponding to PATHNAME or the first instance of PATHNAME found
-   in $PATH.  Returns a newly-allocated string. */
+   in $PATH.  If (FLAGS&CMDSRCH_HASH) is non-zero, insert the instance of
+   PATHNAME found in $PATH into the command hash table.  If (FLAGS&CMDSRCH_STDPATH)
+   is non-zero, we are running in a `command -p' environment and should use
+   the Posix standard path.
+   Returns a newly-allocated string. */
 char *
-search_for_command (pathname)
-     char *pathname;
+search_for_command (pathname, flags)
+     const char *pathname;
+     int flags;
 {
-  char *hashed_file, *command;
+  char *hashed_file, *command, *pathlist;
   int temp_path, st;
   SHELL_VAR *path;
 
@@ -280,14 +347,14 @@ search_for_command (pathname)
 
   /* If PATH is in the temporary environment for this command, don't use the
      hash table to search for the full pathname. */
-  path = find_tempenv_variable ("PATH");
-  temp_path = path != 0;
+  path = find_variable_tempenv ("PATH");
+  temp_path = path && tempvar_p (path);
 
   /* Don't waste time trying to find hashed data for a pathname
      that is already completely specified or if we're using a command-
      specific value for PATH. */
-  if (path == 0 && absolute_program (pathname) == 0)
-    hashed_file = find_hashed_filename (pathname);
+  if (temp_path == 0 && absolute_program (pathname) == 0)
+    hashed_file = phash_search (pathname);
 
   /* If a command found in the hash table no longer exists, we need to
      look for it in $PATH.  Thank you Posix.2.  This forces us to stat
@@ -296,9 +363,9 @@ search_for_command (pathname)
   if (hashed_file && (posixly_correct || check_hashed_filenames))
     {
       st = file_status (hashed_file);
-      if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0)
+      if ((st & (FS_EXISTS|FS_EXECABLE)) != (FS_EXISTS|FS_EXECABLE))
        {
-         remove_hashed_filename (pathname);
+         phash_remove (pathname);
          free (hashed_file);
          hashed_file = (char *)NULL;
        }
@@ -312,26 +379,40 @@ search_for_command (pathname)
     command = savestring (pathname);
   else
     {
-      /* If $PATH is in the temporary environment, we've already retrieved
-        it, so don't bother trying again. */
-      if (temp_path)
+      if (flags & CMDSRCH_STDPATH)
+       pathlist = conf_standard_path ();
+      else if (temp_path || path)
+       pathlist = value_cell (path);
+      else
+       pathlist = 0;
+
+      command = find_user_command_in_path (pathname, pathlist, FS_EXEC_PREFERRED|FS_NODIRS);
+
+      if (command && hashing_enabled && temp_path == 0 && (flags & CMDSRCH_HASH))
        {
-         command = find_user_command_in_path (pathname, value_cell (path),
-                                              FS_EXEC_PREFERRED|FS_NODIRS);
-         if (tempvar_p (path))
-           dispose_variable (path);
+         /* If we found the full pathname the same as the command name, the
+            command probably doesn't exist.  Don't put it into the hash
+            table. */
+         if (STREQ (command, pathname))
+           {
+             st = file_status (command);
+             if (st & FS_EXECABLE)
+               phash_insert ((char *)pathname, command, dot_found_in_search, 1);
+           }
+         else
+           phash_insert ((char *)pathname, command, dot_found_in_search, 1);
        }
-      else
-       command = find_user_command (pathname);
-      if (command && hashing_enabled && temp_path == 0)
-       remember_filename (pathname, command, dot_found_in_search, 1);
+
+      if (flags & CMDSRCH_STDPATH)
+       free (pathlist);
     }
+
   return (command);
 }
 
 char *
 user_command_matches (name, flags, state)
-     char *name;
+     const char *name;
      int flags, state;
 {
   register int i;
@@ -348,7 +429,7 @@ user_command_matches (name, flags, state)
       if (match_list == 0)
        {
          match_list_size = 5;
-         match_list = alloc_array (match_list_size);
+         match_list = strvec_create (match_list_size);
        }
 
       /* Clear out the old match list. */
@@ -369,7 +450,8 @@ user_command_matches (name, flags, state)
          name_len = strlen (name);
          file_to_lose_on = (char *)NULL;
          dot_found_in_search = 0;
-         stat (".", &dotinfo);
+         if (stat (".", &dotinfo) < 0)
+           dotinfo.st_dev = dotinfo.st_ino = 0;        /* so same_file won't match */
          path_list = get_string_value ("PATH");
          path_index = 0;
        }
@@ -391,7 +473,7 @@ user_command_matches (name, flags, state)
          if (match_index + 1 == match_list_size)
            {
              match_list_size += 10;
-             match_list = (char **)xrealloc (match_list, (match_list_size + 1) * sizeof (char *));
+             match_list = strvec_resize (match_list, (match_list_size + 1));
            }
 
          match_list[match_index++] = match;
@@ -414,7 +496,7 @@ user_command_matches (name, flags, state)
 
 static char *
 find_absolute_program (name, flags)
-     char *name;
+     const char *name;
      int flags;
 {
   int st;
@@ -431,19 +513,20 @@ find_absolute_program (name, flags)
   if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE)))
     return (savestring (name));
 
-  return ((char *)NULL);
+  return (NULL);
 }
 
 static char *
 find_in_path_element (name, path, flags, name_len, dotinfop)
-     char *name, *path;
+     const char *name;
+     char *path;
      int flags, name_len;
      struct stat *dotinfop;
 {
   int status;
   char *full_path, *xpath;
 
-  xpath = (*path == '~') ? bash_tilde_expand (path) : path;
+  xpath = (posixly_correct == 0 && *path == '~') ? bash_tilde_expand (path, 0) : path;
 
   /* Remember the location of "." in the path, in all its forms
      (as long as they begin with a `.', e.g. `./.') */
@@ -467,9 +550,14 @@ find_in_path_element (name, path, flags, name_len, dotinfop)
   if (flags & FS_EXISTS)
     return (full_path);
 
+  /* If we have a readable file, and the caller wants a readable file, this
+     is it. */
+  if ((flags & FS_READABLE) && (status & FS_READABLE))
+    return (full_path);
+
   /* If the file is executable, then it satisfies the cases of
       EXEC_ONLY and EXEC_PREFERRED.  Return this file unconditionally. */
-  if ((status & FS_EXECABLE) &&
+  if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) &&
       (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0)))
     {
       FREE (file_to_lose_on);
@@ -480,13 +568,15 @@ find_in_path_element (name, path, flags, name_len, dotinfop)
   /* The file is not executable, but it does exist.  If we prefer
      an executable, then remember this one if it is the first one
      we have found. */
-  if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0)
+  if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0 && exec_name_should_ignore (full_path) == 0)
     file_to_lose_on = savestring (full_path);
 
   /* If we want only executable files, or we don't want directories and
-     this file is a directory, fail. */
-  if ((flags & FS_EXEC_ONLY) || (flags & FS_EXEC_PREFERRED) ||
-      ((flags & FS_NODIRS) && (status & FS_DIRECTORY)))
+     this file is a directory, or we want a readable file and this file
+     isn't readable, fail. */
+  if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) ||
+      ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) ||
+      ((flags & FS_READABLE) && (status & FS_READABLE) == 0))
     {
       free (full_path);
       return ((char *)NULL);
@@ -509,7 +599,7 @@ find_in_path_element (name, path, flags, name_len, dotinfop)
 */
 static char *
 find_user_command_in_path (name, path_list, flags)
-     char *name;
+     const char *name;
      char *path_list;
      int flags;
 {
@@ -532,7 +622,8 @@ find_user_command_in_path (name, path_list, flags)
 
   file_to_lose_on = (char *)NULL;
   name_len = strlen (name);
-  stat (".", &dotinfo);
+  if (stat (".", &dotinfo) < 0)
+    dotinfo.st_dev = dotinfo.st_ino = 0;
   path_index = 0;
 
   while (path_list[path_index])
@@ -578,3 +669,14 @@ find_user_command_in_path (name, path_list, flags)
 
   return (file_to_lose_on);
 }
+
+/* External interface to find a command given a $PATH.  Separate from
+   find_user_command_in_path to allow future customization. */
+char *
+find_in_path (name, path_list, flags)
+     const char *name;
+     char *path_list;
+     int flags;
+{
+  return (find_user_command_in_path (name, path_list, flags));
+}