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