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