GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/mandoc/main.c Lines: 326 577 56.5 %
Date: 2017-11-13 Branches: 197 443 44.5 %

Line Branch Exec Source
1
/*	$OpenBSD: main.c,v 1.205 2017/08/21 15:41:26 schwarze Exp $ */
2
/*
3
 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4
 * Copyright (c) 2010-2012, 2014-2017 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 "mandoc_xr.h"
41
#include "roff.h"
42
#include "mdoc.h"
43
#include "man.h"
44
#include "tag.h"
45
#include "main.h"
46
#include "manconf.h"
47
#include "mansearch.h"
48
49
enum	outmode {
50
	OUTMODE_DEF = 0,
51
	OUTMODE_FLN,
52
	OUTMODE_LST,
53
	OUTMODE_ALL,
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_MARKDOWN,	/* -Tmarkdown */
65
	OUTT_LINT,	/* -Tlint */
66
	OUTT_PS,	/* -Tps */
67
	OUTT_PDF	/* -Tpdf */
68
};
69
70
struct	curparse {
71
	struct mparse	 *mp;
72
	struct manoutput *outopts;	/* output options */
73
	void		 *outdata;	/* data for output */
74
	char		 *os_s;		/* operating system for display */
75
	int		  wstop;	/* stop after a file with a warning */
76
	enum mandocerr	  mmin;		/* ignore messages below this */
77
	enum mandoc_os	  os_e;		/* check base system conventions */
78
	enum outt	  outtype;	/* which output to use */
79
};
80
81
82
int			  mandocdb(int, char *[]);
83
84
static	void		  check_xr(const char *);
85
static	int		  fs_lookup(const struct manpaths *,
86
				size_t ipath, const char *,
87
				const char *, const char *,
88
				struct manpage **, size_t *);
89
static	int		  fs_search(const struct mansearch *,
90
				const struct manpaths *, int, char**,
91
				struct manpage **, size_t *);
92
static	int		  koptions(int *, char *);
93
static	void		  moptions(int *, char *);
94
static	void		  mmsg(enum mandocerr, enum mandoclevel,
95
				const char *, int, int, const char *);
