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 
35 static    void       manconf_file(struct manconf *, const char *);
36 static    void       manpath_add(struct manpaths *, const char *, int);
37 static    void       manpath_parseline(struct manpaths *, char *, int);
38 
39 
40 void
manconf_parse(struct manconf * conf,const char * file,char * defp,char * auxp)41 manconf_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 
94 void
manpath_base(struct manpaths * dirs)95 manpath_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  */
104 static void
manpath_parseline(struct manpaths * dirs,char * path,int complain)105 manpath_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  */
120 static void
manpath_add(struct manpaths * dirs,const char * dir,int complain)121 manpath_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 
150 void
manconf_free(struct manconf * conf)151 manconf_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 
165 static void
manconf_file(struct manconf * conf,const char * file)166 manconf_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 
226 out:
227           if (*manpath_default != '\0')
228                     manpath_parseline(&conf->manpath, manpath_default, 0);
229 }
230 
231 int
manconf_output(struct manoutput * conf,const char * cp,int fromfile)232 manconf_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