]>
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" | |
8bb198e6 | 10 | #include "filebuf.h" |
83634d38 | 11 | #include "sysdir.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 | |
c8e02b87 | 24 | GIT__USE_STRMAP |
01fed0a8 | 25 | |
128d3731 VM |
26 | typedef struct cvar_t { |
27 | struct cvar_t *next; | |
a1abe66a | 28 | git_config_entry *entry; |
8c1f4ab4 | 29 | bool 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 { |
4b99b8f5 | 90 | git_atomic refcount; |
c2b67043 | 91 | git_strmap *values; |
4b99b8f5 CMN |
92 | } refcounted_strmap; |
93 | ||
94 | typedef struct { | |
95 | git_config_backend parent; | |
96 | /* mutex to coordinate accessing the values */ | |
97 | git_mutex values_mutex; | |
98 | refcounted_strmap *values; | |
55ebd7d3 CMN |
99 | } diskfile_header; |
100 | ||
101 | typedef struct { | |
102 | diskfile_header header; | |
103 | ||
104 | git_config_level_t level; | |
c0335005 | 105 | |
19be0692 | 106 | git_array_t(struct reader) readers; |
c0335005 | 107 | |
744cc03e | 108 | char *file_path; |
b0b527e0 | 109 | } diskfile_backend; |
c0335005 | 110 | |
55ebd7d3 CMN |
111 | typedef struct { |
112 | diskfile_header header; | |
113 | ||
114 | diskfile_backend *snapshot_from; | |
115 | } diskfile_readonly_backend; | |
116 | ||
bf99390e | 117 | static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth); |
3005855f | 118 | static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value); |
49938cad | 119 | static char *escape_value(const char *ptr); |
c0335005 | 120 | |
55ebd7d3 | 121 | int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in); |
0500a1ef | 122 | static int config_snapshot(git_config_backend **out, git_config_backend *in); |
55ebd7d3 | 123 | |
d209cc47 | 124 | static void set_parse_error(struct reader *reader, int col, const char *error_str) |
dda708e7 VM |
125 | { |
126 | giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)", | |
d209cc47 | 127 | error_str, reader->file_path, reader->line_number, col); |
dda708e7 VM |
128 | } |
129 | ||
55ebd7d3 CMN |
130 | static int config_error_readonly(void) |
131 | { | |
132 | giterr_set(GITERR_CONFIG, "this backend is read-only"); | |
133 | return -1; | |
134 | } | |
135 | ||
128d3731 | 136 | static void cvar_free(cvar_t *var) |
c0335005 CMN |
137 | { |
138 | if (var == NULL) | |
139 | return; | |
140 | ||
a1abe66a | 141 | git__free((char*)var->entry->name); |
142 | git__free((char *)var->entry->value); | |
143 | git__free(var->entry); | |
3286c408 | 144 | git__free(var); |
c0335005 CMN |
145 | } |
146 | ||
1e7799e8 RB |
147 | int git_config_file_normalize_section(char *start, char *end) |
148 | { | |
149 | char *scan; | |
150 | ||
151 | if (start == end) | |
152 | return GIT_EINVALIDSPEC; | |
153 | ||
154 | /* Validate and downcase range */ | |
155 | for (scan = start; *scan; ++scan) { | |
156 | if (end && scan >= end) | |
157 | break; | |
158 | if (isalnum(*scan)) | |
75a4636f | 159 | *scan = (char)git__tolower(*scan); |
1e7799e8 RB |
160 | else if (*scan != '-' || scan == start) |
161 | return GIT_EINVALIDSPEC; | |
162 | } | |
163 | ||
164 | if (scan == start) | |
165 | return GIT_EINVALIDSPEC; | |
166 | ||
167 | return 0; | |
168 | } | |
169 | ||
55ebd7d3 CMN |
170 | /* Add or append the new config option */ |
171 | static int append_entry(git_strmap *values, cvar_t *var) | |
172 | { | |
173 | git_strmap_iter pos; | |
174 | cvar_t *existing; | |
175 | int error = 0; | |
176 | ||
177 | pos = git_strmap_lookup_index(values, var->entry->name); | |
178 | if (!git_strmap_valid_index(values, pos)) { | |
179 | git_strmap_insert(values, var->entry->name, var, error); | |
180 | } else { | |
181 | existing = git_strmap_value_at(values, pos); | |
182 | while (existing->next != NULL) { | |
183 | existing = existing->next; | |
184 | } | |
185 | existing->next = var; | |
186 | } | |
187 | ||
188 | if (error > 0) | |
189 | error = 0; | |
190 | ||
191 | return error; | |
192 | } | |
193 | ||
c2b67043 | 194 | static void free_vars(git_strmap *values) |
3b3577c7 | 195 | { |
fefd4551 | 196 | cvar_t *var = NULL; |
3b3577c7 | 197 | |
fefd4551 CMN |
198 | if (values == NULL) |
199 | return; | |
3b3577c7 | 200 | |
c2b67043 | 201 | git_strmap_foreach_value(values, var, |
01fed0a8 RB |
202 | while (var != NULL) { |
203 | cvar_t *next = CVAR_LIST_NEXT(var); | |
204 | cvar_free(var); | |
205 | var = next; | |
206 | }); | |
3b3577c7 | 207 | |
c2b67043 | 208 | git_strmap_free(values); |
3b3577c7 CMN |
209 | } |
210 | ||
4b99b8f5 CMN |
211 | static void refcounted_strmap_free(refcounted_strmap *map) |
212 | { | |
213 | if (!map) | |
214 | return; | |
215 | ||
216 | if (git_atomic_dec(&map->refcount) != 0) | |
217 | return; | |
218 | ||
219 | free_vars(map->values); | |
220 | git__free(map); | |
221 | } | |
222 | ||
223 | /** | |
224 | * Take the current values map from the backend and increase its | |
225 | * refcount. This is its own function to make sure we use the mutex to | |
226 | * avoid the map pointer from changing under us. | |
227 | */ | |
228 | static refcounted_strmap *refcounted_strmap_take(diskfile_header *h) | |
229 | { | |
230 | refcounted_strmap *map; | |
231 | ||
232 | git_mutex_lock(&h->values_mutex); | |
233 | ||
234 | map = h->values; | |
235 | git_atomic_inc(&map->refcount); | |
236 | ||
237 | git_mutex_unlock(&h->values_mutex); | |
238 | ||
239 | return map; | |
240 | } | |
241 | ||
242 | static int refcounted_strmap_alloc(refcounted_strmap **out) | |
243 | { | |
244 | refcounted_strmap *map; | |
245 | int error; | |
246 | ||
247 | map = git__calloc(1, sizeof(refcounted_strmap)); | |
b1914c36 | 248 | GITERR_CHECK_ALLOC(map); |
4b99b8f5 CMN |
249 | |
250 | git_atomic_set(&map->refcount, 1); | |
b1914c36 RB |
251 | |
252 | if ((error = git_strmap_alloc(&map->values)) < 0) | |
4b99b8f5 | 253 | git__free(map); |
b1914c36 RB |
254 | else |
255 | *out = map; | |
4b99b8f5 | 256 | |
4b99b8f5 CMN |
257 | return error; |
258 | } | |
259 | ||
16adc9fa | 260 | static int config_open(git_config_backend *cfg, git_config_level_t level) |
c0335005 | 261 | { |
dda708e7 | 262 | int res; |
19be0692 | 263 | struct reader *reader; |
b0b527e0 | 264 | diskfile_backend *b = (diskfile_backend *)cfg; |
c0335005 | 265 | |
744cc03e RB |
266 | b->level = level; |
267 | ||
4b99b8f5 | 268 | if ((res = refcounted_strmap_alloc(&b->header.values)) < 0) |
40ed4990 | 269 | return res; |
fefd4551 | 270 | |
19be0692 CMN |
271 | git_array_init(b->readers); |
272 | reader = git_array_alloc(b->readers); | |
40ed4990 | 273 | if (!reader) { |
4b99b8f5 | 274 | refcounted_strmap_free(b->header.values); |
40ed4990 RB |
275 | return -1; |
276 | } | |
19be0692 CMN |
277 | memset(reader, 0, sizeof(struct reader)); |
278 | ||
279 | reader->file_path = git__strdup(b->file_path); | |
280 | GITERR_CHECK_ALLOC(reader->file_path); | |
281 | ||
282 | git_buf_init(&reader->buffer, 0); | |
744cc03e | 283 | res = git_futils_readbuffer_updated( |
19be0692 | 284 | &reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL); |
9462c471 | 285 | |
4e90a0a4 | 286 | /* It's fine if the file doesn't exist */ |
dda708e7 VM |
287 | if (res == GIT_ENOTFOUND) |
288 | return 0; | |
289 | ||
bf99390e | 290 | if (res < 0 || (res = config_read(b->header.values->values, b, reader, level, 0)) < 0) { |
4b99b8f5 | 291 | refcounted_strmap_free(b->header.values); |
55ebd7d3 | 292 | b->header.values = NULL; |
dda708e7 | 293 | } |
c0335005 | 294 | |
53ea0513 | 295 | reader = git_array_get(b->readers, 0); |
19be0692 | 296 | git_buf_free(&reader->buffer); |
40ed4990 | 297 | |
744cc03e RB |
298 | return res; |
299 | } | |
300 | ||
bd95f836 CMN |
301 | /* The meat of the refresh, as we want to use it in different places */ |
302 | static int config__refresh(git_config_backend *cfg) | |
303 | { | |
4b99b8f5 | 304 | refcounted_strmap *values = NULL, *tmp; |
bd95f836 CMN |
305 | diskfile_backend *b = (diskfile_backend *)cfg; |
306 | struct reader *reader = NULL; | |
307 | int error = 0; | |
308 | ||
4b99b8f5 | 309 | if ((error = refcounted_strmap_alloc(&values)) < 0) |
bd95f836 CMN |
310 | goto out; |
311 | ||
312 | reader = git_array_get(b->readers, git_array_size(b->readers) - 1); | |
a37aa82e | 313 | GITERR_CHECK_ALLOC(reader); |
bd95f836 | 314 | |
bf99390e | 315 | if ((error = config_read(values->values, b, reader, b->level, 0)) < 0) |
bd95f836 CMN |
316 | goto out; |
317 | ||
4b99b8f5 CMN |
318 | git_mutex_lock(&b->header.values_mutex); |
319 | ||
320 | tmp = b->header.values; | |
321 | b->header.values = values; | |
322 | values = tmp; | |
323 | ||
324 | git_mutex_unlock(&b->header.values_mutex); | |
bd95f836 CMN |
325 | |
326 | out: | |
4b99b8f5 | 327 | refcounted_strmap_free(values); |
a37aa82e RB |
328 | if (reader) |
329 | git_buf_free(&reader->buffer); | |
bd95f836 CMN |
330 | return error; |
331 | } | |
332 | ||
54b2a37a | 333 | static int config_refresh(git_config_backend *cfg) |
744cc03e | 334 | { |
c047317e | 335 | int error = 0, updated = 0, any_updated = 0; |
744cc03e | 336 | diskfile_backend *b = (diskfile_backend *)cfg; |
6014b7b5 | 337 | struct reader *reader = NULL; |
a9fb7989 | 338 | uint32_t i; |
744cc03e | 339 | |
a9fb7989 CMN |
340 | for (i = 0; i < git_array_size(b->readers); i++) { |
341 | reader = git_array_get(b->readers, i); | |
c047317e | 342 | error = git_futils_readbuffer_updated( |
40ed4990 RB |
343 | &reader->buffer, reader->file_path, |
344 | &reader->file_mtime, &reader->file_size, &updated); | |
a9fb7989 | 345 | |
a37aa82e RB |
346 | if (error < 0 && error != GIT_ENOTFOUND) |
347 | return error; | |
a9fb7989 CMN |
348 | |
349 | if (updated) | |
350 | any_updated = 1; | |
351 | } | |
352 | ||
353 | if (!any_updated) | |
c047317e | 354 | return (error == GIT_ENOTFOUND) ? 0 : error; |
744cc03e | 355 | |
bd95f836 | 356 | return config__refresh(cfg); |
c0335005 CMN |
357 | } |
358 | ||
54b2a37a | 359 | static void backend_free(git_config_backend *_backend) |
c0335005 | 360 | { |
b0b527e0 | 361 | diskfile_backend *backend = (diskfile_backend *)_backend; |
19be0692 | 362 | uint32_t i; |
c0335005 CMN |
363 | |
364 | if (backend == NULL) | |
365 | return; | |
366 | ||
19be0692 CMN |
367 | for (i = 0; i < git_array_size(backend->readers); i++) { |
368 | struct reader *r = git_array_get(backend->readers, i); | |
369 | git__free(r->file_path); | |
370 | } | |
371 | git_array_clear(backend->readers); | |
372 | ||
3286c408 | 373 | git__free(backend->file_path); |
4b99b8f5 | 374 | refcounted_strmap_free(backend->header.values); |
4af0ef96 | 375 | git_mutex_free(&backend->header.values_mutex); |
3286c408 | 376 | git__free(backend); |
c0335005 CMN |
377 | } |
378 | ||
a603c191 | 379 | static void config_iterator_free( |
eba73992 | 380 | git_config_iterator* iter) |
c0335005 | 381 | { |
0500a1ef | 382 | iter->backend->free(iter->backend); |
a603c191 NG |
383 | git__free(iter); |
384 | } | |
c0335005 | 385 | |
4d588d97 | 386 | static int config_iterator_next( |
99dfb538 | 387 | git_config_entry **entry, |
eba73992 | 388 | git_config_iterator *iter) |
a603c191 | 389 | { |
82ae6fcd | 390 | git_config_file_iter *it = (git_config_file_iter *) iter; |
55ebd7d3 | 391 | diskfile_header *h = (diskfile_header *) it->parent.backend; |
4b99b8f5 | 392 | git_strmap *values = h->values->values; |
82ae6fcd | 393 | int err = 0; |
a603c191 | 394 | cvar_t * var; |
c0335005 | 395 | |
eba73992 | 396 | if (it->next_var == NULL) { |
55ebd7d3 | 397 | err = git_strmap_next((void**) &var, &(it->iter), values); |
a603c191 | 398 | } else { |
eba73992 | 399 | var = it->next_var; |
b3ff1dab | 400 | } |
c0335005 | 401 | |
a603c191 | 402 | if (err < 0) { |
eba73992 | 403 | it->next_var = NULL; |
5880962d | 404 | return err; |
a603c191 | 405 | } |
0c8858de | 406 | |
99dfb538 | 407 | *entry = var->entry; |
eba73992 | 408 | it->next_var = CVAR_LIST_NEXT(var); |
b3ff1dab | 409 | |
eba73992 CMN |
410 | return 0; |
411 | } | |
c0335005 | 412 | |
eba73992 CMN |
413 | static int config_iterator_new( |
414 | git_config_iterator **iter, | |
415 | struct git_config_backend* backend) | |
416 | { | |
0500a1ef CMN |
417 | diskfile_header *h; |
418 | git_config_file_iter *it; | |
419 | git_config_backend *snapshot; | |
420 | diskfile_backend *b = (diskfile_backend *) backend; | |
421 | int error; | |
b3ff1dab | 422 | |
0500a1ef CMN |
423 | if ((error = config_snapshot(&snapshot, backend)) < 0) |
424 | return error; | |
425 | ||
426 | if ((error = snapshot->open(snapshot, b->level)) < 0) | |
427 | return error; | |
428 | ||
429 | it = git__calloc(1, sizeof(git_config_file_iter)); | |
eba73992 CMN |
430 | GITERR_CHECK_ALLOC(it); |
431 | ||
0500a1ef CMN |
432 | h = (diskfile_header *)snapshot; |
433 | ||
55ebd7d3 CMN |
434 | /* strmap_begin() is currently a macro returning 0 */ |
435 | GIT_UNUSED(h); | |
436 | ||
0500a1ef | 437 | it->parent.backend = snapshot; |
55ebd7d3 | 438 | it->iter = git_strmap_begin(h->values); |
eba73992 CMN |
439 | it->next_var = NULL; |
440 | ||
441 | it->parent.next = config_iterator_next; | |
442 | it->parent.free = config_iterator_free; | |
443 | *iter = (git_config_iterator *) it; | |
a603c191 NG |
444 | |
445 | return 0; | |
c0335005 CMN |
446 | } |
447 | ||
54b2a37a | 448 | static int config_set(git_config_backend *cfg, const char *name, const char *value) |
c0335005 | 449 | { |
b0b527e0 | 450 | diskfile_backend *b = (diskfile_backend *)cfg; |
4b99b8f5 CMN |
451 | refcounted_strmap *map; |
452 | git_strmap *values; | |
49938cad | 453 | char *key, *esc_value = NULL; |
01fed0a8 | 454 | khiter_t pos; |
49938cad | 455 | int rval, ret; |
fefd4551 | 456 | |
f4be8209 | 457 | if ((rval = git_config__normalize_name(name, &key)) < 0) |
1e7799e8 | 458 | return rval; |
c0335005 | 459 | |
4b99b8f5 CMN |
460 | map = refcounted_strmap_take(&b->header); |
461 | values = map->values; | |
462 | ||
c0335005 | 463 | /* |
fefd4551 CMN |
464 | * Try to find it in the existing values and update it if it |
465 | * only has one value. | |
c0335005 | 466 | */ |
55ebd7d3 CMN |
467 | pos = git_strmap_lookup_index(values, key); |
468 | if (git_strmap_valid_index(values, pos)) { | |
469 | cvar_t *existing = git_strmap_value_at(values, pos); | |
aa13bf05 | 470 | |
dda708e7 VM |
471 | if (existing->next != NULL) { |
472 | giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); | |
4b99b8f5 CMN |
473 | ret = -1; |
474 | goto out; | |
dda708e7 | 475 | } |
fefd4551 | 476 | |
aa13bf05 | 477 | /* don't update if old and new values already match */ |
a1abe66a | 478 | if ((!existing->entry->value && !value) || |
eaf37034 CMN |
479 | (existing->entry->value && value && |
480 | !strcmp(existing->entry->value, value))) { | |
4b99b8f5 CMN |
481 | ret = 0; |
482 | goto out; | |
dda708e7 | 483 | } |
29dca088 CMN |
484 | } |
485 | ||
eaf37034 | 486 | /* No early returns due to sanity checks, let's write it out and refresh */ |
c0335005 | 487 | |
dda708e7 | 488 | if (value) { |
49938cad CMN |
489 | esc_value = escape_value(value); |
490 | GITERR_CHECK_ALLOC(esc_value); | |
c0335005 CMN |
491 | } |
492 | ||
eaf37034 CMN |
493 | if ((ret = config_write(b, key, NULL, esc_value)) < 0) |
494 | goto out; | |
3df9cc59 | 495 | |
eaf37034 | 496 | ret = config_refresh(cfg); |
c0335005 | 497 | |
eaf37034 | 498 | out: |
4b99b8f5 | 499 | refcounted_strmap_free(map); |
eaf37034 CMN |
500 | git__free(esc_value); |
501 | git__free(key); | |
502 | return ret; | |
c0335005 CMN |
503 | } |
504 | ||
9a97f49e CMN |
505 | /* release the map containing the entry as an equivalent to freeing it */ |
506 | static void release_map(git_config_entry *entry) | |
507 | { | |
508 | refcounted_strmap *map = (refcounted_strmap *) entry->payload; | |
509 | refcounted_strmap_free(map); | |
510 | } | |
511 | ||
c0335005 CMN |
512 | /* |
513 | * Internal function that actually gets the value in string form | |
514 | */ | |
9a97f49e | 515 | static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out) |
c0335005 | 516 | { |
55ebd7d3 | 517 | diskfile_header *h = (diskfile_header *)cfg; |
4b99b8f5 | 518 | refcounted_strmap *map; |
523032cd CMN |
519 | git_strmap *values; |
520 | khiter_t pos; | |
73fc5e01 | 521 | cvar_t *var; |
9a97f49e | 522 | int error = 0; |
523032cd | 523 | |
9a97f49e | 524 | if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0)) |
523032cd CMN |
525 | return error; |
526 | ||
4b99b8f5 CMN |
527 | map = refcounted_strmap_take(h); |
528 | values = map->values; | |
529 | ||
523032cd | 530 | pos = git_strmap_lookup_index(values, key); |
c0335005 | 531 | |
dda708e7 | 532 | /* no error message; the config system will write one */ |
4b99b8f5 CMN |
533 | if (!git_strmap_valid_index(values, pos)) { |
534 | refcounted_strmap_free(map); | |
dda708e7 | 535 | return GIT_ENOTFOUND; |
4b99b8f5 | 536 | } |
c0335005 | 537 | |
55ebd7d3 | 538 | var = git_strmap_value_at(values, pos); |
73fc5e01 CMN |
539 | while (var->next) |
540 | var = var->next; | |
541 | ||
542 | *out = var->entry; | |
9a97f49e CMN |
543 | (*out)->free = release_map; |
544 | (*out)->payload = map; | |
545 | ||
546 | return error; | |
c0335005 CMN |
547 | } |
548 | ||
01fed0a8 | 549 | static int config_set_multivar( |
54b2a37a | 550 | git_config_backend *cfg, const char *name, const char *regexp, const char *value) |
3005855f | 551 | { |
3005855f | 552 | diskfile_backend *b = (diskfile_backend *)cfg; |
4b99b8f5 CMN |
553 | refcounted_strmap *map; |
554 | git_strmap *values; | |
3005855f CMN |
555 | char *key; |
556 | regex_t preg; | |
dda708e7 | 557 | int result; |
01fed0a8 | 558 | khiter_t pos; |
3005855f | 559 | |
dda708e7 | 560 | assert(regexp); |
3005855f | 561 | |
f4be8209 | 562 | if ((result = git_config__normalize_name(name, &key)) < 0) |
1e7799e8 | 563 | return result; |
3005855f | 564 | |
4b99b8f5 CMN |
565 | map = refcounted_strmap_take(&b->header); |
566 | values = b->header.values->values; | |
567 | ||
55ebd7d3 CMN |
568 | pos = git_strmap_lookup_index(values, key); |
569 | if (!git_strmap_valid_index(values, pos)) { | |
e5a27f03 CMN |
570 | /* If we don't have it, behave like a normal set */ |
571 | result = config_set(cfg, name, value); | |
4b99b8f5 | 572 | refcounted_strmap_free(map); |
01fed0a8 | 573 | git__free(key); |
e5a27f03 | 574 | return result; |
0a43d7cb | 575 | } |
3005855f | 576 | |
dda708e7 | 577 | result = regcomp(&preg, regexp, REG_EXTENDED); |
129022ee | 578 | if (result != 0) { |
dda708e7 | 579 | giterr_set_regex(&preg, result); |
4b99b8f5 CMN |
580 | result = -1; |
581 | goto out; | |
0a43d7cb | 582 | } |
3005855f | 583 | |
eaf37034 CMN |
584 | /* If we do have it, set call config_write() and reload */ |
585 | if ((result = config_write(b, key, &preg, value)) < 0) | |
586 | goto out; | |
3005855f | 587 | |
eaf37034 | 588 | result = config_refresh(cfg); |
3005855f | 589 | |
eaf37034 | 590 | out: |
4b99b8f5 | 591 | refcounted_strmap_free(map); |
2bc8fa02 | 592 | git__free(key); |
3005855f | 593 | regfree(&preg); |
dda708e7 VM |
594 | |
595 | return result; | |
3005855f CMN |
596 | } |
597 | ||
54b2a37a | 598 | static int config_delete(git_config_backend *cfg, const char *name) |
80a665aa | 599 | { |
dda708e7 | 600 | cvar_t *var; |
80a665aa | 601 | diskfile_backend *b = (diskfile_backend *)cfg; |
4b99b8f5 | 602 | refcounted_strmap *map; git_strmap *values; |
fefd4551 | 603 | char *key; |
dda708e7 | 604 | int result; |
01fed0a8 | 605 | khiter_t pos; |
fefd4551 | 606 | |
f4be8209 | 607 | if ((result = git_config__normalize_name(name, &key)) < 0) |
1e7799e8 | 608 | return result; |
fefd4551 | 609 | |
4b99b8f5 CMN |
610 | map = refcounted_strmap_take(&b->header); |
611 | values = b->header.values->values; | |
612 | ||
55ebd7d3 | 613 | pos = git_strmap_lookup_index(values, key); |
2bc8fa02 | 614 | git__free(key); |
fefd4551 | 615 | |
55ebd7d3 | 616 | if (!git_strmap_valid_index(values, pos)) { |
4b99b8f5 | 617 | refcounted_strmap_free(map); |
1d4dcc4b | 618 | giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); |
dda708e7 | 619 | return GIT_ENOTFOUND; |
1d4dcc4b | 620 | } |
fefd4551 | 621 | |
55ebd7d3 | 622 | var = git_strmap_value_at(values, pos); |
4b99b8f5 | 623 | refcounted_strmap_free(map); |
80a665aa | 624 | |
dda708e7 VM |
625 | if (var->next != NULL) { |
626 | giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete"); | |
627 | return -1; | |
628 | } | |
80a665aa | 629 | |
8c1f4ab4 CMN |
630 | if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0) |
631 | return result; | |
fefd4551 | 632 | |
8c1f4ab4 | 633 | return config_refresh(cfg); |
80a665aa CMN |
634 | } |
635 | ||
3793fa9b DRT |
636 | static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp) |
637 | { | |
3793fa9b | 638 | diskfile_backend *b = (diskfile_backend *)cfg; |
4b99b8f5 CMN |
639 | refcounted_strmap *map; |
640 | git_strmap *values; | |
3793fa9b DRT |
641 | char *key; |
642 | regex_t preg; | |
643 | int result; | |
644 | khiter_t pos; | |
645 | ||
646 | if ((result = git_config__normalize_name(name, &key)) < 0) | |
647 | return result; | |
648 | ||
4b99b8f5 CMN |
649 | map = refcounted_strmap_take(&b->header); |
650 | values = b->header.values->values; | |
651 | ||
55ebd7d3 | 652 | pos = git_strmap_lookup_index(values, key); |
3793fa9b | 653 | |
55ebd7d3 | 654 | if (!git_strmap_valid_index(values, pos)) { |
4b99b8f5 | 655 | refcounted_strmap_free(map); |
3793fa9b | 656 | git__free(key); |
8c1f4ab4 | 657 | giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name); |
3793fa9b DRT |
658 | return GIT_ENOTFOUND; |
659 | } | |
660 | ||
4b99b8f5 CMN |
661 | refcounted_strmap_free(map); |
662 | ||
3793fa9b | 663 | result = regcomp(&preg, regexp, REG_EXTENDED); |
129022ee | 664 | if (result != 0) { |
3793fa9b | 665 | giterr_set_regex(&preg, result); |
8c1f4ab4 CMN |
666 | result = -1; |
667 | goto out; | |
3793fa9b DRT |
668 | } |
669 | ||
8c1f4ab4 CMN |
670 | if ((result = config_write(b, key, &preg, NULL)) < 0) |
671 | goto out; | |
3793fa9b | 672 | |
8c1f4ab4 | 673 | result = config_refresh(cfg); |
3793fa9b | 674 | |
8c1f4ab4 | 675 | out: |
3793fa9b | 676 | git__free(key); |
a71331eb | 677 | regfree(&preg); |
3793fa9b DRT |
678 | return result; |
679 | } | |
680 | ||
55ebd7d3 CMN |
681 | static int config_snapshot(git_config_backend **out, git_config_backend *in) |
682 | { | |
683 | diskfile_backend *b = (diskfile_backend *) in; | |
684 | ||
685 | return git_config_file__snapshot(out, b); | |
686 | } | |
687 | ||
54b2a37a | 688 | int git_config_file__ondisk(git_config_backend **out, const char *path) |
c0335005 | 689 | { |
b0b527e0 | 690 | diskfile_backend *backend; |
c0335005 | 691 | |
69177621 | 692 | backend = git__calloc(1, sizeof(diskfile_backend)); |
dda708e7 | 693 | GITERR_CHECK_ALLOC(backend); |
c0335005 | 694 | |
55ebd7d3 | 695 | backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; |
4af0ef96 | 696 | git_mutex_init(&backend->header.values_mutex); |
c0335005 CMN |
697 | |
698 | backend->file_path = git__strdup(path); | |
dda708e7 | 699 | GITERR_CHECK_ALLOC(backend->file_path); |
c0335005 | 700 | |
55ebd7d3 CMN |
701 | backend->header.parent.open = config_open; |
702 | backend->header.parent.get = config_get; | |
703 | backend->header.parent.set = config_set; | |
704 | backend->header.parent.set_multivar = config_set_multivar; | |
705 | backend->header.parent.del = config_delete; | |
706 | backend->header.parent.del_multivar = config_delete_multivar; | |
707 | backend->header.parent.iterator = config_iterator_new; | |
55ebd7d3 CMN |
708 | backend->header.parent.snapshot = config_snapshot; |
709 | backend->header.parent.free = backend_free; | |
710 | ||
711 | *out = (git_config_backend *)backend; | |
712 | ||
713 | return 0; | |
714 | } | |
715 | ||
716 | static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value) | |
717 | { | |
718 | GIT_UNUSED(cfg); | |
719 | GIT_UNUSED(name); | |
720 | GIT_UNUSED(value); | |
721 | ||
722 | return config_error_readonly(); | |
723 | } | |
724 | ||
725 | static int config_set_multivar_readonly( | |
726 | git_config_backend *cfg, const char *name, const char *regexp, const char *value) | |
727 | { | |
728 | GIT_UNUSED(cfg); | |
729 | GIT_UNUSED(name); | |
730 | GIT_UNUSED(regexp); | |
731 | GIT_UNUSED(value); | |
732 | ||
733 | return config_error_readonly(); | |
734 | } | |
735 | ||
736 | static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp) | |
737 | { | |
738 | GIT_UNUSED(cfg); | |
739 | GIT_UNUSED(name); | |
740 | GIT_UNUSED(regexp); | |
741 | ||
742 | return config_error_readonly(); | |
743 | } | |
744 | ||
745 | static int config_delete_readonly(git_config_backend *cfg, const char *name) | |
746 | { | |
747 | GIT_UNUSED(cfg); | |
748 | GIT_UNUSED(name); | |
749 | ||
750 | return config_error_readonly(); | |
751 | } | |
752 | ||
55ebd7d3 CMN |
753 | static void backend_readonly_free(git_config_backend *_backend) |
754 | { | |
755 | diskfile_backend *backend = (diskfile_backend *)_backend; | |
756 | ||
757 | if (backend == NULL) | |
758 | return; | |
759 | ||
4b99b8f5 | 760 | refcounted_strmap_free(backend->header.values); |
4af0ef96 | 761 | git_mutex_free(&backend->header.values_mutex); |
55ebd7d3 CMN |
762 | git__free(backend); |
763 | } | |
764 | ||
55ebd7d3 CMN |
765 | static int config_readonly_open(git_config_backend *cfg, git_config_level_t level) |
766 | { | |
767 | diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg; | |
768 | diskfile_backend *src = b->snapshot_from; | |
ad5adacb | 769 | diskfile_header *src_header = &src->header; |
4b99b8f5 | 770 | refcounted_strmap *src_map; |
ad5adacb AR |
771 | int error; |
772 | ||
9a97f49e | 773 | if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0) |
ad5adacb | 774 | return error; |
55ebd7d3 CMN |
775 | |
776 | /* We're just copying data, don't care about the level */ | |
777 | GIT_UNUSED(level); | |
778 | ||
ad5adacb | 779 | src_map = refcounted_strmap_take(src_header); |
2280b388 | 780 | b->header.values = src_map; |
55ebd7d3 | 781 | |
2280b388 | 782 | return 0; |
55ebd7d3 CMN |
783 | } |
784 | ||
785 | int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) | |
786 | { | |
787 | diskfile_readonly_backend *backend; | |
788 | ||
789 | backend = git__calloc(1, sizeof(diskfile_readonly_backend)); | |
790 | GITERR_CHECK_ALLOC(backend); | |
791 | ||
792 | backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; | |
4af0ef96 | 793 | git_mutex_init(&backend->header.values_mutex); |
55ebd7d3 CMN |
794 | |
795 | backend->snapshot_from = in; | |
796 | ||
9a97f49e | 797 | backend->header.parent.readonly = 1; |
55ebd7d3 CMN |
798 | backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; |
799 | backend->header.parent.open = config_readonly_open; | |
800 | backend->header.parent.get = config_get; | |
801 | backend->header.parent.set = config_set_readonly; | |
802 | backend->header.parent.set_multivar = config_set_multivar_readonly; | |
803 | backend->header.parent.del = config_delete_readonly; | |
804 | backend->header.parent.del_multivar = config_delete_multivar_readonly; | |
805 | backend->header.parent.iterator = config_iterator_new; | |
55ebd7d3 | 806 | backend->header.parent.free = backend_readonly_free; |
c0335005 | 807 | |
54b2a37a | 808 | *out = (git_config_backend *)backend; |
c0335005 | 809 | |
dda708e7 | 810 | return 0; |
c0335005 CMN |
811 | } |
812 | ||
d209cc47 | 813 | static int reader_getchar_raw(struct reader *reader) |
c0335005 CMN |
814 | { |
815 | int c; | |
816 | ||
d209cc47 | 817 | c = *reader->read_ptr++; |
c0335005 CMN |
818 | |
819 | /* | |
820 | Win 32 line breaks: if we find a \r\n sequence, | |
821 | return only the \n as a newline | |
822 | */ | |
d209cc47 CMN |
823 | if (c == '\r' && *reader->read_ptr == '\n') { |
824 | reader->read_ptr++; | |
c0335005 CMN |
825 | c = '\n'; |
826 | } | |
827 | ||
828 | if (c == '\n') | |
d209cc47 | 829 | reader->line_number++; |
c0335005 CMN |
830 | |
831 | if (c == 0) { | |
d209cc47 | 832 | reader->eof = 1; |
2a950c94 | 833 | c = '\0'; |
c0335005 CMN |
834 | } |
835 | ||
836 | return c; | |
837 | } | |
838 | ||
839 | #define SKIP_WHITESPACE (1 << 1) | |
840 | #define SKIP_COMMENTS (1 << 2) | |
841 | ||
d209cc47 | 842 | static int reader_getchar(struct reader *reader, int flags) |
c0335005 CMN |
843 | { |
844 | const int skip_whitespace = (flags & SKIP_WHITESPACE); | |
845 | const int skip_comments = (flags & SKIP_COMMENTS); | |
846 | int c; | |
847 | ||
d209cc47 | 848 | assert(reader->read_ptr); |
c0335005 | 849 | |
d209cc47 CMN |
850 | do { |
851 | c = reader_getchar_raw(reader); | |
2a950c94 | 852 | } while (c != '\n' && c != '\0' && skip_whitespace && git__isspace(c)); |
c0335005 CMN |
853 | |
854 | if (skip_comments && (c == '#' || c == ';')) { | |
d209cc47 CMN |
855 | do { |
856 | c = reader_getchar_raw(reader); | |
2a950c94 | 857 | } while (c != '\n' && c != '\0'); |
c0335005 CMN |
858 | } |
859 | ||
860 | return c; | |
861 | } | |
862 | ||
863 | /* | |
864 | * Read the next char, but don't move the reading pointer. | |
865 | */ | |
d209cc47 | 866 | static int reader_peek(struct reader *reader, int flags) |
c0335005 CMN |
867 | { |
868 | void *old_read_ptr; | |
869 | int old_lineno, old_eof; | |
870 | int ret; | |
871 | ||
d209cc47 | 872 | assert(reader->read_ptr); |
c0335005 | 873 | |
d209cc47 CMN |
874 | old_read_ptr = reader->read_ptr; |
875 | old_lineno = reader->line_number; | |
876 | old_eof = reader->eof; | |
c0335005 | 877 | |
d209cc47 | 878 | ret = reader_getchar(reader, flags); |
c0335005 | 879 | |
d209cc47 CMN |
880 | reader->read_ptr = old_read_ptr; |
881 | reader->line_number = old_lineno; | |
882 | reader->eof = old_eof; | |
c0335005 CMN |
883 | |
884 | return ret; | |
885 | } | |
886 | ||
c0335005 CMN |
887 | /* |
888 | * Read and consume a line, returning it in newly-allocated memory. | |
889 | */ | |
d209cc47 | 890 | static char *reader_readline(struct reader *reader, bool skip_whitespace) |
c0335005 CMN |
891 | { |
892 | char *line = NULL; | |
893 | char *line_src, *line_end; | |
f1453c59 | 894 | size_t line_len, alloc_len; |
c0335005 | 895 | |
d209cc47 | 896 | line_src = reader->read_ptr; |
f2abee47 | 897 | |
2c1075d6 CMN |
898 | if (skip_whitespace) { |
899 | /* Skip empty empty lines */ | |
0f49200c | 900 | while (git__isspace(*line_src)) |
2c1075d6 CMN |
901 | ++line_src; |
902 | } | |
f2abee47 | 903 | |
45e93ef3 | 904 | line_end = strchr(line_src, '\n'); |
c0335005 | 905 | |
45e93ef3 | 906 | /* no newline at EOF */ |
c0335005 CMN |
907 | if (line_end == NULL) |
908 | line_end = strchr(line_src, 0); | |
c0335005 | 909 | |
f2abee47 | 910 | line_len = line_end - line_src; |
c0335005 | 911 | |
f1453c59 ET |
912 | if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, line_len, 1) || |
913 | (line = git__malloc(alloc_len)) == NULL) { | |
c0335005 | 914 | return NULL; |
392702ee | 915 | } |
c0335005 | 916 | |
f2abee47 | 917 | memcpy(line, line_src, line_len); |
c0335005 | 918 | |
79a34396 | 919 | do line[line_len] = '\0'; |
0f49200c | 920 | while (line_len-- > 0 && git__isspace(line[line_len])); |
c0335005 CMN |
921 | |
922 | if (*line_end == '\n') | |
923 | line_end++; | |
924 | ||
925 | if (*line_end == '\0') | |
d209cc47 | 926 | reader->eof = 1; |
c0335005 | 927 | |
d209cc47 CMN |
928 | reader->line_number++; |
929 | reader->read_ptr = line_end; | |
c0335005 CMN |
930 | |
931 | return line; | |
932 | } | |
933 | ||
934 | /* | |
935 | * Consume a line, without storing it anywhere | |
936 | */ | |
d209cc47 | 937 | static void reader_consume_line(struct reader *reader) |
c0335005 CMN |
938 | { |
939 | char *line_start, *line_end; | |
940 | ||
d209cc47 | 941 | line_start = reader->read_ptr; |
c0335005 CMN |
942 | line_end = strchr(line_start, '\n'); |
943 | /* No newline at EOF */ | |
944 | if(line_end == NULL){ | |
945 | line_end = strchr(line_start, '\0'); | |
946 | } | |
947 | ||
948 | if (*line_end == '\n') | |
949 | line_end++; | |
950 | ||
951 | if (*line_end == '\0') | |
d209cc47 | 952 | reader->eof = 1; |
c0335005 | 953 | |
d209cc47 CMN |
954 | reader->line_number++; |
955 | reader->read_ptr = line_end; | |
c0335005 CMN |
956 | } |
957 | ||
765fdf4a | 958 | GIT_INLINE(int) config_keychar(int c) |
c0335005 CMN |
959 | { |
960 | return isalnum(c) || c == '-'; | |
961 | } | |
962 | ||
d209cc47 | 963 | static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name) |
c0335005 | 964 | { |
9ac581bf CMN |
965 | int c, rpos; |
966 | char *first_quote, *last_quote; | |
967 | git_buf buf = GIT_BUF_INIT; | |
f1453c59 | 968 | size_t quoted_len, alloc_len, base_name_len = strlen(base_name); |
392702ee | 969 | |
c0335005 CMN |
970 | /* |
971 | * base_name is what came before the space. We should be at the | |
972 | * first quotation mark, except for now, line isn't being kept in | |
973 | * sync so we only really use it to calculate the length. | |
974 | */ | |
975 | ||
976 | first_quote = strchr(line, '"'); | |
977 | last_quote = strrchr(line, '"'); | |
392702ee | 978 | quoted_len = last_quote - first_quote; |
c0335005 | 979 | |
392702ee | 980 | if (quoted_len == 0) { |
d209cc47 | 981 | set_parse_error(reader, 0, "Missing closing quotation mark in section header"); |
dda708e7 VM |
982 | return -1; |
983 | } | |
c0335005 | 984 | |
f1453c59 ET |
985 | GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len); |
986 | GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2); | |
392702ee | 987 | |
f1453c59 | 988 | git_buf_grow(&buf, alloc_len); |
9ac581bf | 989 | git_buf_printf(&buf, "%s.", base_name); |
c0335005 | 990 | |
c0335005 | 991 | rpos = 0; |
c0335005 CMN |
992 | |
993 | line = first_quote; | |
566dd8ce | 994 | c = line[++rpos]; |
c0335005 CMN |
995 | |
996 | /* | |
997 | * At the end of each iteration, whatever is stored in c will be | |
998 | * added to the string. In case of error, jump to out | |
999 | */ | |
1000 | do { | |
5892277c | 1001 | |
c0335005 | 1002 | switch (c) { |
2d9f5b9f | 1003 | case 0: |
d209cc47 | 1004 | set_parse_error(reader, 0, "Unexpected end-of-line in section header"); |
2d9f5b9f ET |
1005 | git_buf_free(&buf); |
1006 | return -1; | |
1007 | ||
c0335005 | 1008 | case '"': |
566dd8ce | 1009 | goto end_parse; |
dda708e7 | 1010 | |
c0335005 | 1011 | case '\\': |
566dd8ce | 1012 | c = line[++rpos]; |
2d9f5b9f | 1013 | |
566dd8ce L |
1014 | if (c == 0) { |
1015 | set_parse_error(reader, rpos, "Unexpected end-of-line in section header"); | |
dda708e7 VM |
1016 | git_buf_free(&buf); |
1017 | return -1; | |
c0335005 | 1018 | } |
dda708e7 | 1019 | |
c0335005 CMN |
1020 | default: |
1021 | break; | |
1022 | } | |
1023 | ||
66566516 | 1024 | git_buf_putc(&buf, (char)c); |
566dd8ce L |
1025 | c = line[++rpos]; |
1026 | } while (line + rpos < last_quote); | |
1027 | ||
1028 | end_parse: | |
1029 | if (line[rpos] != '"' || line[rpos + 1] != ']') { | |
1030 | set_parse_error(reader, rpos, "Unexpected text after closing quotes"); | |
1031 | git_buf_free(&buf); | |
1032 | return -1; | |
1033 | } | |
c0335005 | 1034 | |
dda708e7 VM |
1035 | *section_name = git_buf_detach(&buf); |
1036 | return 0; | |
c0335005 CMN |
1037 | } |
1038 | ||
d209cc47 | 1039 | static int parse_section_header(struct reader *reader, char **section_out) |
c0335005 CMN |
1040 | { |
1041 | char *name, *name_end; | |
1042 | int name_length, c, pos; | |
dda708e7 | 1043 | int result; |
c0335005 | 1044 | char *line; |
392702ee | 1045 | size_t line_len; |
c0335005 | 1046 | |
d209cc47 | 1047 | line = reader_readline(reader, true); |
c0335005 | 1048 | if (line == NULL) |
dda708e7 | 1049 | return -1; |
c0335005 CMN |
1050 | |
1051 | /* find the end of the variable's name */ | |
566dd8ce | 1052 | name_end = strrchr(line, ']'); |
5a0659fe | 1053 | if (name_end == NULL) { |
3286c408 | 1054 | git__free(line); |
d209cc47 | 1055 | set_parse_error(reader, 0, "Missing ']' in section header"); |
dda708e7 | 1056 | return -1; |
5a0659fe | 1057 | } |
c0335005 | 1058 | |
f1453c59 | 1059 | GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1); |
392702ee | 1060 | name = git__malloc(line_len); |
dda708e7 | 1061 | GITERR_CHECK_ALLOC(name); |
c0335005 CMN |
1062 | |
1063 | name_length = 0; | |
1064 | pos = 0; | |
1065 | ||
1066 | /* Make sure we were given a section header */ | |
1067 | c = line[pos++]; | |
dda708e7 | 1068 | assert(c == '['); |
c0335005 CMN |
1069 | |
1070 | c = line[pos++]; | |
1071 | ||
1072 | do { | |
0f49200c | 1073 | if (git__isspace(c)){ |
c0335005 | 1074 | name[name_length] = '\0'; |
d209cc47 | 1075 | result = parse_section_header_ext(reader, line, name, section_out); |
3286c408 VM |
1076 | git__free(line); |
1077 | git__free(name); | |
dda708e7 | 1078 | return result; |
c0335005 CMN |
1079 | } |
1080 | ||
1081 | if (!config_keychar(c) && c != '.') { | |
d209cc47 | 1082 | set_parse_error(reader, pos, "Unexpected character in header"); |
dda708e7 | 1083 | goto fail_parse; |
c0335005 CMN |
1084 | } |
1085 | ||
75a4636f | 1086 | name[name_length++] = (char)git__tolower(c); |
c0335005 CMN |
1087 | |
1088 | } while ((c = line[pos++]) != ']'); | |
1089 | ||
5a0659fe | 1090 | if (line[pos - 1] != ']') { |
d209cc47 | 1091 | set_parse_error(reader, pos, "Unexpected end of file"); |
dda708e7 | 1092 | goto fail_parse; |
5a0659fe | 1093 | } |
f58c53ce | 1094 | |
3286c408 | 1095 | git__free(line); |
dda708e7 VM |
1096 | |
1097 | name[name_length] = 0; | |
c0335005 | 1098 | *section_out = name; |
c0335005 | 1099 | |
dda708e7 VM |
1100 | return 0; |
1101 | ||
1102 | fail_parse: | |
3286c408 VM |
1103 | git__free(line); |
1104 | git__free(name); | |
dda708e7 | 1105 | return -1; |
c0335005 CMN |
1106 | } |
1107 | ||
d209cc47 | 1108 | static int skip_bom(struct reader *reader) |
c0335005 | 1109 | { |
7bf87ab6 RB |
1110 | git_bom_t bom; |
1111 | int bom_offset = git_buf_text_detect_bom(&bom, | |
d209cc47 | 1112 | &reader->buffer, reader->read_ptr - reader->buffer.ptr); |
c0335005 | 1113 | |
7bf87ab6 | 1114 | if (bom == GIT_BOM_UTF8) |
d209cc47 | 1115 | reader->read_ptr += bom_offset; |
c0335005 | 1116 | |
7bf87ab6 | 1117 | /* TODO: reference implementation is pretty stupid with BoM */ |
c0335005 | 1118 | |
dda708e7 | 1119 | return 0; |
c0335005 CMN |
1120 | } |
1121 | ||
1122 | /* | |
1123 | (* basic types *) | |
1124 | digit = "0".."9" | |
1125 | integer = digit { digit } | |
1126 | alphabet = "a".."z" + "A" .. "Z" | |
1127 | ||
1128 | section_char = alphabet | "." | "-" | |
1129 | extension_char = (* any character except newline *) | |
1130 | any_char = (* any character *) | |
1131 | variable_char = "alphabet" | "-" | |
1132 | ||
1133 | ||
1134 | (* actual grammar *) | |
1135 | config = { section } | |
1136 | ||
1137 | section = header { definition } | |
1138 | ||
1139 | header = "[" section [subsection | subsection_ext] "]" | |
1140 | ||
1141 | subsection = "." section | |
1142 | subsection_ext = "\"" extension "\"" | |
1143 | ||
1144 | section = section_char { section_char } | |
1145 | extension = extension_char { extension_char } | |
1146 | ||
1147 | definition = variable_name ["=" variable_value] "\n" | |
1148 | ||
1149 | variable_name = variable_char { variable_char } | |
1150 | variable_value = string | boolean | integer | |
1151 | ||
1152 | string = quoted_string | plain_string | |
1153 | quoted_string = "\"" plain_string "\"" | |
1154 | plain_string = { any_char } | |
1155 | ||
1156 | boolean = boolean_true | boolean_false | |
1157 | boolean_true = "yes" | "1" | "true" | "on" | |
1158 | boolean_false = "no" | "0" | "false" | "off" | |
1159 | */ | |
1160 | ||
2c1075d6 | 1161 | static int strip_comments(char *line, int in_quotes) |
c0335005 | 1162 | { |
e009a705 | 1163 | int quote_count = in_quotes, backslash_count = 0; |
c0335005 CMN |
1164 | char *ptr; |
1165 | ||
1166 | for (ptr = line; *ptr; ++ptr) { | |
1167 | if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\') | |
1168 | quote_count++; | |
1169 | ||
e009a705 ET |
1170 | if ((ptr[0] == ';' || ptr[0] == '#') && |
1171 | (quote_count % 2) == 0 && | |
1172 | (backslash_count % 2) == 0) { | |
c0335005 CMN |
1173 | ptr[0] = '\0'; |
1174 | break; | |
1175 | } | |
e009a705 ET |
1176 | |
1177 | if (ptr[0] == '\\') | |
1178 | backslash_count++; | |
1179 | else | |
1180 | backslash_count = 0; | |
c0335005 CMN |
1181 | } |
1182 | ||
2c1075d6 | 1183 | /* skip any space at the end */ |
0a641647 | 1184 | while (ptr > line && git__isspace(ptr[-1])) { |
2c1075d6 | 1185 | ptr--; |
c0335005 | 1186 | } |
2c1075d6 CMN |
1187 | ptr[0] = '\0'; |
1188 | ||
1189 | return quote_count; | |
c0335005 CMN |
1190 | } |
1191 | ||
d8d25acb CMN |
1192 | static int included_path(git_buf *out, const char *dir, const char *path) |
1193 | { | |
1194 | /* From the user's home */ | |
1195 | if (path[0] == '~' && path[1] == '/') | |
83634d38 | 1196 | return git_sysdir_find_global_file(out, &path[1]); |
d8d25acb CMN |
1197 | |
1198 | return git_path_join_unrooted(out, path, dir, NULL); | |
1199 | } | |
1200 | ||
49938cad CMN |
1201 | static const char *escapes = "ntb\"\\"; |
1202 | static const char *escaped = "\n\t\b\"\\"; | |
1203 | ||
1204 | /* Escape the values to write them to the file */ | |
1205 | static char *escape_value(const char *ptr) | |
1206 | { | |
1207 | git_buf buf = GIT_BUF_INIT; | |
1208 | size_t len; | |
1209 | const char *esc; | |
1210 | ||
1211 | assert(ptr); | |
1212 | ||
1213 | len = strlen(ptr); | |
c57f6682 NV |
1214 | if (!len) |
1215 | return git__calloc(1, sizeof(char)); | |
1216 | ||
49938cad CMN |
1217 | git_buf_grow(&buf, len); |
1218 | ||
1219 | while (*ptr != '\0') { | |
1220 | if ((esc = strchr(escaped, *ptr)) != NULL) { | |
1221 | git_buf_putc(&buf, '\\'); | |
1222 | git_buf_putc(&buf, escapes[esc - escaped]); | |
1223 | } else { | |
1224 | git_buf_putc(&buf, *ptr); | |
1225 | } | |
1226 | ptr++; | |
1227 | } | |
1228 | ||
1229 | if (git_buf_oom(&buf)) { | |
1230 | git_buf_free(&buf); | |
1231 | return NULL; | |
1232 | } | |
1233 | ||
1234 | return git_buf_detach(&buf); | |
1235 | } | |
1236 | ||
2c1075d6 | 1237 | /* '\"' -> '"' etc */ |
7f2e61f3 ET |
1238 | static int unescape_line( |
1239 | char **out, bool *is_multi, const char *ptr, int quote_count) | |
c0335005 | 1240 | { |
7f2e61f3 | 1241 | char *str, *fixed, *esc; |
f1453c59 | 1242 | size_t ptr_len = strlen(ptr), alloc_len; |
2c1075d6 | 1243 | |
7f2e61f3 ET |
1244 | *is_multi = false; |
1245 | ||
f1453c59 ET |
1246 | if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) || |
1247 | (str = git__malloc(alloc_len)) == NULL) { | |
7f2e61f3 | 1248 | return -1; |
392702ee ET |
1249 | } |
1250 | ||
7f2e61f3 | 1251 | fixed = str; |
c0335005 | 1252 | |
2c1075d6 CMN |
1253 | while (*ptr != '\0') { |
1254 | if (*ptr == '"') { | |
1255 | quote_count++; | |
1256 | } else if (*ptr != '\\') { | |
7f2e61f3 | 1257 | *fixed++ = *ptr; |
2c1075d6 CMN |
1258 | } else { |
1259 | /* backslash, check the next char */ | |
1260 | ptr++; | |
1261 | /* if we're at the end, it's a multiline, so keep the backslash */ | |
1262 | if (*ptr == '\0') { | |
7f2e61f3 ET |
1263 | *is_multi = true; |
1264 | goto done; | |
2c1075d6 | 1265 | } |
2c1075d6 | 1266 | if ((esc = strchr(escapes, *ptr)) != NULL) { |
7f2e61f3 | 1267 | *fixed++ = escaped[esc - escapes]; |
2c1075d6 CMN |
1268 | } else { |
1269 | git__free(str); | |
1270 | giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr); | |
7f2e61f3 | 1271 | return -1; |
2c1075d6 CMN |
1272 | } |
1273 | } | |
1274 | ptr++; | |
1275 | } | |
1276 | ||
7f2e61f3 ET |
1277 | done: |
1278 | *fixed = '\0'; | |
1279 | *out = str; | |
9f35754a | 1280 | |
7f2e61f3 | 1281 | return 0; |
c0335005 CMN |
1282 | } |
1283 | ||
d209cc47 | 1284 | static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes) |
c0335005 | 1285 | { |
2c1075d6 CMN |
1286 | char *line = NULL, *proc_line = NULL; |
1287 | int quote_count; | |
7f2e61f3 | 1288 | bool multiline; |
c0335005 CMN |
1289 | |
1290 | /* Check that the next line exists */ | |
d209cc47 | 1291 | line = reader_readline(reader, false); |
c0335005 | 1292 | if (line == NULL) |
dda708e7 | 1293 | return -1; |
c0335005 | 1294 | |
7f2e61f3 ET |
1295 | /* We've reached the end of the file, there is no continuation. |
1296 | * (this is not an error). | |
1297 | */ | |
c0335005 | 1298 | if (line[0] == '\0') { |
dda708e7 | 1299 | git__free(line); |
7f2e61f3 | 1300 | return 0; |
c0335005 CMN |
1301 | } |
1302 | ||
2c1075d6 | 1303 | quote_count = strip_comments(line, !!in_quotes); |
c0335005 CMN |
1304 | |
1305 | /* If it was just a comment, pretend it didn't exist */ | |
1306 | if (line[0] == '\0') { | |
dda708e7 | 1307 | git__free(line); |
d209cc47 | 1308 | return parse_multiline_variable(reader, value, quote_count); |
dda708e7 | 1309 | /* TODO: unbounded recursion. This **could** be exploitable */ |
c0335005 CMN |
1310 | } |
1311 | ||
7f2e61f3 | 1312 | if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) { |
2c1075d6 CMN |
1313 | git__free(line); |
1314 | return -1; | |
c0335005 | 1315 | } |
dda708e7 | 1316 | /* add this line to the multiline var */ |
e009a705 | 1317 | |
2c1075d6 | 1318 | git_buf_puts(value, proc_line); |
dda708e7 | 1319 | git__free(line); |
2c1075d6 | 1320 | git__free(proc_line); |
c0335005 CMN |
1321 | |
1322 | /* | |
dda708e7 VM |
1323 | * If we need to continue reading the next line, let's just |
1324 | * keep putting stuff in the buffer | |
c0335005 | 1325 | */ |
7f2e61f3 | 1326 | if (multiline) |
d209cc47 | 1327 | return parse_multiline_variable(reader, value, quote_count); |
c0335005 | 1328 | |
dda708e7 | 1329 | return 0; |
c0335005 CMN |
1330 | } |
1331 | ||
2c8c00c6 ET |
1332 | GIT_INLINE(bool) is_namechar(char c) |
1333 | { | |
1334 | return isalnum(c) || c == '-'; | |
1335 | } | |
1336 | ||
1337 | static int parse_name( | |
1338 | char **name, const char **value, struct reader *reader, const char *line) | |
1339 | { | |
1340 | const char *name_end = line, *value_start; | |
1341 | ||
1342 | *name = NULL; | |
1343 | *value = NULL; | |
1344 | ||
1345 | while (*name_end && is_namechar(*name_end)) | |
1346 | name_end++; | |
1347 | ||
1348 | if (line == name_end) { | |
1349 | set_parse_error(reader, 0, "Invalid configuration key"); | |
1350 | return -1; | |
1351 | } | |
1352 | ||
1353 | value_start = name_end; | |
1354 | ||
1355 | while (*value_start && git__isspace(*value_start)) | |
1356 | value_start++; | |
1357 | ||
1358 | if (*value_start == '=') { | |
1359 | *value = value_start + 1; | |
1360 | } else if (*value_start) { | |
1361 | set_parse_error(reader, 0, "Invalid configuration key"); | |
1362 | return -1; | |
1363 | } | |
1364 | ||
1365 | if ((*name = git__strndup(line, name_end - line)) == NULL) | |
1366 | return -1; | |
1367 | ||
1368 | return 0; | |
1369 | } | |
1370 | ||
d209cc47 | 1371 | static int parse_variable(struct reader *reader, char **var_name, char **var_value) |
c0335005 | 1372 | { |
c0335005 CMN |
1373 | const char *value_start = NULL; |
1374 | char *line; | |
2c1075d6 | 1375 | int quote_count; |
7f2e61f3 | 1376 | bool multiline; |
c0335005 | 1377 | |
d209cc47 | 1378 | line = reader_readline(reader, true); |
c0335005 | 1379 | if (line == NULL) |
dda708e7 | 1380 | return -1; |
c0335005 | 1381 | |
2c1075d6 | 1382 | quote_count = strip_comments(line, 0); |
c0335005 | 1383 | |
dda708e7 VM |
1384 | /* If there is no value, boolean true is assumed */ |
1385 | *var_value = NULL; | |
c0335005 | 1386 | |
b162d97a CMN |
1387 | if (parse_name(var_name, &value_start, reader, line) < 0) |
1388 | goto on_error; | |
1389 | ||
c0335005 CMN |
1390 | /* |
1391 | * Now, let's try to parse the value | |
1392 | */ | |
1393 | if (value_start != NULL) { | |
0f49200c | 1394 | while (git__isspace(value_start[0])) |
c0335005 CMN |
1395 | value_start++; |
1396 | ||
7f2e61f3 ET |
1397 | if (unescape_line(var_value, &multiline, value_start, 0) < 0) |
1398 | goto on_error; | |
1399 | ||
1400 | if (multiline) { | |
dda708e7 | 1401 | git_buf multi_value = GIT_BUF_INIT; |
7f2e61f3 ET |
1402 | git_buf_attach(&multi_value, *var_value, 0); |
1403 | ||
1404 | if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || | |
1405 | git_buf_oom(&multi_value)) { | |
dda708e7 | 1406 | git_buf_free(&multi_value); |
7f2e61f3 | 1407 | goto on_error; |
9f861826 | 1408 | } |
c0335005 | 1409 | |
dda708e7 | 1410 | *var_value = git_buf_detach(&multi_value); |
c0335005 | 1411 | } |
c0335005 CMN |
1412 | } |
1413 | ||
3286c408 | 1414 | git__free(line); |
dda708e7 | 1415 | return 0; |
7f2e61f3 ET |
1416 | |
1417 | on_error: | |
1418 | git__free(*var_name); | |
1419 | git__free(line); | |
1420 | return -1; | |
c0335005 | 1421 | } |
bf99390e ET |
1422 | |
1423 | static int config_parse( | |
1424 | struct reader *reader, | |
2a950c94 ET |
1425 | int (*on_section)(struct reader **reader, const char *current_section, const char *line, size_t line_len, void *data), |
1426 | int (*on_variable)(struct reader **reader, const char *current_section, char *var_name, char *var_value, const char *line, size_t line_len, void *data), | |
1427 | int (*on_comment)(struct reader **reader, const char *line, size_t line_len, void *data), | |
bf99390e ET |
1428 | int (*on_eof)(struct reader **reader, void *data), |
1429 | void *data) | |
1430 | { | |
2a950c94 | 1431 | char *current_section = NULL, *var_name, *var_value, *line_start; |
bf99390e | 1432 | char c; |
2a950c94 | 1433 | size_t line_len; |
bf99390e ET |
1434 | int result = 0; |
1435 | ||
1436 | skip_bom(reader); | |
1437 | ||
1438 | while (result == 0 && !reader->eof) { | |
2a950c94 ET |
1439 | line_start = reader->read_ptr; |
1440 | ||
bf99390e ET |
1441 | c = reader_peek(reader, SKIP_WHITESPACE); |
1442 | ||
1443 | switch (c) { | |
2a950c94 | 1444 | case '\0': /* EOF when peeking, set EOF in the reader to exit the loop */ |
bf99390e ET |
1445 | reader->eof = 1; |
1446 | break; | |
1447 | ||
1448 | case '[': /* section header, new section begins */ | |
1449 | git__free(current_section); | |
1450 | current_section = NULL; | |
1451 | ||
2a950c94 ET |
1452 | if ((result = parse_section_header(reader, ¤t_section)) == 0 && on_section) { |
1453 | line_len = reader->read_ptr - line_start; | |
1454 | result = on_section(&reader, current_section, line_start, line_len, data); | |
1455 | } | |
bf99390e ET |
1456 | break; |
1457 | ||
2a950c94 | 1458 | case '\n': /* comment or whitespace-only */ |
bf99390e ET |
1459 | case ';': |
1460 | case '#': | |
bf99390e | 1461 | reader_consume_line(reader); |
2a950c94 ET |
1462 | |
1463 | if (on_comment) { | |
1464 | line_len = reader->read_ptr - line_start; | |
1465 | result = on_comment(&reader, line_start, line_len, data); | |
1466 | } | |
bf99390e ET |
1467 | break; |
1468 | ||
1469 | default: /* assume variable declaration */ | |
2a950c94 ET |
1470 | if ((result = parse_variable(reader, &var_name, &var_value)) == 0 && on_variable) { |
1471 | line_len = reader->read_ptr - line_start; | |
1472 | result = on_variable(&reader, current_section, var_name, var_value, line_start, line_len, data); | |
1473 | } | |
bf99390e ET |
1474 | break; |
1475 | } | |
1476 | } | |
1477 | ||
1478 | if (on_eof) | |
1479 | result = on_eof(&reader, data); | |
1480 | ||
1481 | git__free(current_section); | |
1482 | return result; | |
1483 | } | |
1484 | ||
1485 | struct parse_data { | |
1486 | git_strmap *values; | |
1487 | diskfile_backend *cfg_file; | |
1488 | uint32_t reader_idx; | |
1489 | git_config_level_t level; | |
1490 | int depth; | |
1491 | }; | |
1492 | ||
2a950c94 ET |
1493 | static int read_on_variable( |
1494 | struct reader **reader, | |
1495 | const char *current_section, | |
1496 | char *var_name, | |
1497 | char *var_value, | |
1498 | const char *line, | |
1499 | size_t line_len, | |
1500 | void *data) | |
bf99390e ET |
1501 | { |
1502 | struct parse_data *parse_data = (struct parse_data *)data; | |
1503 | git_buf buf = GIT_BUF_INIT; | |
1504 | cvar_t *var; | |
1505 | int result = 0; | |
1506 | ||
63c0cc65 ET |
1507 | GIT_UNUSED(line); |
1508 | GIT_UNUSED(line_len); | |
1509 | ||
bf99390e ET |
1510 | git__strtolower(var_name); |
1511 | git_buf_printf(&buf, "%s.%s", current_section, var_name); | |
1512 | git__free(var_name); | |
1513 | ||
1514 | if (git_buf_oom(&buf)) { | |
1515 | git__free(var_value); | |
1516 | return -1; | |
1517 | } | |
1518 | ||
1519 | var = git__calloc(1, sizeof(cvar_t)); | |
1520 | GITERR_CHECK_ALLOC(var); | |
1521 | var->entry = git__calloc(1, sizeof(git_config_entry)); | |
1522 | GITERR_CHECK_ALLOC(var->entry); | |
1523 | ||
1524 | var->entry->name = git_buf_detach(&buf); | |
1525 | var->entry->value = var_value; | |
1526 | var->entry->level = parse_data->level; | |
1527 | var->included = !!parse_data->depth; | |
1528 | ||
1529 | if ((result = append_entry(parse_data->values, var)) < 0) | |
1530 | return result; | |
1531 | ||
1532 | result = 0; | |
1533 | ||
1534 | /* Add or append the new config option */ | |
1535 | if (!git__strcmp(var->entry->name, "include.path")) { | |
1536 | struct reader *r; | |
1537 | git_buf path = GIT_BUF_INIT; | |
1538 | char *dir; | |
1539 | uint32_t index; | |
1540 | ||
1541 | r = git_array_alloc(parse_data->cfg_file->readers); | |
1542 | /* The reader may have been reallocated */ | |
1543 | *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx); | |
1544 | memset(r, 0, sizeof(struct reader)); | |
1545 | ||
1546 | if ((result = git_path_dirname_r(&path, (*reader)->file_path)) < 0) | |
1547 | return result; | |
1548 | ||
1549 | /* We need to know our index in the array, as the next config_parse call may realloc */ | |
1550 | index = git_array_size(parse_data->cfg_file->readers) - 1; | |
1551 | dir = git_buf_detach(&path); | |
1552 | result = included_path(&path, dir, var->entry->value); | |
1553 | git__free(dir); | |
1554 | ||
1555 | if (result < 0) | |
1556 | return result; | |
1557 | ||
1558 | r->file_path = git_buf_detach(&path); | |
1559 | git_buf_init(&r->buffer, 0); | |
1560 | ||
1561 | result = git_futils_readbuffer_updated( | |
1562 | &r->buffer, r->file_path, &r->file_mtime, &r->file_size, NULL); | |
1563 | ||
1564 | if (result == 0) { | |
1565 | result = config_read(parse_data->values, parse_data->cfg_file, r, parse_data->level, parse_data->depth+1); | |
1566 | r = git_array_get(parse_data->cfg_file->readers, index); | |
1567 | *reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx); | |
1568 | } else if (result == GIT_ENOTFOUND) { | |
1569 | giterr_clear(); | |
1570 | result = 0; | |
1571 | } | |
1572 | ||
1573 | git_buf_free(&r->buffer); | |
1574 | } | |
1575 | ||
1576 | return result; | |
1577 | } | |
1578 | ||
1579 | static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth) | |
1580 | { | |
1581 | struct parse_data parse_data; | |
1582 | ||
1583 | if (depth >= MAX_INCLUDE_DEPTH) { | |
1584 | giterr_set(GITERR_CONFIG, "Maximum config include depth reached"); | |
1585 | return -1; | |
1586 | } | |
1587 | ||
1588 | /* Initialize the reading position */ | |
1589 | reader->read_ptr = reader->buffer.ptr; | |
1590 | reader->eof = 0; | |
1591 | ||
1592 | /* If the file is empty, there's nothing for us to do */ | |
1593 | if (*reader->read_ptr == '\0') | |
1594 | return 0; | |
1595 | ||
1596 | parse_data.values = values; | |
1597 | parse_data.cfg_file = cfg_file; | |
1598 | parse_data.reader_idx = git_array_size(cfg_file->readers) - 1; | |
1599 | parse_data.level = level; | |
1600 | parse_data.depth = depth; | |
1601 | ||
2a950c94 | 1602 | return config_parse(reader, NULL, read_on_variable, NULL, NULL, &parse_data); |
bf99390e ET |
1603 | } |
1604 | ||
1605 | static int write_section(git_filebuf *file, const char *key) | |
1606 | { | |
1607 | int result; | |
1608 | const char *dot; | |
1609 | git_buf buf = GIT_BUF_INIT; | |
1610 | ||
1611 | /* All of this just for [section "subsection"] */ | |
1612 | dot = strchr(key, '.'); | |
1613 | git_buf_putc(&buf, '['); | |
1614 | if (dot == NULL) { | |
1615 | git_buf_puts(&buf, key); | |
1616 | } else { | |
1617 | char *escaped; | |
1618 | git_buf_put(&buf, key, dot - key); | |
1619 | escaped = escape_value(dot + 1); | |
1620 | GITERR_CHECK_ALLOC(escaped); | |
1621 | git_buf_printf(&buf, " \"%s\"", escaped); | |
1622 | git__free(escaped); | |
1623 | } | |
1624 | git_buf_puts(&buf, "]\n"); | |
1625 | ||
1626 | if (git_buf_oom(&buf)) | |
1627 | return -1; | |
1628 | ||
1629 | result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size); | |
1630 | git_buf_free(&buf); | |
1631 | ||
1632 | return result; | |
1633 | } | |
1634 | ||
1635 | static const char *quotes_for_value(const char *value) | |
1636 | { | |
1637 | const char *ptr; | |
1638 | ||
1639 | if (value[0] == ' ' || value[0] == '\0') | |
1640 | return "\""; | |
1641 | ||
1642 | for (ptr = value; *ptr; ++ptr) { | |
1643 | if (*ptr == ';' || *ptr == '#') | |
1644 | return "\""; | |
1645 | } | |
1646 | ||
1647 | if (ptr[-1] == ' ') | |
1648 | return "\""; | |
1649 | ||
1650 | return ""; | |
1651 | } | |
1652 | ||
1653 | struct write_data { | |
1654 | git_filebuf *file; | |
1655 | unsigned int in_section : 1, | |
1656 | preg_replaced : 1; | |
1657 | const char *section; | |
1658 | const char *name; | |
1659 | const regex_t *preg; | |
1660 | const char *value; | |
1661 | }; | |
1662 | ||
2a950c94 ET |
1663 | static int write_line(struct write_data *write_data, const char *line, size_t line_len) |
1664 | { | |
1665 | int result = git_filebuf_write(write_data->file, line, line_len); | |
1666 | ||
1667 | if (!result && line_len && line[line_len-1] != '\n') | |
1668 | result = git_filebuf_printf(write_data->file, "\n"); | |
1669 | ||
1670 | return result; | |
1671 | } | |
1672 | ||
bf99390e ET |
1673 | static int write_value(struct write_data *write_data) |
1674 | { | |
1675 | const char *q; | |
1676 | int result; | |
1677 | ||
1678 | q = quotes_for_value(write_data->value); | |
1679 | result = git_filebuf_printf(write_data->file, | |
1680 | "\t%s = %s%s%s\n", write_data->name, q, write_data->value, q); | |
1681 | ||
1682 | /* If we are updating a single name/value, we're done. Setting `value` | |
1683 | * to `NULL` will prevent us from trying to write it again later (in | |
1684 | * `write_on_section`) if we see the same section repeated. | |
1685 | */ | |
1686 | if (!write_data->preg) | |
1687 | write_data->value = NULL; | |
1688 | ||
1689 | return result; | |
1690 | } | |
1691 | ||
2a950c94 ET |
1692 | static int write_on_section( |
1693 | struct reader **reader, | |
1694 | const char *current_section, | |
1695 | const char *line, | |
1696 | size_t line_len, | |
1697 | void *data) | |
bf99390e ET |
1698 | { |
1699 | struct write_data *write_data = (struct write_data *)data; | |
1700 | int result = 0; | |
1701 | ||
63c0cc65 ET |
1702 | GIT_UNUSED(reader); |
1703 | ||
bf99390e ET |
1704 | /* If we were previously in the correct section (but aren't anymore) |
1705 | * and haven't written our value (for a simple name/value set, not | |
1706 | * a multivar), then append it to the end of the section before writing | |
1707 | * the new one. | |
1708 | */ | |
1709 | if (write_data->in_section && !write_data->preg && write_data->value) | |
1710 | result = write_value(write_data); | |
1711 | ||
1712 | write_data->in_section = strcmp(current_section, write_data->section) == 0; | |
1713 | ||
bf99390e | 1714 | if (!result) |
2a950c94 | 1715 | result = write_line(write_data, line, line_len); |
bf99390e ET |
1716 | |
1717 | return result; | |
1718 | } | |
1719 | ||
2a950c94 ET |
1720 | static int write_on_variable( |
1721 | struct reader **reader, | |
1722 | const char *current_section, | |
1723 | char *var_name, | |
1724 | char *var_value, | |
1725 | const char *line, | |
1726 | size_t line_len, | |
1727 | void *data) | |
bf99390e ET |
1728 | { |
1729 | struct write_data *write_data = (struct write_data *)data; | |
1730 | bool has_matched = false; | |
63c0cc65 ET |
1731 | |
1732 | GIT_UNUSED(reader); | |
1733 | GIT_UNUSED(current_section); | |
bf99390e ET |
1734 | |
1735 | /* See if we are to update this name/value pair; first examine name */ | |
1736 | if (write_data->in_section && | |
1737 | strcasecmp(write_data->name, var_name) == 0) | |
1738 | has_matched = true; | |
1739 | ||
1740 | /* If we have a regex to match the value, see if it matches */ | |
1741 | if (has_matched && write_data->preg != NULL) | |
1742 | has_matched = (regexec(write_data->preg, var_value, 0, NULL, 0) == 0); | |
1743 | ||
2a950c94 ET |
1744 | git__free(var_name); |
1745 | git__free(var_value); | |
bf99390e ET |
1746 | |
1747 | /* If this isn't the name/value we're looking for, simply dump the | |
1748 | * existing data back out and continue on. | |
1749 | */ | |
2a950c94 ET |
1750 | if (!has_matched) |
1751 | return write_line(write_data, line, line_len); | |
bf99390e ET |
1752 | |
1753 | write_data->preg_replaced = 1; | |
1754 | ||
1755 | /* If value is NULL, we are deleting this value; write nothing. */ | |
1756 | if (!write_data->value) | |
1757 | return 0; | |
1758 | ||
1759 | return write_value(write_data); | |
1760 | } | |
1761 | ||
2a950c94 ET |
1762 | static int write_on_comment(struct reader **reader, const char *line, size_t line_len, void *data) |
1763 | { | |
63c0cc65 ET |
1764 | struct write_data *write_data; |
1765 | ||
1766 | GIT_UNUSED(reader); | |
1767 | ||
1768 | write_data = (struct write_data *)data; | |
2a950c94 ET |
1769 | return write_line(write_data, line, line_len); |
1770 | } | |
1771 | ||
bf99390e ET |
1772 | static int write_on_eof(struct reader **reader, void *data) |
1773 | { | |
1774 | struct write_data *write_data = (struct write_data *)data; | |
1775 | int result = 0; | |
1776 | ||
63c0cc65 ET |
1777 | GIT_UNUSED(reader); |
1778 | ||
bf99390e ET |
1779 | /* If we are at the EOF and have not written our value (again, for a |
1780 | * simple name/value set, not a multivar) then we have never seen the | |
1781 | * section in question and should create a new section and write the | |
1782 | * value. | |
1783 | */ | |
1784 | if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) { | |
1785 | if ((result = write_section(write_data->file, write_data->section)) == 0) | |
1786 | result = write_value(write_data); | |
1787 | } | |
1788 | ||
1789 | return result; | |
1790 | } | |
1791 | ||
1792 | /* | |
1793 | * This is pretty much the parsing, except we write out anything we don't have | |
1794 | */ | |
1795 | static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value) | |
1796 | { | |
1797 | int result; | |
63c0cc65 | 1798 | char *section, *name, *ldot; |
bf99390e ET |
1799 | git_filebuf file = GIT_FILEBUF_INIT; |
1800 | struct reader *reader = git_array_get(cfg->readers, 0); | |
1801 | struct write_data write_data; | |
1802 | ||
9c26de0f ET |
1803 | /* Lock the file */ |
1804 | if ((result = git_filebuf_open( | |
1805 | &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) { | |
1806 | git_buf_free(&reader->buffer); | |
1807 | return result; | |
1808 | } | |
bf99390e ET |
1809 | |
1810 | /* We need to read in our own config file */ | |
1811 | result = git_futils_readbuffer(&reader->buffer, cfg->file_path); | |
1812 | ||
1813 | /* Initialise the reading position */ | |
1814 | if (result == GIT_ENOTFOUND) { | |
1815 | reader->read_ptr = NULL; | |
1816 | reader->eof = 1; | |
bf99390e ET |
1817 | git_buf_clear(&reader->buffer); |
1818 | } else if (result == 0) { | |
1819 | reader->read_ptr = reader->buffer.ptr; | |
1820 | reader->eof = 0; | |
bf99390e | 1821 | } else { |
9c26de0f | 1822 | git_filebuf_cleanup(&file); |
bf99390e ET |
1823 | return -1; /* OS error when reading the file */ |
1824 | } | |
1825 | ||
bf99390e ET |
1826 | ldot = strrchr(key, '.'); |
1827 | name = ldot + 1; | |
1828 | section = git__strndup(key, ldot - key); | |
1829 | ||
1830 | write_data.file = &file; | |
1831 | write_data.section = section; | |
1832 | write_data.in_section = 0; | |
1833 | write_data.preg_replaced = 0; | |
1834 | write_data.name = name; | |
1835 | write_data.preg = preg; | |
1836 | write_data.value = value; | |
1837 | ||
b162d97a CMN |
1838 | result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data); |
1839 | git__free(section); | |
1840 | ||
1841 | if (result < 0) { | |
bf99390e ET |
1842 | git_filebuf_cleanup(&file); |
1843 | goto done; | |
1844 | } | |
1845 | ||
1846 | /* refresh stats - if this errors, then commit will error too */ | |
1847 | (void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file); | |
1848 | ||
1849 | result = git_filebuf_commit(&file); | |
1850 | git_buf_free(&reader->buffer); | |
1851 | ||
1852 | done: | |
1853 | git_buf_free(&reader->buffer); | |
1854 | return result; | |
1855 | } | |
1856 |