2 * Copyright (C) 2009-2012 the libgit2 contributors
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/types.h"
19 #include <sys/types.h>
24 typedef struct cvar_t
{
26 git_config_entry
*entry
;
29 #define CVAR_LIST_HEAD(list) ((list)->head)
31 #define CVAR_LIST_TAIL(list) ((list)->tail)
33 #define CVAR_LIST_NEXT(var) ((var)->next)
35 #define CVAR_LIST_EMPTY(list) ((list)->head == NULL)
37 #define CVAR_LIST_APPEND(list, var) do {\
38 if (CVAR_LIST_EMPTY(list)) {\
39 CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
41 CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
42 CVAR_LIST_TAIL(list) = var;\
46 #define CVAR_LIST_REMOVE_HEAD(list) do {\
47 CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
50 #define CVAR_LIST_REMOVE_AFTER(var) do {\
51 CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
54 #define CVAR_LIST_FOREACH(list, iter)\
55 for ((iter) = CVAR_LIST_HEAD(list);\
57 (iter) = CVAR_LIST_NEXT(iter))
60 * Inspired by the FreeBSD functions
62 #define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
63 for ((iter) = CVAR_LIST_HEAD(vars);\
64 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
68 git_config_backend parent
;
86 static int config_parse(diskfile_backend
*cfg_file
, unsigned int level
);
87 static int parse_variable(diskfile_backend
*cfg
, char **var_name
, char **var_value
);
88 static int config_write(diskfile_backend
*cfg
, const char *key
, const regex_t
*preg
, const char *value
);
89 static char *escape_value(const char *ptr
);
91 static void set_parse_error(diskfile_backend
*backend
, int col
, const char *error_str
)
93 giterr_set(GITERR_CONFIG
, "Failed to parse config file: %s (in %s:%d, column %d)",
94 error_str
, backend
->file_path
, backend
->reader
.line_number
, col
);
97 static void cvar_free(cvar_t
*var
)
102 git__free((char*)var
->entry
->name
);
103 git__free((char *)var
->entry
->value
);
104 git__free(var
->entry
);
108 /* Take something the user gave us and make it nice for our hash function */
109 static int normalize_name(const char *in
, char **out
)
111 char *name
, *fdot
, *ldot
;
115 name
= git__strdup(in
);
116 GITERR_CHECK_ALLOC(name
);
118 fdot
= strchr(name
, '.');
119 ldot
= strrchr(name
, '.');
121 if (fdot
== NULL
|| ldot
== NULL
) {
123 giterr_set(GITERR_CONFIG
,
124 "Invalid variable name: '%s'", in
);
128 /* Downcase up to the first dot and after the last one */
129 git__strntolower(name
, fdot
- name
);
130 git__strtolower(ldot
);
136 static void free_vars(git_strmap
*values
)
143 git_strmap_foreach_value(values
, var
,
144 while (var
!= NULL
) {
145 cvar_t
*next
= CVAR_LIST_NEXT(var
);
150 git_strmap_free(values
);
153 static int config_open(git_config_backend
*cfg
, unsigned int level
)
156 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
160 b
->values
= git_strmap_alloc();
161 GITERR_CHECK_ALLOC(b
->values
);
163 git_buf_init(&b
->reader
.buffer
, 0);
164 res
= git_futils_readbuffer_updated(
165 &b
->reader
.buffer
, b
->file_path
, &b
->file_mtime
, &b
->file_size
, NULL
);
167 /* It's fine if the file doesn't exist */
168 if (res
== GIT_ENOTFOUND
)
171 if (res
< 0 || (res
= config_parse(b
, level
)) < 0) {
172 free_vars(b
->values
);
176 git_buf_free(&b
->reader
.buffer
);
180 static int config_refresh(git_config_backend
*cfg
)
182 int res
, updated
= 0;
183 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
184 git_strmap
*old_values
;
186 res
= git_futils_readbuffer_updated(
187 &b
->reader
.buffer
, b
->file_path
, &b
->file_mtime
, &b
->file_size
, &updated
);
188 if (res
< 0 || !updated
)
189 return (res
== GIT_ENOTFOUND
) ? 0 : res
;
191 /* need to reload - store old values and prep for reload */
192 old_values
= b
->values
;
193 b
->values
= git_strmap_alloc();
194 GITERR_CHECK_ALLOC(b
->values
);
196 if ((res
= config_parse(b
, b
->level
)) < 0) {
197 free_vars(b
->values
);
198 b
->values
= old_values
;
200 free_vars(old_values
);
203 git_buf_free(&b
->reader
.buffer
);
207 static void backend_free(git_config_backend
*_backend
)
209 diskfile_backend
*backend
= (diskfile_backend
*)_backend
;
214 git__free(backend
->file_path
);
215 free_vars(backend
->values
);
219 static int file_foreach(
220 git_config_backend
*backend
,
222 int (*fn
)(const git_config_entry
*, void *),
225 diskfile_backend
*b
= (diskfile_backend
*)backend
;
226 cvar_t
*var
, *next_var
;
234 if (regexp
!= NULL
) {
235 if ((result
= regcomp(®ex
, regexp
, REG_EXTENDED
)) < 0) {
236 giterr_set_regex(®ex
, result
);
242 git_strmap_foreach(b
->values
, key
, var
,
243 for (; var
!= NULL
; var
= next_var
) {
244 next_var
= CVAR_LIST_NEXT(var
);
246 /* skip non-matching keys if regexp was provided */
247 if (regexp
&& regexec(®ex
, key
, 0, NULL
, 0) != 0)
250 /* abort iterator on non-zero return value */
251 if (fn(var
->entry
, data
)) {
266 static int config_set(git_config_backend
*cfg
, const char *name
, const char *value
)
268 cvar_t
*var
= NULL
, *old_var
;
269 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
270 char *key
, *esc_value
= NULL
;
274 if (normalize_name(name
, &key
) < 0)
278 * Try to find it in the existing values and update it if it
279 * only has one value.
281 pos
= git_strmap_lookup_index(b
->values
, key
);
282 if (git_strmap_valid_index(b
->values
, pos
)) {
283 cvar_t
*existing
= git_strmap_value_at(b
->values
, pos
);
288 if (existing
->next
!= NULL
) {
289 giterr_set(GITERR_CONFIG
, "Multivar incompatible with simple set");
293 /* don't update if old and new values already match */
294 if ((!existing
->entry
->value
&& !value
) ||
295 (existing
->entry
->value
&& value
&& !strcmp(existing
->entry
->value
, value
)))
299 tmp
= git__strdup(value
);
300 GITERR_CHECK_ALLOC(tmp
);
301 esc_value
= escape_value(value
);
302 GITERR_CHECK_ALLOC(esc_value
);
305 git__free((void *)existing
->entry
->value
);
306 existing
->entry
->value
= tmp
;
308 ret
= config_write(b
, existing
->entry
->name
, NULL
, esc_value
);
310 git__free(esc_value
);
314 var
= git__malloc(sizeof(cvar_t
));
315 GITERR_CHECK_ALLOC(var
);
316 memset(var
, 0x0, sizeof(cvar_t
));
317 var
->entry
= git__malloc(sizeof(git_config_entry
));
318 GITERR_CHECK_ALLOC(var
->entry
);
319 memset(var
->entry
, 0x0, sizeof(git_config_entry
));
321 var
->entry
->name
= key
;
322 var
->entry
->value
= NULL
;
325 var
->entry
->value
= git__strdup(value
);
326 GITERR_CHECK_ALLOC(var
->entry
->value
);
327 esc_value
= escape_value(value
);
328 GITERR_CHECK_ALLOC(esc_value
);
331 if (config_write(b
, key
, NULL
, esc_value
) < 0) {
332 git__free(esc_value
);
337 git__free(esc_value
);
338 git_strmap_insert2(b
->values
, key
, var
, old_var
, rval
);
348 * Internal function that actually gets the value in string form
350 static int config_get(const git_config_backend
*cfg
, const char *name
, const git_config_entry
**out
)
352 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
356 if (normalize_name(name
, &key
) < 0)
359 pos
= git_strmap_lookup_index(b
->values
, key
);
362 /* no error message; the config system will write one */
363 if (!git_strmap_valid_index(b
->values
, pos
))
364 return GIT_ENOTFOUND
;
366 *out
= ((cvar_t
*)git_strmap_value_at(b
->values
, pos
))->entry
;
371 static int config_get_multivar(
372 git_config_backend
*cfg
,
374 const char *regex_str
,
375 int (*fn
)(const git_config_entry
*, void *),
379 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
383 if (normalize_name(name
, &key
) < 0)
386 pos
= git_strmap_lookup_index(b
->values
, key
);
389 if (!git_strmap_valid_index(b
->values
, pos
))
390 return GIT_ENOTFOUND
;
392 var
= git_strmap_value_at(b
->values
, pos
);
394 if (regex_str
!= NULL
) {
398 /* regex matching; build the regex */
399 result
= regcomp(®ex
, regex_str
, REG_EXTENDED
);
401 giterr_set_regex(®ex
, result
);
406 /* and throw the callback only on the variables that
409 if (regexec(®ex
, var
->entry
->value
, 0, NULL
, 0) == 0) {
410 /* early termination by the user is not an error;
411 * just break and return successfully */
412 if (fn(var
->entry
, data
) < 0)
417 } while (var
!= NULL
);
420 /* no regex; go through all the variables */
422 /* early termination by the user is not an error;
423 * just break and return successfully */
424 if (fn(var
->entry
, data
) < 0)
428 } while (var
!= NULL
);
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 (normalize_name(name
, &key
) < 0)
450 pos
= git_strmap_lookup_index(b
->values
, key
);
451 if (!git_strmap_valid_index(b
->values
, pos
)) {
453 return GIT_ENOTFOUND
;
456 var
= git_strmap_value_at(b
->values
, pos
);
458 result
= regcomp(&preg
, regexp
, REG_EXTENDED
);
461 giterr_set_regex(&preg
, result
);
467 if (regexec(&preg
, var
->entry
->value
, 0, NULL
, 0) == 0) {
468 char *tmp
= git__strdup(value
);
469 GITERR_CHECK_ALLOC(tmp
);
471 git__free((void *)var
->entry
->value
);
472 var
->entry
->value
= tmp
;
476 if (var
->next
== NULL
)
482 /* If we've reached the end of the variables and we haven't found it yet, we need to append it */
484 newvar
= git__malloc(sizeof(cvar_t
));
485 GITERR_CHECK_ALLOC(newvar
);
486 memset(newvar
, 0x0, sizeof(cvar_t
));
487 newvar
->entry
= git__malloc(sizeof(git_config_entry
));
488 GITERR_CHECK_ALLOC(newvar
->entry
);
489 memset(newvar
->entry
, 0x0, sizeof(git_config_entry
));
491 newvar
->entry
->name
= git__strdup(var
->entry
->name
);
492 GITERR_CHECK_ALLOC(newvar
->entry
->name
);
494 newvar
->entry
->value
= git__strdup(value
);
495 GITERR_CHECK_ALLOC(newvar
->entry
->value
);
497 newvar
->entry
->level
= var
->entry
->level
;
502 result
= config_write(b
, key
, &preg
, value
);
510 static int config_delete(git_config_backend
*cfg
, const char *name
)
513 diskfile_backend
*b
= (diskfile_backend
*)cfg
;
518 if (normalize_name(name
, &key
) < 0)
521 pos
= git_strmap_lookup_index(b
->values
, key
);
524 if (!git_strmap_valid_index(b
->values
, pos
)) {
525 giterr_set(GITERR_CONFIG
, "Could not find key '%s' to delete", name
);
526 return GIT_ENOTFOUND
;
529 var
= git_strmap_value_at(b
->values
, pos
);
531 if (var
->next
!= NULL
) {
532 giterr_set(GITERR_CONFIG
, "Cannot delete multivar with a single delete");
536 git_strmap_delete_at(b
->values
, pos
);
538 result
= config_write(b
, var
->entry
->name
, NULL
, NULL
);
544 int git_config_file__ondisk(git_config_backend
**out
, const char *path
)
546 diskfile_backend
*backend
;
548 backend
= git__calloc(1, sizeof(diskfile_backend
));
549 GITERR_CHECK_ALLOC(backend
);
551 backend
->parent
.version
= GIT_CONFIG_BACKEND_VERSION
;
553 backend
->file_path
= git__strdup(path
);
554 GITERR_CHECK_ALLOC(backend
->file_path
);
556 backend
->parent
.open
= config_open
;
557 backend
->parent
.get
= config_get
;
558 backend
->parent
.get_multivar
= config_get_multivar
;
559 backend
->parent
.set
= config_set
;
560 backend
->parent
.set_multivar
= config_set_multivar
;
561 backend
->parent
.del
= config_delete
;
562 backend
->parent
.foreach
= file_foreach
;
563 backend
->parent
.refresh
= config_refresh
;
564 backend
->parent
.free
= backend_free
;
566 *out
= (git_config_backend
*)backend
;
571 static int cfg_getchar_raw(diskfile_backend
*cfg
)
575 c
= *cfg
->reader
.read_ptr
++;
578 Win 32 line breaks: if we find a \r\n sequence,
579 return only the \n as a newline
581 if (c
== '\r' && *cfg
->reader
.read_ptr
== '\n') {
582 cfg
->reader
.read_ptr
++;
587 cfg
->reader
.line_number
++;
597 #define SKIP_WHITESPACE (1 << 1)
598 #define SKIP_COMMENTS (1 << 2)
600 static int cfg_getchar(diskfile_backend
*cfg_file
, int flags
)
602 const int skip_whitespace
= (flags
& SKIP_WHITESPACE
);
603 const int skip_comments
= (flags
& SKIP_COMMENTS
);
606 assert(cfg_file
->reader
.read_ptr
);
608 do c
= cfg_getchar_raw(cfg_file
);
609 while (skip_whitespace
&& git__isspace(c
) &&
610 !cfg_file
->reader
.eof
);
612 if (skip_comments
&& (c
== '#' || c
== ';')) {
613 do c
= cfg_getchar_raw(cfg_file
);
621 * Read the next char, but don't move the reading pointer.
623 static int cfg_peek(diskfile_backend
*cfg
, int flags
)
626 int old_lineno
, old_eof
;
629 assert(cfg
->reader
.read_ptr
);
631 old_read_ptr
= cfg
->reader
.read_ptr
;
632 old_lineno
= cfg
->reader
.line_number
;
633 old_eof
= cfg
->reader
.eof
;
635 ret
= cfg_getchar(cfg
, flags
);
637 cfg
->reader
.read_ptr
= old_read_ptr
;
638 cfg
->reader
.line_number
= old_lineno
;
639 cfg
->reader
.eof
= old_eof
;
645 * Read and consume a line, returning it in newly-allocated memory.
647 static char *cfg_readline(diskfile_backend
*cfg
, bool skip_whitespace
)
650 char *line_src
, *line_end
;
653 line_src
= cfg
->reader
.read_ptr
;
655 if (skip_whitespace
) {
656 /* Skip empty empty lines */
657 while (git__isspace(*line_src
))
661 line_end
= strchr(line_src
, '\n');
663 /* no newline at EOF */
664 if (line_end
== NULL
)
665 line_end
= strchr(line_src
, 0);
667 line_len
= line_end
- line_src
;
669 line
= git__malloc(line_len
+ 1);
673 memcpy(line
, line_src
, line_len
);
675 do line
[line_len
] = '\0';
676 while (line_len
-- > 0 && git__isspace(line
[line_len
]));
678 if (*line_end
== '\n')
681 if (*line_end
== '\0')
684 cfg
->reader
.line_number
++;
685 cfg
->reader
.read_ptr
= line_end
;
691 * Consume a line, without storing it anywhere
693 static void cfg_consume_line(diskfile_backend
*cfg
)
695 char *line_start
, *line_end
;
697 line_start
= cfg
->reader
.read_ptr
;
698 line_end
= strchr(line_start
, '\n');
699 /* No newline at EOF */
700 if(line_end
== NULL
){
701 line_end
= strchr(line_start
, '\0');
704 if (*line_end
== '\n')
707 if (*line_end
== '\0')
710 cfg
->reader
.line_number
++;
711 cfg
->reader
.read_ptr
= line_end
;
714 GIT_INLINE(int) config_keychar(int c
)
716 return isalnum(c
) || c
== '-';
719 static int parse_section_header_ext(diskfile_backend
*cfg
, const char *line
, const char *base_name
, char **section_name
)
722 char *first_quote
, *last_quote
;
723 git_buf buf
= GIT_BUF_INIT
;
726 * base_name is what came before the space. We should be at the
727 * first quotation mark, except for now, line isn't being kept in
728 * sync so we only really use it to calculate the length.
731 first_quote
= strchr(line
, '"');
732 last_quote
= strrchr(line
, '"');
734 if (last_quote
- first_quote
== 0) {
735 set_parse_error(cfg
, 0, "Missing closing quotation mark in section header");
739 git_buf_grow(&buf
, strlen(base_name
) + last_quote
- first_quote
+ 2);
740 git_buf_printf(&buf
, "%s.", base_name
);
749 * At the end of each iteration, whatever is stored in c will be
750 * added to the string. In case of error, jump to out
753 if (quote_marks
== 2) {
754 set_parse_error(cfg
, rpos
, "Unexpected text after closing quotes");
773 set_parse_error(cfg
, rpos
, "Unsupported escape sequence");
782 git_buf_putc(&buf
, c
);
783 } while ((c
= line
[rpos
++]) != ']');
785 *section_name
= git_buf_detach(&buf
);
789 static int parse_section_header(diskfile_backend
*cfg
, char **section_out
)
791 char *name
, *name_end
;
792 int name_length
, c
, pos
;
796 line
= cfg_readline(cfg
, true);
800 /* find the end of the variable's name */
801 name_end
= strchr(line
, ']');
802 if (name_end
== NULL
) {
804 set_parse_error(cfg
, 0, "Missing ']' in section header");
808 name
= (char *)git__malloc((size_t)(name_end
- line
) + 1);
809 GITERR_CHECK_ALLOC(name
);
814 /* Make sure we were given a section header */
821 if (git__isspace(c
)){
822 name
[name_length
] = '\0';
823 result
= parse_section_header_ext(cfg
, line
, name
, section_out
);
829 if (!config_keychar(c
) && c
!= '.') {
830 set_parse_error(cfg
, pos
, "Unexpected character in header");
834 name
[name_length
++] = (char) tolower(c
);
836 } while ((c
= line
[pos
++]) != ']');
838 if (line
[pos
- 1] != ']') {
839 set_parse_error(cfg
, pos
, "Unexpected end of file");
845 name
[name_length
] = 0;
856 static int skip_bom(diskfile_backend
*cfg
)
859 int bom_offset
= git_buf_text_detect_bom(&bom
,
860 &cfg
->reader
.buffer
, cfg
->reader
.read_ptr
- cfg
->reader
.buffer
.ptr
);
862 if (bom
== GIT_BOM_UTF8
)
863 cfg
->reader
.read_ptr
+= bom_offset
;
865 /* TODO: reference implementation is pretty stupid with BoM */
873 integer = digit { digit }
874 alphabet = "a".."z" + "A" .. "Z"
876 section_char = alphabet | "." | "-"
877 extension_char = (* any character except newline *)
878 any_char = (* any character *)
879 variable_char = "alphabet" | "-"
885 section = header { definition }
887 header = "[" section [subsection | subsection_ext] "]"
889 subsection = "." section
890 subsection_ext = "\"" extension "\""
892 section = section_char { section_char }
893 extension = extension_char { extension_char }
895 definition = variable_name ["=" variable_value] "\n"
897 variable_name = variable_char { variable_char }
898 variable_value = string | boolean | integer
900 string = quoted_string | plain_string
901 quoted_string = "\"" plain_string "\""
902 plain_string = { any_char }
904 boolean = boolean_true | boolean_false
905 boolean_true = "yes" | "1" | "true" | "on"
906 boolean_false = "no" | "0" | "false" | "off"
909 static int strip_comments(char *line
, int in_quotes
)
911 int quote_count
= in_quotes
;
914 for (ptr
= line
; *ptr
; ++ptr
) {
915 if (ptr
[0] == '"' && ptr
> line
&& ptr
[-1] != '\\')
918 if ((ptr
[0] == ';' || ptr
[0] == '#') && (quote_count
% 2) == 0) {
924 /* skip any space at the end */
925 if (ptr
> line
&& git__isspace(ptr
[-1])) {
933 static int config_parse(diskfile_backend
*cfg_file
, unsigned int level
)
936 char *current_section
= NULL
;
939 cvar_t
*var
, *existing
;
940 git_buf buf
= GIT_BUF_INIT
;
944 /* Initialize the reading position */
945 cfg_file
->reader
.read_ptr
= cfg_file
->reader
.buffer
.ptr
;
946 cfg_file
->reader
.eof
= 0;
948 /* If the file is empty, there's nothing for us to do */
949 if (*cfg_file
->reader
.read_ptr
== '\0')
954 while (result
== 0 && !cfg_file
->reader
.eof
) {
956 c
= cfg_peek(cfg_file
, SKIP_WHITESPACE
);
959 case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
960 cfg_file
->reader
.eof
= 1;
963 case '[': /* section header, new section begins */
964 git__free(current_section
);
965 current_section
= NULL
;
966 result
= parse_section_header(cfg_file
, ¤t_section
);
971 cfg_consume_line(cfg_file
);
974 default: /* assume variable declaration */
975 result
= parse_variable(cfg_file
, &var_name
, &var_value
);
979 var
= git__malloc(sizeof(cvar_t
));
980 GITERR_CHECK_ALLOC(var
);
981 memset(var
, 0x0, sizeof(cvar_t
));
982 var
->entry
= git__malloc(sizeof(git_config_entry
));
983 GITERR_CHECK_ALLOC(var
->entry
);
984 memset(var
->entry
, 0x0, sizeof(git_config_entry
));
986 git__strtolower(var_name
);
987 git_buf_printf(&buf
, "%s.%s", current_section
, var_name
);
990 if (git_buf_oom(&buf
))
993 var
->entry
->name
= git_buf_detach(&buf
);
994 var
->entry
->value
= var_value
;
995 var
->entry
->level
= level
;
997 /* Add or append the new config option */
998 pos
= git_strmap_lookup_index(cfg_file
->values
, var
->entry
->name
);
999 if (!git_strmap_valid_index(cfg_file
->values
, pos
)) {
1000 git_strmap_insert(cfg_file
->values
, var
->entry
->name
, var
, result
);
1005 existing
= git_strmap_value_at(cfg_file
->values
, pos
);
1006 while (existing
->next
!= NULL
) {
1007 existing
= existing
->next
;
1009 existing
->next
= var
;
1016 git__free(current_section
);
1020 static int write_section(git_filebuf
*file
, const char *key
)
1024 git_buf buf
= GIT_BUF_INIT
;
1026 /* All of this just for [section "subsection"] */
1027 dot
= strchr(key
, '.');
1028 git_buf_putc(&buf
, '[');
1030 git_buf_puts(&buf
, key
);
1033 git_buf_put(&buf
, key
, dot
- key
);
1034 escaped
= escape_value(dot
+ 1);
1035 GITERR_CHECK_ALLOC(escaped
);
1036 git_buf_printf(&buf
, " \"%s\"", escaped
);
1039 git_buf_puts(&buf
, "]\n");
1041 if (git_buf_oom(&buf
))
1044 result
= git_filebuf_write(file
, git_buf_cstr(&buf
), buf
.size
);
1051 * This is pretty much the parsing, except we write out anything we don't have
1053 static int config_write(diskfile_backend
*cfg
, const char *key
, const regex_t
*preg
, const char* value
)
1056 int section_matches
= 0, last_section_matched
= 0, preg_replaced
= 0, write_trailer
= 0;
1057 const char *pre_end
= NULL
, *post_start
= NULL
, *data_start
;
1058 char *current_section
= NULL
, *section
, *name
, *ldot
;
1059 git_filebuf file
= GIT_FILEBUF_INIT
;
1061 /* We need to read in our own config file */
1062 result
= git_futils_readbuffer(&cfg
->reader
.buffer
, cfg
->file_path
);
1064 /* Initialise the reading position */
1065 if (result
== GIT_ENOTFOUND
) {
1066 cfg
->reader
.read_ptr
= NULL
;
1067 cfg
->reader
.eof
= 1;
1069 git_buf_clear(&cfg
->reader
.buffer
);
1070 } else if (result
== 0) {
1071 cfg
->reader
.read_ptr
= cfg
->reader
.buffer
.ptr
;
1072 cfg
->reader
.eof
= 0;
1073 data_start
= cfg
->reader
.read_ptr
;
1075 return -1; /* OS error when reading the file */
1079 if (git_filebuf_open(&file
, cfg
->file_path
, 0) < 0)
1083 ldot
= strrchr(key
, '.');
1085 section
= git__strndup(key
, ldot
- key
);
1087 while (!cfg
->reader
.eof
) {
1088 c
= cfg_peek(cfg
, SKIP_WHITESPACE
);
1090 if (c
== '\0') { /* We've arrived at the end of the file */
1093 } else if (c
== '[') { /* section header, new section begins */
1095 * We set both positions to the current one in case we
1096 * need to add a variable to the end of a section. In that
1097 * case, we want both variables to point just before the
1098 * new section. If we actually want to replace it, the
1099 * default case will take care of updating them.
1101 pre_end
= post_start
= cfg
->reader
.read_ptr
;
1103 git__free(current_section
);
1104 current_section
= NULL
;
1105 if (parse_section_header(cfg
, ¤t_section
) < 0)
1108 /* Keep track of when it stops matching */
1109 last_section_matched
= section_matches
;
1110 section_matches
= !strcmp(current_section
, section
);
1113 else if (c
== ';' || c
== '#') {
1114 cfg_consume_line(cfg
);
1119 * If the section doesn't match, but the last section did,
1120 * it means we need to add a variable (so skip the line
1121 * otherwise). If both the section and name match, we need
1122 * to overwrite the variable (so skip the line
1123 * otherwise). pre_end needs to be updated each time so we
1124 * don't loose that information, but we only need to
1125 * update post_start if we're going to use it in this
1128 if (!section_matches
) {
1129 if (!last_section_matched
) {
1130 cfg_consume_line(cfg
);
1134 int has_matched
= 0;
1135 char *var_name
, *var_value
;
1137 pre_end
= cfg
->reader
.read_ptr
;
1138 if (parse_variable(cfg
, &var_name
, &var_value
) < 0)
1141 /* First try to match the name of the variable */
1142 if (strcasecmp(name
, var_name
) == 0)
1145 /* If the name matches, and we have a regex to match the
1146 * value, try to match it */
1147 if (has_matched
&& preg
!= NULL
)
1148 has_matched
= (regexec(preg
, var_value
, 0, NULL
, 0) == 0);
1150 git__free(var_name
);
1151 git__free(var_value
);
1153 /* if there is no match, keep going */
1157 post_start
= cfg
->reader
.read_ptr
;
1160 /* We've found the variable we wanted to change, so
1161 * write anything up to it */
1162 git_filebuf_write(&file
, data_start
, pre_end
- data_start
);
1165 /* Then replace the variable. If the value is NULL, it
1166 * means we want to delete it, so don't write anything. */
1167 if (value
!= NULL
) {
1168 git_filebuf_printf(&file
, "\t%s = %s\n", name
, value
);
1171 /* multiline variable? we need to keep reading lines to match */
1173 data_start
= post_start
;
1178 break; /* break from the loop */
1183 * Being here can mean that
1185 * 1) our section is the last one in the file and we're
1188 * 2) we didn't find a section for us so we need to create it
1191 * 3) we're setting a multivar with a regex, which means we
1192 * continue to search for matching values
1194 * In the last case, if we've already replaced a value, we
1195 * want to write the rest of the file. Otherwise we need to write
1196 * out the whole file and then the new variable.
1198 if (write_trailer
) {
1199 /* Write out rest of the file */
1200 git_filebuf_write(&file
, post_start
, cfg
->reader
.buffer
.size
- (post_start
- data_start
));
1202 if (preg_replaced
) {
1203 git_filebuf_printf(&file
, "\n%s", data_start
);
1205 git_filebuf_write(&file
, cfg
->reader
.buffer
.ptr
, cfg
->reader
.buffer
.size
);
1207 /* And now if we just need to add a variable */
1208 if (!section_matches
&& write_section(&file
, section
) < 0)
1211 /* Sanity check: if we are here, and value is NULL, that means that somebody
1212 * touched the config file after our intial read. We should probably assert()
1213 * this, but instead we'll handle it gracefully with an error. */
1214 if (value
== NULL
) {
1215 giterr_set(GITERR_CONFIG
,
1216 "Race condition when writing a config file (a cvar has been removed)");
1220 /* If we are here, there is at least a section line */
1221 if (cfg
->reader
.buffer
.size
> 0 && *(cfg
->reader
.buffer
.ptr
+ cfg
->reader
.buffer
.size
- 1) != '\n')
1222 git_filebuf_write(&file
, "\n", 1);
1224 git_filebuf_printf(&file
, "\t%s = %s\n", name
, value
);
1229 git__free(current_section
);
1231 /* refresh stats - if this errors, then commit will error too */
1232 (void)git_filebuf_stats(&cfg
->file_mtime
, &cfg
->file_size
, &file
);
1234 result
= git_filebuf_commit(&file
, GIT_CONFIG_FILE_MODE
);
1235 git_buf_free(&cfg
->reader
.buffer
);
1241 git__free(current_section
);
1243 git_filebuf_cleanup(&file
);
1244 git_buf_free(&cfg
->reader
.buffer
);
1248 static const char *escapes
= "ntb\"\\";
1249 static const char *escaped
= "\n\t\b\"\\";
1251 /* Escape the values to write them to the file */
1252 static char *escape_value(const char *ptr
)
1254 git_buf buf
= GIT_BUF_INIT
;
1261 git_buf_grow(&buf
, len
);
1263 while (*ptr
!= '\0') {
1264 if ((esc
= strchr(escaped
, *ptr
)) != NULL
) {
1265 git_buf_putc(&buf
, '\\');
1266 git_buf_putc(&buf
, escapes
[esc
- escaped
]);
1268 git_buf_putc(&buf
, *ptr
);
1273 if (git_buf_oom(&buf
)) {
1278 return git_buf_detach(&buf
);
1281 /* '\"' -> '"' etc */
1282 static char *fixup_line(const char *ptr
, int quote_count
)
1284 char *str
= git__malloc(strlen(ptr
) + 1);
1285 char *out
= str
, *esc
;
1290 while (*ptr
!= '\0') {
1293 } else if (*ptr
!= '\\') {
1296 /* backslash, check the next char */
1298 /* if we're at the end, it's a multiline, so keep the backslash */
1303 if ((esc
= strchr(escapes
, *ptr
)) != NULL
) {
1304 *out
++ = escaped
[esc
- escapes
];
1307 giterr_set(GITERR_CONFIG
, "Invalid escape at %s", ptr
);
1320 static int is_multiline_var(const char *str
)
1322 const char *end
= str
+ strlen(str
);
1323 return (end
> str
) && (end
[-1] == '\\');
1326 static int parse_multiline_variable(diskfile_backend
*cfg
, git_buf
*value
, int in_quotes
)
1328 char *line
= NULL
, *proc_line
= NULL
;
1331 /* Check that the next line exists */
1332 line
= cfg_readline(cfg
, false);
1336 /* We've reached the end of the file, there is input missing */
1337 if (line
[0] == '\0') {
1338 set_parse_error(cfg
, 0, "Unexpected end of file while parsing multine var");
1343 quote_count
= strip_comments(line
, !!in_quotes
);
1345 /* If it was just a comment, pretend it didn't exist */
1346 if (line
[0] == '\0') {
1348 return parse_multiline_variable(cfg
, value
, quote_count
);
1349 /* TODO: unbounded recursion. This **could** be exploitable */
1352 /* Drop the continuation character '\': to closely follow the UNIX
1353 * standard, this character **has** to be last one in the buf, with
1354 * no whitespace after it */
1355 assert(is_multiline_var(value
->ptr
));
1356 git_buf_truncate(value
, git_buf_len(value
) - 1);
1358 proc_line
= fixup_line(line
, in_quotes
);
1359 if (proc_line
== NULL
) {
1363 /* add this line to the multiline var */
1364 git_buf_puts(value
, proc_line
);
1366 git__free(proc_line
);
1369 * If we need to continue reading the next line, let's just
1370 * keep putting stuff in the buffer
1372 if (is_multiline_var(value
->ptr
))
1373 return parse_multiline_variable(cfg
, value
, quote_count
);
1378 static int parse_variable(diskfile_backend
*cfg
, char **var_name
, char **var_value
)
1380 const char *var_end
= NULL
;
1381 const char *value_start
= NULL
;
1385 line
= cfg_readline(cfg
, true);
1389 quote_count
= strip_comments(line
, 0);
1391 var_end
= strchr(line
, '=');
1393 if (var_end
== NULL
)
1394 var_end
= strchr(line
, '\0');
1396 value_start
= var_end
+ 1;
1399 while (var_end
>line
&& git__isspace(*var_end
));
1401 *var_name
= git__strndup(line
, var_end
- line
+ 1);
1402 GITERR_CHECK_ALLOC(*var_name
);
1404 /* If there is no value, boolean true is assumed */
1408 * Now, let's try to parse the value
1410 if (value_start
!= NULL
) {
1411 while (git__isspace(value_start
[0]))
1414 if (is_multiline_var(value_start
)) {
1415 git_buf multi_value
= GIT_BUF_INIT
;
1416 char *proc_line
= fixup_line(value_start
, 0);
1417 GITERR_CHECK_ALLOC(proc_line
);
1418 git_buf_puts(&multi_value
, proc_line
);
1419 git__free(proc_line
);
1420 if (parse_multiline_variable(cfg
, &multi_value
, quote_count
) < 0 || git_buf_oom(&multi_value
)) {
1421 git__free(*var_name
);
1423 git_buf_free(&multi_value
);
1427 *var_value
= git_buf_detach(&multi_value
);
1430 else if (value_start
[0] != '\0') {
1431 *var_value
= fixup_line(value_start
, 0);
1432 GITERR_CHECK_ALLOC(*var_value
);
1433 } else { /* equals sign but missing rhs */
1434 *var_value
= git__strdup("");
1435 GITERR_CHECK_ALLOC(*var_value
);