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