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_compat.h"
44 #include "got_error.h"
46 #include "got_lib_gitconfig.h"
48 #ifndef nitems
49 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
50 #endif
52 #define LOG_MISC 0
53 #define LOG_REPORT 1
54 #ifdef GITCONFIG_DEBUG
55 #define LOG_DBG(x) log_debug x
56 #else
57 #define LOG_DBG(x)
58 #endif
60 #define log_print(...) fprintf(stderr, __VA_ARGS__)
61 #define log_error(...) fprintf(stderr, __VA_ARGS__)
63 #ifdef GITCONFIG_DEBUG
64 static void
65 log_debug(int cls, int level, const char *fmt, ...)
66 {
67 va_list ap;
69 va_start(ap, fmt);
70 vfprintf(stderr, fmt, ap);
71 va_end(ap);
72 putc('\n', stderr);
73 }
74 #endif
76 struct got_gitconfig_trans {
77 TAILQ_ENTRY(got_gitconfig_trans) link;
78 int trans;
79 enum got_gitconfig_op {
80 CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
81 } op;
82 char *section;
83 char *tag;
84 char *value;
85 int override;
86 int is_default;
87 };
89 TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
91 struct got_gitconfig_binding {
92 LIST_ENTRY(got_gitconfig_binding) link;
93 char *section;
94 char *tag;
95 char *value;
96 int is_default;
97 };
99 LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding);
101 struct got_gitconfig {
102 struct got_gitconfig_bindings bindings[256];
103 struct got_gitconfig_trans_head trans_queue;
104 char *addr;
105 int seq;
106 };
108 static __inline__ u_int8_t
109 conf_hash(const char *s)
111 u_int8_t hash = 0;
113 while (*s) {
114 hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
115 s++;
117 return hash;
120 /*
121 * Insert a tag-value combination from LINE (the equal sign is at POS)
122 */
123 static int
124 conf_remove_now(struct got_gitconfig *conf, char *section, char *tag)
126 struct got_gitconfig_binding *cb, *next;
128 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
129 cb = next) {
130 next = LIST_NEXT(cb, link);
131 if (strcasecmp(cb->section, section) == 0 &&
132 strcasecmp(cb->tag, tag) == 0) {
133 LIST_REMOVE(cb, link);
134 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
135 tag, cb->value));
136 free(cb->section);
137 free(cb->tag);
138 free(cb->value);
139 free(cb);
140 return 0;
143 return 1;
146 static int
147 conf_remove_section_now(struct got_gitconfig *conf, char *section)
149 struct got_gitconfig_binding *cb, *next;
150 int unseen = 1;
152 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
153 cb = next) {
154 next = LIST_NEXT(cb, link);
155 if (strcasecmp(cb->section, section) == 0) {
156 unseen = 0;
157 LIST_REMOVE(cb, link);
158 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
159 cb->tag, cb->value));
160 free(cb->section);
161 free(cb->tag);
162 free(cb->value);
163 free(cb);
166 return unseen;
169 /*
170 * Insert a tag-value combination from LINE (the equal sign is at POS)
171 * into SECTION of our configuration database.
172 */
173 static int
174 conf_set_now(struct got_gitconfig *conf, char *section, char *tag,
175 char *value, int override, int is_default)
177 struct got_gitconfig_binding *node = 0;
179 if (override)
180 conf_remove_now(conf, section, tag);
181 else if (got_gitconfig_get_str(conf, section, tag)) {
182 if (!is_default)
183 LOG_DBG((LOG_MISC, 95,
184 "conf_set_now: duplicate tag [%s]:%s, "
185 "ignoring...", section, tag));
186 return 1;
188 node = calloc(1, sizeof *node);
189 if (!node) {
190 log_error("conf_set_now: calloc (1, %lu) failed",
191 (unsigned long)sizeof *node);
192 return 1;
194 node->section = node->tag = node->value = NULL;
195 if ((node->section = strdup(section)) == NULL)
196 goto fail;
197 if ((node->tag = strdup(tag)) == NULL)
198 goto fail;
199 if ((node->value = strdup(value)) == NULL)
200 goto fail;
201 node->is_default = is_default;
203 LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link);
204 LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section,
205 node->tag, node->value));
206 return 0;
207 fail:
208 free(node->value);
209 free(node->tag);
210 free(node->section);
211 free(node);
212 return 1;
215 /*
216 * Parse the line LINE of SZ bytes. Skip Comments, recognize section
217 * headers and feed tag-value pairs into our configuration database.
218 */
219 static const struct got_error *
220 conf_parse_line(char **section, struct got_gitconfig *conf, int trans,
221 char *line, int ln, size_t sz)
223 char *val;
224 size_t i;
225 int j;
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 = strndup(line + 1, i - 1);
240 if (*section == NULL)
241 return got_error_from_errno("strndup");
242 return NULL;
244 while (sz > 0 && isspace((unsigned char)*line)) {
245 line++;
246 sz--;
249 /* Lines starting with '#' or ';' are comments. */
250 if (*line == '#' || *line == ';')
251 return NULL;
253 /* Deal with assignments. */
254 for (i = 0; i < sz; i++)
255 if (line[i] == '=') {
256 /* If no section, we are ignoring the lines. */
257 if (!*section) {
258 log_print("conf_parse_line: %d: ignoring line "
259 "due to no section", ln);
260 return NULL;
262 line[strcspn(line, " \t=")] = '\0';
263 val = line + i + 1 + strspn(line + i + 1, " \t");
264 /* Skip trailing whitespace, if any */
265 for (j = sz - (val - line) - 1; j > 0 &&
266 isspace((unsigned char)val[j]); j--)
267 val[j] = '\0';
268 /* XXX Perhaps should we not ignore errors? */
269 got_gitconfig_set(conf, trans, *section, line, val,
270 0, 0);
271 return NULL;
273 /* Other non-empty lines are weird. */
274 i = strspn(line, " \t");
275 if (line[i])
276 log_print("conf_parse_line: %d: syntax error", ln);
278 return NULL;
281 /* Parse the mapped configuration file. */
282 static const struct got_error *
283 conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
285 const struct got_error *err = NULL;
286 char *cp = buf;
287 char *bufend = buf + sz;
288 char *line, *section = NULL;
289 int ln = 1;
291 line = cp;
292 while (cp < bufend) {
293 if (*cp == '\n') {
294 /* Check for escaped newlines. */
295 if (cp > buf && *(cp - 1) == '\\')
296 *(cp - 1) = *cp = ' ';
297 else {
298 *cp = '\0';
299 err = conf_parse_line(&section, conf, trans,
300 line, ln, cp - line);
301 if (err)
302 return err;
303 line = cp + 1;
305 ln++;
307 cp++;
309 if (cp != line)
310 log_print("conf_parse: last line unterminated, ignored.");
311 return NULL;
314 const struct got_error *
315 got_gitconfig_open(struct got_gitconfig **conf, int fd)
317 size_t i;
319 *conf = calloc(1, sizeof(**conf));
320 if (*conf == NULL)
321 return got_error_from_errno("malloc");
323 for (i = 0; i < nitems((*conf)->bindings); i++)
324 LIST_INIT(&(*conf)->bindings[i]);
325 TAILQ_INIT(&(*conf)->trans_queue);
326 return got_gitconfig_reinit(*conf, fd);
329 static void
330 conf_clear(struct got_gitconfig *conf)
332 struct got_gitconfig_binding *cb;
333 size_t i;
335 if (conf->addr) {
336 for (i = 0; i < nitems(conf->bindings); i++)
337 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
338 cb = LIST_FIRST(&conf->bindings[i]))
339 conf_remove_now(conf, cb->section, cb->tag);
340 free(conf->addr);
341 conf->addr = NULL;
345 /* Execute all queued operations for this transaction. Cleanup. */
346 static int
347 conf_end(struct got_gitconfig *conf, int transaction, int commit)
349 struct got_gitconfig_trans *node, *next;
351 for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
352 next = TAILQ_NEXT(node, link);
353 if (node->trans == transaction) {
354 if (commit)
355 switch (node->op) {
356 case CONF_SET:
357 conf_set_now(conf, node->section,
358 node->tag, node->value,
359 node->override, node->is_default);
360 break;
361 case CONF_REMOVE:
362 conf_remove_now(conf, node->section,
363 node->tag);
364 break;
365 case CONF_REMOVE_SECTION:
366 conf_remove_section_now(conf, node->section);
367 break;
368 default:
369 log_print("got_gitconfig_end: unknown "
370 "operation: %d", node->op);
372 TAILQ_REMOVE(&conf->trans_queue, node, link);
373 free(node->section);
374 free(node->tag);
375 free(node->value);
376 free(node);
379 return 0;
383 void
384 got_gitconfig_close(struct got_gitconfig *conf)
386 conf_clear(conf);
387 free(conf);
390 static int
391 conf_begin(struct got_gitconfig *conf)
393 return ++conf->seq;
396 /* Open the config file and map it into our address space, then parse it. */
397 const struct got_error *
398 got_gitconfig_reinit(struct got_gitconfig *conf, int fd)
400 const struct got_error *err = NULL;
401 int trans;
402 size_t sz;
403 char *new_conf_addr = 0;
404 struct stat st;
406 if (fstat(fd, &st)) {
407 err = got_error_from_errno("fstat");
408 goto fail;
411 sz = st.st_size;
412 new_conf_addr = malloc(sz);
413 if (new_conf_addr == NULL) {
414 err = got_error_from_errno("malloc");
415 goto fail;
417 /* XXX I assume short reads won't happen here. */
418 if (read(fd, new_conf_addr, sz) != (int)sz) {
419 err = got_error_from_errno("read");
420 goto fail;
423 trans = conf_begin(conf);
425 err = conf_parse(conf, trans, new_conf_addr, sz);
426 if (err)
427 goto fail;
429 /* Free potential existing configuration. */
430 conf_clear(conf);
431 conf_end(conf, trans, 1);
432 conf->addr = new_conf_addr;
433 return NULL;
435 fail:
436 free(new_conf_addr);
437 return err;
440 /*
441 * Return the numeric value denoted by TAG in section SECTION or DEF
442 * if that tag does not exist.
443 */
444 int
445 got_gitconfig_get_num(struct got_gitconfig *conf, const char *section,
446 const char *tag, int def)
448 char *value = got_gitconfig_get_str(conf, section, tag);
450 if (value)
451 return atoi(value);
452 return def;
455 /* Validate X according to the range denoted by TAG in section SECTION. */
456 int
457 got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
458 int x)
460 char *value = got_gitconfig_get_str(conf, section, tag);
461 int val, min, max, n;
463 if (!value)
464 return 0;
465 n = sscanf(value, "%d,%d:%d", &val, &min, &max);
466 switch (n) {
467 case 1:
468 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
469 section, tag, val, x));
470 return x == val;
471 case 3:
472 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
473 section, tag, min, x, max));
474 return min <= x && max >= x;
475 default:
476 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
477 "spec %s", section, tag, value);
479 return 0;
482 /* Return the string value denoted by TAG in section SECTION. */
483 char *
484 got_gitconfig_get_str(struct got_gitconfig *conf, const char *section,
485 const char *tag)
487 struct got_gitconfig_binding *cb;
489 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
490 cb = LIST_NEXT(cb, link))
491 if (strcasecmp(section, cb->section) == 0 &&
492 strcasecmp(tag, cb->tag) == 0) {
493 LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
494 section, tag, cb->value));
495 return cb->value;
497 LOG_DBG((LOG_MISC, 95,
498 "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
499 tag));
500 return 0;
503 const struct got_error *
504 got_gitconfig_get_section_list(struct got_gitconfig_list **sections,
505 struct got_gitconfig *conf)
507 const struct got_error *err = NULL;
508 struct got_gitconfig_list *list = NULL;
509 struct got_gitconfig_list_node *node = 0;
510 struct got_gitconfig_binding *cb;
511 size_t i;
513 *sections = NULL;
515 list = malloc(sizeof *list);
516 if (!list)
517 return got_error_from_errno("malloc");
518 TAILQ_INIT(&list->fields);
519 list->cnt = 0;
520 for (i = 0; i < nitems(conf->bindings); i++) {
521 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
522 cb = LIST_NEXT(cb, link)) {
523 int section_present = 0;
524 TAILQ_FOREACH(node, &list->fields, link) {
525 if (strcmp(node->field, cb->section) == 0) {
526 section_present = 1;
527 break;
530 if (section_present)
531 continue;
532 list->cnt++;
533 node = calloc(1, sizeof *node);
534 if (!node) {
535 err = got_error_from_errno("calloc");
536 goto cleanup;
538 node->field = strdup(cb->section);
539 if (!node->field) {
540 err = got_error_from_errno("strdup");
541 goto cleanup;
543 TAILQ_INSERT_TAIL(&list->fields, node, link);
547 *sections = list;
548 return NULL;
550 cleanup:
551 free(node);
552 if (list)
553 got_gitconfig_free_list(list);
554 return err;
557 /*
558 * Build a list of string values out of the comma separated value denoted by
559 * TAG in SECTION.
560 */
561 struct got_gitconfig_list *
562 got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
564 char *liststr = 0, *p, *field, *t;
565 struct got_gitconfig_list *list = 0;
566 struct got_gitconfig_list_node *node = 0;
568 list = malloc(sizeof *list);
569 if (!list)
570 goto cleanup;
571 TAILQ_INIT(&list->fields);
572 list->cnt = 0;
573 liststr = got_gitconfig_get_str(conf, section, tag);
574 if (!liststr)
575 goto cleanup;
576 liststr = strdup(liststr);
577 if (!liststr)
578 goto cleanup;
579 p = liststr;
580 while ((field = strsep(&p, ",")) != NULL) {
581 /* Skip leading whitespace */
582 while (isspace((unsigned char)*field))
583 field++;
584 /* Skip trailing whitespace */
585 if (p)
586 for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
587 *t = '\0';
588 if (*field == '\0') {
589 log_print("got_gitconfig_get_list: empty field, ignoring...");
590 continue;
592 list->cnt++;
593 node = calloc(1, sizeof *node);
594 if (!node)
595 goto cleanup;
596 node->field = strdup(field);
597 if (!node->field)
598 goto cleanup;
599 TAILQ_INSERT_TAIL(&list->fields, node, link);
601 free(liststr);
602 return list;
604 cleanup:
605 free(node);
606 if (list)
607 got_gitconfig_free_list(list);
608 free(liststr);
609 return 0;
612 struct got_gitconfig_list *
613 got_gitconfig_get_tag_list(struct got_gitconfig *conf, const char *section)
615 struct got_gitconfig_list *list = 0;
616 struct got_gitconfig_list_node *node = 0;
617 struct got_gitconfig_binding *cb;
619 list = malloc(sizeof *list);
620 if (!list)
621 goto cleanup;
622 TAILQ_INIT(&list->fields);
623 list->cnt = 0;
624 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
625 cb = LIST_NEXT(cb, link))
626 if (strcasecmp(section, cb->section) == 0) {
627 list->cnt++;
628 node = calloc(1, sizeof *node);
629 if (!node)
630 goto cleanup;
631 node->field = strdup(cb->tag);
632 if (!node->field)
633 goto cleanup;
634 TAILQ_INSERT_TAIL(&list->fields, node, link);
636 return list;
638 cleanup:
639 free(node);
640 if (list)
641 got_gitconfig_free_list(list);
642 return 0;
645 void
646 got_gitconfig_free_list(struct got_gitconfig_list *list)
648 struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
650 while (node) {
651 TAILQ_REMOVE(&list->fields, node, link);
652 free(node->field);
653 free(node);
654 node = TAILQ_FIRST(&list->fields);
656 free(list);
659 static int
660 got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
661 enum got_gitconfig_op op, char *section, char *tag, char *value,
662 int override, int is_default)
664 struct got_gitconfig_trans *node;
666 node = calloc(1, sizeof *node);
667 if (!node) {
668 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
669 (unsigned long)sizeof *node);
670 return 1;
672 node->trans = transaction;
673 node->op = op;
674 node->override = override;
675 node->is_default = is_default;
676 if (section && (node->section = strdup(section)) == NULL)
677 goto fail;
678 if (tag && (node->tag = strdup(tag)) == NULL)
679 goto fail;
680 if (value && (node->value = strdup(value)) == NULL)
681 goto fail;
682 TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
683 return 0;
685 fail:
686 free(node->section);
687 free(node->tag);
688 free(node->value);
689 free(node);
690 return 1;
693 /* Queue a set operation. */
694 int
695 got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
696 char *tag, char *value, int override, int is_default)
698 return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
699 tag, value, override, is_default);
702 /* Queue a remove operation. */
703 int
704 got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
705 char *section, char *tag)
707 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
708 section, tag, NULL, 0, 0);
711 /* Queue a remove section operation. */
712 int
713 got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
714 char *section)
716 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
717 section, NULL, NULL, 0, 0);