96
static	void		  outdata_alloc(struct curparse *);
97
static	void		  parse(struct curparse *, int, const char *);
98
static	void		  passthrough(const char *, int, int);
99
static	pid_t		  spawn_pager(struct tag_files *);
100
static	int		  toptions(struct curparse *, char *);
101
static	void		  usage(enum argmode) __attribute__((__noreturn__));
102
static	int		  woptions(struct curparse *, char *);
103
104
static	const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
105
static	char		  help_arg[] = "help";
106
static	char		 *help_argv[] = {help_arg, NULL};
107
static	enum mandoclevel  rc;
108
static	FILE		 *mmsg_stream;
109
110
111
int
112
main(int argc, char *argv[])
113
{
114
8650
	struct manconf	 conf;
115
4325
	struct mansearch search;
116
4325
	struct curparse	 curp;
117
	struct tag_files *tag_files;
118
4325
	struct manpage	*res, *resp;
119
	const char	*progname, *sec, *thisarg;
120
	char		*conf_file, *defpaths, *auxpaths;
121
4325
	char		*oarg;
122
	unsigned char	*uc;
123
4325
	size_t		 i, sz;
124
	int		 prio, best_prio;
125
	enum outmode	 outmode;
126
	int		 fd;
127
	int		 show_usage;
128
4325
	int		 options;
129
	int		 use_pager;
130
4325
	int		 status, signum;
131
	int		 c;
132
	pid_t		 pager_pid, tc_pgid, man_pgid, pid;
133
134
4325
	progname = getprogname();
135

8650
	if (strncmp(progname, "mandocdb", 8) == 0 ||
136
4325
	    strncmp(progname, "makewhatis", 10) == 0)
137
37
		return mandocdb(argc, argv);
138
139
4288
	if (pledge("stdio rpath tmppath tty proc exec flock cpath wpath", NULL) == -1)
140
		err((int)MANDOCLEVEL_SYSERR, "pledge");
141
142
	/* Search options. */
143
144
4288
	memset(&conf, 0, sizeof(conf));
145
	conf_file = defpaths = NULL;
146
	auxpaths = NULL;
147
148
4288
	memset(&search, 0, sizeof(struct mansearch));
149
4288
	search.outkey = "Nd";
150
4288
	oarg = NULL;
151
152
4288
	if (strcmp(progname, "man") == 0)
153
92
		search.argmode = ARG_NAME;
154
4196
	else if (strncmp(progname, "apropos", 7) == 0)
155
		search.argmode = ARG_EXPR;
156
4196
	else if (strncmp(progname, "whatis", 6) == 0)
157
		search.argmode = ARG_WORD;
158
4196
	else if (strncmp(progname, "help", 4) == 0)
159
		search.argmode = ARG_NAME;
160
	else
161
		search.argmode = ARG_FILE;
162
163
	/* Parser and formatter options. */
164
165
4288
	memset(&curp, 0, sizeof(struct curparse));
166
4288
	curp.outtype = OUTT_LOCALE;
167
4288
	curp.mmin = MANDOCERR_MAX;
168
4288
	curp.outopts = &conf.output;
169
4288
	options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
170
4288
	mmsg_stream = stderr;
171
172
	use_pager = 1;
173
	tag_files = NULL;
174
	show_usage = 0;
175
	outmode = OUTMODE_DEF;
176
177
42741
	while ((c = getopt(argc, argv,
178
14247
	    "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
179

9959
		if (c == 'i' && search.argmode == ARG_EXPR) {
180
			optind--;
181
			break;
182
		}
183




9959
		switch (c) {
184
		case 'a':
185
			outmode = OUTMODE_ALL;
186
			break;
187
		case 'C':
188
			conf_file = optarg;
189
			break;
190
		case 'c':
191
			use_pager = 0;
192
			break;
193
		case 'f':
194
			search.argmode = ARG_WORD;
195
			break;
196
		case 'h':
197
			conf.output.synopsisonly = 1;
198
			use_pager = 0;
199
			outmode = OUTMODE_ALL;
200
			break;
201
		case 'I':
202
3491
			if (strncmp(optarg, "os=", 3)) {
203
				warnx("-I %s: Bad argument", optarg);
204
				return (int)MANDOCLEVEL_BADARG;
205
			}
206
3491
			if (curp.os_s != NULL) {
207
				warnx("-I %s: Duplicate argument", optarg);
208
				return (int)MANDOCLEVEL_BADARG;
209
			}
210
3491
			curp.os_s = mandoc_strdup(optarg + 3);
211
3491
			break;
212
		case 'K':
213
			if ( ! koptions(&options, optarg))
214
				return (int)MANDOCLEVEL_BADARG;
215
			break;
216
		case 'k':
217
56
			search.argmode = ARG_EXPR;
218
56
			break;
219
		case 'l':
220
			search.argmode = ARG_FILE;
221
			outmode = OUTMODE_ALL;
222
			break;
223
		case 'M':
224
92
			defpaths = optarg;
225
92
			break;
226
		case 'm':
227
712
			auxpaths = optarg;
228
712
			break;
229
		case 'O':
230
635
			oarg = optarg;
231
635
			break;
232
		case 'S':
233
12
			search.arch = optarg;
234
12
			break;
235
		case 's':
236
6
			search.sec = optarg;
237
6
			break;
238
		case 'T':
239
4196
			if ( ! toptions(&curp, optarg))
240
				return (int)MANDOCLEVEL_BADARG;
241
			break;
242
		case 'W':
243
687
			if ( ! woptions(&curp, optarg))
244
				return (int)MANDOCLEVEL_BADARG;
245
			break;
246
		case 'w':
247
			outmode = OUTMODE_FLN;
248
72
			break;
249
		default:
250
			show_usage = 1;
251
			break;
252
		}
253
	}
254
255
4288
	if (show_usage)
256
		usage(search.argmode);
257
258
	/* Postprocess options. */
259
260
4288
	if (outmode == OUTMODE_DEF) {
261
4216
		switch (search.argmode) {
262
		case ARG_FILE:
263
			outmode = OUTMODE_ALL;
264
			use_pager = 0;
265
4196
			break;
266
		case ARG_NAME:
267
			outmode = OUTMODE_ONE;
268
			break;
269
		default:
270
			outmode = OUTMODE_LST;
271
20
			break;
272
		}
273
	}
274
275
4288
	if (oarg != NULL) {
276
635
		if (outmode == OUTMODE_LST)
277
2
			search.outkey = oarg;
278
		else {
279
1899
			while (oarg != NULL) {
280
				thisarg = oarg;
281
1266
				if (manconf_output(&conf.output,
282
1266
				    strsep(&oarg, ","), 0) == 0)
283
633
					continue;
284
				warnx("-O %s: Bad argument", thisarg);
285
				return (int)MANDOCLEVEL_BADARG;
286
			}
287
		}
288
	}
289
290

12772
	if (outmode == OUTMODE_FLN ||
291
4288
	    outmode == OUTMODE_LST ||
292
4196
	    !isatty(STDOUT_FILENO))
293
4288
		use_pager = 0;
294
295
4288
	if (!use_pager)
296
4288
		if (pledge("stdio rpath flock cpath wpath", NULL) == -1)
297
			err((int)MANDOCLEVEL_SYSERR, "pledge");
298
299
	/* Parse arguments. */
300
301
4288
	if (argc > 0) {
302
4288
		argc -= optind;
303
4288
		argv += optind;
304
4288
	}
305
	resp = NULL;
306
307
	/*
308
	 * Quirks for help(1)
309
	 * and for a man(1) section argument without -s.
310
	 */
311
312
4288
	if (search.argmode == ARG_NAME) {
313
36
		if (*progname == 'h') {
314
			if (argc == 0) {
315
				argv = help_argv;
316
				argc = 1;
317
			}
318

36
		} else if (argc > 1 &&
319
		    ((uc = (unsigned char *)argv[0]) != NULL) &&
320
		    ((isdigit(uc[0]) && (uc[1] == '\0' ||
321
		      (isalpha(uc[1]) && uc[2] == '\0'))) ||
322
		     (uc[0] == 'n' && uc[1] == '\0'))) {
323
			search.sec = (char *)uc;
324
			argv++;
325
			argc--;
326
		}
327
36
		if (search.arch == NULL)
328
26
			search.arch = getenv("MACHINE");
329
36
		if (search.arch == NULL)
330
26
			search.arch = MACHINE;
331
	}
332
333
4288
	rc = MANDOCLEVEL_OK;
334
335
	/* man(1), whatis(1), apropos(1) */
336
337
4288
	if (search.argmode != ARG_FILE) {
338
184
		if (search.argmode == ARG_NAME &&
339
92
		    outmode == OUTMODE_ONE)
340
			search.firstmatch = 1;
341
342
		/* Access the mandoc database. */
343
344
92
		manconf_parse(&conf, conf_file, defpaths, auxpaths);
345
92
		if ( ! mansearch(&search, &conf.manpath,
346
		    argc, argv, &res, &sz))
347
			usage(search.argmode);
348
349
92
		if (sz == 0) {
350
10
			if (search.argmode == ARG_NAME)
351
6
				fs_search(&search, &conf.manpath,
352
				    argc, argv, &res, &sz);
353
			else
354
4
				warnx("nothing appropriate");
355
		}
356
357
92
		if (sz == 0) {
358
4
			rc = MANDOCLEVEL_BADARG;
359
4
			goto out;
360
		}
361
362
		/*
363
		 * For standard man(1) and -a output mode,
364
		 * prepare for copying filename pointers
365
		 * into the program parameter array.
366
		 */
367
368
88
		if (outmode == OUTMODE_ONE) {
369
			argc = 1;
370
			best_prio = 20;
371
88
		} else if (outmode == OUTMODE_ALL)
372
			argc = (int)sz;
373
374
		/* Iterate all matching manuals. */
375
376
88
		resp = res;
377
396
		for (i = 0; i < sz; i++) {
378
110
			if (outmode == OUTMODE_FLN)
379
84
				puts(res[i].file);
380
26
			else if (outmode == OUTMODE_LST)
381
52
				printf("%s - %s\n", res[i].names,
382
78
				    res[i].output == NULL ? "" :
383
				    res[i].output);
384
			else if (outmode == OUTMODE_ONE) {
385
				/* Search for the best section. */
386
				sec = res[i].file;
387
				sec += strcspn(sec, "123456789");
388
				if (sec[0] == '\0')
389
					continue;
390
				prio = sec_prios[sec[0] - '1'];
391
				if (sec[1] != '/')
392
					prio += 10;
393
				if (prio >= best_prio)
394
					continue;
395
				best_prio = prio;
396
				resp = res + i;
397
			}
398
		}
399
400
		/*
401
		 * For man(1), -a and -i output mode, fall through
402
		 * to the main mandoc(1) code iterating files
403
		 * and running the parsers on each of them.
404
		 */
405
406
88
		if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
407
			goto out;
408
	}
409
410
	/* mandoc(1) */
411
412
4196
	if (use_pager) {
413
		if (pledge("stdio rpath tmppath tty proc exec flock cpath wpath", NULL) == -1)
414
			err((int)MANDOCLEVEL_SYSERR, "pledge");
415
	} else {
416
4196
		if (pledge("stdio rpath flock cpath wpath", NULL) == -1)
417
			err((int)MANDOCLEVEL_SYSERR, "pledge");
418
	}
419
420
4196
	if (search.argmode == ARG_FILE)
421
4196
		moptions(&options, auxpaths);
422
423
4196
	mchars_alloc();
424
8392
	curp.mp = mparse_alloc(options, curp.mmin, mmsg,
425
4196
	    curp.os_e, curp.os_s);
426
427
	/*
428
	 * Conditionally start up the lookaside buffer before parsing.
429
	 */
430
4196
	if (OUTT_MAN == curp.outtype)
431
633
		mparse_keep(curp.mp);
432
433
4196
	if (argc < 1) {
434
		if (use_pager)
435
			tag_files = tag_init();
436
		parse(&curp, STDIN_FILENO, "<stdin>");
437
	}
438
439
16784
	while (argc > 0) {
440
4196
		fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
441
4196
		if (fd != -1) {
442
4196
			if (use_pager) {
443
				tag_files = tag_init();
444
				use_pager = 0;
445
			}
446
447
4196
			if (resp == NULL)
448
4196
				parse(&curp, fd, *argv);
449
			else if (resp->form == FORM_SRC) {
450
				/* For .so only; ignore failure. */
451
				(void)chdir(conf.manpath.paths[resp->ipath]);
452
				parse(&curp, fd, resp->file);
453
			} else
454
				passthrough(resp->file, fd,
455
				    conf.output.synopsisonly);
456
457

8392
			if (ferror(stdout)) {
458
				if (tag_files != NULL) {
459
					warn("%s", tag_files->ofn);
460
					tag_unlink();
461
					tag_files = NULL;
462
				} else
463
					warn("stdout");
464
				rc = MANDOCLEVEL_SYSERR;
465
				break;
466
			}
467
468

4196
			if (argc > 1 && curp.outtype <= OUTT_UTF8) {
469
				if (curp.outdata == NULL)
470
					outdata_alloc(&curp);
471
				terminal_sepline(curp.outdata);
472
			}
473
		} else if (rc < MANDOCLEVEL_ERROR)
474
			rc = MANDOCLEVEL_ERROR;
475
476

4880
		if (MANDOCLEVEL_OK != rc && curp.wstop)
477
			break;
478
479
4196
		if (resp != NULL)
480
			resp++;
481
		else
482
4196
			argv++;
483
4196
		if (--argc)
484
			mparse_reset(curp.mp);
485
	}
486
487
4196
	if (curp.outdata != NULL) {
488

4444
		switch (curp.outtype) {
489
		case OUTT_HTML:
490
72
			html_free(curp.outdata);
491
72
			break;
492
		case OUTT_UTF8:
493
		case OUTT_LOCALE:
494
		case OUTT_ASCII:
495
2150
			ascii_free(curp.outdata);
496
2150
			break;
497
		case OUTT_PDF:
498
		case OUTT_PS:
499
			pspdf_free(curp.outdata);
500
			break;
501
		default:
502
			break;
503
		}
504
	}
505
4196
	mandoc_xr_free();
506
4196
	mparse_free(curp.mp);
507
4196
	mchars_free();
508
509
out:
510
4288
	if (search.argmode != ARG_FILE) {
511
92
		manconf_free(&conf);
512
92
		mansearch_free(res, sz);
513
92
	}
514
515
4288
	free(curp.os_s);
516
517
	/*
518
	 * When using a pager, finish writing both temporary files,
519
	 * fork it, wait for the user to close it, and clean up.
520
	 */
521
522
4288
	if (tag_files != NULL) {
523
		fclose(stdout);
524
		tag_write();
525
		man_pgid = getpgid(0);
526
		tag_files->tcpgid = man_pgid == getpid() ?
527
		    getpgid(getppid()) : man_pgid;
528
		pager_pid = 0;
529
		signum = SIGSTOP;
530
		for (;;) {
531
532
			/* Stop here until moved to the foreground. */
533
534
			tc_pgid = tcgetpgrp(tag_files->ofd);
535
			if (tc_pgid != man_pgid) {
536
				if (tc_pgid == pager_pid) {
537
					(void)tcsetpgrp(tag_files->ofd,
538
					    man_pgid);
539
					if (signum == SIGTTIN)
540
						continue;
541
				} else
542
					tag_files->tcpgid = tc_pgid;
543
				kill(0, signum);
544
				continue;
545
			}
546
547
			/* Once in the foreground, activate the pager. */
548
549
			if (pager_pid) {
550
				(void)tcsetpgrp(tag_files->ofd, pager_pid);
551
				kill(pager_pid, SIGCONT);
552
			} else
553
				pager_pid = spawn_pager(tag_files);
554
555
			/* Wait for the pager to stop or exit. */
556
557
			while ((pid = waitpid(pager_pid, &status,
558
			    WUNTRACED)) == -1 && errno == EINTR)
559
				continue;
560
561
			if (pid == -1) {
562
				warn("wait");
563
				rc = MANDOCLEVEL_SYSERR;
564
				break;
565
			}
566
			if (!WIFSTOPPED(status))
567
				break;
568
569
			signum = WSTOPSIG(status);
570
		}
571
		tag_unlink();
572
	}
573
574
4288
	return (int)rc;
575
4325
}
576
577
static void
578
usage(enum argmode argmode)
579
{
580
581
	switch (argmode) {
582
	case ARG_FILE:
583
		fputs("usage: mandoc [-ac] [-I os=name] "
584
		    "[-K encoding] [-mdoc | -man] [-O options]\n"
585
		    "\t      [-T output] [-W level] [file ...]\n", stderr);
586
		break;
587
	case ARG_NAME:
588
		fputs("usage: man [-acfhklw] [-C file] [-M path] "
589
		    "[-m path] [-S subsection]\n"
590
		    "\t   [[-s] section] name ...\n", stderr);
591
		break;
592
	case ARG_WORD:
593
		fputs("usage: whatis [-afk] [-C file] "
594
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
595
		    "\t      [-s section] name ...\n", stderr);
596
		break;
597
	case ARG_EXPR:
598
		fputs("usage: apropos [-afk] [-C file] "
599
		    "[-M path] [-m path] [-O outkey] [-S arch]\n"
600
		    "\t       [-s section] expression ...\n", stderr);
601
		break;
602
	}
603
	exit((int)MANDOCLEVEL_BADARG);
604
}
605
606
static int
607
fs_lookup(const struct manpaths *paths, size_t ipath,
608
	const char *sec, const char *arch, const char *name,
609
	struct manpage **res, size_t *ressz)
610
{
611
96
	glob_t		 globinfo;
612
	struct manpage	*page;
613
48
	char		*file;
614
	int		 globres;
615
	enum form	 form;
616
617
	form = FORM_SRC;
618
48
	mandoc_asprintf(&file, "%s/man%s/%s.%s",
619
48
	    paths->paths[ipath], sec, name, sec);
620
48
	if (access(file, R_OK) != -1)
621
		goto found;
622
42
	free(file);
623
624
42
	mandoc_asprintf(&file, "%s/cat%s/%s.0",
625
42
	    paths->paths[ipath], sec, name);
626
42
	if (access(file, R_OK) != -1) {
627
		form = FORM_CAT;
628
		goto found;
629
	}
630
42
	free(file);
631
632
42
	if (arch != NULL) {
633
36
		mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
634
36
		    paths->paths[ipath], sec, arch, name, sec);
635
36
		if (access(file, R_OK) != -1)
636
			goto found;
637
36
		free(file);
638
36
	}
639
640
42
	mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
641
42
	    paths->paths[ipath], sec, name);
642
42
	globres = glob(file, 0, NULL, &globinfo);
643
42
	if (globres != 0 && globres != GLOB_NOMATCH)
644
		warn("%s: glob", file);
645
42
	free(file);
646
42
	if (globres == 0)
647
		file = mandoc_strdup(*globinfo.gl_pathv);
648
42
	globfree(&globinfo);
649
42
	if (globres == 0)
650
		goto found;
651

48
	if (res != NULL || ipath + 1 != paths->sz)
652
39
		return 0;
653
654
3
	mandoc_asprintf(&file, "%s.%s", name, sec);
655
3
	globres = access(file, R_OK);
656
3
	free(file);
657
3
	return globres != -1;
658
659
found:
660
6
	warnx("outdated mandoc.db lacks %s(%s) entry, run makewhatis %s",
661
6
	    name, sec, paths->paths[ipath]);
662
6
	if (res == NULL) {
663
		free(file);
664
		return 1;
665
	}
666
6
	*res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
667
6
	page = *res + (*ressz - 1);
668
6
	page->file = file;
669
6
	page->names = NULL;
670
6
	page->output = NULL;
671
6
	page->ipath = ipath;
672
6
	page->bits = NAME_FILE & NAME_MASK;
673

24
	page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
674
6
	page->form = form;
675
6
	return 1;
676
48
}
677
678
static int
679
fs_search(const struct mansearch *cfg, const struct manpaths *paths,
680
	int argc, char **argv, struct manpage **res, size_t *ressz)
