]> git.ipfire.org Git - thirdparty/bash.git/blame - findcmd.c
allow some job notifications while running $PROMPT_COMMAND; allow notifications while...
[thirdparty/bash.git] / findcmd.c
CommitLineData
cce855bc
JA
1/* findcmd.c -- Functions to search for commands by name. */
2
b2613ad1 3/* Copyright (C) 1997-2023 Free Software Foundation, Inc.
cce855bc
JA
4
5 This file is part of GNU Bash, the Bourne Again SHell.
6
2e4498b3
CR
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.
cce855bc 11
2e4498b3
CR
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.
cce855bc
JA
16
17 You should have received a copy of the GNU General Public License
2e4498b3
CR
18 along with Bash. If not, see <http://www.gnu.org/licenses/>.
19*/
cce855bc
JA
20
21#include "config.h"
22
23#include <stdio.h>
f73dda09 24#include "chartypes.h"
cce855bc 25#include "bashtypes.h"
d3a24ed2 26#if !defined (_MINIX) && defined (HAVE_SYS_FILE_H)
cce855bc
JA
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
aad3dfa2 35#include <errno.h>
cce855bc 36
cce855bc
JA
37#include "bashansi.h"
38
39#include "memalloc.h"
40#include "shell.h"
ec157dfe 41#include "execute_cmd.h"
cce855bc
JA
42#include "flags.h"
43#include "hashlib.h"
44#include "pathexp.h"
45#include "hashcmd.h"
f73dda09 46#include "findcmd.h" /* matching prototypes and declarations */
cce855bc 47
ff7c092e
CR
48#include <glob/strmatch.h>
49
aad3dfa2
CR
50#if !defined (errno)
51extern int errno;
52#endif
53
cce855bc 54/* Static functions defined and used in this file. */
a61ffa78
CR
55static char *_find_user_command_internal (const char *, int);
56static char *find_user_command_internal (const char *, int);
57static char *find_user_command_in_path (const char *, char *, int, int *);
2e725f73 58static char *find_in_path_element (const char *, char *, int, size_t, struct stat *, int *);
a61ffa78 59static char *find_absolute_program (const char *, int);
f73dda09 60
a61ffa78 61static char *get_next_path_element (char *, int *);
cce855bc
JA
62
63/* The file name which we would try to execute, except that it isn't
64 possible to execute it. This is the first file that matches the
65 name that we are looking for while we are searching $PATH for a
66 suitable one to execute. If we cannot find a suitable executable
67 file, then we use this one. */
68static char *file_to_lose_on;
69
70/* Non-zero if we should stat every command found in the hash table to
71 make sure it still exists. */
25a0eacf 72int check_hashed_filenames = CHECKHASH_DEFAULT;
cce855bc
JA
73
74/* DOT_FOUND_IN_SEARCH becomes non-zero when find_user_command ()
75 encounters a `.' as the directory pathname while scanning the
76 list of possible pathnames; i.e., if `.' comes before the directory
77 containing the file of interest. */
78int dot_found_in_search = 0;
79
ff7c092e
CR
80/* Set up EXECIGNORE; a blacklist of patterns that executable files should not
81 match. */
82static struct ignorevar execignore =
83{
84 "EXECIGNORE",
85 NULL,
86 0,
87 NULL,
88 NULL
89};
90
91void
b2613ad1 92setup_exec_ignore (const char *varname)
ff7c092e
CR
93{
94 setup_ignore_patterns (&execignore);
95}
96
97static int
a61ffa78 98exec_name_should_ignore (const char *name)
ff7c092e
CR
99{
100 struct ign *p;
101
102 for (p = execignore.ignores; p && p->val; p++)
e6f5e0c8 103 if (strmatch (p->val, (char *)name, FNMATCH_EXTFLAG|FNM_CASEFOLD) != FNM_NOMATCH)
ff7c092e
CR
104 return 1;
105 return 0;
106}
107
cce855bc
JA
108/* Return some flags based on information about this file.
109 The EXISTS bit is non-zero if the file is found.
e9e3e4fe 110 The EXECABLE bit is non-zero the file is executable.
cce855bc
JA
111 Zero is returned if the file is not found. */
112int
a61ffa78 113file_status (const char *name)
cce855bc
JA
114{
115 struct stat finfo;
cdb32d45 116 int r;
cce855bc
JA
117
118 /* Determine whether this file exists or not. */
119 if (stat (name, &finfo) < 0)
a629483b
CR
120#if defined (__MVS__) && defined (S_ISEXTL)
121 {
122 /* Workaround for z/OS not supporting stat on external links */
123 int old_errno = errno;
124 if (!(errno == ENOENT && lstat(name, &finfo) == 0 && S_ISEXTL(finfo.st_mode,finfo.st_genvalue)))
125 {
126 errno = old_errno; /* lstat may reset errno */
127 return (0);
128 }
129 }
130#else
cce855bc 131 return (0);
a629483b 132#endif
cce855bc
JA
133
134 /* If the file is a directory, then it is not "executable" in the
135 sense of the shell. */
136 if (S_ISDIR (finfo.st_mode))
137 return (FS_EXISTS|FS_DIRECTORY);
138
cdb32d45
CR
139 r = FS_EXISTS;
140
4301bca7
CR
141#if defined (HAVE_EACCESS)
142 /* Use eaccess(2) if we have it to take things like ACLs and other
143 file access mechanisms into account. eaccess uses the effective
144 user and group IDs, not the real ones. We could use sh_eaccess,
145 but we don't want any special treatment for /dev/fd. */
ff7c092e 146 if (exec_name_should_ignore (name) == 0 && eaccess (name, X_OK) == 0)
4301bca7
CR
147 r |= FS_EXECABLE;
148 if (eaccess (name, R_OK) == 0)
149 r |= FS_READABLE;
150
151 return r;
152#elif defined (AFS)
cce855bc
JA
153 /* We have to use access(2) to determine access because AFS does not
154 support Unix file system semantics. This may produce wrong
155 answers for non-AFS files when ruid != euid. I hate AFS. */
ff7c092e 156 if (exec_name_should_ignore (name) == 0 && access (name, X_OK) == 0)
cdb32d45
CR
157 r |= FS_EXECABLE;
158 if (access (name, R_OK) == 0)
159 r |= FS_READABLE;
160
161 return r;
4301bca7 162#else /* !HAVE_EACCESS && !AFS */
cce855bc
JA
163
164 /* Find out if the file is actually executable. By definition, the
165 only other criteria is that the file has an execute bit set that
cdb32d45 166 we can use. The same with whether or not a file is readable. */
cce855bc
JA
167
168 /* Root only requires execute permission for any of owner, group or
cdb32d45 169 others to be able to exec a file, and can read any file. */
cce855bc
JA
170 if (current_user.euid == (uid_t)0)
171 {
cdb32d45 172 r |= FS_READABLE;
ff7c092e 173 if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUGO))
cdb32d45
CR
174 r |= FS_EXECABLE;
175 return r;
cce855bc
JA
176 }
177
cdb32d45 178 /* If we are the owner of the file, the owner bits apply. */
d3a24ed2 179 if (current_user.euid == finfo.st_uid)
cdb32d45 180 {
ff7c092e 181 if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXUSR))
cdb32d45
CR
182 r |= FS_EXECABLE;
183 if (finfo.st_mode & S_IRUSR)
184 r |= FS_READABLE;
185 }
cce855bc
JA
186
187 /* If we are in the owning group, the group permissions apply. */
d3a24ed2 188 else if (group_member (finfo.st_gid))
cdb32d45 189 {
ff7c092e 190 if (exec_name_should_ignore (name) == 0 && (finfo.st_mode & S_IXGRP))
cdb32d45
CR
191 r |= FS_EXECABLE;
192 if (finfo.st_mode & S_IRGRP)
193 r |= FS_READABLE;
194 }
cce855bc 195
d3a24ed2
CR
196 /* Else we check whether `others' have permission to execute the file */
197 else
cdb32d45 198 {
4dd820b4 199 if (exec_name_should_ignore (name) == 0 && finfo.st_mode & S_IXOTH)
cdb32d45
CR
200 r |= FS_EXECABLE;
201 if (finfo.st_mode & S_IROTH)
202 r |= FS_READABLE;
203 }
204
205 return r;
cce855bc
JA
206#endif /* !AFS */
207}
208
209/* Return non-zero if FILE exists and is executable.
210 Note that this function is the definition of what an
211 executable file is; do not change this unless YOU know
212 what an executable file is. */
213int
a61ffa78 214executable_file (const char *file)
cce855bc
JA
215{
216 int s;
217
218 s = file_status (file);
85ec0778 219#if defined (EISDIR)
aad3dfa2
CR
220 if (s & FS_DIRECTORY)
221 errno = EISDIR; /* let's see if we can improve error messages */
222#endif
cce855bc
JA
223 return ((s & FS_EXECABLE) && ((s & FS_DIRECTORY) == 0));
224}
225
226int
a61ffa78 227is_directory (const char *file)
cce855bc
JA
228{
229 return (file_status (file) & FS_DIRECTORY);
230}
231
28ef6c31 232int
a61ffa78 233executable_or_directory (const char *file)
28ef6c31
JA
234{
235 int s;
236
237 s = file_status (file);
238 return ((s & FS_EXECABLE) || (s & FS_DIRECTORY));
239}
240
cce855bc
JA
241/* Locate the executable file referenced by NAME, searching along
242 the contents of the shell PATH variable. Return a new string
243 which is the full pathname to the file, or NULL if the file
244 couldn't be found. If a file is found that isn't executable,
245 and that is the only match, then return that. */
246char *
a61ffa78 247find_user_command (const char *name)
cce855bc
JA
248{
249 return (find_user_command_internal (name, FS_EXEC_PREFERRED|FS_NODIRS));
250}
251
252/* Locate the file referenced by NAME, searching along the contents
253 of the shell PATH variable. Return a new string which is the full
254 pathname to the file, or NULL if the file couldn't be found. This
cdb32d45
CR
255 returns the first readable file found; designed to be used to look
256 for shell scripts or files to source. */
cce855bc 257char *
a61ffa78 258find_path_file (const char *name)
cce855bc 259{
cdb32d45 260 return (find_user_command_internal (name, FS_READABLE));
cce855bc
JA
261}
262
deedd41c
CR
263/* Get $PATH and normalize it. USE_TEMPENV, if non-zero, says to look in the
264 temporary environment first. Normalizing means converting PATH= into ".". */
265char *
266path_value (const char *pathvar, int use_tempenv)
267{
268 SHELL_VAR *var;
269 char *path;
270
271 var = use_tempenv ? find_variable_tempenv (pathvar) : find_variable (pathvar);
272 path = var ? value_cell (var) : (char *)NULL;
273
274 if (path == 0 || *path)
275 return (path);
276 else /* *path == '\0' */
277 return ".";
278}
279
cce855bc 280static char *
a61ffa78 281_find_user_command_internal (const char *name, int flags)
cce855bc
JA
282{
283 char *path_list, *cmd;
284 SHELL_VAR *var;
285
7117c2d2 286 /* Search for the value of PATH in both the temporary environments and
cce855bc 287 in the regular list of variables. */
deedd41c 288 path_list = path_value ("PATH", 1);
cce855bc 289
deedd41c 290 if (path_list == 0)
cce855bc
JA
291 return (savestring (name));
292
6c4a9a5c 293 cmd = find_user_command_in_path (name, path_list, flags, (int *)0);
cce855bc 294
cce855bc
JA
295 return (cmd);
296}
297
298static char *
a61ffa78 299find_user_command_internal (const char *name, int flags)
cce855bc
JA
300{
301#ifdef __WIN32__
302 char *res, *dotexe;
303
f73dda09 304 dotexe = (char *)xmalloc (strlen (name) + 5);
cce855bc
JA
305 strcpy (dotexe, name);
306 strcat (dotexe, ".exe");
307 res = _find_user_command_internal (dotexe, flags);
308 free (dotexe);
309 if (res == 0)
310 res = _find_user_command_internal (name, flags);
311 return res;
312#else
313 return (_find_user_command_internal (name, flags));
314#endif
315}
316
317/* Return the next element from PATH_LIST, a colon separated list of
318 paths. PATH_INDEX_POINTER is the address of an index into PATH_LIST;
319 the index is modified by this function.
320 Return the next element of PATH_LIST or NULL if there are no more. */
321static char *
a61ffa78 322get_next_path_element (char *path_list, int *path_index_pointer)
cce855bc
JA
323{
324 char *path;
325
326 path = extract_colon_unit (path_list, path_index_pointer);
327
bb70624e 328 if (path == 0)
cce855bc
JA
329 return (path);
330
bb70624e 331 if (*path == '\0')
cce855bc
JA
332 {
333 free (path);
334 path = savestring (".");
335 }
336
337 return (path);
338}
339
340/* Look for PATHNAME in $PATH. Returns either the hashed command
341 corresponding to PATHNAME or the first instance of PATHNAME found
1573ba78 342 in $PATH. If (FLAGS&CMDSRCH_HASH) is non-zero, insert the instance of
b06200f7
CR
343 PATHNAME found in $PATH into the command hash table.
344 If (FLAGS&CMDSRCH_STDPATH) is non-zero, we are running in a `command -p'
345 environment and should use the Posix standard path.
1573ba78 346 Returns a newly-allocated string. */
cce855bc 347char *
a61ffa78 348search_for_command (const char *pathname, int flags)
cce855bc 349{
124d67cd 350 char *hashed_file, *command, *path_list;
cce855bc
JA
351 int temp_path, st;
352 SHELL_VAR *path;
353
354 hashed_file = command = (char *)NULL;
355
356 /* If PATH is in the temporary environment for this command, don't use the
357 hash table to search for the full pathname. */
348a457e 358 path = find_variable_tempenv ("PATH");
7117c2d2 359 temp_path = path && tempvar_p (path);
cce855bc
JA
360
361 /* Don't waste time trying to find hashed data for a pathname
362 that is already completely specified or if we're using a command-
363 specific value for PATH. */
b06200f7 364 if (temp_path == 0 && (flags & CMDSRCH_STDPATH) == 0 && absolute_program (pathname) == 0)
7117c2d2 365 hashed_file = phash_search (pathname);
cce855bc
JA
366
367 /* If a command found in the hash table no longer exists, we need to
368 look for it in $PATH. Thank you Posix.2. This forces us to stat
369 every command found in the hash table. */
370
371 if (hashed_file && (posixly_correct || check_hashed_filenames))
372 {
373 st = file_status (hashed_file);
d3ad40de 374 if ((st & (FS_EXISTS|FS_EXECABLE)) != (FS_EXISTS|FS_EXECABLE))
cce855bc 375 {
7117c2d2 376 phash_remove (pathname);
cce855bc
JA
377 free (hashed_file);
378 hashed_file = (char *)NULL;
379 }
380 }
381
382 if (hashed_file)
383 command = hashed_file;
384 else if (absolute_program (pathname))
385 /* A command containing a slash is not looked up in PATH or saved in
386 the hash table. */
387 command = savestring (pathname);
388 else
389 {
1573ba78 390 if (flags & CMDSRCH_STDPATH)
124d67cd 391 path_list = conf_standard_path ();
1573ba78 392 else if (temp_path || path)
deedd41c
CR
393 {
394 path_list = value_cell (path);
395 if (path_list && *path_list == '\0')
396 path_list = ".";
397 }
1573ba78 398 else
124d67cd 399 path_list = 0;
1573ba78 400
6c4a9a5c 401 command = find_user_command_in_path (pathname, path_list, FS_EXEC_PREFERRED|FS_NODIRS, &st);
1573ba78
CR
402
403 if (command && hashing_enabled && temp_path == 0 && (flags & CMDSRCH_HASH))
28ef6c31 404 {
1573ba78
CR
405 /* If we found the full pathname the same as the command name, the
406 command probably doesn't exist. Don't put it into the hash
b06200f7 407 table unless it's an executable file in the current directory. */
1573ba78
CR
408 if (STREQ (command, pathname))
409 {
deedd41c
CR
410 if (path_list == 0)
411 {
412 dot_found_in_search = 1;
413 st = file_status (pathname);
414 }
1573ba78
CR
415 if (st & FS_EXECABLE)
416 phash_insert ((char *)pathname, command, dot_found_in_search, 1);
417 }
03e4274f
CR
418 /* If we're in posix mode, don't add files without the execute bit
419 to the hash table. */
a9cf0031 420 else if (posixly_correct || check_hashed_filenames)
03e4274f 421 {
03e4274f
CR
422 if (st & FS_EXECABLE)
423 phash_insert ((char *)pathname, command, dot_found_in_search, 1);
424 }
1573ba78
CR
425 else
426 phash_insert ((char *)pathname, command, dot_found_in_search, 1);
28ef6c31 427 }
1573ba78
CR
428
429 if (flags & CMDSRCH_STDPATH)
124d67cd 430 free (path_list);
cce855bc 431 }
1573ba78 432
cce855bc
JA
433 return (command);
434}
435
436char *
a61ffa78 437user_command_matches (const char *name, int flags, int state)
cce855bc
JA
438{
439 register int i;
2e725f73
CR
440 int path_index;
441 size_t name_len;
cce855bc
JA
442 char *path_list, *path_element, *match;
443 struct stat dotinfo;
444 static char **match_list = NULL;
2e725f73
CR
445 static size_t match_list_size = 0;
446 static size_t match_index = 0;
cce855bc
JA
447
448 if (state == 0)
449 {
450 /* Create the list of matches. */
451 if (match_list == 0)
452 {
453 match_list_size = 5;
7117c2d2 454 match_list = strvec_create (match_list_size);
cce855bc
JA
455 }
456
457 /* Clear out the old match list. */
458 for (i = 0; i < match_list_size; i++)
459 match_list[i] = 0;
460
461 /* We haven't found any files yet. */
462 match_index = 0;
463
464 if (absolute_program (name))
465 {
466 match_list[0] = find_absolute_program (name, flags);
467 match_list[1] = (char *)NULL;
468 path_list = (char *)NULL;
469 }
470 else
471 {
472 name_len = strlen (name);
473 file_to_lose_on = (char *)NULL;
474 dot_found_in_search = 0;
631b20c6
CR
475 if (stat (".", &dotinfo) < 0)
476 dotinfo.st_dev = dotinfo.st_ino = 0; /* so same_file won't match */
deedd41c
CR
477 path_list = path_value ("PATH", 0);
478 path_index = 0;
cce855bc
JA
479 }
480
481 while (path_list && path_list[path_index])
482 {
483 path_element = get_next_path_element (path_list, &path_index);
484
485 if (path_element == 0)
486 break;
487
6c4a9a5c 488 match = find_in_path_element (name, path_element, flags, name_len, &dotinfo, (int *)0);
cce855bc
JA
489 free (path_element);
490
491 if (match == 0)
492 continue;
493
494 if (match_index + 1 == match_list_size)
495 {
496 match_list_size += 10;
7117c2d2 497 match_list = strvec_resize (match_list, (match_list_size + 1));
cce855bc
JA
498 }
499
500 match_list[match_index++] = match;
501 match_list[match_index] = (char *)NULL;
502 FREE (file_to_lose_on);
503 file_to_lose_on = (char *)NULL;
504 }
505
506 /* We haven't returned any strings yet. */
507 match_index = 0;
508 }
509
510 match = match_list[match_index];
511
512 if (match)
513 match_index++;
514
515 return (match);
516}
517
cce855bc 518static char *
a61ffa78 519find_absolute_program (const char *name, int flags)
cce855bc
JA
520{
521 int st;
522
523 st = file_status (name);
524
525 /* If the file doesn't exist, quit now. */
526 if ((st & FS_EXISTS) == 0)
527 return ((char *)NULL);
528
529 /* If we only care about whether the file exists or not, return
530 this filename. Otherwise, maybe we care about whether this
531 file is executable. If it is, and that is what we want, return it. */
532 if ((flags & FS_EXISTS) || ((flags & FS_EXEC_ONLY) && (st & FS_EXECABLE)))
533 return (savestring (name));
534
f73dda09 535 return (NULL);
cce855bc
JA
536}
537
538static char *
2e725f73 539find_in_path_element (const char *name, char *path, int flags, size_t name_len, struct stat *dotinfop, int *rflagsp)
cce855bc
JA
540{
541 int status;
542 char *full_path, *xpath;
543
e198129d 544 xpath = (posixly_correct == 0 && *path == '~') ? bash_tilde_expand (path, 0) : path;
cce855bc
JA
545
546 /* Remember the location of "." in the path, in all its forms
547 (as long as they begin with a `.', e.g. `./.') */
b06200f7
CR
548 /* We could also do this or something similar for all relative pathnames
549 found while searching PATH. */
cce855bc
JA
550 if (dot_found_in_search == 0 && *xpath == '.')
551 dot_found_in_search = same_file (".", xpath, dotinfop, (struct stat *)NULL);
552
bb70624e 553 full_path = sh_makepath (xpath, name, 0);
cce855bc
JA
554
555 status = file_status (full_path);
556
557 if (xpath != path)
558 free (xpath);
559
6c4a9a5c
CR
560 if (rflagsp)
561 *rflagsp = status;
562
cce855bc
JA
563 if ((status & FS_EXISTS) == 0)
564 {
565 free (full_path);
566 return ((char *)NULL);
567 }
568
569 /* The file exists. If the caller simply wants the first file, here it is. */
570 if (flags & FS_EXISTS)
571 return (full_path);
572
cdb32d45
CR
573 /* If we have a readable file, and the caller wants a readable file, this
574 is it. */
575 if ((flags & FS_READABLE) && (status & FS_READABLE))
576 return (full_path);
577
cce855bc
JA
578 /* If the file is executable, then it satisfies the cases of
579 EXEC_ONLY and EXEC_PREFERRED. Return this file unconditionally. */
cdb32d45 580 if ((status & FS_EXECABLE) && (flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) &&
cce855bc
JA
581 (((flags & FS_NODIRS) == 0) || ((status & FS_DIRECTORY) == 0)))
582 {
583 FREE (file_to_lose_on);
584 file_to_lose_on = (char *)NULL;
585 return (full_path);
586 }
587
588 /* The file is not executable, but it does exist. If we prefer
589 an executable, then remember this one if it is the first one
590 we have found. */
4dd820b4 591 if ((flags & FS_EXEC_PREFERRED) && file_to_lose_on == 0 && exec_name_should_ignore (full_path) == 0)
cce855bc
JA
592 file_to_lose_on = savestring (full_path);
593
594 /* If we want only executable files, or we don't want directories and
cdb32d45
CR
595 this file is a directory, or we want a readable file and this file
596 isn't readable, fail. */
597 if ((flags & (FS_EXEC_ONLY|FS_EXEC_PREFERRED)) ||
598 ((flags & FS_NODIRS) && (status & FS_DIRECTORY)) ||
599 ((flags & FS_READABLE) && (status & FS_READABLE) == 0))
cce855bc
JA
600 {
601 free (full_path);
602 return ((char *)NULL);
603 }
604 else
605 return (full_path);
606}
607
608/* This does the dirty work for find_user_command_internal () and
609 user_command_matches ().
610 NAME is the name of the file to search for.
deedd41c
CR
611 PATH_LIST is a colon separated list of directories to search. It is the
612 caller's responsibility to pass a non-empty path if they want an empty
613 path to be treated specially.
cce855bc
JA
614 FLAGS contains bit fields which control the files which are eligible.
615 Some values are:
616 FS_EXEC_ONLY: The file must be an executable to be found.
617 FS_EXEC_PREFERRED: If we can't find an executable, then the
618 the first file matching NAME will do.
619 FS_EXISTS: The first file found will do.
620 FS_NODIRS: Don't find any directories.
621*/
622static char *
a61ffa78 623find_user_command_in_path (const char *name, char *path_list, int flags, int *rflagsp)
cce855bc
JA
624{
625 char *full_path, *path;
2e725f73
CR
626 int path_index, rflags;
627 size_t name_len;
cce855bc
JA
628 struct stat dotinfo;
629
630 /* We haven't started looking, so we certainly haven't seen
631 a `.' as the directory path yet. */
632 dot_found_in_search = 0;
633
6c4a9a5c
CR
634 if (rflagsp)
635 *rflagsp = 0;
636
cce855bc
JA
637 if (absolute_program (name))
638 {
639 full_path = find_absolute_program (name, flags);
640 return (full_path);
641 }
642
643 if (path_list == 0 || *path_list == '\0')
644 return (savestring (name)); /* XXX */
645
646 file_to_lose_on = (char *)NULL;
647 name_len = strlen (name);
631b20c6
CR
648 if (stat (".", &dotinfo) < 0)
649 dotinfo.st_dev = dotinfo.st_ino = 0;
cce855bc
JA
650 path_index = 0;
651
652 while (path_list[path_index])
653 {
654 /* Allow the user to interrupt out of a lengthy path search. */
655 QUIT;
656
657 path = get_next_path_element (path_list, &path_index);
658 if (path == 0)
659 break;
660
661 /* Side effects: sets dot_found_in_search, possibly sets
662 file_to_lose_on. */
6c4a9a5c 663 full_path = find_in_path_element (name, path, flags, name_len, &dotinfo, &rflags);
cce855bc
JA
664 free (path);
665
6c4a9a5c
CR
666 /* We use the file status flag bits to check whether full_path is a
667 directory, which we reject here. */
668 if (full_path && (rflags & FS_DIRECTORY))
cce855bc
JA
669 {
670 free (full_path);
671 continue;
672 }
673
674 if (full_path)
675 {
6c4a9a5c
CR
676 if (rflagsp)
677 *rflagsp = rflags;
cce855bc
JA
678 FREE (file_to_lose_on);
679 return (full_path);
680 }
681 }
682
683 /* We didn't find exactly what the user was looking for. Return
684 the contents of FILE_TO_LOSE_ON which is NULL when the search
685 required an executable, or non-NULL if a file was found and the
28ef6c31
JA
686 search would accept a non-executable as a last resort. If the
687 caller specified FS_NODIRS, and file_to_lose_on is a directory,
688 return NULL. */
6c4a9a5c 689 if (file_to_lose_on && (flags & FS_NODIRS) && file_isdir (file_to_lose_on))
28ef6c31
JA
690 {
691 free (file_to_lose_on);
692 file_to_lose_on = (char *)NULL;
693 }
694
cce855bc
JA
695 return (file_to_lose_on);
696}
1573ba78
CR
697
698/* External interface to find a command given a $PATH. Separate from
699 find_user_command_in_path to allow future customization. */
700char *
a61ffa78 701find_in_path (const char *name, char *path_list, int flags)
1573ba78 702{
6c4a9a5c 703 return (find_user_command_in_path (name, path_list, flags, (int *)0));
1573ba78 704}