]> git.cameronkatri.com Git - mandoc.git/blob - main.c
document new SEE ALSO .Xr warnings
[mandoc.git] / main.c
1 /* $Id: main.c,v 1.192 2014/09/03 23:21:47 schwarze Exp $ */
2 /*
3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2011, 2012, 2014 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 AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 #include "config.h"
20
21 #include <sys/types.h>
22
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include "mandoc.h"
34 #include "mandoc_aux.h"
35 #include "main.h"
36 #include "mdoc.h"
37 #include "man.h"
38 #include "manpath.h"
39 #include "mansearch.h"
40
41 #if !defined(__GNUC__) || (__GNUC__ < 2)
42 # if !defined(lint)
43 # define __attribute__(x)
44 # endif
45 #endif /* !defined(__GNUC__) || (__GNUC__ < 2) */
46
47 enum outmode {
48 OUTMODE_DEF = 0,
49 OUTMODE_FLN,
50 OUTMODE_LST,
51 OUTMODE_ALL,
52 OUTMODE_INT,
53 OUTMODE_ONE
54 };
55
56 typedef void (*out_mdoc)(void *, const struct mdoc *);
57 typedef void (*out_man)(void *, const struct man *);
58 typedef void (*out_free)(void *);
59
60 enum outt {
61 OUTT_ASCII = 0, /* -Tascii */
62 OUTT_LOCALE, /* -Tlocale */
63 OUTT_UTF8, /* -Tutf8 */
64 OUTT_TREE, /* -Ttree */
65 OUTT_MAN, /* -Tman */
66 OUTT_HTML, /* -Thtml */
67 OUTT_XHTML, /* -Txhtml */
68 OUTT_LINT, /* -Tlint */
69 OUTT_PS, /* -Tps */
70 OUTT_PDF /* -Tpdf */
71 };
72
73 struct curparse {
74 struct mparse *mp;
75 enum mandoclevel wlevel; /* ignore messages below this */
76 int wstop; /* stop after a file with a warning */
77 enum outt outtype; /* which output to use */
78 out_mdoc outmdoc; /* mdoc output ptr */
79 out_man outman; /* man output ptr */
80 out_free outfree; /* free output ptr */
81 void *outdata; /* data for output */
82 char outopts[BUFSIZ]; /* buf of output opts */
83 };
84
85 static int moptions(int *, char *);
86 static void mmsg(enum mandocerr, enum mandoclevel,
87 const char *, int, int, const char *);
88 static void parse(struct curparse *, int,
89 const char *, enum mandoclevel *);
90 static enum mandoclevel passthrough(const char *, int);
91 static void spawn_pager(void);
92 static int toptions(struct curparse *, char *);
93 static void usage(enum argmode) __attribute__((noreturn));
94 static void version(void) __attribute__((noreturn));
95 static int woptions(struct curparse *, char *);
96
97 static const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
98 static const char *progname;
99
100
101 int
102 main(int argc, char *argv[])
103 {
104 struct curparse curp;
105 struct mansearch search;
106 struct manpaths paths;
107 char *conf_file, *defpaths, *auxpaths;
108 char *defos;
109 #if HAVE_SQLITE3
110 struct manpage *res, *resp;
111 size_t isec, i, sz;
112 int prio, best_prio;
113 char sec;
114 #endif
115 enum mandoclevel rc;
116 enum outmode outmode;
117 pid_t child_pid;
118 int fd;
119 int show_usage;
120 int use_pager;
121 int options;
122 int c;
123
124 progname = strrchr(argv[0], '/');
125 if (progname == NULL)
126 progname = argv[0];
127 else
128 ++progname;
129
130 /* Search options. */
131
132 memset(&paths, 0, sizeof(struct manpaths));
133 conf_file = defpaths = auxpaths = NULL;
134
135 memset(&search, 0, sizeof(struct mansearch));
136 search.outkey = "Nd";
137
138 if (strcmp(progname, "man") == 0)
139 search.argmode = ARG_NAME;
140 else if (strncmp(progname, "apropos", 7) == 0)
141 search.argmode = ARG_EXPR;
142 else if (strncmp(progname, "whatis", 6) == 0)
143 search.argmode = ARG_WORD;
144 else
145 search.argmode = ARG_FILE;
146
147 /* Parser and formatter options. */
148
149 memset(&curp, 0, sizeof(struct curparse));
150 curp.outtype = OUTT_ASCII;
151 curp.wlevel = MANDOCLEVEL_FATAL;
152 options = MPARSE_SO;
153 defos = NULL;
154
155 use_pager = 1;
156 show_usage = 0;
157 outmode = OUTMODE_DEF;
158
159 while (-1 != (c = getopt(argc, argv, "aC:cfhI:iklM:m:O:S:s:T:VW:w"))) {
160 switch (c) {
161 case 'a':
162 outmode = OUTMODE_ALL;
163 break;
164 case 'C':
165 conf_file = optarg;
166 break;
167 case 'c':
168 use_pager = 0;
169 break;
170 case 'f':
171 search.argmode = ARG_WORD;
172 break;
173 case 'h':
174 (void)strlcat(curp.outopts, "synopsis,", BUFSIZ);
175 outmode = OUTMODE_ALL;
176 break;
177 case 'I':
178 if (strncmp(optarg, "os=", 3)) {
179 fprintf(stderr,
180 "%s: -I%s: Bad argument\n",
181 progname, optarg);
182 return((int)MANDOCLEVEL_BADARG);
183 }
184 if (defos) {
185 fprintf(stderr,
186 "%s: -I%s: Duplicate argument\n",
187 progname, optarg);
188 return((int)MANDOCLEVEL_BADARG);
189 }
190 defos = mandoc_strdup(optarg + 3);
191 break;
192 case 'i':
193 outmode = OUTMODE_INT;
194 break;
195 case 'k':
196 search.argmode = ARG_EXPR;
197 break;
198 case 'l':
199 search.argmode = ARG_FILE;
200 outmode = OUTMODE_ALL;
201 break;
202 case 'M':
203 defpaths = optarg;
204 break;
205 case 'm':
206 auxpaths = optarg;
207 break;
208 case 'O':
209 search.outkey = optarg;
210 (void)strlcat(curp.outopts, optarg, BUFSIZ);
211 (void)strlcat(curp.outopts, ",", BUFSIZ);
212 break;
213 case 'S':
214 search.arch = optarg;
215 break;
216 case 's':
217 search.sec = optarg;
218 break;
219 case 'T':
220 if ( ! toptions(&curp, optarg))
221 return((int)MANDOCLEVEL_BADARG);
222 break;
223 case 'W':
224 if ( ! woptions(&curp, optarg))
225 return((int)MANDOCLEVEL_BADARG);
226 break;
227 case 'w':
228 outmode = OUTMODE_FLN;
229 break;
230 case 'V':
231 version();
232 /* NOTREACHED */
233 default:
234 show_usage = 1;
235 break;
236 }
237 }
238
239 if (show_usage)
240 usage(search.argmode);
241
242 /* Postprocess options. */
243
244 if (outmode == OUTMODE_DEF) {
245 switch (search.argmode) {
246 case ARG_FILE:
247 outmode = OUTMODE_ALL;
248 use_pager = 0;
249 break;
250 case ARG_NAME:
251 outmode = OUTMODE_ONE;
252 break;
253 default:
254 outmode = OUTMODE_LST;
255 break;
256 }
257 }
258
259 /* Parse arguments. */
260
261 argc -= optind;
262 argv += optind;
263 #if HAVE_SQLITE3
264 resp = NULL;
265 #endif
266
267 /* Quirk for a man(1) section argument without -s. */
268
269 if (search.argmode == ARG_NAME &&
270 argv[0] != NULL &&
271 isdigit((unsigned char)argv[0][0]) &&
272 (argv[0][1] == '\0' || !strcmp(argv[0], "3p"))) {
273 search.sec = argv[0];
274 argv++;
275 argc--;
276 }
277
278 rc = MANDOCLEVEL_OK;
279
280 /* man(1), whatis(1), apropos(1) */
281
282 if (search.argmode != ARG_FILE) {
283 #if HAVE_SQLITE3
284 if (argc == 0)
285 usage(search.argmode);
286
287 /* Access the mandoc database. */
288
289 manpath_parse(&paths, conf_file, defpaths, auxpaths);
290 mansearch_setup(1);
291 if( ! mansearch(&search, &paths, argc, argv, &res, &sz))
292 usage(search.argmode);
293 resp = res;
294
295 if (sz == 0) {
296 if (search.argmode == ARG_NAME)
297 fprintf(stderr, "%s: No entry for %s "
298 "in the manual.\n", progname, argv[0]);
299 rc = MANDOCLEVEL_BADARG;
300 goto out;
301 }
302
303 /*
304 * For standard man(1) and -a output mode,
305 * prepare for copying filename pointers
306 * into the program parameter array.
307 */
308
309 if (outmode == OUTMODE_ONE) {
310 argc = 1;
311 best_prio = 10;
312 } else if (outmode == OUTMODE_ALL)
313 argc = (int)sz;
314
315 /* Iterate all matching manuals. */
316
317 for (i = 0; i < sz; i++) {
318 if (outmode == OUTMODE_FLN)
319 puts(res[i].file);
320 else if (outmode == OUTMODE_LST)
321 printf("%s - %s\n", res[i].names,
322 res[i].output == NULL ? "" :
323 res[i].output);
324 else if (outmode == OUTMODE_ONE) {
325 /* Search for the best section. */
326 isec = strcspn(res[i].file, "123456789");
327 sec = res[i].file[isec];
328 if ('\0' == sec)
329 continue;
330 prio = sec_prios[sec - '1'];
331 if (prio >= best_prio)
332 continue;
333 best_prio = prio;
334 resp = res + i;
335 }
336 }
337
338 /*
339 * For man(1), -a and -i output mode, fall through
340 * to the main mandoc(1) code iterating files
341 * and running the parsers on each of them.
342 */
343
344 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
345 goto out;
346 #else
347 fputs("mandoc: database support not compiled in\n",
348 stderr);
349 return((int)MANDOCLEVEL_BADARG);
350 #endif
351 }
352
353 /* mandoc(1) */
354
355 if ( ! moptions(&options, auxpaths))
356 return((int)MANDOCLEVEL_BADARG);
357
358 if (use_pager && isatty(STDOUT_FILENO))
359 spawn_pager();
360
361 curp.mp = mparse_alloc(options, curp.wlevel, mmsg, defos);
362
363 /*
364 * Conditionally start up the lookaside buffer before parsing.
365 */
366 if (OUTT_MAN == curp.outtype)
367 mparse_keep(curp.mp);
368
369 if (argc == 0)
370 parse(&curp, STDIN_FILENO, "<stdin>", &rc);
371
372 while (argc) {
373 #if HAVE_SQLITE3
374 if (resp != NULL) {
375 rc = mparse_open(curp.mp, &fd, resp->file,
376 &child_pid);
377 if (fd == -1)
378 /* nothing */;
379 else if (resp->form & FORM_SRC) {
380 /* For .so only; ignore failure. */
381 chdir(paths.paths[resp->ipath]);
382 parse(&curp, fd, resp->file, &rc);
383 } else
384 rc = passthrough(resp->file, fd);
385 resp++;
386 } else
387 #endif
388 {
389 rc = mparse_open(curp.mp, &fd, *argv++,
390 &child_pid);
391 if (fd != -1)
392 parse(&curp, fd, argv[-1], &rc);
393 }
394
395 if (child_pid &&
396 mparse_wait(curp.mp, child_pid) != MANDOCLEVEL_OK)
397 rc = MANDOCLEVEL_SYSERR;
398
399 if (MANDOCLEVEL_OK != rc && curp.wstop)
400 break;
401 argc--;
402 }
403
404 if (curp.outfree)
405 (*curp.outfree)(curp.outdata);
406 if (curp.mp)
407 mparse_free(curp.mp);
408
409 #if HAVE_SQLITE3
410 out:
411 if (search.argmode != ARG_FILE) {
412 manpath_free(&paths);
413 mansearch_free(res, sz);
414 mansearch_setup(0);
415 }
416 #endif
417
418 free(defos);
419
420 return((int)rc);
421 }
422
423 static void
424 version(void)
425 {
426
427 printf("mandoc %s\n", VERSION);
428 exit((int)MANDOCLEVEL_OK);
429 }
430
431 static void
432 usage(enum argmode argmode)
433 {
434
435 switch (argmode) {
436 case ARG_FILE:
437 fputs("usage: mandoc [-acfhklV] [-Ios=name] "
438 "[-mformat] [-Ooption] [-Toutput] [-Wlevel]\n"
439 "\t [file ...]\n", stderr);
440 break;
441 case ARG_NAME:
442 fputs("usage: man [-acfhklVw] [-C file] "
443 "[-M path] [-m path] [-S arch] [-s section]\n"
444 "\t [section] name ...\n", stderr);
445 break;
446 case ARG_WORD:
447 fputs("usage: whatis [-acfhklVw] [-C file] "
448 "[-M path] [-m path] [-O outkey] [-S arch]\n"
449 "\t [-s section] name ...\n", stderr);
450 break;
451 case ARG_EXPR:
452 fputs("usage: apropos [-acfhklVw] [-C file] "
453 "[-M path] [-m path] [-O outkey] [-S arch]\n"
454 "\t [-s section] expression ...\n", stderr);
455 break;
456 }
457 exit((int)MANDOCLEVEL_BADARG);
458 }
459
460 static void
461 parse(struct curparse *curp, int fd, const char *file,
462 enum mandoclevel *level)
463 {
464 enum mandoclevel rc;
465 struct mdoc *mdoc;
466 struct man *man;
467
468 /* Begin by parsing the file itself. */
469
470 assert(file);
471 assert(fd >= -1);
472
473 rc = mparse_readfd(curp->mp, fd, file);
474
475 /* Stop immediately if the parse has failed. */
476
477 if (MANDOCLEVEL_FATAL <= rc)
478 goto cleanup;
479
480 /*
481 * With -Wstop and warnings or errors of at least the requested
482 * level, do not produce output.
483 */
484
485 if (MANDOCLEVEL_OK != rc && curp->wstop)
486 goto cleanup;
487
488 /* If unset, allocate output dev now (if applicable). */
489
490 if ( ! (curp->outman && curp->outmdoc)) {
491 switch (curp->outtype) {
492 case OUTT_XHTML:
493 curp->outdata = xhtml_alloc(curp->outopts);
494 curp->outfree = html_free;
495 break;
496 case OUTT_HTML:
497 curp->outdata = html_alloc(curp->outopts);
498 curp->outfree = html_free;
499 break;
500 case OUTT_UTF8:
501 curp->outdata = utf8_alloc(curp->outopts);
502 curp->outfree = ascii_free;
503 break;
504 case OUTT_LOCALE:
505 curp->outdata = locale_alloc(curp->outopts);
506 curp->outfree = ascii_free;
507 break;
508 case OUTT_ASCII:
509 curp->outdata = ascii_alloc(curp->outopts);
510 curp->outfree = ascii_free;
511 break;
512 case OUTT_PDF:
513 curp->outdata = pdf_alloc(curp->outopts);
514 curp->outfree = pspdf_free;
515 break;
516 case OUTT_PS:
517 curp->outdata = ps_alloc(curp->outopts);
518 curp->outfree = pspdf_free;
519 break;
520 default:
521 break;
522 }
523
524 switch (curp->outtype) {
525 case OUTT_HTML:
526 /* FALLTHROUGH */
527 case OUTT_XHTML:
528 curp->outman = html_man;
529 curp->outmdoc = html_mdoc;
530 break;
531 case OUTT_TREE:
532 curp->outman = tree_man;
533 curp->outmdoc = tree_mdoc;
534 break;
535 case OUTT_MAN:
536 curp->outmdoc = man_mdoc;
537 curp->outman = man_man;
538 break;
539 case OUTT_PDF:
540 /* FALLTHROUGH */
541 case OUTT_ASCII:
542 /* FALLTHROUGH */
543 case OUTT_UTF8:
544 /* FALLTHROUGH */
545 case OUTT_LOCALE:
546 /* FALLTHROUGH */
547 case OUTT_PS:
548 curp->outman = terminal_man;
549 curp->outmdoc = terminal_mdoc;
550 break;
551 default:
552 break;
553 }
554 }
555
556 mparse_result(curp->mp, &mdoc, &man, NULL);
557
558 /* Execute the out device, if it exists. */
559
560 if (man && curp->outman)
561 (*curp->outman)(curp->outdata, man);
562 if (mdoc && curp->outmdoc)
563 (*curp->outmdoc)(curp->outdata, mdoc);
564
565 cleanup:
566
567 mparse_reset(curp->mp);
568
569 if (*level < rc)
570 *level = rc;
571 }
572
573 static enum mandoclevel
574 passthrough(const char *file, int fd)
575 {
576 char buf[BUFSIZ];
577 const char *syscall;
578 ssize_t nr, nw, off;
579
580 while ((nr = read(fd, buf, BUFSIZ)) != -1 && nr != 0)
581 for (off = 0; off < nr; off += nw)
582 if ((nw = write(STDOUT_FILENO, buf + off,
583 (size_t)(nr - off))) == -1 || nw == 0) {
584 syscall = "write";
585 goto fail;
586 }
587
588 if (nr == 0) {
589 close(fd);
590 return(MANDOCLEVEL_OK);
591 }
592
593 syscall = "read";
594 fail:
595 fprintf(stderr, "%s: %s: SYSERR: %s: %s",
596 progname, file, syscall, strerror(errno));
597 return(MANDOCLEVEL_SYSERR);
598 }
599
600 static int
601 moptions(int *options, char *arg)
602 {
603
604 if (arg == NULL)
605 /* nothing to do */;
606 else if (0 == strcmp(arg, "doc"))
607 *options |= MPARSE_MDOC;
608 else if (0 == strcmp(arg, "andoc"))
609 /* nothing to do */;
610 else if (0 == strcmp(arg, "an"))
611 *options |= MPARSE_MAN;
612 else {
613 fprintf(stderr, "%s: -m%s: Bad argument\n",
614 progname, arg);
615 return(0);
616 }
617
618 return(1);
619 }
620
621 static int
622 toptions(struct curparse *curp, char *arg)
623 {
624
625 if (0 == strcmp(arg, "ascii"))
626 curp->outtype = OUTT_ASCII;
627 else if (0 == strcmp(arg, "lint")) {
628 curp->outtype = OUTT_LINT;
629 curp->wlevel = MANDOCLEVEL_WARNING;
630 } else if (0 == strcmp(arg, "tree"))
631 curp->outtype = OUTT_TREE;
632 else if (0 == strcmp(arg, "man"))
633 curp->outtype = OUTT_MAN;
634 else if (0 == strcmp(arg, "html"))
635 curp->outtype = OUTT_HTML;
636 else if (0 == strcmp(arg, "utf8"))
637 curp->outtype = OUTT_UTF8;
638 else if (0 == strcmp(arg, "locale"))
639 curp->outtype = OUTT_LOCALE;
640 else if (0 == strcmp(arg, "xhtml"))
641 curp->outtype = OUTT_XHTML;
642 else if (0 == strcmp(arg, "ps"))
643 curp->outtype = OUTT_PS;
644 else if (0 == strcmp(arg, "pdf"))
645 curp->outtype = OUTT_PDF;
646 else {
647 fprintf(stderr, "%s: -T%s: Bad argument\n",
648 progname, arg);
649 return(0);
650 }
651
652 return(1);
653 }
654
655 static int
656 woptions(struct curparse *curp, char *arg)
657 {
658 char *v, *o;
659 const char *toks[6];
660
661 toks[0] = "stop";
662 toks[1] = "all";
663 toks[2] = "warning";
664 toks[3] = "error";
665 toks[4] = "fatal";
666 toks[5] = NULL;
667
668 while (*arg) {
669 o = arg;
670 switch (getsubopt(&arg, UNCONST(toks), &v)) {
671 case 0:
672 curp->wstop = 1;
673 break;
674 case 1:
675 /* FALLTHROUGH */
676 case 2:
677 curp->wlevel = MANDOCLEVEL_WARNING;
678 break;
679 case 3:
680 curp->wlevel = MANDOCLEVEL_ERROR;
681 break;
682 case 4:
683 curp->wlevel = MANDOCLEVEL_FATAL;
684 break;
685 default:
686 fprintf(stderr, "%s: -W%s: Bad argument\n",
687 progname, o);
688 return(0);
689 }
690 }
691
692 return(1);
693 }
694
695 static void
696 mmsg(enum mandocerr t, enum mandoclevel lvl,
697 const char *file, int line, int col, const char *msg)
698 {
699 const char *mparse_msg;
700
701 fprintf(stderr, "%s: %s:", progname, file);
702
703 if (line)
704 fprintf(stderr, "%d:%d:", line, col + 1);
705
706 fprintf(stderr, " %s", mparse_strlevel(lvl));
707
708 if (NULL != (mparse_msg = mparse_strerror(t)))
709 fprintf(stderr, ": %s", mparse_msg);
710
711 if (msg)
712 fprintf(stderr, ": %s", msg);
713
714 fputc('\n', stderr);
715 }
716
717 static void
718 spawn_pager(void)
719 {
720 #define MAX_PAGER_ARGS 16
721 char *argv[MAX_PAGER_ARGS];
722 const char *pager;
723 char *cp;
724 int fildes[2];
725 int argc;
726
727 if (pipe(fildes) == -1) {
728 fprintf(stderr, "%s: pipe: %s\n",
729 progname, strerror(errno));
730 return;
731 }
732
733 switch (fork()) {
734 case -1:
735 fprintf(stderr, "%s: fork: %s\n",
736 progname, strerror(errno));
737 exit((int)MANDOCLEVEL_SYSERR);
738 case 0:
739 close(fildes[0]);
740 if (dup2(fildes[1], STDOUT_FILENO) == -1) {
741 fprintf(stderr, "%s: dup output: %s\n",
742 progname, strerror(errno));
743 exit((int)MANDOCLEVEL_SYSERR);
744 }
745 return;
746 default:
747 break;
748 }
749
750 /* The original process becomes the pager. */
751
752 close(fildes[1]);
753 if (dup2(fildes[0], STDIN_FILENO) == -1) {
754 fprintf(stderr, "%s: dup input: %s\n",
755 progname, strerror(errno));
756 exit((int)MANDOCLEVEL_SYSERR);
757 }
758
759 pager = getenv("MANPAGER");
760 if (pager == NULL || *pager == '\0')
761 pager = getenv("PAGER");
762 if (pager == NULL || *pager == '\0')
763 pager = "/usr/bin/more -s";
764 cp = mandoc_strdup(pager);
765
766 /*
767 * Parse the pager command into words.
768 * Intentionally do not do anything fancy here.
769 */
770
771 argc = 0;
772 while (argc + 1 < MAX_PAGER_ARGS) {
773 argv[argc++] = cp;
774 cp = strchr(cp, ' ');
775 if (cp == NULL)
776 break;
777 *cp++ = '\0';
778 while (*cp == ' ')
779 cp++;
780 if (*cp == '\0')
781 break;
782 }
783 argv[argc] = NULL;
784
785 /* Hand over to the pager. */
786
787 execvp(argv[0], argv);
788 fprintf(stderr, "%s: exec: %s\n",
789 progname, strerror(errno));
790 exit((int)MANDOCLEVEL_SYSERR);
791 }