xref: /openbsd/usr.bin/which/which.c (revision 8932bfb7)
1 /*	$OpenBSD: which.c,v 1.17 2011/03/11 04:30:21 guenther 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/param.h>
20 #include <sys/stat.h>
21 #include <sys/sysctl.h>
22 
23 #include <err.h>
24 #include <errno.h>
25 #include <locale.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #define PROG_WHICH	1
32 #define PROG_WHEREIS	2
33 
34 extern char *__progname;
35 
36 int findprog(char *, char *, int, int);
37 __dead void usage(void);
38 
39 /*
40  * which(1) -- find an executable(s) in the user's path
41  * whereis(1) -- find an executable(s) in the default user path
42  *
43  * Return values:
44  *	0 - all executables found
45  *	1 - some found, some not
46  *	2 - none found
47  */
48 
49 int
50 main(int argc, char *argv[])
51 {
52 	char *path;
53 	size_t n;
54 	int ch, allmatches = 0, notfound = 0, progmode = PROG_WHICH;
55 
56 	(void)setlocale(LC_ALL, "");
57 
58 	while ((ch = getopt(argc, argv, "a")) != -1)
59 		switch (ch) {
60 		case 'a':
61 			allmatches = 1;
62 			break;
63 		default:
64 			usage();
65 		}
66 	argc -= optind;
67 	argv += optind;
68 
69 	if (argc == 0)
70 		usage();
71 
72 	/*
73 	 * which(1) uses user's $PATH.
74 	 * whereis(1) uses user.cs_path from sysctl(3).
75 	 */
76 	if (strcmp(__progname, "whereis") == 0) {
77 		int mib[2];
78 
79 		progmode = PROG_WHEREIS;
80 		mib[0] = CTL_USER;
81 		mib[1] = USER_CS_PATH;
82 		if (sysctl(mib, 2, NULL, &n, NULL, 0) == -1)
83 			err(1, "unable to get length of user.cs_path");
84 		if (n == 0)
85 			errx(1, "user.cs_path was zero length!");
86 		if ((path = (char *)malloc(n)) == NULL)
87 			errx(1, "can't allocate memory.");
88 		if (sysctl(mib, 2, path, &n, NULL, 0) == -1)
89 			err(1, "unable to get user.cs_path");
90 	} else {
91 		if ((path = getenv("PATH")) == NULL)
92 			err(1, "can't get $PATH from environment");
93 	}
94 
95 	/* To make access(2) do what we want */
96 	if (setgid(getegid()))
97 		err(1, "Can't set gid to %u", getegid());
98 	if (setuid(geteuid()))
99 		err(1, "Can't set uid to %u", geteuid());
100 
101 	for (n = 0; n < argc; n++)
102 		if (findprog(argv[n], path, progmode, allmatches) == 0)
103 			notfound++;
104 
105 	exit((notfound == 0) ? 0 : ((notfound == argc) ? 2 : 1));
106 }
107 
108 int
109 findprog(char *prog, char *path, int progmode, int allmatches)
110 {
111 	char *p, filename[MAXPATHLEN];
112 	int proglen, plen, rval = 0;
113 	struct stat sbuf;
114 	char *pathcpy;
115 
116 	/* Special case if prog contains '/' */
117 	if (strchr(prog, '/')) {
118 		if ((stat(prog, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
119 		    access(prog, X_OK) == 0) {
120 			(void)puts(prog);
121 			return (1);
122 		} else {
123 			warnx("%s: Command not found.", prog);
124 			return (0);
125 		}
126 	}
127 
128 	if ((path = strdup(path)) == NULL)
129 		err(1, "strdup");
130 	pathcpy = path;
131 
132 	proglen = strlen(prog);
133 	while ((p = strsep(&pathcpy, ":")) != NULL) {
134 		if (*p == '\0')
135 			p = ".";
136 
137 		plen = strlen(p);
138 		while (p[plen-1] == '/')
139 			p[--plen] = '\0';	/* strip trailing '/' */
140 
141 		if (plen + 1 + proglen >= sizeof(filename)) {
142 			warnx("%s/%s: %s", p, prog, strerror(ENAMETOOLONG));
143 			free(path);
144 			return (0);
145 		}
146 
147 		snprintf(filename, sizeof(filename), "%s/%s", p, prog);
148 		if ((stat(filename, &sbuf) == 0) && S_ISREG(sbuf.st_mode) &&
149 		    access(filename, X_OK) == 0) {
150 			(void)puts(filename);
151 			rval = 1;
152 			if (!allmatches) {
153 				free(path);
154 				return (rval);
155 			}
156 		}
157 	}
158 	(void)free(path);
159 
160 	/* whereis(1) is silent on failure. */
161 	if (!rval && progmode != PROG_WHEREIS)
162 		warnx("%s: Command not found.", prog);
163 	return (rval);
164 }
165 
166 __dead void
167 usage(void)
168 {
169 	(void)fprintf(stderr, "usage: %s [-a] name ...\n", __progname);
170 	exit(1);
171 }
172