1/*	Id: manpath.c,v 1.37 2018/11/22 11:30:23 schwarze Exp  */
2/*
3 * Copyright (c) 2011,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org>
4 * Copyright (c) 2011 Kristaps Dzonsons <kristaps@bsd.lv>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18#include "config.h"
19
20#include <sys/types.h>
21#include <sys/stat.h>
22
23#include <ctype.h>
24#if HAVE_ERR
25#include <err.h>
26#endif
27#include <limits.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31
32#include "mandoc_aux.h"
33#include "manconf.h"
34
35static	void	 manconf_file(struct manconf *, const char *);
36static	void	 manpath_add(struct manpaths *, const char *, int);
37static	void	 manpath_parseline(struct manpaths *, char *, int);
38
39
40void
41manconf_parse(struct manconf *conf, const char *file,
42		char *defp, char *auxp)
43{
44	char		*insert;
45
46	/* Always prepend -m. */
47	manpath_parseline(&conf->manpath, auxp, 1);
48
49	/* If -M is given, it overrides everything else. */
50	if (NULL != defp) {
51		manpath_parseline(&conf->manpath, defp, 1);
52		return;
53	}
54
55	/* MANPATH and man.conf(5) cooperate. */
56	defp = getenv("MANPATH");
57	if (NULL == file)
58		file = MAN_CONF_FILE;
59
60	/* No MANPATH; use man.conf(5) only. */
61	if (NULL == defp || '\0' == defp[0]) {
62		manconf_file(conf, file);
63		return;
64	}
65
66	/* Prepend man.conf(5) to MANPATH. */
67	if (':' == defp[0]) {
68		manconf_file(conf, file);
69		manpath_parseline(&conf->manpath, defp, 0);
70		return;
71	}
72
73	/* Append man.conf(5) to MANPATH. */
74	if (':' == defp[strlen(defp) - 1]) {
75		manpath_parseline(&conf->manpath, defp, 0);
76		manconf_file(conf, file);
77		return;
78	}
79
80	/* Insert man.conf(5) into MANPATH. */
81	insert = strstr(defp, "::");
82	if (NULL != insert) {
83		*insert++ = '\0';
84		manpath_parseline(&conf->manpath, defp, 0);
85		manconf_file(conf, file);
86		manpath_parseline(&conf->manpath, insert + 1, 0);
87		return;
88	}
89
90	/* MANPATH overrides man.conf(5) completely. */
91	manpath_parseline(&conf->manpath, defp, 0);
92}
93
94void
95manpath_base(struct manpaths *dirs)
96{
97	char path_base[] = MANPATH_BASE;
98	manpath_parseline(dirs, path_base, 0);
99}
100
101/*
102 * Parse a FULL pathname from a colon-separated list of arrays.
103 */
104static void
105manpath_parseline(struct manpaths *dirs, char *path, int complain)
106{
107	char	*dir;
108
109	if (NULL == path)
110		return;
111
112	for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
113		manpath_add(dirs, dir, complain);
114}
115
116/*
117 * Add a directory to the array, ignoring bad directories.
118 * Grow the array one-by-one for simplicity's sake.
119 */
120static void
121manpath_add(struct manpaths *dirs, const char *dir, int complain)
122{
123	char		 buf[PATH_MAX];
124	struct stat	 sb;
125	char		*cp;
126	size_t		 i;
127
128	if (NULL == (cp = realpath(dir, buf))) {
129		if (complain)
130			warn("manpath: %s", dir);
131		return;
132	}
133
134	for (i = 0; i < dirs->sz; i++)
135		if (0 == strcmp(dirs->paths[i], dir))
136			return;
137
138	if (stat(cp, &sb) == -1) {
139		if (complain)
140			warn("manpath: %s", dir);
141		return;
142	}
143
144	dirs->paths = mandoc_reallocarray(dirs->paths,
145	    dirs->sz + 1, sizeof(char *));
146
147	dirs->paths[dirs->sz++] = mandoc_strdup(cp);
148}
149
150void
151manconf_free(struct manconf *conf)
152{
153	size_t		 i;
154
155	for (i = 0; i < conf->manpath.sz; i++)
156		free(conf->manpath.paths[i]);
157
158	free(conf->manpath.paths);
159	free(conf->output.includes);
160	free(conf->output.man);
161	free(conf->output.paper);
162	free(conf->output.style);
163}
164
165static void
166manconf_file(struct manconf *conf, const char *file)
167{
168	const char *const toks[] = { "manpath", "output", "_whatdb" };
169	char manpath_default[] = MANPATH_DEFAULT;
170
171	FILE		*stream;
172	char		*line, *cp, *ep;
173	size_t		 linesz, tok, toklen;
174	ssize_t		 linelen;
175
176	if ((stream = fopen(file, "r")) == NULL)
177		goto out;
178
179	line = NULL;
180	linesz = 0;
181
182	while ((linelen = getline(&line, &linesz, stream)) != -1) {
183		cp = line;
184		ep = cp + linelen - 1;
185		while (ep > cp && isspace((unsigned char)*ep))
186			*ep-- = '\0';
187		while (isspace((unsigned char)*cp))
188			cp++;
189		if (cp == ep || *cp == '#')
190			continue;
191
192		for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) {
193			toklen = strlen(toks[tok]);
194			if (cp + toklen < ep &&
195			    isspace((unsigned char)cp[toklen]) &&
196			    strncmp(cp, toks[tok], toklen) == 0) {
197				cp += toklen;
198				while (isspace((unsigned char)*cp))
199					cp++;
200				break;
201			}
202		}
203
204		switch (tok) {
205		case 2:  /* _whatdb */
206			while (ep > cp && ep[-1] != '/')
207				ep--;
208			if (ep == cp)
209				continue;
210			*ep = '\0';
211			/* FALLTHROUGH */
212		case 0:  /* manpath */
213			manpath_add(&conf->manpath, cp, 0);
214			*manpath_default = '\0';
215			break;
216		case 1:  /* output */
217			manconf_output(&conf->output, cp, 1);
218			break;
219		default:
220			break;
221		}
222	}
223	free(line);
224	fclose(stream);
225
226out:
227	if (*manpath_default != '\0')
228		manpath_parseline(&conf->manpath, manpath_default, 0);
229}
230
231int
232manconf_output(struct manoutput *conf, const char *cp, int fromfile)
233{
234	const char *const toks[] = {
235	    "includes", "man", "paper", "style", "indent", "width",
236	    "tag", "fragment", "mdoc", "noval", "toc"
237	};
238
239	const char	*errstr;
240	char		*oldval;
241	size_t		 len, tok;
242
243	for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) {
244		len = strlen(toks[tok]);
245		if ( ! strncmp(cp, toks[tok], len) &&
246		    strchr(" =	", cp[len]) != NULL) {
247			cp += len;
248			if (*cp == '=')
249				cp++;
250			while (isspace((unsigned char)*cp))
251				cp++;
252			break;
253		}
254	}
255
256	if (tok < 6 && *cp == '\0') {
257		warnx("-O %s=?: Missing argument value", toks[tok]);
258		return -1;
259	}
260	if (tok > 6 && *cp != '\0') {
261		warnx("-O %s: Does not take a value: %s", toks[tok], cp);
262		return -1;
263	}
264
265	switch (tok) {
266	case 0:
267		if (conf->includes != NULL) {
268			oldval = mandoc_strdup(conf->includes);
269			break;
270		}
271		conf->includes = mandoc_strdup(cp);
272		return 0;
273	case 1:
274		if (conf->man != NULL) {
275			oldval = mandoc_strdup(conf->man);
276			break;
277		}
278		conf->man = mandoc_strdup(cp);
279		return 0;
280	case 2:
281		if (conf->paper != NULL) {
282			oldval = mandoc_strdup(conf->paper);
283			break;
284		}
285		conf->paper = mandoc_strdup(cp);
286		return 0;
287	case 3:
288		if (conf->style != NULL) {
289			oldval = mandoc_strdup(conf->style);
290			break;
291		}
292		conf->style = mandoc_strdup(cp);
293		return 0;
294	case 4:
295		if (conf->indent) {
296			mandoc_asprintf(&oldval, "%zu", conf->indent);
297			break;
298		}
299		conf->indent = strtonum(cp, 0, 1000, &errstr);
300		if (errstr == NULL)
301			return 0;
302		warnx("-O indent=%s is %s", cp, errstr);
303		return -1;
304	case 5:
305		if (conf->width) {
306			mandoc_asprintf(&oldval, "%zu", conf->width);
307			break;
308		}
309		conf->width = strtonum(cp, 1, 1000, &errstr);
310		if (errstr == NULL)
311			return 0;
312		warnx("-O width=%s is %s", cp, errstr);
313		return -1;
314	case 6:
315		if (conf->tag != NULL) {
316			oldval = mandoc_strdup(conf->tag);
317			break;
318		}
319		conf->tag = mandoc_strdup(cp);
320		return 0;
321	case 7:
322		conf->fragment = 1;
323		return 0;
324	case 8:
325		conf->mdoc = 1;
326		return 0;
327	case 9:
328		conf->noval = 1;
329		return 0;
330	case 10:
331		conf->toc = 1;
332		return 0;
333	default:
334		if (fromfile)
335			warnx("-O %s: Bad argument", cp);
336		return -1;
337	}
338	if (fromfile == 0)
339		warnx("-O %s=%s: Option already set to %s",
340		    toks[tok], cp, oldval);
341	free(oldval);
342	return -1;
343}
344