GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/main.c Lines: 0 496 0.0 %
Date: 2016-12-06 Branches: 0 377 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: main.c,v 1.176 2016/07/15 19:31:53 schwarze Exp $ */
2
/*
3
 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2010-2012, 2014-2016 Ingo Schwarze <schwarze@openbsd.org>
5
 * Copyright (c) 2010 Joerg Sonnenberger <joerg@netbsd.org>
6
 *
7
 * Permission to use, copy, modify, and distribute this software for any
8
 * purpose with or without fee is hereby granted, provided that the above
9
 * copyright notice and this permission notice appear in all copies.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18
 */
19
20
#include <sys/types.h>
21
#include <sys/param.h>	/* MACHINE */
22
#include <sys/wait.h>
23
24
#include <assert.h>
25
#include <ctype.h>
26
#include <err.h>
27
#include <errno.h>
28
#include <fcntl.h>
29
#include <glob.h>
30
#include <signal.h>
31
#include <stdio.h>
32
#include <stdint.h>
33
#include <stdlib.h>
34
#include <string.h>
35
#include <time.h>
36
#include <unistd.h>
37
38
#include "mandoc_aux.h"
39
#include "mandoc.h"
40
#include "roff.h"
41
#include "mdoc.h"
42
#include "man.h"
43
#include "tag.h"
44
#include "main.h"
45
#include "manconf.h"
46
#include "mansearch.h"
47
48
enum	outmode {
49
	OUTMODE_DEF = 0,
50
	OUTMODE_FLN,
51
	OUTMODE_LST,
52
	OUTMODE_ALL,
53
	OUTMODE_INT,
54
	OUTMODE_ONE
55
};
56
57
enum	outt {
58
	OUTT_ASCII = 0,	/* -Tascii */
59
	OUTT_LOCALE,	/* -Tlocale */
60
	OUTT_UTF8,	/* -Tutf8 */
61
	OUTT_TREE,	/* -Ttree */
62
	OUTT_MAN,	/* -Tman */
63
	OUTT_HTML,	/* -Thtml */
64
	OUTT_LINT,	/* -Tlint */
65
	OUTT_PS,	/* -Tps */
66
	OUTT_PDF	/* -Tpdf */
67
};
68
69
struct	curparse {
70
	struct mparse	 *mp;
71
	enum mandoclevel  wlevel;	/* ignore messages below this */
72
	int		  wstop;	/* stop after a file with a warning */
73
	enum outt	  outtype;	/* which output to use */
74
	void		 *outdata;	/* data for output */
75
	struct manoutput *outopts;	/* output options */
76
};
77
78
79
int			  mandocdb(int, char *[]);
80
81
static	int		  fs_lookup(const struct manpaths *,
82
				size_t ipath, const char *,
83
				const char *, const char *,
84
				struct manpage **, size_t *);
85
static	void		  fs_search(const struct mansearch *,
86
				const struct manpaths *, int, char**,
87
				struct manpage **, size_t *);
88
static	int		  koptions(int *, char *);
89
static	int		  moptions(int *, char *);
90
static	void		  mmsg(enum mandocerr, enum mandoclevel,
91
				const char *, int, int, const char *);
