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