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 $ */
5 * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
6 * Copyright (c) 2000, 2001, 2002 HÃ¥kan Olsson. All rights reserved.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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.
29 #include <sys/types.h>
30 #include <sys/queue.h>
42 #include "got_error.h"
44 #include "got_lib_gitconfig.h"
47 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
52 #ifdef GITCONFIG_DEBUG
53 #define LOG_DBG(x) log_debug x
58 #define log_print printf
59 #define log_error printf
61 #ifdef GITCONFIG_DEBUG
63 log_debug(int cls, int level, const char *fmt, ...)
68 vfprintf(stderr, fmt, ap);
74 struct got_gitconfig_trans {
75 TAILQ_ENTRY(got_gitconfig_trans) link;
77 enum got_gitconfig_op {
78 CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
87 TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
89 struct got_gitconfig_binding {
90 LIST_ENTRY(got_gitconfig_binding) link;
97 LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding);
99 struct got_gitconfig {
100 struct got_gitconfig_bindings bindings[256];
101 struct got_gitconfig_trans_head trans_queue;
106 static __inline__ u_int8_t
107 conf_hash(const char *s)
112 hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
119 * Insert a tag-value combination from LINE (the equal sign is at POS)
122 conf_remove_now(struct got_gitconfig *conf, char *section, char *tag)
124 struct got_gitconfig_binding *cb, *next;
126 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
128 next = LIST_NEXT(cb, link);
129 if (strcasecmp(cb->section, section) == 0 &&
130 strcasecmp(cb->tag, tag) == 0) {
131 LIST_REMOVE(cb, link);
132 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
145 conf_remove_section_now(struct got_gitconfig *conf, char *section)
147 struct got_gitconfig_binding *cb, *next;
150 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
152 next = LIST_NEXT(cb, link);
153 if (strcasecmp(cb->section, section) == 0) {
155 LIST_REMOVE(cb, link);
156 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
157 cb->tag, cb->value));
168 * Insert a tag-value combination from LINE (the equal sign is at POS)
169 * into SECTION of our configuration database.
172 conf_set_now(struct got_gitconfig *conf, char *section, char *tag,
173 char *value, int override, int is_default)
175 struct got_gitconfig_binding *node = 0;
178 conf_remove_now(conf, section, tag);
179 else if (got_gitconfig_get_str(conf, section, tag)) {
181 LOG_DBG((LOG_MISC, 95,
182 "conf_set_now: duplicate tag [%s]:%s, "
183 "ignoring...", section, tag));
186 node = calloc(1, sizeof *node);
188 log_error("conf_set_now: calloc (1, %lu) failed",
189 (unsigned long)sizeof *node);
192 node->section = node->tag = node->value = NULL;
193 if ((node->section = strdup(section)) == NULL)
195 if ((node->tag = strdup(tag)) == NULL)
197 if ((node->value = strdup(value)) == NULL)
199 node->is_default = is_default;
201 LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link);
202 LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section,
203 node->tag, node->value));
214 * Parse the line LINE of SZ bytes. Skip Comments, recognize section
215 * headers and feed tag-value pairs into our configuration database.
217 static const struct got_error *
218 conf_parse_line(char **section, struct got_gitconfig *conf, int trans,
219 char *line, int ln, size_t sz)
225 /* Lines starting with '#' or ';' are comments. */
226 if (*line == '#' || *line == ';')
229 /* '[section]' parsing... */
231 for (i = 1; i < sz; i++)
236 log_print("conf_parse_line: %d:"
237 "unmatched ']', ignoring until next section", ln);
241 *section = strndup(line + 1, i - 1);
242 if (*section == NULL)
243 return got_error_from_errno("strndup");
246 while (isspace((unsigned char)*line))
249 /* Deal with assignments. */
250 for (i = 0; i < sz; i++)
251 if (line[i] == '=') {
252 /* If no section, we are ignoring the lines. */
254 log_print("conf_parse_line: %d: ignoring line "
255 "due to no section", ln);
258 line[strcspn(line, " \t=")] = '\0';
259 val = line + i + 1 + strspn(line + i + 1, " \t");
260 /* Skip trailing whitespace, if any */
261 for (j = sz - (val - line) - 1; j > 0 &&
262 isspace((unsigned char)val[j]); j--)
264 /* XXX Perhaps should we not ignore errors? */
265 got_gitconfig_set(conf, trans, *section, line, val,
269 /* Other non-empty lines are weird. */
270 i = strspn(line, " \t");
272 log_print("conf_parse_line: %d: syntax error", ln);
277 /* Parse the mapped configuration file. */
278 static const struct got_error *
279 conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
281 const struct got_error *err = NULL;
283 char *bufend = buf + sz;
284 char *line, *section = NULL;
288 while (cp < bufend) {
290 /* Check for escaped newlines. */
291 if (cp > buf && *(cp - 1) == '\\')
292 *(cp - 1) = *cp = ' ';
295 err = conf_parse_line(§ion, conf, trans,
296 line, ln, cp - line);
306 log_print("conf_parse: last line unterminated, ignored.");
310 const struct got_error *
311 got_gitconfig_open(struct got_gitconfig **conf, int fd)
315 *conf = calloc(1, sizeof(**conf));
317 return got_error_from_errno("malloc");
319 for (i = 0; i < nitems((*conf)->bindings); i++)
320 LIST_INIT(&(*conf)->bindings[i]);
321 TAILQ_INIT(&(*conf)->trans_queue);
322 return got_gitconfig_reinit(*conf, fd);
326 conf_clear(struct got_gitconfig *conf)
328 struct got_gitconfig_binding *cb;
332 for (i = 0; i < nitems(conf->bindings); i++)
333 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
334 cb = LIST_FIRST(&conf->bindings[i]))
335 conf_remove_now(conf, cb->section, cb->tag);
341 /* Execute all queued operations for this transaction. Cleanup. */
343 conf_end(struct got_gitconfig *conf, int transaction, int commit)
345 struct got_gitconfig_trans *node, *next;
347 for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
348 next = TAILQ_NEXT(node, link);
349 if (node->trans == transaction) {
353 conf_set_now(conf, node->section,
354 node->tag, node->value,
355 node->override, node->is_default);
358 conf_remove_now(conf, node->section,
361 case CONF_REMOVE_SECTION:
362 conf_remove_section_now(conf, node->section);
365 log_print("got_gitconfig_end: unknown "
366 "operation: %d", node->op);
368 TAILQ_REMOVE(&conf->trans_queue, node, link);
380 got_gitconfig_close(struct got_gitconfig *conf)
387 conf_begin(struct got_gitconfig *conf)
392 /* Open the config file and map it into our address space, then parse it. */
393 const struct got_error *
394 got_gitconfig_reinit(struct got_gitconfig *conf, int fd)
396 const struct got_error *err = NULL;
399 char *new_conf_addr = 0;
402 if (fstat(fd, &st)) {
403 err = got_error_from_errno("fstat");
408 new_conf_addr = malloc(sz);
409 if (new_conf_addr == NULL) {
410 err = got_error_from_errno("malloc");
413 /* XXX I assume short reads won't happen here. */
414 if (read(fd, new_conf_addr, sz) != (int)sz) {
415 err = got_error_from_errno("read");
419 trans = conf_begin(conf);
421 err = conf_parse(conf, trans, new_conf_addr, sz);
425 /* Free potential existing configuration. */
427 conf_end(conf, trans, 1);
428 conf->addr = new_conf_addr;
437 * Return the numeric value denoted by TAG in section SECTION or DEF
438 * if that tag does not exist.
441 got_gitconfig_get_num(struct got_gitconfig *conf, const char *section,
442 const char *tag, int def)
444 char *value = got_gitconfig_get_str(conf, section, tag);
451 /* Validate X according to the range denoted by TAG in section SECTION. */
453 got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
456 char *value = got_gitconfig_get_str(conf, section, tag);
457 int val, min, max, n;
461 n = sscanf(value, "%d,%d:%d", &val, &min, &max);
464 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
465 section, tag, val, x));
468 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
469 section, tag, min, x, max));
470 return min <= x && max >= x;
472 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
473 "spec %s", section, tag, value);
478 /* Return the string value denoted by TAG in section SECTION. */
480 got_gitconfig_get_str(struct got_gitconfig *conf, const char *section,
483 struct got_gitconfig_binding *cb;
485 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
486 cb = LIST_NEXT(cb, link))
487 if (strcasecmp(section, cb->section) == 0 &&
488 strcasecmp(tag, cb->tag) == 0) {
489 LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
490 section, tag, cb->value));
493 LOG_DBG((LOG_MISC, 95,
494 "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
499 const struct got_error *
500 got_gitconfig_get_section_list(struct got_gitconfig_list **sections,
501 struct got_gitconfig *conf)
503 const struct got_error *err = NULL;
504 struct got_gitconfig_list *list = NULL;
505 struct got_gitconfig_list_node *node = 0;
506 struct got_gitconfig_binding *cb;
511 list = malloc(sizeof *list);
513 return got_error_from_errno("malloc");
514 TAILQ_INIT(&list->fields);
516 for (i = 0; i < nitems(conf->bindings); i++) {
517 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
518 cb = LIST_NEXT(cb, link)) {
519 int section_present = 0;
520 TAILQ_FOREACH(node, &list->fields, link) {
521 if (strcmp(node->field, cb->section) == 0) {
529 node = calloc(1, sizeof *node);
531 err = got_error_from_errno("calloc");
534 node->field = strdup(cb->section);
536 err = got_error_from_errno("strdup");
539 TAILQ_INSERT_TAIL(&list->fields, node, link);
549 got_gitconfig_free_list(list);
554 * Build a list of string values out of the comma separated value denoted by
557 struct got_gitconfig_list *
558 got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
560 char *liststr = 0, *p, *field, *t;
561 struct got_gitconfig_list *list = 0;
562 struct got_gitconfig_list_node *node = 0;
564 list = malloc(sizeof *list);
567 TAILQ_INIT(&list->fields);
569 liststr = got_gitconfig_get_str(conf, section, tag);
572 liststr = strdup(liststr);
576 while ((field = strsep(&p, ",")) != NULL) {
577 /* Skip leading whitespace */
578 while (isspace((unsigned char)*field))
580 /* Skip trailing whitespace */
582 for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
584 if (*field == '\0') {
585 log_print("got_gitconfig_get_list: empty field, ignoring...");
589 node = calloc(1, sizeof *node);
592 node->field = strdup(field);
595 TAILQ_INSERT_TAIL(&list->fields, node, link);
603 got_gitconfig_free_list(list);
608 struct got_gitconfig_list *
609 got_gitconfig_get_tag_list(struct got_gitconfig *conf, const char *section)
611 struct got_gitconfig_list *list = 0;
612 struct got_gitconfig_list_node *node = 0;
613 struct got_gitconfig_binding *cb;
615 list = malloc(sizeof *list);
618 TAILQ_INIT(&list->fields);
620 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
621 cb = LIST_NEXT(cb, link))
622 if (strcasecmp(section, cb->section) == 0) {
624 node = calloc(1, sizeof *node);
627 node->field = strdup(cb->tag);
630 TAILQ_INSERT_TAIL(&list->fields, node, link);
637 got_gitconfig_free_list(list);
642 got_gitconfig_free_list(struct got_gitconfig_list *list)
644 struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
647 TAILQ_REMOVE(&list->fields, node, link);
650 node = TAILQ_FIRST(&list->fields);
656 got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
657 enum got_gitconfig_op op, char *section, char *tag, char *value,
658 int override, int is_default)
660 struct got_gitconfig_trans *node;
662 node = calloc(1, sizeof *node);
664 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
665 (unsigned long)sizeof *node);
668 node->trans = transaction;
670 node->override = override;
671 node->is_default = is_default;
672 if (section && (node->section = strdup(section)) == NULL)
674 if (tag && (node->tag = strdup(tag)) == NULL)
676 if (value && (node->value = strdup(value)) == NULL)
678 TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
689 /* Queue a set operation. */
691 got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
692 char *tag, char *value, int override, int is_default)
694 return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
695 tag, value, override, is_default);
698 /* Queue a remove operation. */
700 got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
701 char *section, char *tag)
703 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
704 section, tag, NULL, 0, 0);
707 /* Queue a remove section operation. */
709 got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
712 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
713 section, NULL, NULL, 0, 0);