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 <errno.h>
21 #include <getopt.h>
22 #include <limits.h>
23 #include <sys/stat.h>
24 #include <unistd.h>
25 
26 #include <algorithm>
27 #include <cstdlib>
28 #include <cstring>
29 #include <iostream>
30 #include <list>
31 #include <map>
32 #include <regex>
33 #include <sstream>
34 #include <string>
35 #include <vector>
36 
37 using std::cerr;
38 using std::cin;
39 using std::copy;
40 using std::cout;
41 using std::endl;
42 using std::list;
43 using std::map;
44 using std::regex;
45 using std::string;
46 using std::stringstream;
47 using std::vector;
48 
49 int processOpts(int argc, char *argv[]);
50 void usage();
51 void version();
52 void split(const std::string &s, char delim, vector<string> &elems);
53 int findPaths(string filename);
54 void readAliasesAndFunctions();
55 void setupFromEnv();
56 
57 enum tty_opts
58 {
59  opt_skip_dot,
60  opt_show_dot,
61  opt_skip_tilde,
62  opt_show_tilde,
63  opt_tty_only,
64 };
65 
67 {
68  public:
70  : all(0), readAliases(0), readFunctions(0), showDot(0), skipDot(0),
71  showTilde(0), skipTilde(0){};
72  int all;
73  int readAliases;
74  int readFunctions;
75  int showDot;
76  int skipDot;
77  int showTilde;
78  int skipTilde;
79 } static opts;
80 
81 static list<string> aliases, functions;
82 static string home, cwd;
83 
84 int processOpts(int argc, char *argv[])
85 {
86  int c, longOption, ttyOnly = 0;
87  struct option long_options[] = {
88  {"all", no_argument, &opts.all, 1},
89  {"read-alias", no_argument, &opts.readAliases, 1},
90  {"skip-alias", no_argument, &opts.readAliases, 0},
91  {"read-functions", no_argument, &opts.readFunctions, 1},
92  {"skip-functions", no_argument, &opts.readFunctions, 0},
93  {"skip-dot", no_argument, &longOption, opt_skip_dot},
94  {"show-dot", no_argument, &longOption, opt_show_dot},
95  {"skip-tilde", no_argument, &longOption, opt_skip_tilde},
96  {"show-tilde", no_argument, &longOption, opt_show_tilde},
97  {"tty-only", no_argument, &longOption, opt_tty_only},
98  {"version", no_argument, 0, 'v'},
99  {"help", no_argument, 0, 'h'},
100  {0, 0, 0, 0}};
101 
102  while (1)
103  {
104  c = getopt_long(argc, argv, "aivVh", long_options, NULL);
105 
106  if (c == -1)
107  break;
108 
109  switch (c)
110  {
111  case 0:
112  switch (longOption)
113  {
114  case opt_skip_dot:
115  opts.skipDot = !ttyOnly;
116  break;
117  case opt_show_dot:
118  opts.showDot = !ttyOnly;
119  break;
120  case opt_skip_tilde:
121  opts.skipTilde = !ttyOnly;
122  break;
123  case opt_show_tilde:
124  opts.showTilde = !ttyOnly;
125  break;
126  case opt_tty_only:
127  ttyOnly = !isatty(1);
128  break;
129  }
130  break;
131  case 'a':
132  opts.all = 1;
133  break;
134  case 'i':
135  opts.readAliases = 1;
136  break;
137  case 'v':
138  case 'V':
139  version();
140  return 1;
141  case 'h':
142  usage();
143  return 1;
144  }
145  }
146 
147  return 0;
148 }
149 
150 void usage()
151 {
152  cout << "Usage: which [options] [--] COMMAND [...]" << endl;
153  cout << "Write the full path of COMMAND(s) to standard output." << endl;
154  cout << endl;
155  cout << " --version, -[vV] Print version and exit successfully." << endl;
156  cout << " --help, Print this help and exit successfully." << endl;
157  cout << " --skip-dot Skip directories in PATH that start with a dot."
158  << endl;
159  cout << " --skip-tilde Skip directories in PATH that start with a "
160  "tilde."
161  << endl;
162  cout << " --show-dot Don't expand a dot to current directory in "
163  "output."
164  << endl;
165  cout << " --show-tilde Output a tilde for HOME directory for non-root."
166  << endl;
167  cout << " --tty-only Stop processing options on the right if not on "
168  "tty."
169  << endl;
170  cout << " --all, -a Print all matches in PATH, not just the first"
171  << endl;
172  cout << " --read-alias, -i Read list of aliases from stdin." << endl;
173  cout << " --skip-alias Ignore option --read-alias; don't read stdin."
174  << endl;
175  cout << " --read-functions Read shell functions from stdin." << endl;
176  cout << " --skip-functions Ignore option --read-functions; don't read "
177  "stdin."
178  << endl;
179  cout << endl;
180  cout << "Recommended use is to write the output of (alias; declare -f) to "
181  "standard"
182  << endl;
183  cout << "input, so that which can show aliases and shell functions. See "
184  "which(1) fo"
185  << endl;
186  cout << "examples." << endl;
187  cout << endl;
188  cout << "If the options --read-alias and/or --read-functions are specified "
189  "then the"
190  << endl;
191  cout << "output can be a full alias or function definition, optionally "
192  "followed by"
193  << endl;
194  cout << "the full path of each command used inside of those." << endl;
195 }
196 
197 void version()
198 {
199  cout << "which v1.0, Copyright (C) 2014 Nathan Hoad" << endl;
200 }
201 
202 void split(const std::string &s, char delim, vector<string> &elems)
203 {
204  stringstream ss(s);
205  string item;
206  while (std::getline(ss, item, delim))
207  {
208  elems.push_back(item);
209  }
210 }
211 
212 int findPaths(string filename)
213 {
214  char *path;
215  vector<string> dirs;
216  int failed;
217 
218  for (auto &alias : aliases)
219  {
220  if (alias.find(filename) == 0)
221  {
222  cout << alias << endl;
223  if (opts.all == 0)
224  return 0;
225  }
226  }
227 
228  for (auto &function : functions)
229  {
230  if (function.find(filename) == 0)
231  {
232  cout << function << endl;
233  if (opts.all == 0)
234  return 0;
235  }
236  }
237 
238  path = getenv("PATH");
239  failed = 1;
240  split(path ? path : "", ':', dirs);
241 
242  for (auto &dir : dirs)
243  {
244  // clean up paths so we don't render double slashes
245  while (dir.rbegin() != dir.rend() && *dir.rbegin() == '/')
246  dir.pop_back();
247 
248  string fullpath = dir + "/" + filename;
249  string printed_path;
250 
251  struct stat sb;
252  if ((stat(fullpath.c_str(), &sb) == 0) && sb.st_mode & S_IXUSR)
253  {
254  if (opts.showDot && dir[0] == '.' &&
255  fullpath.compare(0, cwd.size(), cwd) == 0)
256  {
257  printed_path = string("./") + filename;
258  }
259  else
260  {
261  if (opts.showTilde && !home.empty() &&
262  fullpath.compare(0, home.size(), home) == 0)
263  {
264  fullpath = fullpath.substr(
265  home.size(), fullpath.size() - home.size());
266  printed_path = string("~") + fullpath;
267  }
268  else
269  {
270  printed_path = fullpath;
271  }
272  }
273 
274  if (printed_path.size())
275  {
276  if (opts.skipTilde && printed_path[0] == '~' && geteuid() != 0)
277  continue;
278  else if (opts.skipDot && printed_path[0] == '.')
279  continue;
280  cout << printed_path << endl;
281  failed = 0;
282  if (opts.all == 0)
283  break;
284  }
285  }
286  }
287 
288  return failed;
289 }
290 
291 void readAliasesAndFunctions()
292 {
293  if (!opts.readFunctions && !opts.readAliases)
294  return;
295 
296  if (isatty(0))
297  cerr << "which: warning: stdin is a tty" << endl;
298 
299  string line, function;
300 
301  while (std::getline(cin, line, '\n'))
302  {
303  if (opts.readAliases && regex_match(line, regex("^.+=.+?$")))
304  aliases.push_back(line);
305  else if (opts.readFunctions)
306  {
307  function = line + "\n";
308  while (std::getline(cin, line, '\n') && line != "}")
309  {
310  function += line + "\n";
311  }
312 
313  if (line == "}")
314  {
315  function += "}\n";
316  functions.push_back(function);
317  function = "";
318  }
319  }
320  else
321  {
322  cerr << "which: line doesn't appear to be an alias and functions "
323  "are disabled "
324  << line << endl;
325  exit(EXIT_FAILURE);
326  }
327  }
328 }
329 
330 void setupFromEnv()
331 {
332  char *_home;
333  char _cwd[PATH_MAX];
334 
335  _home = getenv("HOME");
336  home = _home ? _home : "";
337 
338  memset(_cwd, 0, PATH_MAX);
339  if (getcwd(_cwd, PATH_MAX) == NULL)
340  {
341  cerr << "which: unable to retrieve current working directory "
342  << strerror(errno) << endl;
343  exit(EXIT_FAILURE);
344  }
345 
346  cwd = _cwd;
347 }
348 
349 int main(int argc, char *argv[])
350 {
351  int status;
352 
353  if (processOpts(argc, argv) != 0)
354  return EXIT_FAILURE;
355 
356  setupFromEnv();
357  readAliasesAndFunctions();
358 
359  status = EXIT_SUCCESS;
360 
361  for (int i = optind; i < argc; i++)
362  {
363  if (findPaths(argv[i]) != 0)
364  {
365  cout << argv[i] << " not found" << endl;
366  status = EXIT_FAILURE;
367  }
368  }
369 
370  return status;
371 }