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