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