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