1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 /*
  22  * Copyright (c) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
  23  */
  24 
  25 /*      Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989  AT&T.       */
  26 /*              All rights reserved.                                    */
  27 
  28 /*
  29  * University Copyright- Copyright (c) 1982, 1986, 1988
  30  * The Regents of the University of California
  31  * All Rights Reserved
  32  *
  33  * University Acknowledgment- Portions of this document are derived from
  34  * software developed by the University of California, Berkeley, and its
  35  * contributors.
  36  */
  37 
  38 
  39 /*
  40  * man
  41  * links to apropos, whatis, and catman
  42  * This version uses more for underlining and paging.
  43  */
  44 
  45 #include <stdio.h>
  46 #include <ctype.h>
  47 #include <sgtty.h>
  48 #include <sys/param.h>
  49 #include <sys/types.h>
  50 #include <sys/stat.h>
  51 #include <signal.h>
  52 #include <string.h>
  53 #include <malloc.h>
  54 #include <dirent.h>
  55 #include <errno.h>
  56 #include <fcntl.h>
  57 #include <locale.h>
  58 #include <stdlib.h>
  59 #include <unistd.h>
  60 #include <memory.h>
  61 #include <limits.h>
  62 #include <wchar.h>
  63 
  64 #define MACROF  "tmac.an"               /* name of <locale> macro file */
  65 #define TMAC_AN "-man"          /* default macro file */
  66 
  67 /*
  68  * The default search path for man subtrees.
  69  */
  70 
  71 #define MANDIR          "/usr/share/man"        /* default mandir */
  72 #define MAKEWHATIS      "/usr/lib/makewhatis"
  73 #define WHATIS          "windex"
  74 #define TEMPLATE        "/tmp/mpXXXXXX"
  75 #define CONFIG          "man.cf"
  76 
  77 /*
  78  * Names for formatting and display programs.  The values given
  79  * below are reasonable defaults, but sites with source may
  80  * wish to modify them to match the local environment.  The
  81  * value for TCAT is particularly problematic as there's no
  82  * accepted standard value available for it.  (The definition
  83  * below assumes C.A.T. troff output and prints it).
  84  */
  85 
  86 #define MORE    "more -s"               /* default paging filter */
  87 #define CAT_S   "/usr/bin/cat -s"       /* for '-' opt (no more) */
  88 #define CAT_    "/usr/bin/cat"          /* for when output is not a tty */
  89 #define TROFF   "troff"                 /* local name for troff */
  90 #define TCAT    "lp -c -T troff"        /* command to "display" troff output */
  91 
  92 #define SOLIMIT         10      /* maximum allowed .so chain length */
  93 #define MAXDIRS         128     /* max # of subdirs per manpath */
  94 #define MAXPAGES        128     /* max # for multiple pages */
  95 #define PLEN            3       /* prefix length {man, cat, fmt} */
  96 #define TMPLEN          7       /* length of tmpfile prefix */
  97 #define MAXTOKENS       64
  98 
  99 #define DOT_SO          ".so "
 100 #define PREPROC_SPEC    "'\\\" "
 101 
 102 #define DPRINTF         if (debug && !catmando) \
 103                                 (void) printf
 104 
 105 #define sys(s)          (debug ? ((void)puts(s), 0) : system(s))
 106 #define eq(a, b)        (strcmp(a, b) == 0)
 107 #define match(a, b, c)  (strncmp(a, b, c) == 0)
 108 
 109 #define ISDIR(A)        ((A.st_mode & S_IFMT) == S_IFDIR)
 110 
 111 #define SROFF_CMD       "/usr/lib/sgml/sgml2roff" /* sgml converter */
 112 #define MANDIRNAME      "man"                     /* man directory */
 113 #define SGMLDIR         "sman"                    /* sman directory */
 114 #define SGML_SYMBOL     "<!DOCTYPE"  /* a sgml file should contain this */
 115 #define SGML_SYMBOL_LEN         9       /* length of SGML_SYMBOL */
 116 
 117 /*
 118  * Directory mapping of old directories to new directories
 119  */
 120 
 121 typedef struct {
 122         char *old_name;
 123         char *new_name;
 124 } map_entry;
 125 
 126 static const map_entry map[] = {
 127                                         { "3b", "3ucb" },
 128                                         { "3e", "3elf" },
 129                                         { "3g", "3gen" },
 130                                         { "3k", "3kstat" },
 131                                         { "3n", "3socket" },
 132                                         { "3r", "3rt" },
 133                                         { "3s", "3c" },
 134                                         { "3t", "3thr" },
 135                                         { "3x", "3curses" },
 136                                         { "3xc", "3xcurses" },
 137                                         { "3xn", "3xnet" }
 138 };
 139 
 140 /*
 141  * A list of known preprocessors to precede the formatter itself
 142  * in the formatting pipeline.  Preprocessors are specified by
 143  * starting a manual page with a line of the form:
 144  *      '\" X
 145  * where X is a string consisting of letters from the p_tag fields
 146  * below.
 147  */
 148 static const struct preprocessor {
 149         char    p_tag;
 150         char    *p_nroff,
 151                 *p_troff,
 152                 *p_stdin_char;
 153 } preprocessors [] = {
 154         {'c',   "cw",                           "cw",           "-"},
 155         {'e',   "neqn /usr/share/lib/pub/eqnchar",
 156                         "eqn /usr/share/lib/pub/eqnchar",       "-"},
 157         {'p',   "gpic",                         "gpic",         "-"},
 158         {'r',   "refer",                        "refer",        "-"},
 159         {'t',   "tbl",                          "tbl",          ""},
 160         {'v',   "vgrind -f",                    "vgrind -f",    "-"},
 161         {0,     0,                              0,              0}
 162 };
 163 
 164 struct suffix {
 165         char *ds;
 166         char *fs;
 167 };
 168 
 169 /*
 170  * Flags that control behavior of build_manpath()
 171  *
 172  *   BMP_ISPATH         pathv is a vector constructed from PATH.
 173  *                      Perform appropriate path translations for
 174  *                      manpath.
 175  *   BMP_APPEND_MANDIR  Add /usr/share/man to the end if it
 176  *                      hasn't already appeared earlier.
 177  *   BMP_FALLBACK_MANDIR Append /usr/share/man only if no other
 178  *                      manpath (including derived from PATH)
 179  *                      elements are valid.
 180  */
 181 #define BMP_ISPATH              1
 182 #define BMP_APPEND_MANDIR       2
 183 #define BMP_FALLBACK_MANDIR     4
 184 
 185 /*
 186  * When doing equality comparisons of directories, device and inode
 187  * comparisons are done.  The dupsec and dupnode structures are used
 188  * to form a list of lists for this processing.
 189  */
 190 struct secnode {
 191         char            *secp;
 192         struct secnode  *next;
 193 };
 194 struct dupnode {
 195         dev_t           dev;    /* from struct stat st_dev */
 196         ino_t           ino;    /* from struct stat st_ino */
 197         struct secnode  *secl;  /* sections already considered */
 198         struct dupnode  *next;
 199 };
 200 
 201 /*
 202  * Map directories that may appear in PATH to the corresponding
 203  * man directory
 204  */
 205 static struct pathmap {
 206         char    *bindir;
 207         char    *mandir;
 208         dev_t   dev;
 209         ino_t   ino;
 210 } bintoman[] = {
 211         {"/sbin",               "/usr/share/man,1m",                    0, 0},
 212         {"/usr/sbin",           "/usr/share/man,1m",                    0, 0},
 213         {"/usr/ucb",            "/usr/share/man,1b",                    0, 0},
 214         {"/usr/bin/X11",        "/usr/X11/share/man",                   0, 0},
 215         /*
 216          * Restrict to section 1 so that whatis /usr/{,xpg4,xpg6}/bin/ls
 217          * does not confuse users with section 1 and 1b
 218          */
 219         {"/usr/bin",            "/usr/share/man,1,1m,1s,1t,1c",         0, 0},
 220         {"/usr/xpg4/bin",       "/usr/share/man,1",                     0, 0},
 221         {"/usr/xpg6/bin",       "/usr/share/man,1",                     0, 0},
 222         {NULL,                  NULL,                                   0, 0}
 223 };
 224 
 225 /*
 226  * Subdirectories to search for unformatted/formatted man page
 227  * versions, in nroff and troff variations.  The searching
 228  * code in manual() is structured to expect there to be two
 229  * subdirectories apiece, the first for unformatted files
 230  * and the second for formatted ones.
 231  */
 232 static char     *nroffdirs[] = { "man", "cat", 0 };
 233 static char     *troffdirs[] = { "man", "fmt", 0 };
 234 
 235 #define MAN_USAGE "\
 236 usage:\tman [-] [-adFlprt] [-M path] [-T macro-package ] [ -s section ] \
 237 name ...\n\
 238 \tman [-M path] -k keyword ...\n\tman [-M path] -f file ..."
 239 #define CATMAN_USAGE "\
 240 usage:\tcatman [-p] [-c|-ntw] [-M path] [-T macro-package ] [sections]"
 241 
 242 static char *opts[] = {
 243         "FfkrpP:M:T:ts:lad",    /* man */
 244         "wpnP:M:T:tc"           /* catman */
 245 };
 246 
 247 struct man_node {
 248         char *path;             /* mandir path */
 249         char **secv;            /* submandir suffices */
 250         int  defsrch;           /* hint for man -p to avoid section list */
 251         int  frompath;          /* hint for man -d and catman -p */
 252         struct man_node *next;
 253 };
 254 
 255 static char     *pages[MAXPAGES];
 256 static char     **endp = pages;
 257 
 258 /*
 259  * flags (options)
 260  */
 261 static int      nomore;
 262 static int      troffit;
 263 static int      debug;
 264 static int      Tflag;
 265 static int      sargs;
 266 static int      margs;
 267 static int      force;
 268 static int      found;
 269 static int      list;
 270 static int      all;
 271 static int      whatis;
 272 static int      apropos;
 273 static int      catmando;
 274 static int      nowhatis;
 275 static int      whatonly;
 276 static int      compargs;       /* -c option for catman */
 277 static int      printmp;
 278 
 279 static char     *CAT    = CAT_;
 280 static char     macros[MAXPATHLEN];
 281 static char     *mansec;
 282 static char     *pager;
 283 static char     *troffcmd;
 284 static char     *troffcat;
 285 static char     **subdirs;
 286 
 287 static char *check_config(char *);
 288 static struct man_node *build_manpath(char **, int);
 289 static void getpath(struct man_node *, char **);
 290 static void getsect(struct man_node *, char **);
 291 static void get_all_sect(struct man_node *);
 292 static void catman(struct man_node *, char **, int);
 293 static int makecat(char *, char **, int);
 294 static int getdirs(char *, char ***, short);
 295 static void whatapro(struct man_node *, char *, int);
 296 static void lookup_windex(char *, char *, char **);
 297 static int icmp(wchar_t *, wchar_t *);
 298 static void more(char **, int);
 299 static void cleanup(char **);
 300 static void bye(int);
 301 static char **split(char *, char);
 302 static void freev(char **);
 303 static void fullpaths(struct man_node **);
 304 static void lower(char *);
 305 static int cmp(const void *, const void *);
 306 static int manual(struct man_node *, char *);
 307 static void mandir(char **, char *, char *);
 308 static void sortdir(DIR *, char ***);
 309 static int searchdir(char *, char *, char *);
 310 static int windex(char **, char *, char *);
 311 static void section(struct suffix *, char *);
 312 static int bfsearch(FILE *, char **, char *, char **);
 313 static int compare(char *, char *, char **);
 314 static int format(char *, char *, char *, char *);
 315 static char *addlocale(char *);
 316 static int get_manconfig(FILE *, char *);
 317 static void     malloc_error(void);
 318 static int      sgmlcheck(const char *);
 319 static char *map_section(char *, char *);
 320 static void free_manp(struct man_node *manp);
 321 static void init_bintoman(void);
 322 static char *path_to_manpath(char *);
 323 static int dupcheck(struct man_node *, struct dupnode **);
 324 static void free_dupnode(struct dupnode *);
 325 static void print_manpath(struct man_node *, char *);
 326 
 327 /*
 328  * This flag is used when the SGML-to-troff converter
 329  * is absent - all the SGML searches are bypassed.
 330  */
 331 static int no_sroff = 0;
 332 
 333 /*
 334  * This flag is used to describe the case where we've found
 335  * an SGML formatted manpage in the sman directory, we haven't
 336  * found a troff formatted manpage, and we don't have the SGML to troff
 337  * conversion utility on the system.
 338  */
 339 static int sman_no_man_no_sroff;
 340 
 341 static char language[PATH_MAX + 1];     /* LC_MESSAGES */
 342 static char localedir[PATH_MAX + 1];    /* locale specific path component */
 343 
 344 static int      defaultmandir = 1;      /* if processing default mandir, 1 */
 345 
 346 static char *newsection = NULL;
 347 
 348 int
 349 main(int argc, char *argv[])
 350 {
 351         int badopts = 0;
 352         int c;
 353         char **pathv;
 354         char *cmdname;
 355         char *manpath = NULL;
 356         static struct man_node  *manpage = NULL;
 357         int bmp_flags = 0;
 358         int err = 0;
 359 
 360         if (access(SROFF_CMD, F_OK | X_OK) != 0)
 361                 no_sroff = 1;
 362 
 363         (void) setlocale(LC_ALL, "");
 364         (void) strcpy(language, setlocale(LC_MESSAGES, (char *)0));
 365         if (strcmp("C", language) != 0)
 366                 (void) sprintf(localedir, "%s", language);
 367 
 368 #if !defined(TEXT_DOMAIN)
 369 #define TEXT_DOMAIN "SYS_TEST"
 370 #endif
 371         (void) textdomain(TEXT_DOMAIN);
 372 
 373         (void) strcpy(macros, TMAC_AN);
 374 
 375         /*
 376          * get base part of command name
 377          */
 378         if ((cmdname = strrchr(argv[0], '/')) != NULL)
 379                 cmdname++;
 380         else
 381                 cmdname = argv[0];
 382 
 383         if (eq(cmdname, "apropos") || eq(cmdname, "whatis")) {
 384                 whatis++;
 385                 apropos = (*cmdname == 'a');
 386                 if ((optind = 1) == argc) {
 387                         (void) fprintf(stderr, gettext("%s what?\n"), cmdname);
 388                         exit(2);
 389                 }
 390                 goto doargs;
 391         } else if (eq(cmdname, "catman"))
 392                 catmando++;
 393 
 394         opterr = 0;
 395         while ((c = getopt(argc, argv, opts[catmando])) != -1)
 396                 switch (c) {
 397 
 398                 /*
 399                  * man specific options
 400                  */
 401                 case 'k':
 402                         apropos++;
 403                         /*FALLTHROUGH*/
 404                 case 'f':
 405                         whatis++;
 406                         break;
 407                 case 'F':
 408                         force++;        /* do lookups the hard way */
 409                         break;
 410                 case 's':
 411                         mansec = optarg;
 412                         sargs++;
 413                         break;
 414                 case 'r':
 415                         nomore++, troffit++;
 416                         break;
 417                 case 'l':
 418                         list++;         /* implies all */
 419                         /*FALLTHROUGH*/
 420                 case 'a':
 421                         all++;
 422                         break;
 423                 case 'd':
 424                         debug++;
 425                         break;
 426                 /*
 427                  * man and catman use -p differently.  In catman it
 428                  * enables debug mode and in man it prints the (possibly
 429                  * derived from PATH or name operand) MANPATH.
 430                  */
 431                 case 'p':
 432                         if (catmando == 0) {
 433                                 printmp++;
 434                         } else {
 435                                 debug++;
 436                         }
 437                         break;
 438                 case 'n':
 439                         nowhatis++;
 440                         break;
 441                 case 'w':
 442                         whatonly++;
 443                         break;
 444                 case 'c':       /* n|troff compatibility */
 445                         if (no_sroff)
 446                                 (void) fprintf(stderr, gettext(
 447                                     "catman: SGML conversion not "
 448                                     "available -- -c flag ignored\n"));
 449                         else
 450                                 compargs++;
 451                         continue;
 452 
 453                 /*
 454                  * shared options
 455                  */
 456                 case 'P':       /* Backwards compatibility */
 457                 case 'M':       /* Respecify path for man pages. */
 458                         manpath = optarg;
 459                         margs++;
 460                         break;
 461                 case 'T':       /* Respecify man macros */
 462                         (void) strcpy(macros, optarg);
 463                         Tflag++;
 464                         break;
 465                 case 't':
 466                         troffit++;
 467                         break;
 468                 case '?':
 469                         badopts++;
 470                 }
 471 
 472         /*
 473          *  Bad options or no args?
 474          *      (man -p and catman don't need args)
 475          */
 476         if (badopts || (!catmando && !printmp && optind == argc)) {
 477                 (void) fprintf(stderr, "%s\n", catmando ?
 478                     gettext(CATMAN_USAGE) : gettext(MAN_USAGE));
 479                 exit(2);
 480         }
 481 
 482         if (compargs && (nowhatis || whatonly || troffit)) {
 483                 (void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
 484                 (void) fprintf(stderr, gettext(
 485                     "-c option cannot be used with [-w][-n][-t]\n"));
 486                 exit(2);
 487         }
 488 
 489         if (sargs && margs && catmando) {
 490                 (void) fprintf(stderr, "%s\n", gettext(CATMAN_USAGE));
 491                 exit(2);
 492         }
 493 
 494         if (troffit == 0 && nomore == 0 && !isatty(fileno(stdout)))
 495                 nomore++;
 496 
 497         /*
 498          * Collect environment information.
 499          */
 500         if (troffit) {
 501                 if ((troffcmd = getenv("TROFF")) == NULL)
 502                         troffcmd = TROFF;
 503                 if ((troffcat = getenv("TCAT")) == NULL)
 504                         troffcat = TCAT;
 505         } else {
 506                 if (((pager = getenv("PAGER")) == NULL) ||
 507                     (*pager == NULL))
 508                         pager = MORE;
 509         }
 510 
 511 doargs:
 512         subdirs = troffit ? troffdirs : nroffdirs;
 513 
 514         init_bintoman();
 515 
 516         if (manpath == NULL && (manpath = getenv("MANPATH")) == NULL) {
 517                 if ((manpath = getenv("PATH")) != NULL) {
 518                         bmp_flags = BMP_ISPATH | BMP_APPEND_MANDIR;
 519                 } else {
 520                         manpath = MANDIR;
 521                 }
 522         }
 523 
 524         pathv = split(manpath, ':');
 525 
 526         manpage = build_manpath(pathv, bmp_flags);
 527 
 528         /* release pathv allocated by split() */
 529         freev(pathv);
 530 
 531         fullpaths(&manpage);
 532 
 533         if (catmando) {
 534                 catman(manpage, argv+optind, argc-optind);
 535                 exit(0);
 536         }
 537 
 538         /*
 539          * The manual routine contains windows during which
 540          * termination would leave a temp file behind.  Thus
 541          * we blanket the whole thing with a clean-up routine.
 542          */
 543         if (signal(SIGINT, SIG_IGN) == SIG_DFL) {
 544                 (void) signal(SIGINT, bye);
 545                 (void) signal(SIGQUIT, bye);
 546                 (void) signal(SIGTERM, bye);
 547         }
 548 
 549         /*
 550          * "man -p" without operands
 551          */
 552         if ((printmp != 0) && (optind == argc)) {
 553                 print_manpath(manpage, NULL);
 554                 exit(0);
 555         }
 556 
 557         for (; optind < argc; optind++) {
 558                 if (strcmp(argv[optind], "-") == 0) {
 559                         nomore++;
 560                         CAT = CAT_S;
 561                 } else {
 562                         char *cmd;
 563                         static struct man_node *mp;
 564                         char *pv[2];
 565 
 566                         /*
 567                          * If full path to command specified, customize
 568                          * manpath accordingly
 569                          */
 570                         if ((cmd = strrchr(argv[optind], '/')) != NULL) {
 571                                 *cmd = '\0';
 572                                 if ((pv[0] = strdup(argv[optind])) == NULL) {
 573                                         malloc_error();
 574                                 }
 575                                 pv[1] = NULL;
 576                                 *cmd = '/';
 577                                 mp = build_manpath(pv,
 578                                     BMP_ISPATH|BMP_FALLBACK_MANDIR);
 579                         } else {
 580                                 mp = manpage;
 581                         }
 582 
 583                         if (whatis) {
 584                                 whatapro(mp, argv[optind], apropos);
 585                         } else if (printmp != 0) {
 586                                 print_manpath(mp, argv[optind]);
 587                         } else {
 588                                 err += manual(mp, argv[optind]);
 589                         }
 590 
 591                         if (mp != NULL && mp != manpage) {
 592                                 free(pv[0]);
 593                                 free_manp(mp);
 594                         }
 595                 }
 596         }
 597         return (err == 0 ? 0 : 1);
 598         /*NOTREACHED*/
 599 }
 600 
 601 /*
 602  * This routine builds the manpage structure from MANPATH or PATH,
 603  * depending on flags.  See BMP_* definitions above for valid
 604  * flags.
 605  *
 606  * Assumes pathv elements were malloc'd, as done by split().
 607  * Elements may be freed and reallocated to have different contents.
 608  */
 609 
 610 static struct man_node *
 611 build_manpath(char **pathv, int flags)
 612 {
 613         struct man_node *manpage = NULL;
 614         struct man_node *currp = NULL;
 615         struct man_node *lastp = NULL;
 616         char **p;
 617         char **q;
 618         char *mand = NULL;
 619         char *mandir = MANDIR;
 620         int s;
 621         struct dupnode *didup = NULL;
 622         struct stat sb;
 623 
 624         s = sizeof (struct man_node);
 625         for (p = pathv; *p; ) {
 626 
 627                 if (flags & BMP_ISPATH) {
 628                         if ((mand = path_to_manpath(*p)) == NULL) {
 629                                 goto next;
 630                         }
 631                         free(*p);
 632                         *p = mand;
 633                 }
 634                 q = split(*p, ',');
 635                 if (stat(q[0], &sb) != 0 || (sb.st_mode & S_IFDIR) == 0) {
 636                         freev(q);
 637                         goto next;
 638                 }
 639 
 640                 if (access(q[0], R_OK|X_OK) != 0) {
 641                         if (catmando) {
 642                                 (void) fprintf(stderr,
 643                                     gettext("%s is not accessible.\n"),
 644                                     q[0]);
 645                                 (void) fflush(stderr);
 646                         }
 647                 } else {
 648 
 649                         /*
 650                          * Some element exists.  Do not append MANDIR as a
 651                          * fallback.
 652                          */
 653                         flags &= ~BMP_FALLBACK_MANDIR;
 654 
 655                         if ((currp = (struct man_node *)calloc(1, s)) == NULL) {
 656                                 malloc_error();
 657                         }
 658 
 659                         currp->frompath = (flags & BMP_ISPATH);
 660 
 661                         if (manpage == NULL) {
 662                                 lastp = manpage = currp;
 663                         }
 664 
 665                         getpath(currp, p);
 666                         getsect(currp, p);
 667 
 668                         /*
 669                          * If there are no new elements in this path,
 670                          * do not add it to the manpage list
 671                          */
 672                         if (dupcheck(currp, &didup) != 0) {
 673                                 freev(currp->secv);
 674                                 free(currp);
 675                         } else {
 676                                 currp->next = NULL;
 677                                 if (currp != manpage) {
 678                                         lastp->next = currp;
 679                                 }
 680                                 lastp = currp;
 681                         }
 682                 }
 683                 freev(q);
 684 next:
 685                 /*
 686                  * Special handling of appending MANDIR.
 687                  * After all pathv elements have been processed, append MANDIR
 688                  * if needed.
 689                  */
 690                 if (p == &mandir) {
 691                         break;
 692                 }
 693                 p++;
 694                 if (*p != NULL) {
 695                         continue;
 696                 }
 697                 if (flags & (BMP_APPEND_MANDIR|BMP_FALLBACK_MANDIR)) {
 698                         p = &mandir;
 699                         flags &= ~BMP_ISPATH;
 700                 }
 701         }
 702 
 703         free_dupnode(didup);
 704 
 705         return (manpage);
 706 }
 707 
 708 /*
 709  * Stores the mandir path into the manp structure.
 710  */
 711 
 712 static void
 713 getpath(struct man_node *manp, char **pv)
 714 {
 715         char *s;
 716         int i = 0;
 717 
 718         s = *pv;
 719 
 720         while (*s != NULL && *s != ',')
 721                 i++, s++;
 722 
 723         manp->path = (char *)malloc(i+1);
 724         if (manp->path == NULL)
 725                 malloc_error();
 726         (void) strncpy(manp->path, *pv, i);
 727         *(manp->path + i) = '\0';
 728 }
 729 
 730 /*
 731  * Stores the mandir's corresponding sections (submandir
 732  * directories) into the manp structure.
 733  */
 734 
 735 static void
 736 getsect(struct man_node *manp, char **pv)
 737 {
 738         char *sections;
 739         char **sectp;
 740 
 741         if (sargs) {
 742                 manp->secv = split(mansec, ',');
 743 
 744                 for (sectp = manp->secv; *sectp; sectp++)
 745                         lower(*sectp);
 746         } else if ((sections = strchr(*pv, ',')) != NULL) {
 747                 if (debug) {
 748                         if (manp->frompath != 0) {
 749 /*
 750  * TRANSLATION_NOTE - message for man -d or catman -p
 751  * ex. /usr/share/man: derived from PATH, MANSECTS=,1b
 752  */
 753                                 (void) printf(gettext(
 754                                     "%s: derived from PATH, MANSECTS=%s\n"),
 755                                     manp->path, sections);
 756                         } else {
 757 /*
 758  * TRANSLATION_NOTE - message for man -d or catman -p
 759  * ex. /usr/share/man: from -M option, MANSECTS=,1,2,3c
 760  */
 761                                 (void) fprintf(stdout, gettext(
 762                                     "%s: from -M option, MANSECTS=%s\n"),
 763                                     manp->path, sections);
 764                         }
 765                 }
 766                 manp->secv = split(++sections, ',');
 767                 for (sectp = manp->secv; *sectp; sectp++)
 768                         lower(*sectp);
 769 
 770                 if (*manp->secv == NULL)
 771                         get_all_sect(manp);
 772         } else if ((sections = check_config(*pv)) != NULL) {
 773                 manp->defsrch = 1;
 774 /*
 775  * TRANSLATION_NOTE - message for man -d or catman -p
 776  * ex. /usr/share/man: from man.cf, MANSECTS=1,1m,1c
 777  */
 778                 if (debug)
 779                         (void) fprintf(stdout, gettext(
 780                             "%s: from %s, MANSECTS=%s\n"),
 781                             manp->path, CONFIG, sections);
 782                 manp->secv = split(sections, ',');
 783 
 784                 for (sectp = manp->secv; *sectp; sectp++)
 785                         lower(*sectp);
 786 
 787                 if (*manp->secv == NULL)
 788                         get_all_sect(manp);
 789         } else {
 790                 manp->defsrch = 1;
 791 /*
 792  * TRANSLATION_NOTE - message for man -d or catman -p
 793  * if man.cf has not been found or sections has not been specified
 794  * man/catman searches the sections lexicographically.
 795  */
 796                 if (debug)
 797                         (void) fprintf(stdout, gettext(
 798                             "%s: search the sections lexicographically\n"),
 799                             manp->path);
 800                 manp->secv = NULL;
 801                 get_all_sect(manp);
 802         }
 803 }
 804 
 805 /*
 806  * Get suffices of all sub-mandir directories in a mandir.
 807  */
 808 
 809 static void
 810 get_all_sect(struct man_node *manp)
 811 {
 812         DIR *dp;
 813         char **dirv;
 814         char **dv;
 815         char **p;
 816         char *prev = NULL;
 817         char *tmp = NULL;
 818         int  plen;
 819         int     maxentries = MAXTOKENS;
 820         int     entries = 0;
 821 
 822         if ((dp = opendir(manp->path)) == 0)
 823                 return;
 824 
 825         /*
 826          * sortdir() allocates memory for dirv and dirv[].
 827          */
 828         sortdir(dp, &dirv);
 829 
 830         (void) closedir(dp);
 831 
 832         if (manp->secv == NULL) {
 833                 /*
 834                  * allocates memory for manp->secv only if it's NULL
 835                  */
 836                 manp->secv = (char **)malloc(maxentries * sizeof (char *));
 837                 if (manp->secv == NULL)
 838                         malloc_error();
 839         }
 840 
 841         for (dv = dirv, p = manp->secv; *dv; dv++) {
 842                 plen = PLEN;
 843                 if (match(*dv, SGMLDIR, PLEN+1))
 844                         ++plen;
 845 
 846                 if (strcmp(*dv, CONFIG) == 0) {
 847                         /* release memory allocated by sortdir */
 848                         free(*dv);
 849                         continue;
 850                 }
 851 
 852                 if (tmp != NULL)
 853                         free(tmp);
 854                 tmp = strdup(*dv + plen);
 855                 if (tmp == NULL)
 856                         malloc_error();
 857                 (void) sprintf(tmp, "%s", *dv + plen);
 858 
 859                 if (prev != NULL) {
 860                         if (strcmp(prev, tmp) == 0) {
 861                                 /* release memory allocated by sortdir */
 862                                 free(*dv);
 863                                 continue;
 864                         }
 865                 }
 866 
 867                 if (prev != NULL)
 868                         free(prev);
 869                 prev = strdup(*dv + plen);
 870                 if (prev == NULL)
 871                         malloc_error();
 872                 (void) sprintf(prev, "%s", *dv + plen);
 873                 /*
 874                  * copy the string in (*dv + plen) to *p
 875                  */
 876                 *p = strdup(*dv + plen);
 877                 if (*p == NULL)
 878                         malloc_error();
 879                 p++;
 880                 entries++;
 881                 if (entries == maxentries) {
 882                         maxentries += MAXTOKENS;
 883                         manp->secv = (char **)realloc(manp->secv,
 884                             sizeof (char *) * maxentries);
 885                         if (manp->secv == NULL)
 886                                 malloc_error();
 887                         p = manp->secv + entries;
 888                 }
 889                 /* release memory allocated by sortdir */
 890                 free(*dv);
 891         }
 892         *p = 0;
 893         /* release memory allocated by sortdir */
 894         free(dirv);
 895 }
 896 
 897 /*
 898  * Format man pages (build cat pages); if no
 899  * sections are specified, build all of them.
 900  * When building cat pages:
 901  *      catman() tries to build cat pages for locale specific
 902  *      man dirs first.  Then, catman() tries to build cat pages
 903  *      for the default man dir (for C locale like /usr/share/man)
 904  *      regardless of the locale.
 905  * When building windex file:
 906  *      catman() tries to build windex file for locale specific
 907  *      man dirs first.  Then, catman() tries to build windex file
 908  *      for the default man dir (for C locale like /usr/share/man)
 909  *      regardless of the locale.
 910  */
 911 
 912 static void
 913 catman(struct man_node *manp, char **argv, int argc)
 914 {
 915         char cmdbuf[BUFSIZ];
 916         char **dv;
 917         int changed;
 918         struct man_node *p;
 919         int ndirs = 0;
 920         char *ldir;
 921         int     i;
 922         struct dupnode *dnp = NULL;
 923         char   **realsecv;
 924         /*
 925          * May be overwritten in dupcheck() so must be kept out of .rodata.
 926          */
 927         char    fakename[] = " catman ";
 928         char    *fakesecv[2];
 929 
 930         fakesecv[0] = fakename;
 931         fakesecv[1] = NULL;
 932 
 933         for (p = manp; p != NULL; p = p->next) {
 934                 /*
 935                  * prevent catman from doing very heavy lifting multiple
 936                  * times on some directory
 937                  */
 938                 realsecv = p->secv;
 939                 p->secv = fakesecv;
 940                 if (dupcheck(p, &dnp) != 0) {
 941                         p->secv = realsecv;
 942                         continue;
 943                 }
 944 
 945 /*
 946  * TRANSLATION_NOTE - message for catman -p
 947  * ex. mandir path = /usr/share/man
 948  */
 949                 if (debug)
 950                         (void) fprintf(stdout, gettext(
 951                             "\nmandir path = %s\n"), p->path);
 952                 ndirs = 0;
 953 
 954                 /*
 955                  * Build cat pages
 956                  * addlocale() allocates memory and returns it
 957                  */
 958                 ldir = addlocale(p->path);
 959                 if (!whatonly) {
 960                         if (*localedir != '\0') {
 961                                 if (defaultmandir)
 962                                         defaultmandir = 0;
 963                                 /* getdirs allocate memory for dv */
 964                                 ndirs = getdirs(ldir, &dv, 1);
 965                                 if (ndirs != 0) {
 966                                         changed = argc ?
 967                                             makecat(ldir, argv, argc) :
 968                                             makecat(ldir, dv, ndirs);
 969                                         /* release memory by getdirs */
 970                                         for (i = 0; i < ndirs; i++) {
 971                                                 free(dv[i]);
 972                                         }
 973                                         free(dv);
 974                                 }
 975                         }
 976 
 977                         /* default man dir is always processed */
 978                         defaultmandir = 1;
 979                         ndirs = getdirs(p->path, &dv, 1);
 980                         changed = argc ?
 981                             makecat(p->path, argv, argc) :
 982                             makecat(p->path, dv, ndirs);
 983                         /* release memory allocated by getdirs */
 984                         for (i = 0; i < ndirs; i++) {
 985                                 free(dv[i]);
 986                         }
 987                         free(dv);
 988                 }
 989                 /*
 990                  * Build whatis database
 991                  *  print error message if locale is set and man dir not found
 992                  *  won't build it at all if -c option is on
 993                  */
 994                 if (!compargs && (whatonly || (!nowhatis && changed))) {
 995                         if (*localedir != '\0') {
 996                                 /* just count the number of ndirs */
 997                                 if ((ndirs = getdirs(ldir, NULL, 0)) != 0) {
 998                                         (void) sprintf(cmdbuf,
 999                                             "/usr/bin/sh %s %s",
1000                                             MAKEWHATIS, ldir);
1001                                         (void) sys(cmdbuf);
1002                                 }
1003                         }
1004                         /* whatis database of the default man dir */
1005                         /* will be always built in C locale. */
1006                         (void) sprintf(cmdbuf,
1007                             "/usr/bin/sh %s %s",
1008                             MAKEWHATIS, p->path);
1009                         (void) sys(cmdbuf);
1010                 }
1011                 /* release memory allocated by addlocale() */
1012                 free(ldir);
1013         }
1014         free_dupnode(dnp);
1015 }
1016 
1017 /*
1018  * Build cat pages for given sections
1019  */
1020 
1021 static int
1022 makecat(char *path, char **dv, int ndirs)
1023 {
1024         DIR *dp, *sdp;
1025         struct dirent *d;
1026         struct stat sbuf;
1027         char mandir[MAXPATHLEN+1];
1028         char smandir[MAXPATHLEN+1];
1029         char catdir[MAXPATHLEN+1];
1030         char *dirp, *sdirp;
1031         int i, fmt;
1032         int manflag, smanflag;
1033 
1034         for (i = fmt = 0; i < ndirs; i++) {
1035                 (void) snprintf(mandir, MAXPATHLEN, "%s/%s%s",
1036                     path, MANDIRNAME, dv[i]);
1037                 (void) snprintf(smandir, MAXPATHLEN, "%s/%s%s",
1038                     path, SGMLDIR, dv[i]);
1039                 (void) snprintf(catdir, MAXPATHLEN, "%s/%s%s",
1040                     path, subdirs[1], dv[i]);
1041                 dirp = strrchr(mandir, '/') + 1;
1042                 sdirp = strrchr(smandir, '/') + 1;
1043 
1044                 manflag = smanflag = 0;
1045 
1046                 if ((dp = opendir(mandir)) != NULL)
1047                         manflag = 1;
1048 
1049                 if (!no_sroff && (sdp = opendir(smandir)) != NULL)
1050                         smanflag = 1;
1051 
1052                 if (dp == 0 && sdp == 0) {
1053                         if (strcmp(mandir, CONFIG) == 0)
1054                                 perror(mandir);
1055                         continue;
1056                 }
1057 /*
1058  * TRANSLATION_NOTE - message for catman -p
1059  * ex. Building cat pages for mandir = /usr/share/man/ja
1060  */
1061                 if (debug)
1062                         (void) fprintf(stdout, gettext(
1063                             "Building cat pages for mandir = %s\n"), path);
1064 
1065                 if (!compargs && stat(catdir, &sbuf) < 0) {
1066                         (void) umask(02);
1067 /*
1068  * TRANSLATION_NOTE - message for catman -p
1069  * ex. mkdir /usr/share/man/ja/cat3c
1070  */
1071                         if (debug)
1072                                 (void) fprintf(stdout, gettext("mkdir %s\n"),
1073                                     catdir);
1074                         else {
1075                                 if (mkdir(catdir, 0755) < 0) {
1076                                         perror(catdir);
1077                                         continue;
1078                                 }
1079                                 (void) chmod(catdir, 0755);
1080                         }
1081                 }
1082 
1083                 /*
1084                  * if it is -c option of catman, if there is no
1085                  * coresponding man dir for sman files to go to,
1086                  * make the man dir
1087                  */
1088 
1089                 if (compargs && !manflag) {
1090                         if (mkdir(mandir, 0755) < 0) {
1091                                 perror(mandir);
1092                                 continue;
1093                         }
1094                         (void) chmod(mandir, 0755);
1095                 }
1096 
1097                 if (smanflag) {
1098                         while ((d = readdir(sdp))) {
1099                                 if (eq(".", d->d_name) || eq("..", d->d_name))
1100                                         continue;
1101 
1102                                 if (format(path, sdirp, (char *)0, d->d_name)
1103                                     > 0)
1104                                         fmt++;
1105                         }
1106                 }
1107 
1108                 if (manflag && !compargs) {
1109                         while ((d = readdir(dp))) {
1110                                 if (eq(".", d->d_name) || eq("..", d->d_name))
1111                                         continue;
1112 
1113                                 if (format(path, dirp, (char *)0, d->d_name)
1114                                     > 0)
1115                                         fmt++;
1116                         }
1117                 }
1118 
1119                 if (manflag)
1120                         (void) closedir(dp);
1121 
1122                 if (smanflag)
1123                         (void) closedir(sdp);
1124 
1125         }
1126         return (fmt);
1127 }
1128 
1129 
1130 /*
1131  * Get all "man" and "sman" dirs under a given manpath
1132  * and return the number found
1133  * If -c option is on, only count sman dirs
1134  */
1135 
1136 static int
1137 getdirs(char *path, char ***dirv, short flag)
1138 {
1139         DIR *dp;
1140         struct dirent *d;
1141         int n = 0;
1142         int plen, sgml_flag, man_flag;
1143         int i = 0;
1144         int     maxentries = MAXDIRS;
1145         char    **dv;
1146 
1147         if ((dp = opendir(path)) == 0) {
1148                 if (debug) {
1149                         if (*localedir != '\0')
1150                                 (void) printf(gettext("\
1151 locale is %s, search in %s\n"), localedir, path);
1152                         perror(path);
1153                 }
1154                 return (0);
1155         }
1156 
1157         if (flag) {
1158                 /* allocate memory for dirv */
1159                 *dirv = (char **)malloc(sizeof (char *) *
1160                     maxentries);
1161                 if (*dirv == NULL)
1162                         malloc_error();
1163                 dv = *dirv;
1164         }
1165         while ((d = readdir(dp))) {
1166                 plen = PLEN;
1167                 man_flag = sgml_flag = 0;
1168                 if (match(d->d_name, SGMLDIR, PLEN+1)) {
1169                         plen = PLEN + 1;
1170                         sgml_flag = 1;
1171                         i++;
1172                 }
1173 
1174                 if (match(subdirs[0], d->d_name, PLEN))
1175                         man_flag = 1;
1176 
1177                 if (compargs && sgml_flag) {
1178                         if (flag) {
1179                                 *dv = strdup(d->d_name+plen);
1180                                 if (*dv == NULL)
1181                                         malloc_error();
1182                                 dv++;
1183                                 n = i;
1184                         }
1185                 } else if (!compargs && (sgml_flag || man_flag)) {
1186                         if (flag) {
1187                                 *dv = strdup(d->d_name+plen);
1188                                 if (*dv == NULL)
1189                                         malloc_error();
1190                                 dv++;
1191                         }
1192                         n++;
1193                 }
1194                 if (flag) {
1195                         if ((dv - *dirv) == maxentries) {
1196                                 int entries = maxentries;
1197                                 maxentries += MAXTOKENS;
1198                                 *dirv = (char **)realloc(*dirv,
1199                                     sizeof (char *) * maxentries);
1200                                 if (*dirv == NULL)
1201                                         malloc_error();
1202                                 dv = *dirv + entries;
1203                         }
1204                 }
1205         }
1206 
1207         (void) closedir(dp);
1208         return (n);
1209 }
1210 
1211 
1212 /*
1213  * Find matching whatis or apropos entries
1214  * whatapro() tries to handle the windex file of the locale specific
1215  * man dirs first, then tries to handle the windex file of the default
1216  * man dir (of C locale like /usr/share/man).
1217  */
1218 
1219 static void
1220 whatapro(struct man_node *manp, char *word, int apropos)
1221 {
1222         char whatpath[MAXPATHLEN+1];
1223         char *p;
1224         struct man_node *b;
1225         int ndirs = 0;
1226         char *ldir;
1227 
1228 
1229 /*
1230  * TRANSLATION_NOTE - message for man -d
1231  * %s takes a parameter to -k option.
1232  */
1233         DPRINTF(gettext("word = %s \n"), word);
1234 
1235         /*
1236          * get base part of name
1237          */
1238         if (!apropos) {
1239                 if ((p = strrchr(word, '/')) == NULL)
1240                         p = word;
1241                 else
1242                         p++;
1243         } else {
1244                 p = word;
1245         }
1246 
1247         for (b = manp; b != NULL; b = b->next) {
1248 
1249                 if (*localedir != '\0') {
1250                         /* addlocale() allocates memory and returns it */
1251                         ldir = addlocale(b->path);
1252                         if (defaultmandir)
1253                                 defaultmandir = 0;
1254                         ndirs = getdirs(ldir, NULL, 0);
1255                         if (ndirs != 0) {
1256                                 (void) sprintf(whatpath, "%s/%s", ldir, WHATIS);
1257 /*
1258  * TRANSLATION_NOTE - message for man -d
1259  * ex. mandir path = /usr/share/man/ja
1260  */
1261                                 DPRINTF(gettext("\nmandir path = %s\n"), ldir);
1262                                 lookup_windex(whatpath, p, b->secv);
1263                         }
1264                         /* release memory allocated by addlocale() */
1265                         free(ldir);
1266                 }
1267 
1268                 defaultmandir = 1;
1269                 (void) sprintf(whatpath, "%s/%s", b->path, WHATIS);
1270 /*
1271  * TRANSLATION_NOTE - message for man -d
1272  * ex. mandir path = /usr/share/man
1273  */
1274                 DPRINTF(gettext("\nmandir path = %s\n"), b->path);
1275 
1276                 lookup_windex(whatpath, p, b->secv);
1277         }
1278 }
1279 
1280 
1281 static void
1282 lookup_windex(char *whatpath, char *word, char **secv)
1283 {
1284         FILE *fp;
1285         char *matches[MAXPAGES];
1286         char **pp;
1287         wchar_t wbuf[BUFSIZ];
1288         wchar_t *word_wchar = NULL;
1289         wchar_t *ws;
1290         size_t  word_len, ret;
1291 
1292         if ((fp = fopen(whatpath, "r")) == NULL) {
1293                 perror(whatpath);
1294                 return;
1295         }
1296 
1297         if (apropos) {
1298                 word_len = strlen(word) + 1;
1299                 if ((word_wchar = (wchar_t *)malloc(sizeof (wchar_t) *
1300                     word_len)) == NULL) {
1301                         malloc_error();
1302                 }
1303                 ret = mbstowcs(word_wchar, (const char *)word, word_len);
1304                 if (ret == (size_t)-1) {
1305                         (void) fprintf(stderr, gettext(
1306                             "Invalid character in keyword\n"));
1307                         exit(1);
1308                 }
1309                 while (fgetws(wbuf, BUFSIZ, fp) != NULL)
1310                         for (ws = wbuf; *ws; ws++)
1311                                 if (icmp(word_wchar, ws) == 0) {
1312                                         (void) printf("%ws", wbuf);
1313                                         break;
1314                                 }
1315         } else {
1316                 if (bfsearch(fp, matches, word, secv))
1317                         for (pp = matches; *pp; pp++) {
1318                                 (void) printf("%s", *pp);
1319                                 /*
1320                                  * release memory allocated by
1321                                  * strdup() in bfsearch()
1322                                  */
1323                                 free(*pp);
1324                         }
1325         }
1326         (void) fclose(fp);
1327         if (word_wchar)
1328                 free(word_wchar);
1329 
1330 }
1331 
1332 
1333 /*
1334  * case-insensitive compare unless upper case is used
1335  * ie)  "mount" matches mount, Mount, MOUNT
1336  *      "Mount" matches Mount, MOUNT
1337  *      "MOUNT" matches MOUNT only
1338  *      If matched return 0.  Otherwise, return 1.
1339  */
1340 
1341 static int
1342 icmp(wchar_t *ws, wchar_t *wt)
1343 {
1344         for (; (*ws == 0) ||
1345             (*ws == (iswupper(*ws) ? *wt: towlower(*wt)));
1346             ws++, wt++)
1347                 if (*ws == 0)
1348                         return (0);
1349 
1350         return (1);
1351 }
1352 
1353 
1354 /*
1355  * Invoke PAGER with all matching man pages
1356  */
1357 
1358 static void
1359 more(char **pages, int plain)
1360 {
1361         char cmdbuf[BUFSIZ];
1362         char **vp;
1363 
1364         /*
1365          * Dont bother.
1366          */
1367         if (list || (*pages == 0))
1368                 return;
1369 
1370         if (plain && troffit) {
1371                 cleanup(pages);
1372                 return;
1373         }
1374         (void) sprintf(cmdbuf, "%s", troffit ? troffcat :
1375             plain ? CAT : pager);
1376 
1377         /*
1378          * Build arg list
1379          */
1380         for (vp = pages; vp < endp; vp++) {
1381                 (void) strcat(cmdbuf, " ");
1382                 (void) strcat(cmdbuf, *vp);
1383         }
1384         (void) sys(cmdbuf);
1385         cleanup(pages);
1386 }
1387 
1388 
1389 /*
1390  * Get rid of dregs.
1391  */
1392 
1393 static void
1394 cleanup(char **pages)
1395 {
1396         char **vp;
1397 
1398         for (vp = pages; vp < endp; vp++) {
1399                 if (match(TEMPLATE, *vp, TMPLEN))
1400                         (void) unlink(*vp);
1401                 free(*vp);
1402         }
1403 
1404         endp = pages;   /* reset */
1405 }
1406 
1407 
1408 /*
1409  * Clean things up after receiving a signal.
1410  */
1411 
1412 /*ARGSUSED*/
1413 static void
1414 bye(int sig)
1415 {
1416         cleanup(pages);
1417         exit(1);
1418         /*NOTREACHED*/
1419 }
1420 
1421 
1422 /*
1423  * Split a string by specified separator.
1424  *    ignore empty components/adjacent separators.
1425  *    returns vector to all tokens
1426  */
1427 
1428 static char **
1429 split(char *s1, char sep)
1430 {
1431         char **tokv, **vp;
1432         char *mp, *tp;
1433         int maxentries = MAXTOKENS;
1434         int entries = 0;
1435 
1436         tokv = vp = (char **)malloc(maxentries * sizeof (char *));
1437         if (tokv == NULL)
1438                 malloc_error();
1439         mp = s1;
1440         for (; mp && *mp; mp = tp) {
1441                 tp = strchr(mp, sep);
1442                 if (mp == tp) {         /* empty component */
1443                         tp++;                   /* ignore */
1444                         continue;
1445                 }
1446                 if (tp) {
1447                         /* a component found */
1448                         size_t  len;
1449 
1450                         len = tp - mp;
1451                         *vp = (char *)malloc(sizeof (char) * len + 1);
1452                         if (*vp == NULL)
1453                                 malloc_error();
1454                         (void) strncpy(*vp, mp, len);
1455                         *(*vp + len) = '\0';
1456                         tp++;
1457                         vp++;
1458                 } else {
1459                         /* the last component */
1460                         *vp = strdup(mp);
1461                         if (*vp == NULL)
1462                                 malloc_error();
1463                         vp++;
1464                 }
1465                 entries++;
1466                 if (entries == maxentries) {
1467                         maxentries += MAXTOKENS;
1468                         tokv = (char **)realloc(tokv,
1469                             maxentries * sizeof (char *));
1470                         if (tokv == NULL)
1471                                 malloc_error();
1472                         vp = tokv + entries;
1473                 }
1474         }
1475         *vp = 0;
1476         return (tokv);
1477 }
1478 
1479 /*
1480  * Free a vector allocated by split();
1481  */
1482 static void
1483 freev(char **v)
1484 {
1485         int i;
1486         if (v != NULL) {
1487                 for (i = 0; v[i] != NULL; i++) {
1488                         free(v[i]);
1489                 }
1490                 free(v);
1491         }
1492 }
1493 
1494 /*
1495  * Convert paths to full paths if necessary
1496  *
1497  */
1498 
1499 static void
1500 fullpaths(struct man_node **manp_head)
1501 {
1502         char *cwd = NULL;
1503         char *p;
1504         char cwd_gotten = 0;
1505         struct man_node *manp = *manp_head;
1506         struct man_node *b;
1507         struct man_node *prev = NULL;
1508 
1509         for (b = manp; b != NULL; b = b->next) {
1510                 if (*(b->path) == '/') {
1511                         prev = b;
1512                         continue;
1513                 }
1514 
1515                 /* try to get cwd if haven't already */
1516                 if (!cwd_gotten) {
1517                         cwd = getcwd(NULL, MAXPATHLEN+1);
1518                         cwd_gotten = 1;
1519                 }
1520 
1521                 if (cwd) {
1522                         /* case: relative manpath with cwd: make absolute */
1523                         if ((p = malloc(strlen(b->path)+strlen(cwd)+2)) ==
1524                             NULL) {
1525                                 malloc_error();
1526                         }
1527                         (void) sprintf(p, "%s/%s", cwd, b->path);
1528                         /*
1529                          * resetting b->path
1530                          */
1531                         free(b->path);
1532                         b->path = p;
1533                 } else {
1534                         /* case: relative manpath but no cwd: omit path entry */
1535                         if (prev)
1536                                 prev->next = b->next;
1537                         else
1538                                 *manp_head = b->next;
1539 
1540                         free_manp(b);
1541                 }
1542         }
1543         /*
1544          * release memory allocated by getcwd()
1545          */
1546         free(cwd);
1547 }
1548 
1549 /*
1550  * Free a man_node structure and its contents
1551  */
1552 
1553 static void
1554 free_manp(struct man_node *manp)
1555 {
1556         char **p;
1557 
1558         free(manp->path);
1559         p = manp->secv;
1560         while ((p != NULL) && (*p != NULL)) {
1561                 free(*p);
1562                 p++;
1563         }
1564         free(manp->secv);
1565         free(manp);
1566 }
1567 
1568 
1569 /*
1570  * Map (in place) to lower case
1571  */
1572 
1573 static void
1574 lower(char *s)
1575 {
1576         if (s == 0)
1577                 return;
1578         while (*s) {
1579                 if (isupper(*s))
1580                         *s = tolower(*s);
1581                 s++;
1582         }
1583 }
1584 
1585 
1586 /*
1587  * compare for sort()
1588  * sort first by section-spec, then by prefix {sman, man, cat, fmt}
1589  *      note: prefix is reverse sorted so that "sman" and "man" always
1590  *      comes before {cat, fmt}
1591  */
1592 
1593 static int
1594 cmp(const void *arg1, const void *arg2)
1595 {
1596         int n;
1597         char **p1 = (char **)arg1;
1598         char **p2 = (char **)arg2;
1599 
1600 
1601         /* by section; sman always before man dirs */
1602         if ((n = strcmp(*p1 + PLEN + (**p1 == 's' ? 1 : 0),
1603             *p2 + PLEN + (**p2 == 's' ? 1 : 0))))
1604                 return (n);
1605 
1606         /* by prefix reversed */
1607         return (strncmp(*p2, *p1, PLEN));
1608 }
1609 
1610 
1611 /*
1612  * Find a man page ...
1613  *   Loop through each path specified,
1614  *   first try the lookup method (whatis database),
1615  *   and if it doesn't exist, do the hard way.
1616  */
1617 
1618 static int
1619 manual(struct man_node *manp, char *name)
1620 {
1621         struct man_node *p;
1622         struct man_node *local;
1623         int ndirs = 0;
1624         char *ldir;
1625         char *ldirs[2];
1626         char *fullname = name;
1627         char *slash;
1628 
1629         if ((slash = strrchr(name, '/')) != NULL) {
1630                 name = slash + 1;
1631         }
1632 
1633         /*
1634          *  for each path in MANPATH
1635          */
1636         found = 0;
1637 
1638         for (p = manp; p != NULL; p = p->next) {
1639 /*
1640  * TRANSLATION_NOTE - message for man -d
1641  * ex. mandir path = /usr/share/man
1642  */
1643                 DPRINTF(gettext("\nmandir path = %s\n"), p->path);
1644 
1645                 if (*localedir != '\0') {
1646                         /* addlocale() allocates memory and returns it */
1647                         ldir = addlocale(p->path);
1648                         if (defaultmandir)
1649                                 defaultmandir = 0;
1650 /*
1651  * TRANSLATION_NOTE - message for man -d
1652  * ex. localedir = ja, ldir = /usr/share/man/ja
1653  */
1654                         if (debug)
1655                                 (void) printf(gettext(
1656                                     "localedir = %s, ldir = %s\n"),
1657                                     localedir, ldir);
1658                         ndirs = getdirs(ldir, NULL, 0);
1659                         if (ndirs != 0) {
1660                                 ldirs[0] = ldir;
1661                                 ldirs[1] = NULL;
1662                                 local = build_manpath(ldirs, 0);
1663                                 if (force ||
1664                                     windex(local->secv, ldir, name) < 0)
1665                                         mandir(local->secv, ldir, name);
1666                                 free_manp(local);
1667                         }
1668                         /* release memory allocated by addlocale() */
1669                         free(ldir);
1670                 }
1671 
1672                 defaultmandir = 1;
1673                 /*
1674                  * locale mandir not valid, man page in locale
1675                  * mandir not found, or -a option present
1676                  */
1677                 if (ndirs == 0 || !found || all) {
1678                         if (force || windex(p->secv, p->path, name) < 0)
1679                                 mandir(p->secv, p->path, name);
1680                 }
1681 
1682                 if (found && !all)
1683                         break;
1684         }
1685 
1686         if (found) {
1687                 more(pages, nomore);
1688         } else {
1689                 if (sargs) {
1690                         (void) fprintf(stderr, gettext("No entry for %s in "
1691                             "section(s) %s of the manual.\n"),
1692                             fullname, mansec);
1693                 } else {
1694                         (void) fprintf(stderr, gettext(
1695                             "No manual entry for %s.\n"), fullname, mansec);
1696                 }
1697 
1698                 if (sman_no_man_no_sroff)
1699                         (void) fprintf(stderr, gettext("(An SGML manpage was "
1700                             "found for '%s' but it cannot be displayed.)\n"),
1701                             fullname, mansec);
1702         }
1703         sman_no_man_no_sroff = 0;
1704         return (!found);
1705 }
1706 
1707 
1708 /*
1709  * For a specified manual directory,
1710  *      read, store, & sort section subdirs,
1711  *      for each section specified
1712  *              find and search matching subdirs
1713  */
1714 
1715 static void
1716 mandir(char **secv, char *path, char *name)
1717 {
1718         DIR *dp;
1719         char **dirv;
1720         char **dv, **pdv;
1721         int len, dslen, plen = PLEN;
1722 
1723         if ((dp = opendir(path)) == 0) {
1724 /*
1725  * TRANSLATION_NOTE - message for man -d or catman -p
1726  * opendir(%s) returned 0
1727  */
1728                 if (debug)
1729                         (void) fprintf(stdout, gettext(
1730                             " opendir on %s failed\n"), path);
1731                 return;
1732         }
1733 
1734 /*
1735  * TRANSLATION_NOTE - message for man -d or catman -p
1736  * ex. mandir path = /usr/share/man/ja
1737  */
1738         if (debug)
1739                 (void) printf(gettext("mandir path = %s\n"), path);
1740 
1741         /*
1742          * sordir() allocates memory for dirv and dirv[].
1743          */
1744         sortdir(dp, &dirv);
1745         /*
1746          * Search in the order specified by MANSECTS
1747          */
1748         for (; *secv; secv++) {
1749 /*
1750  * TRANSLATION_NOTE - message for man -d or catman -p
1751  * ex.  section = 3c
1752  */
1753                 DPRINTF(gettext("  section = %s\n"), *secv);
1754                 len = strlen(*secv);
1755                 for (dv = dirv; *dv; dv++) {
1756                         plen = PLEN;
1757                         if (*dv[0] == 's')
1758                                 plen++;
1759                         dslen = strlen(*dv+plen);
1760                         if (dslen > len)
1761                                 len = dslen;
1762                         if (**secv == '\\') {
1763                                 if (!eq(*secv + 1, *dv+plen))
1764                                         continue;
1765                         } else if (strncasecmp(*secv, *dv+plen, len) != 0) {
1766                                 /* check to see if directory name changed */
1767                                 if (!all &&
1768                                     (newsection = map_section(*secv, path))
1769                                     == NULL) {
1770                                         continue;
1771                                 }
1772                                 if (newsection == NULL)
1773                                         newsection = "";
1774                                 if (!match(newsection, *dv+plen, len)) {
1775                                         continue;
1776                                 }
1777                         }
1778 
1779                         if (searchdir(path, *dv, name) == 0)
1780                                 continue;
1781 
1782                         if (!all) {
1783                                 /* release memory allocated by sortdir() */
1784                                 pdv = dirv;
1785                                 while (*pdv) {
1786                                         free(*pdv);
1787                                         pdv++;
1788                                 }
1789                                 (void) closedir(dp);
1790                                 /* release memory allocated by sortdir() */
1791                                 free(dirv);
1792                                 return;
1793                         }
1794                         /*
1795                          * if we found a match in the man dir skip
1796                          * the corresponding cat dir if it exists
1797                          */
1798                         if (all && **dv == 'm' && *(dv+1) &&
1799                             eq(*(dv+1)+plen, *dv+plen))
1800                                         dv++;
1801                 }
1802         }
1803         /* release memory allocated by sortdir() */
1804         pdv = dirv;
1805         while (*pdv) {
1806                 free(*pdv);
1807                 pdv++;
1808         }
1809         free(dirv);
1810         (void) closedir(dp);
1811 }
1812 
1813 /*
1814  * Sort directories.
1815  */
1816 
1817 static void
1818 sortdir(DIR *dp, char ***dirv)
1819 {
1820         struct dirent *d;
1821         char **dv;
1822         int     maxentries = MAXDIRS;
1823         int     entries = 0;
1824 
1825         *dirv = (char **)malloc(sizeof (char *) * maxentries);
1826         dv = *dirv;
1827         while ((d = readdir(dp))) {     /* store dirs */
1828                 if (eq(d->d_name, ".") || eq(d->d_name, ".."))    /* ignore */
1829                         continue;
1830 
1831                 /* check if it matches sman, man, cat format */
1832                 if (match(d->d_name, SGMLDIR, PLEN+1) ||
1833                     match(d->d_name, subdirs[0], PLEN) ||
1834                     match(d->d_name, subdirs[1], PLEN)) {
1835                         *dv = malloc(strlen(d->d_name) + 1);
1836                         if (*dv == NULL)
1837                                 malloc_error();
1838                         (void) strcpy(*dv, d->d_name);
1839                         dv++;
1840                         entries++;
1841                         if (entries == maxentries) {
1842                                 maxentries += MAXDIRS;
1843                                 *dirv = (char **)realloc(*dirv,
1844                                     sizeof (char *) * maxentries);
1845                                 if (*dirv == NULL)
1846                                         malloc_error();
1847                                 dv = *dirv + entries;
1848                         }
1849                 }
1850         }
1851         *dv = 0;
1852 
1853         qsort((void *)*dirv, dv - *dirv, sizeof (char *), cmp);
1854 
1855 }
1856 
1857 
1858 /*
1859  * Search a section subdirectory for a
1860  * given man page, return 1 for success
1861  */
1862 
1863 static int
1864 searchdir(char *path, char *dir, char *name)
1865 {
1866         DIR *sdp;
1867         struct dirent *sd;
1868         char sectpath[MAXPATHLEN+1];
1869         char file[MAXNAMLEN+1];
1870         char dname[MAXPATHLEN+1];
1871         char *last;
1872         int nlen;
1873 
1874 /*
1875  * TRANSLATION_NOTE - message for man -d or catman -p
1876  * ex.   scanning = man3c
1877  */
1878         DPRINTF(gettext("    scanning = %s\n"), dir);
1879         (void) sprintf(sectpath, "%s/%s", path, dir);
1880         (void) snprintf(file, MAXPATHLEN, "%s.", name);
1881 
1882         if ((sdp = opendir(sectpath)) == 0) {
1883                 if (errno != ENOTDIR)   /* ignore matching cruft */
1884                         perror(sectpath);
1885                 return (0);
1886         }
1887         while ((sd = readdir(sdp))) {
1888                 last = strrchr(sd->d_name, '.');
1889                 nlen = last - sd->d_name;
1890                 (void) sprintf(dname, "%.*s.", nlen, sd->d_name);
1891                 if (eq(dname, file) || eq(sd->d_name, name)) {
1892                         if (no_sroff && *dir == 's') {
1893                                 sman_no_man_no_sroff = 1;
1894                                 return (0);
1895                         }
1896                         (void) format(path, dir, name, sd->d_name);
1897                         (void) closedir(sdp);
1898                         return (1);
1899                 }
1900         }
1901         (void) closedir(sdp);
1902         return (0);
1903 }
1904 
1905 /*
1906  * Check the hash table of old directory names to see if there is a
1907  * new directory name.
1908  * Returns new directory name if a match; after checking to be sure
1909  * directory exists.
1910  * Otherwise returns NULL
1911  */
1912 
1913 static char *
1914 map_section(char *section, char *path)
1915 {
1916         int i;
1917         int len;
1918         char fullpath[MAXPATHLEN];
1919 
1920         if (list)  /* -l option fall through */
1921                 return (NULL);
1922 
1923         for (i = 0; i <= ((sizeof (map)/sizeof (map[0]) - 1)); i++) {
1924                 if (strlen(section) > strlen(map[i].new_name)) {
1925                         len = strlen(section);
1926                 } else {
1927                         len = strlen(map[i].new_name);
1928                 }
1929                 if (match(section, map[i].old_name, len)) {
1930                         (void) sprintf(fullpath,
1931                             "%s/sman%s", path, map[i].new_name);
1932                         if (!access(fullpath, R_OK | X_OK)) {
1933                                 return (map[i].new_name);
1934                         } else {
1935                                 return (NULL);
1936                         }
1937                 }
1938         }
1939 
1940         return (NULL);
1941 }
1942 
1943 
1944 /*
1945  * Use windex database for quick lookup of man pages
1946  * instead of mandir() (brute force search)
1947  */
1948 
1949 static int
1950 windex(char **secv, char *path, char *name)
1951 {
1952         FILE *fp;
1953         struct stat sbuf;
1954         struct suffix *sp;
1955         struct suffix   psecs[MAXPAGES+1];
1956         char whatfile[MAXPATHLEN+1];
1957         char page[MAXPATHLEN+1];
1958         char *matches[MAXPAGES];
1959         char *file, *dir;
1960         char **sv, **vp;
1961         int len, dslen, exist, i;
1962         int     found_in_windex = 0;
1963         char *tmp[] = {0, 0, 0, 0};
1964 
1965 
1966         (void) sprintf(whatfile, "%s/%s", path, WHATIS);
1967         if ((fp = fopen(whatfile, "r")) == NULL) {
1968                 if (errno == ENOENT)
1969                         return (-1);
1970                 return (0);
1971         }
1972 
1973 /*
1974  * TRANSLATION_NOTE - message for man -d or catman -p
1975  * ex. search in = /usr/share/man/ja/windex file
1976  */
1977         if (debug)
1978                 (void) fprintf(stdout, gettext(
1979                     " search in = %s file\n"), whatfile);
1980 
1981         if (bfsearch(fp, matches, name, NULL) == 0) {
1982                 (void) fclose(fp);
1983                 return (-1); /* force search in mandir */
1984         }
1985 
1986         (void) fclose(fp);
1987 
1988         /*
1989          * Save and split sections
1990          * section() allocates memory for sp->ds
1991          */
1992         for (sp = psecs, vp = matches; *vp; vp++, sp++) {
1993                 if ((sp - psecs) < MAXPAGES) {
1994                         section(sp, *vp);
1995                 } else {
1996                         if (debug)
1997                                 (void) fprintf(stderr, gettext(
1998                                     "too many sections in %s windex entry\n"),
1999                                     name);
2000 
2001                         /* Setting sp->ds to NULL signifies end-of-data. */
2002                         sp->ds = 0;
2003                         goto finish;
2004                 }
2005         }
2006 
2007         sp->ds = 0;
2008 
2009         /*
2010          * Search in the order specified
2011          * by MANSECTS
2012          */
2013         for (; *secv; secv++) {
2014                 len = strlen(*secv);
2015 
2016 /*
2017  * TRANSLATION_NOTE - message for man -d or catman -p
2018  * ex.  search an entry to match printf.3c
2019  */
2020                 if (debug)
2021                         (void) fprintf(stdout, gettext(
2022                             "  search an entry to match %s.%s\n"), name, *secv);
2023                 /*
2024                  * For every whatis entry that
2025                  * was matched
2026                  */
2027                 for (sp = psecs; sp->ds; sp++) {
2028                         dslen = strlen(sp->ds);
2029                         if (dslen > len)
2030                                 len = dslen;
2031                         if (**secv == '\\') {
2032                                 if (!eq(*secv + 1, sp->ds))
2033                                         continue;
2034                         } else if (!match(*secv, sp->ds, len)) {
2035                                 /* check to see if directory name changed */
2036                                 if (!all &&
2037                                     (newsection = map_section(*secv, path))
2038                                     == NULL) {
2039                                         continue;
2040                                 }
2041                                 if (newsection == NULL)
2042                                         newsection = "";
2043                                 if (!match(newsection, sp->ds, len)) {
2044                                         continue;
2045                                 }
2046                         }
2047                         /*
2048                          * here to form "sman", "man", "cat"|"fmt" in
2049                          * order
2050                          */
2051                         if (!no_sroff) {
2052                                 tmp[0] = SGMLDIR;
2053                                 for (i = 1; i < 4; i++)
2054                                         tmp[i] = subdirs[i-1];
2055                         } else {
2056                                 for (i = 0; i < 3; i++)
2057                                         tmp[i] = subdirs[i];
2058                         }
2059 
2060                         for (sv = tmp; *sv; sv++) {
2061                                 (void) sprintf(page,
2062                                     "%s/%s%s/%s%s%s", path, *sv,
2063                                     sp->ds, name, *sp->fs ? "." : "",
2064                                     sp->fs);
2065                                 exist = (stat(page, &sbuf) == 0);
2066                                 if (exist)
2067                                         break;
2068                         }
2069                         if (!exist) {
2070                                 (void) fprintf(stderr, gettext(
2071                                     "%s entry incorrect:  %s(%s) not found.\n"),
2072                                     WHATIS, name, sp->ds);
2073                                 continue;
2074                         }
2075 
2076                         file = strrchr(page, '/'), *file = 0;
2077                         dir = strrchr(page, '/');
2078 
2079                         /*
2080                          * By now we have a match
2081                          */
2082                         found_in_windex = 1;
2083                         (void) format(path, ++dir, name, ++file);
2084 
2085                         if (!all)
2086                                 goto finish;
2087                 }
2088         }
2089 finish:
2090         /*
2091          * release memory allocated by section()
2092          */
2093         sp = psecs;
2094         while (sp->ds) {
2095                 free(sp->ds);
2096                 sp->ds = NULL;
2097                 sp++;
2098         }
2099 
2100         /*
2101          * If we didn't find a match, return failure as if we didn't find
2102          * the windex at all. Why? Well, if you create a windex, then upgrade
2103          * to a later release that contains new man pages, and forget to
2104          * recreate the windex (since we don't do that automatically), you
2105          * won't see any new man pages since they aren't in the windex.
2106          * Pretending we didn't see a windex at all if there are no matches
2107          * forces a search of the underlying directory. After all, the
2108          * goal of the windex is to enable searches (man -k) and speed things
2109          * up, not to _prevent_ you from seeing new man pages, so this seems
2110          * ok. The only problem is when there are multiple entries (different
2111          * sections), and some are in and some are out. Say you do 'man ls',
2112          * and ls(1) isn't in the windex, but ls(1B) is. In that case, we
2113          * will find a match in ls(1B), and you'll see that man page.
2114          * That doesn't seem bad since if you specify the section the search
2115          * will be restricted too. So in the example above, if you do
2116          * 'man -s 1 ls' you'll get ls(1).
2117          */
2118         if (found_in_windex)
2119                 return (0);
2120         else
2121                 return (-1);
2122 }
2123 
2124 
2125 /*
2126  * Return pointers to the section-spec
2127  * and file-suffix of a whatis entry
2128  */
2129 
2130 static void
2131 section(struct suffix *sp, char *s)
2132 {
2133         char *lp, *p;
2134 
2135         lp = strchr(s, '(');
2136         p = strchr(s, ')');
2137 
2138         if (++lp == 0 || p == 0 || lp == p) {
2139                 (void) fprintf(stderr,
2140                     gettext("mangled windex entry:\n\t%s\n"), s);
2141                 return;
2142         }
2143         *p = 0;
2144 
2145         /*
2146          * copy the string pointed to by lp
2147          */
2148         lp = strdup(lp);
2149         if (lp == NULL)
2150                 malloc_error();
2151         /*
2152          * release memory in s
2153          * s has been allocated memory in bfsearch()
2154          */
2155         free(s);
2156 
2157         lower(lp);
2158 
2159         /*
2160          * split section-specifier if file-name
2161          * suffix differs from section-suffix
2162          */
2163         sp->ds = lp;
2164         if ((p = strchr(lp, '/'))) {
2165                 *p++ = 0;
2166                 sp->fs = p;
2167         } else
2168                 sp->fs = lp;
2169 }
2170 
2171 
2172 /*
2173  * Binary file search to find matching man
2174  *   pages in whatis database.
2175  */
2176 
2177 static int
2178 bfsearch(FILE *fp, char **matchv, char *key, char **secv)
2179 {
2180         char entry[BUFSIZ];
2181         char **vp;
2182         long top, bot, mid;
2183         int     c;
2184 
2185         vp = matchv;
2186         bot = 0;
2187         (void) fseek(fp, 0L, 2);
2188         top = ftell(fp);
2189         for (;;) {
2190                 mid = (top+bot)/2;
2191                 (void) fseek(fp, mid, 0);
2192                 do {
2193                         c = getc(fp);
2194                         mid++;
2195                 } while (c != EOF && c != '\n');
2196                 if (fgets(entry, sizeof (entry), fp) == NULL)
2197                         break;
2198                 switch (compare(key, entry, secv)) {
2199                 case -2:
2200                 case -1:
2201                 case 0:
2202                         if (top <= mid)
2203                                 break;
2204                         top = mid;
2205                         continue;
2206                 case 1:
2207                 case 2:
2208                         bot = mid;
2209                         continue;
2210                 }
2211                 break;
2212         }
2213         (void) fseek(fp, bot, 0);
2214         while (ftell(fp) < top) {
2215                 if (fgets(entry, sizeof (entry), fp) == NULL) {
2216                         *matchv = 0;
2217                         return (matchv - vp);
2218                 }
2219                 switch (compare(key, entry, secv)) {
2220                 case -2:
2221                         *matchv = 0;
2222                         return (matchv - vp);
2223                 case -1:
2224                 case 0:
2225                         *matchv = strdup(entry);
2226                         if (*matchv == NULL)
2227                                 malloc_error();
2228                         else
2229                                 matchv++;
2230                         break;
2231                 case 1:
2232                 case 2:
2233                         continue;
2234                 }
2235                 break;
2236         }
2237         while (fgets(entry, sizeof (entry), fp)) {
2238                 switch (compare(key, entry, secv)) {
2239                 case -1:
2240                 case 0:
2241                         *matchv = strdup(entry);
2242                         if (*matchv == NULL)
2243                                 malloc_error();
2244                         else
2245                                 matchv++;
2246                         continue;
2247                 }
2248                 break;
2249         }
2250         *matchv = 0;
2251         return (matchv - vp);
2252 }
2253 
2254 static int
2255 compare(char *key, char *entry, char **secv)
2256 {
2257         char    *entbuf;
2258         char    *s;
2259         int     comp, mlen;
2260         int     mbcurmax = MB_CUR_MAX;
2261         char    *secp = NULL;
2262         int     rv;
2263         int     eblen;
2264 
2265         entbuf = strdup(entry);
2266         if (entbuf == NULL) {
2267                 malloc_error();
2268         }
2269         eblen = strlen(entbuf);
2270 
2271         s = entbuf;
2272         while (*s) {
2273                 if (*s == '\t' || *s == ' ') {
2274                         *s = '\0';
2275                         break;
2276                 }
2277                 mlen = mblen(s, mbcurmax);
2278                 if (mlen == -1) {
2279                         (void) fprintf(stderr, gettext(
2280                             "Invalid character in windex file.\n"));
2281                         exit(1);
2282                 }
2283                 s += mlen;
2284         }
2285         /*
2286          * Find the section within parantheses
2287          */
2288         if (secv != NULL && (s - entbuf) < eblen) {
2289                 if ((secp = strchr(s + 1, ')')) != NULL) {
2290                         *secp = '\0';
2291                         if ((secp = strchr(s + 1, '(')) != NULL) {
2292                                 secp++;
2293                         }
2294                 }
2295         }
2296 
2297         comp = strcmp(key, entbuf);
2298         if (comp == 0) {
2299                 if (secp == NULL) {
2300                         rv = 0;
2301                 } else {
2302                         while (*secv != NULL) {
2303                                 if ((strcmp(*secv, secp)) == 0) {
2304                                         rv = 0;
2305                                         break;
2306                                 }
2307                                 secv++;
2308                         }
2309                 }
2310         } else if (comp < 0) {
2311                 rv = -2;
2312         } else {
2313                 rv = 2;
2314         }
2315         free(entbuf);
2316         return (rv);
2317 }
2318 
2319 
2320 /*
2321  * Format a man page and follow .so references
2322  * if necessary.
2323  */
2324 
2325 static int
2326 format(char *path, char *dir, char *name, char *pg)
2327 {
2328         char manpname[MAXPATHLEN+1], catpname[MAXPATHLEN+1];
2329         char manpname_sgml[MAXPATHLEN+1], smantmpname[MAXPATHLEN+1];
2330         char soed[MAXPATHLEN+1], soref[MAXPATHLEN+1];
2331         char manbuf[BUFSIZ], cmdbuf[BUFSIZ], tmpbuf[BUFSIZ];
2332         char tmpdir[MAXPATHLEN+1];
2333         int socount, updatedcat, regencat;
2334         struct stat mansb, catsb, smansb;
2335         char *tmpname;
2336         int catonly = 0;
2337         struct stat statb;
2338         int plen = PLEN;
2339         FILE *md;
2340         int tempfd;
2341         ssize_t count;
2342         int     temp, sgml_flag = 0, check_flag = 0;
2343         char prntbuf[BUFSIZ + 1];
2344         char *ptr;
2345         char *new_m;
2346         char    *tmpsubdir;
2347 
2348         found++;
2349 
2350         if (*dir != 'm' && *dir != 's')
2351                 catonly++;
2352 
2353 
2354         if (*dir == 's') {
2355                 tmpsubdir = SGMLDIR;
2356                 ++plen;
2357                 (void) sprintf(manpname_sgml, "%s/man%s/%s",
2358                     path, dir+plen, pg);
2359         } else
2360                 tmpsubdir = MANDIRNAME;
2361 
2362         if (list) {
2363                 (void) printf(gettext("%s (%s)\t-M %s\n"),
2364                     name, dir+plen, path);
2365                 return (-1);
2366         }
2367 
2368         (void) sprintf(manpname, "%s/%s%s/%s", path, tmpsubdir, dir+plen, pg);
2369         (void) sprintf(catpname, "%s/%s%s/%s", path, subdirs[1], dir+plen, pg);
2370 
2371         (void) sprintf(smantmpname, "%s/%s%s/%s", path, SGMLDIR, dir+plen, pg);
2372 
2373 /*
2374  * TRANSLATION_NOTE - message for man -d or catman -p
2375  * ex.  unformatted = /usr/share/man/ja/man3s/printf.3s
2376  */
2377         DPRINTF(gettext(
2378             "      unformatted = %s\n"), catonly ? "" : manpname);
2379 /*
2380  * TRANSLATION_NOTE - message for man -d or catman -p
2381  * ex.  formatted = /usr/share/man/ja/cat3s/printf.3s
2382  */
2383         DPRINTF(gettext(
2384             "      formatted = %s\n"), catpname);
2385 
2386         /*
2387          * Take care of indirect references to other man pages;
2388          * i.e., resolve files containing only ".so manx/file.x".
2389          * We follow .so chains, replacing title with the .so'ed
2390          * file at each stage, and keeping track of how many times
2391          * we've done so, so that we can avoid looping.
2392          */
2393         *soed = 0;
2394         socount = 0;
2395         for (;;) {
2396                 FILE *md;
2397                 char *cp;
2398                 char *s;
2399                 char *new_s;
2400 
2401                 if (catonly)
2402                         break;
2403                 /*
2404                  * Grab manpname's first line, stashing it in manbuf.
2405                  */
2406 
2407 
2408                 if ((md = fopen(manpname, "r")) == NULL) {
2409                         if (*soed && errno == ENOENT) {
2410                                 (void) fprintf(stderr,
2411                                     gettext("Can't find referent of "
2412                                     ".so in %s\n"), soed);
2413                                 (void) fflush(stderr);
2414                                 return (-1);
2415                         }
2416                         perror(manpname);
2417                         return (-1);
2418                 }
2419 
2420                 /*
2421                  * If this is a directory, just ignore it.
2422                  */
2423                 if (fstat(fileno(md), &statb) == NULL) {
2424                         if (S_ISDIR(statb.st_mode)) {
2425                                 if (debug) {
2426                                         (void) fprintf(stderr,
2427                                             "\tignoring directory %s\n",
2428                                             manpname);
2429                                         (void) fflush(stderr);
2430                                 }
2431                                 (void) fclose(md);
2432                                 return (-1);
2433                         }
2434                 }
2435 
2436                 if (fgets(manbuf, BUFSIZ-1, md) == NULL) {
2437                         (void) fclose(md);
2438                         (void) fprintf(stderr, gettext("%s: null file\n"),
2439                             manpname);
2440                         (void) fflush(stderr);
2441                         return (-1);
2442                 }
2443                 (void) fclose(md);
2444 
2445                 if (strncmp(manbuf, DOT_SO, sizeof (DOT_SO) - 1))
2446                         break;
2447 so_again:       if (++socount > SOLIMIT) {
2448                         (void) fprintf(stderr, gettext(".so chain too long\n"));
2449                         (void) fflush(stderr);
2450                         return (-1);
2451                 }
2452                 s = manbuf + sizeof (DOT_SO) - 1;
2453                 if ((check_flag == 1) && ((new_s = strrchr(s, '/')) != NULL)) {
2454                                 new_s++;
2455                                 (void) sprintf(s, "%s%s/%s",
2456                                     tmpsubdir, dir+plen, new_s);
2457                 }
2458 
2459                 cp = strrchr(s, '\n');
2460                 if (cp)
2461                         *cp = '\0';
2462                 /*
2463                  * Compensate for sloppy typists by stripping
2464                  * trailing white space.
2465                  */
2466                 cp = s + strlen(s);
2467                 while (--cp >= s && (*cp == ' ' || *cp == '\t'))
2468                         *cp = '\0';
2469 
2470                 /*
2471                  * Go off and find the next link in the chain.
2472                  */
2473                 (void) strcpy(soed, manpname);
2474                 (void) strcpy(soref, s);
2475                 (void) sprintf(manpname, "%s/%s", path, s);
2476 /*
2477  * TRANSLATION_NOTE - message for man -d or catman -p
2478  * ex.  .so ref = man3c/string.3c
2479  */
2480                 DPRINTF(gettext(".so ref = %s\n"), s);
2481         }
2482 
2483         /*
2484          * Make symlinks if so'ed and cattin'
2485          */
2486         if (socount && catmando) {
2487                 (void) sprintf(cmdbuf, "cd %s; rm -f %s; ln -s ../%s%s %s",
2488                     path, catpname, subdirs[1], soref+plen, catpname);
2489                 (void) sys(cmdbuf);
2490                 return (1);
2491         }
2492 
2493         /*
2494          * Obtain the cat page that corresponds to the man page.
2495          * If it already exists, is up to date, and if we haven't
2496          * been told not to use it, use it as it stands.
2497          */
2498         regencat = updatedcat = 0;
2499         if (compargs || (!catonly && stat(manpname, &mansb) >= 0 &&
2500             (stat(catpname, &catsb) < 0 || catsb.st_mtime < mansb.st_mtime)) ||
2501             (access(catpname, R_OK) != 0)) {
2502                 /*
2503                  * Construct a shell command line for formatting manpname.
2504                  * The resulting file goes initially into /tmp.  If possible,
2505                  * it will later be moved to catpname.
2506                  */
2507 
2508                 int pipestage = 0;
2509                 int needcol = 0;
2510                 char *cbp = cmdbuf;
2511 
2512                 regencat = updatedcat = 1;
2513 
2514                 if (!catmando && !debug && !check_flag) {
2515                         (void) fprintf(stderr, gettext(
2516                             "Reformatting page.  Please Wait..."));
2517                         if (sargs && (newsection != NULL) &&
2518                             (*newsection != '\0')) {
2519                                 (void) fprintf(stderr, gettext(
2520                                     "\nThe directory name has been changed "
2521                                     "to %s\n"), newsection);
2522                         }
2523                         (void) fflush(stderr);
2524                 }
2525 
2526                 /*
2527                  * in catman command, if the file exists in sman dir already,
2528                  * don't need to convert the file in man dir to cat dir
2529                  */
2530 
2531                 if (!no_sroff && catmando &&
2532                     match(tmpsubdir, MANDIRNAME, PLEN) &&
2533                     stat(smantmpname, &smansb) >= 0)
2534                         return (1);
2535 
2536                 /*
2537                  * cd to path so that relative .so commands will work
2538                  * correctly
2539                  */
2540                 (void) sprintf(cbp, "cd %s; ", path);
2541                 cbp += strlen(cbp);
2542 
2543 
2544                 /*
2545                  * check to see whether it is a sgml file
2546                  * assume sgml symbol(>!DOCTYPE) can be found in the first
2547                  * BUFSIZ bytes
2548                  */
2549 
2550                 if ((temp = open(manpname, 0)) == -1) {
2551                                 perror(manpname);
2552                                 return (-1);
2553                 }
2554 
2555                 if ((count = read(temp, prntbuf, BUFSIZ)) <= 0) {
2556                                 perror(manpname);
2557                                 return (-1);
2558                 }
2559 
2560                 prntbuf[count] = '\0';  /* null terminate */
2561                 ptr = prntbuf;
2562                 if (sgmlcheck((const char *)ptr) == 1) {
2563                         sgml_flag = 1;
2564                         if (defaultmandir && *localedir) {
2565                                 (void) sprintf(cbp, "LC_MESSAGES=C %s %s ",
2566                                     SROFF_CMD, manpname);
2567                         } else {
2568                                 (void) sprintf(cbp, "%s %s ",
2569                                     SROFF_CMD, manpname);
2570                         }
2571                         cbp += strlen(cbp);
2572                 } else if (*dir == 's') {
2573                         (void) close(temp);
2574                         return (-1);
2575                 }
2576                 (void) close(temp);
2577 
2578                 /*
2579                  * Check for special formatting requirements by examining
2580                  * manpname's first line preprocessor specifications.
2581                  */
2582 
2583                 if (strncmp(manbuf, PREPROC_SPEC,
2584                     sizeof (PREPROC_SPEC) - 1) == 0) {
2585                         char *ptp;
2586 
2587                         ptp = manbuf + sizeof (PREPROC_SPEC) - 1;
2588                         while (*ptp && *ptp != '\n') {
2589                                 const struct preprocessor *pp;
2590 
2591                                 /*
2592                                  * Check for a preprocessor we know about.
2593                                  */
2594                                 for (pp = preprocessors; pp->p_tag; pp++) {
2595                                         if (pp->p_tag == *ptp)
2596                                                 break;
2597                                 }
2598                                 if (pp->p_tag == 0) {
2599                                         (void) fprintf(stderr,
2600                                             gettext("unknown preprocessor "
2601                                             "specifier %c\n"), *ptp);
2602                                         (void) fflush(stderr);
2603                                         return (-1);
2604                                 }
2605 
2606                                 /*
2607                                  * Add it to the pipeline.
2608                                  */
2609                                 (void) sprintf(cbp, "%s %s |",
2610                                     troffit ? pp->p_troff : pp->p_nroff,
2611                                     pipestage++ == 0 ? manpname :
2612                                     pp->p_stdin_char);
2613                                 cbp += strlen(cbp);
2614 
2615                                 /*
2616                                  * Special treatment: if tbl is among the
2617                                  * preprocessors and we'll process with
2618                                  * nroff, we have to pass things through
2619                                  * col at the end of the pipeline.
2620                                  */
2621                                 if (pp->p_tag == 't' && !troffit)
2622                                         needcol++;
2623 
2624                                 ptp++;
2625                         }
2626                 }
2627 
2628                 /*
2629                  * if catman, use the cat page name
2630                  * otherwise, dup template and create another
2631                  * (needed for multiple pages)
2632                  */
2633                 if (catmando)
2634                         tmpname = catpname;
2635                 else {
2636                         tmpname = strdup(TEMPLATE);
2637                         if (tmpname == NULL)
2638                                 malloc_error();
2639                         (void) close(mkstemp(tmpname));
2640                 }
2641 
2642                 if (! Tflag) {
2643                         if (*localedir != '\0') {
2644                                 (void) sprintf(macros, "%s/%s", path, MACROF);
2645 /*
2646  * TRANSLATION_NOTE - message for man -d or catman -p
2647  * ex.  locale macros = /usr/share/man/ja/tmac.an
2648  */
2649                                 if (debug)
2650                                         (void) printf(gettext(
2651                                             "\nlocale macros = %s "),
2652                                             macros);
2653                                 if (stat(macros, &statb) < 0)
2654                                         (void) strcpy(macros, TMAC_AN);
2655 /*
2656  * TRANSLATION_NOTE - message for man -d or catman -p
2657  * ex.  macros = /usr/share/man/ja/tman.an
2658  */
2659                                 if (debug)
2660                                         (void) printf(gettext(
2661                                             "\nmacros = %s\n"),
2662                                             macros);
2663                         }
2664                 }
2665 
2666                 tmpdir[0] = '\0';
2667                 if (sgml_flag == 1) {
2668                         if (check_flag == 0) {
2669                                 strcpy(tmpdir, "/tmp/sman_XXXXXX");
2670                                 if ((tempfd = mkstemp(tmpdir)) == -1) {
2671                                         (void) fprintf(stderr, gettext(
2672                                             "%s: null file\n"), tmpdir);
2673                                         (void) fflush(stderr);
2674                                         return (-1);
2675                                 }
2676 
2677                                 if (debug)
2678                                         close(tempfd);
2679 
2680                                 (void) sprintf(tmpbuf, "%s > %s",
2681                                     cmdbuf, tmpdir);
2682                                 if (sys(tmpbuf)) {
2683 /*
2684  * TRANSLATION_NOTE - message for man -d or catman -p
2685  * Error message if sys(%s) failed
2686  */
2687                                         (void) fprintf(stderr, gettext(
2688                                             "sys(%s) fail!\n"), tmpbuf);
2689                                         (void) fprintf(stderr,
2690                                             gettext(" aborted (sorry)\n"));
2691                                         (void) fflush(stderr);
2692                                         /* release memory for tmpname */
2693                                         if (!catmando) {
2694                                                 (void) unlink(tmpdir);
2695                                                 (void) unlink(tmpname);
2696                                                 free(tmpname);
2697                                         }
2698                                         return (-1);
2699                                 } else if (debug == 0) {
2700                                         if ((md = fdopen(tempfd, "r"))
2701                                             == NULL) {
2702                                                 (void) fprintf(stderr, gettext(
2703                                                     "%s: null file\n"), tmpdir);
2704                                                 (void) fflush(stderr);
2705                                                 close(tempfd);
2706                                                 /* release memory for tmpname */
2707                                                 if (!catmando)
2708                                                         free(tmpname);
2709                                                 return (-1);
2710                                         }
2711 
2712                                         /* if the file is empty, */
2713                                         /* it's a fragment, do nothing */
2714                                         if (fgets(manbuf, BUFSIZ-1, md)
2715                                             == NULL) {
2716                                                 (void) fclose(md);
2717                                                 /* release memory for tmpname */
2718                                                 if (!catmando)
2719                                                         free(tmpname);
2720                                                 return (1);
2721                                         }
2722                                         (void) fclose(md);
2723 
2724                                         if (strncmp(manbuf, DOT_SO,
2725                                             sizeof (DOT_SO) - 1) == 0) {
2726                                                 if (!compargs) {
2727                                                 check_flag = 1;
2728                                                 (void) unlink(tmpdir);
2729                                                 (void) unlink(tmpname);
2730                                                 /* release memory for tmpname */
2731                                                 if (!catmando)
2732                                                         free(tmpname);
2733                                                 goto so_again;
2734                                                 } else {
2735                                                         (void) unlink(tmpdir);
2736                                                 strcpy(tmpdir,
2737                                                     "/tmp/sman_XXXXXX");
2738                                                 tempfd = mkstemp(tmpdir);
2739                                                 if ((tempfd == -1) ||
2740                                                     (md = fdopen(tempfd, "w"))
2741                                                     == NULL) {
2742                                                         (void) fprintf(stderr,
2743                                                             gettext(
2744                                                             "%s: null file\n"),
2745                                                             tmpdir);
2746                                                         (void) fflush(stderr);
2747                                                         if (tempfd != -1)
2748                                                                 close(tempfd);
2749                                                 /* release memory for tmpname */
2750                                                         if (!catmando)
2751                                                                 free(tmpname);
2752                                                         return (-1);
2753                                                 }
2754                                 if ((new_m = strrchr(manbuf, '/')) != NULL) {
2755                 (void) fprintf(md, ".so man%s%s\n", dir+plen, new_m);
2756                                                         } else {
2757 /*
2758  * TRANSLATION_NOTE - message for catman -c
2759  * Error message if unable to get file name
2760  */
2761                                 (void) fprintf(stderr,
2762                                     gettext("file not found\n"));
2763                                 (void) fflush(stderr);
2764                                 return (-1);
2765                                 }
2766                                                         (void) fclose(md);
2767                                                 }
2768                                         }
2769                                 }
2770                                 if (catmando && compargs)
2771                                         (void) sprintf(cmdbuf, "cat %s > %s",
2772                                             tmpdir, manpname_sgml);
2773                                 else
2774         (void) sprintf(cmdbuf, " cat %s | tbl | eqn | %s %s - %s > %s",
2775             tmpdir, troffit ? troffcmd : "nroff -u0 -Tlp",
2776             macros, troffit ? "" : " | col -x", tmpname);
2777                         } else
2778                                 if (catmando && compargs)
2779                                         (void) sprintf(cbp, " > %s",
2780                                             manpname_sgml);
2781                                 else
2782         (void) sprintf(cbp, " | tbl | eqn | %s %s - %s > %s",
2783             troffit ? troffcmd : "nroff -u0 -Tlp",
2784             macros, troffit ? "" : " | col -x", tmpname);
2785 
2786                 } else
2787         (void) sprintf(cbp, "%s %s %s%s > %s",
2788             troffit ? troffcmd : "nroff -u0 -Tlp",
2789             macros, pipestage == 0 ? manpname : "-",
2790             troffit ? "" : " | col -x", tmpname);
2791 
2792                 /* Reformat the page. */
2793                 if (sys(cmdbuf)) {
2794 /*
2795  * TRANSLATION_NOTE - message for man -d or catman -p
2796  * Error message if sys(%s) failed
2797  */
2798                         (void) fprintf(stderr, gettext(
2799                             "sys(%s) fail!\n"), cmdbuf);
2800                         (void) fprintf(stderr, gettext(" aborted (sorry)\n"));
2801                         (void) fflush(stderr);
2802                         (void) unlink(tmpname);
2803                         /* release memory for tmpname */
2804                         if (!catmando)
2805                                 free(tmpname);
2806                         return (-1);
2807                 }
2808 
2809                 if (tmpdir[0] != '\0')
2810                         (void) unlink(tmpdir);
2811 
2812                 if (catmando)
2813                         return (1);
2814 
2815                 /*
2816                  * Attempt to move the cat page to its proper home.
2817                  */
2818                 (void) sprintf(cmdbuf,
2819                     "trap '' 1 15; /usr/bin/mv -f %s %s 2> /dev/null",
2820                     tmpname,
2821                     catpname);
2822                 if (sys(cmdbuf))
2823                         updatedcat = 0;
2824                 else if (debug == 0)
2825                         (void) chmod(catpname, 0644);
2826 
2827                 if (debug) {
2828                         /* release memory for tmpname */
2829                         if (!catmando)
2830                                 free(tmpname);
2831                         (void) unlink(tmpname);
2832                         return (1);
2833                 }
2834 
2835                 (void) fprintf(stderr, gettext(" done\n"));
2836                 (void) fflush(stderr);
2837         }
2838 
2839         /*
2840          * Save file name (dup if necessary)
2841          * to view later
2842          * fix for 1123802 - don't save names if we are invoked as catman
2843          */
2844         if (!catmando) {
2845                 char    **tmpp;
2846                 int     dup;
2847                 char    *newpage;
2848 
2849                 if (regencat && !updatedcat)
2850                         newpage = tmpname;
2851                 else {
2852                         newpage = strdup(catpname);
2853                         if (newpage == NULL)
2854                                 malloc_error();
2855                 }
2856                 /* make sure we don't add a dup */
2857                 dup = 0;
2858                 for (tmpp = pages; tmpp < endp; tmpp++) {
2859                         if (strcmp(*tmpp, newpage) == 0) {
2860                                 dup = 1;
2861                                 break;
2862                         }
2863                 }
2864                 if (!dup)
2865                         *endp++ = newpage;
2866                 if (endp >= &pages[MAXPAGES]) {
2867                         fprintf(stderr,
2868                             gettext("Internal pages array overflow!\n"));
2869                         exit(1);
2870                 }
2871         }
2872 
2873         return (regencat);
2874 }
2875 
2876 /*
2877  * Add <localedir> to the path.
2878  */
2879 
2880 static char *
2881 addlocale(char *path)
2882 {
2883 
2884         char *tmp;
2885 
2886         tmp = malloc(strlen(path) + strlen(localedir) + 2);
2887         if (tmp == NULL)
2888                 malloc_error();
2889         (void) sprintf(tmp, "%s/%s", path, localedir);
2890         return (tmp);
2891 
2892 }
2893 
2894 /*
2895  * From the configuration file "man.cf", get the order of suffices of
2896  * sub-mandirs to be used in the search path for a given mandir.
2897  */
2898 
2899 static char *
2900 check_config(char *path)
2901 {
2902         FILE *fp;
2903         static char submandir[BUFSIZ];
2904         char *sect;
2905         char fname[MAXPATHLEN];
2906 
2907         (void) sprintf(fname, "%s/%s", path, CONFIG);
2908 
2909         if ((fp = fopen(fname, "r")) == NULL)
2910                 return (NULL);
2911         else {
2912                 if (get_manconfig(fp, submandir) == -1) {
2913                         (void) fclose(fp);
2914                         return (NULL);
2915                 }
2916 
2917                 (void) fclose(fp);
2918 
2919                 sect = strchr(submandir, '=');
2920                 if (sect != NULL)
2921                         return (++sect);
2922                 else
2923                         return (NULL);
2924         }
2925 }
2926 
2927 /*
2928  *  This routine is for getting the MANSECTS entry from man.cf.
2929  *  It sets submandir to the line in man.cf that contains
2930  *      MANSECTS=sections[,sections]...
2931  */
2932 
2933 static int
2934 get_manconfig(FILE *fp, char *submandir)
2935 {
2936         char *s, *t, *rc;
2937         char buf[BUFSIZ];
2938 
2939         while ((rc = fgets(buf, sizeof (buf), fp)) != NULL) {
2940 
2941                 /*
2942                  * skip leading blanks
2943                  */
2944                 for (t = buf; *t != '\0'; t++) {
2945                         if (!isspace(*t))
2946                                 break;
2947                 }
2948                 /*
2949                  * skip line that starts with '#' or empty line
2950                  */
2951                 if (*t == '#' || *t == '\0')
2952                         continue;
2953 
2954                 if (strstr(buf, "MANSECTS") != NULL)
2955                         break;
2956         }
2957 
2958         /*
2959          * the man.cf file doesn't have a MANSECTS entry
2960          */
2961         if (rc == NULL)
2962                 return (-1);
2963 
2964         s = strchr(buf, '\n');
2965         *s = '\0';      /* replace '\n' with '\0' */
2966 
2967         (void) strcpy(submandir, buf);
2968         return (0);
2969 }
2970 
2971 static void
2972 malloc_error(void)
2973 {
2974         (void) fprintf(stderr, gettext(
2975             "Memory allocation failed.\n"));
2976         exit(1);
2977 }
2978 
2979 static int
2980 sgmlcheck(const char *s1)
2981 {
2982         const char      *s2 = SGML_SYMBOL;
2983         int     len;
2984 
2985         while (*s1) {
2986                 /*
2987                  * Assume the first character of SGML_SYMBOL(*s2) is '<'.
2988                  * Therefore, not necessary to do toupper(*s1) here.
2989                  */
2990                 if (*s1 == *s2) {
2991                         /*
2992                          * *s1 is '<'.  Check the following substring matches
2993                          * with "!DOCTYPE".
2994                          */
2995                         s1++;
2996                         if (strncasecmp(s1, s2 + 1, SGML_SYMBOL_LEN - 1)
2997                             == 0) {
2998                                 /*
2999                                  * SGML_SYMBOL found
3000                                  */
3001                                 return (1);
3002                         }
3003                         continue;
3004                 } else if (isascii(*s1)) {
3005                         /*
3006                          * *s1 is an ASCII char
3007                          * Skip one character
3008                          */
3009                         s1++;
3010                         continue;
3011                 } else {
3012                         /*
3013                          * *s1 is a non-ASCII char or
3014                          * the first byte of the multibyte char.
3015                          * Skip one character
3016                          */
3017                         len = mblen(s1, MB_CUR_MAX);
3018                         if (len == -1)
3019                                 len = 1;
3020                         s1 += len;
3021                         continue;
3022                 }
3023         }
3024         /*
3025          * SGML_SYMBOL not found
3026          */
3027         return (0);
3028 }
3029 
3030 /*
3031  * Initializes the bintoman array with appropriate device and inode info
3032  */
3033 
3034 static void
3035 init_bintoman(void)
3036 {
3037         int i;
3038         struct stat sb;
3039 
3040         for (i = 0; bintoman[i].bindir != NULL; i++) {
3041                 if (stat(bintoman[i].bindir, &sb) == 0) {
3042                         bintoman[i].dev = sb.st_dev;
3043                         bintoman[i].ino = sb.st_ino;
3044                 } else {
3045                         bintoman[i].dev = NODEV;
3046                 }
3047         }
3048 }
3049 
3050 /*
3051  * If a duplicate is found, return 1
3052  * If a duplicate is not found, add it to the dupnode list and return 0
3053  */
3054 static int
3055 dupcheck(struct man_node *mnp, struct dupnode **dnp)
3056 {
3057         struct dupnode  *curdnp;
3058         struct secnode  *cursnp;
3059         struct stat     sb;
3060         int             i;
3061         int             rv = 1;
3062         int             dupfound;
3063 
3064         /*
3065          * If the path doesn't exist, treat it as a duplicate
3066          */
3067         if (stat(mnp->path, &sb) != 0) {
3068                 return (1);
3069         }
3070 
3071         /*
3072          * If no sections were found in the man dir, treat it as duplicate
3073          */
3074         if (mnp->secv == NULL) {
3075                 return (1);
3076         }
3077 
3078         /*
3079          * Find the dupnode structure for the previous time this directory
3080          * was looked at.  Device and inode numbers are compared so that
3081          * directories that are reached via different paths (e.g. /usr/man vs.
3082          * /usr/share/man) are treated as equivalent.
3083          */
3084         for (curdnp = *dnp; curdnp != NULL; curdnp = curdnp->next) {
3085                 if (curdnp->dev == sb.st_dev && curdnp->ino == sb.st_ino) {
3086                         break;
3087                 }
3088         }
3089 
3090         /*
3091          * First time this directory has been seen.  Add a new node to the
3092          * head of the list.  Since all entries are guaranteed to be unique
3093          * copy all sections to new node.
3094          */
3095         if (curdnp == NULL) {
3096                 if ((curdnp = calloc(1, sizeof (struct dupnode))) == NULL) {
3097                         malloc_error();
3098                 }
3099                 for (i = 0; mnp->secv[i] != NULL; i++) {
3100                         if ((cursnp = calloc(1, sizeof (struct secnode)))
3101                             == NULL) {
3102                                 malloc_error();
3103                         }
3104                         cursnp->next = curdnp->secl;
3105                         curdnp->secl = cursnp;
3106                         if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
3107                                 malloc_error();
3108                         }
3109                 }
3110                 curdnp->dev = sb.st_dev;
3111                 curdnp->ino = sb.st_ino;
3112                 curdnp->next = *dnp;
3113                 *dnp = curdnp;
3114                 return (0);
3115         }
3116 
3117         /*
3118          * Traverse the section vector in the man_node and the section list
3119          * in dupnode cache to eliminate all duplicates from man_node
3120          */
3121         for (i = 0; mnp->secv[i] != NULL; i++) {
3122                 dupfound = 0;
3123                 for (cursnp = curdnp->secl; cursnp != NULL;
3124                     cursnp = cursnp->next) {
3125                         if (strcmp(mnp->secv[i], cursnp->secp) == 0) {
3126                                 dupfound = 1;
3127                                 break;
3128                         }
3129                 }
3130                 if (dupfound) {
3131                         mnp->secv[i][0] = '\0';
3132                         continue;
3133                 }
3134 
3135 
3136                 /*
3137                  * Update curdnp and set return value to indicate that this
3138                  * was not all duplicates.
3139                  */
3140                 if ((cursnp = calloc(1, sizeof (struct secnode))) == NULL) {
3141                         malloc_error();
3142                 }
3143                 cursnp->next = curdnp->secl;
3144                 curdnp->secl = cursnp;
3145                 if ((cursnp->secp = strdup(mnp->secv[i])) == NULL) {
3146                         malloc_error();
3147                 }
3148                 rv = 0;
3149         }
3150 
3151         return (rv);
3152 }
3153 
3154 /*
3155  * Given a bin directory, return the corresponding man directory.
3156  * Return string must be free()d by the caller.
3157  *
3158  * NULL will be returned if no matching man directory can be found.
3159  */
3160 
3161 static char *
3162 path_to_manpath(char *bindir)
3163 {
3164         char    *mand, *p;
3165         int     i;
3166         struct stat     sb;
3167 
3168         /*
3169          * First look for known translations for specific bin paths
3170          */
3171         if (stat(bindir, &sb) != 0) {
3172                 return (NULL);
3173         }
3174         for (i = 0; bintoman[i].bindir != NULL; i++) {
3175                 if (sb.st_dev == bintoman[i].dev &&
3176                     sb.st_ino == bintoman[i].ino) {
3177                         if ((mand = strdup(bintoman[i].mandir)) == NULL) {
3178                                 malloc_error();
3179                         }
3180                         if ((p = strchr(mand, ',')) != NULL) {
3181                                 *p = '\0';
3182                         }
3183                         if (stat(mand, &sb) != 0) {
3184                                 free(mand);
3185                                 return (NULL);
3186                         }
3187                         if (p != NULL) {
3188                                 *p = ',';
3189                         }
3190                         return (mand);
3191                 }
3192         }
3193 
3194         /*
3195          * No specific translation found.  Try `dirname $bindir`/man
3196          * and `dirname $bindir`/share/man
3197          */
3198         if ((mand = malloc(PATH_MAX)) == NULL) {
3199                 malloc_error();
3200         }
3201 
3202         if (strlcpy(mand, bindir, PATH_MAX) >= PATH_MAX) {
3203                 free(mand);
3204                 return (NULL);
3205         }
3206 
3207         /*
3208          * Advance to end of buffer, strip trailing /'s then remove last
3209          * directory component.
3210          */
3211         for (p = mand; *p != '\0'; p++)
3212                 ;
3213         for (; p > mand && *p == '/'; p--)
3214                 ;
3215         for (; p > mand && *p != '/'; p--)
3216                 ;
3217         if (p == mand && *p == '.') {
3218                 if (realpath("..", mand) == NULL) {
3219                         free(mand);
3220                         return (NULL);
3221                 }
3222                 for (; *p != '\0'; p++)
3223                         ;
3224         } else {
3225                 *p = '\0';
3226         }
3227 
3228         if (strlcat(mand, "/man", PATH_MAX) >= PATH_MAX) {
3229                 free(mand);
3230                 return (NULL);
3231         }
3232 
3233         if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
3234                 return (mand);
3235         }
3236 
3237         /*
3238          * Strip the /man off and try /share/man
3239          */
3240         *p = '\0';
3241         if (strlcat(mand, "/share/man", PATH_MAX) >= PATH_MAX) {
3242                 free(mand);
3243                 return (NULL);
3244         }
3245         if ((stat(mand, &sb) == 0) && S_ISDIR(sb.st_mode)) {
3246                 return (mand);
3247         }
3248 
3249         /*
3250          * No man or share/man directory found
3251          */
3252         free(mand);
3253         return (NULL);
3254 }
3255 
3256 /*
3257  * Free a linked list of dupnode structs
3258  */
3259 void
3260 free_dupnode(struct dupnode *dnp) {
3261         struct dupnode *dnp2;
3262         struct secnode *snp;
3263 
3264         while (dnp != NULL) {
3265                 dnp2 = dnp;
3266                 dnp = dnp->next;
3267                 while (dnp2->secl != NULL) {
3268                         snp = dnp2->secl;
3269                         dnp2->secl = dnp2->secl->next;
3270                         free(snp->secp);
3271                         free(snp);
3272                 }
3273                 free(dnp2);
3274         }
3275 }
3276 
3277 /*
3278  * prints manp linked list to stdout.
3279  *
3280  * If namep is NULL, output can be used for setting MANPATH.
3281  *
3282  * If namep is not NULL output is two columns.  First column is the string
3283  * pointed to by namep.  Second column is a MANPATH-compatible representation
3284  * of manp linked list.
3285  */
3286 void
3287 print_manpath(struct man_node *manp, char *namep)
3288 {
3289         char colon[2];
3290         char **secp;
3291 
3292         if (namep != NULL) {
3293                 (void) printf("%s ", namep);
3294         }
3295 
3296         colon[0] = '\0';
3297         colon[1] = '\0';
3298 
3299         for (; manp != NULL; manp = manp->next) {
3300                 (void) printf("%s%s", colon, manp->path);
3301                 colon[0] = ':';
3302 
3303                 /*
3304                  * If man.cf or a directory scan was used to create section
3305                  * list, do not print section list again.  If the output of
3306                  * man -p is used to set MANPATH, subsequent runs of man
3307                  * will re-read man.cf and/or scan man directories as
3308                  * required.
3309                  */
3310                 if (manp->defsrch != 0) {
3311                         continue;
3312                 }
3313 
3314                 for (secp = manp->secv; *secp != NULL; secp++) {
3315                         /*
3316                          * Section deduplication may have eliminated some
3317                          * sections from the vector. Avoid displaying this
3318                          * detail which would appear as ",," in output
3319                          */
3320                         if ((*secp)[0] != '\0') {
3321                                 (void) printf(",%s", *secp);
3322                         }
3323                 }
3324         }
3325         (void) printf("\n");
3326 }