File Coverage

deps/libgit2/src/libgit2/config_file.c
Criterion Covered Total %
statement 349 554 63.0
branch 148 340 43.5
condition n/a
subroutine n/a
pod n/a
total 497 894 55.5


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "config.h"
9              
10             #include "git2/config.h"
11             #include "git2/sys/config.h"
12              
13             #include "array.h"
14             #include "str.h"
15             #include "config_backend.h"
16             #include "config_entries.h"
17             #include "config_parse.h"
18             #include "filebuf.h"
19             #include "regexp.h"
20             #include "sysdir.h"
21             #include "wildmatch.h"
22             #include "hash.h"
23              
24             /* Max depth for [include] directives */
25             #define MAX_INCLUDE_DEPTH 10
26              
27             typedef struct config_file {
28             git_futils_filestamp stamp;
29             unsigned char checksum[GIT_HASH_SHA1_SIZE];
30             char *path;
31             git_array_t(struct config_file) includes;
32             } config_file;
33              
34             typedef struct {
35             git_config_backend parent;
36             git_mutex values_mutex;
37             git_config_entries *entries;
38             const git_repository *repo;
39             git_config_level_t level;
40              
41             git_array_t(git_config_parser) readers;
42              
43             bool locked;
44             git_filebuf locked_buf;
45             git_str locked_content;
46              
47             config_file file;
48             } config_file_backend;
49              
50             typedef struct {
51             const git_repository *repo;
52             config_file *file;
53             git_config_entries *entries;
54             git_config_level_t level;
55             unsigned int depth;
56             } config_file_parse_data;
57              
58             static int config_file_read(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth);
59             static int config_file_read_buffer(git_config_entries *entries, const git_repository *repo, config_file *file, git_config_level_t level, int depth, const char *buf, size_t buflen);
60             static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value);
61             static char *escape_value(const char *ptr);
62              
63             /**
64             * Take the current values map from the backend and increase its
65             * refcount. This is its own function to make sure we use the mutex to
66             * avoid the map pointer from changing under us.
67             */
68 1980           static int config_file_entries_take(git_config_entries **out, config_file_backend *b)
69             {
70             int error;
71              
72 1980 50         if ((error = git_mutex_lock(&b->values_mutex)) < 0) {
73 0           git_error_set(GIT_ERROR_OS, "failed to lock config backend");
74 0           return error;
75             }
76              
77 1980           git_config_entries_incref(b->entries);
78 1980           *out = b->entries;
79              
80 1980           git_mutex_unlock(&b->values_mutex);
81              
82 1980           return 0;
83             }
84              
85 140           static void config_file_clear(config_file *file)
86             {
87             config_file *include;
88             uint32_t i;
89              
90 140 50         if (file == NULL)
91 0           return;
92              
93 140 50         git_array_foreach(file->includes, i, include) {
    0          
94 0           config_file_clear(include);
95             }
96 140           git_array_clear(file->includes);
97              
98 140           git__free(file->path);
99             }
100              
101 140           static int config_file_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
102             {
103 140           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
104             int res;
105              
106 140           b->level = level;
107 140           b->repo = repo;
108              
109 140 50         if ((res = git_config_entries_new(&b->entries)) < 0)
110 0           return res;
111              
112 140 100         if (!git_fs_path_exists(b->file.path))
113 3           return 0;
114              
115             /*
116             * git silently ignores configuration files that are not
117             * readable. We emulate that behavior. This is particularly
118             * important for sandboxed applications on macOS where the
119             * git configuration files may not be readable.
120             */
121 137 50         if (p_access(b->file.path, R_OK) < 0)
122 0           return GIT_ENOTFOUND;
123              
124 137 50         if (res < 0 || (res = config_file_read(b->entries, repo, &b->file, level, 0)) < 0) {
    50          
125 0           git_config_entries_free(b->entries);
126 0           b->entries = NULL;
127             }
128              
129 137           return res;
130             }
131              
132 1924           static int config_file_is_modified(int *modified, config_file *file)
133             {
134             config_file *include;
135 1924           git_str buf = GIT_STR_INIT;
136             unsigned char checksum[GIT_HASH_SHA1_SIZE];
137             uint32_t i;
138 1924           int error = 0;
139              
140 1924           *modified = 0;
141              
142 1924 100         if (!git_futils_filestamp_check(&file->stamp, file->path))
143 1904           goto check_includes;
144              
145 20 100         if ((error = git_futils_readbuffer(&buf, file->path)) < 0)
146 1           goto out;
147              
148 19 50         if ((error = git_hash_buf(checksum, buf.ptr, buf.size, GIT_HASH_ALGORITHM_SHA1)) < 0)
149 0           goto out;
150              
151 19 50         if (memcmp(checksum, file->checksum, GIT_HASH_SHA1_SIZE) != 0) {
152 19           *modified = 1;
153 19           goto out;
154             }
155              
156             check_includes:
157 1904 50         git_array_foreach(file->includes, i, include) {
    0          
158 0 0         if ((error = config_file_is_modified(modified, include)) < 0 || *modified)
    0          
159             goto out;
160             }
161              
162             out:
163 1924           git_str_dispose(&buf);
164              
165 1924           return error;
166             }
167              
168 69           static void config_file_clear_includes(config_file_backend *cfg)
169             {
170             config_file *include;
171             uint32_t i;
172              
173 69 50         git_array_foreach(cfg->file.includes, i, include)
    0          
174 0           config_file_clear(include);
175 69           git_array_clear(cfg->file.includes);
176 69           }
177              
178 69           static int config_file_set_entries(git_config_backend *cfg, git_config_entries *entries)
179             {
180 69           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
181 69           git_config_entries *old = NULL;
182             int error;
183              
184 69 50         if (b->parent.readonly) {
185 0           git_error_set(GIT_ERROR_CONFIG, "this backend is read-only");
186 0           return -1;
187             }
188              
189 69 50         if ((error = git_mutex_lock(&b->values_mutex)) < 0) {
190 0           git_error_set(GIT_ERROR_OS, "failed to lock config backend");
191 0           goto out;
192             }
193              
194 69           old = b->entries;
195 69           b->entries = entries;
196              
197 69           git_mutex_unlock(&b->values_mutex);
198              
199             out:
200 69           git_config_entries_free(old);
201 69           return error;
202             }
203              
204 50           static int config_file_refresh_from_buffer(git_config_backend *cfg, const char *buf, size_t buflen)
205             {
206 50           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
207 50           git_config_entries *entries = NULL;
208             int error;
209              
210 50           config_file_clear_includes(b);
211              
212 50 50         if ((error = git_config_entries_new(&entries)) < 0 ||
    50          
213 50           (error = config_file_read_buffer(entries, b->repo, &b->file,
214 50 50         b->level, 0, buf, buflen)) < 0 ||
215 50           (error = config_file_set_entries(cfg, entries)) < 0)
216             goto out;
217              
218 50           entries = NULL;
219             out:
220 50           git_config_entries_free(entries);
221 50           return error;
222             }
223              
224 1924           static int config_file_refresh(git_config_backend *cfg)
225             {
226 1924           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
227 1924           git_config_entries *entries = NULL;
228             int error, modified;
229              
230 1924 50         if (cfg->readonly)
231 0           return 0;
232              
233 1924 100         if ((error = config_file_is_modified(&modified, &b->file)) < 0 && error != GIT_ENOTFOUND)
    50          
234 0           goto out;
235              
236 1924 100         if (!modified)
237 1905           return 0;
238              
239 19           config_file_clear_includes(b);
240              
241 19 50         if ((error = git_config_entries_new(&entries)) < 0 ||
    50          
242 19 50         (error = config_file_read(entries, b->repo, &b->file, b->level, 0)) < 0 ||
243 19           (error = config_file_set_entries(cfg, entries)) < 0)
244             goto out;
245              
246 19           entries = NULL;
247             out:
248 19           git_config_entries_free(entries);
249              
250 1924 50         return (error == GIT_ENOTFOUND) ? 0 : error;
251             }
252              
253 140           static void config_file_free(git_config_backend *_backend)
254             {
255 140           config_file_backend *backend = GIT_CONTAINER_OF(_backend, config_file_backend, parent);
256              
257 140 50         if (backend == NULL)
258 0           return;
259              
260 140           config_file_clear(&backend->file);
261 140           git_config_entries_free(backend->entries);
262 140           git_mutex_free(&backend->values_mutex);
263 140           git__free(backend);
264             }
265              
266 1031           static int config_file_iterator(
267             git_config_iterator **iter,
268             struct git_config_backend *backend)
269             {
270 1031           config_file_backend *b = GIT_CONTAINER_OF(backend, config_file_backend, parent);
271 1031           git_config_entries *dupped = NULL, *entries = NULL;
272             int error;
273              
274 1031 50         if ((error = config_file_refresh(backend)) < 0 ||
    50          
275 1031 50         (error = config_file_entries_take(&entries, b)) < 0 ||
276 1031           (error = git_config_entries_dup(&dupped, entries)) < 0 ||
277 1031           (error = git_config_entries_iterator_new(iter, dupped)) < 0)
278             goto out;
279              
280             out:
281             /* Let iterator delete duplicated entries when it's done */
282 1031           git_config_entries_free(entries);
283 1031           git_config_entries_free(dupped);
284 1031           return error;
285             }
286              
287 1004           static int config_file_snapshot(git_config_backend **out, git_config_backend *backend)
288             {
289 1004           return git_config_backend_snapshot(out, backend);
290             }
291              
292 41           static int config_file_set(git_config_backend *cfg, const char *name, const char *value)
293             {
294 41           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
295             git_config_entries *entries;
296             git_config_entry *existing;
297 41           char *key, *esc_value = NULL;
298             int error;
299              
300 41 50         if ((error = git_config__normalize_name(name, &key)) < 0)
301 0           return error;
302              
303 41 50         if ((error = config_file_entries_take(&entries, b)) < 0)
304 0           return error;
305              
306             /* Check whether we'd be modifying an included or multivar key */
307 41 100         if ((error = git_config_entries_get_unique(&existing, entries, key)) < 0) {
308 37 50         if (error != GIT_ENOTFOUND)
309 0           goto out;
310 37           error = 0;
311 4 50         } else if ((!existing->value && !value) ||
    0          
    50          
312 4 50         (existing->value && value && !strcmp(existing->value, value))) {
    100          
313             /* don't update if old and new values already match */
314 1           error = 0;
315 1           goto out;
316             }
317              
318             /* No early returns due to sanity checks, let's write it out and refresh */
319 40 50         if (value) {
320 40           esc_value = escape_value(value);
321 40 50         GIT_ERROR_CHECK_ALLOC(esc_value);
322             }
323              
324 40 50         if ((error = config_file_write(b, name, key, NULL, esc_value)) < 0)
325 0           goto out;
326              
327             out:
328 41           git_config_entries_free(entries);
329 41           git__free(esc_value);
330 41           git__free(key);
331 41           return error;
332             }
333              
334             /* release the map containing the entry as an equivalent to freeing it */
335 76           static void config_file_entry_free(git_config_entry *entry)
336             {
337 76           git_config_entries *entries = (git_config_entries *) entry->payload;
338 76           git_config_entries_free(entries);
339 76           }
340              
341             /*
342             * Internal function that actually gets the value in string form
343             */
344 893           static int config_file_get(git_config_backend *cfg, const char *key, git_config_entry **out)
345             {
346 893           config_file_backend *h = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
347 893           git_config_entries *entries = NULL;
348             git_config_entry *entry;
349 893           int error = 0;
350              
351 893 50         if (!h->parent.readonly && ((error = config_file_refresh(cfg)) < 0))
    50          
352 0           return error;
353              
354 893 50         if ((error = config_file_entries_take(&entries, h)) < 0)
355 0           return error;
356              
357 893 100         if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
358 817           git_config_entries_free(entries);
359 817           return error;
360             }
361              
362 76           entry->free = config_file_entry_free;
363 76           entry->payload = entries;
364 76           *out = entry;
365              
366 893           return 0;
367             }
368              
369 5           static int config_file_set_multivar(
370             git_config_backend *cfg, const char *name, const char *regexp, const char *value)
371             {
372 5           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
373             git_regexp preg;
374             int result;
375             char *key;
376              
377 5 50         GIT_ASSERT_ARG(regexp);
378              
379 5 50         if ((result = git_config__normalize_name(name, &key)) < 0)
380 0           return result;
381              
382 5 50         if ((result = git_regexp_compile(&preg, regexp, 0)) < 0)
383 0           goto out;
384              
385             /* If we do have it, set call config_file_write() and reload */
386 5 50         if ((result = config_file_write(b, name, key, &preg, value)) < 0)
387 0           goto out;
388              
389             out:
390 5           git__free(key);
391 5           git_regexp_dispose(&preg);
392              
393 5           return result;
394             }
395              
396 15           static int config_file_delete(git_config_backend *cfg, const char *name)
397             {
398 15           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
399 15           git_config_entries *entries = NULL;
400             git_config_entry *entry;
401 15           char *key = NULL;
402             int error;
403              
404 15 50         if ((error = git_config__normalize_name(name, &key)) < 0)
405 0           goto out;
406              
407 15 50         if ((error = config_file_entries_take(&entries, b)) < 0)
408 0           goto out;
409              
410             /* Check whether we'd be modifying an included or multivar key */
411 15 100         if ((error = git_config_entries_get_unique(&entry, entries, key)) < 0) {
412 10 50         if (error == GIT_ENOTFOUND)
413 10           git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
414 10           goto out;
415             }
416              
417 5 50         if ((error = config_file_write(b, name, entry->name, NULL, NULL)) < 0)
418 0           goto out;
419              
420             out:
421 15           git_config_entries_free(entries);
422 15           git__free(key);
423 15           return error;
424             }
425              
426 0           static int config_file_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
427             {
428 0           config_file_backend *b = GIT_CONTAINER_OF(cfg, config_file_backend, parent);
429 0           git_config_entries *entries = NULL;
430 0           git_config_entry *entry = NULL;
431 0           git_regexp preg = GIT_REGEX_INIT;
432 0           char *key = NULL;
433             int result;
434              
435 0 0         if ((result = git_config__normalize_name(name, &key)) < 0)
436 0           goto out;
437              
438 0 0         if ((result = config_file_entries_take(&entries, b)) < 0)
439 0           goto out;
440              
441 0 0         if ((result = git_config_entries_get(&entry, entries, key)) < 0) {
442 0 0         if (result == GIT_ENOTFOUND)
443 0           git_error_set(GIT_ERROR_CONFIG, "could not find key '%s' to delete", name);
444 0           goto out;
445             }
446              
447 0 0         if ((result = git_regexp_compile(&preg, regexp, 0)) < 0)
448 0           goto out;
449              
450 0 0         if ((result = config_file_write(b, name, key, &preg, NULL)) < 0)
451 0           goto out;
452              
453             out:
454 0           git_config_entries_free(entries);
455 0           git__free(key);
456 0           git_regexp_dispose(&preg);
457 0           return result;
458             }
459              
460 0           static int config_file_lock(git_config_backend *_cfg)
461             {
462 0           config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent);
463             int error;
464              
465 0 0         if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0)
466 0           return error;
467              
468 0           error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path);
469 0 0         if (error < 0 && error != GIT_ENOTFOUND) {
    0          
470 0           git_filebuf_cleanup(&cfg->locked_buf);
471 0           return error;
472             }
473              
474 0           cfg->locked = true;
475 0           return 0;
476              
477             }
478              
479 0           static int config_file_unlock(git_config_backend *_cfg, int success)
480             {
481 0           config_file_backend *cfg = GIT_CONTAINER_OF(_cfg, config_file_backend, parent);
482 0           int error = 0;
483              
484 0 0         if (success) {
485 0           git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size);
486 0           error = git_filebuf_commit(&cfg->locked_buf);
487             }
488              
489 0           git_filebuf_cleanup(&cfg->locked_buf);
490 0           git_str_dispose(&cfg->locked_content);
491 0           cfg->locked = false;
492              
493 0           return error;
494             }
495              
496 140           int git_config_backend_from_file(git_config_backend **out, const char *path)
497             {
498             config_file_backend *backend;
499              
500 140           backend = git__calloc(1, sizeof(config_file_backend));
501 140 50         GIT_ERROR_CHECK_ALLOC(backend);
502              
503 140           backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
504 140           git_mutex_init(&backend->values_mutex);
505              
506 140           backend->file.path = git__strdup(path);
507 140 50         GIT_ERROR_CHECK_ALLOC(backend->file.path);
508 140           git_array_init(backend->file.includes);
509              
510 140           backend->parent.open = config_file_open;
511 140           backend->parent.get = config_file_get;
512 140           backend->parent.set = config_file_set;
513 140           backend->parent.set_multivar = config_file_set_multivar;
514 140           backend->parent.del = config_file_delete;
515 140           backend->parent.del_multivar = config_file_delete_multivar;
516 140           backend->parent.iterator = config_file_iterator;
517 140           backend->parent.snapshot = config_file_snapshot;
518 140           backend->parent.lock = config_file_lock;
519 140           backend->parent.unlock = config_file_unlock;
520 140           backend->parent.free = config_file_free;
521              
522 140           *out = (git_config_backend *)backend;
523              
524 140           return 0;
525             }
526              
527 0           static int included_path(git_str *out, const char *dir, const char *path)
528             {
529             /* From the user's home */
530 0 0         if (path[0] == '~' && path[1] == '/')
    0          
531 0           return git_sysdir_expand_global_file(out, &path[1]);
532              
533 0           return git_fs_path_join_unrooted(out, path, dir, NULL);
534             }
535              
536             /* Escape the values to write them to the file */
537 44           static char *escape_value(const char *ptr)
538             {
539             git_str buf;
540             size_t len;
541             const char *esc;
542              
543 44 50         GIT_ASSERT_ARG_WITH_RETVAL(ptr, NULL);
544              
545 44           len = strlen(ptr);
546 44 50         if (!len)
547 0           return git__calloc(1, sizeof(char));
548              
549 44 50         if (git_str_init(&buf, len) < 0)
550 0           return NULL;
551              
552 600 100         while (*ptr != '\0') {
553 556 50         if ((esc = strchr(git_config_escaped, *ptr)) != NULL) {
554 0           git_str_putc(&buf, '\\');
555 0           git_str_putc(&buf, git_config_escapes[esc - git_config_escaped]);
556             } else {
557 556           git_str_putc(&buf, *ptr);
558             }
559 556           ptr++;
560             }
561              
562 44 50         if (git_str_oom(&buf))
563 0           return NULL;
564              
565 44           return git_str_detach(&buf);
566             }
567              
568 0           static int parse_include(config_file_parse_data *parse_data, const char *file)
569             {
570             config_file *include;
571 0           git_str path = GIT_STR_INIT;
572             char *dir;
573             int result;
574              
575 0 0         if (!file)
576 0           return 0;
577              
578 0 0         if ((result = git_fs_path_dirname_r(&path, parse_data->file->path)) < 0)
579 0           return result;
580              
581 0           dir = git_str_detach(&path);
582 0           result = included_path(&path, dir, file);
583 0           git__free(dir);
584              
585 0 0         if (result < 0)
586 0           return result;
587              
588 0 0         include = git_array_alloc(parse_data->file->includes);
    0          
589 0 0         GIT_ERROR_CHECK_ALLOC(include);
590 0           memset(include, 0, sizeof(*include));
591 0           git_array_init(include->includes);
592 0           include->path = git_str_detach(&path);
593              
594 0           result = config_file_read(parse_data->entries, parse_data->repo, include,
595 0           parse_data->level, parse_data->depth+1);
596              
597 0 0         if (result == GIT_ENOTFOUND) {
598 0           git_error_clear();
599 0           result = 0;
600             }
601              
602 0           return result;
603             }
604              
605 0           static int do_match_gitdir(
606             int *matches,
607             const git_repository *repo,
608             const char *cfg_file,
609             const char *condition,
610             bool case_insensitive)
611             {
612 0           git_str pattern = GIT_STR_INIT, gitdir = GIT_STR_INIT;
613             int error;
614              
615 0 0         if (condition[0] == '.' && git_fs_path_is_dirsep(condition[1])) {
    0          
616 0           git_fs_path_dirname_r(&pattern, cfg_file);
617 0           git_str_joinpath(&pattern, pattern.ptr, condition + 2);
618 0 0         } else if (condition[0] == '~' && git_fs_path_is_dirsep(condition[1]))
    0          
619 0           git_sysdir_expand_global_file(&pattern, condition + 1);
620 0 0         else if (!git_fs_path_is_absolute(condition))
621 0           git_str_joinpath(&pattern, "**", condition);
622             else
623 0           git_str_sets(&pattern, condition);
624              
625 0 0         if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]))
626 0           git_str_puts(&pattern, "**");
627              
628 0 0         if (git_str_oom(&pattern)) {
629 0           error = -1;
630 0           goto out;
631             }
632              
633 0 0         if ((error = git_repository__item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0)
634 0           goto out;
635              
636 0 0         if (git_fs_path_is_dirsep(gitdir.ptr[gitdir.size - 1]))
637 0           git_str_truncate(&gitdir, gitdir.size - 1);
638              
639 0           *matches = wildmatch(pattern.ptr, gitdir.ptr,
640 0           WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH;
641             out:
642 0           git_str_dispose(&pattern);
643 0           git_str_dispose(&gitdir);
644 0           return error;
645             }
646              
647 0           static int conditional_match_gitdir(
648             int *matches,
649             const git_repository *repo,
650             const char *cfg_file,
651             const char *value)
652             {
653 0           return do_match_gitdir(matches, repo, cfg_file, value, false);
654             }
655              
656 0           static int conditional_match_gitdir_i(
657             int *matches,
658             const git_repository *repo,
659             const char *cfg_file,
660             const char *value)
661             {
662 0           return do_match_gitdir(matches, repo, cfg_file, value, true);
663             }
664              
665 0           static int conditional_match_onbranch(
666             int *matches,
667             const git_repository *repo,
668             const char *cfg_file,
669             const char *condition)
670             {
671 0           git_str reference = GIT_STR_INIT, buf = GIT_STR_INIT;
672             int error;
673              
674 0           GIT_UNUSED(cfg_file);
675              
676             /*
677             * NOTE: you cannot use `git_repository_head` here. Looking up the
678             * HEAD reference will create the ODB, which causes us to read the
679             * repo's config for keys like core.precomposeUnicode. As we're
680             * just parsing the config right now, though, this would result in
681             * an endless recursion.
682             */
683              
684 0 0         if ((error = git_str_joinpath(&buf, git_repository_path(repo), GIT_HEAD_FILE)) < 0 ||
    0          
685 0           (error = git_futils_readbuffer(&reference, buf.ptr)) < 0)
686             goto out;
687 0           git_str_rtrim(&reference);
688              
689 0 0         if (git__strncmp(reference.ptr, GIT_SYMREF, strlen(GIT_SYMREF)))
690 0           goto out;
691 0           git_str_consume(&reference, reference.ptr + strlen(GIT_SYMREF));
692              
693 0 0         if (git__strncmp(reference.ptr, GIT_REFS_HEADS_DIR, strlen(GIT_REFS_HEADS_DIR)))
694 0           goto out;
695 0           git_str_consume(&reference, reference.ptr + strlen(GIT_REFS_HEADS_DIR));
696              
697             /*
698             * If the condition ends with a '/', then we should treat it as if
699             * it had '**' appended.
700             */
701 0 0         if ((error = git_str_sets(&buf, condition)) < 0)
702 0           goto out;
703 0 0         if (git_fs_path_is_dirsep(condition[strlen(condition) - 1]) &&
    0          
704             (error = git_str_puts(&buf, "**")) < 0)
705 0           goto out;
706              
707 0           *matches = wildmatch(buf.ptr, reference.ptr, WM_PATHNAME) == WM_MATCH;
708             out:
709 0           git_str_dispose(&reference);
710 0           git_str_dispose(&buf);
711              
712 0           return error;
713              
714             }
715              
716             static const struct {
717             const char *prefix;
718             int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value);
719             } conditions[] = {
720             { "gitdir:", conditional_match_gitdir },
721             { "gitdir/i:", conditional_match_gitdir_i },
722             { "onbranch:", conditional_match_onbranch }
723             };
724              
725 0           static int parse_conditional_include(config_file_parse_data *parse_data, const char *section, const char *file)
726             {
727             char *condition;
728             size_t section_len, i;
729 0           int error = 0, matches;
730              
731 0 0         if (!parse_data->repo || !file)
    0          
732 0           return 0;
733              
734 0           section_len = strlen(section);
735              
736             /*
737             * We checked that the string starts with `includeIf.` and ends
738             * in `.path` to get here. Make sure it consists of more.
739             */
740 0 0         if (section_len < CONST_STRLEN("includeIf.") + CONST_STRLEN(".path"))
741 0           return 0;
742              
743 0           condition = git__substrdup(section + CONST_STRLEN("includeIf."),
744             section_len - CONST_STRLEN("includeIf.") - CONST_STRLEN(".path"));
745              
746 0 0         GIT_ERROR_CHECK_ALLOC(condition);
747              
748 0 0         for (i = 0; i < ARRAY_SIZE(conditions); i++) {
749 0 0         if (git__prefixcmp(condition, conditions[i].prefix))
750 0           continue;
751              
752 0 0         if ((error = conditions[i].matches(&matches,
753             parse_data->repo,
754 0           parse_data->file->path,
755 0           condition + strlen(conditions[i].prefix))) < 0)
756 0           break;
757              
758 0 0         if (matches)
759 0           error = parse_include(parse_data, file);
760              
761 0           break;
762             }
763              
764 0           git__free(condition);
765 0           return error;
766             }
767              
768 1561           static int read_on_variable(
769             git_config_parser *reader,
770             const char *current_section,
771             const char *var_name,
772             const char *var_value,
773             const char *line,
774             size_t line_len,
775             void *data)
776             {
777 1561           config_file_parse_data *parse_data = (config_file_parse_data *)data;
778 1561           git_str buf = GIT_STR_INIT;
779             git_config_entry *entry;
780             const char *c;
781 1561           int result = 0;
782              
783 1561           GIT_UNUSED(reader);
784 1561           GIT_UNUSED(line);
785 1561           GIT_UNUSED(line_len);
786              
787 1561 50         if (current_section) {
788             /* TODO: Once warnings lang, we should likely warn
789             * here. Git appears to warn in most cases if it sees
790             * un-namespaced config options.
791             */
792 1561           git_str_puts(&buf, current_section);
793 1561           git_str_putc(&buf, '.');
794             }
795              
796 13340 100         for (c = var_name; *c; c++)
797 11779           git_str_putc(&buf, git__tolower(*c));
798              
799 1561 50         if (git_str_oom(&buf))
800 0           return -1;
801              
802 1561           entry = git__calloc(1, sizeof(git_config_entry));
803 1561 50         GIT_ERROR_CHECK_ALLOC(entry);
804 1561           entry->name = git_str_detach(&buf);
805 1561 50         entry->value = var_value ? git__strdup(var_value) : NULL;
806 1561           entry->level = parse_data->level;
807 1561           entry->include_depth = parse_data->depth;
808              
809 1561 50         if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
810 0           return result;
811              
812 1561           result = 0;
813              
814             /* Add or append the new config option */
815 1561 50         if (!git__strcmp(entry->name, "include.path"))
816 0           result = parse_include(parse_data, entry->value);
817 1561           else if (!git__prefixcmp(entry->name, "includeif.") &&
818 0           !git__suffixcmp(entry->name, ".path"))
819 0           result = parse_conditional_include(parse_data, entry->name, entry->value);
820              
821 1561           return result;
822             }
823              
824 206           static int config_file_read_buffer(
825             git_config_entries *entries,
826             const git_repository *repo,
827             config_file *file,
828             git_config_level_t level,
829             int depth,
830             const char *buf,
831             size_t buflen)
832             {
833             config_file_parse_data parse_data;
834             git_config_parser reader;
835             int error;
836              
837 206 50         if (depth >= MAX_INCLUDE_DEPTH) {
838 0           git_error_set(GIT_ERROR_CONFIG, "maximum config include depth reached");
839 0           return -1;
840             }
841              
842             /* Initialize the reading position */
843 206           reader.path = file->path;
844 206           git_parse_ctx_init(&reader.ctx, buf, buflen);
845              
846             /* If the file is empty, there's nothing for us to do */
847 206 50         if (!reader.ctx.content || *reader.ctx.content == '\0') {
    100          
848 5           error = 0;
849 5           goto out;
850             }
851              
852 201           parse_data.repo = repo;
853 201           parse_data.file = file;
854 201           parse_data.entries = entries;
855 201           parse_data.level = level;
856 201           parse_data.depth = depth;
857              
858 201           error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data);
859              
860             out:
861 206           return error;
862             }
863              
864 156           static int config_file_read(
865             git_config_entries *entries,
866             const git_repository *repo,
867             config_file *file,
868             git_config_level_t level,
869             int depth)
870             {
871 156           git_str contents = GIT_STR_INIT;
872             struct stat st;
873             int error;
874              
875 156 50         if (p_stat(file->path, &st) < 0) {
876 0           error = git_fs_path_set_error(errno, file->path, "stat");
877 0           goto out;
878             }
879              
880 156 50         if ((error = git_futils_readbuffer(&contents, file->path)) < 0)
881 0           goto out;
882              
883 156           git_futils_filestamp_set_from_stat(&file->stamp, &st);
884 156 50         if ((error = git_hash_buf(file->checksum, contents.ptr, contents.size, GIT_HASH_ALGORITHM_SHA1)) < 0)
885 0           goto out;
886              
887 156 50         if ((error = config_file_read_buffer(entries, repo, file, level, depth,
888 156           contents.ptr, contents.size)) < 0)
889 0           goto out;
890              
891             out:
892 156           git_str_dispose(&contents);
893 156           return error;
894             }
895              
896 17           static int write_section(git_str *fbuf, const char *key)
897             {
898             int result;
899             const char *dot;
900 17           git_str buf = GIT_STR_INIT;
901              
902             /* All of this just for [section "subsection"] */
903 17           dot = strchr(key, '.');
904 17           git_str_putc(&buf, '[');
905 17 100         if (dot == NULL) {
906 13           git_str_puts(&buf, key);
907             } else {
908             char *escaped;
909 4           git_str_put(&buf, key, dot - key);
910 4           escaped = escape_value(dot + 1);
911 4 50         GIT_ERROR_CHECK_ALLOC(escaped);
912 4           git_str_printf(&buf, " \"%s\"", escaped);
913 4           git__free(escaped);
914             }
915 17           git_str_puts(&buf, "]\n");
916              
917 17 50         if (git_str_oom(&buf))
918 0           return -1;
919              
920 17           result = git_str_put(fbuf, git_str_cstr(&buf), buf.size);
921 17           git_str_dispose(&buf);
922              
923 17           return result;
924             }
925              
926 45           static const char *quotes_for_value(const char *value)
927             {
928             const char *ptr;
929              
930 45 50         if (value[0] == ' ' || value[0] == '\0')
    50          
931 0           return "\"";
932              
933 683 100         for (ptr = value; *ptr; ++ptr) {
934 638 50         if (*ptr == ';' || *ptr == '#')
    50          
935 0           return "\"";
936             }
937              
938 45 50         if (ptr[-1] == ' ')
939 0           return "\"";
940              
941 45           return "";
942             }
943              
944             struct write_data {
945             git_str *buf;
946             git_str buffered_comment;
947             unsigned int in_section : 1,
948             preg_replaced : 1;
949             const char *orig_section;
950             const char *section;
951             const char *orig_name;
952             const char *name;
953             const git_regexp *preg;
954             const char *value;
955             };
956              
957 571           static int write_line_to(git_str *buf, const char *line, size_t line_len)
958             {
959 571           int result = git_str_put(buf, line, line_len);
960              
961 571 50         if (!result && line_len && line[line_len-1] != '\n')
    50          
    50          
962 0           result = git_str_printf(buf, "\n");
963              
964 571           return result;
965             }
966              
967 571           static int write_line(struct write_data *write_data, const char *line, size_t line_len)
968             {
969 571           return write_line_to(write_data->buf, line, line_len);
970             }
971              
972 45           static int write_value(struct write_data *write_data)
973             {
974             const char *q;
975             int result;
976              
977 45           q = quotes_for_value(write_data->value);
978 45           result = git_str_printf(write_data->buf,
979             "\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q);
980              
981             /* If we are updating a single name/value, we're done. Setting `value`
982             * to `NULL` will prevent us from trying to write it again later (in
983             * `write_on_section`) if we see the same section repeated.
984             */
985 45 100         if (!write_data->preg)
986 40           write_data->value = NULL;
987              
988 45           return result;
989             }
990              
991 178           static int write_on_section(
992             git_config_parser *reader,
993             const char *current_section,
994             const char *line,
995             size_t line_len,
996             void *data)
997             {
998 178           struct write_data *write_data = (struct write_data *)data;
999 178           int result = 0;
1000              
1001 178           GIT_UNUSED(reader);
1002              
1003             /* If we were previously in the correct section (but aren't anymore)
1004             * and haven't written our value (for a simple name/value set, not
1005             * a multivar), then append it to the end of the section before writing
1006             * the new one.
1007             */
1008 178 100         if (write_data->in_section && !write_data->preg && write_data->value)
    50          
    100          
1009 2           result = write_value(write_data);
1010              
1011 178           write_data->in_section = strcmp(current_section, write_data->section) == 0;
1012              
1013             /*
1014             * If there were comments just before this section, dump them as well.
1015             */
1016 178 50         if (!result) {
1017 178           result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size);
1018 178           git_str_clear(&write_data->buffered_comment);
1019             }
1020              
1021 178 50         if (!result)
1022 178           result = write_line(write_data, line, line_len);
1023              
1024 178           return result;
1025             }
1026              
1027 401           static int write_on_variable(
1028             git_config_parser *reader,
1029             const char *current_section,
1030             const char *var_name,
1031             const char *var_value,
1032             const char *line,
1033             size_t line_len,
1034             void *data)
1035             {
1036 401           struct write_data *write_data = (struct write_data *)data;
1037 401           bool has_matched = false;
1038             int error;
1039              
1040 401           GIT_UNUSED(reader);
1041 401           GIT_UNUSED(current_section);
1042              
1043             /*
1044             * If there were comments just before this variable, let's dump them as well.
1045             */
1046 401 50         if ((error = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
1047 0           return error;
1048              
1049 401           git_str_clear(&write_data->buffered_comment);
1050              
1051             /* See if we are to update this name/value pair; first examine name */
1052 401 100         if (write_data->in_section &&
    100          
1053 61           strcasecmp(write_data->name, var_name) == 0)
1054 9           has_matched = true;
1055              
1056             /* If we have a regex to match the value, see if it matches */
1057 401 100         if (has_matched && write_data->preg != NULL)
    100          
1058 1           has_matched = (git_regexp_match(write_data->preg, var_value) == 0);
1059              
1060             /* If this isn't the name/value we're looking for, simply dump the
1061             * existing data back out and continue on.
1062             */
1063 401 100         if (!has_matched)
1064 393           return write_line(write_data, line, line_len);
1065              
1066 8           write_data->preg_replaced = 1;
1067              
1068             /* If value is NULL, we are deleting this value; write nothing. */
1069 8 100         if (!write_data->value)
1070 5           return 0;
1071              
1072 3           return write_value(write_data);
1073             }
1074              
1075 0           static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data)
1076             {
1077             struct write_data *write_data;
1078              
1079 0           GIT_UNUSED(reader);
1080              
1081 0           write_data = (struct write_data *)data;
1082 0           return write_line_to(&write_data->buffered_comment, line, line_len);
1083             }
1084              
1085 50           static int write_on_eof(
1086             git_config_parser *reader, const char *current_section, void *data)
1087             {
1088 50           struct write_data *write_data = (struct write_data *)data;
1089 50           int result = 0;
1090              
1091 50           GIT_UNUSED(reader);
1092              
1093             /*
1094             * If we've buffered comments when reaching EOF, make sure to dump them.
1095             */
1096 50 50         if ((result = git_str_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
1097 0           return result;
1098              
1099             /* If we are at the EOF and have not written our value (again, for a
1100             * simple name/value set, not a multivar) then we have never seen the
1101             * section in question and should create a new section and write the
1102             * value.
1103             */
1104 50 100         if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) {
    50          
    100          
1105             /* write the section header unless we're already in it */
1106 40 100         if (!current_section || strcmp(current_section, write_data->section))
    100          
1107 17           result = write_section(write_data->buf, write_data->orig_section);
1108              
1109 40 50         if (!result)
1110 40           result = write_value(write_data);
1111             }
1112              
1113 50           return result;
1114             }
1115              
1116             /*
1117             * This is pretty much the parsing, except we write out anything we don't have
1118             */
1119 50           static int config_file_write(config_file_backend *cfg, const char *orig_key, const char *key, const git_regexp *preg, const char *value)
1120              
1121             {
1122 50           char *orig_section = NULL, *section = NULL, *orig_name, *name, *ldot;
1123 50           git_str buf = GIT_STR_INIT, contents = GIT_STR_INIT;
1124 50           git_config_parser parser = GIT_CONFIG_PARSER_INIT;
1125 50           git_filebuf file = GIT_FILEBUF_INIT;
1126             struct write_data write_data;
1127             int error;
1128              
1129 50           memset(&write_data, 0, sizeof(write_data));
1130              
1131 50 50         if (cfg->locked) {
1132 0 0         error = git_str_puts(&contents, git_str_cstr(&cfg->locked_content) == NULL ? "" : git_str_cstr(&cfg->locked_content));
1133             } else {
1134 50 50         if ((error = git_filebuf_open(&file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS,
1135             GIT_CONFIG_FILE_MODE)) < 0)
1136 0           goto done;
1137              
1138             /* We need to read in our own config file */
1139 50           error = git_futils_readbuffer(&contents, cfg->file.path);
1140             }
1141 50 100         if (error < 0 && error != GIT_ENOTFOUND)
    50          
1142 0           goto done;
1143              
1144 50 50         if ((git_config_parser_init(&parser, cfg->file.path, contents.ptr, contents.size)) < 0)
1145 0           goto done;
1146              
1147 50           ldot = strrchr(key, '.');
1148 50           name = ldot + 1;
1149 50           section = git__strndup(key, ldot - key);
1150 50 50         GIT_ERROR_CHECK_ALLOC(section);
1151              
1152 50           ldot = strrchr(orig_key, '.');
1153 50           orig_name = ldot + 1;
1154 50           orig_section = git__strndup(orig_key, ldot - orig_key);
1155 50 50         GIT_ERROR_CHECK_ALLOC(orig_section);
1156              
1157 50           write_data.buf = &buf;
1158 50           write_data.orig_section = orig_section;
1159 50           write_data.section = section;
1160 50           write_data.orig_name = orig_name;
1161 50           write_data.name = name;
1162 50           write_data.preg = preg;
1163 50           write_data.value = value;
1164              
1165 50 50         if ((error = git_config_parse(&parser, write_on_section, write_on_variable,
1166             write_on_comment, write_on_eof, &write_data)) < 0)
1167 0           goto done;
1168              
1169 50 50         if (cfg->locked) {
1170 0           size_t len = buf.asize;
1171             /* Update our copy with the modified contents */
1172 0           git_str_dispose(&cfg->locked_content);
1173 0           git_str_attach(&cfg->locked_content, git_str_detach(&buf), len);
1174             } else {
1175 50           git_filebuf_write(&file, git_str_cstr(&buf), git_str_len(&buf));
1176              
1177 50 50         if ((error = git_filebuf_commit(&file)) < 0)
1178 0           goto done;
1179              
1180 50 50         if ((error = config_file_refresh_from_buffer(&cfg->parent, buf.ptr, buf.size)) < 0)
1181 0           goto done;
1182             }
1183              
1184             done:
1185 50           git__free(section);
1186 50           git__free(orig_section);
1187 50           git_str_dispose(&write_data.buffered_comment);
1188 50           git_str_dispose(&buf);
1189 50           git_str_dispose(&contents);
1190 50           git_filebuf_cleanup(&file);
1191 50           git_config_parser_dispose(&parser);
1192              
1193 50           return error;
1194             }