1 /* $NetBSD: catman.c,v 1.21 2003/05/09 00:47:46 itojun 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 *, char *)); 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(*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 strcpy(path, manpaths.gl_pathv[i]); 287 for (p = path; *(p+1) != '\0';) { 288 p = dirname(p); 289 lstat(p, &st3); 290 if (S_ISLNK(st3.st_mode)) { 291 lnk = 1; 292 break; 293 } 294 } 295 } else { 296 len = readlink(manpaths.gl_pathv[i], 297 path, sizeof(path) - 1); 298 if (len == -1) 299 continue; 300 path[len] = '\0'; 301 if (!strcmp(path, manpaths.gl_pathv[j])) 302 lnk = 1; 303 } 304 if (lnk) 305 break; 306 } 307 } 308 309 if (!lnk) 310 addentry(defnewp, manpaths.gl_pathv[i], 0); 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->list, q) { 326 mandir = e_path->s; 327 strcpy(catdir, mandir); 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(catdir, mandir) 337 const char *catdir; 338 const char *mandir; 339 { 340 TAG *buildp, *crunchp; 341 ENTRY *e_build, *e_crunch; 342 char manpage[PATH_MAX]; 343 char catpage[PATH_MAX]; 344 char linkname[PATH_MAX]; 345 char buffer[PATH_MAX], *bp; 346 char tmp[PATH_MAX]; 347 char buildsuff[256], buildcmd[256]; 348 char crunchsuff[256], crunchcmd[256]; 349 char match[256]; 350 struct stat manstat; 351 struct stat catstat; 352 struct stat lnkstat; 353 struct dirent *dp; 354 DIR *dirp; 355 int len, error; 356 357 if ((dirp = opendir(mandir)) == 0) { 358 warn("can't open %s", mandir); 359 return; 360 } 361 362 if (stat(catdir, &catstat) < 0) { 363 if (errno != ENOENT) { 364 warn("can't stat %s", catdir); 365 closedir(dirp); 366 return; 367 } 368 if (f_noprint == 0) 369 printf("mkdir %s\n", catdir); 370 if (f_noaction == 0 && mkdir(catdir, 0755) < 0) { 371 warn("can't create %s", catdir); 372 closedir(dirp); 373 return; 374 } 375 } 376 377 while ((dp = readdir(dirp)) != NULL) { 378 if (strcmp(dp->d_name, ".") == 0 || 379 strcmp(dp->d_name, "..") == 0) 380 continue; 381 382 snprintf(manpage, sizeof(manpage), "%s/%s", mandir, dp->d_name); 383 snprintf(catpage, sizeof(catpage), "%s/%s", catdir, dp->d_name); 384 385 e_build = NULL; 386 buildp = getlist("_build", 1); 387 TAILQ_FOREACH(e_build, &buildp->list, q) { 388 splitentry(e_build->s, buildsuff, 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 crunchp = getlist("_crunch", 1); 400 TAILQ_FOREACH(e_crunch, &crunchp->list, q) { 401 splitentry(e_crunch->s, crunchsuff, crunchcmd); 402 snprintf(match, sizeof(match), "*%s", crunchsuff); 403 if (!fnmatch(match, manpage, 0)) 404 break; 405 } 406 407 if (lstat(manpage, &manstat) <0) { 408 warn("can't stat %s", manpage); 409 continue; 410 } else { 411 if (S_ISLNK(manstat.st_mode)) { 412 strcpy(buffer, catpage); 413 strcpy(linkname, basename(buffer)); 414 len = readlink(manpage, buffer, 415 sizeof(buffer) - 1); 416 buffer[PATH_MAX - 1] = '\0'; 417 if (len == -1) { 418 warn("can't stat read symbolic link %s", 419 manpage); 420 continue; 421 } 422 buffer[len] = '\0'; 423 bp = basename(buffer); 424 strcpy(tmp, manpage); 425 snprintf(manpage, sizeof(manpage), "%s/%s", 426 dirname(tmp), bp); 427 strcpy(tmp, catpage); 428 snprintf(catpage, sizeof(catpage), "%s/%s", 429 dirname(tmp), buffer); 430 } 431 else 432 *linkname = '\0'; 433 } 434 435 if (!e_crunch) { 436 *crunchsuff = *crunchcmd = '\0'; 437 } 438 setcatsuffix(catpage, buildsuff, crunchsuff); 439 if (*linkname != '\0') 440 setcatsuffix(linkname, buildsuff, crunchsuff); 441 442 if (stat(manpage, &manstat) < 0) { 443 warn("can't stat %s", manpage); 444 continue; 445 } 446 447 if (!S_ISREG(manstat.st_mode)) { 448 warnx("not a regular file %s", manpage); 449 continue; 450 } 451 452 if ((error = stat(catpage, &catstat)) && 453 errno != ENOENT) { 454 warn("can't stat %s", catpage); 455 continue; 456 } 457 458 if ((error && errno == ENOENT) || 459 manstat.st_mtime > catstat.st_mtime) { 460 if (f_noformat) { 461 dowhatis = 1; 462 } else { 463 /* 464 * reformat out of date manpage 465 */ 466 makecat(manpage, catpage, buildcmd, crunchcmd); 467 dowhatis = 1; 468 } 469 } 470 471 if (*linkname != '\0') { 472 strcpy(tmp, catpage); 473 snprintf(tmp, sizeof(tmp), "%s/%s", dirname(tmp), 474 linkname); 475 if ((error = lstat(tmp, &lnkstat)) && 476 errno != ENOENT) { 477 warn("can't stat %s", tmp); 478 continue; 479 } 480 481 if (error && errno == ENOENT) { 482 if (f_noformat) { 483 dowhatis = 1; 484 } else { 485 /* 486 * create symbolic link 487 */ 488 if (f_noprint == 0) 489 printf("ln -s %s %s\n", catpage, 490 linkname); 491 if (f_noaction == 0) { 492 strcpy(tmp, catpage); 493 if (chdir(dirname(tmp)) == -1) { 494 warn("can't chdir"); 495 continue; 496 } 497 498 if (symlink(catpage, linkname) 499 == -1) { 500 warn("can't create" 501 " symbolic" 502 " link %s", 503 linkname); 504 continue; 505 } 506 } 507 dowhatis = 1; 508 } 509 } 510 } 511 } 512 closedir(dirp); 513 } 514 515 static int 516 splitentry(s, first, second) 517 char *s; 518 char *first; 519 char *second; 520 { 521 char *c; 522 523 for (c = s; *c != '\0' && !isspace(*c); ++c) 524 ; 525 if (*c == '\0') 526 return(0); 527 strncpy(first, s, c-s); 528 first[c-s] = '\0'; 529 for (; *c != '\0' && isspace(*c); ++c) 530 ; 531 strcpy(second, c); 532 return(1); 533 } 534 535 static void 536 setcatsuffix(catpage, suffix, crunchsuff) 537 char *catpage; 538 const char *suffix; 539 const char *crunchsuff; 540 { 541 TAG *tp; 542 char *p; 543 544 for (p = catpage + strlen(catpage); p != catpage; p--) 545 if (!fnmatch(suffix, p, 0)) { 546 tp = getlist("_suffix", 1); 547 if (! TAILQ_EMPTY(&tp->list)) { 548 sprintf(p, "%s%s", 549 TAILQ_FIRST(&tp->list)->s, crunchsuff); 550 } else { 551 sprintf(p, ".0%s", crunchsuff); 552 } 553 break; 554 } 555 } 556 557 static void 558 makecat(manpage, catpage, buildcmd, crunchcmd) 559 const char *manpage; 560 const char *catpage; 561 const char *buildcmd; 562 const char *crunchcmd; 563 { 564 char crunchbuf[1024]; 565 char sysbuf[2048]; 566 567 snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage); 568 569 if (*crunchcmd != '\0') { 570 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage); 571 snprintf(sysbuf, sizeof(sysbuf), "%s | %s", sysbuf, crunchbuf); 572 } else { 573 snprintf(sysbuf, sizeof(sysbuf), "%s > %s", sysbuf, catpage); 574 } 575 576 if (f_noprint == 0) 577 printf("%s\n", sysbuf); 578 if (f_noaction == 0) 579 dosystem(sysbuf); 580 } 581 582 static void 583 makewhatis(void) 584 { 585 TAG *whatdbp; 586 ENTRY *e_whatdb; 587 char sysbuf[1024]; 588 589 whatdbp = getlist("_whatdb", 1); 590 TAILQ_FOREACH(e_whatdb, &whatdbp->list, 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(cmd) 602 const char *cmd; 603 { 604 int status; 605 606 if ((status = system(cmd)) == 0) 607 return; 608 609 if (status == -1) 610 err(1, "cannot execute action"); 611 if (WIFSIGNALED(status)) 612 errx(1, "child was signaled to quit. aborting"); 613 if (WIFSTOPPED(status)) 614 errx(1, "child was stopped. aborting"); 615 if (f_ignerr == 0) 616 errx(1, "*** Exited %d", status); 617 warnx("*** Exited %d (continuing)", status); 618 } 619 620 static void 621 usage(void) 622 { 623 (void)fprintf(stderr, 624 "usage: catman [-knpsw] [-m manpath] [sections]\n"); 625 (void)fprintf(stderr, 626 " catman [-knpsw] [-M manpath] [sections]\n"); 627 exit(1); 628 } 629