1 /*-
2  * Copyright (c) 2016 Marcel Kaiser. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <stdarg.h>
28 #include <ctype.h>
29 #include <string.h>
30 #include <stdbool.h>
31 #include <limits.h>
32 #include <unistd.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <errno.h>
36 #include <err.h>
37 #include <pwd.h>
38 #include <grp.h>
39 #include "dsbcfg.h"
40 
41 static int	write_var(dsbcfg_var_t *, FILE *);
42 static int	parse_line(char *, dsbcfg_vardef_t *, int, dsbcfg_var_t *);
43 static int	var_set_defaults(dsbcfg_var_t *, dsbcfg_vardef_t *, int);
44 static int	open_cfg_file(const char *, const char *);
45 static bool	is_label(const char *);
46 static char	*readln(void);
47 static char	*cfgpath(const char *, const char *);
48 static char	*cutok(char *, bool *);
49 static char	*escape_str(const char *);
50 static char	*strdupstrcat(char **, char *);
51 static char	**add_string(char ***, const char *);
52 static void	close_cfg_file(void);
53 static void	seterr(int, const char *, ...);
54 static void	free_node(dsbcfg_t *);
55 static dsbcfg_t	*new_config_node(int);
56 
57 #define NERRCODES (sizeof(errtbl) / sizeof(errtbl[0]))
58 
59 static struct errtbl_s {
60 	int	   code;
61 	const char *msg;
foonull62 } errtbl[] = {
63 	{ DSBCFG_ERR_SYS_ERROR,    "Fatal error"		    },
64 	{ DSBCFG_ERR_INCOMPLETE,   "Incomplete escape sequence"     },
65 	{ DSBCFG_ERR_UNTERMINATED, "Unterminated quoted string"     },
66 	{ DSBCFG_ERR_MISSING_SEP,  "Missing '='"		    },
67 	{ DSBCFG_ERR_MISSING_VAL,  "Missing value"		    },
68 	{ DSBCFG_ERR_UNKNOWN_VAR,  "Unknown variable"		    },
69 	{ DSBCFG_ERR_PATH_EXCEED,  "Path name too long"		    },
70 	{ DSBCFG_ERR_DUPLICATED,   "Duplicated config file section" }
71 };
72 
73 static struct error_s {
74 	int  lineno;			 /* Current line number. */
75 	int  errcode;			 /* error code for error table. */
76 	int  _errno;			 /* Saved errno. */
77 	char prfx[1024];		 /* function name/message. */
78 	char *file;
79 } _error;
80 
81 static struct parser_s {
82 	int  lineno;			 /* Current line number. */
83 	int  bufsz;			 /* Total capacity of lnbuf */
84 	int  rd;			 /* # of bytes in lnbuf */
85 	int  slen;			 /* Length of next to return str. */
86 	bool needline;
87 	char file[_POSIX_PATH_MAX * 3];	 /* File name . */
88 	char *lnbuf;			 /* Line buffer for readln(). */
89 	char *pbuf;
90 	FILE *fp;
91 } parser;
92 
93 void
94 dsbcfg_printerr()
95 {
96 	unsigned int i;
97 
98 	if (_error.errcode == -1)
99 		return;
100 	for (i = 0; i < NERRCODES && errtbl[i].code != _error.errcode; i++)
101 		;
102 	if (i == NERRCODES)
103 		warnx("Unknown error code");
104 	else {
105 		if (_error.errcode & DSBCFG_ERR_SYNTAX_ERROR)
106 			(void)fprintf(stderr, "Syntax error: ");
107 		else if (_error.errcode == DSBCFG_ERR_SYS_ERROR &&
108 		    _error._errno != ENOENT)
109 			(void)fprintf(stderr, "Fatal: ");
110 		if (_error.prfx[0] != '\0')
111 			(void)fprintf(stderr, "%s: ", _error.prfx);
112 		(void)fprintf(stderr, "%s", errtbl[i].msg);
113 		if (_error.errcode == DSBCFG_ERR_SYS_ERROR) {
114 			(void)fprintf(stderr, ": %s\n",
115 			    strerror(_error._errno));
116 			errno = _error._errno;
117 			/* Not config file related, so return here. */
118 			return;
119 		} else if (_error.lineno > 0) {
120 			(void)fprintf(stderr, ", in file %s, line %d\n",
121 			    _error.file, _error.lineno);
122 		} else
123 			(void)fprintf(stderr, "\n");
124 	}
125 	errno = _error._errno;
126 }
127 
128 const char *
129 dsbcfg_strerror()
130 {
131 	unsigned int i;
132 	static char strbuf[1024 + sizeof(_error.prfx)], *p;
133 
134 	p = strbuf; *p = '\0';
135 	if (_error.errcode == -1)
136 		return (strbuf);
137 	for (i = 0; i < NERRCODES && errtbl[i].code != _error.errcode; i++)
138 		;
139 	if (i == NERRCODES)
140 		return (strncpy(strbuf, "Unknown error code", sizeof(strbuf)));
141 	else {
142 		if (_error.errcode & DSBCFG_ERR_SYNTAX_ERROR) {
143 			(void)snprintf(p, sizeof(strbuf) - strlen(p) - 1,
144 			    "Syntax error: ");
145 		} else if (_error.errcode == DSBCFG_ERR_SYS_ERROR &&
146 		    _error._errno != ENOENT) {
147 			(void)snprintf(p, sizeof(strbuf) - strlen(p) - 1,
148 			    "Fatal: ");
149 		}
150 		if (_error.prfx[0] != '\0') {
151 			(void)snprintf(p + strlen(p),
152 			    sizeof(strbuf) - strlen(p) - 1,
153 			    "%s: ", _error.prfx);
154 		}
155 		(void)snprintf(p + strlen(p), sizeof(strbuf) - strlen(p) - 1,
156 		    "%s", errtbl[i].msg);
157 		if (_error.errcode == DSBCFG_ERR_SYS_ERROR) {
158 			(void)snprintf(p + strlen(p),
159 			    sizeof(strbuf) - strlen(p) - 1, ": %s\n",
160 			    strerror(_error._errno));
161 			errno = _error._errno;
162 			/* Not config file related, so return here. */
163 			return (strbuf);
164 		} else if (_error.lineno > 0) {
165 			(void)snprintf(p + strlen(p),
166 			    sizeof(strbuf) - strlen(p) - 1,
167 			    ", in file %s, line %d\n", _error.file,
168 			    _error.lineno);
169 		} else
170 			(void)snprintf(p + strlen(p),
171 			    sizeof(strbuf) - strlen(p) - 1, "\n");
172 	}
173 	errno = _error._errno;
174 
175 	return (strbuf);
176 }
177 
178 static void
179 seterr(int errcode, const char *msg, ...)
180 {
181 	va_list ap;
182 
183 	_error.file    = parser.file;
184 	_error.lineno  = parser.lineno;
185 	_error._errno  = errno;
186 	_error.errcode = errcode;
187 	_error.prfx[0] = '\0';
188 
189 	if (msg != NULL) {
190 		va_start(ap, msg);
191 		(void)vsnprintf(_error.prfx, sizeof(_error.prfx), msg, ap);
192 	}
193 }
194 
195 /*
196  * Creates the dir ~/.config/DSB, and if 'dir' is not NULL, it creates
197  * ~/.config/DSB/dir. On success, dsbcfg_mkdir returns the complete path.
198  */
199 char *
200 dsbcfg_mkdir(const char *dir)
201 {
202 	int	      len;
203 	char	      *path, *p, *q;
204 	struct stat   sb;
205 	struct passwd *pw;
206 
207 	if ((pw = getpwuid(getuid())) == NULL) {
208 		seterr(DSBCFG_ERR_SYS_ERROR,
209 		    "Couldn't find you in the password file");
210 		return (NULL);
211         }
212 	endpwent();
213 
214 	len = sizeof(PATH_DSB_CFG_DIR) + strlen(pw->pw_dir) + 4;
215 	if (dir != NULL)
216 		len += strlen(dir);
217 	if ((path = malloc(len)) == NULL)
218 		return (NULL);
219 	(void)snprintf(path, len, "%s/", pw->pw_dir);
220 	for (p = PATH_DSB_CFG_DIR, q = path + strlen(path); *p != '\0';) {
221 		while (*p != '\0' && *p != '/')
222 			*q++ = *p++;
223 		if (*p == '/')
224 			p++;
225 		*q++ = '/'; *q = '\0';
226 		if (stat(path, &sb) == -1) {
227 			if (errno != ENOENT) {
228 				seterr(DSBCFG_ERR_SYS_ERROR, "stat()");
229 				return (NULL);
230 			}
231 			if (mkdir(path, S_IRWXU) == -1) {
232 				seterr(DSBCFG_ERR_SYS_ERROR, "mkdir()");
233 				return (NULL);
234 			}
235 		}
236 	}
237 	if (dir != NULL) {
238 		(void)strncat(path, dir, len - strlen(path) - 1);
239 		if (stat(path, &sb) == -1) {
240 			if (errno != ENOENT) {
241 				seterr(DSBCFG_ERR_SYS_ERROR, "stat()");
242 				return (NULL);
243 			}
244 			if (mkdir(path, S_IRWXU) == -1) {
245 				seterr(DSBCFG_ERR_SYS_ERROR, "mkdir()");
246 				return (NULL);
247 			}
248 		}
249 	} else
250 		*--q = '\0'; /* Remove trailing '/' */
251 	return (path);
252 }
253 
254 /*
255  * Returns the full path to config file. If 'file' begins with a '/' (full
256  * path), cfgpath() returns that path.
257  *
258  * If file is not a full path, cfgpath() returns
259  * <config base dir>/<subdir>/<file> if subdir != NULL, and
260  * <config base dir>/<file> if subdir == NULL.
261  */
262 static char *
263 cfgpath(const char *subdir, const char *file)
264 {
265 	struct passwd *pw;
266 
267 	if (*file != '/') {
268 		if ((pw = getpwuid(getuid())) == NULL) {
269 			seterr(DSBCFG_ERR_SYS_ERROR,
270 			    "Couldn't find you in the password file");
271 			return (NULL);
272 		}
273 		endpwent();
274 	}
275 	if (strlen(subdir != NULL ? subdir : "") + strlen(file) +
276 	    (*file != '/' ? strlen(PATH_DSB_CFG_DIR) : 0)       +
277 	    4 > sizeof(parser.file)) {
278 		seterr(DSBCFG_ERR_PATH_EXCEED, NULL);
279 		return (NULL);
280 	}
281 	if (*file != '/') {
282 		(void)snprintf(parser.file, sizeof(parser.file),
283 		    subdir != NULL ? "%s/%s/%s/%s" : "%s/%s%s/%s", pw->pw_dir,
284 		    PATH_DSB_CFG_DIR, subdir != NULL ? subdir : "", file);
285 	} else
286 		(void)strncpy(parser.file, file, sizeof(parser.file));
287 	return (parser.file);
288 }
289 
290 dsbcfg_t *
291 dsbcfg_read(const char *subdir, const char *file, dsbcfg_vardef_t *vardefs,
292 	    int nvardefs)
293 {
294 	char	 *ln;
295 	dsbcfg_t *cfg, *cp;
296 
297 	errno = 0; cfg = NULL;
298 	if (open_cfg_file(subdir, file) == -1)
299 		return (NULL);
300 	/*
301 	 * Get all global variables, that is, variables before
302 	 * the first labeled block.
303 	 */
304 	while ((ln = readln()) != NULL && !is_label(ln)) {
305 		while (isspace(*ln))
306 			ln++;
307 		if (!*ln || *ln == '#')
308 			continue;
309 		if (cfg == NULL) {
310 			cp = cfg = new_config_node(nvardefs);
311 			if (cfg == NULL)
312 				goto error;
313 			if (var_set_defaults(cp->vars, vardefs, nvardefs) == -1)
314 				return (NULL);
315 		}
316 		(void)strtok(ln, "\n");
317 		if (parse_line(ln, vardefs, nvardefs, cp->vars) == -1)
318 			goto error;
319 	}
320 	if (ln == NULL) {
321 		if (cfg == NULL) {
322 			/* Empty config file. */
323 			if ((cfg = new_config_node(nvardefs)) == NULL)
324 				goto error;
325 			if (var_set_defaults(cfg->vars, vardefs,
326 			    nvardefs) == -1)
327 				return (NULL);
328 		}
329  		close_cfg_file(); return (cfg);
330 	}
331 	for (; ln != NULL; ln = readln()) {
332 		if (is_label(ln)) {
333 			(void)strtok(ln, ":");
334 			if (cfg != NULL && dsbcfg_getnode(cfg, ln) != NULL) {
335 				seterr(DSBCFG_ERR_DUPLICATED,
336 				    "Section '%s'", ln);
337 				goto error;
338 			}
339 			if (cfg != NULL) {
340 				cp->next = new_config_node(nvardefs);
341 				if (cp->next == NULL)
342 					goto error;
343 				cp = cp->next;
344 			} else {
345 				if ((cfg = new_config_node(nvardefs)) == NULL)
346 					goto error;
347 				cp = cfg;
348 			}
349 			if ((cp->label = strdup(ln)) == NULL) {
350 				seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
351 				goto error;
352 			}
353 			if (var_set_defaults(cp->vars, vardefs, nvardefs) == -1)
354 				return (NULL);
355 		} else {
356 			(void)strtok(ln, "\n");
357 			if (parse_line(ln, vardefs, nvardefs, cp->vars) == -1)
358 				goto error;
359 		}
360 	}
361 	close_cfg_file();
362 	return (cfg);
363 error:
364 	dsbcfg_free(cfg); close_cfg_file();
365 	return (NULL);
366 }
367 
368 char **
369 dsbcfg_list_to_strings(const char *str, bool *error)
370 {
371 	char *buf, *p, **v;
372 
373 	*error = false;
374 	if (str == NULL)
375 		return (NULL);
376 	if ((buf = strdup(str)) == NULL) {
377 		seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
378 		return (NULL);
379 	}
380 	for (v = NULL, p = buf; (p = cutok(p, error)) != NULL; p = NULL) {
381 		if (add_string(&v, p) == NULL) {
382 			free(buf); free(v);
383 			return (NULL);
384 		}
385 	}
386 	free(buf);
387 	return (*error ? NULL : v);
388 }
389 
390 dsbcfg_t *
391 dsbcfg_new(const char *label, dsbcfg_vardef_t *vardefs, int nvardefs)
392 {
393 	dsbcfg_t *cfg;
394 
395 	if ((cfg = new_config_node(nvardefs)) == NULL)
396 		goto error;
397 	if (label != NULL) {
398 		if ((cfg->label = strdup(label)) == NULL) {
399 			seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
400 			goto error;
401 		}
402 	}
403 	if (var_set_defaults(cfg->vars, vardefs, nvardefs) == -1)
404 		goto error;
405 	return (cfg);
406 error:
407 	dsbcfg_free(cfg);
408 	return (NULL);
409 }
410 
411 void
412 dsbcfg_delnode(dsbcfg_t **cfg, const char *label)
413 {
414 	dsbcfg_t *cp, *prev;
415 
416 	if (label == NULL) {
417 		if ((*cfg)->label != NULL)
418 			return;
419 		cp = (*cfg)->next, free_node(*cfg), *cfg = cp;
420 		return;
421 	}
422 	for (cp = prev = *cfg; cp != NULL; cp = cp->next) {
423 		if (cp->label != NULL && !strcmp(cp->label, label)) {
424 			prev->next = cp->next, free_node(cp);
425 			return;
426 		} else
427 			prev = cp;
428 	}
429 }
430 
431 dsbcfg_t *
432 dsbcfg_getnode(dsbcfg_t *cfg, const char *label)
433 {
434 	for (; cfg != NULL; cfg = cfg->next) {
435 		if (label == NULL && cfg->label == NULL)
436 			return (cfg);
437 		if (cfg->label != NULL && !strcmp(cfg->label, label))
438 			return (cfg);
439 	}
440 	return (NULL);
441 }
442 
443 void
444 dsbcfg_free(dsbcfg_t *cfg)
445 {
446 	dsbcfg_t *cp, *next;
447 
448 	for (cp = cfg; cp != NULL; cp = next)
449 		next = cp->next, free_node(cp);
450 }
451 
452 static void
453 free_node(dsbcfg_t *node)
454 {
455 	int    i;
456 	char **pp;
457 
458 	free(node->label);
459 	for (i = 0; i < node->nvars && node->vars != NULL; i++) {
460 		if (node->vars[i].type == DSBCFG_VAR_STRINGS) {
461 			for (pp = node->vars[i].val.strings;
462 			     pp != NULL && *pp != NULL; pp++)
463 				free(*pp);
464 			free(node->vars[i].val.strings);
465 		} else if (node->vars[i].type == DSBCFG_VAR_STRING)
466 			free(node->vars[i].val.string);
467 	}
468 	free(node->vars); free(node);
469 }
470 
471 dsbcfg_t *
472 dsbcfg_addnode(dsbcfg_t *cfg, const char *label, dsbcfg_vardef_t *vardefs,
473 	       int ndefs)
474 {
475 	dsbcfg_t *cp;
476 
477 	if (dsbcfg_getnode(cfg, label) != NULL) {
478 		seterr(DSBCFG_ERR_DUPLICATED,
479 		    label == NULL ? "Global section%s" : "Section '%s'",
480 		    label == NULL ? "" : label);
481 		return (NULL);
482 	}
483 	for (cp = cfg; cp != NULL && cp->next != NULL; cp = cp->next)
484 		;
485 	if (cp == NULL)
486 		return (NULL);
487 	if ((cp->next = new_config_node(ndefs)) == NULL)
488 		return (NULL);
489 	cp = cp->next;
490 	if ((cp->label = strdup(label)) == NULL) {
491 		seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
492 		return (NULL);
493 	}
494 	if (var_set_defaults(cp->vars, vardefs, ndefs) == -1)
495 		return (NULL);
496 	return (cp);
497 }
498 
499 int
500 dsbcfg_setval(dsbcfg_t *node, int vid, dsbcfg_val_t val)
501 {
502 	char **pp;
503 
504 	if (node->vars[vid].type == DSBCFG_VAR_STRING) {
505 		free(node->vars[vid].val.string);
506 		if ((node->vars[vid].val.string = strdup(val.string)) == NULL) {
507 			seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
508 			return (-1);
509 		}
510 	} else if (node->vars[vid].type == DSBCFG_VAR_STRINGS) {
511 		for (pp = node->vars[vid].val.strings;
512 		     pp != NULL && *pp != NULL; pp++)
513 			free(*pp);
514 		free(node->vars[vid].val.strings);
515 		node->vars[vid].val.strings = NULL;
516 		for (pp = val.strings; pp != NULL && *pp != NULL; pp++) {
517 			if (add_string(&node->vars[vid].val.strings, *pp)
518 			    == NULL)
519 				return (-1);
520 		}
521 	} else if (node->vars[vid].type == DSBCFG_VAR_INTEGER) {
522 		node->vars[vid].val.integer = val.integer;
523 	} else if (node->vars[vid].type == DSBCFG_VAR_BOOLEAN) {
524 		node->vars[vid].val.boolean = val.boolean;
525 	}
526 	return (0);
527 }
528 
529 /*
530  * Extends the string vector at *strv by the given string and terminates
531  * the vector with a NULL-pointer.
532  *
533  * Returns the new string vector.
534  */
535 static char **
536 add_string(char ***strv, const char *str)
537 {
538         static int    n;
539         static char **p;
540 
541 	if (*strv == NULL)
542 		n = 0;
543 	else {
544 		for (p = *strv, n = 0; p[n] != NULL; n++)
545 			;
546 	}
547 	n += 2;
548 	if ((p = realloc(*strv, n * sizeof(char *))) == NULL)
549 		goto error;
550 	*strv = p;
551 	if ((p[n - 2] = strdup(str)) == NULL)
552 		goto error;
553 	p[n - 1] = NULL;
554 
555 	return (p);
556 error:
557 	seterr(DSBCFG_ERR_SYS_ERROR, "add_string()");
558 	for (p = *strv; p != NULL && *p != NULL; p++)
559 		free(*p);
560 	return (NULL);
561 }
562 
563 static char *
564 strdupstrcat(char **buf, char *str)
565 {
566 	char   *p;
567 	size_t len;
568 
569 	len = strlen(str) + 1;
570 	if (*buf != NULL)
571 		len += strlen(*buf);
572 	if ((p = realloc(*buf, len)) == NULL) {
573 		seterr(DSBCFG_ERR_SYS_ERROR, "realloc()");
574 		return (NULL);
575 	}
576 	*buf = p;
577 	return (strcat(p, str));
578 }
579 
580 static char *
581 escape_str(const char *str)
582 {
583 	char *p, *esc;
584 
585 	if ((esc = malloc(2 * strlen(str) + 1)) == NULL) {
586 		seterr(DSBCFG_ERR_SYS_ERROR, "malloc()");
587 		return (NULL);
588 	}
589 	for (p = esc; *str != '\0'; str++) {
590 		if (*str == '"' || *str == '\\')
591 			*p++ = '\\';
592 		*p++ = *str;
593 	}
594 	*p = '\0';
595 
596 	return (esc);
597 }
598 
599 /*
600  * Extracts the first (str != NULL) or the next (str == NULL) token from a
601  * comma  separated  list  of  (quoted) strings, while respecting the
602  * escape rules.
603  *
604  * Returns the start address of the token, or NULL if the string is empty,
605  * or a syntax error was found.
606  */
607 static char *
608 cutok(char *str, bool *error)
609 {
610 	static int  esc, quote;
611 	static char *p, *q, *start = NULL;
612 
613 	if (str != NULL) {
614 		free(parser.pbuf);
615 		parser.needline = false;
616 		parser.pbuf = start = strdup(str);
617 		if (parser.pbuf == NULL) {
618 			*error = true;
619 			seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
620 			return (NULL);
621 		}
622 	} else if (parser.needline) {
623 		parser.needline = false;
624 		if ((p = readln()) == NULL) {
625 			if (_error.errcode > 0) {
626 				*error = true; return (NULL);
627 			}
628 			/* Ignore '\' at the end of the last line. */
629 			return (cutok(NULL, error));
630 		}
631 		(void)strtok(p, "\n");
632 		if (*p == '\0')
633 			/* Empty line */
634 			return (cutok(NULL, error));
635 		/* Ignore trailing spaces and tabs. */
636 		for (q = p; isspace(*q); q++)
637 			;
638 		if ((start = strdupstrcat(&parser.pbuf, q)) == NULL) {
639 			*error = true; return (NULL);
640 		}
641 	}
642 	/* Check if line ends with a '\' */
643 	for (esc = 0, p = parser.pbuf; *p != '\0'; p++) {
644 		if (*p == '\\')
645 			esc ^= 1;
646 		else if (esc)
647 			esc ^= 1;
648 	}
649 	if (esc) {
650 		/* Current line ends with a '\'. Concatinate next line. */
651 		p[-1] = '\0';
652 		parser.needline = true;
653 		return (cutok(NULL, error));
654 	} else
655 		parser.needline = false;
656 	while (*start != '\0' && isspace(*start))
657 		start++;
658 	if (*start == '"') {
659 		start++; quote = 1;
660 	} else
661 		quote = 0;
662 	if (*start == '\0') {
663 		if (quote == 1) {
664 			seterr(DSBCFG_ERR_UNTERMINATED, NULL);
665 			*error = true;
666 		} else
667 			*error = false;
668 		return (NULL);
669 	}
670 	*error = true;
671 	for (p = str = start; *str != '\0'; str++) {
672 		if (*str == '\\') {
673 			*p++ = *++str;
674 		} else if (*str == '"') {
675 			quote ^= 1;
676 		} else if (!quote) {
677 			if (*str == '#') {
678 				*p = '\0'; p = start; start = str;
679 				return (p);
680 			} if (isspace(*str)) {
681 				continue;
682 			} else if (*str == ',') {
683 				*p++ = '\0'; p = start; start = str + 1;
684 				*error = false;
685 				return (p);
686 			} else
687 				*p++ = *str;
688 		} else
689 			*p++ = *str;
690 	}
691 	*p = '\0'; p = start; start = str;
692 	if (quote == 1) {
693 		seterr(DSBCFG_ERR_UNTERMINATED, NULL);
694 		return (NULL);
695 	}
696 	*error = false;
697 	return (p);
698 }
699 
700 static bool
701 is_label(const char *str)
702 {
703 	const char *p;
704 
705 	for (p = str; isspace(*p); p++)
706 		;
707 	if (*p == '#')
708 		return (false);
709 	if (!isspace(*str) && *str != ':') {
710 		if ((p = strchr(str, ':')) != NULL) {
711 			while (isspace(*(++p)))
712 				;
713 			if (*p == '\0' || *p == '#')
714 				/* Match. */
715 				return (true);
716 		}
717 	}
718 	/* No match. */
719         return (false);
720 }
721 
722 static char *
723 readln()
724 {
725 	int		 i, n;
726 	char		*p;
727 	struct parser_s *ps = &parser;
728 
729 	if (ps->lnbuf == NULL) {
730 		if ((ps->lnbuf = malloc(_POSIX2_LINE_MAX)) == NULL)
731 			return (NULL);
732 		ps->bufsz = _POSIX2_LINE_MAX;
733 	}
734 	n = 0;
735 	do {
736 		ps->rd += n;
737 		if (ps->slen > 0) {
738 			for (i = 0; i < ps->rd - ps->slen; i++)
739 				ps->lnbuf[i] = ps->lnbuf[i + ps->slen];
740 		}
741 		ps->rd  -= ps->slen;
742 		ps->slen = 0;
743 		for (i = 0; i < ps->rd && ps->lnbuf[i] != '\n'; i++)
744 			;
745 		if (i < ps->rd && ps->lnbuf[i] == '\n') {
746 			ps->slen = i + 1;
747 			if (ps->slen >= ps->bufsz - 1)
748 				ps->slen = ps->rd = 0;
749 			ps->lnbuf[i] = '\0'; ps->lineno++;
750 			return (ps->lnbuf);
751 		}
752 		if (ps->rd >= ps->bufsz - 1) {
753 			p = realloc(ps->lnbuf, ps->bufsz + _POSIX2_LINE_MAX);
754 			if (p == NULL) {
755 				seterr(DSBCFG_ERR_SYS_ERROR, "realloc()");
756 				return (NULL);
757 			}
758 			ps->lnbuf  = p;
759 			ps->bufsz += _POSIX2_LINE_MAX;
760 		}
761 	} while ((n = fread(ps->lnbuf + ps->rd, 1,
762 	    ps->bufsz - ps->rd - 1, ps->fp)) > 0);
763 	if (ferror(ps->fp)) {
764 		seterr(DSBCFG_ERR_SYS_ERROR, "fread()");
765 		return (NULL);
766 	}
767 	if (ps->rd > 0) {
768 		ps->lnbuf[ps->rd] = '\0';
769 		ps->slen = ps->rd = 0; ps->lineno++;
770 		return (ps->lnbuf);
771 	}
772 	ps->slen = ps->rd = 0;
773 
774 	return (NULL);
775 }
776 
777 static int
778 var_set_defaults(dsbcfg_var_t *vars, dsbcfg_vardef_t *vardefs, int nvardefs)
779 {
780 	int    i, id;
781 	char **pp;
782 
783 	for (i = 0; i < nvardefs; i++) {
784 		id = vardefs[i].id;
785 		vars[id].name = vardefs[i].name;
786 		vars[id].type = vardefs[i].type;
787 
788 		if (vardefs[i].type == DSBCFG_VAR_STRING &&
789 		    vardefs[i].dflt.string != NULL) {
790 			vars[id].val.string = strdup(vardefs[i].dflt.string);
791 			if (vars[id].val.string == NULL) {
792 				seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
793 				return (-1);
794 			}
795 		} else if (vardefs[i].type == DSBCFG_VAR_STRINGS) {
796 			for (pp = vardefs[i].dflt.strings;
797 			     pp != NULL && *pp != NULL; pp++) {
798 				if (add_string(&vars[id].val.strings, *pp)
799 				    == NULL)
800 					return (-1);
801 			}
802 		} else
803 			vars[id].val = vardefs[i].dflt;
804 	}
805 	return (0);
806 }
807 
808 static int
809 parse_line(char *str, dsbcfg_vardef_t *vardefs, int nvardefs,
810 	   dsbcfg_var_t *_vars)
811 {
812 	int   i, id;
813 	bool  error;
814 	char *var, *val, *p, **pp;
815 
816 	var = str + strspn(str, " \t\r\n");
817 	if (*var == '\0' || *var == '#')
818 		return (0);
819 	for (val = var + strcspn(var, " =\t");
820 	    *val != '=' && *val != '\0'; val++)
821 		*val = '\0';
822 	if (*val != '=') {
823 		seterr(DSBCFG_ERR_MISSING_SEP, NULL); return (-1);
824 	}
825 	*val++ = '\0'; val += strspn(val, " \t\n");
826 	for (i = 0; i < nvardefs; i++) {
827 		if (strcmp(var, vardefs[i].name) != 0)
828 			continue;
829 		id = vardefs[i].id;
830 		_vars[id].name = vardefs[i].name;
831 		_vars[id].type = vardefs[i].type;
832 
833 		switch (vardefs[i].type) {
834 		case DSBCFG_VAR_STRINGS:
835 			for (pp = _vars[id].val.strings;
836 			     pp != NULL && *pp != NULL; pp++)
837 				free(*pp), *pp = NULL;
838 			free(_vars[id].val.strings);
839 			_vars[id].val.strings = NULL;
840 			for (p = val; (p = cutok(p, &error)) != NULL;
841 			     p = NULL) {
842 				if (add_string(&_vars[id].val.strings, p)
843 				    == NULL)
844 					return (-1);
845 			}
846 			if (error)
847 				return (-1);
848 			break;
849 		case DSBCFG_VAR_STRING:
850 			if ((p = cutok(val, &error)) == NULL)
851 				return (-1);
852 			free(_vars[id].val.string);
853 			if ((_vars[id].val.string = strdup(p)) == NULL) {
854 				seterr(DSBCFG_ERR_SYS_ERROR, "strdup()");
855 				return (-1);
856 			}
857 			break;
858 		case DSBCFG_VAR_BOOLEAN:
859 			if ((p = cutok(val, &error)) == NULL)
860 				return (-1);
861 			if (strcasecmp(p, "false") == 0 ||
862 			    strcasecmp(p, "no") == 0	||
863 			    (isdigit(p[0]) && strtol(p, NULL, 10) == 0))
864 				_vars[id].val.boolean = false;
865 			else
866 				_vars[id].val.boolean = true;
867 			break;
868 		case DSBCFG_VAR_INTEGER:
869 			if ((p = cutok(val, &error)) == NULL)
870 				return (-1);
871 			_vars[id].val.integer = strtol(p, NULL, 10);
872 			break;
873 		}
874 		return (0);
875 	}
876 	seterr(DSBCFG_ERR_UNKNOWN_VAR, NULL);
877 	return (-1);
878 }
879 
880 static dsbcfg_t *
881 new_config_node(int nvars)
882 {
883 	dsbcfg_t *cp;
884 
885 	if ((cp = calloc(1, sizeof(dsbcfg_t))) == NULL) {
886 		seterr(DSBCFG_ERR_SYS_ERROR, "calloc()");
887 		return (NULL);
888 	}
889 	cp->nvars = nvars;
890 	cp->vars  = calloc(nvars, sizeof(dsbcfg_var_t));
891 	if (nvars > 0 && cp->vars == NULL) {
892 		seterr(DSBCFG_ERR_SYS_ERROR, "calloc()");
893 		return (NULL);
894 	}
895 	return (cp);
896 }
897 
898 static int
899 open_cfg_file(const char *subdir, const char *file)
900 {
901 	parser.fp     = NULL;
902 	parser.pbuf   = parser.lnbuf = NULL;
903 	parser.lineno = parser.bufsz = parser.rd = parser.slen = 0;
904 
905 	_error.errcode = _error._errno = -1;
906 
907 	if (cfgpath(subdir, file) == NULL)
908 		return (-1);
909 	if ((parser.fp = fopen(parser.file, "r+")) == NULL) {
910 		seterr(DSBCFG_ERR_SYS_ERROR, parser.file);
911 		return (-1);
912 	}
913 	return (0);
914 }
915 
916 static void
917 close_cfg_file()
918 {
919 	if (parser.fp != NULL)
920 		(void)fclose(parser.fp);
921 	free(parser.pbuf); free(parser.lnbuf);
922 	parser.fp     = NULL;
923 	parser.pbuf   = parser.lnbuf = NULL;
924 	parser.lineno = parser.bufsz = parser.rd = parser.slen = 0;
925 }
926 
927 int
928 dsbcfg_write(const char *subdir, const char *file, const dsbcfg_t *cfg)
929 {
930 	int   i, fd;
931 	FILE *fp, *tmpfp;
932 	char *path, tmpl[sizeof(parser.file) + 8];
933 
934 	fd = -1; fp = tmpfp = NULL; path = NULL;
935 	if (open_cfg_file(subdir, file) == -1) {
936 		if (errno != ENOENT)
937 			goto error;
938 		if ((path = dsbcfg_mkdir(subdir)) == NULL)
939 			return (-1);
940 		free(path);
941 		if ((path = cfgpath(subdir, file)) == NULL)
942 			goto error;
943 		if ((fp = fopen(path, "w+")) == NULL) {
944 			seterr(DSBCFG_ERR_SYS_ERROR,
945 			    "Couldn't create config file.");
946 			goto error;
947 		}
948 		(void)fclose(fp);
949 		if (open_cfg_file(subdir, file) == -1)
950 			goto error;
951 	}
952 	(void)strcpy(tmpl, parser.file); (void)strcat(tmpl, ".XXXXX");
953 
954 	if ((fd = mkstemp(tmpl)) == -1) {
955 		seterr(DSBCFG_ERR_SYS_ERROR, "mkstemp()");
956 		goto error;
957 	}
958 	if ((tmpfp = fdopen(fd, "r+")) == NULL) {
959 		seterr(DSBCFG_ERR_SYS_ERROR, "fdopen()");
960 		goto error;
961 	}
962 	for (; cfg != NULL; cfg = cfg->next) {
963 		if (cfg->label != NULL)
964 			(void)fprintf(tmpfp, "%s:\n", cfg->label);
965 		for (i = 0; i < cfg->nvars; i++)
966 			write_var(&cfg->vars[i], tmpfp);
967 		(void)fputs("\n", tmpfp);
968 	}
969 	close_cfg_file(); (void)fclose(tmpfp);
970 	rename(tmpl, parser.file);
971 	return (0);
972 error:
973 	if (tmpfp != NULL)
974 		(void)fclose(tmpfp);
975 	free(path); close_cfg_file();
976 	return (-1);
977 }
978 
979 static int
980 write_var(dsbcfg_var_t *var, FILE *fp)
981 {
982 	char *p, **s;
983 
984 	if (var == NULL)
985 		return (0);
986 
987 	switch (var->type) {
988 	case DSBCFG_VAR_STRING:
989 		if (var->val.string == NULL)
990 			return (0);
991 		if ((p = escape_str(var->val.string)) == NULL)
992 			return (-1);
993 		(void)fprintf(fp, "%s = \"%s\"\n", var->name, p);
994 		free(p);
995 		break;
996 	case DSBCFG_VAR_STRINGS:
997 		(void)fprintf(fp, "%s = ", var->name);
998 		for (s = var->val.strings;
999 		    s != NULL && *s != NULL; s++) {
1000 			if ((p = escape_str(*s)) == NULL)
1001 				return (-1);
1002 			(void)fprintf(fp, "\"%s\"", p);
1003 			if (s[1] != NULL)
1004 				(void)fputs(", ", fp);
1005 			free(p);
1006 		}
1007 		(void)fputc('\n', fp);
1008 		break;
1009 	case DSBCFG_VAR_INTEGER:
1010 		(void)fprintf(fp, "%s = %d\n", var->name, var->val.integer);
1011 		break;
1012 	case DSBCFG_VAR_BOOLEAN:
1013 		(void)fprintf(fp, "%s = %s\n", var->name,
1014 		    var->val.boolean ? "true" : "false");
1015 		break;
1016 	}
1017 	return (0);
1018 }
1019 
1020