Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 1 | /* findcmd.c -- Functions to search for commands by name. */ |
| 2 | |
Jari Aalto | 3185942 | 2009-01-12 13:36:28 +0000 | [diff] [blame] | 3 | /* Copyright (C) 1997-2009 Free Software Foundation, Inc. |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 4 | |
| 5 | This file is part of GNU Bash, the Bourne Again SHell. |
| 6 | |
Jari Aalto | 3185942 | 2009-01-12 13:36:28 +0000 | [diff] [blame] | 7 | Bash is free software: you can redistribute it and/or modify |
| 8 | it under the terms of the GNU General Public License as published by |
| 9 | the Free Software Foundation, either version 3 of the License, or |
| 10 | (at your option) any later version. |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 11 | |
Jari Aalto | 3185942 | 2009-01-12 13:36:28 +0000 | [diff] [blame] | 12 | Bash is distributed in the hope that it will be useful, |
| 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | GNU General Public License for more details. |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 16 | |
| 17 | You should have received a copy of the GNU General Public License |
Jari Aalto | 3185942 | 2009-01-12 13:36:28 +0000 | [diff] [blame] | 18 | along with Bash. If not, see <http://www.gnu.org/licenses/>. |
| 19 | */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 20 | |
| 21 | #include "config.h" |
| 22 | |
| 23 | #include <stdio.h> |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 24 | #include "chartypes.h" |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 25 | #include "bashtypes.h" |
Jari Aalto | b80f644 | 2004-07-27 13:29:18 +0000 | [diff] [blame] | 26 | #if !defined (_MINIX) && defined (HAVE_SYS_FILE_H) |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 27 | # include <sys/file.h> |
| 28 | #endif |
| 29 | #include "filecntl.h" |
| 30 | #include "posixstat.h" |
| 31 | |
| 32 | #if defined (HAVE_UNISTD_H) |
| 33 | # include <unistd.h> |
| 34 | #endif |
Chet Ramey | 495aee4 | 2011-11-22 19:11:26 -0500 | [diff] [blame] | 35 | #include <errno.h> |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 36 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 37 | #include "bashansi.h" |
| 38 | |
| 39 | #include "memalloc.h" |
| 40 | #include "shell.h" |
| 41 | #include "flags.h" |
| 42 | #include "hashlib.h" |
| 43 | #include "pathexp.h" |
| 44 | #include "hashcmd.h" |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 45 | #include "findcmd.h" /* matching prototypes and declarations */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 46 | |
Chet Ramey | 495aee4 | 2011-11-22 19:11:26 -0500 | [diff] [blame] | 47 | #if !defined (errno) |
| 48 | extern int errno; |
| 49 | #endif |
| 50 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 51 | extern int posixly_correct; |
| 52 | |
| 53 | /* Static functions defined and used in this file. */ |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 54 | static char *_find_user_command_internal __P((const char *, int)); |
| 55 | static char *find_user_command_internal __P((const char *, int)); |
| 56 | static char *find_user_command_in_path __P((const char *, char *, int)); |
| 57 | static char *find_in_path_element __P((const char *, char *, int, int, struct stat *)); |
| 58 | static char *find_absolute_program __P((const char *, int)); |
| 59 | |
| 60 | static char *get_next_path_element __P((char *, int *)); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 61 | |
| 62 | /* The file name which we would try to execute, except that it isn't |
| 63 | possible to execute it. This is the first file that matches the |
| 64 | name that we are looking for while we are searching $PATH for a |
| 65 | suitable one to execute. If we cannot find a suitable executable |
| 66 | file, then we use this one. */ |
| 67 | static char *file_to_lose_on; |
| 68 | |
| 69 | /* Non-zero if we should stat every command found in the hash table to |
| 70 | make sure it still exists. */ |
| 71 | int check_hashed_filenames; |
| 72 | |
| 73 | /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command () |
| 74 | encounters a `.' as the directory pathname while scanning the |
| 75 | list of possible pathnames; i.e., if `.' comes before the directory |
| 76 | containing the file of interest. */ |
| 77 | int dot_found_in_search = 0; |
| 78 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 79 | /* Return some flags based on information about this file. |
| 80 | The EXISTS bit is non-zero if the file is found. |
| 81 | The EXECABLE bit is non-zero the file is executble. |
| 82 | Zero is returned if the file is not found. */ |
| 83 | int |
| 84 | file_status (name) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 85 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 86 | { |
| 87 | struct stat finfo; |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 88 | int r; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 89 | |
| 90 | /* Determine whether this file exists or not. */ |
| 91 | if (stat (name, &finfo) < 0) |
| 92 | return (0); |
| 93 | |
| 94 | /* If the file is a directory, then it is not "executable" in the |
| 95 | sense of the shell. */ |
| 96 | if (S_ISDIR (finfo.st_mode)) |
| 97 | return (FS_EXISTS|FS_DIRECTORY); |
| 98 | |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 99 | r = FS_EXISTS; |
| 100 | |
Chet Ramey | 0001803 | 2011-11-21 20:51:19 -0500 | [diff] [blame] | 101 | #if defined (HAVE_EACCESS) |
| 102 | /* Use eaccess(2) if we have it to take things like ACLs and other |
| 103 | file access mechanisms into account. eaccess uses the effective |
| 104 | user and group IDs, not the real ones. We could use sh_eaccess, |
| 105 | but we don't want any special treatment for /dev/fd. */ |
| 106 | if (eaccess (name, X_OK) == 0) |
| 107 | r |= FS_EXECABLE; |
| 108 | if (eaccess (name, R_OK) == 0) |
| 109 | r |= FS_READABLE; |
| 110 | |
| 111 | return r; |
| 112 | #elif defined (AFS) |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 113 | /* We have to use access(2) to determine access because AFS does not |
| 114 | support Unix file system semantics. This may produce wrong |
| 115 | answers for non-AFS files when ruid != euid. I hate AFS. */ |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 116 | if (access (name, X_OK) == 0) |
| 117 | r |= FS_EXECABLE; |
| 118 | if (access (name, R_OK) == 0) |
| 119 | r |= FS_READABLE; |
| 120 | |
| 121 | return r; |
Chet Ramey | 0001803 | 2011-11-21 20:51:19 -0500 | [diff] [blame] | 122 | #else /* !HAVE_EACCESS && !AFS */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 123 | |
| 124 | /* Find out if the file is actually executable. By definition, the |
| 125 | only other criteria is that the file has an execute bit set that |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 126 | we can use. The same with whether or not a file is readable. */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 127 | |
| 128 | /* Root only requires execute permission for any of owner, group or |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 129 | others to be able to exec a file, and can read any file. */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 130 | if (current_user.euid == (uid_t)0) |
| 131 | { |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 132 | r |= FS_READABLE; |
| 133 | if (finfo.st_mode & S_IXUGO) |
| 134 | r |= FS_EXECABLE; |
| 135 | return r; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 136 | } |
| 137 | |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 138 | /* If we are the owner of the file, the owner bits apply. */ |
Jari Aalto | b80f644 | 2004-07-27 13:29:18 +0000 | [diff] [blame] | 139 | if (current_user.euid == finfo.st_uid) |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 140 | { |
| 141 | if (finfo.st_mode & S_IXUSR) |
| 142 | r |= FS_EXECABLE; |
| 143 | if (finfo.st_mode & S_IRUSR) |
| 144 | r |= FS_READABLE; |
| 145 | } |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 146 | |
| 147 | /* If we are in the owning group, the group permissions apply. */ |
Jari Aalto | b80f644 | 2004-07-27 13:29:18 +0000 | [diff] [blame] | 148 | else if (group_member (finfo.st_gid)) |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 149 | { |
| 150 | if (finfo.st_mode & S_IXGRP) |
| 151 | r |= FS_EXECABLE; |
| 152 | if (finfo.st_mode & S_IRGRP) |
| 153 | r |= FS_READABLE; |
| 154 | } |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 155 | |
Jari Aalto | b80f644 | 2004-07-27 13:29:18 +0000 | [diff] [blame] | 156 | /* Else we check whether `others' have permission to execute the file */ |
| 157 | else |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 158 | { |
| 159 | if (finfo.st_mode & S_IXOTH) |
| 160 | r |= FS_EXECABLE; |
| 161 | if (finfo.st_mode & S_IROTH) |
| 162 | r |= FS_READABLE; |
| 163 | } |
| 164 | |
| 165 | return r; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 166 | #endif /* !AFS */ |
| 167 | } |
| 168 | |
| 169 | /* Return non-zero if FILE exists and is executable. |
| 170 | Note that this function is the definition of what an |
| 171 | executable file is; do not change this unless YOU know |
| 172 | what an executable file is. */ |
| 173 | int |
| 174 | executable_file (file) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 175 | const char *file; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 176 | { |
| 177 | int s; |
| 178 | |
| 179 | s = file_status (file); |
Chet Ramey | 495aee4 | 2011-11-22 19:11:26 -0500 | [diff] [blame] | 180 | #if defined EISDIR |
| 181 | if (s & FS_DIRECTORY) |
| 182 | errno = EISDIR; /* let's see if we can improve error messages */ |
| 183 | #endif |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 184 | return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0)); |
| 185 | } |
| 186 | |
| 187 | int |
| 188 | is_directory (file) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 189 | const char *file; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 190 | { |
| 191 | return (file_status (file) & FS_DIRECTORY); |
| 192 | } |
| 193 | |
Jari Aalto | 28ef6c3 | 2001-04-06 19:14:31 +0000 | [diff] [blame] | 194 | int |
| 195 | executable_or_directory (file) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 196 | const char *file; |
Jari Aalto | 28ef6c3 | 2001-04-06 19:14:31 +0000 | [diff] [blame] | 197 | { |
| 198 | int s; |
| 199 | |
| 200 | s = file_status (file); |
| 201 | return ((s & FS_EXECABLE) || (s & FS_DIRECTORY)); |
| 202 | } |
| 203 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 204 | /* Locate the executable file referenced by NAME, searching along |
| 205 | the contents of the shell PATH variable. Return a new string |
| 206 | which is the full pathname to the file, or NULL if the file |
| 207 | couldn't be found. If a file is found that isn't executable, |
| 208 | and that is the only match, then return that. */ |
| 209 | char * |
| 210 | find_user_command (name) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 211 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 212 | { |
| 213 | return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS)); |
| 214 | } |
| 215 | |
| 216 | /* Locate the file referenced by NAME, searching along the contents |
| 217 | of the shell PATH variable. Return a new string which is the full |
| 218 | pathname to the file, or NULL if the file couldn't be found. This |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 219 | returns the first readable file found; designed to be used to look |
| 220 | for shell scripts or files to source. */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 221 | char * |
| 222 | find_path_file (name) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 223 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 224 | { |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 225 | return (find_user_command_internal (name, FS_READABLE)); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | static char * |
| 229 | _find_user_command_internal (name, flags) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 230 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 231 | int flags; |
| 232 | { |
| 233 | char *path_list, *cmd; |
| 234 | SHELL_VAR *var; |
| 235 | |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 236 | /* Search for the value of PATH in both the temporary environments and |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 237 | in the regular list of variables. */ |
| 238 | if (var = find_variable_internal ("PATH", 1)) /* XXX could be array? */ |
| 239 | path_list = value_cell (var); |
| 240 | else |
| 241 | path_list = (char *)NULL; |
| 242 | |
| 243 | if (path_list == 0 || *path_list == '\0') |
| 244 | return (savestring (name)); |
| 245 | |
| 246 | cmd = find_user_command_in_path (name, path_list, flags); |
| 247 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 248 | return (cmd); |
| 249 | } |
| 250 | |
| 251 | static char * |
| 252 | find_user_command_internal (name, flags) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 253 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 254 | int flags; |
| 255 | { |
| 256 | #ifdef __WIN32__ |
| 257 | char *res, *dotexe; |
| 258 | |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 259 | dotexe = (char *)xmalloc (strlen (name) + 5); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 260 | strcpy (dotexe, name); |
| 261 | strcat (dotexe, ".exe"); |
| 262 | res = _find_user_command_internal (dotexe, flags); |
| 263 | free (dotexe); |
| 264 | if (res == 0) |
| 265 | res = _find_user_command_internal (name, flags); |
| 266 | return res; |
| 267 | #else |
| 268 | return (_find_user_command_internal (name, flags)); |
| 269 | #endif |
| 270 | } |
| 271 | |
| 272 | /* Return the next element from PATH_LIST, a colon separated list of |
| 273 | paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST; |
| 274 | the index is modified by this function. |
| 275 | Return the next element of PATH_LIST or NULL if there are no more. */ |
| 276 | static char * |
| 277 | get_next_path_element (path_list, path_index_pointer) |
| 278 | char *path_list; |
| 279 | int *path_index_pointer; |
| 280 | { |
| 281 | char *path; |
| 282 | |
| 283 | path = extract_colon_unit (path_list, path_index_pointer); |
| 284 | |
Jari Aalto | bb70624 | 2000-03-17 21:46:59 +0000 | [diff] [blame] | 285 | if (path == 0) |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 286 | return (path); |
| 287 | |
Jari Aalto | bb70624 | 2000-03-17 21:46:59 +0000 | [diff] [blame] | 288 | if (*path == '\0') |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 289 | { |
| 290 | free (path); |
| 291 | path = savestring ("."); |
| 292 | } |
| 293 | |
| 294 | return (path); |
| 295 | } |
| 296 | |
| 297 | /* Look for PATHNAME in $PATH. Returns either the hashed command |
| 298 | corresponding to PATHNAME or the first instance of PATHNAME found |
| 299 | in $PATH. Returns a newly-allocated string. */ |
| 300 | char * |
| 301 | search_for_command (pathname) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 302 | const char *pathname; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 303 | { |
| 304 | char *hashed_file, *command; |
| 305 | int temp_path, st; |
| 306 | SHELL_VAR *path; |
| 307 | |
| 308 | hashed_file = command = (char *)NULL; |
| 309 | |
| 310 | /* If PATH is in the temporary environment for this command, don't use the |
| 311 | hash table to search for the full pathname. */ |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 312 | path = find_variable_internal ("PATH", 1); |
| 313 | temp_path = path && tempvar_p (path); |
| 314 | if (temp_path == 0 && path) |
| 315 | path = (SHELL_VAR *)NULL; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 316 | |
| 317 | /* Don't waste time trying to find hashed data for a pathname |
| 318 | that is already completely specified or if we're using a command- |
| 319 | specific value for PATH. */ |
| 320 | if (path == 0 && absolute_program (pathname) == 0) |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 321 | hashed_file = phash_search (pathname); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 322 | |
| 323 | /* If a command found in the hash table no longer exists, we need to |
| 324 | look for it in $PATH. Thank you Posix.2. This forces us to stat |
| 325 | every command found in the hash table. */ |
| 326 | |
| 327 | if (hashed_file && (posixly_correct || check_hashed_filenames)) |
| 328 | { |
| 329 | st = file_status (hashed_file); |
Jari Aalto | f1be666 | 2008-11-18 13:15:12 +0000 | [diff] [blame] | 330 | if ((st & (FS_EXISTS|FS_EXECABLE)) != (FS_EXISTS|FS_EXECABLE)) |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 331 | { |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 332 | phash_remove (pathname); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 333 | free (hashed_file); |
| 334 | hashed_file = (char *)NULL; |
| 335 | } |
| 336 | } |
| 337 | |
| 338 | if (hashed_file) |
| 339 | command = hashed_file; |
| 340 | else if (absolute_program (pathname)) |
| 341 | /* A command containing a slash is not looked up in PATH or saved in |
| 342 | the hash table. */ |
| 343 | command = savestring (pathname); |
| 344 | else |
| 345 | { |
| 346 | /* If $PATH is in the temporary environment, we've already retrieved |
| 347 | it, so don't bother trying again. */ |
| 348 | if (temp_path) |
Jari Aalto | 28ef6c3 | 2001-04-06 19:14:31 +0000 | [diff] [blame] | 349 | { |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 350 | command = find_user_command_in_path (pathname, value_cell (path), |
| 351 | FS_EXEC_PREFERRED|FS_NODIRS); |
Jari Aalto | 28ef6c3 | 2001-04-06 19:14:31 +0000 | [diff] [blame] | 352 | } |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 353 | else |
| 354 | command = find_user_command (pathname); |
| 355 | if (command && hashing_enabled && temp_path == 0) |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 356 | phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */ |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 357 | } |
| 358 | return (command); |
| 359 | } |
| 360 | |
| 361 | char * |
| 362 | user_command_matches (name, flags, state) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 363 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 364 | int flags, state; |
| 365 | { |
| 366 | register int i; |
| 367 | int path_index, name_len; |
| 368 | char *path_list, *path_element, *match; |
| 369 | struct stat dotinfo; |
| 370 | static char **match_list = NULL; |
| 371 | static int match_list_size = 0; |
| 372 | static int match_index = 0; |
| 373 | |
| 374 | if (state == 0) |
| 375 | { |
| 376 | /* Create the list of matches. */ |
| 377 | if (match_list == 0) |
| 378 | { |
| 379 | match_list_size = 5; |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 380 | match_list = strvec_create (match_list_size); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 381 | } |
| 382 | |
| 383 | /* Clear out the old match list. */ |
| 384 | for (i = 0; i < match_list_size; i++) |
| 385 | match_list[i] = 0; |
| 386 | |
| 387 | /* We haven't found any files yet. */ |
| 388 | match_index = 0; |
| 389 | |
| 390 | if (absolute_program (name)) |
| 391 | { |
| 392 | match_list[0] = find_absolute_program (name, flags); |
| 393 | match_list[1] = (char *)NULL; |
| 394 | path_list = (char *)NULL; |
| 395 | } |
| 396 | else |
| 397 | { |
| 398 | name_len = strlen (name); |
| 399 | file_to_lose_on = (char *)NULL; |
| 400 | dot_found_in_search = 0; |
| 401 | stat (".", &dotinfo); |
| 402 | path_list = get_string_value ("PATH"); |
| 403 | path_index = 0; |
| 404 | } |
| 405 | |
| 406 | while (path_list && path_list[path_index]) |
| 407 | { |
| 408 | path_element = get_next_path_element (path_list, &path_index); |
| 409 | |
| 410 | if (path_element == 0) |
| 411 | break; |
| 412 | |
| 413 | match = find_in_path_element (name, path_element, flags, name_len, &dotinfo); |
| 414 | |
| 415 | free (path_element); |
| 416 | |
| 417 | if (match == 0) |
| 418 | continue; |
| 419 | |
| 420 | if (match_index + 1 == match_list_size) |
| 421 | { |
| 422 | match_list_size += 10; |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 423 | match_list = strvec_resize (match_list, (match_list_size + 1)); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 424 | } |
| 425 | |
| 426 | match_list[match_index++] = match; |
| 427 | match_list[match_index] = (char *)NULL; |
| 428 | FREE (file_to_lose_on); |
| 429 | file_to_lose_on = (char *)NULL; |
| 430 | } |
| 431 | |
| 432 | /* We haven't returned any strings yet. */ |
| 433 | match_index = 0; |
| 434 | } |
| 435 | |
| 436 | match = match_list[match_index]; |
| 437 | |
| 438 | if (match) |
| 439 | match_index++; |
| 440 | |
| 441 | return (match); |
| 442 | } |
| 443 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 444 | static char * |
| 445 | find_absolute_program (name, flags) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 446 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 447 | int flags; |
| 448 | { |
| 449 | int st; |
| 450 | |
| 451 | st = file_status (name); |
| 452 | |
| 453 | /* If the file doesn't exist, quit now. */ |
| 454 | if ((st & FS_EXISTS) == 0) |
| 455 | return ((char *)NULL); |
| 456 | |
| 457 | /* If we only care about whether the file exists or not, return |
| 458 | this filename. Otherwise, maybe we care about whether this |
| 459 | file is executable. If it is, and that is what we want, return it. */ |
| 460 | if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE))) |
| 461 | return (savestring (name)); |
| 462 | |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 463 | return (NULL); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 464 | } |
| 465 | |
| 466 | static char * |
| 467 | find_in_path_element (name, path, flags, name_len, dotinfop) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 468 | const char *name; |
| 469 | char *path; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 470 | int flags, name_len; |
| 471 | struct stat *dotinfop; |
| 472 | { |
| 473 | int status; |
| 474 | char *full_path, *xpath; |
| 475 | |
Jari Aalto | 7117c2d | 2002-07-17 14:10:11 +0000 | [diff] [blame] | 476 | xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 477 | |
| 478 | /* Remember the location of "." in the path, in all its forms |
| 479 | (as long as they begin with a `.', e.g. `./.') */ |
| 480 | if (dot_found_in_search == 0 && *xpath == '.') |
| 481 | dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL); |
| 482 | |
Jari Aalto | bb70624 | 2000-03-17 21:46:59 +0000 | [diff] [blame] | 483 | full_path = sh_makepath (xpath, name, 0); |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 484 | |
| 485 | status = file_status (full_path); |
| 486 | |
| 487 | if (xpath != path) |
| 488 | free (xpath); |
| 489 | |
| 490 | if ((status & FS_EXISTS) == 0) |
| 491 | { |
| 492 | free (full_path); |
| 493 | return ((char *)NULL); |
| 494 | } |
| 495 | |
| 496 | /* The file exists. If the caller simply wants the first file, here it is. */ |
| 497 | if (flags & FS_EXISTS) |
| 498 | return (full_path); |
| 499 | |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 500 | /* If we have a readable file, and the caller wants a readable file, this |
| 501 | is it. */ |
| 502 | if ((flags & FS_READABLE) && (status & FS_READABLE)) |
| 503 | return (full_path); |
| 504 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 505 | /* If the file is executable, then it satisfies the cases of |
| 506 | EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */ |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 507 | if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) && |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 508 | (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0))) |
| 509 | { |
| 510 | FREE (file_to_lose_on); |
| 511 | file_to_lose_on = (char *)NULL; |
| 512 | return (full_path); |
| 513 | } |
| 514 | |
| 515 | /* The file is not executable, but it does exist. If we prefer |
| 516 | an executable, then remember this one if it is the first one |
| 517 | we have found. */ |
| 518 | if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0) |
| 519 | file_to_lose_on = savestring (full_path); |
| 520 | |
| 521 | /* If we want only executable files, or we don't want directories and |
Jari Aalto | 95732b4 | 2005-12-07 14:08:12 +0000 | [diff] [blame] | 522 | this file is a directory, or we want a readable file and this file |
| 523 | isn't readable, fail. */ |
| 524 | if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) || |
| 525 | ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) || |
| 526 | ((flags & FS_READABLE) && (status & FS_READABLE) == 0)) |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 527 | { |
| 528 | free (full_path); |
| 529 | return ((char *)NULL); |
| 530 | } |
| 531 | else |
| 532 | return (full_path); |
| 533 | } |
| 534 | |
| 535 | /* This does the dirty work for find_user_command_internal () and |
| 536 | user_command_matches (). |
| 537 | NAME is the name of the file to search for. |
| 538 | PATH_LIST is a colon separated list of directories to search. |
| 539 | FLAGS contains bit fields which control the files which are eligible. |
| 540 | Some values are: |
| 541 | FS_EXEC_ONLY: The file must be an executable to be found. |
| 542 | FS_EXEC_PREFERRED: If we can't find an executable, then the |
| 543 | the first file matching NAME will do. |
| 544 | FS_EXISTS: The first file found will do. |
| 545 | FS_NODIRS: Don't find any directories. |
| 546 | */ |
| 547 | static char * |
| 548 | find_user_command_in_path (name, path_list, flags) |
Jari Aalto | f73dda0 | 2001-11-13 17:56:06 +0000 | [diff] [blame] | 549 | const char *name; |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 550 | char *path_list; |
| 551 | int flags; |
| 552 | { |
| 553 | char *full_path, *path; |
| 554 | int path_index, name_len; |
| 555 | struct stat dotinfo; |
| 556 | |
| 557 | /* We haven't started looking, so we certainly haven't seen |
| 558 | a `.' as the directory path yet. */ |
| 559 | dot_found_in_search = 0; |
| 560 | |
| 561 | if (absolute_program (name)) |
| 562 | { |
| 563 | full_path = find_absolute_program (name, flags); |
| 564 | return (full_path); |
| 565 | } |
| 566 | |
| 567 | if (path_list == 0 || *path_list == '\0') |
| 568 | return (savestring (name)); /* XXX */ |
| 569 | |
| 570 | file_to_lose_on = (char *)NULL; |
| 571 | name_len = strlen (name); |
| 572 | stat (".", &dotinfo); |
| 573 | path_index = 0; |
| 574 | |
| 575 | while (path_list[path_index]) |
| 576 | { |
| 577 | /* Allow the user to interrupt out of a lengthy path search. */ |
| 578 | QUIT; |
| 579 | |
| 580 | path = get_next_path_element (path_list, &path_index); |
| 581 | if (path == 0) |
| 582 | break; |
| 583 | |
| 584 | /* Side effects: sets dot_found_in_search, possibly sets |
| 585 | file_to_lose_on. */ |
| 586 | full_path = find_in_path_element (name, path, flags, name_len, &dotinfo); |
| 587 | free (path); |
| 588 | |
| 589 | /* This should really be in find_in_path_element, but there isn't the |
| 590 | right combination of flags. */ |
| 591 | if (full_path && is_directory (full_path)) |
| 592 | { |
| 593 | free (full_path); |
| 594 | continue; |
| 595 | } |
| 596 | |
| 597 | if (full_path) |
| 598 | { |
| 599 | FREE (file_to_lose_on); |
| 600 | return (full_path); |
| 601 | } |
| 602 | } |
| 603 | |
| 604 | /* We didn't find exactly what the user was looking for. Return |
| 605 | the contents of FILE_TO_LOSE_ON which is NULL when the search |
| 606 | required an executable, or non-NULL if a file was found and the |
Jari Aalto | 28ef6c3 | 2001-04-06 19:14:31 +0000 | [diff] [blame] | 607 | search would accept a non-executable as a last resort. If the |
| 608 | caller specified FS_NODIRS, and file_to_lose_on is a directory, |
| 609 | return NULL. */ |
| 610 | if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on)) |
| 611 | { |
| 612 | free (file_to_lose_on); |
| 613 | file_to_lose_on = (char *)NULL; |
| 614 | } |
| 615 | |
Jari Aalto | cce855b | 1998-04-17 19:52:44 +0000 | [diff] [blame] | 616 | return (file_to_lose_on); |
| 617 | } |