Blob


1 /* $OpenBSD: conf.c,v 1.107 2017/10/27 08:29:32 mpi Exp $ */
2 /* $EOM: conf.c,v 1.48 2000/12/04 02:04:29 angelos Exp $ */
4 /*
5 * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
6 * Copyright (c) 2000, 2001, 2002 Håkan Olsson. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
29 #include <sys/types.h>
30 #include <sys/queue.h>
31 #include <sys/stat.h>
33 #include <ctype.h>
34 #include <fcntl.h>
35 #include <stdarg.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <errno.h>
42 #include "got_error.h"
44 #include "got_lib_gitconfig.h"
46 #ifndef nitems
47 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
48 #endif
50 #define LOG_MISC 0
51 #define LOG_REPORT 1
52 #ifdef GITCONFIG_DEBUG
53 #define LOG_DBG(x) log_debug x
54 #else
55 #define LOG_DBG(x)
56 #endif
58 #define log_print printf
59 #define log_error printf
61 #ifdef GITCONFIG_DEBUG
62 static void
63 log_debug(int cls, int level, const char *fmt, ...)
64 {
65 va_list ap;
67 va_start(ap, fmt);
68 vfprintf(stderr, fmt, ap);
69 va_end(ap);
70 }
71 #endif
73 struct got_gitconfig_trans {
74 TAILQ_ENTRY(got_gitconfig_trans) link;
75 int trans;
76 enum got_gitconfig_op {
77 CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
78 } op;
79 char *section;
80 char *tag;
81 char *value;
82 int override;
83 int is_default;
84 };
86 TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
88 struct got_gitconfig_binding {
89 LIST_ENTRY(got_gitconfig_binding) link;
90 char *section;
91 char *tag;
92 char *value;
93 int is_default;
94 };
96 LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding);
98 struct got_gitconfig {
99 struct got_gitconfig_bindings bindings[256];
100 struct got_gitconfig_trans_head trans_queue;
101 char *addr;
102 int seq;
103 };
105 static __inline__ u_int8_t
106 conf_hash(char *s)
108 u_int8_t hash = 0;
110 while (*s) {
111 hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
112 s++;
114 return hash;
117 /*
118 * Insert a tag-value combination from LINE (the equal sign is at POS)
119 */
120 static int
121 conf_remove_now(struct got_gitconfig *conf, char *section, char *tag)
123 struct got_gitconfig_binding *cb, *next;
125 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
126 cb = next) {
127 next = LIST_NEXT(cb, link);
128 if (strcasecmp(cb->section, section) == 0 &&
129 strcasecmp(cb->tag, tag) == 0) {
130 LIST_REMOVE(cb, link);
131 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
132 tag, cb->value));
133 free(cb->section);
134 free(cb->tag);
135 free(cb->value);
136 free(cb);
137 return 0;
140 return 1;
143 static int
144 conf_remove_section_now(struct got_gitconfig *conf, char *section)
146 struct got_gitconfig_binding *cb, *next;
147 int unseen = 1;
149 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
150 cb = next) {
151 next = LIST_NEXT(cb, link);
152 if (strcasecmp(cb->section, section) == 0) {
153 unseen = 0;
154 LIST_REMOVE(cb, link);
155 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
156 cb->tag, cb->value));
157 free(cb->section);
158 free(cb->tag);
159 free(cb->value);
160 free(cb);
163 return unseen;
166 /*
167 * Insert a tag-value combination from LINE (the equal sign is at POS)
168 * into SECTION of our configuration database.
169 */
170 static int
171 conf_set_now(struct got_gitconfig *conf, char *section, char *tag,
172 char *value, int override, int is_default)
174 struct got_gitconfig_binding *node = 0;
176 if (override)
177 conf_remove_now(conf, section, tag);
178 else if (got_gitconfig_get_str(conf, section, tag)) {
179 if (!is_default)
180 log_print("conf_set_now: duplicate tag [%s]:%s, "
181 "ignoring...\n", section, tag);
182 return 1;
184 node = calloc(1, sizeof *node);
185 if (!node) {
186 log_error("conf_set_now: calloc (1, %lu) failed",
187 (unsigned long)sizeof *node);
188 return 1;
190 node->section = node->tag = node->value = NULL;
191 if ((node->section = strdup(section)) == NULL)
192 goto fail;
193 if ((node->tag = strdup(tag)) == NULL)
194 goto fail;
195 if ((node->value = strdup(value)) == NULL)
196 goto fail;
197 node->is_default = is_default;
199 LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link);
200 LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section,
201 node->tag, node->value));
202 return 0;
203 fail:
204 free(node->value);
205 free(node->tag);
206 free(node->section);
207 free(node);
208 return 1;
211 /*
212 * Parse the line LINE of SZ bytes. Skip Comments, recognize section
213 * headers and feed tag-value pairs into our configuration database.
214 */
215 static const struct got_error *
216 conf_parse_line(char **section, struct got_gitconfig *conf, int trans,
217 char *line, int ln, size_t sz)
219 char *val;
220 size_t i;
221 int j;
223 /* Lines starting with '#' or ';' are comments. */
224 if (*line == '#' || *line == ';')
225 return NULL;
227 /* '[section]' parsing... */
228 if (*line == '[') {
229 for (i = 1; i < sz; i++)
230 if (line[i] == ']')
231 break;
232 free(*section);
233 if (i == sz) {
234 log_print("conf_parse_line: %d:"
235 "unmatched ']', ignoring until next section", ln);
236 *section = NULL;
237 return NULL;
239 *section = malloc(i);
240 if (*section == NULL)
241 return got_error_from_errno("malloc");
242 strlcpy(*section, line + 1, i);
243 return NULL;
245 while (isspace((unsigned char)*line))
246 line++;
248 /* Deal with assignments. */
249 for (i = 0; i < sz; i++)
250 if (line[i] == '=') {
251 /* If no section, we are ignoring the lines. */
252 if (!*section) {
253 log_print("conf_parse_line: %d: ignoring line "
254 "due to no section", ln);
255 return NULL;
257 line[strcspn(line, " \t=")] = '\0';
258 val = line + i + 1 + strspn(line + i + 1, " \t");
259 /* Skip trailing whitespace, if any */
260 for (j = sz - (val - line) - 1; j > 0 &&
261 isspace((unsigned char)val[j]); j--)
262 val[j] = '\0';
263 /* XXX Perhaps should we not ignore errors? */
264 got_gitconfig_set(conf, trans, *section, line, val,
265 0, 0);
266 return NULL;
268 /* Other non-empty lines are weird. */
269 i = strspn(line, " \t");
270 if (line[i])
271 log_print("conf_parse_line: %d: syntax error", ln);
273 return NULL;
276 /* Parse the mapped configuration file. */
277 static const struct got_error *
278 conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
280 const struct got_error *err = NULL;
281 char *cp = buf;
282 char *bufend = buf + sz;
283 char *line, *section = NULL;
284 int ln = 1;
286 line = cp;
287 while (cp < bufend) {
288 if (*cp == '\n') {
289 /* Check for escaped newlines. */
290 if (cp > buf && *(cp - 1) == '\\')
291 *(cp - 1) = *cp = ' ';
292 else {
293 *cp = '\0';
294 err = conf_parse_line(&section, conf, trans,
295 line, ln, cp - line);
296 if (err)
297 return err;
298 line = cp + 1;
300 ln++;
302 cp++;
304 if (cp != line)
305 log_print("conf_parse: last line unterminated, ignored.");
306 return NULL;
309 const struct got_error *
310 got_gitconfig_open(struct got_gitconfig **conf, int fd)
312 unsigned int i;
314 *conf = calloc(1, sizeof(**conf));
315 if (*conf == NULL)
316 return got_error_from_errno("malloc");
318 for (i = 0; i < nitems((*conf)->bindings); i++)
319 LIST_INIT(&(*conf)->bindings[i]);
320 TAILQ_INIT(&(*conf)->trans_queue);
321 return got_gitconfig_reinit(*conf, fd);
324 static void
325 conf_clear(struct got_gitconfig *conf)
327 struct got_gitconfig_binding *cb;
328 int i;
330 if (conf->addr) {
331 for (i = 0; i < nitems(conf->bindings); i++)
332 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
333 cb = LIST_FIRST(&conf->bindings[i]))
334 conf_remove_now(conf, cb->section, cb->tag);
335 free(conf->addr);
336 conf->addr = NULL;
340 /* Execute all queued operations for this transaction. Cleanup. */
341 static int
342 conf_end(struct got_gitconfig *conf, int transaction, int commit)
344 struct got_gitconfig_trans *node, *next;
346 for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
347 next = TAILQ_NEXT(node, link);
348 if (node->trans == transaction) {
349 if (commit)
350 switch (node->op) {
351 case CONF_SET:
352 conf_set_now(conf, node->section,
353 node->tag, node->value,
354 node->override, node->is_default);
355 break;
356 case CONF_REMOVE:
357 conf_remove_now(conf, node->section,
358 node->tag);
359 break;
360 case CONF_REMOVE_SECTION:
361 conf_remove_section_now(conf, node->section);
362 break;
363 default:
364 log_print("got_gitconfig_end: unknown "
365 "operation: %d", node->op);
367 TAILQ_REMOVE(&conf->trans_queue, node, link);
368 free(node->section);
369 free(node->tag);
370 free(node->value);
371 free(node);
374 return 0;
378 void
379 got_gitconfig_close(struct got_gitconfig *conf)
381 conf_clear(conf);
382 free(conf);
385 static int
386 conf_begin(struct got_gitconfig *conf)
388 return ++conf->seq;
391 /* Open the config file and map it into our address space, then parse it. */
392 const struct got_error *
393 got_gitconfig_reinit(struct got_gitconfig *conf, int fd)
395 const struct got_error *err = NULL;
396 int trans;
397 size_t sz;
398 char *new_conf_addr = 0;
399 struct stat st;
401 if (fstat(fd, &st)) {
402 err = got_error_from_errno("fstat");
403 goto fail;
406 sz = st.st_size;
407 new_conf_addr = malloc(sz);
408 if (new_conf_addr == NULL) {
409 err = got_error_from_errno("malloc");
410 goto fail;
412 /* XXX I assume short reads won't happen here. */
413 if (read(fd, new_conf_addr, sz) != (int)sz) {
414 err = got_error_from_errno("read");
415 goto fail;
418 trans = conf_begin(conf);
420 err = conf_parse(conf, trans, new_conf_addr, sz);
421 if (err)
422 goto fail;
424 /* Free potential existing configuration. */
425 conf_clear(conf);
426 conf_end(conf, trans, 1);
427 conf->addr = new_conf_addr;
428 return NULL;
430 fail:
431 free(new_conf_addr);
432 return err;
435 /*
436 * Return the numeric value denoted by TAG in section SECTION or DEF
437 * if that tag does not exist.
438 */
439 int
440 got_gitconfig_get_num(struct got_gitconfig *conf, char *section, char *tag,
441 int def)
443 char *value = got_gitconfig_get_str(conf, section, tag);
445 if (value)
446 return atoi(value);
447 return def;
450 /* Validate X according to the range denoted by TAG in section SECTION. */
451 int
452 got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
453 int x)
455 char *value = got_gitconfig_get_str(conf, section, tag);
456 int val, min, max, n;
458 if (!value)
459 return 0;
460 n = sscanf(value, "%d,%d:%d", &val, &min, &max);
461 switch (n) {
462 case 1:
463 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
464 section, tag, val, x));
465 return x == val;
466 case 3:
467 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
468 section, tag, min, x, max));
469 return min <= x && max >= x;
470 default:
471 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
472 "spec %s", section, tag, value);
474 return 0;
477 /* Return the string value denoted by TAG in section SECTION. */
478 char *
479 got_gitconfig_get_str(struct got_gitconfig *conf, char *section, char *tag)
481 struct got_gitconfig_binding *cb;
483 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
484 cb = LIST_NEXT(cb, link))
485 if (strcasecmp(section, cb->section) == 0 &&
486 strcasecmp(tag, cb->tag) == 0) {
487 LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
488 section, tag, cb->value));
489 return cb->value;
491 LOG_DBG((LOG_MISC, 95,
492 "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
493 tag));
494 return 0;
497 const struct got_error *
498 got_gitconfig_get_section_list(struct got_gitconfig_list **sections,
499 struct got_gitconfig *conf)
501 const struct got_error *err = NULL;
502 struct got_gitconfig_list *list = NULL;
503 struct got_gitconfig_list_node *node = 0;
504 struct got_gitconfig_binding *cb;
505 int i;
507 *sections = NULL;
509 list = malloc(sizeof *list);
510 if (!list)
511 return got_error_from_errno("malloc");
512 TAILQ_INIT(&list->fields);
513 list->cnt = 0;
514 for (i = 0; i < nitems(conf->bindings); i++) {
515 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
516 cb = LIST_NEXT(cb, link)) {
517 list->cnt++;
518 node = calloc(1, sizeof *node);
519 if (!node) {
520 err = got_error_from_errno("calloc");
521 goto cleanup;
523 node->field = strdup(cb->section);
524 if (!node->field) {
525 err = got_error_from_errno("strdup");
526 goto cleanup;
528 TAILQ_INSERT_TAIL(&list->fields, node, link);
532 *sections = list;
533 return NULL;
535 cleanup:
536 free(node);
537 if (list)
538 got_gitconfig_free_list(list);
539 return err;
542 /*
543 * Build a list of string values out of the comma separated value denoted by
544 * TAG in SECTION.
545 */
546 struct got_gitconfig_list *
547 got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
549 char *liststr = 0, *p, *field, *t;
550 struct got_gitconfig_list *list = 0;
551 struct got_gitconfig_list_node *node = 0;
553 list = malloc(sizeof *list);
554 if (!list)
555 goto cleanup;
556 TAILQ_INIT(&list->fields);
557 list->cnt = 0;
558 liststr = got_gitconfig_get_str(conf, section, tag);
559 if (!liststr)
560 goto cleanup;
561 liststr = strdup(liststr);
562 if (!liststr)
563 goto cleanup;
564 p = liststr;
565 while ((field = strsep(&p, ",")) != NULL) {
566 /* Skip leading whitespace */
567 while (isspace((unsigned char)*field))
568 field++;
569 /* Skip trailing whitespace */
570 if (p)
571 for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
572 *t = '\0';
573 if (*field == '\0') {
574 log_print("got_gitconfig_get_list: empty field, ignoring...");
575 continue;
577 list->cnt++;
578 node = calloc(1, sizeof *node);
579 if (!node)
580 goto cleanup;
581 node->field = strdup(field);
582 if (!node->field)
583 goto cleanup;
584 TAILQ_INSERT_TAIL(&list->fields, node, link);
586 free(liststr);
587 return list;
589 cleanup:
590 free(node);
591 if (list)
592 got_gitconfig_free_list(list);
593 free(liststr);
594 return 0;
597 struct got_gitconfig_list *
598 got_gitconfig_get_tag_list(struct got_gitconfig *conf, char *section)
600 struct got_gitconfig_list *list = 0;
601 struct got_gitconfig_list_node *node = 0;
602 struct got_gitconfig_binding *cb;
604 list = malloc(sizeof *list);
605 if (!list)
606 goto cleanup;
607 TAILQ_INIT(&list->fields);
608 list->cnt = 0;
609 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
610 cb = LIST_NEXT(cb, link))
611 if (strcasecmp(section, cb->section) == 0) {
612 list->cnt++;
613 node = calloc(1, sizeof *node);
614 if (!node)
615 goto cleanup;
616 node->field = strdup(cb->tag);
617 if (!node->field)
618 goto cleanup;
619 TAILQ_INSERT_TAIL(&list->fields, node, link);
621 return list;
623 cleanup:
624 free(node);
625 if (list)
626 got_gitconfig_free_list(list);
627 return 0;
630 void
631 got_gitconfig_free_list(struct got_gitconfig_list *list)
633 struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
635 while (node) {
636 TAILQ_REMOVE(&list->fields, node, link);
637 free(node->field);
638 free(node);
639 node = TAILQ_FIRST(&list->fields);
641 free(list);
644 static int
645 got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
646 enum got_gitconfig_op op, char *section, char *tag, char *value,
647 int override, int is_default)
649 struct got_gitconfig_trans *node;
651 node = calloc(1, sizeof *node);
652 if (!node) {
653 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
654 (unsigned long)sizeof *node);
655 return 1;
657 node->trans = transaction;
658 node->op = op;
659 node->override = override;
660 node->is_default = is_default;
661 if (section && (node->section = strdup(section)) == NULL)
662 goto fail;
663 if (tag && (node->tag = strdup(tag)) == NULL)
664 goto fail;
665 if (value && (node->value = strdup(value)) == NULL)
666 goto fail;
667 TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
668 return 0;
670 fail:
671 free(node->section);
672 free(node->tag);
673 free(node->value);
674 free(node);
675 return 1;
678 /* Queue a set operation. */
679 int
680 got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
681 char *tag, char *value, int override, int is_default)
683 return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
684 tag, value, override, is_default);
687 /* Queue a remove operation. */
688 int
689 got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
690 char *section, char *tag)
692 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
693 section, tag, NULL, 0, 0);
696 /* Queue a remove section operation. */
697 int
698 got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
699 char *section)
701 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
702 section, NULL, NULL, 0, 0);