92
static	void		  parse(struct curparse *, int, const char *);
93
static	void		  passthrough(const char *, int, int);
94
static	pid_t		  spawn_pager(struct tag_files *);
95
static	int		  toptions(struct curparse *, char *);
96
static	void		  usage(enum argmode) __attribute__((noreturn));
97
static	int		  woptions(struct curparse *, char *);
98
99
static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
100
static	char		  help_arg[] = "help";
101
static	char		 *help_argv[] = {help_arg, NULL};
102
static	enum mandoclevel  rc;
103
104
105
int
106
main(int argc, char *argv[])
107
{
108
	struct manconf	 conf;
109
	struct curparse	 curp;
110
	struct mansearch search;
111
	struct tag_files *tag_files;
112
	const char	*progname;
113
	char		*auxpaths;
114
	char		*defos;
115
	unsigned char	*uc;
116
	struct manpage	*res, *resp;
117
	char		*conf_file, *defpaths;
118
	const char	*sec;
119
	size_t		 i, sz;
120
	int		 prio, best_prio;
121
	enum outmode	 outmode;
122
	int		 fd;
123
	int		 show_usage;
124
	int		 options;
125
	int		 use_pager;
126
	int		 status, signum;
127
	int		 c;
128
	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
129
130
	progname = getprogname();
131
	if (strncmp(progname, "mandocdb", 8) == 0 ||
132
	    strncmp(progname, "makewhatis", 10) == 0)
133
		return mandocdb(argc, argv);
134
135
	if (pledge("stdio rpath tmppath tty proc exec flock wpath cpath", NULL) == -1)
136
		err((int)MANDOCLEVEL_SYSERR, "pledge");
137
138
	/* Search options. */
139
140
	memset(&conf, 0, sizeof(conf));
141
	conf_file = defpaths = NULL;
142
	auxpaths = NULL;
143
144
	memset(&search, 0, sizeof(struct mansearch));
145
	search.outkey = "Nd";
146
147
	if (strcmp(progname, "man") == 0)
148
		search.argmode = ARG_NAME;
149
	else if (strncmp(progname, "apropos", 7) == 0)
150
		search.argmode = ARG_EXPR;
151
	else if (strncmp(progname, "whatis", 6) == 0)
152
		search.argmode = ARG_WORD;
153
	else if (strncmp(progname, "help", 4) == 0)
154
		search.argmode = ARG_NAME;
155
	else
156
		search.argmode = ARG_FILE;
157
158
	/* Parser and formatter options. */
159
160
	memset(&curp, 0, sizeof(struct curparse));
161
	curp.outtype = OUTT_LOCALE;
162
	curp.wlevel  = MANDOCLEVEL_BADARG;
163
	curp.outopts = &conf.output;
164
	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
165
	defos = NULL;
166
167
	use_pager = 1;
168
	tag_files = NULL;
169
	show_usage = 0;
170
	outmode = OUTMODE_DEF;
171
172
	while (-1 != (c = getopt(argc, argv,
173
			"aC:cfhI:iK:klM:m:O:S:s:T:VW:w"))) {
174
		switch (c) {
175
		case 'a':
176
			outmode = OUTMODE_ALL;
177
			break;
178
		case 'C':
179
			conf_file = optarg;
180
			break;
181
		case 'c':
182
			use_pager = 0;
183
			break;
184
		case 'f':
185
			search.argmode = ARG_WORD;
186
			break;
187
		case 'h':
188
			conf.output.synopsisonly = 1;
189
			use_pager = 0;
190
			outmode = OUTMODE_ALL;
191
			break;
192
		case 'I':
193
			if (strncmp(optarg, "os=", 3)) {
194
				warnx("-I %s: Bad argument", optarg);
195
				return (int)MANDOCLEVEL_BADARG;
196
			}
197
			if (defos) {
198
				warnx("-I %s: Duplicate argument", optarg);
199
				return (int)MANDOCLEVEL_BADARG;
200
			}
201
			defos = mandoc_strdup(optarg + 3);
202
			break;
203
		case 'i':
204
			outmode = OUTMODE_INT;
205
			break;
206
		case 'K':
207
			if ( ! koptions(&options, optarg))
208
				return (int)MANDOCLEVEL_BADARG;
209
			break;
210
		case 'k':
211
			search.argmode = ARG_EXPR;
212
			break;
213
		case 'l':
214
			search.argmode = ARG_FILE;
215
			outmode = OUTMODE_ALL;
216
			break;
217
		case 'M':
218
			defpaths = optarg;
219
			break;
220
		case 'm':
221
			auxpaths = optarg;
222
			break;
223
		case 'O':
224
			search.outkey = optarg;
225
			while (optarg != NULL)
226
				manconf_output(&conf.output,
227
				    strsep(&optarg, ","));
228
			break;
229
		case 'S':
230
			search.arch = optarg;
231
			break;
232
		case 's':
233
			search.sec = optarg;
234
			break;
235
		case 'T':
236
			if ( ! toptions(&curp, optarg))
237
				return (int)MANDOCLEVEL_BADARG;
238
			break;
239
		case 'W':
240
			if ( ! woptions(&curp, optarg))
241
				return (int)MANDOCLEVEL_BADARG;
242
			break;
243
		case 'w':
244
			outmode = OUTMODE_FLN;
245
			break;
246
		default:
247
			show_usage = 1;
248
			break;
249
		}
250
	}
251
252
	if (show_usage)
253
		usage(search.argmode);
254
255
	/* Postprocess options. */
256
257
	if (outmode == OUTMODE_DEF) {
258
		switch (search.argmode) {
259
		case ARG_FILE:
260
			outmode = OUTMODE_ALL;
261
			use_pager = 0;
262
			break;
263
		case ARG_NAME:
264
			outmode = OUTMODE_ONE;
265
			break;
266
		default:
267
			outmode = OUTMODE_LST;
268
			break;
269
		}
270
	}
271
272
	if (outmode == OUTMODE_FLN ||
273
	    outmode == OUTMODE_LST ||
274
	    !isatty(STDOUT_FILENO))
275
		use_pager = 0;
276
277
	if (!use_pager)
278
		if (pledge("stdio rpath flock wpath cpath", NULL) == -1)
279
			err((int)MANDOCLEVEL_SYSERR, "pledge");
280
281
	/* Parse arguments. */
282
283
	if (argc > 0) {
284
		argc -= optind;
285
		argv += optind;
286
	}
287
	resp = NULL;
288
289
	/*
290
	 * Quirks for help(1)
291
	 * and for a man(1) section argument without -s.
292
	 */
293
294
	if (search.argmode == ARG_NAME) {
295
		if (*progname == 'h') {
296
			if (argc == 0) {
297
				argv = help_argv;
298
				argc = 1;
299
			}
300
		} else if (argc > 1 &&
301
		    ((uc = (unsigned char *)argv[0]) != NULL) &&
302
		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
303
		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
304
		     (uc[0] == 'n' && uc[1] == '\0'))) {
305
			search.sec = (char *)uc;
306
			argv++;
307
			argc--;
308
		}
309
		if (search.arch == NULL)
310
			search.arch = getenv("MACHINE");
311
		if (search.arch == NULL)
312
			search.arch = MACHINE;
313
	}
