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