1 /* 2 * Copyright � 2002, J�rg Wunsch 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, 17 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 19 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 21 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 22 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 * POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 /* 27 * 4.3BSD UI-compatible whereis(1) utility. Rewritten from scratch 28 * since the original 4.3BSD version suffers legal problems that 29 * prevent it from being redistributed, and since the 4.4BSD version 30 * was pretty inferior in functionality. 31 */ 32 33 #include <sys/types.h> 34 35 __FBSDID("$FreeBSD$"); 36 37 #include <sys/stat.h> 38 #include <sys/sysctl.h> 39 40 #include <dirent.h> 41 #include <err.h> 42 #include <errno.h> 43 #include <regex.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <sysexits.h> 48 #include <unistd.h> 49 50 #include "pathnames.h" 51 52 typedef const char *ccharp; 53 54 int opt_b, opt_m, opt_q, opt_s, opt_u, opt_x; 55 ccharp *bindirs, *mandirs, *sourcedirs; 56 char **query; 57 58 const char *sourcepath = PATH_SOURCES; 59 60 char *colonify(ccharp *); 61 int contains(ccharp *, const char *); 62 void decolonify(char *, ccharp **, int *); 63 void defaults(void); 64 void scanopts(int, char **); 65 void usage(void); 66 67 /* 68 * Throughout this program, a number of strings are dynamically 69 * allocated but never freed. Their memory is written to when 70 * splitting the strings into string lists which will later be 71 * processed. Since it's important that those string lists remain 72 * valid even after the functions allocating the memory returned, 73 * those functions cannot free them. They could be freed only at end 74 * of main(), which is pretty pointless anyway. 75 * 76 * The overall amount of memory to be allocated for processing the 77 * strings is not expected to exceed a few kilobytes. For that 78 * reason, allocation can usually always be assumed to succeed (within 79 * a virtual memory environment), thus we simply bail out using 80 * abort(3) in case of an allocation failure. 81 */ 82 83 void 84 usage(void) 85 { 86 errx(EX_USAGE, 87 "usage: whereis [-bmqsux] [-BMS dir... -f] name ..."); 88 } 89 90 /* 91 * Scan options passed to program. 92 * 93 * Note that the -B/-M/-S options expect a list of directory 94 * names that must be terminated with -f. 95 */ 96 void 97 scanopts(int argc, char **argv) 98 { 99 int c, i, opt_f; 100 ccharp **dirlist; 101 102 opt_f = 0; 103 while ((c = getopt(argc, argv, "BMSbfmqsux")) != -1) 104 switch (c) { 105 case 'B': 106 dirlist = &bindirs; 107 goto dolist; 108 109 case 'M': 110 dirlist = &mandirs; 111 goto dolist; 112 113 case 'S': 114 dirlist = &sourcedirs; 115 dolist: 116 i = 0; 117 while (optind < argc && 118 strcmp(argv[optind], "-f") != 0 && 119 strcmp(argv[optind], "-B") != 0 && 120 strcmp(argv[optind], "-M") != 0 && 121 strcmp(argv[optind], "-S") != 0) { 122 *dirlist = realloc(*dirlist, 123 (i + 2) * sizeof(char *)); 124 if (*dirlist == NULL) 125 abort(); 126 (*dirlist)[i] = argv[optind]; 127 i++; 128 optind++; 129 } 130 (*dirlist)[i] = NULL; 131 break; 132 133 case 'b': 134 opt_b = 1; 135 break; 136 137 case 'f': 138 goto breakout; 139 140 case 'm': 141 opt_m = 1; 142 break; 143 144 case 'q': 145 opt_q = 1; 146 break; 147 148 case 's': 149 opt_s = 1; 150 break; 151 152 case 'u': 153 opt_u = 1; 154 break; 155 156 case 'x': 157 opt_x = 1; 158 break; 159 160 default: 161 usage(); 162 } 163 breakout: 164 if (optind == argc) 165 usage(); 166 query = argv + optind; 167 } 168 169 /* 170 * Find out whether string `s' is contained in list `cpp'. 171 */ 172 int 173 contains(ccharp *cpp, const char *s) 174 { 175 ccharp cp; 176 177 if (cpp == NULL) 178 return (0); 179 180 while ((cp = *cpp) != NULL) { 181 if (strcmp(cp, s) == 0) 182 return (1); 183 cpp++; 184 } 185 return (0); 186 } 187 188 /* 189 * Split string `s' at colons, and pass it to the string list pointed 190 * to by `cppp' (which has `*ip' elements). Note that the original 191 * string is modified by replacing the colon with a NUL byte. The 192 * partial string is only added if it has a length greater than 0, and 193 * if it's not already contained in the string list. 194 */ 195 void 196 decolonify(char *s, ccharp **cppp, int *ip) 197 { 198 char *cp; 199 200 while ((cp = strchr(s, ':')), *s != '\0') { 201 if (cp) 202 *cp = '\0'; 203 if (strlen(s) && !contains(*cppp, s)) { 204 *cppp = realloc(*cppp, (*ip + 2) * sizeof(char *)); 205 if (cppp == NULL) 206 abort(); 207 (*cppp)[*ip] = s; 208 (*cppp)[*ip + 1] = NULL; 209 (*ip)++; 210 } 211 if (cp) 212 s = cp + 1; 213 else 214 break; 215 } 216 } 217 218 /* 219 * Join string list `cpp' into a colon-separated string. 220 */ 221 char * 222 colonify(ccharp *cpp) 223 { 224 size_t s; 225 char *cp; 226 int i; 227 228 if (cpp == NULL) 229 return (0); 230 231 for (s = 0, i = 0; cpp[i] != NULL; i++) 232 s += strlen(cpp[i]) + 1; 233 if ((cp = malloc(s + 1)) == NULL) 234 abort(); 235 for (i = 0, *cp = '\0'; cpp[i] != NULL; i++) { 236 strcat(cp, cpp[i]); 237 strcat(cp, ":"); 238 } 239 cp[s - 1] = '\0'; /* eliminate last colon */ 240 241 return (cp); 242 } 243 244 /* 245 * Provide defaults for all options and directory lists. 246 */ 247 void 248 defaults(void) 249 { 250 size_t s; 251 char *b, buf[BUFSIZ], *cp; 252 int nele; 253 FILE *p; 254 DIR *dir; 255 struct stat sb; 256 struct dirent *dirp; 257 258 /* default to -bms if none has been specified */ 259 if (!opt_b && !opt_m && !opt_s) 260 opt_b = opt_m = opt_s = 1; 261 262 /* -b defaults to default path + /usr/libexec + 263 * /usr/games + user's path */ 264 if (!bindirs) { 265 if (sysctlbyname("user.cs_path", (void *)NULL, &s, 266 (void *)NULL, 0) == -1) 267 err(EX_OSERR, "sysctlbyname(\"user.cs_path\")"); 268 if ((b = malloc(s + 1)) == NULL) 269 abort(); 270 if (sysctlbyname("user.cs_path", b, &s, (void *)NULL, 0) == -1) 271 err(EX_OSERR, "sysctlbyname(\"user.cs_path\")"); 272 nele = 0; 273 decolonify(b, &bindirs, &nele); 274 bindirs = realloc(bindirs, (nele + 3) * sizeof(char *)); 275 if (bindirs == NULL) 276 abort(); 277 bindirs[nele++] = PATH_LIBEXEC; 278 bindirs[nele++] = PATH_GAMES; 279 bindirs[nele] = NULL; 280 if ((cp = getenv("PATH")) != NULL) { 281 /* don't destroy the original environment... */ 282 if ((b = malloc(strlen(cp) + 1)) == NULL) 283 abort(); 284 strcpy(b, cp); 285 decolonify(b, &bindirs, &nele); 286 } 287 } 288 289 /* -m defaults to $(manpath) */ 290 if (!mandirs) { 291 if ((p = popen(MANPATHCMD, "r")) == NULL) 292 err(EX_OSERR, "cannot execute manpath command"); 293 if (fgets(buf, BUFSIZ - 1, p) == NULL || 294 pclose(p)) 295 err(EX_OSERR, "error processing manpath results"); 296 if ((b = strchr(buf, '\n')) != NULL) 297 *b = '\0'; 298 if ((b = malloc(strlen(buf) + 1)) == NULL) 299 abort(); 300 strcpy(b, buf); 301 nele = 0; 302 decolonify(b, &mandirs, &nele); 303 } 304 305 /* -s defaults to precompiled list, plus subdirs of /usr/ports */ 306 if (!sourcedirs) { 307 if ((b = malloc(strlen(sourcepath) + 1)) == NULL) 308 abort(); 309 strcpy(b, sourcepath); 310 nele = 0; 311 decolonify(b, &sourcedirs, &nele); 312 313 if (stat(PATH_PORTS, &sb) == -1) { 314 if (errno == ENOENT) 315 /* no /usr/ports, we are done */ 316 return; 317 err(EX_OSERR, "stat(" PATH_PORTS ")"); 318 } 319 if ((sb.st_mode & S_IFMT) != S_IFDIR) 320 /* /usr/ports is not a directory, ignore */ 321 return; 322 if (access(PATH_PORTS, R_OK | X_OK) != 0) 323 return; 324 if ((dir = opendir(PATH_PORTS)) == NULL) 325 err(EX_OSERR, "opendir" PATH_PORTS ")"); 326 while ((dirp = readdir(dir)) != NULL) { 327 if (dirp->d_name[0] == '.' || 328 strcmp(dirp->d_name, "CVS") == 0) 329 /* ignore dot entries and CVS subdir */ 330 continue; 331 if ((b = malloc(sizeof PATH_PORTS + 1 + dirp->d_namlen)) 332 == NULL) 333 abort(); 334 strcpy(b, PATH_PORTS); 335 strcat(b, "/"); 336 strcat(b, dirp->d_name); 337 if (stat(b, &sb) == -1 || 338 (sb.st_mode & S_IFMT) != S_IFDIR || 339 access(b, R_OK | X_OK) != 0) { 340 free(b); 341 continue; 342 } 343 sourcedirs = realloc(sourcedirs, 344 (nele + 2) * sizeof(char *)); 345 if (sourcedirs == NULL) 346 abort(); 347 sourcedirs[nele++] = b; 348 sourcedirs[nele] = NULL; 349 } 350 closedir(dir); 351 } 352 } 353 354 int 355 main(int argc, char **argv) 356 { 357 int unusual, i, printed; 358 char *bin, buf[BUFSIZ], *cp, *cp2, *man, *name, *src; 359 ccharp *dp; 360 size_t s; 361 struct stat sb; 362 regex_t re, re2; 363 regmatch_t matches[2]; 364 regoff_t rlen; 365 FILE *p; 366 367 scanopts(argc, argv); 368 defaults(); 369 370 if (mandirs == NULL) 371 opt_m = 0; 372 if (bindirs == NULL) 373 opt_b = 0; 374 if (sourcedirs == NULL) 375 opt_s = 0; 376 if (opt_m + opt_b + opt_s == 0) 377 errx(EX_DATAERR, "no directories to search"); 378 379 if (opt_m) { 380 setenv("MANPATH", colonify(mandirs), 1); 381 if ((i = regcomp(&re, MANWHEREISMATCH, REG_EXTENDED)) != 0) { 382 regerror(i, &re, buf, BUFSIZ - 1); 383 errx(EX_UNAVAILABLE, "regcomp(%s) failed: %s", 384 MANWHEREISMATCH, buf); 385 } 386 } 387 388 for (; (name = *query) != NULL; query++) { 389 /* strip leading path name component */ 390 if ((cp = strrchr(name, '/')) != NULL) 391 name = cp + 1; 392 /* strip SCCS or RCS suffix/prefix */ 393 if (strlen(name) > 2 && strncmp(name, "s.", 2) == 0) 394 name += 2; 395 if ((s = strlen(name)) > 2 && strcmp(name + s - 2, ",v") == 0) 396 name[s - 2] = '\0'; 397 /* compression suffix */ 398 s = strlen(name); 399 if (s > 2 && 400 (strcmp(name + s - 2, ".z") == 0 || 401 strcmp(name + s - 2, ".Z") == 0)) 402 name[s - 2] = '\0'; 403 else if (s > 3 && 404 strcmp(name + s - 3, ".gz") == 0) 405 name[s - 3] = '\0'; 406 else if (s > 4 && 407 strcmp(name + s - 4, ".bz2") == 0) 408 name[s - 4] = '\0'; 409 410 unusual = 0; 411 bin = man = src = NULL; 412 s = strlen(name); 413 414 if (opt_b) { 415 /* 416 * Binaries have to match exactly, and must be regular 417 * executable files. 418 */ 419 unusual++; 420 for (dp = bindirs; *dp != NULL; dp++) { 421 cp = malloc(strlen(*dp) + 1 + s + 1); 422 if (cp == NULL) 423 abort(); 424 strcpy(cp, *dp); 425 strcat(cp, "/"); 426 strcat(cp, name); 427 if (stat(cp, &sb) == 0 && 428 (sb.st_mode & S_IFMT) == S_IFREG && 429 (sb.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) 430 != 0) { 431 unusual--; 432 bin = cp; 433 break; 434 } 435 free(cp); 436 } 437 } 438 439 if (opt_m) { 440 /* 441 * Ask the man command to perform the search for us. 442 */ 443 unusual++; 444 cp = malloc(sizeof MANWHEREISCMD - 2 + s); 445 if (cp == NULL) 446 abort(); 447 sprintf(cp, MANWHEREISCMD, name); 448 if ((p = popen(cp, "r")) != NULL && 449 fgets(buf, BUFSIZ - 1, p) != NULL && 450 pclose(p) == 0) { 451 unusual--; 452 if ((cp2 = strchr(buf, '\n')) != NULL) 453 *cp2 = '\0'; 454 if (regexec(&re, buf, 2, matches, 0) == 0 && 455 (rlen = matches[1].rm_eo - matches[1].rm_so) 456 > 0) { 457 /* 458 * man -w found compressed 459 * page, need to pick up 460 * source page name. 461 */ 462 cp2 = malloc(rlen + 1); 463 if (cp2 == NULL) 464 abort(); 465 memcpy(cp2, buf + matches[1].rm_so, 466 rlen); 467 cp2[rlen] = '\0'; 468 man = cp2; 469 } else { 470 /* 471 * man -w found plain source 472 * page, use it. 473 */ 474 s = strlen(buf); 475 cp2 = malloc(s + 1); 476 if (cp2 == NULL) 477 abort(); 478 strcpy(cp2, buf); 479 man = cp2; 480 } 481 } 482 free(cp); 483 } 484 485 if (opt_s) { 486 /* 487 * Sources match if a subdir with the exact 488 * name is found. 489 */ 490 unusual++; 491 for (dp = sourcedirs; *dp != NULL; dp++) { 492 cp = malloc(strlen(*dp) + 1 + s + 1); 493 if (cp == NULL) 494 abort(); 495 strcpy(cp, *dp); 496 strcat(cp, "/"); 497 strcat(cp, name); 498 if (stat(cp, &sb) == 0 && 499 (sb.st_mode & S_IFMT) == S_IFDIR) { 500 unusual--; 501 src = cp; 502 break; 503 } 504 free(cp); 505 } 506 /* 507 * If still not found, ask locate to search it 508 * for us. This will find sources for things 509 * like lpr that are well hidden in the 510 * /usr/src tree, but takes a lot longer. 511 * Thus, option -x (`expensive') prevents this 512 * search. 513 * 514 * Do only match locate output that starts 515 * with one of our source directories, and at 516 * least one further level of subdirectories. 517 */ 518 if (opt_x || src) 519 goto done_sources; 520 521 cp = malloc(sizeof LOCATECMD - 2 + s); 522 if (cp == NULL) 523 abort(); 524 sprintf(cp, LOCATECMD, name); 525 if ((p = popen(cp, "r")) == NULL) 526 goto done_sources; 527 while (src == NULL && 528 (fgets(buf, BUFSIZ - 1, p)) != NULL) { 529 if ((cp2 = strchr(buf, '\n')) != NULL) 530 *cp2 = '\0'; 531 for (dp = sourcedirs; 532 src == NULL && *dp != NULL; 533 dp++) { 534 cp2 = malloc(strlen(*dp) + 9); 535 if (cp2 == NULL) 536 abort(); 537 strcpy(cp2, "^"); 538 strcat(cp2, *dp); 539 strcat(cp2, "/[^/]+/"); 540 if ((i = regcomp(&re2, cp2, 541 REG_EXTENDED|REG_NOSUB)) 542 != 0) { 543 regerror(i, &re, buf, 544 BUFSIZ - 1); 545 errx(EX_UNAVAILABLE, 546 "regcomp(%s) failed: %s", 547 cp2, buf); 548 } 549 free(cp2); 550 if (regexec(&re2, buf, 0, 551 (regmatch_t *)NULL, 0) 552 == 0) { 553 unusual--; 554 src = buf; 555 } 556 regfree(&re2); 557 } 558 } 559 pclose(p); 560 free(cp); 561 } 562 done_sources: 563 564 if (opt_u && !unusual) 565 continue; 566 567 printed = 0; 568 if (!opt_q) { 569 printf("%s:", name); 570 printed++; 571 } 572 if (bin) { 573 if (printed++) 574 putchar(' '); 575 fputs(bin, stdout); 576 } 577 if (man) { 578 if (printed++) 579 putchar(' '); 580 fputs(man, stdout); 581 } 582 if (src) { 583 if (printed++) 584 putchar(' '); 585 fputs(src, stdout); 586 } 587 if (printed) 588 putchar('\n'); 589 } 590 591 if (opt_m) 592 regfree(&re); 593 594 return (0); 595 } 596