1 /* $NetBSD: catman.c,v 1.19 2002/10/19 20:33:19 provos 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 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 defp = getlist("_default", 1); 170 171 subp = getlist("_subdir", 1); 172 173 /* 174 * 0: If one or more sections was specified, rewrite _subdir list. 175 */ 176 if (sections != NULL) { 177 sectnewp = getlist("_section_new", 1); 178 for(p=sections; *p;) { 179 i = snprintf(buf, sizeof(buf), "man%c", *p++); 180 for(; *p && !isdigit(*p) && i<sizeof(buf)-1; i++) 181 buf[i] = *p++; 182 buf[i] = '\0'; 183 addentry(sectnewp, buf, 0); 184 } 185 subp = sectnewp; 186 } 187 188 /* 189 * 1: If the user specified a MANPATH variable, or set the -M 190 * option, we replace the _default list with the user's list, 191 * appending the entries in the _subdir list and the machine. 192 */ 193 if (m_path == NULL) 194 m_path = getenv("MANPATH"); 195 if (m_path != NULL) { 196 while ((e_defp = TAILQ_FIRST(&defp->list)) != NULL) { 197 free(e_defp->s); 198 TAILQ_REMOVE(&defp->list, e_defp, q); 199 } 200 for (p = strtok(m_path, ":"); 201 p != NULL; p = strtok(NULL, ":")) { 202 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 203 TAILQ_FOREACH(e_subp, &subp->list, q) { 204 if(!strncmp(e_subp->s, "cat", 3)) 205 continue; 206 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 207 p, slashp, e_subp->s, machine); 208 addentry(defp, buf, 0); 209 } 210 } 211 } 212 213 /* 214 * 2: If the user did not specify MANPATH, -M or a section, rewrite 215 * the _default list to include the _subdir list and the machine. 216 */ 217 if (m_path == NULL) { 218 defp = getlist("_default", 1); 219 defnewp = getlist("_default_new1", 1); 220 221 TAILQ_FOREACH(e_defp, &defp->list, q) { 222 slashp = 223 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/"; 224 TAILQ_FOREACH(e_subp, &subp->list, q) { 225 if(!strncmp(e_subp->s, "cat", 3)) 226 continue; 227 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 228 e_defp->s, slashp, e_subp->s, machine); 229 addentry(defnewp, buf, 0); 230 } 231 } 232 defp = defnewp; 233 } 234 235 /* 236 * 3: If the user set the -m option, insert the user's list before 237 * whatever list we have, again appending the _subdir list and 238 * the machine. 239 */ 240 if (m_add != NULL) 241 for (p = strtok(m_add, ":"); p != NULL; p = strtok(NULL, ":")) { 242 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 243 TAILQ_FOREACH(e_subp, &subp->list, q) { 244 if(!strncmp(e_subp->s, "cat", 3)) 245 continue; 246 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 247 p, slashp, e_subp->s, machine); 248 addentry(defp, buf, 1); 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 i,j,len,lnk, gflags; 270 char path[PATH_MAX], *p; 271 272 273 gflags = 0; 274 TAILQ_FOREACH(e_defp, &defp->list, q) { 275 glob(e_defp->s, GLOB_BRACE | GLOB_NOSORT | gflags, NULL, 276 &manpaths); 277 gflags = GLOB_APPEND; 278 } 279 280 defnewp = getlist("_default_new2", 1); 281 282 for(i=0; i<manpaths.gl_pathc; i++) { 283 lnk = 0; 284 lstat(manpaths.gl_pathv[i], &st1); 285 for(j=0; j<manpaths.gl_pathc; j++) { 286 if (i != j) { 287 lstat(manpaths.gl_pathv[j], &st2); 288 if (st1.st_ino == st2.st_ino) { 289 strcpy(path, manpaths.gl_pathv[i]); 290 for(p = path; *(p+1) != '\0';) { 291 p = dirname(p); 292 lstat(p, &st3); 293 if (S_ISLNK(st3.st_mode)) { 294 lnk = 1; 295 break; 296 } 297 } 298 } else { 299 len = readlink(manpaths.gl_pathv[i], 300 path, PATH_MAX - 1); 301 if (len == -1) 302 continue; 303 if (!strcmp(path, manpaths.gl_pathv[j])) 304 lnk = 1; 305 } 306 if (lnk) 307 break; 308 } 309 } 310 311 if (!lnk) 312 addentry(defnewp, manpaths.gl_pathv[i], 0); 313 } 314 315 globfree(&manpaths); 316 317 defp = defnewp; 318 } 319 320 static void 321 catman(void) 322 { 323 ENTRY *e_path; 324 const char *mandir; 325 char catdir[PATH_MAX], *cp; 326 327 TAILQ_FOREACH(e_path, &defp->list, q) { 328 mandir = e_path->s; 329 strcpy(catdir, mandir); 330 if(!(cp = strstr(catdir, "man/man"))) 331 continue; 332 cp+=4; *cp++ = 'c'; *cp++ = 'a'; *cp = 't'; 333 scanmandir(catdir, mandir); 334 } 335 } 336 337 static void 338 scanmandir(catdir, mandir) 339 const char *catdir; 340 const char *mandir; 341 { 342 TAG *buildp, *crunchp; 343 ENTRY *e_build, *e_crunch; 344 char manpage[PATH_MAX]; 345 char catpage[PATH_MAX]; 346 char linkname[PATH_MAX]; 347 char buffer[PATH_MAX], *bp; 348 char tmp[PATH_MAX]; 349 char buildsuff[256], buildcmd[256]; 350 char crunchsuff[256], crunchcmd[256]; 351 char match[256]; 352 struct stat manstat; 353 struct stat catstat; 354 struct stat lnkstat; 355 struct dirent *dp; 356 DIR *dirp; 357 int len, error; 358 359 if ((dirp = opendir(mandir)) == 0) { 360 warn("can't open %s", mandir); 361 return; 362 } 363 364 if (stat(catdir, &catstat) < 0) { 365 if (errno != ENOENT) { 366 warn("can't stat %s", catdir); 367 closedir(dirp); 368 return; 369 } 370 if (f_noprint == 0) 371 printf("mkdir %s\n", catdir); 372 if (f_noaction == 0 && mkdir(catdir,0755) < 0) { 373 warn("can't create %s", catdir); 374 closedir(dirp); 375 return; 376 } 377 } 378 379 while ((dp = readdir(dirp)) != NULL) { 380 if (strcmp(dp->d_name, ".") == 0 || 381 strcmp(dp->d_name, "..") == 0) 382 continue; 383 384 snprintf(manpage, sizeof(manpage), "%s/%s", mandir, dp->d_name); 385 snprintf(catpage, sizeof(catpage), "%s/%s", catdir, dp->d_name); 386 387 e_build = NULL; 388 buildp = getlist("_build", 1); 389 TAILQ_FOREACH(e_build, &buildp->list, q) { 390 splitentry(e_build->s, buildsuff, 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, crunchcmd); 404 snprintf(match, sizeof(match), "*%s", crunchsuff); 405 if(!fnmatch(match, manpage, 0)) 406 break; 407 } 408 409 if (lstat(manpage, &manstat) <0) { 410 warn("can't stat %s", manpage); 411 continue; 412 } else { 413 if(S_ISLNK(manstat.st_mode)) { 414 strcpy(buffer, catpage); 415 strcpy(linkname, basename(buffer)); 416 len = readlink(manpage, buffer, PATH_MAX); 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 if(*c == '\0') 525 return(0); 526 strncpy(first, s, c-s); 527 first[c-s] = '\0'; 528 for(; *c != '\0' && isspace(*c); ++c); 529 strcpy(second, c); 530 return(1); 531 } 532 533 static void 534 setcatsuffix(catpage, suffix, crunchsuff) 535 char *catpage; 536 const char *suffix; 537 const char *crunchsuff; 538 { 539 TAG *tp; 540 char *p; 541 542 for(p = catpage + strlen(catpage); p!=catpage; p--) 543 if(!fnmatch(suffix, p, 0)) { 544 tp = getlist("_suffix", 1); 545 if (! TAILQ_EMPTY(&tp->list)) { 546 sprintf(p, "%s%s", 547 TAILQ_FIRST(&tp->list)->s, crunchsuff); 548 } else { 549 sprintf(p, ".0%s", crunchsuff); 550 } 551 break; 552 } 553 } 554 555 static void 556 makecat(manpage, catpage, buildcmd, crunchcmd) 557 const char *manpage; 558 const char *catpage; 559 const char *buildcmd; 560 const char *crunchcmd; 561 { 562 char crunchbuf[1024]; 563 char sysbuf[2048]; 564 565 snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage); 566 567 if(*crunchcmd != '\0') { 568 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage); 569 snprintf(sysbuf, sizeof(sysbuf), "%s | %s", sysbuf, crunchbuf); 570 } else { 571 snprintf(sysbuf, sizeof(sysbuf), "%s > %s", sysbuf, catpage); 572 } 573 574 if (f_noprint == 0) 575 printf("%s\n", sysbuf); 576 if (f_noaction == 0) 577 dosystem(sysbuf); 578 } 579 580 static void 581 makewhatis(void) 582 { 583 TAG *whatdbp; 584 ENTRY *e_whatdb; 585 char sysbuf[1024]; 586 587 whatdbp = getlist("_whatdb", 1); 588 TAILQ_FOREACH(e_whatdb, &whatdbp->list, q) { 589 snprintf(sysbuf, sizeof(sysbuf), "%s %s", 590 _PATH_WHATIS, dirname(e_whatdb->s)); 591 if (f_noprint == 0) 592 printf("%s\n", sysbuf); 593 if (f_noaction == 0) 594 dosystem(sysbuf); 595 } 596 } 597 598 static void 599 dosystem(cmd) 600 const char *cmd; 601 { 602 int status; 603 604 if ((status = system(cmd)) == 0) 605 return; 606 607 if (status == -1) 608 err(1, "cannot execute action"); 609 if (WIFSIGNALED(status)) 610 errx(1, "child was signaled to quit. aborting"); 611 if (WIFSTOPPED(status)) 612 errx(1, "child was stopped. aborting"); 613 if (f_ignerr == 0) 614 errx(1, "*** Exited %d", status); 615 warnx("*** Exited %d (continuing)", status); 616 } 617 618 static void 619 usage(void) 620 { 621 (void)fprintf(stderr, 622 "usage: catman [-knpsw] [-m manpath] [sections]\n"); 623 (void)fprintf(stderr, 624 " catman [-knpsw] [-M manpath] [sections]\n"); 625 exit(1); 626 } 627