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