The Pedigree Project 0.1
user/applications/which/main.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <algorithm>
21#include <cstdlib>
22#include <cstring>
23#include <errno.h>
24#include <getopt.h>
25#include <iostream>
26#include <limits.h>
27#include <list>
28#include <map>
29#include <regex>
30#include <sstream>
31#include <string>
32#include <unistd.h>
33#include <vector>
34
35#include <sys/stat.h>
36
37using std::cerr;
38using std::cin;
39using std::copy;
40using std::cout;
41using std::endl;
42using std::list;
43using std::map;
44using std::regex;
45using std::string;
46using std::stringstream;
47using std::vector;
48
49int processOpts(int argc, char* argv[]);
50void usage();
51void version();
52void split(const std::string& s, char delim, vector<string>& elems);
53int findPaths(string filename);
54void readAliasesAndFunctions();
55void setupFromEnv();
56
57enum tty_opts {
58 opt_skip_dot,
59 opt_show_dot,
60 opt_skip_tilde,
61 opt_show_tilde,
62 opt_tty_only,
63};
64
66 public:
68 : all(0),
69 readAliases(0),
70 readFunctions(0),
71 showDot(0),
72 skipDot(0),
73 showTilde(0),
74 skipTilde(0) {};
75 int all;
76 int readAliases;
77 int readFunctions;
78 int showDot;
79 int skipDot;
80 int showTilde;
81 int skipTilde;
82} static opts;
83
84static list<string> aliases, functions;
85static string home, cwd;
86
87int processOpts(int argc, char* argv[]) {
88 int c, longOption, ttyOnly = 0;
89 struct option long_options[] = {{"all", no_argument, &opts.all, 1},
90 {"read-alias", no_argument, &opts.readAliases, 1},
91 {"skip-alias", no_argument, &opts.readAliases, 0},
92 {"read-functions", no_argument, &opts.readFunctions, 1},
93 {"skip-functions", no_argument, &opts.readFunctions, 0},
94 {"skip-dot", no_argument, &longOption, opt_skip_dot},
95 {"show-dot", no_argument, &longOption, opt_show_dot},
96 {"skip-tilde", no_argument, &longOption, opt_skip_tilde},
97 {"show-tilde", no_argument, &longOption, opt_show_tilde},
98 {"tty-only", no_argument, &longOption, opt_tty_only},
99 {"version", no_argument, 0, 'v'},
100 {"help", no_argument, 0, 'h'},
101 {0, 0, 0, 0}};
102
103 while (1) {
104 c = getopt_long(argc, argv, "aivVh", long_options, NULL);
105
106 if (c == -1)
107 break;
108
109 switch (c) {
110 case 0:
111 switch (longOption) {
112 case opt_skip_dot:
113 opts.skipDot = !ttyOnly;
114 break;
115 case opt_show_dot:
116 opts.showDot = !ttyOnly;
117 break;
118 case opt_skip_tilde:
119 opts.skipTilde = !ttyOnly;
120 break;
121 case opt_show_tilde:
122 opts.showTilde = !ttyOnly;
123 break;
124 case opt_tty_only:
125 ttyOnly = !isatty(1);
126 break;
127 }
128 break;
129 case 'a':
130 opts.all = 1;
131 break;
132 case 'i':
133 opts.readAliases = 1;
134 break;
135 case 'v':
136 case 'V':
137 version();
138 return 1;
139 case 'h':
140 usage();
141 return 1;
142 }
143 }
144
145 return 0;
146}
147
148void usage() {
149 cout << "Usage: which [options] [--] COMMAND [...]" << endl;
150 cout << "Write the full path of COMMAND(s) to standard output." << endl;
151 cout << endl;
152 cout << " --version, -[vV] Print version and exit successfully." << endl;
153 cout << " --help, Print this help and exit successfully." << endl;
154 cout << " --skip-dot Skip directories in PATH that start with a dot." << endl;
155 cout << " --skip-tilde Skip directories in PATH that start with a "
156 "tilde."
157 << endl;
158 cout << " --show-dot Don't expand a dot to current directory in "
159 "output."
160 << endl;
161 cout << " --show-tilde Output a tilde for HOME directory for non-root." << endl;
162 cout << " --tty-only Stop processing options on the right if not on "
163 "tty."
164 << endl;
165 cout << " --all, -a Print all matches in PATH, not just the first" << endl;
166 cout << " --read-alias, -i Read list of aliases from stdin." << endl;
167 cout << " --skip-alias Ignore option --read-alias; don't read stdin." << endl;
168 cout << " --read-functions Read shell functions from stdin." << endl;
169 cout << " --skip-functions Ignore option --read-functions; don't read "
170 "stdin."
171 << endl;
172 cout << endl;
173 cout << "Recommended use is to write the output of (alias; declare -f) to "
174 "standard"
175 << endl;
176 cout << "input, so that which can show aliases and shell functions. See "
177 "which(1) fo"
178 << endl;
179 cout << "examples." << endl;
180 cout << endl;
181 cout << "If the options --read-alias and/or --read-functions are specified "
182 "then the"
183 << endl;
184 cout << "output can be a full alias or function definition, optionally "
185 "followed by"
186 << endl;
187 cout << "the full path of each command used inside of those." << endl;
188}
189
190void version() {
191 cout << "which v1.0, Copyright (C) 2014 Nathan Hoad" << endl;
192}
193
194void split(const std::string& s, char delim, vector<string>& elems) {
195 stringstream ss(s);
196 string item;
197 while (std::getline(ss, item, delim)) {
198 elems.push_back(item);
199 }
200}
201
202int findPaths(string filename) {
203 char* path;
204 vector<string> dirs;
205 int failed;
206
207 for (auto& alias : aliases) {
208 if (alias.find(filename) == 0) {
209 cout << alias << endl;
210 if (opts.all == 0)
211 return 0;
212 }
213 }
214
215 for (auto& function : functions) {
216 if (function.find(filename) == 0) {
217 cout << function << endl;
218 if (opts.all == 0)
219 return 0;
220 }
221 }
222
223 path = getenv("PATH");
224 failed = 1;
225 split(path ? path : "", ':', dirs);
226
227 for (auto& dir : dirs) {
228 // clean up paths so we don't render double slashes
229 while (dir.rbegin() != dir.rend() && *dir.rbegin() == '/')
230 dir.pop_back();
231
232 string fullpath = dir + "/" + filename;
233 string printed_path;
234
235 struct stat sb;
236 if ((stat(fullpath.c_str(), &sb) == 0) && sb.st_mode & S_IXUSR) {
237 if (opts.showDot && dir[0] == '.' && fullpath.compare(0, cwd.size(), cwd) == 0) {
238 printed_path = string("./") + filename;
239 } else {
240 if (opts.showTilde && !home.empty() && fullpath.compare(0, home.size(), home) == 0) {
241 fullpath = fullpath.substr(home.size(), fullpath.size() - home.size());
242 printed_path = string("~") + fullpath;
243 } else {
244 printed_path = fullpath;
245 }
246 }
247
248 if (printed_path.size()) {
249 if (opts.skipTilde && printed_path[0] == '~' && geteuid() != 0)
250 continue;
251 else if (opts.skipDot && printed_path[0] == '.')
252 continue;
253 cout << printed_path << endl;
254 failed = 0;
255 if (opts.all == 0)
256 break;
257 }
258 }
259 }
260
261 return failed;
262}
263
264void readAliasesAndFunctions() {
265 if (!opts.readFunctions && !opts.readAliases)
266 return;
267
268 if (isatty(0))
269 cerr << "which: warning: stdin is a tty" << endl;
270
271 string line, function;
272
273 while (std::getline(cin, line, '\n')) {
274 if (opts.readAliases && regex_match(line, regex("^.+=.+?$")))
275 aliases.push_back(line);
276 else if (opts.readFunctions) {
277 function = line + "\n";
278 while (std::getline(cin, line, '\n') && line != "}") {
279 function += line + "\n";
280 }
281
282 if (line == "}") {
283 function += "}\n";
284 functions.push_back(function);
285 function = "";
286 }
287 } else {
288 cerr << "which: line doesn't appear to be an alias and functions "
289 "are disabled "
290 << line << endl;
291 exit(EXIT_FAILURE);
292 }
293 }
294}
295
296void setupFromEnv() {
297 char* _home;
298 char _cwd[PATH_MAX];
299
300 _home = getenv("HOME");
301 home = _home ? _home : "";
302
303 memset(_cwd, 0, PATH_MAX);
304 if (getcwd(_cwd, PATH_MAX) == NULL) {
305 cerr << "which: unable to retrieve current working directory " << strerror(errno) << endl;
306 exit(EXIT_FAILURE);
307 }
308
309 cwd = _cwd;
310}
311
312int main(int argc, char* argv[]) {
313 int status;
314
315 if (processOpts(argc, argv) != 0)
316 return EXIT_FAILURE;
317
318 setupFromEnv();
319 readAliasesAndFunctions();
320
321 status = EXIT_SUCCESS;
322
323 for (int i = optind; i < argc; i++) {
324 if (findPaths(argv[i]) != 0) {
325 cout << argv[i] << " not found" << endl;
326 status = EXIT_FAILURE;
327 }
328 }
329
330 return status;
331}