681
{
682
	const char *const sections[] =
683
	    {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
684
	const size_t nsec = sizeof(sections)/sizeof(sections[0]);
685
686
	size_t		 ipath, isec, lastsz;
687
688
18
	assert(cfg->argmode == ARG_NAME);
689
690
9
	if (res != NULL)
691
6
		*res = NULL;
692
9
	*ressz = lastsz = 0;
693
36
	while (argc) {
694
42
		for (ipath = 0; ipath < paths->sz; ipath++) {
695
12
			if (cfg->sec != NULL) {
696
10
				if (fs_lookup(paths, ipath, cfg->sec,
697
16
				    cfg->arch, *argv, res, ressz) &&
698
2
				    cfg->firstmatch)
699
					return 1;
700
88
			} else for (isec = 0; isec < nsec; isec++)
701
84
				if (fs_lookup(paths, ipath, sections[isec],
702
80
				    cfg->arch, *argv, res, ressz) &&
703
4
				    cfg->firstmatch)
704
					return 1;
705
		}
706

15
		if (res != NULL && *ressz == lastsz)
707
			warnx("No entry for %s in the manual.", *argv);
708
9
		lastsz = *ressz;
709
9
		argv++;
710
9
		argc--;
711
	}
712
9
	return 0;
713
9
}
714
715
static void
716
parse(struct curparse *curp, int fd, const char *file)
717
{
718
	enum mandoclevel  rctmp;
719
8392
	struct roff_man	 *man;
720
721
	/* Begin by parsing the file itself. */
722
723
4196
	assert(file);
724
4196
	assert(fd >= 0);
725
726
4196
	rctmp = mparse_readfd(curp->mp, fd, file);
727
4196
	if (fd != STDIN_FILENO)
728
4196
		close(fd);
729
4196
	if (rc < rctmp)
730
378
		rc = rctmp;
731
732
	/*
733
	 * With -Wstop and warnings or errors of at least the requested
734
	 * level, do not produce output.
735
	 */
736
737

4574
	if (rctmp != MANDOCLEVEL_OK && curp->wstop)
738
		return;
739
740
4196
	if (curp->outdata == NULL)
741
4196
		outdata_alloc(curp);
742
743
4196
	mparse_result(curp->mp, &man, NULL);
744
745
	/* Execute the out device, if it exists. */
746
747
4196
	if (man == NULL)
748
		return;
749
4196
	mandoc_xr_reset();
750
4196
	if (man->macroset == MACROSET_MDOC) {
751

2818
		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
752
2818
			mdoc_validate(man);
753


5147
		switch (curp->outtype) {
754
		case OUTT_HTML:
755
51
			html_mdoc(curp->outdata, man);
756
51
			break;
757
		case OUTT_TREE:
758
			tree_mdoc(curp->outdata, man);
759
			break;
760
		case OUTT_MAN:
761
633
			man_mdoc(curp->outdata, man);
762
633
			break;
763
		case OUTT_PDF:
764
		case OUTT_ASCII:
765
		case OUTT_UTF8:
766
		case OUTT_LOCALE:
767
		case OUTT_PS:
768
991
			terminal_mdoc(curp->outdata, man);
769
991
			break;
770
		case OUTT_MARKDOWN:
771
654
			markdown_mdoc(curp->outdata, man);
772
654
			break;
773
		default:
774
			break;
775
		}
776
	}
777
4196
	if (man->macroset == MACROSET_MAN) {
778

1378
		if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
779
1378
			man_validate(man);
780


2558
		switch (curp->outtype) {
781
		case OUTT_HTML:
782
21
			html_man(curp->outdata, man);
783
21
			break;
784
		case OUTT_TREE:
785
			tree_man(curp->outdata, man);
786
			break;
787
		case OUTT_MAN:
788
			man_man(curp->outdata, man);
789
			break;
790
		case OUTT_PDF:
791
		case OUTT_ASCII:
792
		case OUTT_UTF8:
793
		case OUTT_LOCALE:
794
		case OUTT_PS:
795
1159
			terminal_man(curp->outdata, man);
796
1159
			break;
797
		default:
798
			break;
799
		}
800
	}
801
4196
	if (curp->mmin < MANDOCERR_STYLE)
802
687
		check_xr(file);
803
4196
	mparse_updaterc(curp->mp, &rc);
804
8392
}
805
806
static void
807
check_xr(const char *file)
808
{
809
	static struct manpaths	 paths;
810
1374
	struct mansearch	 search;
811
	struct mandoc_xr	*xr;
812
687
	char			*cp;
813
687
	size_t			 sz;
814
815
687
	if (paths.sz == 0)
816
687
		manpath_base(&paths);
817
818
2310
	for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
819
468
		if (xr->line == -1)
820
			continue;
821
6
		search.arch = NULL;
822
6
		search.sec = xr->sec;
823
6
		search.outkey = NULL;
824
6
		search.argmode = ARG_NAME;
825
6
		search.firstmatch = 1;
826
6
		if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
827
			continue;
828
3
		if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
829
			continue;
830
3
		if (xr->count == 1)
831
3
			mandoc_asprintf(&cp, "Xr %s %s", xr->name, xr->sec);
832
		else
833
			mandoc_asprintf(&cp, "Xr %s %s (%d times)",
834
			    xr->name, xr->sec, xr->count);
835
3
		mmsg(MANDOCERR_XR_BAD, MANDOCLEVEL_STYLE,
836
3
		    file, xr->line, xr->pos + 1, cp);
837
3
		free(cp);
838
3
	}
839
687
}
840
841
static void
842
outdata_alloc(struct curparse *curp)
843
{
844

10614
	switch (curp->outtype) {
845
	case OUTT_HTML:
846
72
		curp->outdata = html_alloc(curp->outopts);
847
72
		break;
848
	case OUTT_UTF8:
849
66
		curp->outdata = utf8_alloc(curp->outopts);
850
66
		break;
851
	case OUTT_LOCALE:
852
		curp->outdata = locale_alloc(curp->outopts);
853
		break;
854
	case OUTT_ASCII:
855
2084
		curp->outdata = ascii_alloc(curp->outopts);
856
2084
		break;
857
	case OUTT_PDF:
858
		curp->outdata = pdf_alloc(curp->outopts);
859
		break;
860
	case OUTT_PS:
861
		curp->outdata = ps_alloc(curp->outopts);
862
		break;
863
	default:
864
		break;
865
	}
866
6418
}
867
868
static void
869
passthrough(const char *file, int fd, int synopsis_only)
870
{
871
	const char	 synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
872
	const char	 synr[] = "SYNOPSIS";
873
874
	FILE		*stream;
875
	const char	*syscall;
876
	char		*line, *cp;
877
	size_t		 linesz;
878
	ssize_t		 len, written;
879
	int		 print;
880
881
	line = NULL;
882
	linesz = 0;
883
884
	if (fflush(stdout) == EOF) {
885
		syscall = "fflush";
886
		goto fail;
887
	}
888
889
	if ((stream = fdopen(fd, "r")) == NULL) {
890
		close(fd);
891
		syscall = "fdopen";
892
		goto fail;
893
	}
894
895
	print = 0;
896
	while ((len = getline(&line, &linesz, stream)) != -1) {
897
		cp = line;
898
		if (synopsis_only) {
899
			if (print) {
900
				if ( ! isspace((unsigned char)*cp))
901
					goto done;
902
				while (isspace((unsigned char)*cp)) {
903
					cp++;
904
					len--;
905
				}
906
			} else {
907
				if (strcmp(cp, synb) == 0 ||
908
				    strcmp(cp, synr) == 0)
909
					print = 1;
910
				continue;
911
			}
912
		}
913
		for (; len > 0; len -= written) {
914
			if ((written = write(STDOUT_FILENO, cp, len)) != -1)
915
				continue;
916
			fclose(stream);
917
			syscall = "write";
918
			goto fail;
919
		}
920
	}
921
922
	if (ferror(stream)) {
923
		fclose(stream);
924
		syscall = "getline";
925
		goto fail;
926
	}
927
928
done:
929
	free(line);
930
	fclose(stream);
931
	return;
932
933
fail:
934
	free(line);
935
	warn("%s: SYSERR: %s", file, syscall);
936
	if (rc < MANDOCLEVEL_SYSERR)
937
		rc = MANDOCLEVEL_SYSERR;
938
}
939
940
static int
941
koptions(int *options, char *arg)
942
{
943
944
	if ( ! strcmp(arg, "utf-8")) {
945
		*options |=  MPARSE_UTF8;
946
		*options &= ~MPARSE_LATIN1;
947
	} else if ( ! strcmp(arg, "iso-8859-1")) {
948
		*options |=  MPARSE_LATIN1;
949
		*options &= ~MPARSE_UTF8;
950
	} else if ( ! strcmp(arg, "us-ascii")) {
951
		*options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
952
	} else {
953
		warnx("-K %s: Bad argument", arg);
954
		return 0;
955
	}
956
	return 1;
957
}
958
959
static void
960
moptions(int *options, char *arg)
961
{
962
963
8392
	if (arg == NULL)
964
		return;
965
712
	if (strcmp(arg, "doc") == 0)
966
77
		*options |= MPARSE_MDOC;
967
635
	else if (strcmp(arg, "an") == 0)
968
635
		*options |= MPARSE_MAN;
969
4196
}
970
971
static int
972
toptions(struct curparse *curp, char *arg)
973
{
974
975
8392
	if (0 == strcmp(arg, "ascii"))
976
2084
		curp->outtype = OUTT_ASCII;
977
2112
	else if (0 == strcmp(arg, "lint")) {
978
687
		curp->outtype = OUTT_LINT;
979
687
		curp->mmin = MANDOCERR_BASE;
980
687
		mmsg_stream = stdout;
981
2112
	} else if (0 == strcmp(arg, "tree"))
982
		curp->outtype = OUTT_TREE;
983
1425
	else if (0 == strcmp(arg, "man"))
984
633
		curp->outtype = OUTT_MAN;
985
792
	else if (0 == strcmp(arg, "html"))
986
72
		curp->outtype = OUTT_HTML;
987
720
	else if (0 == strcmp(arg, "markdown"))
988
654
		curp->outtype = OUTT_MARKDOWN;
989
66
	else if (0 == strcmp(arg, "utf8"))
990
66
		curp->outtype = OUTT_UTF8;
991
	else if (0 == strcmp(arg, "locale"))
992
		curp->outtype = OUTT_LOCALE;
993
	else if (0 == strcmp(arg, "ps"))
994
		curp->outtype = OUTT_PS;
995
	else if (0 == strcmp(arg, "pdf"))
996
		curp->outtype = OUTT_PDF;
997
	else {
998
		warnx("-T %s: Bad argument", arg);
999
		return 0;
1000
	}
1001
1002
4196
	return 1;
1003
4196
}
1004
1005
static int
1006
woptions(struct curparse *curp, char *arg)
1007
{
1008
687
	char		*v, *o;
1009
687
	const char	*toks[11];
1010
1011
687
	toks[0] = "stop";
1012
687
	toks[1] = "all";
1013
687
	toks[2] = "base";
1014
687
	toks[3] = "style";
1015
687
	toks[4] = "warning";
1016
687
	toks[5] = "error";
1017
687
	toks[6] = "unsupp";
1018
687
	toks[7] = "fatal";
1019
687
	toks[8] = "openbsd";
1020
687
	toks[9] = "netbsd";
1021
687
	toks[10] = NULL;
1022
1023
2748
	while (*arg) {
1024
		o = arg;
1025


687
		switch (getsubopt(&arg, (char * const *)toks, &v)) {
1026
		case 0:
1027
			curp->wstop = 1;
1028
			break;
1029
		case 1:
1030
		case 2:
1031
687
			curp->mmin = MANDOCERR_BASE;
1032
687
			break;
1033
		case 3:
1034
			curp->mmin = MANDOCERR_STYLE;
1035
			break;
1036
		case 4:
1037
			curp->mmin = MANDOCERR_WARNING;
1038
			break;
1039
		case 5:
1040
			curp->mmin = MANDOCERR_ERROR;
1041
			break;
1042
		case 6:
1043
			curp->mmin = MANDOCERR_UNSUPP;
1044
			break;
1045
		case 7:
1046
			curp->mmin = MANDOCERR_MAX;
1047
			break;
1048
		case 8:
1049
			curp->mmin = MANDOCERR_BASE;
1050
			curp->os_e = MANDOC_OS_OPENBSD;
1051
			break;
1052
		case 9:
1053
			curp->mmin = MANDOCERR_BASE;
1054
			curp->os_e = MANDOC_OS_NETBSD;
1055
			break;
1056
		default:
1057
			warnx("-W %s: Bad argument", o);
1058
			return 0;
1059
		}
1060
	}
1061
687
	return 1;
1062
687
}
1063
1064
static void
1065
mmsg(enum mandocerr t, enum mandoclevel lvl,
1066
		const char *file, int line, int col, const char *msg)
