1 /* findcmd.c -- Functions to search for commands by name. */
3 /* Copyright (C) 1997 Free Software Foundation, Inc.
5 This file is part of GNU Bash, the Bourne Again SHell.
7 Bash is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
12 Bash is distributed in the hope that it will be useful, but WITHOUT
13 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
15 License for more details.
17 You should have received a copy of the GNU General Public License
18 along with Bash; see the file COPYING. If not, write to the
19 Free Software Foundation Inc.,
20 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */
25 #include "chartypes.h"
26 #include "bashtypes.h"
27 #if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
28 # include <sys/file.h>
31 #include "posixstat.h"
33 #if defined (HAVE_UNISTD_H)
45 #include "findcmd.h" /* matching prototypes and declarations */
47 extern int posixly_correct
;
49 /* Static functions defined and used in this file. */
50 static char *_find_user_command_internal
__P((const char *, int));
51 static char *find_user_command_internal
__P((const char *, int));
52 static char *find_user_command_in_path
__P((const char *, char *, int));
53 static char *find_in_path_element
__P((const char *, char *, int, int, struct stat
*));
54 static char *find_absolute_program
__P((const char *, int));
56 static char *get_next_path_element
__P((char *, int *));
58 /* The file name which we would try to execute, except that it isn't
59 possible to execute it. This is the first file that matches the
60 name that we are looking for while we are searching $PATH for a
61 suitable one to execute. If we cannot find a suitable executable
62 file, then we use this one. */
63 static char *file_to_lose_on
;
65 /* Non-zero if we should stat every command found in the hash table to
66 make sure it still exists. */
67 int check_hashed_filenames
;
69 /* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command ()
70 encounters a `.' as the directory pathname while scanning the
71 list of possible pathnames; i.e., if `.' comes before the directory
72 containing the file of interest. */
73 int dot_found_in_search
= 0;
75 /* Return some flags based on information about this file.
76 The EXISTS bit is non-zero if the file is found.
77 The EXECABLE bit is non-zero the file is executble.
78 Zero is returned if the file is not found. */
86 /* Determine whether this file exists or not. */
87 if (stat (name
, &finfo
) < 0)
90 /* If the file is a directory, then it is not "executable" in the
91 sense of the shell. */
92 if (S_ISDIR (finfo
.st_mode
))
93 return (FS_EXISTS
|FS_DIRECTORY
);
98 /* We have to use access(2) to determine access because AFS does not
99 support Unix file system semantics. This may produce wrong
100 answers for non-AFS files when ruid != euid. I hate AFS. */
101 if (access (name
, X_OK
) == 0)
103 if (access (name
, R_OK
) == 0)
109 /* Find out if the file is actually executable. By definition, the
110 only other criteria is that the file has an execute bit set that
111 we can use. The same with whether or not a file is readable. */
113 /* Root only requires execute permission for any of owner, group or
114 others to be able to exec a file, and can read any file. */
115 if (current_user
.euid
== (uid_t
)0)
118 if (finfo
.st_mode
& S_IXUGO
)
123 /* If we are the owner of the file, the owner bits apply. */
124 if (current_user
.euid
== finfo
.st_uid
)
126 if (finfo
.st_mode
& S_IXUSR
)
128 if (finfo
.st_mode
& S_IRUSR
)
132 /* If we are in the owning group, the group permissions apply. */
133 else if (group_member (finfo
.st_gid
))
135 if (finfo
.st_mode
& S_IXGRP
)
137 if (finfo
.st_mode
& S_IRGRP
)
141 /* Else we check whether `others' have permission to execute the file */
144 if (finfo
.st_mode
& S_IXOTH
)
146 if (finfo
.st_mode
& S_IROTH
)
154 /* Return non-zero if FILE exists and is executable.
155 Note that this function is the definition of what an
156 executable file is; do not change this unless YOU know
157 what an executable file is. */
159 executable_file (file
)
164 s
= file_status (file
);
165 return ((s
& FS_EXECABLE
) && ((s
& FS_DIRECTORY
) == 0));
172 return (file_status (file
) & FS_DIRECTORY
);
176 executable_or_directory (file
)
181 s
= file_status (file
);
182 return ((s
& FS_EXECABLE
) || (s
& FS_DIRECTORY
));
185 /* Locate the executable file referenced by NAME, searching along
186 the contents of the shell PATH variable. Return a new string
187 which is the full pathname to the file, or NULL if the file
188 couldn't be found. If a file is found that isn't executable,
189 and that is the only match, then return that. */
191 find_user_command (name
)
194 return (find_user_command_internal (name
, FS_EXEC_PREFERRED
|FS_NODIRS
));
197 /* Locate the file referenced by NAME, searching along the contents
198 of the shell PATH variable. Return a new string which is the full
199 pathname to the file, or NULL if the file couldn't be found. This
200 returns the first readable file found; designed to be used to look
201 for shell scripts or files to source. */
203 find_path_file (name
)
206 return (find_user_command_internal (name
, FS_READABLE
));
210 _find_user_command_internal (name
, flags
)
214 char *path_list
, *cmd
;
217 /* Search for the value of PATH in both the temporary environments and
218 in the regular list of variables. */
219 if (var
= find_variable_internal ("PATH", 1)) /* XXX could be array? */
220 path_list
= value_cell (var
);
222 path_list
= (char *)NULL
;
224 if (path_list
== 0 || *path_list
== '\0')
225 return (savestring (name
));
227 cmd
= find_user_command_in_path (name
, path_list
, flags
);
233 find_user_command_internal (name
, flags
)
240 dotexe
= (char *)xmalloc (strlen (name
) + 5);
241 strcpy (dotexe
, name
);
242 strcat (dotexe
, ".exe");
243 res
= _find_user_command_internal (dotexe
, flags
);
246 res
= _find_user_command_internal (name
, flags
);
249 return (_find_user_command_internal (name
, flags
));
253 /* Return the next element from PATH_LIST, a colon separated list of
254 paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST;
255 the index is modified by this function.
256 Return the next element of PATH_LIST or NULL if there are no more. */
258 get_next_path_element (path_list
, path_index_pointer
)
260 int *path_index_pointer
;
264 path
= extract_colon_unit (path_list
, path_index_pointer
);
272 path
= savestring (".");
278 /* Look for PATHNAME in $PATH. Returns either the hashed command
279 corresponding to PATHNAME or the first instance of PATHNAME found
280 in $PATH. Returns a newly-allocated string. */
282 search_for_command (pathname
)
283 const char *pathname
;
285 char *hashed_file
, *command
;
289 hashed_file
= command
= (char *)NULL
;
291 /* If PATH is in the temporary environment for this command, don't use the
292 hash table to search for the full pathname. */
293 path
= find_variable_internal ("PATH", 1);
294 temp_path
= path
&& tempvar_p (path
);
295 if (temp_path
== 0 && path
)
296 path
= (SHELL_VAR
*)NULL
;
298 /* Don't waste time trying to find hashed data for a pathname
299 that is already completely specified or if we're using a command-
300 specific value for PATH. */
301 if (path
== 0 && absolute_program (pathname
) == 0)
302 hashed_file
= phash_search (pathname
);
304 /* If a command found in the hash table no longer exists, we need to
305 look for it in $PATH. Thank you Posix.2. This forces us to stat
306 every command found in the hash table. */
308 if (hashed_file
&& (posixly_correct
|| check_hashed_filenames
))
310 st
= file_status (hashed_file
);
311 if ((st
& (FS_EXISTS
|FS_EXECABLE
)) != (FS_EXISTS
|FS_EXECABLE
))
313 phash_remove (pathname
);
315 hashed_file
= (char *)NULL
;
320 command
= hashed_file
;
321 else if (absolute_program (pathname
))
322 /* A command containing a slash is not looked up in PATH or saved in
324 command
= savestring (pathname
);
327 /* If $PATH is in the temporary environment, we've already retrieved
328 it, so don't bother trying again. */
331 command
= find_user_command_in_path (pathname
, value_cell (path
),
332 FS_EXEC_PREFERRED
|FS_NODIRS
);
335 command
= find_user_command (pathname
);
336 if (command
&& hashing_enabled
&& temp_path
== 0)
337 phash_insert ((char *)pathname
, command
, dot_found_in_search
, 1); /* XXX fix const later */
343 user_command_matches (name
, flags
, state
)
348 int path_index
, name_len
;
349 char *path_list
, *path_element
, *match
;
351 static char **match_list
= NULL
;
352 static int match_list_size
= 0;
353 static int match_index
= 0;
357 /* Create the list of matches. */
361 match_list
= strvec_create (match_list_size
);
364 /* Clear out the old match list. */
365 for (i
= 0; i
< match_list_size
; i
++)
368 /* We haven't found any files yet. */
371 if (absolute_program (name
))
373 match_list
[0] = find_absolute_program (name
, flags
);
374 match_list
[1] = (char *)NULL
;
375 path_list
= (char *)NULL
;
379 name_len
= strlen (name
);
380 file_to_lose_on
= (char *)NULL
;
381 dot_found_in_search
= 0;
382 stat (".", &dotinfo
);
383 path_list
= get_string_value ("PATH");
387 while (path_list
&& path_list
[path_index
])
389 path_element
= get_next_path_element (path_list
, &path_index
);
391 if (path_element
== 0)
394 match
= find_in_path_element (name
, path_element
, flags
, name_len
, &dotinfo
);
401 if (match_index
+ 1 == match_list_size
)
403 match_list_size
+= 10;
404 match_list
= strvec_resize (match_list
, (match_list_size
+ 1));
407 match_list
[match_index
++] = match
;
408 match_list
[match_index
] = (char *)NULL
;
409 FREE (file_to_lose_on
);
410 file_to_lose_on
= (char *)NULL
;
413 /* We haven't returned any strings yet. */
417 match
= match_list
[match_index
];
426 find_absolute_program (name
, flags
)
432 st
= file_status (name
);
434 /* If the file doesn't exist, quit now. */
435 if ((st
& FS_EXISTS
) == 0)
436 return ((char *)NULL
);
438 /* If we only care about whether the file exists or not, return
439 this filename. Otherwise, maybe we care about whether this
440 file is executable. If it is, and that is what we want, return it. */
441 if ((flags
& FS_EXISTS
) || ((flags
& FS_EXEC_ONLY
) && (st
& FS_EXECABLE
)))
442 return (savestring (name
));
448 find_in_path_element (name
, path
, flags
, name_len
, dotinfop
)
452 struct stat
*dotinfop
;
455 char *full_path
, *xpath
;
457 xpath
= (*path
== '~') ? bash_tilde_expand (path
, 0) : path
;
459 /* Remember the location of "." in the path, in all its forms
460 (as long as they begin with a `.', e.g. `./.') */
461 if (dot_found_in_search
== 0 && *xpath
== '.')
462 dot_found_in_search
= same_file (".", xpath
, dotinfop
, (struct stat
*)NULL
);
464 full_path
= sh_makepath (xpath
, name
, 0);
466 status
= file_status (full_path
);
471 if ((status
& FS_EXISTS
) == 0)
474 return ((char *)NULL
);
477 /* The file exists. If the caller simply wants the first file, here it is. */
478 if (flags
& FS_EXISTS
)
481 /* If we have a readable file, and the caller wants a readable file, this
483 if ((flags
& FS_READABLE
) && (status
& FS_READABLE
))
486 /* If the file is executable, then it satisfies the cases of
487 EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */
488 if ((status
& FS_EXECABLE
) && (flags
& (FS_EXEC_ONLY
|FS_EXEC_PREFERRED
)) &&
489 (((flags
& FS_NODIRS
) == 0) || ((status
& FS_DIRECTORY
) == 0)))
491 FREE (file_to_lose_on
);
492 file_to_lose_on
= (char *)NULL
;
496 /* The file is not executable, but it does exist. If we prefer
497 an executable, then remember this one if it is the first one
499 if ((flags
& FS_EXEC_PREFERRED
) && file_to_lose_on
== 0)
500 file_to_lose_on
= savestring (full_path
);
502 /* If we want only executable files, or we don't want directories and
503 this file is a directory, or we want a readable file and this file
504 isn't readable, fail. */
505 if ((flags
& (FS_EXEC_ONLY
|FS_EXEC_PREFERRED
)) ||
506 ((flags
& FS_NODIRS
) && (status
& FS_DIRECTORY
)) ||
507 ((flags
& FS_READABLE
) && (status
& FS_READABLE
) == 0))
510 return ((char *)NULL
);
516 /* This does the dirty work for find_user_command_internal () and
517 user_command_matches ().
518 NAME is the name of the file to search for.
519 PATH_LIST is a colon separated list of directories to search.
520 FLAGS contains bit fields which control the files which are eligible.
522 FS_EXEC_ONLY: The file must be an executable to be found.
523 FS_EXEC_PREFERRED: If we can't find an executable, then the
524 the first file matching NAME will do.
525 FS_EXISTS: The first file found will do.
526 FS_NODIRS: Don't find any directories.
529 find_user_command_in_path (name
, path_list
, flags
)
534 char *full_path
, *path
;
535 int path_index
, name_len
;
538 /* We haven't started looking, so we certainly haven't seen
539 a `.' as the directory path yet. */
540 dot_found_in_search
= 0;
542 if (absolute_program (name
))
544 full_path
= find_absolute_program (name
, flags
);
548 if (path_list
== 0 || *path_list
== '\0')
549 return (savestring (name
)); /* XXX */
551 file_to_lose_on
= (char *)NULL
;
552 name_len
= strlen (name
);
553 stat (".", &dotinfo
);
556 while (path_list
[path_index
])
558 /* Allow the user to interrupt out of a lengthy path search. */
561 path
= get_next_path_element (path_list
, &path_index
);
565 /* Side effects: sets dot_found_in_search, possibly sets
567 full_path
= find_in_path_element (name
, path
, flags
, name_len
, &dotinfo
);
570 /* This should really be in find_in_path_element, but there isn't the
571 right combination of flags. */
572 if (full_path
&& is_directory (full_path
))
580 FREE (file_to_lose_on
);
585 /* We didn't find exactly what the user was looking for. Return
586 the contents of FILE_TO_LOSE_ON which is NULL when the search
587 required an executable, or non-NULL if a file was found and the
588 search would accept a non-executable as a last resort. If the
589 caller specified FS_NODIRS, and file_to_lose_on is a directory,
591 if (file_to_lose_on
&& (flags
& FS_NODIRS
) && is_directory (file_to_lose_on
))
593 free (file_to_lose_on
);
594 file_to_lose_on
= (char *)NULL
;
597 return (file_to_lose_on
);