]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mandoc/main.c
Merge llvm, clang, compiler-rt, libc++, lld, and lldb release_80 branch
[FreeBSD/FreeBSD.git] / contrib / mandoc / main.c
1 /*      $Id: main.c,v 1.306 2018/05/14 14:10:23 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2010-2012, 2014-2018 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 #include "config.h"
20
21 #include <sys/types.h>
22 #include <sys/ioctl.h>
23 #include <sys/param.h>  /* MACHINE */
24 #include <sys/wait.h>
25
26 #include <assert.h>
27 #include <ctype.h>
28 #if HAVE_ERR
29 #include <err.h>
30 #endif
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <glob.h>
34 #if HAVE_SANDBOX_INIT
35 #include <sandbox.h>
36 #endif
37 #include <signal.h>
38 #include <stdio.h>
39 #include <stdint.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <termios.h>
43 #include <time.h>
44 #include <unistd.h>
45
46 #include "mandoc_aux.h"
47 #include "mandoc.h"
48 #include "mandoc_xr.h"
49 #include "roff.h"
50 #include "mdoc.h"
51 #include "man.h"
52 #include "tag.h"
53 #include "main.h"
54 #include "manconf.h"
55 #include "mansearch.h"
56
57 enum    outmode {
58         OUTMODE_DEF = 0,
59         OUTMODE_FLN,
60         OUTMODE_LST,
61         OUTMODE_ALL,
62         OUTMODE_ONE
63 };
64
65 enum    outt {
66         OUTT_ASCII = 0, /* -Tascii */
67         OUTT_LOCALE,    /* -Tlocale */
68         OUTT_UTF8,      /* -Tutf8 */
69         OUTT_TREE,      /* -Ttree */
70         OUTT_MAN,       /* -Tman */
71         OUTT_HTML,      /* -Thtml */
72         OUTT_MARKDOWN,  /* -Tmarkdown */
73         OUTT_LINT,      /* -Tlint */
74         OUTT_PS,        /* -Tps */
75         OUTT_PDF        /* -Tpdf */
76 };
77
78 struct  curparse {
79         struct mparse    *mp;
80         struct manoutput *outopts;      /* output options */
81         void             *outdata;      /* data for output */
82         char             *os_s;         /* operating system for display */
83         int               wstop;        /* stop after a file with a warning */
84         enum mandocerr    mmin;         /* ignore messages below this */
85         enum mandoc_os    os_e;         /* check base system conventions */
86         enum outt         outtype;      /* which output to use */
87 };
88
89
90 int                       mandocdb(int, char *[]);
91
92 static  void              check_xr(const char *);
93 static  int               fs_lookup(const struct manpaths *,
94                                 size_t ipath, const char *,
95                                 const char *, const char *,
96                                 struct manpage **, size_t *);
97 static  int               fs_search(const struct mansearch *,
98                                 const struct manpaths *, int, char**,
99                                 struct manpage **, size_t *);
100 static  int               koptions(int *, char *);
101 static  void              moptions(int *, char *);
102 static  void              mmsg(enum mandocerr, enum mandoclevel,
103                                 const char *, int, int, const char *);
104 static  void              outdata_alloc(struct curparse *);
105 static  void              parse(struct curparse *, int, const char *);
106 static  void              passthrough(const char *, int, int);
107 static  pid_t             spawn_pager(struct tag_files *);
108 static  int               toptions(struct curparse *, char *);
109 static  void              usage(enum argmode) __attribute__((__noreturn__));
110 static  int               woptions(struct curparse *, char *);
111
112 static  const int sec_prios[] = {1, 4, 5, 8, 6, 3, 7, 2, 9};
113 static  char              help_arg[] = "help";
114 static  char             *help_argv[] = {help_arg, NULL};
115 static  enum mandoclevel  rc;
116 static  FILE             *mmsg_stream;
117
118
119 int
120 main(int argc, char *argv[])
121 {
122         struct manconf   conf;
123         struct mansearch search;
124         struct curparse  curp;
125         struct winsize   ws;
126         struct tag_files *tag_files;
127         struct manpage  *res, *resp;
128         const char      *progname, *sec, *thisarg;
129         char            *conf_file, *defpaths, *auxpaths;
130         char            *oarg;
131         unsigned char   *uc;
132         size_t           i, sz;
133         int              prio, best_prio;
134         enum outmode     outmode;
135         int              fd, startdir;
136         int              show_usage;
137         int              options;
138         int              use_pager;
139         int              status, signum;
140         int              c;
141         pid_t            pager_pid, tc_pgid, man_pgid, pid;
142
143 #if HAVE_PROGNAME
144         progname = getprogname();
145 #else
146         if (argc < 1)
147                 progname = mandoc_strdup("mandoc");
148         else if ((progname = strrchr(argv[0], '/')) == NULL)
149                 progname = argv[0];
150         else
151                 ++progname;
152         setprogname(progname);
153 #endif
154
155         if (strncmp(progname, "mandocdb", 8) == 0 ||
156             strcmp(progname, BINM_MAKEWHATIS) == 0)
157                 return mandocdb(argc, argv);
158
159 #if HAVE_PLEDGE
160         if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
161                 err((int)MANDOCLEVEL_SYSERR, "pledge");
162 #endif
163
164 #if HAVE_SANDBOX_INIT
165         if (sandbox_init(kSBXProfileNoInternet, SANDBOX_NAMED, NULL) == -1)
166                 errx((int)MANDOCLEVEL_SYSERR, "sandbox_init");
167 #endif
168
169         /* Search options. */
170
171         memset(&conf, 0, sizeof(conf));
172         conf_file = defpaths = NULL;
173         auxpaths = NULL;
174
175         memset(&search, 0, sizeof(struct mansearch));
176         search.outkey = "Nd";
177         oarg = NULL;
178
179         if (strcmp(progname, BINM_MAN) == 0)
180                 search.argmode = ARG_NAME;
181         else if (strcmp(progname, BINM_APROPOS) == 0)
182                 search.argmode = ARG_EXPR;
183         else if (strcmp(progname, BINM_WHATIS) == 0)
184                 search.argmode = ARG_WORD;
185         else if (strncmp(progname, "help", 4) == 0)
186                 search.argmode = ARG_NAME;
187         else
188                 search.argmode = ARG_FILE;
189
190         /* Parser and formatter options. */
191
192         memset(&curp, 0, sizeof(struct curparse));
193         curp.outtype = OUTT_LOCALE;
194         curp.mmin = MANDOCERR_MAX;
195         curp.outopts = &conf.output;
196         options = MPARSE_SO | MPARSE_UTF8 | MPARSE_LATIN1;
197         mmsg_stream = stderr;
198
199         use_pager = 1;
200         tag_files = NULL;
201         show_usage = 0;
202         outmode = OUTMODE_DEF;
203
204         while ((c = getopt(argc, argv,
205             "aC:cfhI:iK:klM:m:O:S:s:T:VW:w")) != -1) {
206                 if (c == 'i' && search.argmode == ARG_EXPR) {
207                         optind--;
208                         break;
209                 }
210                 switch (c) {
211                 case 'a':
212                         outmode = OUTMODE_ALL;
213                         break;
214                 case 'C':
215                         conf_file = optarg;
216                         break;
217                 case 'c':
218                         use_pager = 0;
219                         break;
220                 case 'f':
221                         search.argmode = ARG_WORD;
222                         break;
223                 case 'h':
224                         conf.output.synopsisonly = 1;
225                         use_pager = 0;
226                         outmode = OUTMODE_ALL;
227                         break;
228                 case 'I':
229                         if (strncmp(optarg, "os=", 3)) {
230                                 warnx("-I %s: Bad argument", optarg);
231                                 return (int)MANDOCLEVEL_BADARG;
232                         }
233                         if (curp.os_s != NULL) {
234                                 warnx("-I %s: Duplicate argument", optarg);
235                                 return (int)MANDOCLEVEL_BADARG;
236                         }
237                         curp.os_s = mandoc_strdup(optarg + 3);
238                         break;
239                 case 'K':
240                         if ( ! koptions(&options, optarg))
241                                 return (int)MANDOCLEVEL_BADARG;
242                         break;
243                 case 'k':
244                         search.argmode = ARG_EXPR;
245                         break;
246                 case 'l':
247                         search.argmode = ARG_FILE;
248                         outmode = OUTMODE_ALL;
249                         break;
250                 case 'M':
251 #ifdef __FreeBSD__
252                         defpaths = strdup(optarg);
253                         if (defpaths == NULL)
254                                 err(1, "strdup");
255 #else
256                         defpaths = optarg;
257 #endif
258                         break;
259                 case 'm':
260                         auxpaths = optarg;
261                         break;
262                 case 'O':
263                         oarg = optarg;
264                         break;
265                 case 'S':
266                         search.arch = optarg;
267                         break;
268                 case 's':
269                         search.sec = optarg;
270                         break;
271                 case 'T':
272                         if ( ! toptions(&curp, optarg))
273                                 return (int)MANDOCLEVEL_BADARG;
274                         break;
275                 case 'W':
276                         if ( ! woptions(&curp, optarg))
277                                 return (int)MANDOCLEVEL_BADARG;
278                         break;
279                 case 'w':
280                         outmode = OUTMODE_FLN;
281                         break;
282                 default:
283                         show_usage = 1;
284                         break;
285                 }
286         }
287
288         if (show_usage)
289                 usage(search.argmode);
290
291         /* Postprocess options. */
292
293         if (outmode == OUTMODE_DEF) {
294                 switch (search.argmode) {
295                 case ARG_FILE:
296                         outmode = OUTMODE_ALL;
297                         use_pager = 0;
298                         break;
299                 case ARG_NAME:
300                         outmode = OUTMODE_ONE;
301                         break;
302                 default:
303                         outmode = OUTMODE_LST;
304                         break;
305                 }
306         }
307
308         if (oarg != NULL) {
309                 if (outmode == OUTMODE_LST)
310                         search.outkey = oarg;
311                 else {
312                         while (oarg != NULL) {
313                                 thisarg = oarg;
314                                 if (manconf_output(&conf.output,
315                                     strsep(&oarg, ","), 0) == 0)
316                                         continue;
317                                 warnx("-O %s: Bad argument", thisarg);
318                                 return (int)MANDOCLEVEL_BADARG;
319                         }
320                 }
321         }
322
323         if (outmode == OUTMODE_FLN ||
324             outmode == OUTMODE_LST ||
325             !isatty(STDOUT_FILENO))
326                 use_pager = 0;
327
328         if (use_pager &&
329             (conf.output.width == 0 || conf.output.indent == 0) &&
330             ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1 &&
331             ws.ws_col > 1) {
332                 if (conf.output.width == 0 && ws.ws_col < 79)
333                         conf.output.width = ws.ws_col - 1;
334                 if (conf.output.indent == 0 && ws.ws_col < 66)
335                         conf.output.indent = 3;
336         }
337
338 #if HAVE_PLEDGE
339         if (!use_pager)
340                 if (pledge("stdio rpath", NULL) == -1)
341                         err((int)MANDOCLEVEL_SYSERR, "pledge");
342 #endif
343
344         /* Parse arguments. */
345
346         if (argc > 0) {
347                 argc -= optind;
348                 argv += optind;
349         }
350         resp = NULL;
351
352         /*
353          * Quirks for help(1)
354          * and for a man(1) section argument without -s.
355          */
356
357         if (search.argmode == ARG_NAME) {
358                 if (*progname == 'h') {
359                         if (argc == 0) {
360                                 argv = help_argv;
361                                 argc = 1;
362                         }
363                 } else if (argc > 1 &&
364                     ((uc = (unsigned char *)argv[0]) != NULL) &&
365                     ((isdigit(uc[0]) && (uc[1] == '\0' ||
366                       (isalpha(uc[1]) && uc[2] == '\0'))) ||
367                      (uc[0] == 'n' && uc[1] == '\0'))) {
368                         search.sec = (char *)uc;
369                         argv++;
370                         argc--;
371                 }
372                 if (search.arch == NULL)
373                         search.arch = getenv("MACHINE");
374 #ifdef MACHINE
375                 if (search.arch == NULL)
376                         search.arch = MACHINE;
377 #endif
378         }
379
380         rc = MANDOCLEVEL_OK;
381
382         /* man(1), whatis(1), apropos(1) */
383
384         if (search.argmode != ARG_FILE) {
385                 if (search.argmode == ARG_NAME &&
386                     outmode == OUTMODE_ONE)
387                         search.firstmatch = 1;
388
389 #ifdef __FreeBSD__
390                 /*
391                  * Use manpath(1) to populate defpaths if -M is not specified.
392                  * Don't treat any failures as fatal.
393                  */
394                 if (defpaths == NULL) {
395                         FILE *fp;
396                         size_t linecap = 0;
397                         ssize_t linelen;
398
399                         if ((fp = popen("/usr/bin/manpath -q", "r")) != NULL) {
400                                 if ((linelen = getline(&defpaths,
401                                     &linecap, fp)) > 0) {
402                                         /* Strip trailing newline */
403                                         defpaths[linelen - 1] = '\0';
404                                 }
405                                 pclose(fp);
406                         }
407                 }
408 #endif
409
410                 /* Access the mandoc database. */
411
412                 manconf_parse(&conf, conf_file, defpaths, auxpaths);
413 #ifdef __FreeBSD__
414                 free(defpaths);
415 #endif
416
417                 if ( ! mansearch(&search, &conf.manpath,
418                     argc, argv, &res, &sz))
419                         usage(search.argmode);
420
421                 if (sz == 0 && search.argmode == ARG_NAME)
422                         fs_search(&search, &conf.manpath,
423                             argc, argv, &res, &sz);
424
425                 if (search.argmode == ARG_NAME) {
426                         for (c = 0; c < argc; c++) {
427                                 if (strchr(argv[c], '/') == NULL)
428                                         continue;
429                                 if (access(argv[c], R_OK) == -1) {
430                                         warn("%s", argv[c]);
431                                         continue;
432                                 }
433                                 res = mandoc_reallocarray(res,
434                                     sz + 1, sizeof(*res));
435                                 res[sz].file = mandoc_strdup(argv[c]);
436                                 res[sz].names = NULL;
437                                 res[sz].output = NULL;
438                                 res[sz].ipath = SIZE_MAX;
439                                 res[sz].bits = 0;
440                                 res[sz].sec = 10;
441                                 res[sz].form = FORM_SRC;
442                                 sz++;
443                         }
444                 }
445
446                 if (sz == 0) {
447                         if (search.argmode != ARG_NAME)
448                                 warnx("nothing appropriate");
449                         rc = MANDOCLEVEL_BADARG;
450                         goto out;
451                 }
452
453                 /*
454                  * For standard man(1) and -a output mode,
455                  * prepare for copying filename pointers
456                  * into the program parameter array.
457                  */
458
459                 if (outmode == OUTMODE_ONE) {
460                         argc = 1;
461                         best_prio = 20;
462                 } else if (outmode == OUTMODE_ALL)
463                         argc = (int)sz;
464
465                 /* Iterate all matching manuals. */
466
467                 resp = res;
468                 for (i = 0; i < sz; i++) {
469                         if (outmode == OUTMODE_FLN)
470                                 puts(res[i].file);
471                         else if (outmode == OUTMODE_LST)
472                                 printf("%s - %s\n", res[i].names,
473                                     res[i].output == NULL ? "" :
474                                     res[i].output);
475                         else if (outmode == OUTMODE_ONE) {
476                                 /* Search for the best section. */
477                                 sec = res[i].file;
478                                 sec += strcspn(sec, "123456789");
479                                 if (sec[0] == '\0')
480                                         continue;
481                                 prio = sec_prios[sec[0] - '1'];
482                                 if (sec[1] != '/')
483                                         prio += 10;
484                                 if (prio >= best_prio)
485                                         continue;
486                                 best_prio = prio;
487                                 resp = res + i;
488                         }
489                 }
490
491                 /*
492                  * For man(1), -a and -i output mode, fall through
493                  * to the main mandoc(1) code iterating files
494                  * and running the parsers on each of them.
495                  */
496
497                 if (outmode == OUTMODE_FLN || outmode == OUTMODE_LST)
498                         goto out;
499         }
500
501         /* mandoc(1) */
502
503 #if HAVE_PLEDGE
504         if (use_pager) {
505                 if (pledge("stdio rpath tmppath tty proc exec", NULL) == -1)
506                         err((int)MANDOCLEVEL_SYSERR, "pledge");
507         } else {
508                 if (pledge("stdio rpath", NULL) == -1)
509                         err((int)MANDOCLEVEL_SYSERR, "pledge");
510         }
511 #endif
512
513         if (search.argmode == ARG_FILE)
514                 moptions(&options, auxpaths);
515
516         mchars_alloc();
517         curp.mp = mparse_alloc(options, curp.mmin, mmsg,
518             curp.os_e, curp.os_s);
519
520         /*
521          * Conditionally start up the lookaside buffer before parsing.
522          */
523         if (OUTT_MAN == curp.outtype)
524                 mparse_keep(curp.mp);
525
526         if (argc < 1) {
527                 if (use_pager)
528                         tag_files = tag_init();
529                 parse(&curp, STDIN_FILENO, "<stdin>");
530         }
531
532         /*
533          * Remember the original working directory, if possible.
534          * This will be needed if some names on the command line
535          * are page names and some are relative file names.
536          * Do not error out if the current directory is not
537          * readable: Maybe it won't be needed after all.
538          */
539         startdir = open(".", O_RDONLY | O_DIRECTORY);
540
541         while (argc > 0) {
542
543                 /*
544                  * Changing directories is not needed in ARG_FILE mode.
545                  * Do it on a best-effort basis.  Even in case of
546                  * failure, some functionality may still work.
547                  */
548                 if (resp != NULL) {
549                         if (resp->ipath != SIZE_MAX)
550                                 (void)chdir(conf.manpath.paths[resp->ipath]);
551                         else if (startdir != -1)
552                                 (void)fchdir(startdir);
553                 }
554
555                 fd = mparse_open(curp.mp, resp != NULL ? resp->file : *argv);
556                 if (fd != -1) {
557                         if (use_pager) {
558                                 tag_files = tag_init();
559                                 use_pager = 0;
560                         }
561
562                         if (resp == NULL)
563                                 parse(&curp, fd, *argv);
564                         else if (resp->form == FORM_SRC)
565                                 parse(&curp, fd, resp->file);
566                         else
567                                 passthrough(resp->file, fd,
568                                     conf.output.synopsisonly);
569
570                         if (ferror(stdout)) {
571                                 if (tag_files != NULL) {
572                                         warn("%s", tag_files->ofn);
573                                         tag_unlink();
574                                         tag_files = NULL;
575                                 } else
576                                         warn("stdout");
577                                 rc = MANDOCLEVEL_SYSERR;
578                                 break;
579                         }
580
581                         if (argc > 1 && curp.outtype <= OUTT_UTF8) {
582                                 if (curp.outdata == NULL)
583                                         outdata_alloc(&curp);
584                                 terminal_sepline(curp.outdata);
585                         }
586                 } else if (rc < MANDOCLEVEL_ERROR)
587                         rc = MANDOCLEVEL_ERROR;
588
589                 if (MANDOCLEVEL_OK != rc && curp.wstop)
590                         break;
591
592                 if (resp != NULL)
593                         resp++;
594                 else
595                         argv++;
596                 if (--argc)
597                         mparse_reset(curp.mp);
598         }
599         if (startdir != -1) {
600                 (void)fchdir(startdir);
601                 close(startdir);
602         }
603
604         if (curp.outdata != NULL) {
605                 switch (curp.outtype) {
606                 case OUTT_HTML:
607                         html_free(curp.outdata);
608                         break;
609                 case OUTT_UTF8:
610                 case OUTT_LOCALE:
611                 case OUTT_ASCII:
612                         ascii_free(curp.outdata);
613                         break;
614                 case OUTT_PDF:
615                 case OUTT_PS:
616                         pspdf_free(curp.outdata);
617                         break;
618                 default:
619                         break;
620                 }
621         }
622         mandoc_xr_free();
623         mparse_free(curp.mp);
624         mchars_free();
625
626 out:
627         if (search.argmode != ARG_FILE) {
628                 manconf_free(&conf);
629                 mansearch_free(res, sz);
630         }
631
632         free(curp.os_s);
633
634         /*
635          * When using a pager, finish writing both temporary files,
636          * fork it, wait for the user to close it, and clean up.
637          */
638
639         if (tag_files != NULL) {
640                 fclose(stdout);
641                 tag_write();
642                 man_pgid = getpgid(0);
643                 tag_files->tcpgid = man_pgid == getpid() ?
644                     getpgid(getppid()) : man_pgid;
645                 pager_pid = 0;
646                 signum = SIGSTOP;
647                 for (;;) {
648
649                         /* Stop here until moved to the foreground. */
650
651                         tc_pgid = tcgetpgrp(tag_files->ofd);
652                         if (tc_pgid != man_pgid) {
653                                 if (tc_pgid == pager_pid) {
654                                         (void)tcsetpgrp(tag_files->ofd,
655                                             man_pgid);
656                                         if (signum == SIGTTIN)
657                                                 continue;
658                                 } else
659                                         tag_files->tcpgid = tc_pgid;
660                                 kill(0, signum);
661                                 continue;
662                         }
663
664                         /* Once in the foreground, activate the pager. */
665
666                         if (pager_pid) {
667                                 (void)tcsetpgrp(tag_files->ofd, pager_pid);
668                                 kill(pager_pid, SIGCONT);
669                         } else
670                                 pager_pid = spawn_pager(tag_files);
671
672                         /* Wait for the pager to stop or exit. */
673
674                         while ((pid = waitpid(pager_pid, &status,
675                             WUNTRACED)) == -1 && errno == EINTR)
676                                 continue;
677
678                         if (pid == -1) {
679                                 warn("wait");
680                                 rc = MANDOCLEVEL_SYSERR;
681                                 break;
682                         }
683                         if (!WIFSTOPPED(status))
684                                 break;
685
686                         signum = WSTOPSIG(status);
687                 }
688                 tag_unlink();
689         }
690
691         return (int)rc;
692 }
693
694 static void
695 usage(enum argmode argmode)
696 {
697
698         switch (argmode) {
699         case ARG_FILE:
700                 fputs("usage: mandoc [-ac] [-I os=name] "
701                     "[-K encoding] [-mdoc | -man] [-O options]\n"
702                     "\t      [-T output] [-W level] [file ...]\n", stderr);
703                 break;
704         case ARG_NAME:
705                 fputs("usage: man [-acfhklw] [-C file] [-M path] "
706                     "[-m path] [-S subsection]\n"
707                     "\t   [[-s] section] name ...\n", stderr);
708                 break;
709         case ARG_WORD:
710                 fputs("usage: whatis [-afk] [-C file] "
711                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
712                     "\t      [-s section] name ...\n", stderr);
713                 break;
714         case ARG_EXPR:
715                 fputs("usage: apropos [-afk] [-C file] "
716                     "[-M path] [-m path] [-O outkey] [-S arch]\n"
717                     "\t       [-s section] expression ...\n", stderr);
718                 break;
719         }
720         exit((int)MANDOCLEVEL_BADARG);
721 }
722
723 static int
724 fs_lookup(const struct manpaths *paths, size_t ipath,
725         const char *sec, const char *arch, const char *name,
726         struct manpage **res, size_t *ressz)
727 {
728         glob_t           globinfo;
729         struct manpage  *page;
730         char            *file;
731         int              globres;
732         enum form        form;
733
734         form = FORM_SRC;
735         mandoc_asprintf(&file, "%s/man%s/%s.%s",
736             paths->paths[ipath], sec, name, sec);
737         if (access(file, R_OK) != -1)
738                 goto found;
739         free(file);
740
741         mandoc_asprintf(&file, "%s/cat%s/%s.0",
742             paths->paths[ipath], sec, name);
743         if (access(file, R_OK) != -1) {
744                 form = FORM_CAT;
745                 goto found;
746         }
747         free(file);
748
749         if (arch != NULL) {
750                 mandoc_asprintf(&file, "%s/man%s/%s/%s.%s",
751                     paths->paths[ipath], sec, arch, name, sec);
752                 if (access(file, R_OK) != -1)
753                         goto found;
754                 free(file);
755         }
756
757         mandoc_asprintf(&file, "%s/man%s/%s.[01-9]*",
758             paths->paths[ipath], sec, name);
759         globres = glob(file, 0, NULL, &globinfo);
760         if (globres != 0 && globres != GLOB_NOMATCH)
761                 warn("%s: glob", file);
762         free(file);
763         if (globres == 0)
764                 file = mandoc_strdup(*globinfo.gl_pathv);
765         globfree(&globinfo);
766         if (globres == 0)
767                 goto found;
768         if (res != NULL || ipath + 1 != paths->sz)
769                 return 0;
770
771         mandoc_asprintf(&file, "%s.%s", name, sec);
772         globres = access(file, R_OK);
773         free(file);
774         return globres != -1;
775
776 found:
777         warnx("outdated mandoc.db lacks %s(%s) entry, run %s %s",
778             name, sec, BINM_MAKEWHATIS, paths->paths[ipath]);
779         if (res == NULL) {
780                 free(file);
781                 return 1;
782         }
783         *res = mandoc_reallocarray(*res, ++*ressz, sizeof(struct manpage));
784         page = *res + (*ressz - 1);
785         page->file = file;
786         page->names = NULL;
787         page->output = NULL;
788         page->ipath = ipath;
789         page->bits = NAME_FILE & NAME_MASK;
790         page->sec = (*sec >= '1' && *sec <= '9') ? *sec - '1' + 1 : 10;
791         page->form = form;
792         return 1;
793 }
794
795 static int
796 fs_search(const struct mansearch *cfg, const struct manpaths *paths,
797         int argc, char **argv, struct manpage **res, size_t *ressz)
798 {
799         const char *const sections[] =
800             {"1", "8", "6", "2", "3", "5", "7", "4", "9", "3p"};
801         const size_t nsec = sizeof(sections)/sizeof(sections[0]);
802
803         size_t           ipath, isec, lastsz;
804
805         assert(cfg->argmode == ARG_NAME);
806
807         if (res != NULL)
808                 *res = NULL;
809         *ressz = lastsz = 0;
810         while (argc) {
811                 for (ipath = 0; ipath < paths->sz; ipath++) {
812                         if (cfg->sec != NULL) {
813                                 if (fs_lookup(paths, ipath, cfg->sec,
814                                     cfg->arch, *argv, res, ressz) &&
815                                     cfg->firstmatch)
816                                         return 1;
817                         } else for (isec = 0; isec < nsec; isec++)
818                                 if (fs_lookup(paths, ipath, sections[isec],
819                                     cfg->arch, *argv, res, ressz) &&
820                                     cfg->firstmatch)
821                                         return 1;
822                 }
823                 if (res != NULL && *ressz == lastsz &&
824                     strchr(*argv, '/') == NULL)
825                         warnx("No entry for %s in the manual.", *argv);
826                 lastsz = *ressz;
827                 argv++;
828                 argc--;
829         }
830         return 0;
831 }
832
833 static void
834 parse(struct curparse *curp, int fd, const char *file)
835 {
836         enum mandoclevel  rctmp;
837         struct roff_man  *man;
838
839         /* Begin by parsing the file itself. */
840
841         assert(file);
842         assert(fd >= 0);
843
844         rctmp = mparse_readfd(curp->mp, fd, file);
845         if (fd != STDIN_FILENO)
846                 close(fd);
847         if (rc < rctmp)
848                 rc = rctmp;
849
850         /*
851          * With -Wstop and warnings or errors of at least the requested
852          * level, do not produce output.
853          */
854
855         if (rctmp != MANDOCLEVEL_OK && curp->wstop)
856                 return;
857
858         if (curp->outdata == NULL)
859                 outdata_alloc(curp);
860
861         mparse_result(curp->mp, &man, NULL);
862
863         /* Execute the out device, if it exists. */
864
865         if (man == NULL)
866                 return;
867         mandoc_xr_reset();
868         if (man->macroset == MACROSET_MDOC) {
869                 if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
870                         mdoc_validate(man);
871                 switch (curp->outtype) {
872                 case OUTT_HTML:
873                         html_mdoc(curp->outdata, man);
874                         break;
875                 case OUTT_TREE:
876                         tree_mdoc(curp->outdata, man);
877                         break;
878                 case OUTT_MAN:
879                         man_mdoc(curp->outdata, man);
880                         break;
881                 case OUTT_PDF:
882                 case OUTT_ASCII:
883                 case OUTT_UTF8:
884                 case OUTT_LOCALE:
885                 case OUTT_PS:
886                         terminal_mdoc(curp->outdata, man);
887                         break;
888                 case OUTT_MARKDOWN:
889                         markdown_mdoc(curp->outdata, man);
890                         break;
891                 default:
892                         break;
893                 }
894         }
895         if (man->macroset == MACROSET_MAN) {
896                 if (curp->outtype != OUTT_TREE || !curp->outopts->noval)
897                         man_validate(man);
898                 switch (curp->outtype) {
899                 case OUTT_HTML:
900                         html_man(curp->outdata, man);
901                         break;
902                 case OUTT_TREE:
903                         tree_man(curp->outdata, man);
904                         break;
905                 case OUTT_MAN:
906                         man_man(curp->outdata, man);
907                         break;
908                 case OUTT_PDF:
909                 case OUTT_ASCII:
910                 case OUTT_UTF8:
911                 case OUTT_LOCALE:
912                 case OUTT_PS:
913                         terminal_man(curp->outdata, man);
914                         break;
915                 default:
916                         break;
917                 }
918         }
919         if (curp->mmin < MANDOCERR_STYLE)
920                 check_xr(file);
921         mparse_updaterc(curp->mp, &rc);
922 }
923
924 static void
925 check_xr(const char *file)
926 {
927         static struct manpaths   paths;
928         struct mansearch         search;
929         struct mandoc_xr        *xr;
930         char                    *cp;
931         size_t                   sz;
932
933         if (paths.sz == 0)
934                 manpath_base(&paths);
935
936         for (xr = mandoc_xr_get(); xr != NULL; xr = xr->next) {
937                 if (xr->line == -1)
938                         continue;
939                 search.arch = NULL;
940                 search.sec = xr->sec;
941                 search.outkey = NULL;
942                 search.argmode = ARG_NAME;
943                 search.firstmatch = 1;
944                 if (mansearch(&search, &paths, 1, &xr->name, NULL, &sz))
945                         continue;
946                 if (fs_search(&search, &paths, 1, &xr->name, NULL, &sz))
947                         continue;
948                 if (xr->count == 1)
949                         mandoc_asprintf(&cp, "Xr %s %s", xr->name, xr->sec);
950                 else
951                         mandoc_asprintf(&cp, "Xr %s %s (%d times)",
952                             xr->name, xr->sec, xr->count);
953                 mmsg(MANDOCERR_XR_BAD, MANDOCLEVEL_STYLE,
954                     file, xr->line, xr->pos + 1, cp);
955                 free(cp);
956         }
957 }
958
959 static void
960 outdata_alloc(struct curparse *curp)
961 {
962         switch (curp->outtype) {
963         case OUTT_HTML:
964                 curp->outdata = html_alloc(curp->outopts);
965                 break;
966         case OUTT_UTF8:
967                 curp->outdata = utf8_alloc(curp->outopts);
968                 break;
969         case OUTT_LOCALE:
970                 curp->outdata = locale_alloc(curp->outopts);
971                 break;
972         case OUTT_ASCII:
973                 curp->outdata = ascii_alloc(curp->outopts);
974                 break;
975         case OUTT_PDF:
976                 curp->outdata = pdf_alloc(curp->outopts);
977                 break;
978         case OUTT_PS:
979                 curp->outdata = ps_alloc(curp->outopts);
980                 break;
981         default:
982                 break;
983         }
984 }
985
986 static void
987 passthrough(const char *file, int fd, int synopsis_only)
988 {
989         const char       synb[] = "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS";
990         const char       synr[] = "SYNOPSIS";
991
992         FILE            *stream;
993         const char      *syscall;
994         char            *line, *cp;
995         size_t           linesz;
996         ssize_t          len, written;
997         int              print;
998
999         line = NULL;
1000         linesz = 0;
1001
1002         if (fflush(stdout) == EOF) {
1003                 syscall = "fflush";
1004                 goto fail;
1005         }
1006
1007         if ((stream = fdopen(fd, "r")) == NULL) {
1008                 close(fd);
1009                 syscall = "fdopen";
1010                 goto fail;
1011         }
1012
1013         print = 0;
1014         while ((len = getline(&line, &linesz, stream)) != -1) {
1015                 cp = line;
1016                 if (synopsis_only) {
1017                         if (print) {
1018                                 if ( ! isspace((unsigned char)*cp))
1019                                         goto done;
1020                                 while (isspace((unsigned char)*cp)) {
1021                                         cp++;
1022                                         len--;
1023                                 }
1024                         } else {
1025                                 if (strcmp(cp, synb) == 0 ||
1026                                     strcmp(cp, synr) == 0)
1027                                         print = 1;
1028                                 continue;
1029                         }
1030                 }
1031                 for (; len > 0; len -= written) {
1032                         if ((written = write(STDOUT_FILENO, cp, len)) != -1)
1033                                 continue;
1034                         fclose(stream);
1035                         syscall = "write";
1036                         goto fail;
1037                 }
1038         }
1039
1040         if (ferror(stream)) {
1041                 fclose(stream);
1042                 syscall = "getline";
1043                 goto fail;
1044         }
1045
1046 done:
1047         free(line);
1048         fclose(stream);
1049         return;
1050
1051 fail:
1052         free(line);
1053         warn("%s: SYSERR: %s", file, syscall);
1054         if (rc < MANDOCLEVEL_SYSERR)
1055                 rc = MANDOCLEVEL_SYSERR;
1056 }
1057
1058 static int
1059 koptions(int *options, char *arg)
1060 {
1061
1062         if ( ! strcmp(arg, "utf-8")) {
1063                 *options |=  MPARSE_UTF8;
1064                 *options &= ~MPARSE_LATIN1;
1065         } else if ( ! strcmp(arg, "iso-8859-1")) {
1066                 *options |=  MPARSE_LATIN1;
1067                 *options &= ~MPARSE_UTF8;
1068         } else if ( ! strcmp(arg, "us-ascii")) {
1069                 *options &= ~(MPARSE_UTF8 | MPARSE_LATIN1);
1070         } else {
1071                 warnx("-K %s: Bad argument", arg);
1072                 return 0;
1073         }
1074         return 1;
1075 }
1076
1077 static void
1078 moptions(int *options, char *arg)
1079 {
1080
1081         if (arg == NULL)
1082                 return;
1083         if (strcmp(arg, "doc") == 0)
1084                 *options |= MPARSE_MDOC;
1085         else if (strcmp(arg, "an") == 0)
1086                 *options |= MPARSE_MAN;
1087 }
1088
1089 static int
1090 toptions(struct curparse *curp, char *arg)
1091 {
1092
1093         if (0 == strcmp(arg, "ascii"))
1094                 curp->outtype = OUTT_ASCII;
1095         else if (0 == strcmp(arg, "lint")) {
1096                 curp->outtype = OUTT_LINT;
1097                 curp->mmin = MANDOCERR_BASE;
1098                 mmsg_stream = stdout;
1099         } else if (0 == strcmp(arg, "tree"))
1100                 curp->outtype = OUTT_TREE;
1101         else if (0 == strcmp(arg, "man"))
1102                 curp->outtype = OUTT_MAN;
1103         else if (0 == strcmp(arg, "html"))
1104                 curp->outtype = OUTT_HTML;
1105         else if (0 == strcmp(arg, "markdown"))
1106                 curp->outtype = OUTT_MARKDOWN;
1107         else if (0 == strcmp(arg, "utf8"))
1108                 curp->outtype = OUTT_UTF8;
1109         else if (0 == strcmp(arg, "locale"))
1110                 curp->outtype = OUTT_LOCALE;
1111         else if (0 == strcmp(arg, "ps"))
1112                 curp->outtype = OUTT_PS;
1113         else if (0 == strcmp(arg, "pdf"))
1114                 curp->outtype = OUTT_PDF;
1115         else {
1116                 warnx("-T %s: Bad argument", arg);
1117                 return 0;
1118         }
1119
1120         return 1;
1121 }
1122
1123 static int
1124 woptions(struct curparse *curp, char *arg)
1125 {
1126         char            *v, *o;
1127         const char      *toks[11];
1128
1129         toks[0] = "stop";
1130         toks[1] = "all";
1131         toks[2] = "base";
1132         toks[3] = "style";
1133         toks[4] = "warning";
1134         toks[5] = "error";
1135         toks[6] = "unsupp";
1136         toks[7] = "fatal";
1137         toks[8] = "openbsd";
1138         toks[9] = "netbsd";
1139         toks[10] = NULL;
1140
1141         while (*arg) {
1142                 o = arg;
1143                 switch (getsubopt(&arg, (char * const *)toks, &v)) {
1144                 case 0:
1145                         curp->wstop = 1;
1146                         break;
1147                 case 1:
1148                 case 2:
1149                         curp->mmin = MANDOCERR_BASE;
1150                         break;
1151                 case 3:
1152                         curp->mmin = MANDOCERR_STYLE;
1153                         break;
1154                 case 4:
1155                         curp->mmin = MANDOCERR_WARNING;
1156                         break;
1157                 case 5:
1158                         curp->mmin = MANDOCERR_ERROR;
1159                         break;
1160                 case 6:
1161                         curp->mmin = MANDOCERR_UNSUPP;
1162                         break;
1163                 case 7:
1164                         curp->mmin = MANDOCERR_MAX;
1165                         break;
1166                 case 8:
1167                         curp->mmin = MANDOCERR_BASE;
1168                         curp->os_e = MANDOC_OS_OPENBSD;
1169                         break;
1170                 case 9:
1171                         curp->mmin = MANDOCERR_BASE;
1172                         curp->os_e = MANDOC_OS_NETBSD;
1173                         break;
1174                 default:
1175                         warnx("-W %s: Bad argument", o);
1176                         return 0;
1177                 }
1178         }
1179         return 1;
1180 }
1181
1182 static void
1183 mmsg(enum mandocerr t, enum mandoclevel lvl,
1184                 const char *file, int line, int col, const char *msg)
1185 {
1186         const char      *mparse_msg;
1187
1188         fprintf(mmsg_stream, "%s: %s:", getprogname(),
1189             file == NULL ? "<stdin>" : file);
1190
1191         if (line)
1192                 fprintf(mmsg_stream, "%d:%d:", line, col + 1);
1193
1194         fprintf(mmsg_stream, " %s", mparse_strlevel(lvl));
1195
1196         if ((mparse_msg = mparse_strerror(t)) != NULL)
1197                 fprintf(mmsg_stream, ": %s", mparse_msg);
1198
1199         if (msg)
1200                 fprintf(mmsg_stream, ": %s", msg);
1201
1202         fputc('\n', mmsg_stream);
1203 }
1204
1205 static pid_t
1206 spawn_pager(struct tag_files *tag_files)
1207 {
1208         const struct timespec timeout = { 0, 100000000 };  /* 0.1s */
1209 #define MAX_PAGER_ARGS 16
1210         char            *argv[MAX_PAGER_ARGS];
1211         const char      *pager;
1212         char            *cp;
1213         size_t           cmdlen;
1214         int              argc;
1215         pid_t            pager_pid;
1216
1217         pager = getenv("MANPAGER");
1218         if (pager == NULL || *pager == '\0')
1219                 pager = getenv("PAGER");
1220         if (pager == NULL || *pager == '\0')
1221                 pager = "less -s";
1222         cp = mandoc_strdup(pager);
1223
1224         /*
1225          * Parse the pager command into words.
1226          * Intentionally do not do anything fancy here.
1227          */
1228
1229         argc = 0;
1230         while (argc + 4 < MAX_PAGER_ARGS) {
1231                 argv[argc++] = cp;
1232                 cp = strchr(cp, ' ');
1233                 if (cp == NULL)
1234                         break;
1235                 *cp++ = '\0';
1236                 while (*cp == ' ')
1237                         cp++;
1238                 if (*cp == '\0')
1239                         break;
1240         }
1241
1242         /* For less(1), use the tag file. */
1243
1244         if ((cmdlen = strlen(argv[0])) >= 4) {
1245                 cp = argv[0] + cmdlen - 4;
1246                 if (strcmp(cp, "less") == 0) {
1247                         argv[argc++] = mandoc_strdup("-T");
1248                         argv[argc++] = tag_files->tfn;
1249                 }
1250         }
1251         argv[argc++] = tag_files->ofn;
1252         argv[argc] = NULL;
1253
1254         switch (pager_pid = fork()) {
1255         case -1:
1256                 err((int)MANDOCLEVEL_SYSERR, "fork");
1257         case 0:
1258                 break;
1259         default:
1260                 (void)setpgid(pager_pid, 0);
1261                 (void)tcsetpgrp(tag_files->ofd, pager_pid);
1262 #if HAVE_PLEDGE
1263                 if (pledge("stdio rpath tmppath tty proc", NULL) == -1)
1264                         err((int)MANDOCLEVEL_SYSERR, "pledge");
1265 #endif
1266                 tag_files->pager_pid = pager_pid;
1267                 return pager_pid;
1268         }
1269
1270         /* The child process becomes the pager. */
1271
1272         if (dup2(tag_files->ofd, STDOUT_FILENO) == -1)
1273                 err((int)MANDOCLEVEL_SYSERR, "pager stdout");
1274         close(tag_files->ofd);
1275         assert(tag_files->tfd == -1);
1276
1277         /* Do not start the pager before controlling the terminal. */
1278
1279         while (tcgetpgrp(STDOUT_FILENO) != getpid())
1280                 nanosleep(&timeout, NULL);
1281
1282         execvp(argv[0], argv);
1283         err((int)MANDOCLEVEL_SYSERR, "exec %s", argv[0]);
1284 }