1 /*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright (c) 2000 Dan Papasian. All rights reserved.
5 * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. The name of the author may not be used to endorse or promote products
16 * derived from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include <sys/fcntl.h>
31 #include <sys/param.h>
32 #include <sys/stat.h>
33 #include <err.h>
34 #include <limits.h>
35 #include <stdbool.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 #define EXIT_USAGE 2
42
43 static void usage(void) __NORETURN;
44 static bool print_matches(char *, const char *const);
45
46 static bool silent = false;
47 static bool allpaths = false;
48
49 int
main(int argc,char ** argv)50 main(int argc, char **argv)
51 {
52 char *p, *path;
53 size_t pathlen;
54 int opt, status;
55
56 status = EXIT_SUCCESS;
57
58 while ((opt = getopt(argc, argv, "as")) != -1) {
59 switch (opt) {
60 case 'a':
61 allpaths = true;
62 break;
63 case 's':
64 silent = true;
65 break;
66 default:
67 usage();
68 break;
69 }
70 }
71
72 argv += optind;
73 argc -= optind;
74
75 if (argc == 0)
76 exit(EXIT_SUCCESS);
77
78 if ((p = getenv("PATH")) == NULL)
79 errx(EXIT_FAILURE, "Could not find PATH in environment");
80
81 pathlen = strlen(p);
82 path = strdup(p);
83
84 if (path == NULL)
85 err(EXIT_FAILURE, "Failed to duplicate PATH");
86
87 while (argc > 0) {
88 memcpy(path, p, pathlen + 1);
89
90 if (strlen(*argv) >= FILENAME_MAX) {
91 status = EXIT_FAILURE;
92
93 warnx("operand too long '%s'", *argv);
94 } else if (!print_matches(path, *argv)) {
95 status = EXIT_FAILURE;
96
97 if (!silent) {
98 (void) printf("no %s in", *argv);
99
100 if (pathlen > 0) {
101 char *q = path;
102 const char *d;
103
104 memcpy(q, p, pathlen + 1);
105
106 while ((d = strsep(&q, ":")) != NULL) {
107 (void) printf(" %s",
108 *d == '\0' ? "." : d);
109 }
110 }
111
112 (void) printf("\n");
113 }
114 }
115
116 argv++;
117 argc--;
118 }
119
120 free(path);
121 exit(status);
122 }
123
124 static void
usage(void)125 usage(void)
126 {
127 (void) fprintf(stderr, "usage: which [-as] program ...\n");
128 exit(EXIT_USAGE);
129 }
130
131 static bool
is_there(const char * const candidate)132 is_there(const char *const candidate)
133 {
134 struct stat fin;
135
136 if (faccessat(AT_FDCWD, candidate, X_OK, AT_EACCESS) == 0 &&
137 stat(candidate, &fin) == 0 &&
138 S_ISREG(fin.st_mode)) {
139 if (!silent)
140 printf("%s\n", candidate);
141
142 return (true);
143 }
144
145 return (false);
146 }
147
148 static bool
print_matches(char * path,const char * const filename)149 print_matches(char *path, const char *const filename)
150 {
151 char candidate[PATH_MAX];
152 const char *d;
153 bool found = false;
154
155 if (strchr(filename, '/') != NULL)
156 return (is_there(filename));
157
158 while ((d = strsep(&path, ":")) != NULL) {
159 if (*d == '\0')
160 d = ".";
161 if (snprintf(candidate, sizeof (candidate), "%s/%s", d,
162 filename) >= (int)sizeof (candidate))
163 continue;
164 if (is_there(candidate)) {
165 found = true;
166 if (!allpaths)
167 break;
168 }
169 }
170
171 return (found);
172 }
173