/ src / which.c
which.c
  1  /*-
  2   * Copyright (c) 2011-2025 Baptiste Daroussin <bapt@FreeBSD.org>
  3   * Copyright (c) 2011-2012 Julien Laffaye <jlaffaye@FreeBSD.org>
  4   * Copyright (c) 2011-2012 Marin Atanasov Nikolov <dnaeon@gmail.com>
  5   * Copyright (c) 2014 Matthew Seaman <matthew@FreeBSD.org>
  6   *
  7   * SPDX-License-Identifier: BSD-2-Clause
  8   */
  9  
 10  #include <sys/param.h>
 11  #include <sys/stat.h>
 12  
 13  #include <err.h>
 14  #include <getopt.h>
 15  #include <stdio.h>
 16  #include <string.h>
 17  #include <unistd.h>
 18  #include <fnmatch.h>
 19  
 20  #include <pkg.h>
 21  #include "pkgcli.h"
 22  #include <string.h>
 23  #include <xmalloc.h>
 24  
 25  void
 26  usage_which(void)
 27  {
 28  	fprintf(stderr, "Usage: pkg which [-mqgop] <file>\n\n");
 29  	fprintf(stderr, "For more information see 'pkg help which'.\n");
 30  }
 31  
 32  static bool is_there(char *);
 33  int get_match(char **, char **, char *);
 34  
 35  static bool
 36  already_in_list(charv_t *list, const char *pattern)
 37  {
 38  	vec_foreach(*list, i) {
 39  		if (STREQ(list->d[i], pattern))
 40  			return (true);
 41  	}
 42  
 43  	return (false);
 44  }
 45  
 46  int
 47  exec_which(int argc, char **argv)
 48  {
 49  	struct pkgdb	*db = NULL;
 50  	struct pkgdb_it	*it = NULL;
 51  	struct pkg	*pkg = NULL;
 52  	struct pkg_file *file = NULL;
 53  	char		 pathabs[MAXPATHLEN];
 54  	char		*p, *path, *match, *savedpath;
 55  	int		 retcode = EXIT_FAILURE;
 56  	int		 ch, res, pathlen = 0;
 57  	bool		 orig = false;
 58  	bool		 glob = false;
 59  	bool		 search = false;
 60  	bool		 search_s = false;
 61  	bool		 show_match = false;
 62  	charv_t		 patterns = vec_init();
 63  
 64  	struct option longopts[] = {
 65  		{ "glob",		no_argument,	NULL,	'g' },
 66  		{ "origin",		no_argument,	NULL,	'o' },
 67  		{ "path-search",	no_argument,	NULL,	'p' },
 68  		{ "quiet",		no_argument,	NULL,	'q' },
 69                  { "show-match",         no_argument,    NULL,   'm' },
 70  		{ NULL,			0,		NULL,	0   },
 71  	};
 72  
 73  	path = NULL;
 74  
 75  	while ((ch = getopt_long(argc, argv, "+gopqm", longopts, NULL)) != -1) {
 76  		switch (ch) {
 77  		case 'g':
 78  			glob = true;
 79  			break;
 80  		case 'o':
 81  			orig = true;
 82  			break;
 83  		case 'p':
 84  			search_s = true;
 85  			break;
 86  		case 'q':
 87  			quiet = true;
 88  			break;
 89  		case 'm':
 90  			show_match = true;
 91  			break;
 92  		default:
 93  			usage_which();
 94  			return (EXIT_FAILURE);
 95  		}
 96  	}
 97  
 98  	argc -= optind;
 99  	argv += optind;
100  
101  	if (argc < 1) {
102  		usage_which();
103  		return (EXIT_FAILURE);
104  	}
105  
106  	if (pkgdb_open(&db, PKGDB_DEFAULT_READONLY) != EPKG_OK) {
107  		return (EXIT_FAILURE);
108  	}
109  
110  	if (pkgdb_obtain_lock(db, PKGDB_LOCK_READONLY) != EPKG_OK) {
111  		pkgdb_close(db);
112  		warnx("Cannot get a read lock on a database, it is locked by another process");
113  		return (EXIT_FAILURE);
114  	}
115  
116  	if (search_s) {
117  		if ((path = getenv("PATH")) == NULL) {
118  			printf("$PATH is not set, falling back to non-search behaviour\n");
119  			search_s = false;
120  		} else {
121  			pathlen = strlen(path) + 1;
122  		}
123  	}
124  
125  	while (argc >= 1) {
126  		retcode = EXIT_FAILURE;
127  		if (search_s) {
128  			if ((argv[0][0] == '.') || (argv[0][0] == '/')) {
129  				search = false;
130  			} else {
131  				search = true;
132  
133  				if (strlen(argv[0]) >= FILENAME_MAX) {
134  					retcode = EXIT_FAILURE;
135  					goto cleanup;
136  				}
137  
138  				p = xmalloc(pathlen);
139  				strlcpy(p, path, pathlen);
140  
141  				match = NULL;
142  				savedpath=p;
143  				for (;;) {
144  					res = get_match(&match, &p, argv[0]);
145  					if (res == EXIT_FAILURE) {
146  						printf("%s was not found in PATH, falling back to non-search behaviour\n", argv[0]);
147  						search = false;
148  					} else {
149  						pkg_absolutepath(match, pathabs, sizeof(pathabs), false);
150  						/* ensure not not append twice an entry if PATH is messy */
151  						if (already_in_list(&patterns, pathabs))
152  							continue;
153  						vec_push(&patterns, xstrdup(pathabs));
154  						free(match);
155  					}
156  
157  					if (p == NULL)
158  						break;
159  				}
160  				free(savedpath);
161  			}
162  		}
163  
164  		if (!glob && !search) {
165  			pkg_absolutepath(argv[0], pathabs, sizeof(pathabs), false);
166  			vec_push(&patterns, xstrdup(pathabs));
167  		} else if (!search) {
168  			if (strlcpy(pathabs, argv[0], sizeof(pathabs)) >= sizeof(pathabs)) {
169  				retcode = EXIT_FAILURE;
170  				goto cleanup;
171                          }
172  			vec_push(&patterns, xstrdup(pathabs));
173  		}
174  
175  
176  		vec_foreach(patterns, i) {
177  			if ((it = pkgdb_query_which(db, patterns.d[i], glob)) == NULL) {
178  				retcode = EXIT_FAILURE;
179  				goto cleanup;
180  			}
181  
182  			pkg = NULL;
183  			while (pkgdb_it_next(it, &pkg, (glob && show_match) ? PKG_LOAD_FILES : PKG_LOAD_BASIC) == EPKG_OK) {
184  				retcode = EXIT_SUCCESS;
185  				if (quiet && orig && !show_match)
186  					pkg_printf("%o\n", pkg);
187  				else if (quiet && !orig && !show_match)
188  					pkg_printf("%n-%v\n", pkg, pkg);
189  				else if (!quiet && orig && !show_match)
190  					pkg_printf("%S was installed by package %o\n", patterns.d[i], pkg);
191  				else if (!quiet && !orig && !show_match)
192  					pkg_printf("%S was installed by package %n-%v\n", patterns.d[i], pkg, pkg);
193  				else if (glob && show_match) {
194  					if (!quiet)
195  						pkg_printf("%S was glob searched and found in package %n-%v\n", patterns.d[i], pkg, pkg, pkg);
196  					while(pkg_files(pkg, &file) == EPKG_OK) {
197  						pkg_asprintf(&match, "%Fn", file);
198  						if (match == NULL)
199  							err(EXIT_FAILURE, "pkg_asprintf");
200  						if(!fnmatch(patterns.d[i], match, 0))
201  							printf("%s\n", match);
202  						free(match);
203  					}
204  				}
205  			}
206  			if (retcode != EXIT_SUCCESS && !quiet)
207  				printf("%s was not found in the database\n", patterns.d[i]);
208  
209  			pkg_free(pkg);
210  			pkgdb_it_free(it);
211  
212  		}
213  		vec_free_and_free(&patterns, free);
214  
215  		argc--;
216  		argv++;
217  
218  	}
219  
220  	cleanup:
221  		pkgdb_release_lock(db, PKGDB_LOCK_READONLY);
222  		pkgdb_close(db);
223  
224  	return (retcode);
225  }
226  
227  
228  static bool
229  is_there(char *candidate)
230  {
231  	return (access(candidate, F_OK) == 0);
232  }
233  
234  int
235  get_match(char **pathabs, char **path, char *filename)
236  {
237  	char candidate[PATH_MAX];
238  	const char *d;
239  	int len;
240  
241  	while ((d = strsep(path, ":")) != NULL) {
242  		if (snprintf(candidate, sizeof(candidate), "%s/%s", d,
243  		    filename) >= (int)sizeof(candidate))
244  			continue;
245  		if (is_there(candidate)) {
246  			*pathabs = xstrdup(candidate);
247  			return (EXIT_SUCCESS);
248  		}
249  	}
250  	return (EXIT_FAILURE);
251  }