xref: /openbsd/usr.bin/which/which.c (revision 5af055cd)
1 /*	$OpenBSD: which.c,v 1.25 2016/01/14 22:02:13 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 1997 Todd C. Miller <Todd.Miller@courtesan.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/stat.h>
20 #include <sys/sysctl.h>
21 
22 #include <err.h>
23 #include <errno.h>
24 #include <locale.h>
25 #include <paths.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <limits.h>
31 
32 #define PROG_WHICH	1
33 #define PROG_WHEREIS	2
34 
35 extern char *__progname;
36 
37 int findprog(char *, char *, int, int);
38 __dead void usage(void);
39 
40 /*
41  * which(1) -- find an executable(s) in the user's path
42  * whereis(1) -- find an executable(s) in the default user path
43  *
44  * Return values:
45  *	0 - all executables found
46  *	1 - some found, some not
47  *	2 - none found
48  */
49 
50 int
51 main(int argc, char *argv[])
52 {
53 	char *path;
54 	size_t n;
55 	int ch, allmatches = 0, notfound = 0, progmode = PROG_WHICH;
56 
57 	(void)setlocale(LC_ALL, "");
58 
59 	while ((ch = getopt(argc, argv, "a")) != -1)
60 		switch (ch) {
61 		case 'a':
62 			allmatches = 1;
63 			break;
64 		default:
65 			usage();
66 		}
67 	argc -= optind;
68 	argv += optind;
69 
70 	if (argc == 0)
71 		usage();
72 
73 	if (strcmp(__progname, "whereis") == 0) {
74 		progmode = PROG_WHEREIS;
75 		path = _PATH_STDPATH;
76 	} else {
77 		if ((path = getenv("PATH")) == NULL || *path == '\0')
78 			path = _PATH_DEFPATH;
79 	}
80 
81 	/* To make access(2) do what we want */
82 	if (setgid(getegid()))
83 		err(1, "Can't set gid to %u", getegid());
84 	if (setuid(geteuid()))
85 		err(1, "Can't set uid to %u", geteuid());
86 
87 	if (pledge("stdio rpath", NULL) == -1)
88 		err(2, "pledge");
89 
90 	for (n = 0; n < argc; n++)
91 		if (findprog(argv[n], path, progmode, allmatches) == 0)
92 			notfound++;
93 
94 	exit((notfound == 0) ? 0 : ((notfound == argc) ? 2 : 1));
95 }
96 
97 int
98 findprog(char *prog, char *path, int progmode, int allmatches)
99 {
100 	char *p, filename[PATH_MAX];
101 	int len, rval = 0;
102 	struct stat sbuf;
103 	char *pathcpy;
104 
105 	/* Special case if prog contains '/' */
106 	if (strchr(prog, '/')) {
107 		if ((stat(prog, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
108 		    access(prog, X_OK) == 0) {
109 			(void)puts(prog);
110 			return (1);
111 		} else {
112 			warnx("%s: Command not found.", prog);
113 			return (0);
114 		}
115 	}
116 
117 	if ((path = strdup(path)) == NULL)
118 		err(1, "strdup");
119 	pathcpy = path;
120 
121 	while ((p = strsep(&pathcpy, ":")) != NULL) {
122 		if (*p == '\0')
123 			p = ".";
124 
125 		len = strlen(p);
126 		while (len > 0 && p[len-1] == '/')
127 			p[--len] = '\0';	/* strip trailing '/' */
128 
129 		len = snprintf(filename, sizeof(filename), "%s/%s", p, prog);
130 		if (len < 0 || len >= sizeof(filename)) {
131 			warnc(ENAMETOOLONG, "%s/%s", p, prog);
132 			free(path);
133 			return (0);
134 		}
135 		if ((stat(filename, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
136 		    access(filename, X_OK) == 0) {
137 			(void)puts(filename);
138 			rval = 1;
139 			if (!allmatches) {
140 				free(path);
141 				return (rval);
142 			}
143 		}
144 	}
145 	(void)free(path);
146 
147 	/* whereis(1) is silent on failure. */
148 	if (!rval && progmode != PROG_WHEREIS)
149 		warnx("%s: Command not found.", prog);
150 	return (rval);
151 }
152 
153 __dead void
154 usage(void)
155 {
156 	(void)fprintf(stderr, "usage: %s [-a] name ...\n", __progname);
157 	exit(1);
158 }
159