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>
41 #include "got_compat.h"
43 #include "got_error.h"
45 #include "got_lib_gitconfig.h"
48 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
53 #ifdef GITCONFIG_DEBUG
54 #define LOG_DBG(x) log_debug x
59 #define log_print printf
60 #define log_error printf
62 #ifdef GITCONFIG_DEBUG
64 log_debug(int cls
, int level
, const char *fmt
, ...)
69 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
)) {
182 "conf_set_now: duplicate tag [%s]:%s, "
183 "ignoring...\n", 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
= malloc(i
);
242 if (*section
== NULL
)
243 return got_error_from_errno("malloc");
244 strlcpy(*section
, line
+ 1, i
);
247 while (isspace((unsigned char)*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. */
255 log_print("conf_parse_line: %d: ignoring line "
256 "due to no section", ln
);
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
--)
265 /* XXX Perhaps should we not ignore errors? */
266 got_gitconfig_set(conf
, trans
, *section
, line
, val
,
270 /* Other non-empty lines are weird. */
271 i
= strspn(line
, " \t");
273 log_print("conf_parse_line: %d: syntax error", ln
);
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
;
284 char *bufend
= buf
+ sz
;
285 char *line
, *section
= NULL
;
289 while (cp
< bufend
) {
291 /* Check for escaped newlines. */
292 if (cp
> buf
&& *(cp
- 1) == '\\')
293 *(cp
- 1) = *cp
= ' ';
296 err
= conf_parse_line(§ion
, conf
, trans
,
297 line
, ln
, cp
- line
);
307 log_print("conf_parse: last line unterminated, ignored.");
311 const struct got_error
*
312 got_gitconfig_open(struct got_gitconfig
**conf
, int fd
)
316 *conf
= calloc(1, sizeof(**conf
));
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
);
327 conf_clear(struct got_gitconfig
*conf
)
329 struct got_gitconfig_binding
*cb
;
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
);
342 /* Execute all queued operations for this transaction. Cleanup. */
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
) {
354 conf_set_now(conf
, node
->section
,
355 node
->tag
, node
->value
,
356 node
->override
, node
->is_default
);
359 conf_remove_now(conf
, node
->section
,
362 case CONF_REMOVE_SECTION
:
363 conf_remove_section_now(conf
, node
->section
);
366 log_print("got_gitconfig_end: unknown "
367 "operation: %d", node
->op
);
369 TAILQ_REMOVE(&conf
->trans_queue
, node
, link
);
381 got_gitconfig_close(struct got_gitconfig
*conf
)
388 conf_begin(struct got_gitconfig
*conf
)
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
;
400 char *new_conf_addr
= 0;
403 if (fstat(fd
, &st
)) {
404 err
= got_error_from_errno("fstat");
409 new_conf_addr
= malloc(sz
);
410 if (new_conf_addr
== NULL
) {
411 err
= got_error_from_errno("malloc");
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");
420 trans
= conf_begin(conf
);
422 err
= conf_parse(conf
, trans
, new_conf_addr
, sz
);
426 /* Free potential existing configuration. */
428 conf_end(conf
, trans
, 1);
429 conf
->addr
= new_conf_addr
;
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
);
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
,
457 char *value
= got_gitconfig_get_str(conf
, section
, tag
);
458 int val
, min
, max
, n
;
462 n
= sscanf(value
, "%d,%d:%d", &val
, &min
, &max
);
465 LOG_DBG((LOG_MISC
, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
466 section
, tag
, val
, x
));
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
;
473 log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
474 "spec %s", section
, tag
, value
);
479 /* Return the string value denoted by TAG in section SECTION. */
481 got_gitconfig_get_str(struct got_gitconfig
*conf
, const char *section
,
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
));
494 LOG_DBG((LOG_MISC
, 95,
495 "got_gitconfig_get_str: configuration value not found [%s]:%s", section
,
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
;
512 list
= malloc(sizeof *list
);
514 return got_error_from_errno("malloc");
515 TAILQ_INIT(&list
->fields
);
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) {
530 node
= calloc(1, sizeof *node
);
532 err
= got_error_from_errno("calloc");
535 node
->field
= strdup(cb
->section
);
537 err
= got_error_from_errno("strdup");
540 TAILQ_INSERT_TAIL(&list
->fields
, node
, link
);
550 got_gitconfig_free_list(list
);
555 * Build a list of string values out of the comma separated value denoted by
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
);
568 TAILQ_INIT(&list
->fields
);
570 liststr
= got_gitconfig_get_str(conf
, section
, tag
);
573 liststr
= strdup(liststr
);
577 while ((field
= strsep(&p
, ",")) != NULL
) {
578 /* Skip leading whitespace */
579 while (isspace((unsigned char)*field
))
581 /* Skip trailing whitespace */
583 for (t
= p
- 1; t
> field
&& isspace((unsigned char)*t
); t
--)
585 if (*field
== '\0') {
586 log_print("got_gitconfig_get_list: empty field, ignoring...");
590 node
= calloc(1, sizeof *node
);
593 node
->field
= strdup(field
);
596 TAILQ_INSERT_TAIL(&list
->fields
, node
, link
);
604 got_gitconfig_free_list(list
);
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
);
619 TAILQ_INIT(&list
->fields
);
621 for (cb
= LIST_FIRST(&conf
->bindings
[conf_hash(section
)]); cb
;
622 cb
= LIST_NEXT(cb
, link
))
623 if (strcasecmp(section
, cb
->section
) == 0) {
625 node
= calloc(1, sizeof *node
);
628 node
->field
= strdup(cb
->tag
);
631 TAILQ_INSERT_TAIL(&list
->fields
, node
, link
);
638 got_gitconfig_free_list(list
);
643 got_gitconfig_free_list(struct got_gitconfig_list
*list
)
645 struct got_gitconfig_list_node
*node
= TAILQ_FIRST(&list
->fields
);
648 TAILQ_REMOVE(&list
->fields
, node
, link
);
651 node
= TAILQ_FIRST(&list
->fields
);
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
);
665 log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
666 (unsigned long)sizeof *node
);
669 node
->trans
= transaction
;
671 node
->override
= override
;
672 node
->is_default
= is_default
;
673 if (section
&& (node
->section
= strdup(section
)) == NULL
)
675 if (tag
&& (node
->tag
= strdup(tag
)) == NULL
)
677 if (value
&& (node
->value
= strdup(value
)) == NULL
)
679 TAILQ_INSERT_TAIL(&conf
->trans_queue
, node
, link
);
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
,
713 return got_gitconfig_trans_node(conf
, transaction
, CONF_REMOVE_SECTION
,
714 section
, NULL
, NULL
, 0, 0);