1 /* $NetBSD: catman.c,v 1.35 2013/07/31 22:37:55 soren Exp $ */ 2 3 /* 4 * Copyright (c) 1998 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * Author: Baldassare Dante Profeta <dante@mclink.it> 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <sys/types.h> 32 #include <sys/queue.h> 33 #include <sys/cdefs.h> 34 #include <sys/param.h> 35 #include <sys/stat.h> 36 #include <sys/wait.h> 37 #include <sys/utsname.h> 38 #include <ctype.h> 39 #include <dirent.h> 40 #include <err.h> 41 #include <errno.h> 42 #include <fnmatch.h> 43 #include <limits.h> 44 #include <libgen.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <unistd.h> 49 #include <glob.h> 50 51 #include "manconf.h" 52 #include "pathnames.h" 53 54 int f_nowhatis = 0; 55 int f_noaction = 0; 56 int f_noformat = 0; 57 int f_ignerr = 0; 58 int f_noprint = 0; 59 int dowhatis = 0; 60 61 TAG *defp; /* pointer to _default list */ 62 63 static void setdefentries(char *, char *, const char *); 64 static void uniquepath(void); 65 static void catman(void); 66 static void scanmandir(const char *, const char *); 67 static int splitentry(char *, char *, size_t, char *, size_t); 68 static void setcatsuffix(char *, const char *, const char *); 69 static void makecat(const char *, const char *, const char *, const char *); 70 static void makewhatis(void); 71 static void dosystem(const char *); 72 __dead static void usage(void); 73 74 75 int 76 main(int argc, char * const *argv) 77 { 78 char *m_path = NULL; 79 char *m_add = NULL; 80 int c; 81 82 while ((c = getopt(argc, argv, "km:M:npsw")) != -1) { 83 switch (c) { 84 case 'k': 85 f_ignerr = 1; 86 break; 87 case 'n': 88 f_nowhatis = 1; 89 break; 90 case 'p': 91 f_noaction = 1; 92 break; 93 case 's': 94 f_noprint = 1; 95 break; 96 case 'w': 97 f_noformat = 1; 98 break; 99 case 'm': 100 m_add = optarg; 101 break; 102 case 'M': 103 m_path = optarg; 104 break; 105 default: 106 usage(); 107 } 108 } 109 110 argc -= optind; 111 argv += optind; 112 113 if (f_noprint && f_noaction) 114 f_noprint = 0; 115 116 if (argc > 1) 117 usage(); 118 119 config(_PATH_MANCONF); 120 setdefentries(m_path, m_add, (argc == 0) ? NULL : argv[argc-1]); 121 uniquepath(); 122 123 if (f_noformat == 0 || f_nowhatis == 0) 124 catman(); 125 if (f_nowhatis == 0 && dowhatis) 126 makewhatis(); 127 128 return(0); 129 } 130 131 static void 132 setdefentries(char *m_path, char *m_add, const char *sections) 133 { 134 TAG *defnewp, *sectnewp, *subp; 135 ENTRY *e_defp, *e_subp; 136 const char *p, *slashp; 137 char *machine; 138 char buf[MAXPATHLEN * 2]; 139 int i; 140 141 /* Get the machine type. */ 142 if ((machine = getenv("MACHINE")) == NULL) { 143 struct utsname utsname; 144 145 if (uname(&utsname) == -1) { 146 perror("uname"); 147 exit(1); 148 } 149 machine = utsname.machine; 150 } 151 152 /* If there's no _default list, create an empty one. */ 153 defp = gettag("_default", 1); 154 subp = gettag("_subdir", 1); 155 if (defp == NULL || subp == NULL) 156 err(1, "malloc"); 157 158 /* 159 * 0: If one or more sections was specified, rewrite _subdir list. 160 */ 161 if (sections != NULL) { 162 if ((sectnewp = gettag("_section_new", 1)) == NULL) 163 err(1, "malloc"); 164 for (p = sections; *p;) { 165 i = snprintf(buf, sizeof(buf), "man%c", *p++); 166 for (; *p && !isdigit((unsigned char)*p) && i < (int)sizeof(buf) - 1; i++) 167 buf[i] = *p++; 168 buf[i] = '\0'; 169 if (addentry(sectnewp, buf, 0) < 0) 170 err(1, "malloc"); 171 } 172 subp = sectnewp; 173 } 174 175 /* 176 * 1: If the user specified a MANPATH variable, or set the -M 177 * option, we replace the _default list with the user's list, 178 * appending the entries in the _subdir list and the machine. 179 */ 180 if (m_path == NULL) 181 m_path = getenv("MANPATH"); 182 if (m_path != NULL) { 183 while ((e_defp = TAILQ_FIRST(&defp->entrylist)) != NULL) { 184 free(e_defp->s); 185 TAILQ_REMOVE(&defp->entrylist, e_defp, q); 186 } 187 for (p = strtok(m_path, ":"); 188 p != NULL; p = strtok(NULL, ":")) { 189 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 190 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 191 if (!strncmp(e_subp->s, "cat", 3)) 192 continue; 193 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 194 p, slashp, e_subp->s, machine); 195 if (addentry(defp, buf, 0) < 0) 196 err(1, "malloc"); 197 } 198 } 199 } 200 201 /* 202 * 2: If the user did not specify MANPATH, -M or a section, rewrite 203 * the _default list to include the _subdir list and the machine. 204 */ 205 if (m_path == NULL) { 206 defp = gettag("_default", 1); 207 defnewp = gettag("_default_new1", 1); 208 if (defp == NULL || defnewp == NULL) 209 err(1, "malloc"); 210 211 TAILQ_FOREACH(e_defp, &defp->entrylist, q) { 212 slashp = 213 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/"; 214 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 215 if (!strncmp(e_subp->s, "cat", 3)) 216 continue; 217 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 218 e_defp->s, slashp, e_subp->s, machine); 219 if (addentry(defnewp, buf, 0) < 0) 220 err(1, "malloc"); 221 } 222 } 223 defp = defnewp; 224 } 225 226 /* 227 * 3: If the user set the -m option, insert the user's list before 228 * whatever list we have, again appending the _subdir list and 229 * the machine. 230 */ 231 if (m_add != NULL) 232 for (p = strtok(m_add, ":"); p != NULL; p = strtok(NULL, ":")) { 233 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 234 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 235 if (!strncmp(e_subp->s, "cat", 3)) 236 continue; 237 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 238 p, slashp, e_subp->s, machine); 239 if (addentry(defp, buf, 1) < 0) 240 err(1, "malloc"); 241 } 242 } 243 } 244 245 /* 246 * Remove entries (directory) which are symbolic links to other entries. 247 * Some examples are showed below: 248 * 1) if /usr/X11 -> /usr/X11R6 then remove all /usr/X11/man entries. 249 * 2) if /usr/local/man -> /usr/share/man then remove all /usr/local/man 250 * entries 251 */ 252 static void 253 uniquepath(void) 254 { 255 TAG *defnewp; 256 ENTRY *e_defp; 257 glob_t manpaths; 258 struct stat st1; 259 struct stat st2; 260 struct stat st3; 261 int len, lnk, gflags; 262 size_t i, j; 263 char path[PATH_MAX], *p; 264 265 gflags = 0; 266 TAILQ_FOREACH(e_defp, &defp->entrylist, q) { 267 glob(e_defp->s, GLOB_BRACE | GLOB_NOSORT | gflags, NULL, 268 &manpaths); 269 gflags = GLOB_APPEND; 270 } 271 272 if ((defnewp = gettag("_default_new2", 1)) == NULL) 273 err(1, "malloc"); 274 275 for (i = 0; i < manpaths.gl_pathc; i++) { 276 lnk = 0; 277 lstat(manpaths.gl_pathv[i], &st1); 278 for (j = 0; j < manpaths.gl_pathc; j++) { 279 if (i != j) { 280 lstat(manpaths.gl_pathv[j], &st2); 281 if (st1.st_ino == st2.st_ino) { 282 strlcpy(path, manpaths.gl_pathv[i], 283 sizeof(path)); 284 for (p = path; *(p+1) != '\0';) { 285 p = dirname(p); 286 lstat(p, &st3); 287 if (S_ISLNK(st3.st_mode)) { 288 lnk = 1; 289 break; 290 } 291 } 292 } else { 293 len = readlink(manpaths.gl_pathv[i], 294 path, sizeof(path) - 1); 295 if (len == -1) 296 continue; 297 path[len] = '\0'; 298 if (!strcmp(path, manpaths.gl_pathv[j])) 299 lnk = 1; 300 } 301 if (lnk) 302 break; 303 } 304 } 305 306 if (!lnk) { 307 if (addentry(defnewp, manpaths.gl_pathv[i], 0) < 0) 308 err(1, "malloc"); 309 } 310 } 311 312 globfree(&manpaths); 313 314 defp = defnewp; 315 } 316 317 static void 318 catman(void) 319 { 320 ENTRY *e_path; 321 const char *mandir; 322 char catdir[PATH_MAX], *cp; 323 324 TAILQ_FOREACH(e_path, &defp->entrylist, q) { 325 mandir = e_path->s; 326 strlcpy(catdir, mandir, sizeof(catdir)); 327 if (!(cp = strstr(catdir, "man/man"))) 328 continue; 329 cp += 4; *cp++ = 'c'; *cp++ = 'a'; *cp = 't'; 330 scanmandir(catdir, mandir); 331 } 332 } 333 334 static void 335 scanmandir(const char *catdir, const char *mandir) 336 { 337 TAG *buildp, *crunchp; 338 ENTRY *e_build, *e_crunch; 339 char manpage[PATH_MAX]; 340 char catpage[PATH_MAX]; 341 char linkname[PATH_MAX]; 342 char buffer[PATH_MAX], *bp; 343 char tmp[PATH_MAX]; 344 char buildsuff[256], buildcmd[256]; 345 char crunchsuff[256], crunchcmd[256]; 346 char match[256]; 347 struct stat manstat; 348 struct stat catstat; 349 struct stat lnkstat; 350 struct dirent *dp; 351 DIR *dirp; 352 int len, error; 353 354 if ((dirp = opendir(mandir)) == 0) { 355 warn("can't open %s", mandir); 356 return; 357 } 358 359 if (stat(catdir, &catstat) < 0) { 360 if (errno != ENOENT) { 361 warn("can't stat %s", catdir); 362 closedir(dirp); 363 return; 364 } 365 if (f_noprint == 0) 366 printf("mkdir %s\n", catdir); 367 if (f_noaction == 0 && mkdir(catdir, 0755) < 0) { 368 warn("can't create %s", catdir); 369 closedir(dirp); 370 return; 371 } 372 } 373 374 while ((dp = readdir(dirp)) != NULL) { 375 if (strcmp(dp->d_name, ".") == 0 || 376 strcmp(dp->d_name, "..") == 0) 377 continue; 378 379 snprintf(manpage, sizeof(manpage), "%s/%s", mandir, dp->d_name); 380 snprintf(catpage, sizeof(catpage), "%s/%s", catdir, dp->d_name); 381 382 e_build = NULL; 383 if ((buildp = gettag("_build", 1)) == NULL) 384 err(1, "malloc"); 385 TAILQ_FOREACH(e_build, &buildp->entrylist, q) { 386 splitentry(e_build->s, buildsuff, sizeof(buildsuff), 387 buildcmd, sizeof(buildcmd)); 388 snprintf(match, sizeof(match), "*%s", 389 buildsuff); 390 if (!fnmatch(match, manpage, 0)) 391 break; 392 } 393 394 if (e_build == NULL) 395 continue; 396 397 e_crunch = NULL; 398 if ((crunchp = gettag("_crunch", 1)) == NULL) 399 err(1, "malloc"); 400 TAILQ_FOREACH(e_crunch, &crunchp->entrylist, q) { 401 splitentry(e_crunch->s, crunchsuff, sizeof(crunchsuff), 402 crunchcmd, sizeof(crunchcmd)); 403 snprintf(match, sizeof(match), "*%s", crunchsuff); 404 if (!fnmatch(match, manpage, 0)) 405 break; 406 } 407 408 if (lstat(manpage, &manstat) <0) { 409 warn("can't stat %s", manpage); 410 continue; 411 } else { 412 if (S_ISLNK(manstat.st_mode)) { 413 strlcpy(buffer, catpage, sizeof(buffer)); 414 strlcpy(linkname, basename(buffer), 415 sizeof(linkname)); 416 len = readlink(manpage, buffer, 417 sizeof(buffer) - 1); 418 if (len == -1) { 419 warn("can't stat read symbolic link %s", 420 manpage); 421 continue; 422 } 423 buffer[len] = '\0'; 424 425 if (strcmp(buffer, basename(buffer)) == 0) { 426 bp = basename(buffer); 427 strlcpy(tmp, manpage, sizeof(tmp)); 428 snprintf(manpage, sizeof(manpage), 429 "%s/%s", dirname(tmp), bp); 430 strlcpy(tmp, catpage, sizeof(tmp)); 431 snprintf(catpage, sizeof(catpage), 432 "%s/%s", dirname(tmp), buffer); 433 } else { 434 *linkname = '\0'; 435 } 436 437 } 438 else 439 *linkname = '\0'; 440 } 441 442 if (!e_crunch) { 443 *crunchsuff = *crunchcmd = '\0'; 444 } 445 setcatsuffix(catpage, buildsuff, crunchsuff); 446 if (*linkname != '\0') 447 setcatsuffix(linkname, buildsuff, crunchsuff); 448 449 if (stat(manpage, &manstat) < 0) { 450 warn("can't stat %s", manpage); 451 continue; 452 } 453 454 if (!S_ISREG(manstat.st_mode)) { 455 warnx("not a regular file %s", manpage); 456 continue; 457 } 458 459 if ((error = stat(catpage, &catstat)) && 460 errno != ENOENT) { 461 warn("can't stat %s", catpage); 462 continue; 463 } 464 465 if ((error && errno == ENOENT) || 466 manstat.st_mtime > catstat.st_mtime) { 467 if (f_noformat) { 468 dowhatis = 1; 469 } else { 470 /* 471 * reformat out of date manpage 472 */ 473 makecat(manpage, catpage, buildcmd, crunchcmd); 474 dowhatis = 1; 475 } 476 } 477 478 if (*linkname != '\0') { 479 strlcpy(tmp, catpage, sizeof(tmp)); 480 snprintf(tmp, sizeof(tmp), "%s/%s", dirname(tmp), 481 linkname); 482 if ((error = lstat(tmp, &lnkstat)) && 483 errno != ENOENT) { 484 warn("can't stat %s", tmp); 485 continue; 486 } 487 488 if (error && errno == ENOENT) { 489 if (f_noformat) { 490 dowhatis = 1; 491 } else { 492 /* 493 * create symbolic link 494 */ 495 if (f_noprint == 0) 496 printf("ln -s %s %s\n", catpage, 497 linkname); 498 if (f_noaction == 0) { 499 strlcpy(tmp, catpage, 500 sizeof(tmp)); 501 if (chdir(dirname(tmp)) == -1) { 502 warn("can't chdir"); 503 continue; 504 } 505 506 if (symlink(catpage, linkname) 507 == -1) { 508 warn("can't create" 509 " symbolic" 510 " link %s", 511 linkname); 512 continue; 513 } 514 } 515 dowhatis = 1; 516 } 517 } 518 } 519 } 520 closedir(dirp); 521 } 522 523 static int 524 splitentry(char *s, char *first, size_t firstlen, char *second, 525 size_t secondlen) 526 { 527 char *c; 528 529 for (c = s; *c != '\0' && !isspace((unsigned char)*c); ++c) 530 ; 531 if (*c == '\0') 532 return(0); 533 if ((size_t)(c - s + 1) > firstlen) 534 return(0); 535 strncpy(first, s, c-s); 536 first[c-s] = '\0'; 537 for (; *c != '\0' && isspace((unsigned char)*c); ++c) 538 ; 539 if (strlcpy(second, c, secondlen) >= secondlen) 540 return(0); 541 return(1); 542 } 543 544 static void 545 setcatsuffix(char *catpage, const char *suffix, const char *crunchsuff) 546 { 547 TAG *tp; 548 char *p; 549 550 for (p = catpage + strlen(catpage); p != catpage; p--) 551 if (!fnmatch(suffix, p, 0)) { 552 if ((tp = gettag("_suffix", 1)) == NULL) 553 err(1, "malloc"); 554 if (! TAILQ_EMPTY(&tp->entrylist)) { 555 sprintf(p, "%s%s", 556 TAILQ_FIRST(&tp->entrylist)->s, crunchsuff); 557 } else { 558 sprintf(p, ".0%s", crunchsuff); 559 } 560 break; 561 } 562 } 563 564 static void 565 makecat(const char *manpage, const char *catpage, const char *buildcmd, 566 const char *crunchcmd) 567 { 568 char crunchbuf[1024]; 569 char sysbuf[2048]; 570 size_t len; 571 572 len = snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage); 573 if (len > sizeof(sysbuf)) 574 errx(1, "snprintf"); 575 576 if (*crunchcmd != '\0') { 577 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage); 578 snprintf(sysbuf + len, sizeof(sysbuf) - len, " | %s", 579 crunchbuf); 580 } else { 581 snprintf(sysbuf + len, sizeof(sysbuf) - len, " > %s", catpage); 582 } 583 584 if (f_noprint == 0) 585 printf("%s\n", sysbuf); 586 if (f_noaction == 0) 587 dosystem(sysbuf); 588 } 589 590 static void 591 makewhatis(void) 592 { 593 TAG *whatdbp; 594 ENTRY *e_whatdb; 595 char sysbuf[1024]; 596 597 if ((whatdbp = gettag("_whatdb", 1)) == NULL) 598 err(1, "malloc"); 599 TAILQ_FOREACH(e_whatdb, &whatdbp->entrylist, q) { 600 snprintf(sysbuf, sizeof(sysbuf), "%s %s", 601 _PATH_WHATIS, dirname(e_whatdb->s)); 602 if (f_noprint == 0) 603 printf("%s\n", sysbuf); 604 if (f_noaction == 0) 605 dosystem(sysbuf); 606 } 607 } 608 609 static void 610 dosystem(const char *cmd) 611 { 612 int status; 613 614 if ((status = system(cmd)) == 0) 615 return; 616 617 if (status == -1) 618 err(1, "cannot execute action"); 619 if (WIFSIGNALED(status)) 620 errx(1, "child was signaled to quit. aborting"); 621 if (WIFSTOPPED(status)) 622 errx(1, "child was stopped. aborting"); 623 if (f_ignerr == 0) 624 errx(1, "*** Exited %d", status); 625 warnx("*** Exited %d (continuing)", status); 626 } 627 628 static void 629 usage(void) 630 { 631 (void)fprintf(stderr, 632 "usage: catman [-knpsw] [-m manpath] [sections]\n"); 633 (void)fprintf(stderr, 634 " catman [-knpsw] [-M manpath] [sections]\n"); 635 exit(1); 636 } 637