sync distfile list
[got-portable.git] / lib / gitconfig.c
blob79337138861c4aa917c6cbb1552a0743dde3a958
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.
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.
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/stat.h>
32 #include <ctype.h>
33 #include <fcntl.h>
34 #include <stdarg.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <errno.h>
41 #include "got_compat.h"
43 #include "got_error.h"
45 #include "got_lib_gitconfig.h"
47 #ifndef nitems
48 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
49 #endif
51 #define LOG_MISC 0
52 #define LOG_REPORT 1
53 #ifdef GITCONFIG_DEBUG
54 #define LOG_DBG(x) log_debug x
55 #else
56 #define LOG_DBG(x)
57 #endif
59 #define log_print printf
60 #define log_error printf
62 #ifdef GITCONFIG_DEBUG
63 static void
64 log_debug(int cls, int level, const char *fmt, ...)
66 va_list ap;
68 va_start(ap, fmt);
69 vfprintf(stderr, fmt, ap);
70 va_end(ap);
72 #endif
74 struct got_gitconfig_trans {
75 TAILQ_ENTRY(got_gitconfig_trans) link;
76 int trans;
77 enum got_gitconfig_op {
78 CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
79 } op;
80 char *section;
81 char *tag;
82 char *value;
83 int override;
84 int is_default;
87 TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
89 struct got_gitconfig_binding {
90 LIST_ENTRY(got_gitconfig_binding) link;
91 char *section;
92 char *tag;
93 char *value;
94 int is_default;
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;
102 char *addr;
103 int seq;
106 static __inline__ u_int8_t
107 conf_hash(const char *s)
109 u_int8_t hash = 0;
111 while (*s) {
112 hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
113 s++;
115 return hash;
119 * Insert a tag-value combination from LINE (the equal sign is at POS)
121 static int
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;
127 cb = next) {
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,
133 tag, cb->value));
134 free(cb->section);
135 free(cb->tag);
136 free(cb->value);
137 free(cb);
138 return 0;
141 return 1;
144 static int
145 conf_remove_section_now(struct got_gitconfig *conf, char *section)
147 struct got_gitconfig_binding *cb, *next;
148 int unseen = 1;
150 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
151 cb = next) {
152 next = LIST_NEXT(cb, link);
153 if (strcasecmp(cb->section, section) == 0) {
154 unseen = 0;
155 LIST_REMOVE(cb, link);
156 LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
157 cb->tag, cb->value));
158 free(cb->section);
159 free(cb->tag);
160 free(cb->value);
161 free(cb);
164 return unseen;
168 * Insert a tag-value combination from LINE (the equal sign is at POS)
169 * into SECTION of our configuration database.
171 static int
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;
177 if (override)
178 conf_remove_now(conf, section, tag);
179 else if (got_gitconfig_get_str(conf, section, tag)) {
180 if (!is_default)
181 LOG_DBG((LOG_MISC,
182 "conf_set_now: duplicate tag [%s]:%s, "
183 "ignoring...\n", section, tag));
184 return 1;
186 node = calloc(1, sizeof *node);
187 if (!node) {
188 log_error("conf_set_now: calloc (1, %lu) failed",
189 (unsigned long)sizeof *node);
190 return 1;
192 node->section = node->tag = node->value = NULL;
193 if ((node->section = strdup(section)) == NULL)
194 goto fail;
195 if ((node->tag = strdup(tag)) == NULL)
196 goto fail;
197 if ((node->value = strdup(value)) == NULL)
198 goto fail;
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));
204 return 0;
205 fail:
206 free(node->value);
207 free(node->tag);
208 free(node->section);
209 free(node);
210 return 1;
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)
221 char *val;
222 size_t i;
223 int j;
225 /* Lines starting with '#' or ';' are comments. */
226 if (*line == '#' || *line == ';')
227 return NULL;
229 /* '[section]' parsing... */
230 if (*line == '[') {
231 for (i = 1; i < sz; i++)
232 if (line[i] == ']')
233 break;
234 free(*section);
235 if (i == sz) {
236 log_print("conf_parse_line: %d:"
237 "unmatched ']', ignoring until next section", ln);
238 *section = NULL;
239 return NULL;
241 *section = malloc(i);
242 if (*section == NULL)
243 return got_error_from_errno("malloc");
244 strlcpy(*section, line + 1, i);
245 return NULL;
247 while (isspace((unsigned char)*line))
248 line++;
250 /* Deal with assignments. */
251 for (i = 0; i < sz; i++)
252 if (line[i] == '=') {
253 /* If no section, we are ignoring the lines. */
254 if (!*section) {
255 log_print("conf_parse_line: %d: ignoring line "
256 "due to no section", ln);
257 return NULL;
259 line[strcspn(line, " \t=")] = '\0';
260 val = line + i + 1 + strspn(line + i + 1, " \t");
261 /* Skip trailing whitespace, if any */
262 for (j = sz - (val - line) - 1; j > 0 &&
263 isspace((unsigned char)val[j]); j--)
264 val[j] = '\0';
265 /* XXX Perhaps should we not ignore errors? */
266 got_gitconfig_set(conf, trans, *section, line, val,
267 0, 0);
268 return NULL;
270 /* Other non-empty lines are weird. */
271 i = strspn(line, " \t");
272 if (line[i])
273 log_print("conf_parse_line: %d: syntax error", ln);
275 return NULL;
278 /* Parse the mapped configuration file. */
279 static const struct got_error *
280 conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
282 const struct got_error *err = NULL;
283 char *cp = buf;
284 char *bufend = buf + sz;
285 char *line, *section = NULL;
286 int ln = 1;
288 line = cp;
289 while (cp < bufend) {
290 if (*cp == '\n') {
291 /* Check for escaped newlines. */
292 if (cp > buf && *(cp - 1) == '\\')
293 *(cp - 1) = *cp = ' ';
294 else {
295 *cp = '\0';
296 err = conf_parse_line(&section, conf, trans,
297 line, ln, cp - line);
298 if (err)
299 return err;
300 line = cp + 1;
302 ln++;
304 cp++;
306 if (cp != line)
307 log_print("conf_parse: last line unterminated, ignored.");
308 return NULL;
311 const struct got_error *
312 got_gitconfig_open(struct got_gitconfig **conf, int fd)
314 size_t i;
316 *conf = calloc(1, sizeof(**conf));
317 if (*conf == NULL)
318 return got_error_from_errno("malloc");
320 for (i = 0; i < nitems((*conf)->bindings); i++)
321 LIST_INIT(&(*conf)->bindings[i]);
322 TAILQ_INIT(&(*conf)->trans_queue);
323 return got_gitconfig_reinit(*conf, fd);
326 static void
327 conf_clear(struct got_gitconfig *conf)
329 struct got_gitconfig_binding *cb;
330 size_t i;
332 if (conf->addr) {
333 for (i = 0; i < nitems(conf->bindings); i++)
334 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
335 cb = LIST_FIRST(&conf->bindings[i]))
336 conf_remove_now(conf, cb->section, cb->tag);
337 free(conf->addr);
338 conf->addr = NULL;
342 /* Execute all queued operations for this transaction. Cleanup. */
343 static int
344 conf_end(struct got_gitconfig *conf, int transaction, int commit)
346 struct got_gitconfig_trans *node, *next;
348 for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
349 next = TAILQ_NEXT(node, link);
350 if (node->trans == transaction) {
351 if (commit)
352 switch (node->op) {
353 case CONF_SET:
354 conf_set_now(conf, node->section,
355 node->tag, node->value,
356 node->override, node->is_default);
357 break;
358 case CONF_REMOVE:
359 conf_remove_now(conf, node->section,
360 node->tag);
361 break;
362 case CONF_REMOVE_SECTION:
363 conf_remove_section_now(conf, node->section);
364 break;
365 default:
366 log_print("got_gitconfig_end: unknown "
367 "operation: %d", node->op);
369 TAILQ_REMOVE(&conf->trans_queue, node, link);
370 free(node->section);
371 free(node->tag);
372 free(node->value);
373 free(node);
376 return 0;
380 void
381 got_gitconfig_close(struct got_gitconfig *conf)
383 conf_clear(conf);
384 free(conf);
387 static int
388 conf_begin(struct got_gitconfig *conf)
390 return ++conf->seq;
393 /* Open the config file and map it into our address space, then parse it. */
394 const struct got_error *
395 got_gitconfig_reinit(struct got_gitconfig *conf, int fd)
397 const struct got_error *err = NULL;
398 int trans;
399 size_t sz;
400 char *new_conf_addr = 0;
401 struct stat st;
403 if (fstat(fd, &st)) {
404 err = got_error_from_errno("fstat");
405 goto fail;
408 sz = st.st_size;
409 new_conf_addr = malloc(sz);
410 if (new_conf_addr == NULL) {
411 err = got_error_from_errno("malloc");
412 goto fail;
414 /* XXX I assume short reads won't happen here. */
415 if (read(fd, new_conf_addr, sz) != (int)sz) {
416 err = got_error_from_errno("read");
417 goto fail;
420 trans = conf_begin(conf);
422 err = conf_parse(conf, trans, new_conf_addr, sz);
423 if (err)
424 goto fail;
426 /* Free potential existing configuration. */
427 conf_clear(conf);
428 conf_end(conf, trans, 1);
429 conf->addr = new_conf_addr;
430 return NULL;
432 fail:
433 free(new_conf_addr);
434 return err;
438 * Return the numeric value denoted by TAG in section SECTION or DEF
439 * if that tag does not exist.
442 got_gitconfig_get_num(struct got_gitconfig *conf, const char *section,
443 const char *tag, int def)
445 char *value = got_gitconfig_get_str(conf, section, tag);
447 if (value)
448 return atoi(value);
449 return def;
452 /* Validate X according to the range denoted by TAG in section SECTION. */
454 got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
455 int x)
457 char *value = got_gitconfig_get_str(conf, section, tag);
458 int val, min, max, n;
460 if (!value)
461 return 0;
462 n = sscanf(value, "%d,%d:%d", &val, &min, &max);
463 switch (n) {
464 case 1:
465 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
466 section, tag, val, x));
467 return x == val;
468 case 3:
469 LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
470 section, tag, min, x, max));
471 return min <= x && max >= x;
472 default:
473 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
474 "spec %s", section, tag, value);
476 return 0;
479 /* Return the string value denoted by TAG in section SECTION. */
480 char *
481 got_gitconfig_get_str(struct got_gitconfig *conf, const char *section,
482 const char *tag)
484 struct got_gitconfig_binding *cb;
486 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
487 cb = LIST_NEXT(cb, link))
488 if (strcasecmp(section, cb->section) == 0 &&
489 strcasecmp(tag, cb->tag) == 0) {
490 LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
491 section, tag, cb->value));
492 return cb->value;
494 LOG_DBG((LOG_MISC, 95,
495 "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
496 tag));
497 return 0;
500 const struct got_error *
501 got_gitconfig_get_section_list(struct got_gitconfig_list **sections,
502 struct got_gitconfig *conf)
504 const struct got_error *err = NULL;
505 struct got_gitconfig_list *list = NULL;
506 struct got_gitconfig_list_node *node = 0;
507 struct got_gitconfig_binding *cb;
508 size_t i;
510 *sections = NULL;
512 list = malloc(sizeof *list);
513 if (!list)
514 return got_error_from_errno("malloc");
515 TAILQ_INIT(&list->fields);
516 list->cnt = 0;
517 for (i = 0; i < nitems(conf->bindings); i++) {
518 for (cb = LIST_FIRST(&conf->bindings[i]); cb;
519 cb = LIST_NEXT(cb, link)) {
520 int section_present = 0;
521 TAILQ_FOREACH(node, &list->fields, link) {
522 if (strcmp(node->field, cb->section) == 0) {
523 section_present = 1;
524 break;
527 if (section_present)
528 continue;
529 list->cnt++;
530 node = calloc(1, sizeof *node);
531 if (!node) {
532 err = got_error_from_errno("calloc");
533 goto cleanup;
535 node->field = strdup(cb->section);
536 if (!node->field) {
537 err = got_error_from_errno("strdup");
538 goto cleanup;
540 TAILQ_INSERT_TAIL(&list->fields, node, link);
544 *sections = list;
545 return NULL;
547 cleanup:
548 free(node);
549 if (list)
550 got_gitconfig_free_list(list);
551 return err;
555 * Build a list of string values out of the comma separated value denoted by
556 * TAG in SECTION.
558 struct got_gitconfig_list *
559 got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
561 char *liststr = 0, *p, *field, *t;
562 struct got_gitconfig_list *list = 0;
563 struct got_gitconfig_list_node *node = 0;
565 list = malloc(sizeof *list);
566 if (!list)
567 goto cleanup;
568 TAILQ_INIT(&list->fields);
569 list->cnt = 0;
570 liststr = got_gitconfig_get_str(conf, section, tag);
571 if (!liststr)
572 goto cleanup;
573 liststr = strdup(liststr);
574 if (!liststr)
575 goto cleanup;
576 p = liststr;
577 while ((field = strsep(&p, ",")) != NULL) {
578 /* Skip leading whitespace */
579 while (isspace((unsigned char)*field))
580 field++;
581 /* Skip trailing whitespace */
582 if (p)
583 for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
584 *t = '\0';
585 if (*field == '\0') {
586 log_print("got_gitconfig_get_list: empty field, ignoring...");
587 continue;
589 list->cnt++;
590 node = calloc(1, sizeof *node);
591 if (!node)
592 goto cleanup;
593 node->field = strdup(field);
594 if (!node->field)
595 goto cleanup;
596 TAILQ_INSERT_TAIL(&list->fields, node, link);
598 free(liststr);
599 return list;
601 cleanup:
602 free(node);
603 if (list)
604 got_gitconfig_free_list(list);
605 free(liststr);
606 return 0;
609 struct got_gitconfig_list *
610 got_gitconfig_get_tag_list(struct got_gitconfig *conf, const char *section)
612 struct got_gitconfig_list *list = 0;
613 struct got_gitconfig_list_node *node = 0;
614 struct got_gitconfig_binding *cb;
616 list = malloc(sizeof *list);
617 if (!list)
618 goto cleanup;
619 TAILQ_INIT(&list->fields);
620 list->cnt = 0;
621 for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
622 cb = LIST_NEXT(cb, link))
623 if (strcasecmp(section, cb->section) == 0) {
624 list->cnt++;
625 node = calloc(1, sizeof *node);
626 if (!node)
627 goto cleanup;
628 node->field = strdup(cb->tag);
629 if (!node->field)
630 goto cleanup;
631 TAILQ_INSERT_TAIL(&list->fields, node, link);
633 return list;
635 cleanup:
636 free(node);
637 if (list)
638 got_gitconfig_free_list(list);
639 return 0;
642 void
643 got_gitconfig_free_list(struct got_gitconfig_list *list)
645 struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
647 while (node) {
648 TAILQ_REMOVE(&list->fields, node, link);
649 free(node->field);
650 free(node);
651 node = TAILQ_FIRST(&list->fields);
653 free(list);
656 static int
657 got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
658 enum got_gitconfig_op op, char *section, char *tag, char *value,
659 int override, int is_default)
661 struct got_gitconfig_trans *node;
663 node = calloc(1, sizeof *node);
664 if (!node) {
665 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
666 (unsigned long)sizeof *node);
667 return 1;
669 node->trans = transaction;
670 node->op = op;
671 node->override = override;
672 node->is_default = is_default;
673 if (section && (node->section = strdup(section)) == NULL)
674 goto fail;
675 if (tag && (node->tag = strdup(tag)) == NULL)
676 goto fail;
677 if (value && (node->value = strdup(value)) == NULL)
678 goto fail;
679 TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
680 return 0;
682 fail:
683 free(node->section);
684 free(node->tag);
685 free(node->value);
686 free(node);
687 return 1;
690 /* Queue a set operation. */
692 got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
693 char *tag, char *value, int override, int is_default)
695 return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
696 tag, value, override, is_default);
699 /* Queue a remove operation. */
701 got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
702 char *section, char *tag)
704 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
705 section, tag, NULL, 0, 0);
708 /* Queue a remove section operation. */
710 got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
711 char *section)
713 return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
714 section, NULL, NULL, 0, 0);