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