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

Last change on this file since 3481 was 3286, checked in by bird, 18 years ago

flip DOS slashes in path elements to prevent escape mishaps in scripts.

  • Property svn:eol-style set to native
File size: 16.8 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#ifdef __OS2__
277 else
278 {
279 /* flip DOS slashes to unix slashes */
280 char *p = path;
281 while ((p = strchr (p, '\\')))
282 *p++ = '/';
283 }
284#endif
285
286 return (path);
287}
288
289/* Look for PATHNAME in $PATH. Returns either the hashed command
290 corresponding to PATHNAME or the first instance of PATHNAME found
291 in $PATH. Returns a newly-allocated string. */
292char *
293search_for_command (pathname)
294 const char *pathname;
295{
296 char *hashed_file, *command;
297 int temp_path, st;
298 SHELL_VAR *path;
299
300 hashed_file = command = (char *)NULL;
301
302 /* If PATH is in the temporary environment for this command, don't use the
303 hash table to search for the full pathname. */
304 path = find_variable_internal ("PATH", 1);
305 temp_path = path && tempvar_p (path);
306 if (temp_path == 0 && path)
307 path = (SHELL_VAR *)NULL;
308
309 /* Don't waste time trying to find hashed data for a pathname
310 that is already completely specified or if we're using a command-
311 specific value for PATH. */
312 if (path == 0 && absolute_program (pathname) == 0)
313 hashed_file = phash_search (pathname);
314
315 /* If a command found in the hash table no longer exists, we need to
316 look for it in $PATH. Thank you Posix.2. This forces us to stat
317 every command found in the hash table. */
318
319 if (hashed_file && (posixly_correct || check_hashed_filenames))
320 {
321 st = file_status (hashed_file);
322 if ((st ^ (FS_EXISTS | FS_EXECABLE)) != 0)
323 {
324 phash_remove (pathname);
325 free (hashed_file);
326 hashed_file = (char *)NULL;
327 }
328 }
329
330 if (hashed_file)
331 command = hashed_file;
332 else if (absolute_program (pathname))
333 /* A command containing a slash is not looked up in PATH or saved in
334 the hash table. */
335 command = savestring (pathname);
336 else
337 {
338 /* If $PATH is in the temporary environment, we've already retrieved
339 it, so don't bother trying again. */
340 if (temp_path)
341 {
342 command = find_user_command_in_path (pathname, value_cell (path),
343 FS_EXEC_PREFERRED|FS_NODIRS);
344 }
345 else
346 command = find_user_command (pathname);
347 if (command && hashing_enabled && temp_path == 0)
348 phash_insert ((char *)pathname, command, dot_found_in_search, 1); /* XXX fix const later */
349 }
350 return (command);
351}
352
353char *
354user_command_matches (name, flags, state)
355 const char *name;
356 int flags, state;
357{
358 register int i;
359 int path_index, name_len;
360 char *path_list, *path_element, *match;
361 struct stat dotinfo;
362 static char **match_list = NULL;
363 static int match_list_size = 0;
364 static int match_index = 0;
365
366 if (state == 0)
367 {
368 /* Create the list of matches. */
369 if (match_list == 0)
370 {
371 match_list_size = 5;
372 match_list = strvec_create (match_list_size);
373 }
374
375 /* Clear out the old match list. */
376 for (i = 0; i < match_list_size; i++)
377 match_list[i] = 0;
378
379 /* We haven't found any files yet. */
380 match_index = 0;
381
382 if (absolute_program (name))
383 {
384 match_list[0] = find_absolute_program (name, flags);
385 match_list[1] = (char *)NULL;
386 path_list = (char *)NULL;
387 }
388 else
389 {
390 name_len = strlen (name);
391 file_to_lose_on = (char *)NULL;
392 dot_found_in_search = 0;
393 stat (".", &dotinfo);
394 path_list = get_string_value ("PATH");
395 path_index = 0;
396 }
397
398 while (path_list && path_list[path_index])
399 {
400 path_element = get_next_path_element (path_list, &path_index);
401
402 if (path_element == 0)
403 break;
404
405 match = find_in_path_element (name, path_element, flags, name_len, &dotinfo);
406
407 free (path_element);
408
409 if (match == 0)
410 continue;
411
412 if (match_index + 1 == match_list_size)
413 {
414 match_list_size += 10;
415 match_list = strvec_resize (match_list, (match_list_size + 1));
416 }
417
418 match_list[match_index++] = match;
419 match_list[match_index] = (char *)NULL;
420 FREE (file_to_lose_on);
421 file_to_lose_on = (char *)NULL;
422 }
423
424 /* We haven't returned any strings yet. */
425 match_index = 0;
426 }
427
428 match = match_list[match_index];
429
430 if (match)
431 match_index++;
432
433 return (match);
434}
435
436static char *
437find_absolute_program (name, flags)
438 const char *name;
439 int flags;
440{
441 int st;
442
443 st = file_status (name);
444
445 /* If the file doesn't exist, quit now. */
446 if ((st & FS_EXISTS) == 0)
447 return ((char *)NULL);
448
449 /* If we only care about whether the file exists or not, return
450 this filename. Otherwise, maybe we care about whether this
451 file is executable. If it is, and that is what we want, return it. */
452 if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE)))
453 return (savestring (name));
454
455 return (NULL);
456}
457
458static char *
459find_in_path_element (name, path, flags, name_len, dotinfop)
460 const char *name;
461 char *path;
462 int flags, name_len;
463 struct stat *dotinfop;
464{
465 int status;
466 char *full_path, *xpath;
467
468 xpath = (*path == '~') ? bash_tilde_expand (path, 0) : path;
469
470 /* Remember the location of "." in the path, in all its forms
471 (as long as they begin with a `.', e.g. `./.') */
472 if (dot_found_in_search == 0 && *xpath == '.')
473 dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL);
474
475 full_path = sh_makepath (xpath, name, 0);
476
477 status = file_status (full_path);
478
479 if (xpath != path)
480 free (xpath);
481
482 if ((status & FS_EXISTS) == 0)
483 {
484 free (full_path);
485 return ((char *)NULL);
486 }
487
488 /* The file exists. If the caller simply wants the first file, here it is. */
489 if (flags & FS_EXISTS)
490 return (full_path);
491
492 /* If we have a readable file, and the caller wants a readable file, this
493 is it. */
494 if ((flags & FS_READABLE) && (status & FS_READABLE))
495 return (full_path);
496
497 /* If the file is executable, then it satisfies the cases of
498 EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */
499 if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) &&
500 (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0)))
501 {
502 FREE (file_to_lose_on);
503 file_to_lose_on = (char *)NULL;
504 return (full_path);
505 }
506
507 /* The file is not executable, but it does exist. If we prefer
508 an executable, then remember this one if it is the first one
509 we have found. */
510 if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0)
511 file_to_lose_on = savestring (full_path);
512
513 /* If we want only executable files, or we don't want directories and
514 this file is a directory, or we want a readable file and this file
515 isn't readable, fail. */
516 if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) ||
517 ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) ||
518 ((flags & FS_READABLE) && (status & FS_READABLE) == 0))
519 {
520 free (full_path);
521 return ((char *)NULL);
522 }
523 else
524 return (full_path);
525}
526
527/* This does the dirty work for find_user_command_internal () and
528 user_command_matches ().
529 NAME is the name of the file to search for.
530 PATH_LIST is a colon separated list of directories to search.
531 FLAGS contains bit fields which control the files which are eligible.
532 Some values are:
533 FS_EXEC_ONLY: The file must be an executable to be found.
534 FS_EXEC_PREFERRED: If we can't find an executable, then the
535 the first file matching NAME will do.
536 FS_EXISTS: The first file found will do.
537 FS_NODIRS: Don't find any directories.
538*/
539static char *
540find_user_command_in_path (name, path_list, flags)
541 const char *name;
542 char *path_list;
543 int flags;
544{
545 char *full_path, *path;
546 int path_index, name_len;
547 struct stat dotinfo;
548
549 /* We haven't started looking, so we certainly haven't seen
550 a `.' as the directory path yet. */
551 dot_found_in_search = 0;
552
553 if (absolute_program (name))
554 {
555 full_path = find_absolute_program (name, flags);
556 return (full_path);
557 }
558
559 if (path_list == 0 || *path_list == '\0')
560 return (savestring (name)); /* XXX */
561
562 file_to_lose_on = (char *)NULL;
563 name_len = strlen (name);
564 stat (".", &dotinfo);
565 path_index = 0;
566
567 while (path_list[path_index])
568 {
569 /* Allow the user to interrupt out of a lengthy path search. */
570 QUIT;
571
572 path = get_next_path_element (path_list, &path_index);
573 if (path == 0)
574 break;
575
576 /* Side effects: sets dot_found_in_search, possibly sets
577 file_to_lose_on. */
578 full_path = find_in_path_element (name, path, flags, name_len, &dotinfo);
579 free (path);
580
581 /* This should really be in find_in_path_element, but there isn't the
582 right combination of flags. */
583 if (full_path && is_directory (full_path))
584 {
585 free (full_path);
586 continue;
587 }
588
589 if (full_path)
590 {
591 FREE (file_to_lose_on);
592 return (full_path);
593 }
594 }
595
596 /* We didn't find exactly what the user was looking for. Return
597 the contents of FILE_TO_LOSE_ON which is NULL when the search
598 required an executable, or non-NULL if a file was found and the
599 search would accept a non-executable as a last resort. If the
600 caller specified FS_NODIRS, and file_to_lose_on is a directory,
601 return NULL. */
602 if (file_to_lose_on && (flags & FS_NODIRS) && is_directory (file_to_lose_on))
603 {
604 free (file_to_lose_on);
605 file_to_lose_on = (char *)NULL;
606 }
607
608 return (file_to_lose_on);
609}
Note: See TracBrowser for help on using the repository browser.