1067
{
1068
	const char	*mparse_msg;
1069
1070
8919
	fprintf(mmsg_stream, "%s: %s:", getprogname(),
1071
2973
	    file == NULL ? "<stdin>" : file);
1072
1073
2973
	if (line)
1074
2949
		fprintf(mmsg_stream, "%d:%d:", line, col + 1);
1075
1076
2973
	fprintf(mmsg_stream, " %s", mparse_strlevel(lvl));
1077
1078
2973
	if ((mparse_msg = mparse_strerror(t)) != NULL)
1079
2973
		fprintf(mmsg_stream, ": %s", mparse_msg);
1080
1081
2973
	if (msg)
1082
2448
		fprintf(mmsg_stream, ": %s", msg);
1083
1084
2973
	fputc('\n', mmsg_stream);
1085
2973
}
1086
1087
static pid_t
1088
spawn_pager(struct tag_files *tag_files)
1089
{
1090
	const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1091
#define MAX_PAGER_ARGS 16
1092
	char		*argv[MAX_PAGER_ARGS];
1093
	const char	*pager;
1094
	char		*cp;
1095
	size_t		 cmdlen;
1096
	int		 argc;
1097
	pid_t		 pager_pid;
1098
1099
	pager = getenv("MANPAGER");
1100
	if (pager == NULL || *pager == '\0')
1101
		pager = getenv("PAGER");
1102
	if (pager == NULL || *pager == '\0')
1103
		pager = "more -s";
1104
	cp = mandoc_strdup(pager);
1105
1106
	/*
1107
	 * Parse the pager command into words.
1108
	 * Intentionally do not do anything fancy here.
1109
	 */
1110
1111
	argc = 0;
1112
	while (argc + 4 < MAX_PAGER_ARGS) {
1113
		argv[argc++] = cp;
1114
		cp = strchr(cp, ' ');
1115
		if (cp == NULL)
1116
			break;
1117
		*cp++ = '\0';
1118
		while (*cp == ' ')
1119
			cp++;
1120
		if (*cp == '\0')
1121
			break;
1122
	}
1123
1124
	/* For more(1) and less(1), use the tag file. */
1125
1126
	if ((cmdlen = strlen(argv[0])) >= 4) {
1127
		cp = argv[0] + cmdlen - 4;
1128
		if (strcmp(cp, "less") == 0 || strcmp(cp, "more") == 0) {
1129
			argv[argc++] = mandoc_strdup("-T");
1130
			argv[argc++] = tag_files->tfn;
1131
		}
1132
	}
1133
	argv[argc++] = tag_files->ofn;
1134
	argv[argc] = NULL;
1135
1136
	switch (pager_pid = fork()) {
1137
	case -1:
1138
		err((int)MANDOCLEVEL_SYSERR, "fork");
1139
	case 0:
1140
		break;
1141
	default:
1142
		(void)setpgid(pager_pid, 0);
1143
		(void)tcsetpgrp(tag_files->ofd, pager_pid);
1144
		if (pledge("stdio rpath tmppath tty proc flock cpath wpath", NULL) == -1)
1145
			err((int)MANDOCLEVEL_SYSERR, "pledge");
1146
		tag_files->pager_pid = pager_pid;
1147
		return pager_pid;
1148
	}
1149
1150
	/* The child process becomes the pager. */
1151
1152
	if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1153
		err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1154
	close(tag_files->ofd);
1155
	close(tag_files->tfd);
1156
1157
	/* Do not start the pager before controlling the terminal. */
1158
1159
	while (tcgetpgrp(STDOUT_FILENO) != getpid())
1160
		nanosleep(&timeout, NULL);
1161
1162
	execvp(argv[0], argv);
1163
	err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1164
}