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