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 }