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