314
315
	rc = MANDOCLEVEL_OK;
316
317
	/* man(1), whatis(1), apropos(1) */
318
319
	if (search.argmode != ARG_FILE) {
320
		if (argc == 0)
321
			usage(search.argmode);
322
323
		if (search.argmode == ARG_NAME &&
324
		    outmode == OUTMODE_ONE)
325
			search.firstmatch = 1;
326
327
		/* Access the mandoc database. */
328
329
		manconf_parse(&conf, conf_file, defpaths, auxpaths);
330
		mansearch_setup(1);
331
		if ( ! mansearch(&search, &conf.manpath,
332
		    argc, argv, &res, &sz))
333
			usage(search.argmode);
334
335
		if (sz == 0) {
336
			if (search.argmode == ARG_NAME)
337
				fs_search(&search, &conf.manpath,
338
				    argc, argv, &res, &sz);
339
			else
340
				warnx("nothing appropriate");
341
		}
342
343
		if (sz == 0) {
344
			rc = MANDOCLEVEL_BADARG;
345
			goto out;
346
		}
347
348
		/*
349
		 * For standard man(1) and -a output mode,
350
		 * prepare for copying filename pointers
351
		 * into the program parameter array.
352
		 */
353
354
		if (outmode == OUTMODE_ONE) {
355
			argc = 1;
356
			best_prio = 20;
357
		} else if (outmode == OUTMODE_ALL)
358
			argc = (int)sz;
359
360
		/* Iterate all matching manuals. */
361
362
		resp = res;
363
		for (i = 0; i < sz; i++) {
364
			if (outmode == OUTMODE_FLN)
365
				puts(res[i].file);
366
			else if (outmode == OUTMODE_LST)
367
				printf("%s - %s\n", res[i].names,
368
				    res[i].output == NULL ? "" :
369
				    res[i].output);
370
			else if (outmode == OUTMODE_ONE) {
371
				/* Search for the best section. */
372
				sec = res[i].file;
373
				sec += strcspn(sec, "123456789");
374
				if (sec[0] == '\0')
375
					continue;
376
				prio = sec_prios[sec[0] - '1'];
377
				if (sec[1] != '/')
378
					prio += 10;
379
				if (prio >= best_prio)
380
					continue;
381
				best_prio = prio;
382
				resp = res + i;
383
			}
384
		}
385
386
		/*
387
		 * For man(1), -a and -i output mode, fall through
388
		 * to the main mandoc(1) code iterating files
389
		 * and running the parsers on each of them.
390
		 */
391
392
		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
393
			goto out;
394
	}
