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