/* findcmd.c -- Functions to search for commands by name. */
-/* Copyright (C) 1997 Free Software Foundation, Inc.
+/* Copyright (C) 1997-2012 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 1, 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 */
+
+#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
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)
-
/* Return some flags based on information about this file.
The EXISTS bit is non-zero if the file is found.
The EXECABLE bit is non-zero the file is executble.
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)
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 (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 */
+ 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 (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 (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 (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 (finfo.st_mode & S_IXOTH)
+ r |= FS_EXECABLE;
+ if (finfo.st_mode & S_IROTH)
+ r |= FS_READABLE;
+ }
- return (FS_EXISTS);
+ return r;
#endif /* !AFS */
}
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)
+ const char *file;
+{
+ int s;
+
+ s = file_status (file);
+ return ((s & FS_EXECABLE) || (s & FS_DIRECTORY));
+}
+
/* Locate the executable 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
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));
}
/* 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;
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);
path = extract_colon_unit (path_list, path_index_pointer);
- if (!path)
+ if (path == 0)
return (path);
- if (!*path)
+ if (*path == '\0')
{
free (path);
path = savestring (".");
/* 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&1) is non-zero, insert the instance of PATHNAME
+ found in $PATH into the command hash table. 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;
int temp_path, st;
/* 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);
+ if (temp_path == 0 && path)
+ path = (SHELL_VAR *)NULL;
/* 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);
+ 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
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;
}
/* If $PATH is in the temporary environment, we've already retrieved
it, so don't bother trying again. */
if (temp_path)
- {
+ {
command = find_user_command_in_path (pathname, value_cell (path),
FS_EXEC_PREFERRED|FS_NODIRS);
- if (tempvar_p (path))
- dispose_variable (path);
- }
+ }
else
command = find_user_command (pathname);
- if (command && hashing_enabled && temp_path == 0)
- remember_filename (pathname, command, dot_found_in_search, 1);
+ if (command && hashing_enabled && temp_path == 0 && (flags & 1))
+ phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */
}
return (command);
}
char *
user_command_matches (name, flags, state)
- char *name;
+ const char *name;
int flags, state;
{
register int i;
if (match_list == 0)
{
match_list_size = 5;
- match_list = (char **)xmalloc (match_list_size * sizeof(char *));
+ match_list = strvec_create (match_list_size);
}
/* Clear out the old match list. */
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;
}
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;
return (match);
}
-/* Turn PATH, a directory, and NAME, a filename, into a full pathname.
- This allocates new memory and returns it. */
-static char *
-make_full_pathname (path, name, name_len)
- char *path, *name;
- int name_len;
-{
- char *full_path;
- int path_len;
-
- path_len = strlen (path);
- full_path = xmalloc (2 + path_len + name_len);
- strcpy (full_path, path);
- full_path[path_len] = '/';
- strcpy (full_path + path_len + 1, name);
- return (full_path);
-}
-
static char *
find_absolute_program (name, flags)
- char *name;
+ const char *name;
int flags;
{
int st;
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 = (*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. `./.') */
if (dot_found_in_search == 0 && *xpath == '.')
dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL);
- full_path = make_full_pathname (xpath, name, name_len);
+ full_path = sh_makepath (xpath, name, 0);
status = file_status (full_path);
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);
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);
*/
static char *
find_user_command_in_path (name, path_list, flags)
- char *name;
+ const char *name;
char *path_list;
int 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])
/* We didn't find exactly what the user was looking for. Return
the contents of FILE_TO_LOSE_ON which is NULL when the search
required an executable, or non-NULL if a file was found and the
- search would accept a non-executable as a last resort. */
+ search would accept a non-executable as a last resort. If the
+ caller specified FS_NODIRS, and file_to_lose_on is a directory,
+ return NULL. */
+ if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on))
+ {
+ free (file_to_lose_on);
+ file_to_lose_on = (char *)NULL;
+ }
+
return (file_to_lose_on);
}