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