Refactor and audit which(1)

Use the *at functions instead of building paths manually. We do
still have path-building in recurse() and other areas, but the
long-term goal is to rid most interfaces of that for practical
and security reasons.
In this case, it's more or less trivial.

Also, refactor the manpage to be more consistent with the others.

BUGFIX: Return exit status 3 on error.
This commit is contained in:
FRIGN 2015-04-27 20:01:30 +02:00 committed by sin
parent 035e14c516
commit b333176b8c
3 changed files with 65 additions and 47 deletions

2
README
View File

@ -91,7 +91,7 @@ The following tools are implemented:
=*|o uudecode . =*|o uudecode .
=*|o uuencode . =*|o uuencode .
#*|o wc . #*|o wc .
=* x which . =*|x which .
=*|o xargs (-p) =*|o xargs (-p)
=*|x yes . =*|x yes .

41
which.1
View File

@ -3,33 +3,42 @@
.Os sbase .Os sbase
.Sh NAME .Sh NAME
.Nm which .Nm which
.Nd locate a program file (or files) in the path .Nd locate programs in the path
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm .Nm
.Op Fl a .Op Fl a
.Op Ar name ... .Ar name ...
.Sh DESCRIPTION .Sh DESCRIPTION
.Nm .Nm
looks for programs in looks for each
.Ar name
in the
.Ev PATH .Ev PATH
. directories, stopping at the first match and printing
.Pp the full path to stdout.
If .Sh OPTIONS
.Fl a .Bl -tag -width Ds
is specified it will display all matches and not stop at the first match. .It Fl a
Don't stop at the first match and search all
.Ev PATH
directories.
.El
.Sh EXIT STATUS .Sh EXIT STATUS
The
.Nm
utility exits with one of the following values:
.Bl -tag -width Ds .Bl -tag -width Ds
.It 0 .It 0
All names were successfully resolved. Each
.Ar name
was found.
.It 1 .It 1
Some names were resolved but not all. At least one
.Ar name
was not found.
.It 2 .It 2
No names were resolved. No
.Ar name
was found.
.It 3
An error occurred.
.El .El
.Sh DIAGNOSTICS
If a program is not found it will print "Command not found" to stderr.
.Sh SEE ALSO .Sh SEE ALSO
.Xr environ 7 .Xr environ 7

69
which.c
View File

@ -1,5 +1,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <limits.h> #include <limits.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -14,30 +16,34 @@ static int aflag;
static int static int
which(const char *path, const char *name) which(const char *path, const char *name)
{ {
char file[PATH_MAX], *p, *s, *ptr; char *ptr, *p;
size_t len; size_t i, len;
struct stat st; struct stat st;
int found = 0; int dirfd, found = 0;
p = ptr = estrdup(path); ptr = p = enstrdup(3, path);
for (s = p; (s = strsep(&p, ":")); ) { len = strlen(p);
if (!s[0]) for (i = 0; i < len + 1; i++) {
s = "."; if (ptr[i] != ':' && ptr[i] != '\0')
len = strlen(s); continue;
ptr[i] = '\0';
if (snprintf(file, sizeof(file), "%s%s%s", if ((dirfd = open(p, O_RDONLY, 0)) >= 0) {
s, if (!fstatat(dirfd, name, &st, 0) &&
len > 0 && s[len - 1] != '/' ? "/" : "", S_ISREG(st.st_mode) &&
name) >= sizeof(file)) !faccessat(dirfd, name, X_OK, 0)) {
eprintf("path too long\n"); found = 1;
fputs(p, stdout);
if (stat(file, &st) == 0 && S_ISREG(st.st_mode) && if (i && ptr[i - 1] != '/')
access(file, X_OK) == 0) { fputc('/', stdout);
found = 1; puts(name);
puts(file); if (!aflag) {
if (!aflag) close(dirfd);
break; break;
}
}
close(dirfd);
} }
p = ptr + i + 1;
} }
free(ptr); free(ptr);
@ -47,14 +53,14 @@ which(const char *path, const char *name)
static void static void
usage(void) usage(void)
{ {
eprintf("usage: %s [-a] name...\n", argv0); eprintf("usage: %s [-a] name ...\n", argv0);
} }
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
char *path; char *path;
int i, found; int found = 0, foundall = 1;
ARGBEGIN { ARGBEGIN {
case 'a': case 'a':
@ -68,13 +74,16 @@ main(int argc, char *argv[])
usage(); usage();
if (!(path = getenv("PATH"))) if (!(path = getenv("PATH")))
eprintf("$PATH not set\n"); enprintf(3, "$PATH is not set\n");
for (i = 0, found = 0; i < argc; i++) { for (; *argv; argc--, argv++) {
if (which(path, argv[i])) if (which(path, *argv)) {
found++; found = 1;
else } else {
weprintf("%s: Command not found.\n", argv[i]); weprintf("%s: command not found.\n", *argv);
foundall = 0;
}
} }
return !found ? 2 : found == argc ? 0 : 1;
return found ? foundall ? 0 : 1 : 2;
} }