xref: /openbsd-src/usr.bin/mandoc/manpath.c (revision b79f6b43c56082321ec0363c51fae4b18c7434de)
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