1 /* $NetBSD: catman.c,v 1.38 2019/10/12 17:26:26 christos 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 * 18 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 19 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 21 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 22 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 #ifndef lint 33 __RCSID("$NetBSD: catman.c,v 1.38 2019/10/12 17:26:26 christos Exp $"); 34 #endif /* not lint */ 35 36 #include <sys/types.h> 37 #include <sys/queue.h> 38 #include <sys/param.h> 39 #include <sys/stat.h> 40 #include <sys/wait.h> 41 #include <sys/utsname.h> 42 #include <ctype.h> 43 #include <dirent.h> 44 #include <err.h> 45 #include <errno.h> 46 #include <fnmatch.h> 47 #include <limits.h> 48 #include <libgen.h> 49 #include <stdio.h> 50 #include <stdlib.h> 51 #include <string.h> 52 #include <unistd.h> 53 #include <glob.h> 54 55 #include "manconf.h" 56 #include "pathnames.h" 57 58 int f_nowhatis = 0; 59 int f_noaction = 0; 60 int f_noformat = 0; 61 int f_ignerr = 0; 62 int f_noprint = 0; 63 int dowhatis = 0; 64 65 TAG *defp; /* pointer to _default list */ 66 67 static void setdefentries(char *, char *, const char *); 68 static void uniquepath(void); 69 static void catman(void); 70 static void scanmandir(const char *, const char *); 71 static int splitentry(char *, char *, size_t, char *, size_t); 72 static void setcatsuffix(char *, const char *, const char *); 73 static void makecat(const char *, const char *, const char *, const char *); 74 static void makewhatis(void); 75 static void dosystem(const char *); 76 __dead static void usage(void); 77 78 79 int 80 main(int argc, char * const *argv) 81 { 82 char *m_path = NULL; 83 char *m_add = NULL; 84 int c; 85 86 while ((c = getopt(argc, argv, "km:M:npsw")) != -1) { 87 switch (c) { 88 case 'k': 89 f_ignerr = 1; 90 break; 91 case 'n': 92 f_nowhatis = 1; 93 break; 94 case 'p': 95 f_noaction = 1; 96 break; 97 case 's': 98 f_noprint = 1; 99 break; 100 case 'w': 101 f_noformat = 1; 102 break; 103 case 'm': 104 m_add = optarg; 105 break; 106 case 'M': 107 m_path = optarg; 108 break; 109 default: 110 usage(); 111 } 112 } 113 114 argc -= optind; 115 argv += optind; 116 117 if (f_noprint && f_noaction) 118 f_noprint = 0; 119 120 if (argc > 1) 121 usage(); 122 123 config(_PATH_MANCONF); 124 setdefentries(m_path, m_add, (argc == 0) ? NULL : argv[argc-1]); 125 uniquepath(); 126 127 if (f_noformat == 0 || f_nowhatis == 0) 128 catman(); 129 if (f_nowhatis == 0 && dowhatis) 130 makewhatis(); 131 132 return(0); 133 } 134 135 static void 136 setdefentries(char *m_path, char *m_add, const char *sections) 137 { 138 TAG *defnewp, *sectnewp, *subp; 139 ENTRY *e_defp, *e_subp; 140 const char *p, *slashp; 141 char *machine; 142 char buf[MAXPATHLEN * 2]; 143 int i; 144 145 /* Get the machine type. */ 146 if ((machine = getenv("MACHINE")) == NULL) { 147 struct utsname utsname; 148 149 if (uname(&utsname) == -1) { 150 perror("uname"); 151 exit(1); 152 } 153 machine = utsname.machine; 154 } 155 156 /* If there's no _default list, create an empty one. */ 157 defp = gettag("_default", 1); 158 subp = gettag("_subdir", 1); 159 if (defp == NULL || subp == NULL) 160 err(1, "malloc"); 161 162 /* 163 * 0: If one or more sections was specified, rewrite _subdir list. 164 */ 165 if (sections != NULL) { 166 if ((sectnewp = gettag("_section_new", 1)) == NULL) 167 err(1, "malloc"); 168 for (p = sections; *p;) { 169 i = snprintf(buf, sizeof(buf), "man%c", *p++); 170 for (; *p && !isdigit((unsigned char)*p) && i < (int)sizeof(buf) - 1; i++) 171 buf[i] = *p++; 172 buf[i] = '\0'; 173 if (addentry(sectnewp, buf, 0) < 0) 174 err(1, "malloc"); 175 } 176 subp = sectnewp; 177 } 178 179 /* 180 * 1: If the user specified a MANPATH variable, or set the -M 181 * option, we replace the _default list with the user's list, 182 * appending the entries in the _subdir list and the machine. 183 */ 184 if (m_path == NULL) 185 m_path = getenv("MANPATH"); 186 if (m_path != NULL) { 187 while ((e_defp = TAILQ_FIRST(&defp->entrylist)) != NULL) { 188 free(e_defp->s); 189 TAILQ_REMOVE(&defp->entrylist, e_defp, q); 190 } 191 for (p = strtok(m_path, ":"); 192 p != NULL; p = strtok(NULL, ":")) { 193 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 194 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 195 if (!strncmp(e_subp->s, "cat", 3)) 196 continue; 197 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 198 p, slashp, e_subp->s, machine); 199 if (addentry(defp, buf, 0) < 0) 200 err(1, "malloc"); 201 } 202 } 203 } 204 205 /* 206 * 2: If the user did not specify MANPATH, -M or a section, rewrite 207 * the _default list to include the _subdir list and the machine. 208 */ 209 if (m_path == NULL) { 210 defp = gettag("_default", 1); 211 defnewp = gettag("_default_new1", 1); 212 if (defp == NULL || defnewp == NULL) 213 err(1, "malloc"); 214 215 TAILQ_FOREACH(e_defp, &defp->entrylist, q) { 216 slashp = 217 e_defp->s[strlen(e_defp->s) - 1] == '/' ? "" : "/"; 218 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 219 if (!strncmp(e_subp->s, "cat", 3)) 220 continue; 221 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 222 e_defp->s, slashp, e_subp->s, machine); 223 if (addentry(defnewp, buf, 0) < 0) 224 err(1, "malloc"); 225 } 226 } 227 defp = defnewp; 228 } 229 230 /* 231 * 3: If the user set the -m option, insert the user's list before 232 * whatever list we have, again appending the _subdir list and 233 * the machine. 234 */ 235 if (m_add != NULL) 236 for (p = strtok(m_add, ":"); p != NULL; p = strtok(NULL, ":")) { 237 slashp = p[strlen(p) - 1] == '/' ? "" : "/"; 238 TAILQ_FOREACH(e_subp, &subp->entrylist, q) { 239 if (!strncmp(e_subp->s, "cat", 3)) 240 continue; 241 (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,}", 242 p, slashp, e_subp->s, machine); 243 if (addentry(defp, buf, 1) < 0) 244 err(1, "malloc"); 245 } 246 } 247 } 248 249 /* 250 * Remove entries (directory) which are symbolic links to other entries. 251 * Some examples are showed below: 252 * 1) if /usr/X11 -> /usr/X11R6 then remove all /usr/X11/man entries. 253 * 2) if /usr/local/man -> /usr/share/man then remove all /usr/local/man 254 * entries 255 */ 256 static void 257 uniquepath(void) 258 { 259 TAG *defnewp; 260 ENTRY *e_defp; 261 glob_t manpaths; 262 struct stat st1; 263 struct stat st2; 264 struct stat st3; 265 int len, lnk, gflags; 266 size_t i, j; 267 char path[PATH_MAX], *p; 268 269 gflags = 0; 270 TAILQ_FOREACH(e_defp, &defp->entrylist, q) { 271 glob(e_defp->s, GLOB_BRACE | GLOB_NOSORT | gflags, NULL, 272 &manpaths); 273 gflags = GLOB_APPEND; 274 } 275 276 if ((defnewp = gettag("_default_new2", 1)) == NULL) 277 err(1, "malloc"); 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 if (addentry(defnewp, manpaths.gl_pathv[i], 0) < 0) 312 err(1, "malloc"); 313 } 314 } 315 316 globfree(&manpaths); 317 318 defp = defnewp; 319 } 320 321 static void 322 catman(void) 323 { 324 ENTRY *e_path; 325 const char *mandir; 326 char catdir[PATH_MAX], *cp; 327 328 TAILQ_FOREACH(e_path, &defp->entrylist, q) { 329 mandir = e_path->s; 330 strlcpy(catdir, mandir, sizeof(catdir)); 331 if (!(cp = strstr(catdir, "man/man"))) 332 continue; 333 cp += 4; *cp++ = 'c'; *cp++ = 'a'; *cp = 't'; 334 scanmandir(catdir, mandir); 335 } 336 } 337 338 static void 339 scanmandir(const char *catdir, const char *mandir) 340 { 341 TAG *buildp, *crunchp; 342 ENTRY *e_build, *e_crunch; 343 char manpage[2 * PATH_MAX]; 344 char catpage[2 * PATH_MAX]; 345 char linkname[PATH_MAX]; 346 char buffer[PATH_MAX], *bp; 347 char tmp[2 * PATH_MAX]; 348 char buildsuff[256], buildcmd[256]; 349 char crunchsuff[256], crunchcmd[256]; 350 char match[2 * 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 if ((buildp = gettag("_build", 1)) == NULL) 388 err(1, "malloc"); 389 TAILQ_FOREACH(e_build, &buildp->entrylist, q) { 390 splitentry(e_build->s, buildsuff, sizeof(buildsuff), 391 buildcmd, sizeof(buildcmd)); 392 snprintf(match, sizeof(match), "*%s", 393 buildsuff); 394 if (!fnmatch(match, manpage, 0)) 395 break; 396 } 397 398 if (e_build == NULL) 399 continue; 400 401 e_crunch = NULL; 402 if ((crunchp = gettag("_crunch", 1)) == NULL) 403 err(1, "malloc"); 404 TAILQ_FOREACH(e_crunch, &crunchp->entrylist, q) { 405 splitentry(e_crunch->s, crunchsuff, sizeof(crunchsuff), 406 crunchcmd, sizeof(crunchcmd)); 407 snprintf(match, sizeof(match), "*%s", crunchsuff); 408 if (!fnmatch(match, manpage, 0)) 409 break; 410 } 411 412 if (lstat(manpage, &manstat) <0) { 413 warn("can't stat %s", manpage); 414 continue; 415 } else { 416 if (S_ISLNK(manstat.st_mode)) { 417 strlcpy(buffer, catpage, sizeof(buffer)); 418 strlcpy(linkname, basename(buffer), 419 sizeof(linkname)); 420 len = readlink(manpage, buffer, 421 sizeof(buffer) - 1); 422 if (len == -1) { 423 warn("can't stat read symbolic link %s", 424 manpage); 425 continue; 426 } 427 buffer[len] = '\0'; 428 429 if (strcmp(buffer, basename(buffer)) == 0) { 430 bp = basename(buffer); 431 strlcpy(tmp, manpage, sizeof(tmp)); 432 snprintf(manpage, sizeof(manpage), 433 "%s/%s", dirname(tmp), bp); 434 strlcpy(tmp, catpage, sizeof(tmp)); 435 snprintf(catpage, sizeof(catpage), 436 "%s/%s", dirname(tmp), buffer); 437 } else { 438 *linkname = '\0'; 439 } 440 441 } 442 else 443 *linkname = '\0'; 444 } 445 446 if (!e_crunch) { 447 *crunchsuff = *crunchcmd = '\0'; 448 } 449 setcatsuffix(catpage, buildsuff, crunchsuff); 450 if (*linkname != '\0') 451 setcatsuffix(linkname, buildsuff, crunchsuff); 452 453 if (stat(manpage, &manstat) < 0) { 454 warn("can't stat %s", manpage); 455 continue; 456 } 457 458 if (!S_ISREG(manstat.st_mode)) { 459 warnx("not a regular file %s", manpage); 460 continue; 461 } 462 463 if ((error = stat(catpage, &catstat)) && 464 errno != ENOENT) { 465 warn("can't stat %s", catpage); 466 continue; 467 } 468 469 if ((error && errno == ENOENT) || 470 manstat.st_mtime > catstat.st_mtime) { 471 if (f_noformat) { 472 dowhatis = 1; 473 } else { 474 /* 475 * reformat out of date manpage 476 */ 477 makecat(manpage, catpage, buildcmd, crunchcmd); 478 dowhatis = 1; 479 } 480 } 481 482 if (*linkname != '\0') { 483 strlcpy(tmp, catpage, sizeof(tmp)); 484 snprintf(tmp, sizeof(tmp), "%s/%s", dirname(tmp), 485 linkname); 486 if ((error = lstat(tmp, &lnkstat)) && 487 errno != ENOENT) { 488 warn("can't stat %s", tmp); 489 continue; 490 } 491 492 if (error && errno == ENOENT) { 493 if (f_noformat) { 494 dowhatis = 1; 495 } else { 496 /* 497 * create symbolic link 498 */ 499 if (f_noprint == 0) 500 printf("ln -s %s %s\n", catpage, 501 linkname); 502 if (f_noaction == 0) { 503 strlcpy(tmp, catpage, 504 sizeof(tmp)); 505 if (chdir(dirname(tmp)) == -1) { 506 warn("can't chdir"); 507 continue; 508 } 509 510 if (symlink(catpage, linkname) 511 == -1) { 512 warn("can't create" 513 " symbolic" 514 " link %s", 515 linkname); 516 continue; 517 } 518 } 519 dowhatis = 1; 520 } 521 } 522 } 523 } 524 closedir(dirp); 525 } 526 527 static int 528 splitentry(char *s, char *first, size_t firstlen, char *second, 529 size_t secondlen) 530 { 531 char *c; 532 533 for (c = s; *c != '\0' && !isspace((unsigned char)*c); ++c) 534 ; 535 if (*c == '\0') 536 return(0); 537 if ((size_t)(c - s + 1) > firstlen) 538 return(0); 539 strncpy(first, s, c-s); 540 first[c-s] = '\0'; 541 for (; *c != '\0' && isspace((unsigned char)*c); ++c) 542 ; 543 if (strlcpy(second, c, secondlen) >= secondlen) 544 return(0); 545 return(1); 546 } 547 548 static void 549 setcatsuffix(char *catpage, const char *suffix, const char *crunchsuff) 550 { 551 TAG *tp; 552 char *p; 553 554 for (p = catpage + strlen(catpage); p != catpage; p--) 555 if (!fnmatch(suffix, p, 0)) { 556 if ((tp = gettag("_suffix", 1)) == NULL) 557 err(1, "malloc"); 558 if (! TAILQ_EMPTY(&tp->entrylist)) { 559 sprintf(p, "%s%s", 560 TAILQ_FIRST(&tp->entrylist)->s, crunchsuff); 561 } else { 562 sprintf(p, ".0%s", crunchsuff); 563 } 564 break; 565 } 566 } 567 568 static void 569 makecat(const char *manpage, const char *catpage, const char *buildcmd, 570 const char *crunchcmd) 571 { 572 char crunchbuf[1024]; 573 char sysbuf[2048 + 128]; 574 size_t len; 575 576 len = snprintf(sysbuf, sizeof(sysbuf), buildcmd, manpage); 577 if (len > sizeof(sysbuf)) 578 errx(1, "snprintf"); 579 580 if (*crunchcmd != '\0') { 581 snprintf(crunchbuf, sizeof(crunchbuf), crunchcmd, catpage); 582 snprintf(sysbuf + len, sizeof(sysbuf) - len, " | %s", 583 crunchbuf); 584 } else { 585 snprintf(sysbuf + len, sizeof(sysbuf) - len, " > %s", catpage); 586 } 587 588 if (f_noprint == 0) 589 printf("%s\n", sysbuf); 590 if (f_noaction == 0) 591 dosystem(sysbuf); 592 } 593 594 static void 595 makewhatis(void) 596 { 597 TAG *whatdbp; 598 ENTRY *e_whatdb; 599 char sysbuf[1024]; 600 601 if ((whatdbp = gettag("_whatdb", 1)) == NULL) 602 err(1, "malloc"); 603 TAILQ_FOREACH(e_whatdb, &whatdbp->entrylist, q) { 604 snprintf(sysbuf, sizeof(sysbuf), "%s %s", 605 _PATH_MAKEWHATIS, dirname(e_whatdb->s)); 606 if (f_noprint == 0) 607 printf("%s\n", sysbuf); 608 if (f_noaction == 0) 609 dosystem(sysbuf); 610 } 611 } 612 613 static void 614 dosystem(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