source: trunk/essentials/app-shells/bash/findcmd.c@ 3283

Last change on this file since 3283 was 3280, checked in by bird, 18 years ago

optimized.

  • Property svn:eol-style set to native
File size: 16.6 KB
Line 
1/* findcmd.c -- Functions to search for commands by name. */
2
3/* Copyright (C) 1997 Free Software Foundation, Inc.
4
5 This file is part of GNU Bash, the Bourne Again SHell.
6
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)
10 any later version.
11
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.
16
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. */
21
22#include "config.h"
23
24#include <stdio.h>
25#include "chartypes.h"
26#include "bashtypes.h"
27#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
28# include <sys/file.h>
29#endif
30#include "filecntl.h"
31#include "posixstat.h"
32
33#if defined (HAVE_UNISTD_H)
34# include <unistd.h>
35#endif
36
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"
45#include "findcmd.h" /* matching prototypes and declarations */
46
47extern int posixly_correct;
48
49/* Static functions defined and used in this file. */
50static char *_find_user_command_internal __P((const char *, int));
51static char *find_user_command_internal __P((const char *, int));
52static char *find_user_command_in_path __P((const char *, char *, int));
53static char *find_in_path_element __P((const char *, char *, int, int, struct stat *));
54static char *find_absolute_program __P((const char *, int));
55
56static char *get_next_path_element __P((char *, int *));
57
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. */
63static char *file_to_lose_on;
64
65/* Non-zero if we should stat every command found in the hash table to
66 make sure it still exists. */
67int check_hashed_filenames;
68
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. */
73int dot_found_in_search = 0;
74
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. */
79int
80file_status (name)
81 const char *name;
82{
83 struct stat finfo;
84 int r;
85
86 /* Determine whether this file exists or not. */
87 if (stat (name, &finfo) < 0)
88 return (0);
89
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);
94
95 r = FS_EXISTS;
96
97#if defined (AFS)
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)
102 r |= FS_EXECABLE;
103 if (access (name, R_OK) == 0)
104 r |= FS_READABLE;
105
106 return r;
107#else /* !AFS */
108
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. */
112
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)
116 {
117 r |= FS_READABLE;
118 if (finfo.st_mode & S_IXUGO)
119 r |= FS_EXECABLE;
120 return r;
121 }
122
123 /* If we are the owner of the file, the owner bits apply. */
124 if (current_user.euid == finfo.st_uid)
125 {
126 if (finfo.st_mode & S_IXUSR)
127 r |= FS_EXECABLE;
128 if (finfo.st_mode & S_IRUSR)
129 r |= FS_READABLE;
130 }
131
132 /* If we are in the owning group, the group permissions apply. */
133 else if (group_member (finfo.st_gid))
134 {
135 if (finfo.st_mode & S_IXGRP)
136 r |= FS_EXECABLE;
137 if (finfo.st_mode & S_IRGRP)
138 r |= FS_READABLE;
139 }
140
141 /* Else we check whether `others' have permission to execute the file */
142 else
143 {
144 if (finfo.st_mode & S_IXOTH)
145 r |= FS_EXECABLE;
146 if (finfo.st_mode & S_IROTH)
147 r |= FS_READABLE;
148 }
149
150 return r;
151#endif /* !AFS */
152}
153
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. */
158int
159executable_file (file)
160 const char *file;
161{
162 int s;
163
164 s = file_status (file);
165 return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0));
166}
167
168int
169is_directory (file)
170 const char *file;
171{
172 return (file_status (file) & FS_DIRECTORY);
173}
174
175int
176executable_or_directory (file)
177 const char *file;
178{
179 int s;
180
181 s = file_status (file);
182 return ((s & FS_EXECABLE) || (s & FS_DIRECTORY));
183}
184
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. */
190char *
191find_user_command (name)
192 const char *name;
193{
194 return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS));
195}
196
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. */
202char *
203find_path_file (name)
204 const char *name;
205{
206 return (find_user_command_internal (name, FS_READABLE));
207}
208
209static char *
210_find_user_command_internal (name, flags)
211 const char *name;
212 int flags;
213{
214 char *path_list, *cmd;
215 SHELL_VAR *var;
216
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);
221 else
222 path_list = (char *)NULL;
223
224 if (path_list == 0 || *path_list == '\0')
225 return (savestring (name));
226
227 cmd = find_user_command_in_path (name, path_list, flags);
228
229 return (cmd);
230}
231
232static char *
233find_user_command_internal (name, flags)
234 const char *name;
235 int flags;
236{
237#if defined (__OS2__)
238 /* search for .exe first. */
239 size_t len = strlen (name);
240 if (len < sizeof (".exe") - 1
241 || stricmp (name + (len - sizeof (".exe") - 1), ".exe") != 0)
242 {
243 char *res;
244 char *dotexe = alloca (len + sizeof (".exe"));
245 memcpy (dotexe, name, len);
246 memcpy (dotexe + len, ".exe", sizeof (".exe"));
247 res = _find_user_command_internal (dotexe, flags);
248 if (res)
249 return res;
250 }
251#endif /* __OS2__ */
252 return (_find_user_command_internal (name, flags));
253}
254
255/* Return the next element from PATH_LIST, a colon separated list of
256 paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST;
257 the index is modified by this function.
258 Return the next element of PATH_LIST or NULL if there are no more. */
259static char *
260get_next_path_element (path_list, path_index_pointer)
261 char *path_list;
262 int *path_index_pointer;
263{
264 char *path;
265
266 path = extract_colon_unit (path_list, path_index_pointer);
267
268 if (path == 0)
269 return (path);
270
271 if (*path == '\0')
272 {
273 free (path);
274 path = savestring (".");
275 }
276
277 return (path);
278}
279
280/* Look for PATHNAME in $PATH. Returns either the hashed command
281 corresponding to PATHNAME or the first instance of PATHNAME found
282 in $PATH. Returns a newly-allocated string. */
283char *
284search_for_command (pathname)
285 const char *pathname;
286{
287 char *hashed_file, *command;
288 int temp_path, st;
289 SHELL_VAR *path;
290
291 hashed_file = command = (char *)NULL;
292
293 /* If PATH is in the temporary environment for this command, don't use the
294 hash table to search for the full pathname. */
295 path = find_variable_internal ("PATH", 1);
296 temp_path = path && tempvar_p (path);
297 if (temp_path == 0 && path)
298 path = (SHELL_VAR *)NULL;
299
300 /* Don't waste time trying to find hashed data for a pathname
301 that is already completely specified or if we're using a command-
302 specific value for PATH. */
303 if (path == 0 && absolute_program (pathname) == 0)
304 hashed_file = phash_search (pathname);
305
306 /* If a command found in the hash table no longer exists, we need to
307 look for it in $PATH. Thank you Posix.2. This forces us to stat
308 every command found in the hash table. */
309
310 if (hashed_file && (posixly_correct || check_hashed_filenames))
311 {
312 st = file_status (hashed_file);
313 if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0)
314 {
315 phash_remove (pathname);
316 free (hashed_file);
317 hashed_file = (char *)NULL;
318 }
319 }
320
321 if (hashed_file)
322 command = hashed_file;
323 else if (absolute_program (pathname))
324 /* A command containing a slash is not looked up in PATH or saved in
325 the hash table. */
326 command = savestring (pathname);
327 else
328 {
329 /* If $PATH is in the temporary environment, we've already retrieved
330 it, so don't bother trying again. */
331 if (temp_path)
332 {
333 command = find_user_command_in_path (pathname, value_cell (path),
334 FS_EXEC_PREFERRED|FS_NODIRS);
335 }
336 else
337 command = find_user_command (pathname);
338 if (command && hashing_enabled && temp_path == 0)
339 phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */
340 }
341 return (command);
342}
343
344char *
345user_command_matches (name, flags, state)
346 const char *name;
347 int flags, state;
348{
349 register int i;
350 int path_index, name_len;
351 char *path_list, *path_element, *match;
352 struct stat dotinfo;
353 static char **match_list = NULL;
354 static int match_list_size = 0;
355 static int match_index = 0;
356
357 if (state == 0)
358 {
359 /* Create the list of matches. */
360 if (match_list == 0)
361 {
362 match_list_size = 5;
363 match_list = strvec_create (match_list_size);
364 }
365
366 /* Clear out the old match list. */
367 for (i = 0; i < match_list_size; i++)
368 match_list[i] = 0;
369
370 /* We haven't found any files yet. */
371 match_index = 0;
372
373 if (absolute_program (name))
374 {
375 match_list[0] = find_absolute_program (name, flags);
376 match_list[1] = (char *)NULL;
377 path_list = (char *)NULL;
378 }
379 else
380 {
381 name_len = strlen (name);
382 file_to_lose_on = (char *)NULL;
383 dot_found_in_search = 0;
384 stat (".", &dotinfo);
385 path_list = get_string_value ("PATH");
386 path_index = 0;
387 }
388
389 while (path_list && path_list[path_index])
390 {
391 path_element = get_next_path_element (path_list, &path_index);
392
393 if (path_element == 0)
394 break;
395
396 match = find_in_path_element (name, path_element, flags, name_len, &dotinfo);
397
398 free (path_element);
399
400 if (match == 0)
401 continue;
402
403 if (match_index + 1 == match_list_size)
404 {
405 match_list_size += 10;
406 match_list = strvec_resize (match_list, (match_list_size + 1));
407 }
408
409 match_list[match_index++] = match;
410 match_list[match_index] = (char *)NULL;
411 FREE (file_to_lose_on);
412 file_to_lose_on = (char *)NULL;
413 }
414
415 /* We haven't returned any strings yet. */
416 match_index = 0;
417 }
418
419 match = match_list[match_index];
420
421 if (match)
422 match_index++;
423
424 return (match);
425}
426
427static char *
428find_absolute_program (name, flags)
429 const char *name;
430 int flags;
431{
432 int st;
433
434 st = file_status (name);
435
436 /* If the file doesn't exist, quit now. */
437 if ((st & FS_EXISTS) == 0)
438 return ((char *)NULL);
439
440 /* If we only care about whether the file exists or not, return
441 this filename. Otherwise, maybe we care about whether this
442 file is executable. If it is, and that is what we want, return it. */
443 if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE)))
444 return (savestring (name));
445
446 return (NULL);
447}
448
449static char *
450find_in_path_element (name, path, flags, name_len, dotinfop)
451 const char *name;
452 char *path;
453 int flags, name_len;
454 struct stat *dotinfop;
455{
456 int status;
457 char *full_path, *xpath;
458
459 xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path;
460
461 /* Remember the location of "." in the path, in all its forms
462 (as long as they begin with a `.', e.g. `./.') */
463 if (dot_found_in_search == 0 && *xpath == '.')
464 dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL);
465
466 full_path = sh_makepath (xpath, name, 0);
467
468 status = file_status (full_path);
469
470 if (xpath != path)
471 free (xpath);
472
473 if ((status & FS_EXISTS) == 0)
474 {
475 free (full_path);
476 return ((char *)NULL);
477 }
478
479 /* The file exists. If the caller simply wants the first file, here it is. */
480 if (flags & FS_EXISTS)
481 return (full_path);
482
483 /* If we have a readable file, and the caller wants a readable file, this
484 is it. */
485 if ((flags & FS_READABLE) && (status & FS_READABLE))
486 return (full_path);
487
488 /* If the file is executable, then it satisfies the cases of
489 EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */
490 if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) &&
491 (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0)))
492 {
493 FREE (file_to_lose_on);
494 file_to_lose_on = (char *)NULL;
495 return (full_path);
496 }
497
498 /* The file is not executable, but it does exist. If we prefer
499 an executable, then remember this one if it is the first one
500 we have found. */
501 if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0)
502 file_to_lose_on = savestring (full_path);
503
504 /* If we want only executable files, or we don't want directories and
505 this file is a directory, or we want a readable file and this file
506 isn't readable, fail. */
507 if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) ||
508 ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) ||
509 ((flags & FS_READABLE) && (status & FS_READABLE) == 0))
510 {
511 free (full_path);
512 return ((char *)NULL);
513 }
514 else
515 return (full_path);
516}
517
518/* This does the dirty work for find_user_command_internal () and
519 user_command_matches ().
520 NAME is the name of the file to search for.
521 PATH_LIST is a colon separated list of directories to search.
522 FLAGS contains bit fields which control the files which are eligible.
523 Some values are:
524 FS_EXEC_ONLY: The file must be an executable to be found.
525 FS_EXEC_PREFERRED: If we can't find an executable, then the
526 the first file matching NAME will do.
527 FS_EXISTS: The first file found will do.
528 FS_NODIRS: Don't find any directories.
529*/
530static char *
531find_user_command_in_path (name, path_list, flags)
532 const char *name;
533 char *path_list;
534 int flags;
535{
536 char *full_path, *path;
537 int path_index, name_len;
538 struct stat dotinfo;
539
540 /* We haven't started looking, so we certainly haven't seen
541 a `.' as the directory path yet. */
542 dot_found_in_search = 0;
543
544 if (absolute_program (name))
545 {
546 full_path = find_absolute_program (name, flags);
547 return (full_path);
548 }
549
550 if (path_list == 0 || *path_list == '\0')
551 return (savestring (name)); /* XXX */
552
553 file_to_lose_on = (char *)NULL;
554 name_len = strlen (name);
555 stat (".", &dotinfo);
556 path_index = 0;
557
558 while (path_list[path_index])
559 {
560 /* Allow the user to interrupt out of a lengthy path search. */
561 QUIT;
562
563 path = get_next_path_element (path_list, &path_index);
564 if (path == 0)
565 break;
566
567 /* Side effects: sets dot_found_in_search, possibly sets
568 file_to_lose_on. */
569 full_path = find_in_path_element (name, path, flags, name_len, &dotinfo);
570 free (path);
571
572 /* This should really be in find_in_path_element, but there isn't the
573 right combination of flags. */
574 if (full_path && is_directory (full_path))
575 {
576 free (full_path);
577 continue;
578 }
579
580 if (full_path)
581 {
582 FREE (file_to_lose_on);
583 return (full_path);
584 }
585 }
586
587 /* We didn't find exactly what the user was looking for. Return
588 the contents of FILE_TO_LOSE_ON which is NULL when the search
589 required an executable, or non-NULL if a file was found and the
590 search would accept a non-executable as a last resort. If the
591 caller specified FS_NODIRS, and file_to_lose_on is a directory,
592 return NULL. */
593 if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on))
594 {
595 free (file_to_lose_on);
596 file_to_lose_on = (char *)NULL;
597 }
598
599 return (file_to_lose_on);
600}
Note: See TracBrowser for help on using the repository browser.