1 /*
2 * which v2.x -- print full path of executables
3 * Copyright (C) 1999, 2003, 2007, 2008 Carlo Wood <carlo@gnu.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "sys.h"
20 #include <stdio.h>
21 #include <ctype.h>
22 #include "getopt.h"
23 #include "tilde/tilde.h"
24 #include "bash.h"
25
26 static const char *progname;
27
print_usage(FILE * out)28 static void print_usage(FILE *out)
29 {
30 fprintf(out, "Usage: %s [options] [--] COMMAND [...]\n", progname);
31 fprintf(out, "Write the full path of COMMAND(s) to standard output.\n\n");
32 fprintf(out, " --version, -[vV] Print version and exit successfully.\n");
33 fprintf(out, " --help, Print this help and exit successfully.\n");
34 fprintf(out, " --skip-dot Skip directories in PATH that start with a dot.\n");
35 fprintf(out, " --skip-tilde Skip directories in PATH that start with a tilde.\n");
36 fprintf(out, " --show-dot Don't expand a dot to current directory in output.\n");
37 fprintf(out, " --show-tilde Output a tilde for HOME directory for non-root.\n");
38 fprintf(out, " --tty-only Stop processing options on the right if not on tty.\n");
39 fprintf(out, " --all, -a Print all matches in PATH, not just the first\n");
40 fprintf(out, " --read-alias, -i Read list of aliases from stdin.\n");
41 fprintf(out, " --skip-alias Ignore option --read-alias; don't read stdin.\n");
42 fprintf(out, " --read-functions Read shell functions from stdin.\n");
43 fprintf(out, " --skip-functions Ignore option --read-functions; don't read stdin.\n\n");
44 fprintf(out, "Recommended use is to write the output of (alias; declare -f) to standard\n");
45 fprintf(out, "input, so that which can show aliases and shell functions. See which(1) for\n");
46 fprintf(out, "examples.\n\n");
47 fprintf(out, "If the options --read-alias and/or --read-functions are specified then the\n");
48 fprintf(out, "output can be a full alias or function definition, optionally followed by\n");
49 fprintf(out, "the full path of each command used inside of those.\n\n");
50 fprintf(out, "Report bugs to <which-bugs@gnu.org>.\n");
51 }
52
print_version(void)53 static void print_version(void)
54 {
55 fprintf(stdout, "GNU which v" VERSION ", Copyright (C) 1999 - 2015 Carlo Wood.\n");
56 fprintf(stdout, "GNU which comes with ABSOLUTELY NO WARRANTY;\n");
57 fprintf(stdout, "This program is free software; your freedom to use, change\n");
58 fprintf(stdout, "and distribute this program is protected by the GPL.\n");
59 }
60
print_fail(const char * name,const char * path_list)61 static void print_fail(const char *name, const char *path_list)
62 {
63 fprintf(stderr, "%s: no %s in (%s)\n", progname, name, path_list);
64 }
65
66 static char home[256];
67 static size_t homelen = 0;
68
69 static int absolute_path_given;
70 static int found_path_starts_with_dot;
71 static char *abs_path;
72
73 static int skip_dot = 0, skip_tilde = 0, skip_alias = 0, read_alias = 0;
74 static int show_dot = 0, show_tilde = 0, show_all = 0, tty_only = 0;
75 static int skip_functions = 0, read_functions = 0;
76
find_command_in_path(const char * name,const char * path_list,int * path_index)77 static char *find_command_in_path(const char *name, const char *path_list, int *path_index)
78 {
79 char *found = NULL, *full_path;
80 int status, name_len;
81
82 name_len = strlen(name);
83
84 if (!absolute_program(name))
85 absolute_path_given = 0;
86 else
87 {
88 char *p;
89 absolute_path_given = 1;
90
91 if (abs_path)
92 free(abs_path);
93
94 if (*name != '.' && *name != '/' && *name != '~')
95 {
96 abs_path = (char *)xmalloc(3 + name_len);
97 strcpy(abs_path, "./");
98 strcat(abs_path, name);
99 }
100 else
101 {
102 abs_path = (char *)xmalloc(1 + name_len);
103 strcpy(abs_path, name);
104 }
105
106 path_list = abs_path;
107 p = strrchr(abs_path, '/');
108 *p++ = 0;
109 name = p;
110 }
111
112 while (path_list && path_list[*path_index])
113 {
114 char *path;
115
116 if (absolute_path_given)
117 {
118 path = savestring(path_list);
119 *path_index = strlen(path);
120 }
121 else
122 path = get_next_path_element(path_list, path_index);
123
124 if (!path)
125 break;
126
127 if (*path == '~')
128 {
129 char *t = tilde_expand(path);
130 free(path);
131 path = t;
132
133 if (skip_tilde)
134 {
135 free(path);
136 continue;
137 }
138 }
139
140 if (skip_dot && *path != '/')
141 {
142 free(path);
143 continue;
144 }
145
146 found_path_starts_with_dot = (*path == '.');
147
148 full_path = make_full_pathname(path, name, name_len);
149 free(path);
150
151 status = file_status(full_path);
152
153 if ((status & FS_EXISTS) && (status & FS_EXECABLE))
154 {
155 found = full_path;
156 break;
157 }
158
159 free(full_path);
160 }
161
162 return (found);
163 }
164
165 static char cwd[256];
166 static size_t cwdlen;
167
get_current_working_directory(void)168 static void get_current_working_directory(void)
169 {
170 if (cwdlen)
171 return;
172
173 if (!getcwd(cwd, sizeof(cwd)))
174 {
175 const char *pwd = getenv("PWD");
176 if (pwd && strlen(pwd) < sizeof(cwd))
177 strcpy(cwd, pwd);
178 }
179
180 if (*cwd != '/')
181 {
182 fprintf(stderr, "Can't get current working directory\n");
183 exit(-1);
184 }
185
186 cwdlen = strlen(cwd);
187
188 if (cwd[cwdlen - 1] != '/')
189 {
190 cwd[cwdlen++] = '/';
191 cwd[cwdlen] = 0;
192 }
193 }
194
path_clean_up(const char * path)195 static char *path_clean_up(const char *path)
196 {
197 static char result[256];
198
199 const char *p1 = path;
200 char *p2 = result;
201
202 int saw_slash = 0, saw_slash_dot = 0, saw_slash_dot_dot = 0;
203
204 if (*p1 != '/')
205 {
206 get_current_working_directory();
207 strcpy(result, cwd);
208 saw_slash = 1;
209 p2 = &result[cwdlen];
210 }
211
212 do
213 {
214 /*
215 * Two leading slashes are allowed, having an OS implementation-defined meaning.
216 * See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
217 */
218 if (!saw_slash || *p1 != '/' || (p1 == path + 1 && p1[1] != '/'))
219 *p2++ = *p1;
220 if (saw_slash_dot && (*p1 == '/'))
221 p2 -= 2;
222 if (saw_slash_dot_dot && (*p1 == '/'))
223 {
224 int cnt = 0;
225 do
226 {
227 if (--p2 < result)
228 {
229 strcpy(result, path);
230 return result;
231 }
232 if (*p2 == '/')
233 ++cnt;
234 }
235 while (cnt != 3);
236 ++p2;
237 }
238 saw_slash_dot_dot = saw_slash_dot && (*p1 == '.');
239 saw_slash_dot = saw_slash && (*p1 == '.');
240 saw_slash = (*p1 == '/');
241 }
242 while (*p1++);
243
244 return result;
245 }
246
247 struct function_st {
248 char *name;
249 size_t len;
250 char **lines;
251 int line_count;
252 };
253
254 static struct function_st *functions;
255 static int func_count;
256 static int max_func_count;
257
258 static char **aliases;
259 static int alias_count;
260 static int max_alias_count;
261
func_search(int indent,const char * cmd,struct function_st * func_list,int function_start_type)262 int func_search(int indent, const char *cmd, struct function_st *func_list, int function_start_type)
263 {
264 int i;
265 for (i = 0; i < func_count; ++i)
266 {
267 if (!strcmp(functions[i].name, cmd))
268 {
269 int j;
270 if (indent)
271 fputc('\t', stdout);
272 if (function_start_type == 1)
273 fprintf(stdout, "%s () {\n", cmd);
274 else
275 fprintf(stdout, "%s ()\n", cmd);
276 for (j = 0; j < functions[i].line_count; ++j)
277 {
278 if (indent)
279 fputc('\t', stdout);
280 fputs(functions[i].lines[j], stdout);
281 }
282 return 1;
283 }
284 }
285 return 0;
286 }
287
path_search(int indent,const char * cmd,const char * path_list)288 int path_search(int indent, const char *cmd, const char *path_list)
289 {
290 char *result = NULL;
291 int found_something = 0;
292
293 if (path_list && *path_list != '\0')
294 {
295 int next;
296 int path_index = 0;
297 do
298 {
299 next = show_all;
300 result = find_command_in_path(cmd, path_list, &path_index);
301 if (result)
302 {
303 const char *full_path = path_clean_up(result);
304 int in_home = (show_tilde || skip_tilde) && !strncmp(full_path, home, homelen);
305 if (indent)
306 fprintf(stdout, "\t");
307 if (!(skip_tilde && in_home) && show_dot && found_path_starts_with_dot && !strncmp(full_path, cwd, cwdlen))
308 {
309 full_path += cwdlen;
310 fprintf(stdout, "./");
311 }
312 else if (in_home)
313 {
314 if (skip_tilde)
315 {
316 next = 1;
317 free(result);
318 continue;
319 }
320 if (show_tilde)
321 {
322 full_path += homelen;
323 fprintf(stdout, "~/");
324 }
325 }
326 fprintf(stdout, "%s\n", full_path);
327 free(result);
328 found_something = 1;
329 }
330 else
331 break;
332 }
333 while (next);
334 }
335
336 return found_something;
337 }
338
process_alias(const char * str,int argc,char * argv[],const char * path_list,int function_start_type)339 void process_alias(const char *str, int argc, char *argv[], const char *path_list, int function_start_type)
340 {
341 const char *p = str;
342 int len = 0;
343
344 while(*p == ' ' || *p == '\t')
345 ++p;
346 if (!strncmp("alias", p, 5))
347 p += 5;
348 while(*p == ' ' || *p == '\t')
349 ++p;
350 while(*p && *p != ' ' && *p != '\t' && *p != '=')
351 ++p, ++len;
352
353 for (; argc > 0; --argc, ++argv)
354 {
355 char q = 0;
356 char *cmd;
357
358 if (!*argv || len != strlen(*argv) || strncmp(*argv, &p[-len], len))
359 continue;
360
361 fputs(str, stdout);
362
363 if (!show_all)
364 *argv = NULL;
365
366 while(*p == ' ' || *p == '\t')
367 ++p;
368 if (*p == '=')
369 ++p;
370 while(*p == ' ' || *p == '\t')
371 ++p;
372 if (*p == '"' || *p == '\'')
373 q = *p, ++p;
374
375 for(;;)
376 {
377 int found = 0;
378
379 while(*p == ' ' || *p == '\t')
380 ++p;
381 len = 0;
382 while(*p && *p != ' ' && *p != '\t' && *p != q && *p != '|' && *p != '&')
383 ++p, ++len;
384
385 cmd = (char *)xmalloc(len + 1);
386 strncpy(cmd, &p[-len], len);
387 cmd[len] = 0;
388 if (*argv && !strcmp(cmd, *argv))
389 *argv = NULL;
390 if (read_functions && !strchr(cmd, '/'))
391 found = func_search(1, cmd, functions, function_start_type);
392 if (show_all || !found)
393 path_search(1, cmd, path_list);
394 free(cmd);
395
396 while(*p && (*p != '|' || p[1] == '|') && (*p != '&' || p[1] == '&'))
397 ++p;
398
399 if (!*p)
400 break;
401
402 ++p;
403 }
404
405 break;
406 }
407 }
408
409 enum opts {
410 opt_version,
411 opt_skip_dot,
412 opt_skip_tilde,
413 opt_skip_alias,
414 opt_read_functions,
415 opt_skip_functions,
416 opt_show_dot,
417 opt_show_tilde,
418 opt_tty_only,
419 opt_help
420 };
421
422 #ifdef __TANDEM
423 /* According to Tom Bates, <tom.bates@hp.com> */
424 static uid_t const superuser = 65535;
425 #else
426 static uid_t const superuser = 0;
427 #endif
428
main(int argc,char * argv[])429 int main(int argc, char *argv[])
430 {
431 const char *path_list = getenv("PATH");
432 int short_option, fail_count = 0;
433 static int long_option;
434 struct option longopts[] = {
435 {"help", 0, &long_option, opt_help},
436 {"version", 0, &long_option, opt_version},
437 {"skip-dot", 0, &long_option, opt_skip_dot},
438 {"skip-tilde", 0, &long_option, opt_skip_tilde},
439 {"show-dot", 0, &long_option, opt_show_dot},
440 {"show-tilde", 0, &long_option, opt_show_tilde},
441 {"tty-only", 0, &long_option, opt_tty_only},
442 {"all", 0, NULL, 'a'},
443 {"read-alias", 0, NULL, 'i'},
444 {"skip-alias", 0, &long_option, opt_skip_alias},
445 {"read-functions", 0, &long_option, opt_read_functions},
446 {"skip-functions", 0, &long_option, opt_skip_functions},
447 {NULL, 0, NULL, 0}
448 };
449
450 progname = argv[0];
451 while ((short_option = getopt_long(argc, argv, "aivV", longopts, NULL)) != -1)
452 {
453 switch (short_option)
454 {
455 case 0:
456 switch (long_option)
457 {
458 case opt_help:
459 print_usage(stdout);
460 return 0;
461 case opt_version:
462 print_version();
463 return 0;
464 case opt_skip_dot:
465 skip_dot = !tty_only;
466 break;
467 case opt_skip_tilde:
468 skip_tilde = !tty_only;
469 break;
470 case opt_skip_alias:
471 skip_alias = 1;
472 break;
473 case opt_show_dot:
474 show_dot = !tty_only;
475 break;
476 case opt_show_tilde:
477 show_tilde = (!tty_only && geteuid() != superuser);
478 break;
479 case opt_tty_only:
480 tty_only = !isatty(1);
481 break;
482 case opt_read_functions:
483 read_functions = 1;
484 break;
485 case opt_skip_functions:
486 skip_functions = 1;
487 break;
488 }
489 break;
490 case 'a':
491 show_all = 1;
492 break;
493 case 'i':
494 read_alias = 1;
495 break;
496 case 'v':
497 case 'V':
498 print_version();
499 return 0;
500 }
501 }
502
503 uidget();
504
505 if (show_dot)
506 get_current_working_directory();
507
508 if (show_tilde || skip_tilde)
509 {
510 const char *h;
511
512 if (!(h = getenv("HOME")))
513 h = sh_get_home_dir();
514
515 strncpy(home, h, sizeof(home));
516 home[sizeof(home) - 1] = 0;
517 homelen = strlen(home);
518 if (home[homelen - 1] != '/' && homelen < sizeof(home) - 1)
519 {
520 strcat(home, "/");
521 ++homelen;
522 }
523 }
524
525 if (skip_alias)
526 read_alias = 0;
527
528 if (skip_functions)
529 read_functions = 0;
530
531 argv += optind;
532 argc -= optind;
533
534 if (argc == 0)
535 {
536 print_usage(stderr);
537 return -1;
538 }
539
540 int function_start_type = 0;
541 if (read_alias || read_functions)
542 {
543 char buf[1024];
544 int processing_aliases = read_alias;
545
546 if (isatty(0))
547 {
548 fprintf(stderr, "%s: %s: Warning: stdin is a tty.\n", progname,
549 (read_functions ? read_alias ? "--read-functions, --read-alias, -i" : "--read-functions" : "--read-alias, -i"));
550 }
551
552 while (fgets(buf, sizeof(buf), stdin))
553 {
554 int looks_like_function_start = 0;
555 int function_start_has_declare;
556 if (read_functions)
557 {
558 // bash version 2.0.5a and older output a pattern for `str' like
559 // declare -fx FUNCTION_NAME ()
560 // {
561 // body
562 // }
563 //
564 // bash version 2.0.5b and later output a pattern for `str' like
565 // FUNCTION_NAME ()
566 // {
567 // body
568 // }
569 char *p = buf + strlen(buf) - 1;
570 while (isspace(*p) && p > buf + 2)
571 --p;
572 if (*p == ')' && p[-1] == '(' && p[-2] == ' ')
573 {
574 looks_like_function_start = 1;
575 function_start_has_declare = (strncmp("declare -", buf, 9) == 0);
576 }
577 // Add some zsh support here.
578 // zsh does output a pattern for `str' like
579 // FUNCTION () {
580 // body
581 // }
582 if (p > buf + 4 && *p == '{' && p[-1] == ' ' &&
583 p[-2] == ')' && p[-3] == '(' && p[-4] == ' ')
584 {
585 looks_like_function_start = 1;
586 function_start_type = 1;
587 function_start_has_declare = 0;
588 }
589 }
590 if (processing_aliases && !looks_like_function_start)
591 {
592 // bash version 2.0.5b can throw in lines like "declare -fx FUNCTION_NAME", eat them.
593 if (!strncmp("declare -", buf, 9))
594 continue;
595 if (alias_count == max_alias_count)
596 {
597 max_alias_count += 32;
598 aliases = (char **)xrealloc(aliases, max_alias_count * sizeof(char *));
599 }
600 aliases[alias_count++] = strcpy((char *)xmalloc(strlen(buf) + 1), buf);
601 }
602 else if (read_functions && looks_like_function_start)
603 {
604 struct function_st *function;
605 int max_line_count;
606
607 const char *p = buf;
608 int len = 0;
609
610 processing_aliases = 0;
611
612 // Eat "declare -fx " at start of bash version 2.0.5a and older, if present.
613 if (function_start_has_declare)
614 {
615 p += 9;
616 while(*p && *p++ != ' ');
617 }
618
619 while(*p && *p != ' ')
620 ++p, ++len;
621
622 if (func_count == max_func_count)
623 {
624 max_func_count += 16;
625 functions = (struct function_st *)xrealloc(functions, max_func_count * sizeof(struct function_st));
626 }
627 function = &functions[func_count++];
628 function->name = (char *)xmalloc(len + 1);
629 strncpy(function->name, &p[-len], len);
630 function->name[len] = 0;
631 function->len = len;
632 max_line_count = 32;
633 function->lines = (char **)xmalloc(max_line_count * sizeof(char *));
634 function->line_count = 0;
635 while (fgets(buf, sizeof(buf), stdin))
636 {
637 size_t blen = strlen(buf);
638 function->lines[function->line_count++] = strcpy((char *)xmalloc(blen + 1), buf);
639 if (!strcmp(buf, "}\n"))
640 break;
641 if (function->line_count == max_line_count)
642 {
643 max_line_count += 32;
644 function->lines = (char **)xrealloc(function->lines, max_line_count * sizeof(char *));
645 }
646 }
647 }
648 }
649 if (read_alias)
650 {
651 int i;
652 for (i = 0; i < alias_count; ++i)
653 process_alias(aliases[i], argc, argv, path_list, function_start_type);
654 }
655 }
656
657 for (; argc > 0; --argc, ++argv)
658 {
659 int found_something = 0;
660
661 if (!*argv)
662 continue;
663
664 if (read_functions && !strchr(*argv, '/'))
665 found_something = func_search(0, *argv, functions, function_start_type);
666
667 if ((show_all || !found_something) && !path_search(0, *argv, path_list) && !found_something)
668 {
669 print_fail(absolute_path_given ? strrchr(*argv, '/') + 1 : *argv, absolute_path_given ? abs_path : path_list);
670 ++fail_count;
671 }
672 }
673
674 return fail_count;
675 }
676
677 #ifdef NEED_XMALLOC
xmalloc(size_t size)678 void *xmalloc(size_t size)
679 {
680 void *ptr = malloc(size);
681 if (ptr == NULL)
682 {
683 fprintf(stderr, "%s: Out of memory", progname);
684 exit(-1);
685 }
686 return ptr;
687 }
688
xrealloc(void * ptr,size_t size)689 void *xrealloc(void *ptr, size_t size)
690 {
691 if (!ptr)
692 return xmalloc(size);
693 ptr = realloc(ptr, size);
694 if (size > 0 && ptr == NULL)
695 {
696 fprintf(stderr, "%s: Out of memory\n", progname);
697 exit(-1);
698 }
699 return ptr;
700 }
701 #endif /* NEED_XMALLOC */
702