]> git.proxmox.com Git - libgit2.git/blob - src/config_file.c
First round of config multimap changes
[libgit2.git] / src / config_file.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
3 *
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.
6 */
7
8 #include "common.h"
9 #include "config.h"
10 #include "fileops.h"
11 #include "filebuf.h"
12 #include "buffer.h"
13 #include "git2/config.h"
14 #include "git2/types.h"
15
16
17 #include <ctype.h>
18
19 typedef struct cvar_t {
20 struct cvar_t *next;
21 char *key; /* TODO: we might be able to get rid of this */
22 char *value;
23 } cvar_t;
24
25 typedef struct {
26 struct cvar_t *head;
27 struct cvar_t *tail;
28 } cvar_t_list;
29
30 #define CVAR_LIST_HEAD(list) ((list)->head)
31
32 #define CVAR_LIST_TAIL(list) ((list)->tail)
33
34 #define CVAR_LIST_NEXT(var) ((var)->next)
35
36 #define CVAR_LIST_EMPTY(list) ((list)->head == NULL)
37
38 #define CVAR_LIST_APPEND(list, var) do {\
39 if (CVAR_LIST_EMPTY(list)) {\
40 CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
41 } else {\
42 CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
43 CVAR_LIST_TAIL(list) = var;\
44 }\
45 } while(0)
46
47 #define CVAR_LIST_REMOVE_HEAD(list) do {\
48 CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
49 } while(0)
50
51 #define CVAR_LIST_REMOVE_AFTER(var) do {\
52 CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
53 } while(0)
54
55 #define CVAR_LIST_FOREACH(list, iter)\
56 for ((iter) = CVAR_LIST_HEAD(list);\
57 (iter) != NULL;\
58 (iter) = CVAR_LIST_NEXT(iter))
59
60 /*
61 * Inspired by the FreeBSD functions
62 */
63 #define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
64 for ((iter) = CVAR_LIST_HEAD(vars);\
65 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
66 (iter) = (tmp))
67
68 typedef struct {
69 git_config_file parent;
70
71 git_hashtable *values;
72
73 struct {
74 git_fbuffer buffer;
75 char *read_ptr;
76 int line_number;
77 int eof;
78 } reader;
79
80 char *file_path;
81 } diskfile_backend;
82
83 static int config_parse(diskfile_backend *cfg_file);
84 static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value);
85 static int config_write(diskfile_backend *cfg, const char *key, const char *value);
86
87 static void cvar_free(cvar_t *var)
88 {
89 if (var == NULL)
90 return;
91
92 git__free(var->key);
93 git__free(var->value);
94 git__free(var);
95 }
96
97 /* Take something the user gave us and make it nice for our hash function */
98 static int normalize_name(const char *in, char **out)
99 {
100 char *name, *fdot, *ldot;
101
102 assert(in && out);
103
104 name = git__strdup(in);
105 if (name == NULL)
106 return GIT_ENOMEM;
107
108 fdot = strchr(name, '.');
109 ldot = strrchr(name, '.');
110
111 if (fdot == NULL || ldot == NULL) {
112 git__free(name);
113 return git__throw(GIT_EINVALIDARGS, "Bad format. No dot in '%s'", in);
114 }
115
116 /* Downcase up to the first dot and after the last one */
117 git__strntolower(name, fdot - name);
118 git__strtolower(ldot);
119
120 *out = name;
121 return GIT_SUCCESS;
122 }
123
124 static void free_vars(git_hashtable *values)
125 {
126 const char *GIT_UNUSED(_unused) = NULL;
127 cvar_t *var = NULL;
128
129 if (values == NULL)
130 return;
131
132 GIT_HASHTABLE_FOREACH(values, _unused, var,
133 do {
134 cvar_t *next = CVAR_LIST_NEXT(var);
135 cvar_free(var);
136 var = next;
137 } while (var != NULL);
138 )
139
140 git_hashtable_free(values);
141 }
142
143 static int config_open(git_config_file *cfg)
144 {
145 int error;
146 diskfile_backend *b = (diskfile_backend *)cfg;
147
148 b->values = git_hashtable_alloc (20, git_hash__strhash_cb, git_hash__strcmp_cb);
149 if (b->values == NULL)
150 return GIT_ENOMEM;
151
152 error = git_futils_readbuffer(&b->reader.buffer, b->file_path);
153
154 /* It's fine if the file doesn't exist */
155 if (error == GIT_ENOTFOUND)
156 return GIT_SUCCESS;
157
158 if (error < GIT_SUCCESS)
159 goto cleanup;
160
161 error = config_parse(b);
162 if (error < GIT_SUCCESS)
163 goto cleanup;
164
165 git_futils_freebuffer(&b->reader.buffer);
166
167 return GIT_SUCCESS;
168
169 cleanup:
170 free_vars(b->values);
171 b->values = NULL;
172 git_futils_freebuffer(&b->reader.buffer);
173
174 return git__rethrow(error, "Failed to open config");
175 }
176
177 static void backend_free(git_config_file *_backend)
178 {
179 diskfile_backend *backend = (diskfile_backend *)_backend;
180
181 if (backend == NULL)
182 return;
183
184 git__free(backend->file_path);
185
186 free_vars(backend->values);
187
188 git__free(backend);
189 }
190
191 static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data)
192 {
193 int ret = GIT_SUCCESS;
194 cvar_t *var;
195 diskfile_backend *b = (diskfile_backend *)backend;
196 const char *key;
197
198 GIT_HASHTABLE_FOREACH(b->values, key, var,
199 do {
200 ret = fn(key, var->value, data);
201 if (ret)
202 break;
203 var = CVAR_LIST_NEXT(var);
204 } while (var != NULL);
205 )
206
207
208 return ret;
209 }
210
211 static int config_set(git_config_file *cfg, const char *name, const char *value)
212 {
213 cvar_t *var = NULL;
214 cvar_t *existing = NULL, *old_value = NULL;
215 int error = GIT_SUCCESS;
216 diskfile_backend *b = (diskfile_backend *)cfg;
217 char *key;
218
219 if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
220 return git__rethrow(error, "Failed to normalize variable name '%s'", name);
221
222 /*
223 * Try to find it in the existing values and update it if it
224 * only has one value.
225 */
226 existing = git_hashtable_lookup(b->values, key);
227 if (existing != NULL) {
228 char *tmp;
229
230 git__free(key);
231 if (existing->next != NULL)
232 return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple set");
233
234 tmp = value ? git__strdup(value) : NULL;
235 if (tmp == NULL && value != NULL)
236 return GIT_ENOMEM;
237
238 git__free(existing->value);
239 existing->value = tmp;
240
241 return config_write(b, existing->key, value);
242 }
243
244 var = git__malloc(sizeof(cvar_t));
245 if (var == NULL)
246 return GIT_ENOMEM;
247
248 memset(var, 0x0, sizeof(cvar_t));
249
250 var->key = key;
251
252 var->value = value ? git__strdup(value) : NULL;
253 if (var->value == NULL && value != NULL) {
254 error = GIT_ENOMEM;
255 goto out;
256 }
257
258 error = git_hashtable_insert2(b->values, key, var, (void **)&old_value);
259 if (error < GIT_SUCCESS)
260 goto out;
261
262 cvar_free(old_value);
263
264 error = config_write(b, key, value);
265
266 out:
267 if (error < GIT_SUCCESS)
268 cvar_free(var);
269
270 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to set config value");
271 }
272
273 /*
274 * Internal function that actually gets the value in string form
275 */
276 static int config_get(git_config_file *cfg, const char *name, const char **out)
277 {
278 cvar_t *var;
279 int error = GIT_SUCCESS;
280 diskfile_backend *b = (diskfile_backend *)cfg;
281 char *key;
282
283 if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
284 return error;
285
286 var = git_hashtable_lookup(b->values, key);
287 git__free(key);
288
289 if (var == NULL)
290 return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
291
292 *out = var->value;
293
294 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to get config value for %s", name);
295 }
296
297 static int config_delete(git_config_file *cfg, const char *name)
298 {
299 int error;
300 const cvar_t *var;
301 cvar_t *old_value;
302 diskfile_backend *b = (diskfile_backend *)cfg;
303 char *key;
304
305 if ((error = normalize_name(name, &key)) < GIT_SUCCESS)
306 return error;
307
308 var = git_hashtable_lookup(b->values, key);
309 free(key);
310
311 if (var == NULL)
312 return git__throw(GIT_ENOTFOUND, "Variable '%s' not found", name);
313
314 if (var->next != NULL)
315 return git__throw(GIT_EINVALIDARGS, "Multivar incompatible with simple delete");
316
317
318 if ((error = git_hashtable_remove2(b->values, var->key, (void **)&old_value)) < GIT_SUCCESS)
319 return git__rethrow(error, "Failed to remove %s from hashtable", key);
320
321 error = config_write(b, var->key, NULL);
322 cvar_free(old_value);
323
324 return error;
325 }
326
327 int git_config_file__ondisk(git_config_file **out, const char *path)
328 {
329 diskfile_backend *backend;
330
331 backend = git__malloc(sizeof(diskfile_backend));
332 if (backend == NULL)
333 return GIT_ENOMEM;
334
335 memset(backend, 0x0, sizeof(diskfile_backend));
336
337 backend->file_path = git__strdup(path);
338 if (backend->file_path == NULL) {
339 git__free(backend);
340 return GIT_ENOMEM;
341 }
342
343 backend->parent.open = config_open;
344 backend->parent.get = config_get;
345 backend->parent.set = config_set;
346 backend->parent.del = config_delete;
347 backend->parent.foreach = file_foreach;
348 backend->parent.free = backend_free;
349
350 *out = (git_config_file *)backend;
351
352 return GIT_SUCCESS;
353 }
354
355 static int cfg_getchar_raw(diskfile_backend *cfg)
356 {
357 int c;
358
359 c = *cfg->reader.read_ptr++;
360
361 /*
362 Win 32 line breaks: if we find a \r\n sequence,
363 return only the \n as a newline
364 */
365 if (c == '\r' && *cfg->reader.read_ptr == '\n') {
366 cfg->reader.read_ptr++;
367 c = '\n';
368 }
369
370 if (c == '\n')
371 cfg->reader.line_number++;
372
373 if (c == 0) {
374 cfg->reader.eof = 1;
375 c = '\n';
376 }
377
378 return c;
379 }
380
381 #define SKIP_WHITESPACE (1 << 1)
382 #define SKIP_COMMENTS (1 << 2)
383
384 static int cfg_getchar(diskfile_backend *cfg_file, int flags)
385 {
386 const int skip_whitespace = (flags & SKIP_WHITESPACE);
387 const int skip_comments = (flags & SKIP_COMMENTS);
388 int c;
389
390 assert(cfg_file->reader.read_ptr);
391
392 do c = cfg_getchar_raw(cfg_file);
393 while (skip_whitespace && isspace(c) &&
394 !cfg_file->reader.eof);
395
396 if (skip_comments && (c == '#' || c == ';')) {
397 do c = cfg_getchar_raw(cfg_file);
398 while (c != '\n');
399 }
400
401 return c;
402 }
403
404 /*
405 * Read the next char, but don't move the reading pointer.
406 */
407 static int cfg_peek(diskfile_backend *cfg, int flags)
408 {
409 void *old_read_ptr;
410 int old_lineno, old_eof;
411 int ret;
412
413 assert(cfg->reader.read_ptr);
414
415 old_read_ptr = cfg->reader.read_ptr;
416 old_lineno = cfg->reader.line_number;
417 old_eof = cfg->reader.eof;
418
419 ret = cfg_getchar(cfg, flags);
420
421 cfg->reader.read_ptr = old_read_ptr;
422 cfg->reader.line_number = old_lineno;
423 cfg->reader.eof = old_eof;
424
425 return ret;
426 }
427
428 /*
429 * Read and consume a line, returning it in newly-allocated memory.
430 */
431 static char *cfg_readline(diskfile_backend *cfg)
432 {
433 char *line = NULL;
434 char *line_src, *line_end;
435 size_t line_len;
436
437 line_src = cfg->reader.read_ptr;
438
439 /* Skip empty empty lines */
440 while (isspace(*line_src))
441 ++line_src;
442
443 line_end = strchr(line_src, '\n');
444
445 /* no newline at EOF */
446 if (line_end == NULL)
447 line_end = strchr(line_src, 0);
448
449 line_len = line_end - line_src;
450
451 line = git__malloc(line_len + 1);
452 if (line == NULL)
453 return NULL;
454
455 memcpy(line, line_src, line_len);
456
457 do line[line_len] = '\0';
458 while (line_len-- > 0 && isspace(line[line_len]));
459
460 if (*line_end == '\n')
461 line_end++;
462
463 if (*line_end == '\0')
464 cfg->reader.eof = 1;
465
466 cfg->reader.line_number++;
467 cfg->reader.read_ptr = line_end;
468
469 return line;
470 }
471
472 /*
473 * Consume a line, without storing it anywhere
474 */
475 static void cfg_consume_line(diskfile_backend *cfg)
476 {
477 char *line_start, *line_end;
478
479 line_start = cfg->reader.read_ptr;
480 line_end = strchr(line_start, '\n');
481 /* No newline at EOF */
482 if(line_end == NULL){
483 line_end = strchr(line_start, '\0');
484 }
485
486 if (*line_end == '\n')
487 line_end++;
488
489 if (*line_end == '\0')
490 cfg->reader.eof = 1;
491
492 cfg->reader.line_number++;
493 cfg->reader.read_ptr = line_end;
494 }
495
496 GIT_INLINE(int) config_keychar(int c)
497 {
498 return isalnum(c) || c == '-';
499 }
500
501 static int parse_section_header_ext(const char *line, const char *base_name, char **section_name)
502 {
503 int c, rpos;
504 char *first_quote, *last_quote;
505 git_buf buf = GIT_BUF_INIT;
506 int error = GIT_SUCCESS;
507 int quote_marks;
508 /*
509 * base_name is what came before the space. We should be at the
510 * first quotation mark, except for now, line isn't being kept in
511 * sync so we only really use it to calculate the length.
512 */
513
514 first_quote = strchr(line, '"');
515 last_quote = strrchr(line, '"');
516
517 if (last_quote - first_quote == 0)
518 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. There is no final quotation mark");
519
520 git_buf_grow(&buf, strlen(base_name) + last_quote - first_quote + 2);
521 git_buf_printf(&buf, "%s.", base_name);
522
523 rpos = 0;
524 quote_marks = 0;
525
526 line = first_quote;
527 c = line[rpos++];
528
529 /*
530 * At the end of each iteration, whatever is stored in c will be
531 * added to the string. In case of error, jump to out
532 */
533 do {
534 if (quote_marks == 2) {
535 puts("too many marks");
536 error = git__throw(GIT_EOBJCORRUPTED, "Falied to parse ext header. Text after closing quote");
537 goto out;
538
539 }
540
541 switch (c) {
542 case '"':
543 ++quote_marks;
544 continue;
545 case '\\':
546 c = line[rpos++];
547 switch (c) {
548 case '"':
549 case '\\':
550 break;
551 default:
552 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse ext header. Unsupported escape char \\%c", c);
553 goto out;
554 }
555 break;
556 default:
557 break;
558 }
559
560 git_buf_putc(&buf, c);
561 } while ((c = line[rpos++]) != ']');
562
563 *section_name = git__strdup(git_buf_cstr(&buf));
564 out:
565 git_buf_free(&buf);
566
567 return error;
568 }
569
570 static int parse_section_header(diskfile_backend *cfg, char **section_out)
571 {
572 char *name, *name_end;
573 int name_length, c, pos;
574 int error = GIT_SUCCESS;
575 char *line;
576
577 line = cfg_readline(cfg);
578 if (line == NULL)
579 return GIT_ENOMEM;
580
581 /* find the end of the variable's name */
582 name_end = strchr(line, ']');
583 if (name_end == NULL) {
584 git__free(line);
585 return git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Can't find header name end");
586 }
587
588 name = (char *)git__malloc((size_t)(name_end - line) + 1);
589 if (name == NULL) {
590 git__free(line);
591 return GIT_ENOMEM;
592 }
593
594 name_length = 0;
595 pos = 0;
596
597 /* Make sure we were given a section header */
598 c = line[pos++];
599 if (c != '[') {
600 error = git__throw(GIT_ERROR, "Failed to parse header. Didn't get section header. This is a bug");
601 goto error;
602 }
603
604 c = line[pos++];
605
606 do {
607 if (isspace(c)){
608 name[name_length] = '\0';
609 error = parse_section_header_ext(line, name, section_out);
610 git__free(line);
611 git__free(name);
612 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse header");
613 }
614
615 if (!config_keychar(c) && c != '.') {
616 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Wrong format on header");
617 goto error;
618 }
619
620 name[name_length++] = (char) tolower(c);
621
622 } while ((c = line[pos++]) != ']');
623
624 if (line[pos - 1] != ']') {
625 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse header. Config file ended unexpectedly");
626 goto error;
627 }
628
629 name[name_length] = 0;
630 git__free(line);
631 git__strtolower(name);
632 *section_out = name;
633 return GIT_SUCCESS;
634
635 error:
636 git__free(line);
637 git__free(name);
638 return error;
639 }
640
641 static int skip_bom(diskfile_backend *cfg)
642 {
643 static const char utf8_bom[] = "\xef\xbb\xbf";
644
645 if (cfg->reader.buffer.len < sizeof(utf8_bom))
646 return GIT_SUCCESS;
647
648 if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
649 cfg->reader.read_ptr += sizeof(utf8_bom);
650
651 /* TODO: the reference implementation does pretty stupid
652 shit with the BoM
653 */
654
655 return GIT_SUCCESS;
656 }
657
658 /*
659 (* basic types *)
660 digit = "0".."9"
661 integer = digit { digit }
662 alphabet = "a".."z" + "A" .. "Z"
663
664 section_char = alphabet | "." | "-"
665 extension_char = (* any character except newline *)
666 any_char = (* any character *)
667 variable_char = "alphabet" | "-"
668
669
670 (* actual grammar *)
671 config = { section }
672
673 section = header { definition }
674
675 header = "[" section [subsection | subsection_ext] "]"
676
677 subsection = "." section
678 subsection_ext = "\"" extension "\""
679
680 section = section_char { section_char }
681 extension = extension_char { extension_char }
682
683 definition = variable_name ["=" variable_value] "\n"
684
685 variable_name = variable_char { variable_char }
686 variable_value = string | boolean | integer
687
688 string = quoted_string | plain_string
689 quoted_string = "\"" plain_string "\""
690 plain_string = { any_char }
691
692 boolean = boolean_true | boolean_false
693 boolean_true = "yes" | "1" | "true" | "on"
694 boolean_false = "no" | "0" | "false" | "off"
695 */
696
697 static void strip_comments(char *line)
698 {
699 int quote_count = 0;
700 char *ptr;
701
702 for (ptr = line; *ptr; ++ptr) {
703 if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
704 quote_count++;
705
706 if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
707 ptr[0] = '\0';
708 break;
709 }
710 }
711
712 if (isspace(ptr[-1])) {
713 /* TODO skip whitespace */
714 }
715 }
716
717 static int config_parse(diskfile_backend *cfg_file)
718 {
719 int error = GIT_SUCCESS, c;
720 char *current_section = NULL;
721 char *var_name;
722 char *var_value;
723 cvar_t *var;
724 git_buf buf = GIT_BUF_INIT;
725
726 /* Initialize the reading position */
727 cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
728 cfg_file->reader.eof = 0;
729
730 /* If the file is empty, there's nothing for us to do */
731 if (*cfg_file->reader.read_ptr == '\0')
732 return GIT_SUCCESS;
733
734 skip_bom(cfg_file);
735
736 while (error == GIT_SUCCESS && !cfg_file->reader.eof) {
737
738 c = cfg_peek(cfg_file, SKIP_WHITESPACE);
739
740 switch (c) {
741 case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
742 cfg_file->reader.eof = 1;
743 break;
744
745 case '[': /* section header, new section begins */
746 git__free(current_section);
747 current_section = NULL;
748 error = parse_section_header(cfg_file, &current_section);
749 break;
750
751 case ';':
752 case '#':
753 cfg_consume_line(cfg_file);
754 break;
755
756 default: /* assume variable declaration */
757 error = parse_variable(cfg_file, &var_name, &var_value);
758
759 if (error < GIT_SUCCESS)
760 break;
761
762 var = git__malloc(sizeof(cvar_t));
763 if (var == NULL) {
764 error = GIT_ENOMEM;
765 break;
766 }
767
768 memset(var, 0x0, sizeof(cvar_t));
769
770 git__strtolower(var_name);
771 git_buf_printf(&buf, "%s.%s", current_section, var_name);
772 git__free(var_name);
773
774 if (git_buf_oom(&buf)) {
775 error = GIT_ENOMEM;
776 break;
777 }
778
779 var->key = git_buf_detach(&buf);
780 var->value = var_value;
781
782 /* FIXME: Actually support multivars, don't just overwrite */
783 error = git_hashtable_insert(cfg_file->values, var->key, var);
784
785 break;
786 }
787 }
788
789 git__free(current_section);
790
791 return error == GIT_SUCCESS ? GIT_SUCCESS : git__rethrow(error, "Failed to parse config");
792 }
793
794 static int write_section(git_filebuf *file, const char *key)
795 {
796 int error;
797 const char *fdot, *ldot;
798 git_buf buf = GIT_BUF_INIT;
799
800 /* All of this just for [section "subsection"] */
801 fdot = strchr(key, '.');
802 git_buf_putc(&buf, '[');
803 if (fdot == NULL)
804 git_buf_puts(&buf, key);
805 else
806 git_buf_put(&buf, key, fdot - key);
807 ldot = strrchr(key, '.');
808 if (fdot != ldot && fdot != NULL) {
809 git_buf_putc(&buf, '"');
810 /* TODO: escape */
811 git_buf_put(&buf, fdot + 1, ldot - fdot - 1);
812 git_buf_putc(&buf, '"');
813 }
814 git_buf_puts(&buf, "]\n");
815 if (git_buf_oom(&buf))
816 return GIT_ENOMEM;
817
818 error = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
819 git_buf_free(&buf);
820
821 return error;
822 }
823
824 /*
825 * This is pretty much the parsing, except we write out anything we don't have
826 */
827 static int config_write(diskfile_backend *cfg, const char *key, const char* value)
828 {
829 int error = GIT_SUCCESS, c;
830 int section_matches = 0, last_section_matched = 0;
831 char *current_section = NULL, *section, *name, *ldot;
832 char *var_name, *var_value, *data_start;
833 git_filebuf file = GIT_FILEBUF_INIT;
834 const char *pre_end = NULL, *post_start = NULL;
835
836 /* We need to read in our own config file */
837 error = git_futils_readbuffer(&cfg->reader.buffer, cfg->file_path);
838 if (error < GIT_SUCCESS && error != GIT_ENOTFOUND) {
839 return git__rethrow(error, "Failed to read existing config file %s", cfg->file_path);
840 }
841
842 /* Initialise the reading position */
843 if (error == GIT_ENOTFOUND) {
844 error = GIT_SUCCESS;
845 cfg->reader.read_ptr = NULL;
846 cfg->reader.eof = 1;
847 data_start = NULL;
848 cfg->reader.buffer.len = 0;
849 cfg->reader.buffer.data = NULL;
850 } else {
851 cfg->reader.read_ptr = cfg->reader.buffer.data;
852 cfg->reader.eof = 0;
853 data_start = cfg->reader.read_ptr;
854 }
855
856 /* Lock the file */
857 error = git_filebuf_open(&file, cfg->file_path, 0);
858 if (error < GIT_SUCCESS)
859 return git__rethrow(error, "Failed to lock config file");
860
861 skip_bom(cfg);
862 ldot = strrchr(key, '.');
863 name = ldot + 1;
864 section = git__strndup(key, ldot - key);
865
866 while (error == GIT_SUCCESS && !cfg->reader.eof) {
867 c = cfg_peek(cfg, SKIP_WHITESPACE);
868
869 switch (c) {
870 case '\0': /* We've arrived at the end of the file */
871 break;
872
873 case '[': /* section header, new section begins */
874 /*
875 * We set both positions to the current one in case we
876 * need to add a variable to the end of a section. In that
877 * case, we want both variables to point just before the
878 * new section. If we actually want to replace it, the
879 * default case will take care of updating them.
880 */
881 pre_end = post_start = cfg->reader.read_ptr;
882 if (current_section)
883 git__free(current_section);
884 error = parse_section_header(cfg, &current_section);
885 if (error < GIT_SUCCESS)
886 break;
887
888 /* Keep track of when it stops matching */
889 last_section_matched = section_matches;
890 section_matches = !strcmp(current_section, section);
891 break;
892
893 case ';':
894 case '#':
895 cfg_consume_line(cfg);
896 break;
897
898 default:
899 /*
900 * If the section doesn't match, but the last section did,
901 * it means we need to add a variable (so skip the line
902 * otherwise). If both the section and name match, we need
903 * to overwrite the variable (so skip the line
904 * otherwise). pre_end needs to be updated each time so we
905 * don't loose that information, but we only need to
906 * update post_start if we're going to use it in this
907 * iteration.
908 */
909 if (!section_matches) {
910 if (!last_section_matched) {
911 cfg_consume_line(cfg);
912 break;
913 }
914 } else {
915 int cmp = -1;
916
917 pre_end = cfg->reader.read_ptr;
918 if ((error = parse_variable(cfg, &var_name, &var_value)) == GIT_SUCCESS)
919 cmp = strcasecmp(name, var_name);
920
921 git__free(var_name);
922 git__free(var_value);
923
924 if (cmp != 0)
925 break;
926
927 post_start = cfg->reader.read_ptr;
928 }
929
930 /*
931 * We've found the variable we wanted to change, so
932 * write anything up to it
933 */
934 error = git_filebuf_write(&file, data_start, pre_end - data_start);
935 if (error < GIT_SUCCESS) {
936 git__rethrow(error, "Failed to write the first part of the file");
937 break;
938 }
939
940 /*
941 * Then replace the variable. If the value is NULL, it
942 * means we want to delete it, so pretend everything went
943 * fine
944 */
945 if (value == NULL)
946 error = GIT_SUCCESS;
947 else
948 error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
949 if (error < GIT_SUCCESS) {
950 git__rethrow(error, "Failed to overwrite the variable");
951 break;
952 }
953
954 /* And then the write out rest of the file */
955 error = git_filebuf_write(&file, post_start,
956 cfg->reader.buffer.len - (post_start - data_start));
957
958 if (error < GIT_SUCCESS) {
959 git__rethrow(error, "Failed to write the rest of the file");
960 break;
961 }
962
963 goto cleanup;
964 }
965 }
966
967 /*
968 * Being here can mean that
969 *
970 * 1) our section is the last one in the file and we're
971 * adding a variable
972 *
973 * 2) we didn't find a section for us so we need to create it
974 * ourselves.
975 *
976 * Either way we need to write out the whole file.
977 */
978
979 error = git_filebuf_write(&file, cfg->reader.buffer.data, cfg->reader.buffer.len);
980 if (error < GIT_SUCCESS) {
981 git__rethrow(error, "Failed to write original config content");
982 goto cleanup;
983 }
984
985 /* And now if we just need to add a variable */
986 if (section_matches) {
987 error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
988 goto cleanup;
989 }
990
991 /* Or maybe we need to write out a whole section */
992 error = write_section(&file, section);
993 if (error < GIT_SUCCESS)
994 git__rethrow(error, "Failed to write new section");
995
996 error = git_filebuf_printf(&file, "\t%s = %s\n", name, value);
997 cleanup:
998 git__free(section);
999 git__free(current_section);
1000
1001 if (error < GIT_SUCCESS)
1002 git_filebuf_cleanup(&file);
1003 else
1004 error = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE);
1005
1006 git_futils_freebuffer(&cfg->reader.buffer);
1007 return error;
1008 }
1009
1010 static int is_multiline_var(const char *str)
1011 {
1012 char *end = strrchr(str, '\0') - 1;
1013
1014 while (isspace(*end))
1015 --end;
1016
1017 return *end == '\\';
1018 }
1019
1020 static int parse_multiline_variable(diskfile_backend *cfg, const char *first, char **out)
1021 {
1022 char *line = NULL, *end;
1023 int error = GIT_SUCCESS, ret;
1024 size_t len;
1025 char *buf;
1026
1027 /* Check that the next line exists */
1028 line = cfg_readline(cfg);
1029 if (line == NULL)
1030 return GIT_ENOMEM;
1031
1032 /* We've reached the end of the file, there is input missing */
1033 if (line[0] == '\0') {
1034 error = git__throw(GIT_EOBJCORRUPTED, "Failed to parse multiline var. File ended unexpectedly");
1035 goto out;
1036 }
1037
1038 strip_comments(line);
1039
1040 /* If it was just a comment, pretend it didn't exist */
1041 if (line[0] == '\0') {
1042 error = parse_multiline_variable(cfg, first, out);
1043 goto out;
1044 }
1045
1046 /* Find the continuation character '\' and strip the whitespace */
1047 end = strrchr(first, '\\');
1048 while (isspace(end[-1]))
1049 --end;
1050
1051 *end = '\0'; /* Terminate the string here */
1052
1053 len = strlen(first) + strlen(line) + 2;
1054 buf = git__malloc(len);
1055 if (buf == NULL) {
1056 error = GIT_ENOMEM;
1057 goto out;
1058 }
1059
1060 ret = p_snprintf(buf, len, "%s %s", first, line);
1061 if (ret < 0) {
1062 error = git__throw(GIT_EOSERR, "Failed to parse multiline var. Failed to put together two lines. OS err: %s", strerror(errno));
1063 git__free(buf);
1064 goto out;
1065 }
1066
1067 /*
1068 * If we need to continue reading the next line, pretend
1069 * everything we've read up to now was in one line and call
1070 * ourselves.
1071 */
1072 if (is_multiline_var(buf)) {
1073 char *final_val;
1074 error = parse_multiline_variable(cfg, buf, &final_val);
1075 git__free(buf);
1076 buf = final_val;
1077 }
1078
1079 *out = buf;
1080
1081 out:
1082 git__free(line);
1083 return error;
1084 }
1085
1086 static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_value)
1087 {
1088 char *tmp;
1089 int error = GIT_SUCCESS;
1090 const char *var_end = NULL;
1091 const char *value_start = NULL;
1092 char *line;
1093
1094 line = cfg_readline(cfg);
1095 if (line == NULL)
1096 return GIT_ENOMEM;
1097
1098 strip_comments(line);
1099
1100 var_end = strchr(line, '=');
1101
1102 if (var_end == NULL)
1103 var_end = strchr(line, '\0');
1104 else
1105 value_start = var_end + 1;
1106
1107 if (isspace(var_end[-1])) {
1108 do var_end--;
1109 while (isspace(var_end[0]));
1110 }
1111
1112 tmp = git__strndup(line, var_end - line + 1);
1113 if (tmp == NULL) {
1114 error = GIT_ENOMEM;
1115 goto out;
1116 }
1117
1118 *var_name = tmp;
1119
1120 /*
1121 * Now, let's try to parse the value
1122 */
1123 if (value_start != NULL) {
1124
1125 while (isspace(value_start[0]))
1126 value_start++;
1127
1128 if (value_start[0] == '\0') {
1129 *var_value = NULL;
1130 goto out;
1131 }
1132
1133 if (is_multiline_var(value_start)) {
1134 error = parse_multiline_variable(cfg, value_start, var_value);
1135 if (error != GIT_SUCCESS)
1136 {
1137 *var_value = NULL;
1138 git__free(*var_name);
1139 }
1140 goto out;
1141 }
1142
1143 tmp = git__strdup(value_start);
1144 if (tmp == NULL) {
1145 git__free(*var_name);
1146 *var_value = NULL;
1147 error = GIT_ENOMEM;
1148 goto out;
1149 }
1150
1151 *var_value = tmp;
1152 } else {
1153 /* If there is no value, boolean true is assumed */
1154 *var_value = NULL;
1155 }
1156
1157 out:
1158 git__free(line);
1159 return error;
1160 }