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