395
396
	/* mandoc(1) */
397
398
	if (use_pager) {
399
		if (pledge("stdio rpath tmppath tty proc exec wpath cpath", NULL) == -1)
400
			err((int)MANDOCLEVEL_SYSERR, "pledge");
401
	} else {
402
		if (pledge("stdio rpath wpath cpath", NULL) == -1)
403
			err((int)MANDOCLEVEL_SYSERR, "pledge");
404
	}
405
406
	if (search.argmode == ARG_FILE && ! moptions(&options, auxpaths))
407
		return (int)MANDOCLEVEL_BADARG;
408
409
	mchars_alloc();
410
	curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
411
412
	/*
413
	 * Conditionally start up the lookaside buffer before parsing.
414
	 */
415
	if (OUTT_MAN == curp.outtype)
416
		mparse_keep(curp.mp);
417
418
	if (argc < 1) {
419
		if (use_pager)
420
			tag_files = tag_init();
421
		parse(&curp, STDIN_FILENO, "<stdin>");
422
	}
423
424
	while (argc > 0) {
425
		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
426
		if (fd != -1) {
427
			if (use_pager) {
428
				tag_files = tag_init();
429
				use_pager = 0;
430
			}
431
432
			if (resp == NULL)
433
				parse(&curp, fd, *argv);
434
			else if (resp->form & FORM_SRC) {
435
				/* For .so only; ignore failure. */
436
				chdir(conf.manpath.paths[resp->ipath]);
437
				parse(&curp, fd, resp->file);
438
			} else
439
				passthrough(resp->file, fd,
440
				    conf.output.synopsisonly);
441
442
			if (argc > 1 && curp.outtype <= OUTT_UTF8)
443
				terminal_sepline(curp.outdata);
444
		} else if (rc < MANDOCLEVEL_ERROR)
445
			rc = MANDOCLEVEL_ERROR;
446
447
		if (MANDOCLEVEL_OK != rc && curp.wstop)
448
			break;
449
450
		if (resp != NULL)
451
			resp++;
452
		else
453
			argv++;
454
		if (--argc)
455
			mparse_reset(curp.mp);
456
	}
457
458
	if (curp.outdata != NULL) {
459
		switch (curp.outtype) {
460
		case OUTT_HTML:
461
			html_free(curp.outdata);
462
			break;
463
		case OUTT_UTF8:
464
		case OUTT_LOCALE:
465
		case OUTT_ASCII:
466
			ascii_free(curp.outdata);
467
			break;
468
		case OUTT_PDF:
469
		case OUTT_PS:
470
			pspdf_free(curp.outdata);
471
			break;
472
		default:
473
			break;
474
		}
475
	}
476
	mparse_free(curp.mp);
477
	mchars_free();
478
479
out:
480
	if (search.argmode != ARG_FILE) {
481
		manconf_free(&conf);
482
		mansearch_free(res, sz);
483
		mansearch_setup(0);
484
	}
485
486
	free(defos);
487
488
	/*
489
	 * When using a pager, finish writing both temporary files,
490
	 * fork it, wait for the user to close it, and clean up.
491
	 */
492
493
	if (tag_files != NULL) {
494
		fclose(stdout);
495
		tag_write();
496
		man_pgid = getpgid(0);
497
		tag_files->tcpgid = man_pgid == getpid() ?
498
		    getpgid(getppid()) : man_pgid;
499
		pager_pid = 0;
500
		signum = SIGSTOP;
501
		for (;;) {
502
503
			/* Stop here until moved to the foreground. */
504
505
			tc_pgid = tcgetpgrp(STDIN_FILENO);
506
			if (tc_pgid != man_pgid) {
507
				if (tc_pgid == pager_pid) {
508
					(void)tcsetpgrp(STDIN_FILENO,
509
					    man_pgid);
510
					if (signum == SIGTTIN)
511
						continue;
512
				} else
513
					tag_files->tcpgid = tc_pgid;
514
				kill(0, signum);
515
				continue;
516
			}
517
518
			/* Once in the foreground, activate the pager. */
519
520
			if (pager_pid) {
521
				(void)tcsetpgrp(STDIN_FILENO, pager_pid);
522
				kill(pager_pid, SIGCONT);
523
			} else
524
				pager_pid = spawn_pager(tag_files);
525
526
			/* Wait for the pager to stop or exit. */
527
528
			while ((pid = waitpid(pager_pid, &status,
529
			    WUNTRACED)) == -1 && errno == EINTR)
530
				continue;
531
532
			if (pid == -1) {
533
				warn("wait");
534
				rc = MANDOCLEVEL_SYSERR;
535
				break;
536
			}
537
			if (!WIFSTOPPED(status))
538
				break;
539
540
			signum = WSTOPSIG(status);
541
		}
542
		tag_unlink();
543
	}
