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 printf
61 #define log_error printf
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 }
73 #endif
75 struct got_gitconfig_trans {
76 TAILQ_ENTRY(got_gitconfig_trans) link;
77 int trans;
78 enum got_gitconfig_op {
79 CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
80 } op;
81 char *section;
82 char *tag;
83 char *value;
84 int override;
85 int is_default;
86 };
88 TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
90 struct got_gitconfig_binding {
91 LIST_ENTRY(got_gitconfig_binding) link;
92 char *section;
93 char *tag;
94 char *value;
95 int is_default;
96 };
98 LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding);
100 struct got_gitconfig {
101 struct got_gitconfig_bindings bindings[256];
102 struct got_gitconfig_trans_head trans_queue;
103 char *addr;
104 int seq;
105 };
107 static __inline__ u_int8_t
108 conf_hash(const char *s)
110 u_int8_t hash = 0;
112 while (*s) {
113 hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
114 s++;
116 return hash;
119 /*
120 * Insert a tag-value combination from LINE (the equal sign is at POS)
121 */
122 static int
123 conf_remove_now(struct got_gitconfig *conf, char *section, char *tag)
125 struct got_gitconfig_binding *cb, *next;
127 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
128 cb = next) {
129 next = LIST_NEXT(cb, link);
130 if (strcasecmp(cb->section, section) == 0 &&
131 strcasecmp(cb->tag, tag) == 0) {
132 LIST_REMOVE(cb, link);
133 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
134 tag, cb->value));
135 free(cb->section);
136 free(cb->tag);
137 free(cb->value);
138 free(cb);
139 return 0;
142 return 1;
145 static int
146 conf_remove_section_now(struct got_gitconfig *conf, char *section)
148 struct got_gitconfig_binding *cb, *next;
149 int unseen = 1;
151 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
152 cb = next) {
153 next = LIST_NEXT(cb, link);
154 if (strcasecmp(cb->section, section) == 0) {
155 unseen = 0;
156 LIST_REMOVE(cb, link);
157 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
158 cb->tag, cb->value));
159 free(cb->section);
160 free(cb->tag);
161 free(cb->value);
162 free(cb);
165 return unseen;
168 /*
169 * Insert a tag-value combination from LINE (the equal sign is at POS)
170 * into SECTION of our configuration database.
171 */
172 static int
173 conf_set_now(struct got_gitconfig *conf, char *section, char *tag,
174 char *value, int override, int is_default)
176 struct got_gitconfig_binding *node = 0;
178 if (override)
179 conf_remove_now(conf, section, tag);
180 else if (got_gitconfig_get_str(conf, section, tag)) {
181 if (!is_default)
182 LOG_DBG((LOG_MISC,
183 "conf_set_now: duplicate tag [%s]:%s, "
184 "ignoring...\n", section, tag));
185 return 1;
187 node = calloc(1, sizeof *node);
188 if (!node) {
189 log_error("conf_set_now: calloc (1, %lu) failed",
190 (unsigned long)sizeof *node);
191 return 1;
193 node->section = node->tag = node->value = NULL;
194 if ((node->section = strdup(section)) == NULL)
195 goto fail;
196 if ((node->tag = strdup(tag)) == NULL)
197 goto fail;
198 if ((node->value = strdup(value)) == NULL)
199 goto fail;
200 node->is_default = is_default;
202 LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link);
203 LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section,
204 node->tag, node->value));
205 return 0;
206 fail:
207 free(node->value);
208 free(node->tag);
209 free(node->section);
210 free(node);
211 return 1;
214 /*
215 * Parse the line LINE of SZ bytes. Skip Comments, recognize section
216 * headers and feed tag-value pairs into our configuration database.
217 */
218 static const struct got_error *
219 conf_parse_line(char **section, struct got_gitconfig *conf, int trans,
220 char *line, int ln, size_t sz)
222 char *val;
223 size_t i;
224 int j;
226 /* Lines starting with '#' or ';' are comments. */
227 if (*line == '#' || *line == ';')
228 return NULL;
230 /* '[section]' parsing... */
231 if (*line == '[') {
232 for (i = 1; i < sz; i++)
233 if (line[i] == ']')
234 break;
235 free(*section);
236 if (i == sz) {
237 log_print("conf_parse_line: %d:"
238 "unmatched ']', ignoring until next section", ln);
239 *section = NULL;
240 return NULL;
242 *section = malloc(i);
243 if (*section == NULL)
244 return got_error_from_errno("malloc");
245 strlcpy(*section, line + 1, i);
246 return NULL;
248 while (isspace((unsigned char)*line))
249 line++;
251 /* Deal with assignments. */
252 for (i = 0; i < sz; i++)
253 if (line[i] == '=') {
254 /* If no section, we are ignoring the lines. */
255 if (!*section) {
256 log_print("conf_parse_line: %d: ignoring line "
257 "due to no section", ln);
258 return NULL;
260 line[strcspn(line, " \t=")] = '\0';
261 val = line + i + 1 + strspn(line + i + 1, " \t");
262 /* Skip trailing whitespace, if any */
263 for (j = sz - (val - line) - 1; j > 0 &&
264 isspace((unsigned char)val[j]); j--)
265 val[j] = '\0';
266 /* XXX Perhaps should we not ignore errors? */
267 got_gitconfig_set(conf, trans, *section, line, val,
268 0, 0);
269 return NULL;
271 /* Other non-empty lines are weird. */
272 i = strspn(line, " \t");
273 if (line[i])
274 log_print("conf_parse_line: %d: syntax error", ln);
276 return NULL;
279 /* Parse the mapped configuration file. */
280 static const struct got_error *
281 conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
283 const struct got_error *err = NULL;
284 char *cp = buf;
285 char *bufend = buf + sz;
286 char *line, *section = NULL;
287 int ln = 1;
289 line = cp;
290 while (cp < bufend) {
291 if (*cp == '\n') {
292 /* Check for escaped newlines. */
293 if (cp > buf && *(cp - 1) == '\\')
294 *(cp - 1) = *cp = ' ';
295 else {
296 *cp = '\0';
297 err = conf_parse_line(&section, conf, trans,
298 line, ln, cp - line);
299 if (err)
300 return err;
301 line = cp + 1;
303 ln++;
305 cp++;
307 if (cp != line)
308 log_print("conf_parse: last line unterminated, ignored.");
309 return NULL;
312 const struct got_error *
313 got_gitconfig_open(struct got_gitconfig **conf, int fd)
315 size_t i;
317 *conf = calloc(1, sizeof(**conf));
318 if (*conf == NULL)
319 return got_error_from_errno("malloc");
321 for (i = 0; i < nitems((*conf)->bindings); i++)
322 LIST_INIT(&(*conf)->bindings[i]);
323 TAILQ_INIT(&(*conf)->trans_queue);
324 return got_gitconfig_reinit(*conf, fd);
327 static void
328 conf_clear(struct got_gitconfig *conf)
330 struct got_gitconfig_binding *cb;
331 size_t i;
333 if (conf->addr) {
334 for (i = 0; i < nitems(conf->bindings); i++)
335 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
336 cb = LIST_FIRST(&conf->bindings[i]))
337 conf_remove_now(conf, cb->section, cb->tag);
338 free(conf->addr);
339 conf->addr = NULL;
343 /* Execute all queued operations for this transaction. Cleanup. */
344 static int
345 conf_end(struct got_gitconfig *conf, int transaction, int commit)
347 struct got_gitconfig_trans *node, *next;
349 for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
350 next = TAILQ_NEXT(node, link);
351 if (node->trans == transaction) {
352 if (commit)
353 switch (node->op) {
354 case CONF_SET:
355 conf_set_now(conf, node->section,
356 node->tag, node->value,
357 node->override, node->is_default);
358 break;
359 case CONF_REMOVE:
360 conf_remove_now(conf, node->section,
361 node->tag);
362 break;
363 case CONF_REMOVE_SECTION:
364 conf_remove_section_now(conf, node->section);
365 break;
366 default:
367 log_print("got_gitconfig_end: unknown "
368 "operation: %d", node->op);
370 TAILQ_REMOVE(&conf->trans_queue, node, link);
371 free(node->section);
372 free(node->tag);
373 free(node->value);
374 free(node);
377 return 0;
381 void
382 got_gitconfig_close(struct got_gitconfig *conf)
384 conf_clear(conf);
385 free(conf);
388 static int
389 conf_begin(struct got_gitconfig *conf)
391 return ++conf->seq;
394 /* Open the config file and map it into our address space, then parse it. */
395 const struct got_error *
396 got_gitconfig_reinit(struct got_gitconfig *conf, int fd)
398 const struct got_error *err = NULL;
399 int trans;
400 size_t sz;
401 char *new_conf_addr = 0;
402 struct stat st;
404 if (fstat(fd, &st)) {
405 err = got_error_from_errno("fstat");
406 goto fail;
409 sz = st.st_size;
410 new_conf_addr = malloc(sz);
411 if (new_conf_addr == NULL) {
412 err = got_error_from_errno("malloc");
413 goto fail;
415 /* XXX I assume short reads won't happen here. */
416 if (read(fd, new_conf_addr, sz) != (int)sz) {
417 err = got_error_from_errno("read");
418 goto fail;
421 trans = conf_begin(conf);
423 err = conf_parse(conf, trans, new_conf_addr, sz);
424 if (err)
425 goto fail;
427 /* Free potential existing configuration. */
428 conf_clear(conf);
429 conf_end(conf, trans, 1);
430 conf->addr = new_conf_addr;
431 return NULL;
433 fail:
434 free(new_conf_addr);
435 return err;
438 /*
439 * Return the numeric value denoted by TAG in section SECTION or DEF
440 * if that tag does not exist.
441 */
442 int
443 got_gitconfig_get_num(struct got_gitconfig *conf, const char *section,
444 const char *tag, int def)
446 char *value = got_gitconfig_get_str(conf, section, tag);
448 if (value)
449 return atoi(value);
450 return def;
453 /* Validate X according to the range denoted by TAG in section SECTION. */
454 int
455 got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
456 int x)
458 char *value = got_gitconfig_get_str(conf, section, tag);
459 int val, min, max, n;
461 if (!value)
462 return 0;
463 n = sscanf(value, "%d,%d:%d", &val, &min, &max);
464 switch (n) {
465 case 1:
466 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
467 section, tag, val, x));
468 return x == val;
469 case 3:
470 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
471 section, tag, min, x, max));
472 return min <= x && max >= x;
473 default:
474 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
475 "spec %s", section, tag, value);
477 return 0;
480 /* Return the string value denoted by TAG in section SECTION. */
481 char *
482 got_gitconfig_get_str(struct got_gitconfig *conf, const char *section,
483 const char *tag)
485 struct got_gitconfig_binding *cb;
487 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
488 cb = LIST_NEXT(cb, link))
489 if (strcasecmp(section, cb->section) == 0 &&
490 strcasecmp(tag, cb->tag) == 0) {
491 LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
492 section, tag, cb->value));
493 return cb->value;
495 LOG_DBG((LOG_MISC, 95,
496 "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
497 tag));
498 return 0;
501 const struct got_error *
502 got_gitconfig_get_section_list(struct got_gitconfig_list **sections,
503 struct got_gitconfig *conf)
505 const struct got_error *err = NULL;
506 struct got_gitconfig_list *list = NULL;
507 struct got_gitconfig_list_node *node = 0;
508 struct got_gitconfig_binding *cb;
509 size_t i;
511 *sections = NULL;
513 list = malloc(sizeof *list);
514 if (!list)
515 return got_error_from_errno("malloc");
516 TAILQ_INIT(&list->fields);
517 list->cnt = 0;
518 for (i = 0; i < nitems(conf->bindings); i++) {
519 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
520 cb = LIST_NEXT(cb, link)) {
521 int section_present = 0;
522 TAILQ_FOREACH(node, &list->fields, link) {
523 if (strcmp(node->field, cb->section) == 0) {
524 section_present = 1;
525 break;
528 if (section_present)
529 continue;
530 list->cnt++;
531 node = calloc(1, sizeof *node);
532 if (!node) {
533 err = got_error_from_errno("calloc");
534 goto cleanup;
536 node->field = strdup(cb->section);
537 if (!node->field) {
538 err = got_error_from_errno("strdup");
539 goto cleanup;
541 TAILQ_INSERT_TAIL(&list->fields, node, link);
545 *sections = list;
546 return NULL;
548 cleanup:
549 free(node);
550 if (list)
551 got_gitconfig_free_list(list);
552 return err;
555 /*
556 * Build a list of string values out of the comma separated value denoted by
557 * TAG in SECTION.
558 */
559 struct got_gitconfig_list *
560 got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
562 char *liststr = 0, *p, *field, *t;
563 struct got_gitconfig_list *list = 0;
564 struct got_gitconfig_list_node *node = 0;
566 list = malloc(sizeof *list);
567 if (!list)
568 goto cleanup;
569 TAILQ_INIT(&list->fields);
570 list->cnt = 0;
571 liststr = got_gitconfig_get_str(conf, section, tag);
572 if (!liststr)
573 goto cleanup;
574 liststr = strdup(liststr);
575 if (!liststr)
576 goto cleanup;
577 p = liststr;
578 while ((field = strsep(&p, ",")) != NULL) {
579 /* Skip leading whitespace */
580 while (isspace((unsigned char)*field))
581 field++;
582 /* Skip trailing whitespace */
583 if (p)
584 for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
585 *t = '\0';
586 if (*field == '\0') {
587 log_print("got_gitconfig_get_list: empty field, ignoring...");
588 continue;
590 list->cnt++;
591 node = calloc(1, sizeof *node);
592 if (!node)
593 goto cleanup;
594 node->field = strdup(field);
595 if (!node->field)
596 goto cleanup;
597 TAILQ_INSERT_TAIL(&list->fields, node, link);
599 free(liststr);
600 return list;
602 cleanup:
603 free(node);
604 if (list)
605 got_gitconfig_free_list(list);
606 free(liststr);
607 return 0;
610 struct got_gitconfig_list *
611 got_gitconfig_get_tag_list(struct got_gitconfig *conf, const char *section)
613 struct got_gitconfig_list *list = 0;
614 struct got_gitconfig_list_node *node = 0;
615 struct got_gitconfig_binding *cb;
617 list = malloc(sizeof *list);
618 if (!list)
619 goto cleanup;
620 TAILQ_INIT(&list->fields);
621 list->cnt = 0;
622 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
623 cb = LIST_NEXT(cb, link))
624 if (strcasecmp(section, cb->section) == 0) {
625 list->cnt++;
626 node = calloc(1, sizeof *node);
627 if (!node)
628 goto cleanup;
629 node->field = strdup(cb->tag);
630 if (!node->field)
631 goto cleanup;
632 TAILQ_INSERT_TAIL(&list->fields, node, link);
634 return list;
636 cleanup:
637 free(node);
638 if (list)
639 got_gitconfig_free_list(list);
640 return 0;
643 void
644 got_gitconfig_free_list(struct got_gitconfig_list *list)
646 struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
648 while (node) {
649 TAILQ_REMOVE(&list->fields, node, link);
650 free(node->field);
651 free(node);
652 node = TAILQ_FIRST(&list->fields);
654 free(list);
657 static int
658 got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
659 enum got_gitconfig_op op, char *section, char *tag, char *value,
660 int override, int is_default)
662 struct got_gitconfig_trans *node;
664 node = calloc(1, sizeof *node);
665 if (!node) {
666 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
667 (unsigned long)sizeof *node);
668 return 1;
670 node->trans = transaction;
671 node->op = op;
672 node->override = override;
673 node->is_default = is_default;
674 if (section && (node->section = strdup(section)) == NULL)
675 goto fail;
676 if (tag && (node->tag = strdup(tag)) == NULL)
677 goto fail;
678 if (value && (node->value = strdup(value)) == NULL)
679 goto fail;
680 TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
681 return 0;
683 fail:
684 free(node->section);
685 free(node->tag);
686 free(node->value);
687 free(node);
688 return 1;
691 /* Queue a set operation. */
692 int
693 got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
694 char *tag, char *value, int override, int is_default)
696 return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
697 tag, value, override, is_default);
700 /* Queue a remove operation. */
701 int
702 got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
703 char *section, char *tag)
705 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
706 section, tag, NULL, 0, 0);
709 /* Queue a remove section operation. */
710 int
711 got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
712 char *section)
714 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
715 section, NULL, NULL, 0, 0);