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