544
545
	return (int)rc;
546
}
547
548
static void
549
usage(enum argmode argmode)
550
{
551
552
	switch (argmode) {
553
	case ARG_FILE:
554
		fputs("usage: mandoc [-acfhkl] [-I os=name] "
555
		    "[-K encoding] [-mformat] [-O option]\n"
556
		    "\t      [-T output] [-W level] [file ...]\n", stderr);
557
		break;
558
	case ARG_NAME:
559
		fputs("usage: man [-acfhklw] [-C file] [-I os=name] "
560
		    "[-K encoding] [-M path] [-m path]\n"
561
		    "\t   [-O option=value] [-S subsection] [-s section] "
562
		    "[-T output] [-W level]\n"
563
		    "\t   [section] name ...\n", stderr);
564
		break;
565
	case ARG_WORD:
566
		fputs("usage: whatis [-acfhklw] [-C file] "
567
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
568
		    "\t      [-s section] name ...\n", stderr);
569
		break;
570
	case ARG_EXPR:
571
		fputs("usage: apropos [-acfhklw] [-C file] "
572
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
573
		    "\t       [-s section] expression ...\n", stderr);
574
		break;
575
	}
576
	exit((int)MANDOCLEVEL_BADARG);
577
}
578
579
static int
580
fs_lookup(const struct manpaths *paths, size_t ipath,
581
	const char *sec, const char *arch, const char *name,
582
	struct manpage **res, size_t *ressz)
583
{
584
	glob_t		 globinfo;
585
	struct manpage	*page;
586
	char		*file;
587
	int		 form, globres;
588
589
	form = FORM_SRC;
590
	mandoc_asprintf(&file, "%s/man%s/%s.%s",
591
	    paths->paths[ipath], sec, name, sec);
592
	if (access(file, R_OK) != -1)
593
		goto found;
594
	free(file);
595
596
	mandoc_asprintf(&file, "%s/cat%s/%s.0",
597
	    paths->paths[ipath], sec, name);
598
	if (access(file, R_OK) != -1) {
599
		form = FORM_CAT;
600
		goto found;
601
	}
602
	free(file);
603
604
	if (arch != NULL) {
605
		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
606
		    paths->paths[ipath], sec, arch, name, sec);
607
		if (access(file, R_OK) != -1)
608
			goto found;
609
		free(file);
610
	}
611
612
	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
613
	    paths->paths[ipath], sec, name);
614
	globres = glob(file, 0, NULL, &globinfo);
615
	if (globres != 0 && globres != GLOB_NOMATCH)
616
		warn("%s: glob", file);
617
	free(file);
618
	if (globres == 0)
619
		file = mandoc_strdup(*globinfo.gl_pathv);
620
	globfree(&globinfo);
621
	if (globres != 0)
622
		return 0;
623
624
found:
625
	warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s",
626
	    name, sec, paths->paths[ipath]);
627
	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
628
	page = *res + (*ressz - 1);
629
	page->file = file;
630
	page->names = NULL;
631
	page->output = NULL;
632
	page->ipath = ipath;
633
	page->bits = NAME_FILE & NAME_MASK;
634
	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
635
	page->form = form;
636
	return 1;
637
}
638
639
static void
640
fs_search(const struct mansearch *cfg, const struct manpaths *paths,
641
	int argc, char **argv, struct manpage **res, size_t *ressz)
