#include <sys/fcntl.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <err.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define EXIT_USAGE 2
static void usage(void) __NORETURN;
static bool print_matches(char *, const char *const);
static bool silent = false;
static bool allpaths = false;
int
main(int argc, char **argv)
{
char *p, *path;
size_t pathlen;
int opt, status;
status = EXIT_SUCCESS;
while ((opt = getopt(argc, argv, "as")) != -1) {
switch (opt) {
case 'a':
allpaths = true;
break;
case 's':
silent = true;
break;
default:
usage();
break;
}
}
argv += optind;
argc -= optind;
if (argc == 0)
exit(EXIT_SUCCESS);
if ((p = getenv("PATH")) == NULL)
errx(EXIT_FAILURE, "Could not find PATH in environment");
pathlen = strlen(p);
path = strdup(p);
if (path == NULL)
err(EXIT_FAILURE, "Failed to duplicate PATH");
while (argc > 0) {
memcpy(path, p, pathlen + 1);
if (strlen(*argv) >= FILENAME_MAX) {
status = EXIT_FAILURE;
warnx("operand too long '%s'", *argv);
} else if (!print_matches(path, *argv)) {
status = EXIT_FAILURE;
if (!silent) {
(void) printf("no %s in", *argv);
if (pathlen > 0) {
char *q = path;
const char *d;
memcpy(q, p, pathlen + 1);
while ((d = strsep(&q, ":")) != NULL) {
(void) printf(" %s",
*d == '\0' ? "." : d);
}
}
(void) printf("\n");
}
}
argv++;
argc--;
}
free(path);
exit(status);
}
static void
usage(void)
{
(void) fprintf(stderr, "usage: which [-as] program ...\n");
exit(EXIT_USAGE);
}
static bool
is_there(const char *const candidate)
{
struct stat fin;
if (faccessat(AT_FDCWD, candidate, X_OK, AT_EACCESS) == 0 &&
stat(candidate, &fin) == 0 &&
S_ISREG(fin.st_mode)) {
if (!silent)
printf("%s\n", candidate);
return (true);
}
return (false);
}
static bool
print_matches(char *path, const char *const filename)
{
char candidate[PATH_MAX];
const char *d;
bool found = false;
if (strchr(filename, '/') != NULL)
return (is_there(filename));
while ((d = strsep(&path, ":")) != NULL) {
if (*d == '\0')
d = ".";
if (snprintf(candidate, sizeof (candidate), "%s/%s", d,
filename) >= (int)sizeof (candidate))
continue;
if (is_there(candidate)) {
found = true;
if (!allpaths)
break;
}
}
return (found);
}