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