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