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