2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
14 #include "git2/config.h"
15 #include "git2/sys/config.h"
16 #include "git2/types.h"
21 #include <sys/types.h>
26 typedef struct cvar_t
{
28 git_config_entry
*entry
;
29 int included
; /* whether this is part of [include] */
32 typedef struct git_config_file_iter
{
33 git_config_iterator parent
;
36 } git_config_file_iter
;
38 /* Max depth for [include] directives */
39 #define MAX_INCLUDE_DEPTH 10
41 #define CVAR_LIST_HEAD(list) ((list)->head)
43 #define CVAR_LIST_TAIL(list) ((list)->tail)
45 #define CVAR_LIST_NEXT(var) ((var)->next)
47 #define CVAR_LIST_EMPTY(list) ((list)->head == NULL)
49 #define CVAR_LIST_APPEND(list, var) do {\
50 if (CVAR_LIST_EMPTY(list)) {\
51 CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
53 CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
54 CVAR_LIST_TAIL(list) = var;\
58 #define CVAR_LIST_REMOVE_HEAD(list) do {\
59 CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
62 #define CVAR_LIST_REMOVE_AFTER(var) do {\
63 CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
66 #define CVAR_LIST_FOREACH(list, iter)\
67 for ((iter) = CVAR_LIST_HEAD(list);\
69 (iter) = CVAR_LIST_NEXT(iter))
72 * Inspired by the FreeBSD functions
74 #define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
75 for ((iter) = CVAR_LIST_HEAD(vars);\
76 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
90 git_config_backend parent
;
94 git_array_t(struct reader
) readers
;
98 git_config_level_t level
;
101 static int config_parse(diskfile_backend
*cfg_file
, struct reader
*reader
, git_config_level_t level
, int depth
);
102 static int parse_variable(struct reader
*reader
, char **var_name
, char **var_value
);
103 static int config_write(diskfile_backend
*cfg
, const char *key
, const regex_t
*preg
, const char *value
);
104 static char *escape_value(const char *ptr
);
106 static void set_parse_error(struct reader
*reader
, int col
, const char *error_str
)
108 giterr_set(GITERR_CONFIG
, "Failed to parse config file: %s (in %s:%d, column %d)",
109 error_str
, reader
->file_path
, reader
->line_number
, col
);
112 static void cvar_free(cvar_t
*var
)
117 git__free((char*)var
->entry
->name
);
118 git__free((char *)var
->entry
->value
);
119 git__free(var
->entry
);
123 static int cvar_length(cvar_t
*var
)
135 int git_config_file_normalize_section(char *start
, char *end
)
140 return GIT_EINVALIDSPEC
;
142 /* Validate and downcase range */
143 for (scan
= start
; *scan
; ++scan
) {
144 if (end
&& scan
>= end
)
147 *scan
= (char)tolower(*scan
);
148 else if (*scan
!= '-' || scan
== start
)
149 return GIT_EINVALIDSPEC
;
153 return GIT_EINVALIDSPEC
;
158 static void free_vars(git_strmap
*values
)
165 git_strmap_foreach_value(values
, var
,
166 while (var
!= NULL
) {
167 cvar_t
*next
= CVAR_LIST_NEXT(var
);
172 git_strmap_free(values
);
175 static int config_open(git_config_backend
*cfg
, git_config_level_t level
)
178 struct reader
*reader
;
179 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
183 b
->values
= git_strmap_alloc();
184 GITERR_CHECK_ALLOC(b
->values
);
186 git_array_init(b
->readers
);
187 reader
= git_array_alloc(b
->readers
);
188 memset(reader
, 0, sizeof(struct reader
));
190 reader
->file_path
= git__strdup(b
->file_path
);
191 GITERR_CHECK_ALLOC(reader
->file_path
);
193 git_buf_init(&reader
->buffer
, 0);
194 res
= git_futils_readbuffer_updated(
195 &reader
->buffer
, b
->file_path
, &reader
->file_mtime
, &reader
->file_size
, NULL
);
197 /* It's fine if the file doesn't exist */
198 if (res
== GIT_ENOTFOUND
)
201 if (res
< 0 || (res
= config_parse(b
, reader
, level
, 0)) < 0) {
202 free_vars(b
->values
);
206 reader
= git_array_get(b
->readers
, 0);
207 git_buf_free(&reader
->buffer
);
211 static int config_refresh(git_config_backend
*cfg
)
213 int res
= 0, updated
= 0, any_updated
= 0;
214 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
215 git_strmap
*old_values
;
216 struct reader
*reader
;
219 for (i
= 0; i
< git_array_size(b
->readers
); i
++) {
220 reader
= git_array_get(b
->readers
, i
);
221 res
= git_futils_readbuffer_updated(
222 &reader
->buffer
, reader
->file_path
, &reader
->file_mtime
, &reader
->file_size
, &updated
);
225 return (res
== GIT_ENOTFOUND
) ? 0 : res
;
232 return (res
== GIT_ENOTFOUND
) ? 0 : res
;
234 /* need to reload - store old values and prep for reload */
235 old_values
= b
->values
;
236 b
->values
= git_strmap_alloc();
237 GITERR_CHECK_ALLOC(b
->values
);
239 if ((res
= config_parse(b
, reader
, b
->level
, 0)) < 0) {
240 free_vars(b
->values
);
241 b
->values
= old_values
;
243 free_vars(old_values
);
246 git_buf_free(&reader
->buffer
);
250 static void backend_free(git_config_backend
*_backend
)
252 diskfile_backend
*backend
= (diskfile_backend
*)_backend
;
258 for (i
= 0; i
< git_array_size(backend
->readers
); i
++) {
259 struct reader
*r
= git_array_get(backend
->readers
, i
);
260 git__free(r
->file_path
);
262 git_array_clear(backend
->readers
);
264 git__free(backend
->file_path
);
265 free_vars(backend
->values
);
269 static void config_iterator_free(
270 git_config_iterator
* iter
)
275 static int config_iterator_next(
276 git_config_entry
**entry
,
277 git_config_iterator
*iter
)
279 git_config_file_iter
*it
= (git_config_file_iter
*) iter
;
280 diskfile_backend
*b
= (diskfile_backend
*) it
->parent
.backend
;
284 if (it
->next_var
== NULL
) {
285 err
= git_strmap_next((void**) &var
, &(it
->iter
), b
->values
);
296 it
->next_var
= CVAR_LIST_NEXT(var
);
301 static int config_iterator_new(
302 git_config_iterator
**iter
,
303 struct git_config_backend
* backend
)
305 diskfile_backend
*b
= (diskfile_backend
*)backend
;
306 git_config_file_iter
*it
= git__calloc(1, sizeof(git_config_file_iter
));
310 GITERR_CHECK_ALLOC(it
);
312 it
->parent
.backend
= backend
;
313 it
->iter
= git_strmap_begin(b
->values
);
316 it
->parent
.next
= config_iterator_next
;
317 it
->parent
.free
= config_iterator_free
;
318 *iter
= (git_config_iterator
*) it
;
323 static int config_set(git_config_backend
*cfg
, const char *name
, const char *value
)
325 cvar_t
*var
= NULL
, *old_var
= NULL
;
326 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
327 char *key
, *esc_value
= NULL
;
331 if ((rval
= git_config__normalize_name(name
, &key
)) < 0)
335 * Try to find it in the existing values and update it if it
336 * only has one value.
338 pos
= git_strmap_lookup_index(b
->values
, key
);
339 if (git_strmap_valid_index(b
->values
, pos
)) {
340 cvar_t
*existing
= git_strmap_value_at(b
->values
, pos
);
345 if (existing
->next
!= NULL
) {
346 giterr_set(GITERR_CONFIG
, "Multivar incompatible with simple set");
350 /* don't update if old and new values already match */
351 if ((!existing
->entry
->value
&& !value
) ||
352 (existing
->entry
->value
&& value
&& !strcmp(existing
->entry
->value
, value
)))
356 tmp
= git__strdup(value
);
357 GITERR_CHECK_ALLOC(tmp
);
358 esc_value
= escape_value(value
);
359 GITERR_CHECK_ALLOC(esc_value
);
362 git__free((void *)existing
->entry
->value
);
363 existing
->entry
->value
= tmp
;
365 ret
= config_write(b
, existing
->entry
->name
, NULL
, esc_value
);
367 git__free(esc_value
);
371 var
= git__malloc(sizeof(cvar_t
));
372 GITERR_CHECK_ALLOC(var
);
373 memset(var
, 0x0, sizeof(cvar_t
));
374 var
->entry
= git__malloc(sizeof(git_config_entry
));
375 GITERR_CHECK_ALLOC(var
->entry
);
376 memset(var
->entry
, 0x0, sizeof(git_config_entry
));
378 var
->entry
->name
= key
;
379 var
->entry
->value
= NULL
;
382 var
->entry
->value
= git__strdup(value
);
383 GITERR_CHECK_ALLOC(var
->entry
->value
);
384 esc_value
= escape_value(value
);
385 GITERR_CHECK_ALLOC(esc_value
);
388 if (config_write(b
, key
, NULL
, esc_value
) < 0) {
389 git__free(esc_value
);
394 git__free(esc_value
);
395 git_strmap_insert2(b
->values
, key
, var
, old_var
, rval
);
405 * Internal function that actually gets the value in string form
407 static int config_get(const git_config_backend
*cfg
, const char *name
, const git_config_entry
**out
)
409 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
415 if ((error
= git_config__normalize_name(name
, &key
)) < 0)
418 pos
= git_strmap_lookup_index(b
->values
, key
);
421 /* no error message; the config system will write one */
422 if (!git_strmap_valid_index(b
->values
, pos
))
423 return GIT_ENOTFOUND
;
425 var
= git_strmap_value_at(b
->values
, pos
);
434 static int config_set_multivar(
435 git_config_backend
*cfg
, const char *name
, const char *regexp
, const char *value
)
438 cvar_t
*var
, *newvar
;
439 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
447 if ((result
= git_config__normalize_name(name
, &key
)) < 0)
450 pos
= git_strmap_lookup_index(b
->values
, key
);
451 if (!git_strmap_valid_index(b
->values
, pos
)) {
452 /* If we don't have it, behave like a normal set */
453 result
= config_set(cfg
, name
, value
);
458 var
= git_strmap_value_at(b
->values
, pos
);
460 result
= regcomp(&preg
, regexp
, REG_EXTENDED
);
463 giterr_set_regex(&preg
, result
);
469 if (regexec(&preg
, var
->entry
->value
, 0, NULL
, 0) == 0) {
470 char *tmp
= git__strdup(value
);
471 GITERR_CHECK_ALLOC(tmp
);
473 git__free((void *)var
->entry
->value
);
474 var
->entry
->value
= tmp
;
478 if (var
->next
== NULL
)
484 /* If we've reached the end of the variables and we haven't found it yet, we need to append it */
486 newvar
= git__malloc(sizeof(cvar_t
));
487 GITERR_CHECK_ALLOC(newvar
);
488 memset(newvar
, 0x0, sizeof(cvar_t
));
489 newvar
->entry
= git__malloc(sizeof(git_config_entry
));
490 GITERR_CHECK_ALLOC(newvar
->entry
);
491 memset(newvar
->entry
, 0x0, sizeof(git_config_entry
));
493 newvar
->entry
->name
= git__strdup(var
->entry
->name
);
494 GITERR_CHECK_ALLOC(newvar
->entry
->name
);
496 newvar
->entry
->value
= git__strdup(value
);
497 GITERR_CHECK_ALLOC(newvar
->entry
->value
);
499 newvar
->entry
->level
= var
->entry
->level
;
504 result
= config_write(b
, key
, &preg
, value
);
512 static int config_delete(git_config_backend
*cfg
, const char *name
)
515 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
520 if ((result
= git_config__normalize_name(name
, &key
)) < 0)
523 pos
= git_strmap_lookup_index(b
->values
, key
);
526 if (!git_strmap_valid_index(b
->values
, pos
)) {
527 giterr_set(GITERR_CONFIG
, "Could not find key '%s' to delete", name
);
528 return GIT_ENOTFOUND
;
531 var
= git_strmap_value_at(b
->values
, pos
);
533 if (var
->next
!= NULL
) {
534 giterr_set(GITERR_CONFIG
, "Cannot delete multivar with a single delete");
538 git_strmap_delete_at(b
->values
, pos
);
540 result
= config_write(b
, var
->entry
->name
, NULL
, NULL
);
546 static int config_delete_multivar(git_config_backend
*cfg
, const char *name
, const char *regexp
)
548 cvar_t
*var
, *prev
= NULL
, *new_head
= NULL
;
551 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
557 if ((result
= git_config__normalize_name(name
, &key
)) < 0)
560 pos
= git_strmap_lookup_index(b
->values
, key
);
562 if (!git_strmap_valid_index(b
->values
, pos
)) {
563 giterr_set(GITERR_CONFIG
, "Could not find key '%s' to delete", name
);
565 return GIT_ENOTFOUND
;
568 var
= git_strmap_value_at(b
->values
, pos
);
570 result
= regcomp(&preg
, regexp
, REG_EXTENDED
);
573 giterr_set_regex(&preg
, result
);
578 to_delete
= git__calloc(cvar_length(var
), sizeof(cvar_t
*));
579 GITERR_CHECK_ALLOC(to_delete
);
582 while (var
!= NULL
) {
583 cvar_t
*next
= var
->next
;
585 if (regexec(&preg
, var
->entry
->value
, 0, NULL
, 0) == 0) {
586 // If we are past the head, reattach previous node to next one,
587 // otherwise set the new head for the strmap.
594 to_delete
[to_delete_idx
++] = var
;
602 if (new_head
!= NULL
) {
603 git_strmap_set_value_at(b
->values
, pos
, new_head
);
605 git_strmap_delete_at(b
->values
, pos
);
608 if (to_delete_idx
> 0)
609 result
= config_write(b
, key
, &preg
, NULL
);
611 while (to_delete_idx
-- > 0)
612 cvar_free(to_delete
[to_delete_idx
]);
615 git__free(to_delete
);
620 int git_config_file__ondisk(git_config_backend
**out
, const char *path
)
622 diskfile_backend
*backend
;
624 backend
= git__calloc(1, sizeof(diskfile_backend
));
625 GITERR_CHECK_ALLOC(backend
);
627 backend
->parent
.version
= GIT_CONFIG_BACKEND_VERSION
;
629 backend
->file_path
= git__strdup(path
);
630 GITERR_CHECK_ALLOC(backend
->file_path
);
632 backend
->parent
.open
= config_open
;
633 backend
->parent
.get
= config_get
;
634 backend
->parent
.set
= config_set
;
635 backend
->parent
.set_multivar
= config_set_multivar
;
636 backend
->parent
.del
= config_delete
;
637 backend
->parent
.del_multivar
= config_delete_multivar
;
638 backend
->parent
.iterator
= config_iterator_new
;
639 backend
->parent
.refresh
= config_refresh
;
640 backend
->parent
.free
= backend_free
;
642 *out
= (git_config_backend
*)backend
;
647 static int reader_getchar_raw(struct reader
*reader
)
651 c
= *reader
->read_ptr
++;
654 Win 32 line breaks: if we find a \r\n sequence,
655 return only the \n as a newline
657 if (c
== '\r' && *reader
->read_ptr
== '\n') {
663 reader
->line_number
++;
673 #define SKIP_WHITESPACE (1 << 1)
674 #define SKIP_COMMENTS (1 << 2)
676 static int reader_getchar(struct reader
*reader
, int flags
)
678 const int skip_whitespace
= (flags
& SKIP_WHITESPACE
);
679 const int skip_comments
= (flags
& SKIP_COMMENTS
);
682 assert(reader
->read_ptr
);
685 c
= reader_getchar_raw(reader
);
686 } while (skip_whitespace
&& git__isspace(c
) &&
689 if (skip_comments
&& (c
== '#' || c
== ';')) {
691 c
= reader_getchar_raw(reader
);
699 * Read the next char, but don't move the reading pointer.
701 static int reader_peek(struct reader
*reader
, int flags
)
704 int old_lineno
, old_eof
;
707 assert(reader
->read_ptr
);
709 old_read_ptr
= reader
->read_ptr
;
710 old_lineno
= reader
->line_number
;
711 old_eof
= reader
->eof
;
713 ret
= reader_getchar(reader
, flags
);
715 reader
->read_ptr
= old_read_ptr
;
716 reader
->line_number
= old_lineno
;
717 reader
->eof
= old_eof
;
723 * Read and consume a line, returning it in newly-allocated memory.
725 static char *reader_readline(struct reader
*reader
, bool skip_whitespace
)
728 char *line_src
, *line_end
;
731 line_src
= reader
->read_ptr
;
733 if (skip_whitespace
) {
734 /* Skip empty empty lines */
735 while (git__isspace(*line_src
))
739 line_end
= strchr(line_src
, '\n');
741 /* no newline at EOF */
742 if (line_end
== NULL
)
743 line_end
= strchr(line_src
, 0);
745 line_len
= line_end
- line_src
;
747 line
= git__malloc(line_len
+ 1);
751 memcpy(line
, line_src
, line_len
);
753 do line
[line_len
] = '\0';
754 while (line_len
-- > 0 && git__isspace(line
[line_len
]));
756 if (*line_end
== '\n')
759 if (*line_end
== '\0')
762 reader
->line_number
++;
763 reader
->read_ptr
= line_end
;
769 * Consume a line, without storing it anywhere
771 static void reader_consume_line(struct reader
*reader
)
773 char *line_start
, *line_end
;
775 line_start
= reader
->read_ptr
;
776 line_end
= strchr(line_start
, '\n');
777 /* No newline at EOF */
778 if(line_end
== NULL
){
779 line_end
= strchr(line_start
, '\0');
782 if (*line_end
== '\n')
785 if (*line_end
== '\0')
788 reader
->line_number
++;
789 reader
->read_ptr
= line_end
;
792 GIT_INLINE(int) config_keychar(int c
)
794 return isalnum(c
) || c
== '-';
797 static int parse_section_header_ext(struct reader
*reader
, const char *line
, const char *base_name
, char **section_name
)
800 char *first_quote
, *last_quote
;
801 git_buf buf
= GIT_BUF_INIT
;
803 * base_name is what came before the space. We should be at the
804 * first quotation mark, except for now, line isn't being kept in
805 * sync so we only really use it to calculate the length.
808 first_quote
= strchr(line
, '"');
809 last_quote
= strrchr(line
, '"');
811 if (last_quote
- first_quote
== 0) {
812 set_parse_error(reader
, 0, "Missing closing quotation mark in section header");
816 git_buf_grow(&buf
, strlen(base_name
) + last_quote
- first_quote
+ 2);
817 git_buf_printf(&buf
, "%s.", base_name
);
825 * At the end of each iteration, whatever is stored in c will be
826 * added to the string. In case of error, jump to out
832 set_parse_error(reader
, 0, "Unexpected end-of-line in section header");
843 set_parse_error(reader
, rpos
, "Unexpected end-of-line in section header");
852 git_buf_putc(&buf
, (char)c
);
854 } while (line
+ rpos
< last_quote
);
857 if (line
[rpos
] != '"' || line
[rpos
+ 1] != ']') {
858 set_parse_error(reader
, rpos
, "Unexpected text after closing quotes");
863 *section_name
= git_buf_detach(&buf
);
867 static int parse_section_header(struct reader
*reader
, char **section_out
)
869 char *name
, *name_end
;
870 int name_length
, c
, pos
;
874 line
= reader_readline(reader
, true);
878 /* find the end of the variable's name */
879 name_end
= strrchr(line
, ']');
880 if (name_end
== NULL
) {
882 set_parse_error(reader
, 0, "Missing ']' in section header");
886 name
= (char *)git__malloc((size_t)(name_end
- line
) + 1);
887 GITERR_CHECK_ALLOC(name
);
892 /* Make sure we were given a section header */
899 if (git__isspace(c
)){
900 name
[name_length
] = '\0';
901 result
= parse_section_header_ext(reader
, line
, name
, section_out
);
907 if (!config_keychar(c
) && c
!= '.') {
908 set_parse_error(reader
, pos
, "Unexpected character in header");
912 name
[name_length
++] = (char) tolower(c
);
914 } while ((c
= line
[pos
++]) != ']');
916 if (line
[pos
- 1] != ']') {
917 set_parse_error(reader
, pos
, "Unexpected end of file");
923 name
[name_length
] = 0;
934 static int skip_bom(struct reader
*reader
)
937 int bom_offset
= git_buf_text_detect_bom(&bom
,
938 &reader
->buffer
, reader
->read_ptr
- reader
->buffer
.ptr
);
940 if (bom
== GIT_BOM_UTF8
)
941 reader
->read_ptr
+= bom_offset
;
943 /* TODO: reference implementation is pretty stupid with BoM */
951 integer = digit { digit }
952 alphabet = "a".."z" + "A" .. "Z"
954 section_char = alphabet | "." | "-"
955 extension_char = (* any character except newline *)
956 any_char = (* any character *)
957 variable_char = "alphabet" | "-"
963 section = header { definition }
965 header = "[" section [subsection | subsection_ext] "]"
967 subsection = "." section
968 subsection_ext = "\"" extension "\""
970 section = section_char { section_char }
971 extension = extension_char { extension_char }
973 definition = variable_name ["=" variable_value] "\n"
975 variable_name = variable_char { variable_char }
976 variable_value = string | boolean | integer
978 string = quoted_string | plain_string
979 quoted_string = "\"" plain_string "\""
980 plain_string = { any_char }
982 boolean = boolean_true | boolean_false
983 boolean_true = "yes" | "1" | "true" | "on"
984 boolean_false = "no" | "0" | "false" | "off"
987 static int strip_comments(char *line
, int in_quotes
)
989 int quote_count
= in_quotes
;
992 for (ptr
= line
; *ptr
; ++ptr
) {
993 if (ptr
[0] == '"' && ptr
> line
&& ptr
[-1] != '\\')
996 if ((ptr
[0] == ';' || ptr
[0] == '#') && (quote_count
% 2) == 0) {
1002 /* skip any space at the end */
1003 if (ptr
> line
&& git__isspace(ptr
[-1])) {
1011 static int included_path(git_buf
*out
, const char *dir
, const char *path
)
1013 /* From the user's home */
1014 if (path
[0] == '~' && path
[1] == '/')
1015 return git_futils_find_global_file(out
, &path
[1]);
1017 return git_path_join_unrooted(out
, path
, dir
, NULL
);
1020 static int config_parse(diskfile_backend
*cfg_file
, struct reader
*reader
, git_config_level_t level
, int depth
)
1023 char *current_section
= NULL
;
1026 cvar_t
*var
, *existing
;
1027 git_buf buf
= GIT_BUF_INIT
;
1030 uint32_t reader_idx
;
1032 if (depth
>= MAX_INCLUDE_DEPTH
) {
1033 giterr_set(GITERR_CONFIG
, "Maximum config include depth reached");
1037 reader_idx
= git_array_size(cfg_file
->readers
) - 1;
1038 /* Initialize the reading position */
1039 reader
->read_ptr
= reader
->buffer
.ptr
;
1042 /* If the file is empty, there's nothing for us to do */
1043 if (*reader
->read_ptr
== '\0')
1048 while (result
== 0 && !reader
->eof
) {
1050 c
= reader_peek(reader
, SKIP_WHITESPACE
);
1053 case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
1057 case '[': /* section header, new section begins */
1058 git__free(current_section
);
1059 current_section
= NULL
;
1060 result
= parse_section_header(reader
, ¤t_section
);
1065 reader_consume_line(reader
);
1068 default: /* assume variable declaration */
1069 result
= parse_variable(reader
, &var_name
, &var_value
);
1073 var
= git__malloc(sizeof(cvar_t
));
1074 GITERR_CHECK_ALLOC(var
);
1075 memset(var
, 0x0, sizeof(cvar_t
));
1076 var
->entry
= git__malloc(sizeof(git_config_entry
));
1077 GITERR_CHECK_ALLOC(var
->entry
);
1078 memset(var
->entry
, 0x0, sizeof(git_config_entry
));
1080 git__strtolower(var_name
);
1081 git_buf_printf(&buf
, "%s.%s", current_section
, var_name
);
1082 git__free(var_name
);
1084 if (git_buf_oom(&buf
))
1087 var
->entry
->name
= git_buf_detach(&buf
);
1088 var
->entry
->value
= var_value
;
1089 var
->entry
->level
= level
;
1090 var
->included
= !!depth
;
1092 /* Add or append the new config option */
1093 pos
= git_strmap_lookup_index(cfg_file
->values
, var
->entry
->name
);
1094 if (!git_strmap_valid_index(cfg_file
->values
, pos
)) {
1095 git_strmap_insert(cfg_file
->values
, var
->entry
->name
, var
, result
);
1100 existing
= git_strmap_value_at(cfg_file
->values
, pos
);
1101 while (existing
->next
!= NULL
) {
1102 existing
= existing
->next
;
1104 existing
->next
= var
;
1107 if (!git__strcmp(var
->entry
->name
, "include.path")) {
1109 git_buf path
= GIT_BUF_INIT
;
1113 r
= git_array_alloc(cfg_file
->readers
);
1114 /* The reader may have been reallocated */
1115 reader
= git_array_get(cfg_file
->readers
, reader_idx
);
1116 memset(r
, 0, sizeof(struct reader
));
1117 if ((result
= git_path_dirname_r(&path
, reader
->file_path
)) < 0)
1120 /* We need to know out index in the array, as the next config_parse call may realloc */
1121 index
= git_array_size(cfg_file
->readers
) - 1;
1122 dir
= git_buf_detach(&path
);
1123 result
= included_path(&path
, dir
, var
->entry
->value
);
1129 r
->file_path
= git_buf_detach(&path
);
1130 git_buf_init(&r
->buffer
, 0);
1131 if ((result
= git_futils_readbuffer_updated(&r
->buffer
, r
->file_path
, &r
->file_mtime
,
1132 &r
->file_size
, NULL
)) < 0)
1135 result
= config_parse(cfg_file
, r
, level
, depth
+1);
1136 r
= git_array_get(cfg_file
->readers
, index
);
1137 git_buf_free(&r
->buffer
);
1147 git__free(current_section
);
1151 static int write_section(git_filebuf
*file
, const char *key
)
1155 git_buf buf
= GIT_BUF_INIT
;
1157 /* All of this just for [section "subsection"] */
1158 dot
= strchr(key
, '.');
1159 git_buf_putc(&buf
, '[');
1161 git_buf_puts(&buf
, key
);
1164 git_buf_put(&buf
, key
, dot
- key
);
1165 escaped
= escape_value(dot
+ 1);
1166 GITERR_CHECK_ALLOC(escaped
);
1167 git_buf_printf(&buf
, " \"%s\"", escaped
);
1170 git_buf_puts(&buf
, "]\n");
1172 if (git_buf_oom(&buf
))
1175 result
= git_filebuf_write(file
, git_buf_cstr(&buf
), buf
.size
);
1182 * This is pretty much the parsing, except we write out anything we don't have
1184 static int config_write(diskfile_backend
*cfg
, const char *key
, const regex_t
*preg
, const char* value
)
1187 int section_matches
= 0, last_section_matched
= 0, preg_replaced
= 0, write_trailer
= 0;
1188 const char *pre_end
= NULL
, *post_start
= NULL
, *data_start
;
1189 char *current_section
= NULL
, *section
, *name
, *ldot
;
1190 git_filebuf file
= GIT_FILEBUF_INIT
;
1191 struct reader
*reader
= git_array_get(cfg
->readers
, 0);
1193 /* We need to read in our own config file */
1194 result
= git_futils_readbuffer(&reader
->buffer
, cfg
->file_path
);
1196 /* Initialise the reading position */
1197 if (result
== GIT_ENOTFOUND
) {
1198 reader
->read_ptr
= NULL
;
1201 git_buf_clear(&reader
->buffer
);
1202 } else if (result
== 0) {
1203 reader
->read_ptr
= reader
->buffer
.ptr
;
1205 data_start
= reader
->read_ptr
;
1207 return -1; /* OS error when reading the file */
1211 if (git_filebuf_open(&file
, cfg
->file_path
, 0) < 0)
1215 ldot
= strrchr(key
, '.');
1217 section
= git__strndup(key
, ldot
- key
);
1219 while (!reader
->eof
) {
1220 c
= reader_peek(reader
, SKIP_WHITESPACE
);
1222 if (c
== '\0') { /* We've arrived at the end of the file */
1225 } else if (c
== '[') { /* section header, new section begins */
1227 * We set both positions to the current one in case we
1228 * need to add a variable to the end of a section. In that
1229 * case, we want both variables to point just before the
1230 * new section. If we actually want to replace it, the
1231 * default case will take care of updating them.
1233 pre_end
= post_start
= reader
->read_ptr
;
1235 git__free(current_section
);
1236 current_section
= NULL
;
1237 if (parse_section_header(reader
, ¤t_section
) < 0)
1240 /* Keep track of when it stops matching */
1241 last_section_matched
= section_matches
;
1242 section_matches
= !strcmp(current_section
, section
);
1245 else if (c
== ';' || c
== '#') {
1246 reader_consume_line(reader
);
1251 * If the section doesn't match, but the last section did,
1252 * it means we need to add a variable (so skip the line
1253 * otherwise). If both the section and name match, we need
1254 * to overwrite the variable (so skip the line
1255 * otherwise). pre_end needs to be updated each time so we
1256 * don't loose that information, but we only need to
1257 * update post_start if we're going to use it in this
1260 if (!section_matches
) {
1261 if (!last_section_matched
) {
1262 reader_consume_line(reader
);
1266 int has_matched
= 0;
1267 char *var_name
, *var_value
;
1269 pre_end
= reader
->read_ptr
;
1270 if (parse_variable(reader
, &var_name
, &var_value
) < 0)
1273 /* First try to match the name of the variable */
1274 if (strcasecmp(name
, var_name
) == 0)
1277 /* If the name matches, and we have a regex to match the
1278 * value, try to match it */
1279 if (has_matched
&& preg
!= NULL
)
1280 has_matched
= (regexec(preg
, var_value
, 0, NULL
, 0) == 0);
1282 git__free(var_name
);
1283 git__free(var_value
);
1285 /* if there is no match, keep going */
1289 post_start
= reader
->read_ptr
;
1292 /* We've found the variable we wanted to change, so
1293 * write anything up to it */
1294 git_filebuf_write(&file
, data_start
, pre_end
- data_start
);
1297 /* Then replace the variable. If the value is NULL, it
1298 * means we want to delete it, so don't write anything. */
1299 if (value
!= NULL
) {
1300 git_filebuf_printf(&file
, "\t%s = %s\n", name
, value
);
1303 /* multiline variable? we need to keep reading lines to match */
1304 if (preg
!= NULL
&& section_matches
) {
1305 data_start
= post_start
;
1310 break; /* break from the loop */
1315 * Being here can mean that
1317 * 1) our section is the last one in the file and we're
1320 * 2) we didn't find a section for us so we need to create it
1323 * 3) we're setting a multivar with a regex, which means we
1324 * continue to search for matching values
1326 * In the last case, if we've already replaced a value, we
1327 * want to write the rest of the file. Otherwise we need to write
1328 * out the whole file and then the new variable.
1330 if (write_trailer
) {
1331 /* Write out rest of the file */
1332 git_filebuf_write(&file
, post_start
, reader
->buffer
.size
- (post_start
- data_start
));
1334 if (preg_replaced
) {
1335 git_filebuf_printf(&file
, "\n%s", data_start
);
1337 git_filebuf_write(&file
, reader
->buffer
.ptr
, reader
->buffer
.size
);
1339 /* And now if we just need to add a variable */
1340 if (!section_matches
&& write_section(&file
, section
) < 0)
1343 /* Sanity check: if we are here, and value is NULL, that means that somebody
1344 * touched the config file after our intial read. We should probably assert()
1345 * this, but instead we'll handle it gracefully with an error. */
1346 if (value
== NULL
) {
1347 giterr_set(GITERR_CONFIG
,
1348 "Race condition when writing a config file (a cvar has been removed)");
1352 /* If we are here, there is at least a section line */
1353 if (reader
->buffer
.size
> 0 && *(reader
->buffer
.ptr
+ reader
->buffer
.size
- 1) != '\n')
1354 git_filebuf_write(&file
, "\n", 1);
1356 git_filebuf_printf(&file
, "\t%s = %s\n", name
, value
);
1361 git__free(current_section
);
1363 /* refresh stats - if this errors, then commit will error too */
1364 (void)git_filebuf_stats(&reader
->file_mtime
, &reader
->file_size
, &file
);
1366 result
= git_filebuf_commit(&file
, GIT_CONFIG_FILE_MODE
);
1367 git_buf_free(&reader
->buffer
);
1373 git__free(current_section
);
1375 git_filebuf_cleanup(&file
);
1376 git_buf_free(&reader
->buffer
);
1380 static const char *escapes
= "ntb\"\\";
1381 static const char *escaped
= "\n\t\b\"\\";
1383 /* Escape the values to write them to the file */
1384 static char *escape_value(const char *ptr
)
1386 git_buf buf
= GIT_BUF_INIT
;
1394 return git__calloc(1, sizeof(char));
1396 git_buf_grow(&buf
, len
);
1398 while (*ptr
!= '\0') {
1399 if ((esc
= strchr(escaped
, *ptr
)) != NULL
) {
1400 git_buf_putc(&buf
, '\\');
1401 git_buf_putc(&buf
, escapes
[esc
- escaped
]);
1403 git_buf_putc(&buf
, *ptr
);
1408 if (git_buf_oom(&buf
)) {
1413 return git_buf_detach(&buf
);
1416 /* '\"' -> '"' etc */
1417 static char *fixup_line(const char *ptr
, int quote_count
)
1419 char *str
= git__malloc(strlen(ptr
) + 1);
1420 char *out
= str
, *esc
;
1425 while (*ptr
!= '\0') {
1428 } else if (*ptr
!= '\\') {
1431 /* backslash, check the next char */
1433 /* if we're at the end, it's a multiline, so keep the backslash */
1438 if ((esc
= strchr(escapes
, *ptr
)) != NULL
) {
1439 *out
++ = escaped
[esc
- escapes
];
1442 giterr_set(GITERR_CONFIG
, "Invalid escape at %s", ptr
);
1455 static int is_multiline_var(const char *str
)
1458 const char *end
= str
+ strlen(str
);
1459 while (end
> str
&& end
[-1] == '\\') {
1464 /* An odd number means last backslash wasn't escaped, so it's multiline */
1465 return (end
> str
) && (count
& 1);
1468 static int parse_multiline_variable(struct reader
*reader
, git_buf
*value
, int in_quotes
)
1470 char *line
= NULL
, *proc_line
= NULL
;
1473 /* Check that the next line exists */
1474 line
= reader_readline(reader
, false);
1478 /* We've reached the end of the file, there is input missing */
1479 if (line
[0] == '\0') {
1480 set_parse_error(reader
, 0, "Unexpected end of file while parsing multine var");
1485 quote_count
= strip_comments(line
, !!in_quotes
);
1487 /* If it was just a comment, pretend it didn't exist */
1488 if (line
[0] == '\0') {
1490 return parse_multiline_variable(reader
, value
, quote_count
);
1491 /* TODO: unbounded recursion. This **could** be exploitable */
1494 /* Drop the continuation character '\': to closely follow the UNIX
1495 * standard, this character **has** to be last one in the buf, with
1496 * no whitespace after it */
1497 assert(is_multiline_var(value
->ptr
));
1498 git_buf_shorten(value
, 1);
1500 proc_line
= fixup_line(line
, in_quotes
);
1501 if (proc_line
== NULL
) {
1505 /* add this line to the multiline var */
1506 git_buf_puts(value
, proc_line
);
1508 git__free(proc_line
);
1511 * If we need to continue reading the next line, let's just
1512 * keep putting stuff in the buffer
1514 if (is_multiline_var(value
->ptr
))
1515 return parse_multiline_variable(reader
, value
, quote_count
);
1520 static int parse_variable(struct reader
*reader
, char **var_name
, char **var_value
)
1522 const char *var_end
= NULL
;
1523 const char *value_start
= NULL
;
1527 line
= reader_readline(reader
, true);
1531 quote_count
= strip_comments(line
, 0);
1533 var_end
= strchr(line
, '=');
1535 if (var_end
== NULL
)
1536 var_end
= strchr(line
, '\0');
1538 value_start
= var_end
+ 1;
1541 while (var_end
>line
&& git__isspace(*var_end
));
1543 *var_name
= git__strndup(line
, var_end
- line
+ 1);
1544 GITERR_CHECK_ALLOC(*var_name
);
1546 /* If there is no value, boolean true is assumed */
1550 * Now, let's try to parse the value
1552 if (value_start
!= NULL
) {
1553 while (git__isspace(value_start
[0]))
1556 if (is_multiline_var(value_start
)) {
1557 git_buf multi_value
= GIT_BUF_INIT
;
1558 char *proc_line
= fixup_line(value_start
, 0);
1559 GITERR_CHECK_ALLOC(proc_line
);
1560 git_buf_puts(&multi_value
, proc_line
);
1561 git__free(proc_line
);
1562 if (parse_multiline_variable(reader
, &multi_value
, quote_count
) < 0 || git_buf_oom(&multi_value
)) {
1563 git__free(*var_name
);
1565 git_buf_free(&multi_value
);
1569 *var_value
= git_buf_detach(&multi_value
);
1572 else if (value_start
[0] != '\0') {
1573 *var_value
= fixup_line(value_start
, 0);
1574 GITERR_CHECK_ALLOC(*var_value
);
1575 } else { /* equals sign but missing rhs */
1576 *var_value
= git__strdup("");
1577 GITERR_CHECK_ALLOC(*var_value
);