1*b79f6b43Sschwarze /* $OpenBSD: manpath.c,v 1.31 2021/11/05 18:03:00 schwarze Exp $ */
227255502Sschwarze /*
3*b79f6b43Sschwarze * Copyright (c) 2011,2014,2015,2017-2021 Ingo Schwarze <schwarze@openbsd.org>
427255502Sschwarze * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
527255502Sschwarze *
627255502Sschwarze * Permission to use, copy, modify, and distribute this software for any
727255502Sschwarze * purpose with or without fee is hereby granted, provided that the above
827255502Sschwarze * copyright notice and this permission notice appear in all copies.
927255502Sschwarze *
10d05e3f9eSschwarze * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1127255502Sschwarze * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12d05e3f9eSschwarze * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1327255502Sschwarze * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1427255502Sschwarze * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1527255502Sschwarze * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1627255502Sschwarze * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1727255502Sschwarze */
18a2e5c5f4Sschwarze #include <sys/types.h>
19a2e5c5f4Sschwarze #include <sys/stat.h>
2027255502Sschwarze
2127255502Sschwarze #include <ctype.h>
22ecd1ed85Sschwarze #include <errno.h>
2327255502Sschwarze #include <limits.h>
2427255502Sschwarze #include <stdio.h>
2527255502Sschwarze #include <stdlib.h>
2627255502Sschwarze #include <string.h>
2727255502Sschwarze
284f4f7972Sschwarze #include "mandoc_aux.h"
29ecd1ed85Sschwarze #include "mandoc.h"
304de77decSschwarze #include "manconf.h"
3127255502Sschwarze
3227255502Sschwarze #define MAN_CONF_FILE "/etc/man.conf"
3319b6bef7Sschwarze #define MANPATH_BASE "/usr/share/man:/usr/X11R6/man"
34263f9f36Sschwarze #define MANPATH_DEFAULT "/usr/share/man:/usr/X11R6/man:/usr/local/man"
3527255502Sschwarze
36*b79f6b43Sschwarze static void manconf_file(struct manconf *, const char *, int);
37ecd1ed85Sschwarze static void manpath_add(struct manpaths *, const char *, char);
38ecd1ed85Sschwarze static void manpath_parseline(struct manpaths *, char *, char);
3927255502Sschwarze
404de77decSschwarze
4127255502Sschwarze void
manconf_parse(struct manconf * conf,const char * file,char * pend,char * pbeg)42*b79f6b43Sschwarze manconf_parse(struct manconf *conf, const char *file, char *pend, char *pbeg)
4327255502Sschwarze {
44*b79f6b43Sschwarze int use_path_from_file = 1;
4527255502Sschwarze
460f34e0a0Sschwarze /* Always prepend -m. */
47*b79f6b43Sschwarze manpath_parseline(&conf->manpath, pbeg, 'm');
4827255502Sschwarze
49*b79f6b43Sschwarze if (pend != NULL && *pend != '\0') {
500f34e0a0Sschwarze /* If -M is given, it overrides everything else. */
51*b79f6b43Sschwarze manpath_parseline(&conf->manpath, pend, 'M');
52*b79f6b43Sschwarze use_path_from_file = 0;
53*b79f6b43Sschwarze pbeg = pend = NULL;
54*b79f6b43Sschwarze } else if ((pbeg = getenv("MANPATH")) == NULL || *pbeg == '\0') {
550f34e0a0Sschwarze /* No MANPATH; use man.conf(5) only. */
56*b79f6b43Sschwarze pbeg = pend = NULL;
57*b79f6b43Sschwarze } else if (*pbeg == ':') {
580f34e0a0Sschwarze /* Prepend man.conf(5) to MANPATH. */
59*b79f6b43Sschwarze pend = pbeg + 1;
60*b79f6b43Sschwarze pbeg = NULL;
61*b79f6b43Sschwarze } else if ((pend = strstr(pbeg, "::")) != NULL) {
620f34e0a0Sschwarze /* Insert man.conf(5) into MANPATH. */
63*b79f6b43Sschwarze *pend = '\0';
64*b79f6b43Sschwarze pend += 2;
65*b79f6b43Sschwarze } else if (pbeg[strlen(pbeg) - 1] == ':') {
66*b79f6b43Sschwarze /* Append man.conf(5) to MANPATH. */
67*b79f6b43Sschwarze pend = NULL;
68*b79f6b43Sschwarze } else {
69*b79f6b43Sschwarze /* MANPATH overrides man.conf(5) completely. */
70*b79f6b43Sschwarze use_path_from_file = 0;
71*b79f6b43Sschwarze pend = NULL;
720f34e0a0Sschwarze }
730f34e0a0Sschwarze
74*b79f6b43Sschwarze manpath_parseline(&conf->manpath, pbeg, '\0');
75*b79f6b43Sschwarze
76*b79f6b43Sschwarze if (file == NULL)
77*b79f6b43Sschwarze file = MAN_CONF_FILE;
78*b79f6b43Sschwarze manconf_file(conf, file, use_path_from_file);
79*b79f6b43Sschwarze
80*b79f6b43Sschwarze manpath_parseline(&conf->manpath, pend, '\0');
8127255502Sschwarze }
8227255502Sschwarze
8319b6bef7Sschwarze void
manpath_base(struct manpaths * dirs)8419b6bef7Sschwarze manpath_base(struct manpaths *dirs)
8519b6bef7Sschwarze {
8619b6bef7Sschwarze char path_base[] = MANPATH_BASE;
87ecd1ed85Sschwarze manpath_parseline(dirs, path_base, '\0');
8819b6bef7Sschwarze }
8919b6bef7Sschwarze
9027255502Sschwarze /*
9127255502Sschwarze * Parse a FULL pathname from a colon-separated list of arrays.
9227255502Sschwarze */
9318eee2d9Sschwarze static void
manpath_parseline(struct manpaths * dirs,char * path,char option)94ecd1ed85Sschwarze manpath_parseline(struct manpaths *dirs, char *path, char option)
9527255502Sschwarze {
9627255502Sschwarze char *dir;
9727255502Sschwarze
9827255502Sschwarze if (NULL == path)
9927255502Sschwarze return;
10027255502Sschwarze
10127255502Sschwarze for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
102ecd1ed85Sschwarze manpath_add(dirs, dir, option);
10327255502Sschwarze }
10427255502Sschwarze
10527255502Sschwarze /*
10627255502Sschwarze * Add a directory to the array, ignoring bad directories.
10727255502Sschwarze * Grow the array one-by-one for simplicity's sake.
10827255502Sschwarze */
10927255502Sschwarze static void
manpath_add(struct manpaths * dirs,const char * dir,char option)110ecd1ed85Sschwarze manpath_add(struct manpaths *dirs, const char *dir, char option)
11127255502Sschwarze {
11227255502Sschwarze char buf[PATH_MAX];
113a2e5c5f4Sschwarze struct stat sb;
11427255502Sschwarze char *cp;
1153db9e7afSschwarze size_t i;
11627255502Sschwarze
117ecd1ed85Sschwarze if ((cp = realpath(dir, buf)) == NULL)
118ecd1ed85Sschwarze goto fail;
11927255502Sschwarze
12027255502Sschwarze for (i = 0; i < dirs->sz; i++)
121ecd1ed85Sschwarze if (strcmp(dirs->paths[i], dir) == 0)
12227255502Sschwarze return;
12327255502Sschwarze
124ecd1ed85Sschwarze if (stat(cp, &sb) == -1)
125ecd1ed85Sschwarze goto fail;
126a2e5c5f4Sschwarze
1278286bf36Sschwarze dirs->paths = mandoc_reallocarray(dirs->paths,
128ecd1ed85Sschwarze dirs->sz + 1, sizeof(*dirs->paths));
12927255502Sschwarze dirs->paths[dirs->sz++] = mandoc_strdup(cp);
130ecd1ed85Sschwarze return;
131ecd1ed85Sschwarze
132ecd1ed85Sschwarze fail:
133ecd1ed85Sschwarze if (option != '\0')
134ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
135ecd1ed85Sschwarze "-%c %s: %s", option, dir, strerror(errno));
13627255502Sschwarze }
13727255502Sschwarze
13827255502Sschwarze void
manconf_free(struct manconf * conf)1394de77decSschwarze manconf_free(struct manconf *conf)
14027255502Sschwarze {
1413db9e7afSschwarze size_t i;
14227255502Sschwarze
1434de77decSschwarze for (i = 0; i < conf->manpath.sz; i++)
1444de77decSschwarze free(conf->manpath.paths[i]);
14527255502Sschwarze
1464de77decSschwarze free(conf->manpath.paths);
1474de77decSschwarze free(conf->output.includes);
1484de77decSschwarze free(conf->output.man);
1494de77decSschwarze free(conf->output.paper);
1504de77decSschwarze free(conf->output.style);
15127255502Sschwarze }
15227255502Sschwarze
1534de77decSschwarze static void
manconf_file(struct manconf * conf,const char * file,int use_path_from_file)154*b79f6b43Sschwarze manconf_file(struct manconf *conf, const char *file, int use_path_from_file)
15527255502Sschwarze {
1566709a814Sschwarze const char *const toks[] = { "manpath", "output" };
157ef3112a4Sschwarze char manpath_default[] = MANPATH_DEFAULT;
158d05e3f9eSschwarze
15927255502Sschwarze FILE *stream;
16031f93c25Sschwarze char *line, *cp, *ep;
16131f93c25Sschwarze size_t linesz, tok, toklen;
16231f93c25Sschwarze ssize_t linelen;
16327255502Sschwarze
164d05e3f9eSschwarze if ((stream = fopen(file, "r")) == NULL)
165ef3112a4Sschwarze goto out;
16627255502Sschwarze
16731f93c25Sschwarze line = NULL;
16831f93c25Sschwarze linesz = 0;
16931f93c25Sschwarze
17031f93c25Sschwarze while ((linelen = getline(&line, &linesz, stream)) != -1) {
17131f93c25Sschwarze cp = line;
172eff00e48Smillert ep = cp + linelen - 1;
173eff00e48Smillert while (ep > cp && isspace((unsigned char)*ep))
174eff00e48Smillert *ep-- = '\0';
175d05e3f9eSschwarze while (isspace((unsigned char)*cp))
176d05e3f9eSschwarze cp++;
177eff00e48Smillert if (cp == ep || *cp == '#')
17827255502Sschwarze continue;
179d05e3f9eSschwarze
180d05e3f9eSschwarze for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) {
18131f93c25Sschwarze toklen = strlen(toks[tok]);
18231f93c25Sschwarze if (cp + toklen < ep &&
18331f93c25Sschwarze isspace((unsigned char)cp[toklen]) &&
18431f93c25Sschwarze strncmp(cp, toks[tok], toklen) == 0) {
18531f93c25Sschwarze cp += toklen;
186d05e3f9eSschwarze while (isspace((unsigned char)*cp))
187d05e3f9eSschwarze cp++;
188d05e3f9eSschwarze break;
189d05e3f9eSschwarze }
190d05e3f9eSschwarze }
191d05e3f9eSschwarze
192d05e3f9eSschwarze switch (tok) {
193d05e3f9eSschwarze case 0: /* manpath */
194*b79f6b43Sschwarze if (use_path_from_file)
195ecd1ed85Sschwarze manpath_add(&conf->manpath, cp, '\0');
196ef3112a4Sschwarze *manpath_default = '\0';
1974de77decSschwarze break;
1984de77decSschwarze case 1: /* output */
1994b502eb4Sschwarze manconf_output(&conf->output, cp, 1);
200d05e3f9eSschwarze break;
201d05e3f9eSschwarze default:
202d05e3f9eSschwarze break;
203d05e3f9eSschwarze }
20427255502Sschwarze }
20531f93c25Sschwarze free(line);
20627255502Sschwarze fclose(stream);
207ef3112a4Sschwarze
208ef3112a4Sschwarze out:
209*b79f6b43Sschwarze if (use_path_from_file && *manpath_default != '\0')
210ecd1ed85Sschwarze manpath_parseline(&conf->manpath, manpath_default, '\0');
21127255502Sschwarze }
2124de77decSschwarze
2134b502eb4Sschwarze int
manconf_output(struct manoutput * conf,const char * cp,int fromfile)2144b502eb4Sschwarze manconf_output(struct manoutput *conf, const char *cp, int fromfile)
2154de77decSschwarze {
2164de77decSschwarze const char *const toks[] = {
2170d0f340dSschwarze /* Tokens requiring an argument. */
218d17f6067Sschwarze "includes", "man", "paper", "style", "indent", "width",
2190d0f340dSschwarze "outfilename", "tagfilename",
2200d0f340dSschwarze /* Token taking an optional argument. */
2210d0f340dSschwarze "tag",
2220d0f340dSschwarze /* Tokens not taking arguments. */
22390f584c6Sschwarze "fragment", "mdoc", "noval", "toc"
2244de77decSschwarze };
225c229c1b8Santon const size_t ntoks = sizeof(toks) / sizeof(toks[0]);
2264de77decSschwarze
2274b502eb4Sschwarze const char *errstr;
2284b502eb4Sschwarze char *oldval;
2294de77decSschwarze size_t len, tok;
2304de77decSschwarze
231c229c1b8Santon for (tok = 0; tok < ntoks; tok++) {
2324de77decSschwarze len = strlen(toks[tok]);
233ecd1ed85Sschwarze if (strncmp(cp, toks[tok], len) == 0 &&
2344de77decSschwarze strchr(" = ", cp[len]) != NULL) {
2354de77decSschwarze cp += len;
2364de77decSschwarze if (*cp == '=')
2374de77decSschwarze cp++;
2384de77decSschwarze while (isspace((unsigned char)*cp))
2394de77decSschwarze cp++;
2404de77decSschwarze break;
2414de77decSschwarze }
2424de77decSschwarze }
2434de77decSschwarze
24490f584c6Sschwarze if (tok < 8 && *cp == '\0') {
245ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]);
2464b502eb4Sschwarze return -1;
2474b502eb4Sschwarze }
24890f584c6Sschwarze if (tok > 8 && tok < ntoks && *cp != '\0') {
249ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADVAL, 0, 0, "-O %s=%s", toks[tok], cp);
2504b502eb4Sschwarze return -1;
2514b502eb4Sschwarze }
2524de77decSschwarze
2534de77decSschwarze switch (tok) {
2544de77decSschwarze case 0:
2554b502eb4Sschwarze if (conf->includes != NULL) {
2564b502eb4Sschwarze oldval = mandoc_strdup(conf->includes);
2574de77decSschwarze break;
2584de77decSschwarze }
2594b502eb4Sschwarze conf->includes = mandoc_strdup(cp);
2604b502eb4Sschwarze return 0;
2614b502eb4Sschwarze case 1:
2624b502eb4Sschwarze if (conf->man != NULL) {
2634b502eb4Sschwarze oldval = mandoc_strdup(conf->man);
2644b502eb4Sschwarze break;
2654b502eb4Sschwarze }
2664b502eb4Sschwarze conf->man = mandoc_strdup(cp);
2674b502eb4Sschwarze return 0;
2684b502eb4Sschwarze case 2:
2694b502eb4Sschwarze if (conf->paper != NULL) {
2704b502eb4Sschwarze oldval = mandoc_strdup(conf->paper);
2714b502eb4Sschwarze break;
2724b502eb4Sschwarze }
2734b502eb4Sschwarze conf->paper = mandoc_strdup(cp);
2744b502eb4Sschwarze return 0;
2754b502eb4Sschwarze case 3:
2764b502eb4Sschwarze if (conf->style != NULL) {
2774b502eb4Sschwarze oldval = mandoc_strdup(conf->style);
2784b502eb4Sschwarze break;
2794b502eb4Sschwarze }
2804b502eb4Sschwarze conf->style = mandoc_strdup(cp);
2814b502eb4Sschwarze return 0;
2824b502eb4Sschwarze case 4:
2834b502eb4Sschwarze if (conf->indent) {
2844b502eb4Sschwarze mandoc_asprintf(&oldval, "%zu", conf->indent);
2854b502eb4Sschwarze break;
2864b502eb4Sschwarze }
2874b502eb4Sschwarze conf->indent = strtonum(cp, 0, 1000, &errstr);
2884b502eb4Sschwarze if (errstr == NULL)
2894b502eb4Sschwarze return 0;
290ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
291ecd1ed85Sschwarze "-O indent=%s is %s", cp, errstr);
2924b502eb4Sschwarze return -1;
2934b502eb4Sschwarze case 5:
2944b502eb4Sschwarze if (conf->width) {
2954b502eb4Sschwarze mandoc_asprintf(&oldval, "%zu", conf->width);
2964b502eb4Sschwarze break;
2974b502eb4Sschwarze }
2983d83c79fSschwarze conf->width = strtonum(cp, 1, 1000, &errstr);
2994b502eb4Sschwarze if (errstr == NULL)
3004b502eb4Sschwarze return 0;
301ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
302ecd1ed85Sschwarze "-O width=%s is %s", cp, errstr);
3034b502eb4Sschwarze return -1;
3044b502eb4Sschwarze case 6:
30590f584c6Sschwarze if (conf->outfilename != NULL) {
30690f584c6Sschwarze oldval = mandoc_strdup(conf->outfilename);
30790f584c6Sschwarze break;
30890f584c6Sschwarze }
30990f584c6Sschwarze conf->outfilename = mandoc_strdup(cp);
3104b502eb4Sschwarze return 0;
3110d0f340dSschwarze case 7:
31290f584c6Sschwarze if (conf->tagfilename != NULL) {
31390f584c6Sschwarze oldval = mandoc_strdup(conf->tagfilename);
31490f584c6Sschwarze break;
31590f584c6Sschwarze }
31690f584c6Sschwarze conf->tagfilename = mandoc_strdup(cp);
317385121c9Sschwarze return 0;
3180d0f340dSschwarze /*
3190d0f340dSschwarze * If the index of the following token changes,
3200d0f340dSschwarze * do not forget to adjust the range check above the switch.
3210d0f340dSschwarze */
3220d0f340dSschwarze case 8:
3230d0f340dSschwarze if (conf->tag != NULL) {
3240d0f340dSschwarze oldval = mandoc_strdup(conf->tag);
3250d0f340dSschwarze break;
3260d0f340dSschwarze }
3270d0f340dSschwarze conf->tag = mandoc_strdup(cp);
3280d0f340dSschwarze return 0;
3293327fa00Sschwarze case 9:
33090f584c6Sschwarze conf->fragment = 1;
331d17f6067Sschwarze return 0;
332d17f6067Sschwarze case 10:
33390f584c6Sschwarze conf->mdoc = 1;
33490f584c6Sschwarze return 0;
33590f584c6Sschwarze case 11:
33690f584c6Sschwarze conf->noval = 1;
33790f584c6Sschwarze return 0;
33890f584c6Sschwarze case 12:
3393327fa00Sschwarze conf->toc = 1;
3403327fa00Sschwarze return 0;
3414b502eb4Sschwarze default:
342ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-O %s", cp);
3434b502eb4Sschwarze return -1;
3444b502eb4Sschwarze }
345ecd1ed85Sschwarze if (fromfile) {
346ecd1ed85Sschwarze free(oldval);
347ecd1ed85Sschwarze return 0;
348ecd1ed85Sschwarze } else {
349ecd1ed85Sschwarze mandoc_msg(MANDOCERR_BADVAL_DUPE, 0, 0,
350ecd1ed85Sschwarze "-O %s=%s: already set to %s", toks[tok], cp, oldval);
3514b502eb4Sschwarze free(oldval);
3524b502eb4Sschwarze return -1;
3534de77decSschwarze }
354ecd1ed85Sschwarze }
355