642
{
643
	const char *const sections[] =
644
	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
645
	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
646
647
	size_t		 ipath, isec, lastsz;
648
649
	assert(cfg->argmode == ARG_NAME);
650
651
	*res = NULL;
652
	*ressz = lastsz = 0;
653
	while (argc) {
654
		for (ipath = 0; ipath < paths->sz; ipath++) {
655
			if (cfg->sec != NULL) {
656
				if (fs_lookup(paths, ipath, cfg->sec,
657
				    cfg->arch, *argv, res, ressz) &&
658
				    cfg->firstmatch)
659
					return;
660
			} else for (isec = 0; isec < nsec; isec++)
661
				if (fs_lookup(paths, ipath, sections[isec],
662
				    cfg->arch, *argv, res, ressz) &&
663
				    cfg->firstmatch)
664
					return;
665
		}
666
		if (*ressz == lastsz)
667
			warnx("No entry for %s in the manual.", *argv);
668
		lastsz = *ressz;
669
		argv++;
670
		argc--;
671
	}
672
}
673
674
static void
675
parse(struct curparse *curp, int fd, const char *file)
676
{
677
	enum mandoclevel  rctmp;
678
	struct roff_man	 *man;
679
680
	/* Begin by parsing the file itself. */
681
682
	assert(file);
683
	assert(fd >= 0);
684
685
	rctmp = mparse_readfd(curp->mp, fd, file);
686
	if (fd != STDIN_FILENO)
687
		close(fd);
688
	if (rc < rctmp)
689
		rc = rctmp;
690
691
	/*
692
	 * With -Wstop and warnings or errors of at least the requested
693
	 * level, do not produce output.
694
	 */
695
696
	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
697
		return;
698
699
	/* If unset, allocate output dev now (if applicable). */
700
701
	if (curp->outdata == NULL) {
702
		switch (curp->outtype) {
703
		case OUTT_HTML:
704
			curp->outdata = html_alloc(curp->outopts);
705
			break;
706
		case OUTT_UTF8:
707
			curp->outdata = utf8_alloc(curp->outopts);
708
			break;
709
		case OUTT_LOCALE:
710
			curp->outdata = locale_alloc(curp->outopts);
711
			break;
712
		case OUTT_ASCII:
713
			curp->outdata = ascii_alloc(curp->outopts);
714
			break;
715
		case OUTT_PDF:
716
			curp->outdata = pdf_alloc(curp->outopts);
717
			break;
718
		case OUTT_PS:
719
			curp->outdata = ps_alloc(curp->outopts);
720
			break;
721
		default:
722
			break;
723
		}
724
	}
725
726
	mparse_result(curp->mp, &man, NULL);
727
728
	/* Execute the out device, if it exists. */
729
730
	if (man == NULL)
731
		return;
732
	if (man->macroset == MACROSET_MDOC) {
733
		mdoc_validate(man);
734
		switch (curp->outtype) {
735
		case OUTT_HTML:
736
			html_mdoc(curp->outdata, man);
737
			break;
738
		case OUTT_TREE:
739
			tree_mdoc(curp->outdata, man);
740
			break;
741
		case OUTT_MAN:
742
			man_mdoc(curp->outdata, man);
743
			break;
744
		case OUTT_PDF:
745
		case OUTT_ASCII:
746
		case OUTT_UTF8:
747
		case OUTT_LOCALE:
748
		case OUTT_PS:
749
			terminal_mdoc(curp->outdata, man);
750
			break;
751
		default:
752
			break;
753
		}
754
	}
755
	if (man->macroset == MACROSET_MAN) {
756
		man_validate(man);
757
		switch (curp->outtype) {
758
		case OUTT_HTML:
759
			html_man(curp->outdata, man);
760
			break;
761
		case OUTT_TREE:
762
			tree_man(curp->outdata, man);
763
			break;
764
		case OUTT_MAN:
765
			man_man(curp->outdata, man);
766
			break;
767
		case OUTT_PDF:
768
		case OUTT_ASCII:
769
		case OUTT_UTF8:
770
		case OUTT_LOCALE:
771
		case OUTT_PS:
772
			terminal_man(curp->outdata, man);
773
			break;
774
		default:
775
			break;
776
		}
777
	}
778
}
779
780
static void
781
passthrough(const char *file, int fd, int synopsis_only)
782
{
783
	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
784
	const char	 synr[] = "SYNOPSIS";
785
786
	FILE		*stream;
787
	const char	*syscall;
788
	char		*line, *cp;
789
	size_t		 linesz;
790
	int		 print;
791
792
	line = NULL;
793
	linesz = 0;
794
795
	if ((stream = fdopen(fd, "r")) == NULL) {
796
		close(fd);
797
		syscall = "fdopen";
798
		goto fail;
799
	}
800
801
	print = 0;
802
	while (getline(&line, &linesz, stream) != -1) {
803
		cp = line;
804
		if (synopsis_only) {
805
			if (print) {
806
				if ( ! isspace((unsigned char)*cp))
807
					goto done;
808
				while (isspace((unsigned char)*cp))
809
					cp++;
810
			} else {
811
				if (strcmp(cp, synb) == 0 ||
812
				    strcmp(cp, synr) == 0)
813
					print = 1;
814
				continue;
815
			}
816
		}
817
		if (fputs(cp, stdout)) {
818
			fclose(stream);
819
			syscall = "fputs";
820
			goto fail;
821
		}
822
	}
823
824
	if (ferror(stream)) {
825
		fclose(stream);
826
		syscall = "getline";
827
		goto fail;
828
	}
829
830
done:
831
	free(line);
832
	fclose(stream);
833
	return;
834
835
fail:
836
	free(line);
837
	warn("%s: SYSERR: %s", file, syscall);
838
	if (rc < MANDOCLEVEL_SYSERR)
839
		rc = MANDOCLEVEL_SYSERR;
840
}
841
842
static int
843
koptions(int *options, char *arg)
844
{
845
846
	if ( ! strcmp(arg, "utf-8")) {
847
		*options |=  MPARSE_UTF8;
848
		*options &= ~MPARSE_LATIN1;
849
	} else if ( ! strcmp(arg, "iso-8859-1")) {
850
		*options |=  MPARSE_LATIN1;
851
		*options &= ~MPARSE_UTF8;
852
	} else if ( ! strcmp(arg, "us-ascii")) {
853
		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
854
	} else {
855
		warnx("-K %s: Bad argument", arg);
856
		return 0;
857
	}
858
	return 1;
859
}
860
861
static int
862
moptions(int *options, char *arg)
863
{
864
865
	if (arg == NULL)
866
		/* nothing to do */;
867
	else if (0 == strcmp(arg, "doc"))
868
		*options |= MPARSE_MDOC;
869
	else if (0 == strcmp(arg, "andoc"))
870
		/* nothing to do */;
871
	else if (0 == strcmp(arg, "an"))
872
		*options |= MPARSE_MAN;
873
	else {
874
		warnx("-m %s: Bad argument", arg);
875
		return 0;
876
	}
877
878
	return 1;
879
}
880
881
static int
882
toptions(struct curparse *curp, char *arg)
883
{
884
885
	if (0 == strcmp(arg, "ascii"))
886
		curp->outtype = OUTT_ASCII;
887
	else if (0 == strcmp(arg, "lint")) {
888
		curp->outtype = OUTT_LINT;
889
		curp->wlevel  = MANDOCLEVEL_WARNING;
890
	} else if (0 == strcmp(arg, "tree"))
891
		curp->outtype = OUTT_TREE;
892
	else if (0 == strcmp(arg, "man"))
893
		curp->outtype = OUTT_MAN;
894
	else if (0 == strcmp(arg, "html"))
895
		curp->outtype = OUTT_HTML;
896
	else if (0 == strcmp(arg, "utf8"))
897
		curp->outtype = OUTT_UTF8;
898
	else if (0 == strcmp(arg, "locale"))
899
		curp->outtype = OUTT_LOCALE;
900
	else if (0 == strcmp(arg, "xhtml"))
901
		curp->outtype = OUTT_HTML;
902
	else if (0 == strcmp(arg, "ps"))
903
		curp->outtype = OUTT_PS;
904
	else if (0 == strcmp(arg, "pdf"))
905
		curp->outtype = OUTT_PDF;
906
	else {
907
		warnx("-T %s: Bad argument", arg);
908
		return 0;
909
	}
910
911
	return 1;
912
}
913
914
static int
915
woptions(struct curparse *curp, char *arg)
916
{
917
	char		*v, *o;
918
	const char	*toks[7];
919
920
	toks[0] = "stop";
921
	toks[1] = "all";
922
	toks[2] = "warning";
923
	toks[3] = "error";
924
	toks[4] = "unsupp";
925
	toks[5] = "fatal";
926
	toks[6] = NULL;
927
928
	while (*arg) {
929
		o = arg;
930
		switch (getsubopt(&arg, (char * const *)toks, &v)) {
931
		case 0:
932
			curp->wstop = 1;
933
			break;
934
		case 1:
935
		case 2:
936
			curp->wlevel = MANDOCLEVEL_WARNING;
937
			break;
938
		case 3:
939
			curp->wlevel = MANDOCLEVEL_ERROR;
940
			break;
941
		case 4:
942
			curp->wlevel = MANDOCLEVEL_UNSUPP;
943
			break;
944
		case 5:
945
			curp->wlevel = MANDOCLEVEL_BADARG;
946
			break;
947
		default:
948
			warnx("-W %s: Bad argument", o);
949
			return 0;
950
		}
951
	}
952
953
	return 1;
954
}
955
956
static void
957
mmsg(enum mandocerr t, enum mandoclevel lvl,
958
		const char *file, int line, int col, const char *msg)
