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