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