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