959
{
960
	const char	*mparse_msg;
961
962
	fprintf(stderr, "%s: %s:", getprogname(), file);
963
964
	if (line)
965
		fprintf(stderr, "%d:%d:", line, col + 1);
966
967
	fprintf(stderr, " %s", mparse_strlevel(lvl));
968
969
	if (NULL != (mparse_msg = mparse_strerror(t)))
970
		fprintf(stderr, ": %s", mparse_msg);
971
972
	if (msg)
973
		fprintf(stderr, ": %s", msg);
974
975
	fputc('\n', stderr);
976
}
977
978
static pid_t
979
spawn_pager(struct tag_files *tag_files)
980
{
981
	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
982
#define MAX_PAGER_ARGS 16
983
	char		*argv[MAX_PAGER_ARGS];
984
	const char	*pager;
985
	char		*cp;
986
	size_t		 cmdlen;
987
	int		 argc;
988
	pid_t		 pager_pid;
989
990
	pager = getenv("MANPAGER");
991
	if (pager == NULL || *pager == '\0')
992
		pager = getenv("PAGER");
993
	if (pager == NULL || *pager == '\0')
994
		pager = "more -s";
995
	cp = mandoc_strdup(pager);
996
997
	/*
998
	 * Parse the pager command into words.
999
	 * Intentionally do not do anything fancy here.
1000
	 */
1001
1002
	argc = 0;
1003
	while (argc + 4 < MAX_PAGER_ARGS) {
1004
		argv[argc++] = cp;
1005
		cp = strchr(cp, ' ');
1006
		if (cp == NULL)
1007
			break;
1008
		*cp++ = '\0';
1009
		while (*cp == ' ')
1010
			cp++;
1011
		if (*cp == '\0')
1012
			break;
1013
	}
1014
1015
	/* For more(1) and less(1), use the tag file. */
1016
1017
	if ((cmdlen = strlen(argv[0])) >= 4) {
1018
		cp = argv[0] + cmdlen - 4;
1019
		if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) {
1020
			argv[argc++] = mandoc_strdup("-T");
1021
			argv[argc++] = tag_files->tfn;
1022
		}
1023
	}
1024
	argv[argc++] = tag_files->ofn;
1025
	argv[argc] = NULL;
1026
1027
	switch (pager_pid = fork()) {
1028
	case -1:
1029
		err((int)MANDOCLEVEL_SYSERR, "fork");
1030
	case 0:
1031
		break;
1032
	default:
1033
		(void)setpgid(pager_pid, 0);
1034
		(void)tcsetpgrp(STDIN_FILENO, pager_pid);
1035
		if (pledge("stdio rpath tmppath tty proc wpath cpath", NULL) == -1)
1036
			err((int)MANDOCLEVEL_SYSERR, "pledge");
1037
		tag_files->pager_pid = pager_pid;
1038
		return pager_pid;
1039
	}
1040
1041
	/* The child process becomes the pager. */
1042
1043
	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1044
		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1045
	close(tag_files->ofd);
1046
	close(tag_files->tfd);
1047
1048
	/* Do not start the pager before controlling the terminal. */
1049
1050
	while (tcgetpgrp(STDIN_FILENO) != getpid())
1051
		nanosleep(&timeout, NULL);
1052
1053
	execvp(argv[0], argv);
1054
	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1055
}