/*
* which v2.x -- print full path of executables
* Copyright (C) 1999, 2003, 2007, 2008 Carlo Wood <carlo@gnu.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "sys.h"
#include <stdio.h>
#include <ctype.h>
#include "getopt.h"
#include "tilde/tilde.h"
#include "bash.h"
static const char *progname;
static void print_usage(FILE *out)
{
fprintf(out, "Usage: %s [options] [--] COMMAND [...]\n", progname);
fprintf(out, "Write the full path of COMMAND(s) to standard output.\n\n");
fprintf(out, " --version, -[vV] Print version and exit successfully.\n");
fprintf(out, " --help, Print this help and exit successfully.\n");
fprintf(out, " --skip-dot Skip directories in PATH that start with a dot.\n");
fprintf(out, " --skip-tilde Skip directories in PATH that start with a tilde.\n");
fprintf(out, " --show-dot Don't expand a dot to current directory in output.\n");
fprintf(out, " --show-tilde Output a tilde for HOME directory for non-root.\n");
fprintf(out, " --tty-only Stop processing options on the right if not on tty.\n");
fprintf(out, " --all, -a Print all matches in PATH, not just the first\n");
fprintf(out, " --read-alias, -i Read list of aliases from stdin.\n");
fprintf(out, " --skip-alias Ignore option --read-alias; don't read stdin.\n");
fprintf(out, " --read-functions Read shell functions from stdin.\n");
fprintf(out, " --skip-functions Ignore option --read-functions; don't read stdin.\n\n");
fprintf(out, "Recommended use is to write the output of (alias; declare -f) to standard\n");
fprintf(out, "input, so that which can show aliases and shell functions. See which(1) for\n");
fprintf(out, "examples.\n\n");
fprintf(out, "If the options --read-alias and/or --read-functions are specified then the\n");
fprintf(out, "output can be a full alias or function definition, optionally followed by\n");
fprintf(out, "the full path of each command used inside of those.\n\n");
fprintf(out, "Report bugs to <which-bugs@gnu.org>.\n");
}
static void print_version(void)
{
fprintf(stdout, "GNU which v" VERSION ", Copyright (C) 1999 - 2015 Carlo Wood.\n");
fprintf(stdout, "GNU which comes with ABSOLUTELY NO WARRANTY;\n");
fprintf(stdout, "This program is free software; your freedom to use, change\n");
fprintf(stdout, "and distribute this program is protected by the GPL.\n");
}
static void print_fail(const char *name, const char *path_list)
{
fprintf(stderr, "%s: no %s in (%s)\n", progname, name, path_list);
}
static char home[256];
static size_t homelen = 0;
static int absolute_path_given;
static int found_path_starts_with_dot;
static char *abs_path;
static int skip_dot = 0, skip_tilde = 0, skip_alias = 0, read_alias = 0;
static int show_dot = 0, show_tilde = 0, show_all = 0, tty_only = 0;
static int skip_functions = 0, read_functions = 0;
static char *find_command_in_path(const char *name, const char *path_list, int *path_index)
{
char *found = NULL, *full_path;
int status, name_len;
name_len = strlen(name);
if (!absolute_program(name))
absolute_path_given = 0;
else
{
char *p;
absolute_path_given = 1;
if (abs_path)
free(abs_path);
if (*name != '.' && *name != '/' && *name != '~')
{
abs_path = (char *)xmalloc(3 + name_len);
strcpy(abs_path, "./");
strcat(abs_path, name);
}
else
{
abs_path = (char *)xmalloc(1 + name_len);
strcpy(abs_path, name);
}
path_list = abs_path;
p = strrchr(abs_path, '/');
*p++ = 0;
name = p;
}
while (path_list && path_list[*path_index])
{
char *path;
if (absolute_path_given)
{
path = savestring(path_list);
*path_index = strlen(path);
}
else
path = get_next_path_element(path_list, path_index);
if (!path)
break;
if (*path == '~')
{
char *t = tilde_expand(path);
free(path);
path = t;
if (skip_tilde)
{
free(path);
continue;
}
}
if (skip_dot && *path != '/')
{
free(path);
continue;
}
found_path_starts_with_dot = (*path == '.');
full_path = make_full_pathname(path, name, name_len);
free(path);
status = file_status(full_path);
if ((status & FS_EXISTS) && (status & FS_EXECABLE))
{
found = full_path;
break;
}
free(full_path);
}
return (found);
}
static char cwd[256];
static size_t cwdlen;
static void get_current_working_directory(void)
{
if (cwdlen)
return;
if (!getcwd(cwd, sizeof(cwd)))
{
const char *pwd = getenv("PWD");
if (pwd && strlen(pwd) < sizeof(cwd))
strcpy(cwd, pwd);
}
if (*cwd != '/')
{
fprintf(stderr, "Can't get current working directory\n");
exit(-1);
}
cwdlen = strlen(cwd);
if (cwd[cwdlen - 1] != '/')
{
cwd[cwdlen++] = '/';
cwd[cwdlen] = 0;
}
}
static char *path_clean_up(const char *path)
{
static char result[256];
const char *p1 = path;
char *p2 = result;
int saw_slash = 0, saw_slash_dot = 0, saw_slash_dot_dot = 0;
if (*p1 != '/')
{
get_current_working_directory();
strcpy(result, cwd);
saw_slash = 1;
p2 = &result[cwdlen];
}
do
{
/*
* Two leading slashes are allowed, having an OS implementation-defined meaning.
* See http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap04.html#tag_04_11
*/
if (!saw_slash || *p1 != '/' || (p1 == path + 1 && p1[1] != '/'))
*p2++ = *p1;
if (saw_slash_dot && (*p1 == '/'))
p2 -= 2;
if (saw_slash_dot_dot && (*p1 == '/'))
{
int cnt = 0;
do
{
if (--p2 < result)
{
strcpy(result, path);
return result;
}
if (*p2 == '/')
++cnt;
}
while (cnt != 3);
++p2;
}
saw_slash_dot_dot = saw_slash_dot && (*p1 == '.');
saw_slash_dot = saw_slash && (*p1 == '.');
saw_slash = (*p1 == '/');
}
while (*p1++);
return result;
}
struct function_st {
char *name;
size_t len;
char **lines;
int line_count;
};
static struct function_st *functions;
static int func_count;
static int max_func_count;
static char **aliases;
static int alias_count;
static int max_alias_count;
int func_search(int indent, const char *cmd, struct function_st *func_list, int function_start_type)
{
int i;
for (i = 0; i < func_count; ++i)
{
if (!strcmp(functions[i].name, cmd))
{
int j;
if (indent)
fputc('\t', stdout);
if (function_start_type == 1)
fprintf(stdout, "%s () {\n", cmd);
else
fprintf(stdout, "%s ()\n", cmd);
for (j = 0; j < functions[i].line_count; ++j)
{
if (indent)
fputc('\t', stdout);
fputs(functions[i].lines[j], stdout);
}
return 1;
}
}
return 0;
}
int path_search(int indent, const char *cmd, const char *path_list)
{
char *result = NULL;
int found_something = 0;
if (path_list && *path_list != '\0')
{
int next;
int path_index = 0;
do
{
next = show_all;
result = find_command_in_path(cmd, path_list, &path_index);
if (result)
{
const char *full_path = path_clean_up(result);
int in_home = (show_tilde || skip_tilde) && !strncmp(full_path, home, homelen);
if (indent)
fprintf(stdout, "\t");
if (!(skip_tilde && in_home) && show_dot && found_path_starts_with_dot && !strncmp(full_path, cwd, cwdlen))
{
full_path += cwdlen;
fprintf(stdout, "./");
}
else if (in_home)
{
if (skip_tilde)
{
next = 1;
free(result);
continue;
}
if (show_tilde)
{
full_path += homelen;
fprintf(stdout, "~/");
}
}
fprintf(stdout, "%s\n", full_path);
free(result);
found_something = 1;
}
else
break;
}
while (next);
}
return found_something;
}
void process_alias(const char *str, int argc, char *argv[], const char *path_list, int function_start_type)
{
const char *p = str;
int len = 0;
while(*p == ' ' || *p == '\t')
++p;
if (!strncmp("alias", p, 5))
p += 5;
while(*p == ' ' || *p == '\t')
++p;
while(*p && *p != ' ' && *p != '\t' && *p != '=')
++p, ++len;
for (; argc > 0; --argc, ++argv)
{
char q = 0;
char *cmd;
if (!*argv || len != strlen(*argv) || strncmp(*argv, &p[-len], len))
continue;
fputs(str, stdout);
if (!show_all)
*argv = NULL;
while(*p == ' ' || *p == '\t')
++p;
if (*p == '=')
++p;
while(*p == ' ' || *p == '\t')
++p;
if (*p == '"' || *p == '\'')
q = *p, ++p;
for(;;)
{
int found = 0;
while(*p == ' ' || *p == '\t')
++p;
len = 0;
while(*p && *p != ' ' && *p != '\t' && *p != q && *p != '|' && *p != '&')
++p, ++len;
cmd = (char *)xmalloc(len + 1);
strncpy(cmd, &p[-len], len);
cmd[len] = 0;
if (*argv && !strcmp(cmd, *argv))
*argv = NULL;
if (read_functions && !strchr(cmd, '/'))
found = func_search(1, cmd, functions, function_start_type);
if (show_all || !found)
path_search(1, cmd, path_list);
free(cmd);
while(*p && (*p != '|' || p[1] == '|') && (*p != '&' || p[1] == '&'))
++p;
if (!*p)
break;
++p;
}
break;
}
}
enum opts {
opt_version,
opt_skip_dot,
opt_skip_tilde,
opt_skip_alias,
opt_read_functions,
opt_skip_functions,
opt_show_dot,
opt_show_tilde,
opt_tty_only,
opt_help
};
#ifdef __TANDEM
/* According to Tom Bates, <tom.bates@hp.com> */
static uid_t const superuser = 65535;
#else
static uid_t const superuser = 0;
#endif
int main(int argc, char *argv[])
{
const char *path_list = getenv("PATH");
int short_option, fail_count = 0;
static int long_option;
struct option longopts[] = {
{"help", 0, &long_option, opt_help},
{"version", 0, &long_option, opt_version},
{"skip-dot", 0, &long_option, opt_skip_dot},
{"skip-tilde", 0, &long_option, opt_skip_tilde},
{"show-dot", 0, &long_option, opt_show_dot},
{"show-tilde", 0, &long_option, opt_show_tilde},
{"tty-only", 0, &long_option, opt_tty_only},
{"all", 0, NULL, 'a'},
{"read-alias", 0, NULL, 'i'},
{"skip-alias", 0, &long_option, opt_skip_alias},
{"read-functions", 0, &long_option, opt_read_functions},
{"skip-functions", 0, &long_option, opt_skip_functions},
{NULL, 0, NULL, 0}
};
progname = argv[0];
while ((short_option = getopt_long(argc, argv, "aivV", longopts, NULL)) != -1)
{
switch (short_option)
{
case 0:
switch (long_option)
{
case opt_help:
print_usage(stdout);
return 0;
case opt_version:
print_version();
return 0;
case opt_skip_dot:
skip_dot = !tty_only;
break;
case opt_skip_tilde:
skip_tilde = !tty_only;
break;
case opt_skip_alias:
skip_alias = 1;
break;
case opt_show_dot:
show_dot = !tty_only;
break;
case opt_show_tilde:
show_tilde = (!tty_only && geteuid() != superuser);
break;
case opt_tty_only:
tty_only = !isatty(1);
break;
case opt_read_functions:
read_functions = 1;
break;
case opt_skip_functions:
skip_functions = 1;
break;
}
break;
case 'a':
show_all = 1;
break;
case 'i':
read_alias = 1;
break;
case 'v':
case 'V':
print_version();
return 0;
}
}
uidget();
if (show_dot)
get_current_working_directory();
if (show_tilde || skip_tilde)
{
const char *h;
if (!(h = getenv("HOME")))
h = sh_get_home_dir();
strncpy(home, h, sizeof(home));
home[sizeof(home) - 1] = 0;
homelen = strlen(home);
if (home[homelen - 1] != '/' && homelen < sizeof(home) - 1)
{
strcat(home, "/");
++homelen;
}
}
if (skip_alias)
read_alias = 0;
if (skip_functions)
read_functions = 0;
argv += optind;
argc -= optind;
if (argc == 0)
{
print_usage(stderr);
return -1;
}
int function_start_type = 0;
if (read_alias || read_functions)
{
char buf[1024];
int processing_aliases = read_alias;
if (isatty(0))
{
fprintf(stderr, "%s: %s: Warning: stdin is a tty.\n", progname,
(read_functions ? read_alias ? "--read-functions, --read-alias, -i" : "--read-functions" : "--read-alias, -i"));
}
while (fgets(buf, sizeof(buf), stdin))
{
int looks_like_function_start = 0;
int function_start_has_declare;
if (read_functions)
{
// bash version 2.0.5a and older output a pattern for `str' like
// declare -fx FUNCTION_NAME ()
// {
// body
// }
//
// bash version 2.0.5b and later output a pattern for `str' like
// FUNCTION_NAME ()
// {
// body
// }
char *p = buf + strlen(buf) - 1;
while (isspace(*p) && p > buf + 2)
--p;
if (*p == ')' && p[-1] == '(' && p[-2] == ' ')
{
looks_like_function_start = 1;
function_start_has_declare = (strncmp("declare -", buf, 9) == 0);
}
// Add some zsh support here.
// zsh does output a pattern for `str' like
// FUNCTION () {
// body
// }
if (p > buf + 4 && *p == '{' && p[-1] == ' ' &&
p[-2] == ')' && p[-3] == '(' && p[-4] == ' ')
{
looks_like_function_start = 1;
function_start_type = 1;
function_start_has_declare = 0;
}
}
if (processing_aliases && !looks_like_function_start)
{
// bash version 2.0.5b can throw in lines like "declare -fx FUNCTION_NAME", eat them.
if (!strncmp("declare -", buf, 9))
continue;
if (alias_count == max_alias_count)
{
max_alias_count += 32;
aliases = (char **)xrealloc(aliases, max_alias_count * sizeof(char *));
}
aliases[alias_count++] = strcpy((char *)xmalloc(strlen(buf) + 1), buf);
}
else if (read_functions && looks_like_function_start)
{
struct function_st *function;
int max_line_count;
const char *p = buf;
int len = 0;
processing_aliases = 0;
// Eat "declare -fx " at start of bash version 2.0.5a and older, if present.
if (function_start_has_declare)
{
p += 9;
while(*p && *p++ != ' ');
}
while(*p && *p != ' ')
++p, ++len;
if (func_count == max_func_count)
{
max_func_count += 16;
functions = (struct function_st *)xrealloc(functions, max_func_count * sizeof(struct function_st));
}
function = &functions[func_count++];
function->name = (char *)xmalloc(len + 1);
strncpy(function->name, &p[-len], len);
function->name[len] = 0;
function->len = len;
max_line_count = 32;
function->lines = (char **)xmalloc(max_line_count * sizeof(char *));
function->line_count = 0;
while (fgets(buf, sizeof(buf), stdin))
{
size_t blen = strlen(buf);
function->lines[function->line_count++] = strcpy((char *)xmalloc(blen + 1), buf);
if (!strcmp(buf, "}\n"))
break;
if (function->line_count == max_line_count)
{
max_line_count += 32;
function->lines = (char **)xrealloc(function->lines, max_line_count * sizeof(char *));
}
}
}
}
if (read_alias)
{
int i;
for (i = 0; i < alias_count; ++i)
process_alias(aliases[i], argc, argv, path_list, function_start_type);
}
}
for (; argc > 0; --argc, ++argv)
{
int found_something = 0;
if (!*argv)
continue;
if (read_functions && !strchr(*argv, '/'))
found_something = func_search(0, *argv, functions, function_start_type);
if ((show_all || !found_something) && !path_search(0, *argv, path_list) && !found_something)
{
print_fail(absolute_path_given ? strrchr(*argv, '/') + 1 : *argv, absolute_path_given ? abs_path : path_list);
++fail_count;
}
}
return fail_count;
}
#ifdef NEED_XMALLOC
void *xmalloc(size_t size)
{
void *ptr = malloc(size);
if (ptr == NULL)
{
fprintf(stderr, "%s: Out of memory", progname);
exit(-1);
}
return ptr;
}
void *xrealloc(void *ptr, size_t size)
{
if (!ptr)
return xmalloc(size);
ptr = realloc(ptr, size);
if (size > 0 && ptr == NULL)
{
fprintf(stderr, "%s: Out of memory\n", progname);
exit(-1);
}
return ptr;
}
#endif /* NEED_XMALLOC */