1 /* $OpenBSD: manpath.c,v 1.31 2021/11/05 18:03:00 schwarze Exp $ */
2 /*
3 * Copyright (c) 2011,2014,2015,2017-2021 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 <sys/types.h>
19 #include <sys/stat.h>
20
21 #include <ctype.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include "mandoc_aux.h"
29 #include "mandoc.h"
30 #include "manconf.h"
31
32 #define MAN_CONF_FILE "/etc/man.conf"
33 #define MANPATH_BASE "/usr/share/man:/usr/X11R6/man"
34 #define MANPATH_DEFAULT "/usr/share/man:/usr/X11R6/man:/usr/local/man"
35
36 static void manconf_file(struct manconf *, const char *, int);
37 static void manpath_add(struct manpaths *, const char *, char);
38 static void manpath_parseline(struct manpaths *, char *, char);
39
40
41 void
manconf_parse(struct manconf * conf,const char * file,char * pend,char * pbeg)42 manconf_parse(struct manconf *conf, const char *file, char *pend, char *pbeg)
43 {
44 int use_path_from_file = 1;
45
46 /* Always prepend -m. */
47 manpath_parseline(&conf->manpath, pbeg, 'm');
48
49 if (pend != NULL && *pend != '\0') {
50 /* If -M is given, it overrides everything else. */
51 manpath_parseline(&conf->manpath, pend, 'M');
52 use_path_from_file = 0;
53 pbeg = pend = NULL;
54 } else if ((pbeg = getenv("MANPATH")) == NULL || *pbeg == '\0') {
55 /* No MANPATH; use man.conf(5) only. */
56 pbeg = pend = NULL;
57 } else if (*pbeg == ':') {
58 /* Prepend man.conf(5) to MANPATH. */
59 pend = pbeg + 1;
60 pbeg = NULL;
61 } else if ((pend = strstr(pbeg, "::")) != NULL) {
62 /* Insert man.conf(5) into MANPATH. */
63 *pend = '\0';
64 pend += 2;
65 } else if (pbeg[strlen(pbeg) - 1] == ':') {
66 /* Append man.conf(5) to MANPATH. */
67 pend = NULL;
68 } else {
69 /* MANPATH overrides man.conf(5) completely. */
70 use_path_from_file = 0;
71 pend = NULL;
72 }
73
74 manpath_parseline(&conf->manpath, pbeg, '\0');
75
76 if (file == NULL)
77 file = MAN_CONF_FILE;
78 manconf_file(conf, file, use_path_from_file);
79
80 manpath_parseline(&conf->manpath, pend, '\0');
81 }
82
83 void
manpath_base(struct manpaths * dirs)84 manpath_base(struct manpaths *dirs)
85 {
86 char path_base[] = MANPATH_BASE;
87 manpath_parseline(dirs, path_base, '\0');
88 }
89
90 /*
91 * Parse a FULL pathname from a colon-separated list of arrays.
92 */
93 static void
manpath_parseline(struct manpaths * dirs,char * path,char option)94 manpath_parseline(struct manpaths *dirs, char *path, char option)
95 {
96 char *dir;
97
98 if (NULL == path)
99 return;
100
101 for (dir = strtok(path, ":"); dir; dir = strtok(NULL, ":"))
102 manpath_add(dirs, dir, option);
103 }
104
105 /*
106 * Add a directory to the array, ignoring bad directories.
107 * Grow the array one-by-one for simplicity's sake.
108 */
109 static void
manpath_add(struct manpaths * dirs,const char * dir,char option)110 manpath_add(struct manpaths *dirs, const char *dir, char option)
111 {
112 char buf[PATH_MAX];
113 struct stat sb;
114 char *cp;
115 size_t i;
116
117 if ((cp = realpath(dir, buf)) == NULL)
118 goto fail;
119
120 for (i = 0; i < dirs->sz; i++)
121 if (strcmp(dirs->paths[i], dir) == 0)
122 return;
123
124 if (stat(cp, &sb) == -1)
125 goto fail;
126
127 dirs->paths = mandoc_reallocarray(dirs->paths,
128 dirs->sz + 1, sizeof(*dirs->paths));
129 dirs->paths[dirs->sz++] = mandoc_strdup(cp);
130 return;
131
132 fail:
133 if (option != '\0')
134 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0,
135 "-%c %s: %s", option, dir, strerror(errno));
136 }
137
138 void
manconf_free(struct manconf * conf)139 manconf_free(struct manconf *conf)
140 {
141 size_t i;
142
143 for (i = 0; i < conf->manpath.sz; i++)
144 free(conf->manpath.paths[i]);
145
146 free(conf->manpath.paths);
147 free(conf->output.includes);
148 free(conf->output.man);
149 free(conf->output.paper);
150 free(conf->output.style);
151 }
152
153 static void
manconf_file(struct manconf * conf,const char * file,int use_path_from_file)154 manconf_file(struct manconf *conf, const char *file, int use_path_from_file)
155 {
156 const char *const toks[] = { "manpath", "output" };
157 char manpath_default[] = MANPATH_DEFAULT;
158
159 FILE *stream;
160 char *line, *cp, *ep;
161 size_t linesz, tok, toklen;
162 ssize_t linelen;
163
164 if ((stream = fopen(file, "r")) == NULL)
165 goto out;
166
167 line = NULL;
168 linesz = 0;
169
170 while ((linelen = getline(&line, &linesz, stream)) != -1) {
171 cp = line;
172 ep = cp + linelen - 1;
173 while (ep > cp && isspace((unsigned char)*ep))
174 *ep-- = '\0';
175 while (isspace((unsigned char)*cp))
176 cp++;
177 if (cp == ep || *cp == '#')
178 continue;
179
180 for (tok = 0; tok < sizeof(toks)/sizeof(toks[0]); tok++) {
181 toklen = strlen(toks[tok]);
182 if (cp + toklen < ep &&
183 isspace((unsigned char)cp[toklen]) &&
184 strncmp(cp, toks[tok], toklen) == 0) {
185 cp += toklen;
186 while (isspace((unsigned char)*cp))
187 cp++;
188 break;
189 }
190 }
191
192 switch (tok) {
193 case 0: /* manpath */
194 if (use_path_from_file)
195 manpath_add(&conf->manpath, cp, '\0');
196 *manpath_default = '\0';
197 break;
198 case 1: /* output */
199 manconf_output(&conf->output, cp, 1);
200 break;
201 default:
202 break;
203 }
204 }
205 free(line);
206 fclose(stream);
207
208 out:
209 if (use_path_from_file && *manpath_default != '\0')
210 manpath_parseline(&conf->manpath, manpath_default, '\0');
211 }
212
213 int
manconf_output(struct manoutput * conf,const char * cp,int fromfile)214 manconf_output(struct manoutput *conf, const char *cp, int fromfile)
215 {
216 const char *const toks[] = {
217 /* Tokens requiring an argument. */
218 "includes", "man", "paper", "style", "indent", "width",
219 "outfilename", "tagfilename",
220 /* Token taking an optional argument. */
221 "tag",
222 /* Tokens not taking arguments. */
223 "fragment", "mdoc", "noval", "toc"
224 };
225 const size_t ntoks = sizeof(toks) / sizeof(toks[0]);
226
227 const char *errstr;
228 char *oldval;
229 size_t len, tok;
230
231 for (tok = 0; tok < ntoks; tok++) {
232 len = strlen(toks[tok]);
233 if (strncmp(cp, toks[tok], len) == 0 &&
234 strchr(" = ", cp[len]) != NULL) {
235 cp += len;
236 if (*cp == '=')
237 cp++;
238 while (isspace((unsigned char)*cp))
239 cp++;
240 break;
241 }
242 }
243
244 if (tok < 8 && *cp == '\0') {
245 mandoc_msg(MANDOCERR_BADVAL_MISS, 0, 0, "-O %s=?", toks[tok]);
246 return -1;
247 }
248 if (tok > 8 && tok < ntoks && *cp != '\0') {
249 mandoc_msg(MANDOCERR_BADVAL, 0, 0, "-O %s=%s", toks[tok], cp);
250 return -1;
251 }
252
253 switch (tok) {
254 case 0:
255 if (conf->includes != NULL) {
256 oldval = mandoc_strdup(conf->includes);
257 break;
258 }
259 conf->includes = mandoc_strdup(cp);
260 return 0;
261 case 1:
262 if (conf->man != NULL) {
263 oldval = mandoc_strdup(conf->man);
264 break;
265 }
266 conf->man = mandoc_strdup(cp);
267 return 0;
268 case 2:
269 if (conf->paper != NULL) {
270 oldval = mandoc_strdup(conf->paper);
271 break;
272 }
273 conf->paper = mandoc_strdup(cp);
274 return 0;
275 case 3:
276 if (conf->style != NULL) {
277 oldval = mandoc_strdup(conf->style);
278 break;
279 }
280 conf->style = mandoc_strdup(cp);
281 return 0;
282 case 4:
283 if (conf->indent) {
284 mandoc_asprintf(&oldval, "%zu", conf->indent);
285 break;
286 }
287 conf->indent = strtonum(cp, 0, 1000, &errstr);
288 if (errstr == NULL)
289 return 0;
290 mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
291 "-O indent=%s is %s", cp, errstr);
292 return -1;
293 case 5:
294 if (conf->width) {
295 mandoc_asprintf(&oldval, "%zu", conf->width);
296 break;
297 }
298 conf->width = strtonum(cp, 1, 1000, &errstr);
299 if (errstr == NULL)
300 return 0;
301 mandoc_msg(MANDOCERR_BADVAL_BAD, 0, 0,
302 "-O width=%s is %s", cp, errstr);
303 return -1;
304 case 6:
305 if (conf->outfilename != NULL) {
306 oldval = mandoc_strdup(conf->outfilename);
307 break;
308 }
309 conf->outfilename = mandoc_strdup(cp);
310 return 0;
311 case 7:
312 if (conf->tagfilename != NULL) {
313 oldval = mandoc_strdup(conf->tagfilename);
314 break;
315 }
316 conf->tagfilename = mandoc_strdup(cp);
317 return 0;
318 /*
319 * If the index of the following token changes,
320 * do not forget to adjust the range check above the switch.
321 */
322 case 8:
323 if (conf->tag != NULL) {
324 oldval = mandoc_strdup(conf->tag);
325 break;
326 }
327 conf->tag = mandoc_strdup(cp);
328 return 0;
329 case 9:
330 conf->fragment = 1;
331 return 0;
332 case 10:
333 conf->mdoc = 1;
334 return 0;
335 case 11:
336 conf->noval = 1;
337 return 0;
338 case 12:
339 conf->toc = 1;
340 return 0;
341 default:
342 mandoc_msg(MANDOCERR_BADARG_BAD, 0, 0, "-O %s", cp);
343 return -1;
344 }
345 if (fromfile) {
346 free(oldval);
347 return 0;
348 } else {
349 mandoc_msg(MANDOCERR_BADVAL_DUPE, 0, 0,
350 "-O %s=%s: already set to %s", toks[tok], cp, oldval);
351 free(oldval);
352 return -1;
353 }
354 }
355