config_file.c 33.5 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7
 */

8 9
#include "config_file.h"

10
#include "config.h"
11
#include "filebuf.h"
12
#include "sysdir.h"
13
#include "buffer.h"
14
#include "buf_text.h"
15
#include "git2/config.h"
16
#include "git2/sys/config.h"
17
#include "git2/types.h"
18
#include "strmap.h"
19
#include "array.h"
20
#include "config_parse.h"
21

22
#include <ctype.h>
23 24
#include <sys/types.h>
#include <regex.h>
25

26 27
typedef struct config_entry_list {
	struct config_entry_list *next;
28
	git_config_entry *entry;
29
} config_entry_list;
30

31
typedef struct git_config_file_iter {
32
	git_config_iterator parent;
33
	config_entry_list *head;
34 35
} git_config_file_iter;

36 37
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
38

39
typedef struct {
40
	git_atomic refcount;
41
	git_strmap *map;
42
	config_entry_list *list;
43
} diskfile_entries;
44 45 46 47 48

typedef struct {
	git_config_backend parent;
	/* mutex to coordinate accessing the values */
	git_mutex values_mutex;
49
	diskfile_entries *entries;
50
	const git_repository *repo;
51
	git_config_level_t level;
52 53 54 55 56
} diskfile_header;

typedef struct {
	diskfile_header header;

57 58
	git_array_t(git_config_parser) readers;

59 60 61 62
	bool locked;
	git_filebuf locked_buf;
	git_buf locked_content;

63
	struct config_file file;
64
} diskfile_backend;
65

66 67 68 69 70 71
typedef struct {
	diskfile_header header;

	diskfile_backend *snapshot_from;
} diskfile_readonly_backend;

72 73 74
typedef struct {
	const git_repository *repo;
	const char *file_path;
75
	diskfile_entries *entries;
76 77 78 79
	git_config_level_t level;
	unsigned int depth;
} diskfile_parse_state;

80
static int config_read(diskfile_entries *entries, const git_repository *repo, git_config_file *file, git_config_level_t level, int depth);
81
static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char *value);
82
static char *escape_value(const char *ptr);
83

84
int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
85
static int config_snapshot(git_config_backend **out, git_config_backend *in);
86 87 88 89 90 91 92

static int config_error_readonly(void)
{
	giterr_set(GITERR_CONFIG, "this backend is read-only");
	return -1;
}

93
static void config_entry_list_free(config_entry_list *list)
94
{
95 96 97 98 99 100 101 102 103
	config_entry_list *next;

	while (list != NULL) {
		next = list->next;

		git__free((char*) list->entry->name);
		git__free((char *) list->entry->value);
		git__free(list->entry);
		git__free(list);
104

105 106
		list = next;
	};
107 108
}

109 110 111 112 113 114 115 116 117 118 119 120
int git_config_file_normalize_section(char *start, char *end)
{
	char *scan;

	if (start == end)
		return GIT_EINVALIDSPEC;

	/* Validate and downcase range */
	for (scan = start; *scan; ++scan) {
		if (end && scan >= end)
			break;
		if (isalnum(*scan))
121
			*scan = (char)git__tolower(*scan);
122 123 124 125 126 127 128 129 130 131
		else if (*scan != '-' || scan == start)
			return GIT_EINVALIDSPEC;
	}

	if (scan == start)
		return GIT_EINVALIDSPEC;

	return 0;
}

132 133 134 135 136 137 138 139 140 141 142 143 144
static void config_entry_list_append(config_entry_list **list, config_entry_list *entry)
{
	config_entry_list *head = *list;

	if (head) {
		while (head->next != NULL)
			head = head->next;
		head->next = entry;
	} else {
		*list = entry;
	}
}

145
/* Add or append the new config option */
146
static int diskfile_entries_append(diskfile_entries *entries, git_config_entry *entry)
147 148
{
	git_strmap_iter pos;
149
	config_entry_list *existing, *var;
150 151
	int error = 0;

152
	var = git__calloc(1, sizeof(config_entry_list));
153 154 155
	GITERR_CHECK_ALLOC(var);
	var->entry = entry;

156 157 158
	pos = git_strmap_lookup_index(entries->map, entry->name);
	if (!git_strmap_valid_index(entries->map, pos)) {
		git_strmap_insert(entries->map, entry->name, var, &error);
159 160 161

		if (error > 0)
			error = 0;
162
	} else {
163
		existing = git_strmap_value_at(entries->map, pos);
164
		config_entry_list_append(&existing, var);
165 166
	}

167 168 169 170
	var = git__calloc(1, sizeof(config_entry_list));
	GITERR_CHECK_ALLOC(var);
	var->entry = entry;
	config_entry_list_append(&entries->list, var);
171 172 173 174

	return error;
}

175
static void diskfile_entries_free(diskfile_entries *entries)
176
{
177
	config_entry_list *list = NULL, *next;
178

179
	if (!entries)
180 181
		return;

182
	if (git_atomic_dec(&entries->refcount) != 0)
183 184
		return;

185 186
	git_strmap_foreach_value(entries->map, list, config_entry_list_free(list));
	git_strmap_free(entries->map);
187 188 189 190 191 192 193 194

	list = entries->list;
	while (list != NULL) {
		next = list->next;
		git__free(list);
		list = next;
	}

195
	git__free(entries);
196 197 198 199 200 201 202
}

/**
 * Take the current values map from the backend and increase its
 * refcount. This is its own function to make sure we use the mutex to
 * avoid the map pointer from changing under us.
 */
203
static diskfile_entries *diskfile_entries_take(diskfile_header *h)
204
{
205
	diskfile_entries *entries;
206

207
	if (git_mutex_lock(&h->values_mutex) < 0) {
208
	    giterr_set(GITERR_OS, "failed to lock config backend");
209 210
	    return NULL;
	}
211

212 213
	entries = h->entries;
	git_atomic_inc(&entries->refcount);
214 215 216

	git_mutex_unlock(&h->values_mutex);

217
	return entries;
218 219
}

220
static int diskfile_entries_alloc(diskfile_entries **out)
221
{
222
	diskfile_entries *entries;
223 224
	int error;

225 226
	entries = git__calloc(1, sizeof(diskfile_entries));
	GITERR_CHECK_ALLOC(entries);
227

228
	git_atomic_set(&entries->refcount, 1);
229

230 231
	if ((error = git_strmap_alloc(&entries->map)) < 0)
		git__free(entries);
232
	else
233
		*out = entries;
234 235 236 237

	return error;
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
static void config_file_clear(struct config_file *file)
{
	struct config_file *include;
	uint32_t i;

	if (file == NULL)
		return;

	git_array_foreach(file->includes, i, include) {
		config_file_clear(include);
	}
	git_array_clear(file->includes);

	git__free(file->path);
}

254
static int config_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
255
{
256
	int res;
257
	diskfile_backend *b = (diskfile_backend *)cfg;
258

259
	b->header.level = level;
260
	b->header.repo = repo;
261

262
	if ((res = diskfile_entries_alloc(&b->header.entries)) < 0)
263
		return res;
264

265
	if (!git_path_exists(b->file.path))
266 267
		return 0;

268
	if (res < 0 || (res = config_read(b->header.entries, repo, &b->file, level, 0)) < 0) {
269 270
		diskfile_entries_free(b->header.entries);
		b->header.entries = NULL;
271
	}
272

273 274 275
	return res;
}

276
static int config_is_modified(int *modified, struct config_file *file)
277
{
278
	git_config_file *include;
279 280 281
	git_buf buf = GIT_BUF_INIT;
	git_oid hash;
	uint32_t i;
282
	int error = 0;
283

284 285 286
	*modified = 0;

	if ((error = git_futils_readbuffer(&buf, file->path)) < 0)
287
		goto out;
288

289
	if ((error = git_hash_buf(&hash, buf.ptr, buf.size)) < 0)
290
		goto out;
291

292 293 294 295 296 297 298 299 300
	if (!git_oid_equal(&hash, &file->checksum)) {
		*modified = 1;
		goto out;
	}

	git_array_foreach(file->includes, i, include) {
		if ((error = config_is_modified(modified, include)) < 0 || *modified)
			goto out;
	}
301 302

out:
303
	git_buf_dispose(&buf);
304

305 306 307
	return error;
}

Ben Straub committed
308
static int config_refresh(git_config_backend *cfg)
309 310
{
	diskfile_backend *b = (diskfile_backend *)cfg;
311
	diskfile_entries *entries = NULL, *tmp;
312
	git_config_file *include;
313
	int error, modified;
314
	uint32_t i;
315

316
	if (b->header.parent.readonly)
317
		return config_error_readonly();
318

319 320 321 322 323 324 325
	error = config_is_modified(&modified, &b->file);
	if (error < 0 && error != GIT_ENOTFOUND)
		goto out;

	if (!modified)
		return 0;

326
	if ((error = diskfile_entries_alloc(&entries)) < 0)
327 328
		goto out;

329 330 331 332 333
	/* Reparse the current configuration */
	git_array_foreach(b->file.includes, i, include) {
		config_file_clear(include);
	}
	git_array_clear(b->file.includes);
334

335
	if ((error = config_read(entries, b->header.repo, &b->file, b->header.level, 0)) < 0)
336
		goto out;
337

338 339 340 341
	if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) {
		giterr_set(GITERR_OS, "failed to lock config backend");
		goto out;
	}
342

343 344 345
	tmp = b->header.entries;
	b->header.entries = entries;
	entries = tmp;
346

347
	git_mutex_unlock(&b->header.values_mutex);
348

349
out:
350
	diskfile_entries_free(entries);
351

352
	return (error == GIT_ENOTFOUND) ? 0 : error;
353 354
}

Ben Straub committed
355
static void backend_free(git_config_backend *_backend)
356
{
357
	diskfile_backend *backend = (diskfile_backend *)_backend;
358 359 360 361

	if (backend == NULL)
		return;

362
	config_file_clear(&backend->file);
363
	diskfile_entries_free(backend->header.entries);
364
	git_mutex_free(&backend->header.values_mutex);
365
	git__free(backend);
366 367
}

368
static void config_iterator_free(
369
	git_config_iterator* iter)
370
{
371
	iter->backend->free(iter->backend);
372 373
	git__free(iter);
}
374

375
static int config_iterator_next(
376
	git_config_entry **entry,
377
	git_config_iterator *iter)
378
{
379
	git_config_file_iter *it = (git_config_file_iter *) iter;
380

381 382
	if (!it->head)
		return GIT_ITEROVER;
383

384 385
	*entry = it->head->entry;
	it->head = it->head->next;
386

387 388
	return 0;
}
389

390 391 392 393
static int config_iterator_new(
	git_config_iterator **iter,
	struct git_config_backend* backend)
{
394 395 396
	diskfile_header *h;
	git_config_file_iter *it;
	git_config_backend *snapshot;
397
	diskfile_header *bh = (diskfile_header *) backend;
398
	int error;
399

400 401 402
	if ((error = config_snapshot(&snapshot, backend)) < 0)
		return error;

403
	if ((error = snapshot->open(snapshot, bh->level, bh->repo)) < 0)
404 405 406
		return error;

	it = git__calloc(1, sizeof(git_config_file_iter));
407 408
	GITERR_CHECK_ALLOC(it);

409 410 411
	h = (diskfile_header *)snapshot;

	it->parent.backend = snapshot;
412
	it->head = h->entries->list;
413 414
	it->parent.next = config_iterator_next;
	it->parent.free = config_iterator_free;
415

416
	*iter = (git_config_iterator *) it;
417 418

	return 0;
419 420
}

Ben Straub committed
421
static int config_set(git_config_backend *cfg, const char *name, const char *value)
422
{
423
	diskfile_backend *b = (diskfile_backend *)cfg;
424 425
	diskfile_entries *entries;
	git_strmap *entry_map;
426
	char *key, *esc_value = NULL;
427
	khiter_t pos;
428
	int rval, ret;
429

430
	if ((rval = git_config__normalize_name(name, &key)) < 0)
431
		return rval;
432

433
	if ((entries = diskfile_entries_take(&b->header)) == NULL)
434
		return -1;
435
	entry_map = entries->map;
436

437
	/*
438 439
	 * Try to find it in the existing values and update it if it
	 * only has one value.
440
	 */
441 442 443
	pos = git_strmap_lookup_index(entry_map, key);
	if (git_strmap_valid_index(entry_map, pos)) {
		config_entry_list *existing = git_strmap_value_at(entry_map, pos);
Russell Belfer committed
444

445
		if (existing->next != NULL) {
446
			giterr_set(GITERR_CONFIG, "multivar incompatible with simple set");
447 448
			ret = -1;
			goto out;
449
		}
450

451
		if (existing->entry->include_depth) {
452 453 454 455 456
			giterr_set(GITERR_CONFIG, "modifying included variable is not supported");
			ret = -1;
			goto out;
		}

Russell Belfer committed
457
		/* don't update if old and new values already match */
458
		if ((!existing->entry->value && !value) ||
459 460
			(existing->entry->value && value &&
			 !strcmp(existing->entry->value, value))) {
461 462
			ret = 0;
			goto out;
463
		}
464 465
	}

466
	/* No early returns due to sanity checks, let's write it out and refresh */
467

468
	if (value) {
469 470
		esc_value = escape_value(value);
		GITERR_CHECK_ALLOC(esc_value);
471 472
	}

473
	if ((ret = config_write(b, name, key, NULL, esc_value)) < 0)
474
		goto out;
475

476
	ret = config_refresh(cfg);
477

478
out:
479
	diskfile_entries_free(entries);
480 481 482
	git__free(esc_value);
	git__free(key);
	return ret;
483 484
}

485
/* release the map containing the entry as an equivalent to freeing it */
486
static void free_diskfile_entry(git_config_entry *entry)
487
{
488 489
	diskfile_entries *map = (diskfile_entries *) entry->payload;
	diskfile_entries_free(map);
490 491
}

492 493 494
/*
 * Internal function that actually gets the value in string form
 */
495
static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out)
496
{
497
	diskfile_header *h = (diskfile_header *)cfg;
498 499
	diskfile_entries *entries;
	git_strmap *entry_map;
500
	khiter_t pos;
501
	config_entry_list *var;
502
	int error = 0;
503

504
	if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0))
505 506
		return error;

507
	if ((entries = diskfile_entries_take(h)) == NULL)
508
		return -1;
509
	entry_map = entries->map;
510

511
	pos = git_strmap_lookup_index(entry_map, key);
512

513
	/* no error message; the config system will write one */
514 515
	if (!git_strmap_valid_index(entry_map, pos)) {
		diskfile_entries_free(entries);
516
		return GIT_ENOTFOUND;
517
	}
518

519
	var = git_strmap_value_at(entry_map, pos);
520 521 522 523
	while (var->next)
		var = var->next;

	*out = var->entry;
524 525
	(*out)->free = free_diskfile_entry;
	(*out)->payload = entries;
526 527

	return error;
528 529
}

530
static int config_set_multivar(
Ben Straub committed
531
	git_config_backend *cfg, const char *name, const char *regexp, const char *value)
532 533 534 535
{
	diskfile_backend *b = (diskfile_backend *)cfg;
	char *key;
	regex_t preg;
536
	int result;
537

538
	assert(regexp);
539

540
	if ((result = git_config__normalize_name(name, &key)) < 0)
541
		return result;
542

543
	result = p_regcomp(&preg, regexp, REG_EXTENDED);
544
	if (result != 0) {
545
		giterr_set_regex(&preg, result);
546 547
		result = -1;
		goto out;
548
	}
549

550
	/* If we do have it, set call config_write() and reload */
551
	if ((result = config_write(b, name, key, &preg, value)) < 0)
552
		goto out;
553

554
	result = config_refresh(cfg);
555

556
out:
557
	git__free(key);
558
	regfree(&preg);
559 560

	return result;
561 562
}

Ben Straub committed
563
static int config_delete(git_config_backend *cfg, const char *name)
564
{
565
	config_entry_list *var;
566
	diskfile_backend *b = (diskfile_backend *)cfg;
567 568
	diskfile_entries *map;
	git_strmap *entry_map;
569
	char *key;
570
	int result;
571
	khiter_t pos;
572

573
	if ((result = git_config__normalize_name(name, &key)) < 0)
574
		return result;
575

576
	if ((map = diskfile_entries_take(&b->header)) == NULL)
577
		return -1;
578
	entry_map = b->header.entries->map;
579

580
	pos = git_strmap_lookup_index(entry_map, key);
581
	git__free(key);
582

583 584
	if (!git_strmap_valid_index(entry_map, pos)) {
		diskfile_entries_free(map);
585
		giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
586
		return GIT_ENOTFOUND;
587
	}
588

589 590
	var = git_strmap_value_at(entry_map, pos);
	diskfile_entries_free(map);
591

592
	if (var->entry->include_depth) {
593 594 595 596
		giterr_set(GITERR_CONFIG, "cannot delete included variable");
		return -1;
	}

597
	if (var->next != NULL) {
598
		giterr_set(GITERR_CONFIG, "cannot delete multivar with a single delete");
599 600
		return -1;
	}
601

602
	if ((result = config_write(b, name, var->entry->name, NULL, NULL)) < 0)
603
		return result;
604

605
	return config_refresh(cfg);
606 607
}

608 609 610
static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
	diskfile_backend *b = (diskfile_backend *)cfg;
611 612
	diskfile_entries *map;
	git_strmap *entry_map;
613 614 615 616 617 618 619 620
	char *key;
	regex_t preg;
	int result;
	khiter_t pos;

	if ((result = git_config__normalize_name(name, &key)) < 0)
		return result;

621
	if ((map = diskfile_entries_take(&b->header)) == NULL)
622
		return -1;
623
	entry_map = b->header.entries->map;
624

625
	pos = git_strmap_lookup_index(entry_map, key);
626

627 628
	if (!git_strmap_valid_index(entry_map, pos)) {
		diskfile_entries_free(map);
629
		git__free(key);
630
		giterr_set(GITERR_CONFIG, "could not find key '%s' to delete", name);
631 632 633
		return GIT_ENOTFOUND;
	}

634
	diskfile_entries_free(map);
635

636
	result = p_regcomp(&preg, regexp, REG_EXTENDED);
637
	if (result != 0) {
638
		giterr_set_regex(&preg, result);
639 640
		result = -1;
		goto out;
641 642
	}

643
	if ((result = config_write(b, name, key, &preg, NULL)) < 0)
644
		goto out;
645

646
	result = config_refresh(cfg);
647

648
out:
649
	git__free(key);
650
	regfree(&preg);
651 652 653
	return result;
}

654 655 656 657 658 659 660
static int config_snapshot(git_config_backend **out, git_config_backend *in)
{
	diskfile_backend *b = (diskfile_backend *) in;

	return git_config_file__snapshot(out, b);
}

661 662 663 664 665
static int config_lock(git_config_backend *_cfg)
{
	diskfile_backend *cfg = (diskfile_backend *) _cfg;
	int error;

666
	if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file.path, 0, GIT_CONFIG_FILE_MODE)) < 0)
667 668
		return error;

669
	error = git_futils_readbuffer(&cfg->locked_content, cfg->file.path);
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690
	if (error < 0 && error != GIT_ENOTFOUND) {
		git_filebuf_cleanup(&cfg->locked_buf);
		return error;
	}

	cfg->locked = true;
	return 0;

}

static int config_unlock(git_config_backend *_cfg, int success)
{
	diskfile_backend *cfg = (diskfile_backend *) _cfg;
	int error = 0;

	if (success) {
		git_filebuf_write(&cfg->locked_buf, cfg->locked_content.ptr, cfg->locked_content.size);
		error = git_filebuf_commit(&cfg->locked_buf);
	}

	git_filebuf_cleanup(&cfg->locked_buf);
691
	git_buf_dispose(&cfg->locked_content);
692 693 694 695 696
	cfg->locked = false;

	return error;
}

Ben Straub committed
697
int git_config_file__ondisk(git_config_backend **out, const char *path)
698
{
699
	diskfile_backend *backend;
700

701
	backend = git__calloc(1, sizeof(diskfile_backend));
702
	GITERR_CHECK_ALLOC(backend);
703

704
	backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
705
	git_mutex_init(&backend->header.values_mutex);
706

707 708 709
	backend->file.path = git__strdup(path);
	GITERR_CHECK_ALLOC(backend->file.path);
	git_array_init(backend->file.includes);
710

711 712 713 714 715 716 717 718
	backend->header.parent.open = config_open;
	backend->header.parent.get = config_get;
	backend->header.parent.set = config_set;
	backend->header.parent.set_multivar = config_set_multivar;
	backend->header.parent.del = config_delete;
	backend->header.parent.del_multivar = config_delete_multivar;
	backend->header.parent.iterator = config_iterator_new;
	backend->header.parent.snapshot = config_snapshot;
719 720
	backend->header.parent.lock = config_lock;
	backend->header.parent.unlock = config_unlock;
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764
	backend->header.parent.free = backend_free;

	*out = (git_config_backend *)backend;

	return 0;
}

static int config_set_readonly(git_config_backend *cfg, const char *name, const char *value)
{
	GIT_UNUSED(cfg);
	GIT_UNUSED(name);
	GIT_UNUSED(value);

	return config_error_readonly();
}

static int config_set_multivar_readonly(
	git_config_backend *cfg, const char *name, const char *regexp, const char *value)
{
	GIT_UNUSED(cfg);
	GIT_UNUSED(name);
	GIT_UNUSED(regexp);
	GIT_UNUSED(value);

	return config_error_readonly();
}

static int config_delete_multivar_readonly(git_config_backend *cfg, const char *name, const char *regexp)
{
	GIT_UNUSED(cfg);
	GIT_UNUSED(name);
	GIT_UNUSED(regexp);

	return config_error_readonly();
}

static int config_delete_readonly(git_config_backend *cfg, const char *name)
{
	GIT_UNUSED(cfg);
	GIT_UNUSED(name);

	return config_error_readonly();
}

765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
static int config_lock_readonly(git_config_backend *_cfg)
{
	GIT_UNUSED(_cfg);

	return config_error_readonly();
}

static int config_unlock_readonly(git_config_backend *_cfg, int success)
{
	GIT_UNUSED(_cfg);
	GIT_UNUSED(success);

	return config_error_readonly();
}

780 781 782 783 784 785 786
static void backend_readonly_free(git_config_backend *_backend)
{
	diskfile_backend *backend = (diskfile_backend *)_backend;

	if (backend == NULL)
		return;

787
	diskfile_entries_free(backend->header.entries);
788
	git_mutex_free(&backend->header.values_mutex);
789 790 791
	git__free(backend);
}

792
static int config_readonly_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
793 794 795
{
	diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
	diskfile_backend *src = b->snapshot_from;
796
	diskfile_header *src_header = &src->header;
797
	diskfile_entries *entries;
798 799
	int error;

800
	if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0)
801
		return error;
802

803
	/* We're just copying data, don't care about the level or repo*/
804
	GIT_UNUSED(level);
805
	GIT_UNUSED(repo);
806

807
	if ((entries = diskfile_entries_take(src_header)) == NULL)
808
		return -1;
809
	b->header.entries = entries;
810

811
	return 0;
812 813 814 815 816 817 818 819 820 821
}

int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in)
{
	diskfile_readonly_backend *backend;

	backend = git__calloc(1, sizeof(diskfile_readonly_backend));
	GITERR_CHECK_ALLOC(backend);

	backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
822
	git_mutex_init(&backend->header.values_mutex);
823 824 825

	backend->snapshot_from = in;

826
	backend->header.parent.readonly = 1;
827 828 829 830 831 832 833 834
	backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
	backend->header.parent.open = config_readonly_open;
	backend->header.parent.get = config_get;
	backend->header.parent.set = config_set_readonly;
	backend->header.parent.set_multivar = config_set_multivar_readonly;
	backend->header.parent.del = config_delete_readonly;
	backend->header.parent.del_multivar = config_delete_multivar_readonly;
	backend->header.parent.iterator = config_iterator_new;
835 836
	backend->header.parent.lock = config_lock_readonly;
	backend->header.parent.unlock = config_unlock_readonly;
837
	backend->header.parent.free = backend_readonly_free;
838

Ben Straub committed
839
	*out = (git_config_backend *)backend;
840

841
	return 0;
842 843
}

844 845 846 847
static int included_path(git_buf *out, const char *dir, const char *path)
{
	/* From the user's home */
	if (path[0] == '~' && path[1] == '/')
848
		return git_sysdir_expand_global_file(out, &path[1]);
849 850 851 852

	return git_path_join_unrooted(out, path, dir, NULL);
}

853 854 855
/* Escape the values to write them to the file */
static char *escape_value(const char *ptr)
{
856
	git_buf buf;
857 858 859 860 861 862
	size_t len;
	const char *esc;

	assert(ptr);

	len = strlen(ptr);
863 864 865
	if (!len)
		return git__calloc(1, sizeof(char));

866 867
	if (git_buf_init(&buf, len) < 0)
		return NULL;
868 869

	while (*ptr != '\0') {
870
		if ((esc = strchr(git_config_escaped, *ptr)) != NULL) {
871
			git_buf_putc(&buf, '\\');
872
			git_buf_putc(&buf, git_config_escapes[esc - git_config_escaped]);
873 874 875 876 877 878
		} else {
			git_buf_putc(&buf, *ptr);
		}
		ptr++;
	}

879
	if (git_buf_oom(&buf))
880 881 882 883 884
		return NULL;

	return git_buf_detach(&buf);
}

885
static int parse_include(git_config_parser *reader,
886
	diskfile_parse_state *parse_data, const char *file)
887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907
{
	struct config_file *include;
	git_buf path = GIT_BUF_INIT;
	char *dir;
	int result;

	if ((result = git_path_dirname_r(&path, reader->file->path)) < 0)
		return result;

	dir = git_buf_detach(&path);
	result = included_path(&path, dir, file);
	git__free(dir);

	if (result < 0)
		return result;

	include = git_array_alloc(reader->file->includes);
	memset(include, 0, sizeof(*include));
	git_array_init(include->includes);
	include->path = git_buf_detach(&path);

908
	result = config_read(parse_data->entries, parse_data->repo,
909
		include, parse_data->level, parse_data->depth+1);
910 911 912 913 914 915 916 917 918

	if (result == GIT_ENOTFOUND) {
		giterr_clear();
		result = 0;
	}

	return result;
}

919
static int do_match_gitdir(
920 921 922
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
923 924
	const char *value,
	bool case_insensitive)
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
{
	git_buf path = GIT_BUF_INIT;
	int error, fnmatch_flags;

	if (value[0] == '.' && git_path_is_dirsep(value[1])) {
		git_path_dirname_r(&path, cfg_file);
		git_buf_joinpath(&path, path.ptr, value + 2);
	} else if (value[0] == '~' && git_path_is_dirsep(value[1]))
		git_sysdir_expand_global_file(&path, value + 1);
	else if (!git_path_is_absolute(value))
		git_buf_joinpath(&path, "**", value);
	else
		git_buf_sets(&path, value);

	if (git_buf_oom(&path)) {
		error = -1;
		goto out;
	}

	if (git_path_is_dirsep(value[strlen(value) - 1]))
		git_buf_puts(&path, "**");

	fnmatch_flags = FNM_PATHNAME|FNM_LEADING_DIR;
948 949
	if (case_insensitive)
		fnmatch_flags |= FNM_IGNORECASE;
950 951 952 953 954 955 956

	if ((error = p_fnmatch(path.ptr, git_repository_path(repo), fnmatch_flags)) < 0)
		goto out;

	*matches = (error == 0);

out:
957
	git_buf_dispose(&path);
958 959 960
	return error;
}

961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978
static int conditional_match_gitdir(
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
	const char *value)
{
	return do_match_gitdir(matches, repo, cfg_file, value, false);
}

static int conditional_match_gitdir_i(
	int *matches,
	const git_repository *repo,
	const char *cfg_file,
	const char *value)
{
	return do_match_gitdir(matches, repo, cfg_file, value, true);
}

979 980 981 982
static const struct {
	const char *prefix;
	int (*matches)(int *matches, const git_repository *repo, const char *cfg, const char *value);
} conditions[] = {
983 984
	{ "gitdir:", conditional_match_gitdir },
	{ "gitdir/i:", conditional_match_gitdir_i }
985 986
};

987
static int parse_conditional_include(git_config_parser *reader,
988
	diskfile_parse_state *parse_data, const char *section, const char *file)
989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
{
	char *condition;
	size_t i;
	int error = 0, matches;

	if (!parse_data->repo)
		return 0;

	condition = git__substrdup(section + strlen("includeIf."),
				   strlen(section) - strlen("includeIf.") - strlen(".path"));

	for (i = 0; i < ARRAY_SIZE(conditions); i++) {
		if (git__prefixcmp(condition, conditions[i].prefix))
			continue;

		if ((error = conditions[i].matches(&matches,
						   parse_data->repo,
						   parse_data->file_path,
						   condition + strlen(conditions[i].prefix))) < 0)
			break;

		if (matches)
			error = parse_include(reader, parse_data, file);

		break;
	}

	git__free(condition);
	return error;
}

1020
static int read_on_variable(
1021
	git_config_parser *reader,
1022
	const char *current_section,
1023 1024
	const char *var_name,
	const char *var_value,
1025 1026 1027
	const char *line,
	size_t line_len,
	void *data)
1028
{
1029
	diskfile_parse_state *parse_data = (diskfile_parse_state *)data;
1030
	git_buf buf = GIT_BUF_INIT;
1031
	git_config_entry *entry;
1032
	const char *c;
1033 1034
	int result = 0;

1035 1036 1037
	GIT_UNUSED(line);
	GIT_UNUSED(line_len);

1038 1039 1040 1041
	git_buf_puts(&buf, current_section);
	git_buf_putc(&buf, '.');
	for (c = var_name; *c; c++)
		git_buf_putc(&buf, git__tolower(*c));
1042

1043
	if (git_buf_oom(&buf))
1044 1045
		return -1;

1046 1047 1048
	entry = git__calloc(1, sizeof(git_config_entry));
	GITERR_CHECK_ALLOC(entry);
	entry->name = git_buf_detach(&buf);
1049
	entry->value = var_value ? git__strdup(var_value) : NULL;
1050
	entry->level = parse_data->level;
1051
	entry->include_depth = parse_data->depth;
1052

1053
	if ((result = diskfile_entries_append(parse_data->entries, entry)) < 0)
1054 1055 1056 1057 1058
		return result;

	result = 0;

	/* Add or append the new config option */
1059 1060 1061 1062
	if (!git__strcmp(entry->name, "include.path"))
		result = parse_include(reader, parse_data, entry->value);
	else if (!git__prefixcmp(entry->name, "includeif.") &&
	         !git__suffixcmp(entry->name, ".path"))
1063
		result = parse_conditional_include(reader, parse_data,
1064
						   entry->name, entry->value);
1065

1066 1067 1068
	return result;
}

1069
static int config_read(
1070
	diskfile_entries *entries,
1071
	const git_repository *repo,
1072
	git_config_file *file,
1073 1074
	git_config_level_t level,
	int depth)
1075
{
1076
	diskfile_parse_state parse_data;
1077
	git_config_parser reader;
1078
	git_buf contents = GIT_BUF_INIT;
1079
	int error;
1080 1081

	if (depth >= MAX_INCLUDE_DEPTH) {
1082
		giterr_set(GITERR_CONFIG, "maximum config include depth reached");
1083 1084 1085
		return -1;
	}

1086
	if ((error = git_futils_readbuffer(&contents, file->path)) < 0)
1087 1088
		goto out;

1089 1090 1091
	git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size);

	if ((error = git_hash_buf(&file->checksum, contents.ptr, contents.size)) < 0)
1092 1093
		goto out;

1094
	/* Initialize the reading position */
1095
	reader.file = file;
1096
	git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size);
1097 1098

	/* If the file is empty, there's nothing for us to do */
1099
	if (!reader.ctx.content || *reader.ctx.content == '\0')
1100
		goto out;
1101

1102 1103
	parse_data.repo = repo;
	parse_data.file_path = file->path;
1104
	parse_data.entries = entries;
1105 1106 1107
	parse_data.level = level;
	parse_data.depth = depth;

1108
	error = git_config_parse(&reader, NULL, read_on_variable, NULL, NULL, &parse_data);
1109 1110

out:
1111
	git_buf_dispose(&contents);
1112
	return error;
1113 1114
}

1115
static int write_section(git_buf *fbuf, const char *key)
1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138
{
	int result;
	const char *dot;
	git_buf buf = GIT_BUF_INIT;

	/* All of this just for [section "subsection"] */
	dot = strchr(key, '.');
	git_buf_putc(&buf, '[');
	if (dot == NULL) {
		git_buf_puts(&buf, key);
	} else {
		char *escaped;
		git_buf_put(&buf, key, dot - key);
		escaped = escape_value(dot + 1);
		GITERR_CHECK_ALLOC(escaped);
		git_buf_printf(&buf, " \"%s\"", escaped);
		git__free(escaped);
	}
	git_buf_puts(&buf, "]\n");

	if (git_buf_oom(&buf))
		return -1;

1139
	result = git_buf_put(fbuf, git_buf_cstr(&buf), buf.size);
1140
	git_buf_dispose(&buf);
1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163

	return result;
}

static const char *quotes_for_value(const char *value)
{
	const char *ptr;

	if (value[0] == ' ' || value[0] == '\0')
		return "\"";

	for (ptr = value; *ptr; ++ptr) {
		if (*ptr == ';' || *ptr == '#')
			return "\"";
	}

	if (ptr[-1] == ' ')
		return "\"";

	return "";
}

struct write_data {
1164
	git_buf *buf;
1165
	git_buf buffered_comment;
1166 1167
	unsigned int in_section : 1,
		preg_replaced : 1;
1168
	const char *orig_section;
1169
	const char *section;
1170
	const char *orig_name;
1171 1172 1173 1174 1175
	const char *name;
	const regex_t *preg;
	const char *value;
};

1176
static int write_line_to(git_buf *buf, const char *line, size_t line_len)
1177
{
1178
	int result = git_buf_put(buf, line, line_len);
1179 1180

	if (!result && line_len && line[line_len-1] != '\n')
1181
		result = git_buf_printf(buf, "\n");
1182 1183 1184 1185

	return result;
}

1186 1187 1188 1189 1190
static int write_line(struct write_data *write_data, const char *line, size_t line_len)
{
	return write_line_to(write_data->buf, line, line_len);
}

1191 1192 1193 1194 1195 1196
static int write_value(struct write_data *write_data)
{
	const char *q;
	int result;

	q = quotes_for_value(write_data->value);
1197
	result = git_buf_printf(write_data->buf,
1198
		"\t%s = %s%s%s\n", write_data->orig_name, q, write_data->value, q);
1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209

	/* If we are updating a single name/value, we're done.  Setting `value`
	 * to `NULL` will prevent us from trying to write it again later (in
	 * `write_on_section`) if we see the same section repeated.
	 */
	if (!write_data->preg)
		write_data->value = NULL;

	return result;
}

1210
static int write_on_section(
1211
	git_config_parser *reader,
1212 1213 1214 1215
	const char *current_section,
	const char *line,
	size_t line_len,
	void *data)
1216 1217 1218 1219
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

1220 1221
	GIT_UNUSED(reader);

1222 1223 1224 1225 1226 1227 1228 1229 1230 1231
	/* If we were previously in the correct section (but aren't anymore)
	 * and haven't written our value (for a simple name/value set, not
	 * a multivar), then append it to the end of the section before writing
	 * the new one.
	 */
	if (write_data->in_section && !write_data->preg && write_data->value)
		result = write_value(write_data);

	write_data->in_section = strcmp(current_section, write_data->section) == 0;

1232 1233 1234 1235 1236 1237 1238 1239
	/*
	 * If there were comments just before this section, dump them as well.
	 */
	if (!result) {
		result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size);
		git_buf_clear(&write_data->buffered_comment);
	}

1240
	if (!result)
1241
		result = write_line(write_data, line, line_len);
1242 1243 1244 1245

	return result;
}

1246
static int write_on_variable(
1247
	git_config_parser *reader,
1248
	const char *current_section,
1249 1250
	const char *var_name,
	const char *var_value,
1251 1252 1253
	const char *line,
	size_t line_len,
	void *data)
1254 1255 1256
{
	struct write_data *write_data = (struct write_data *)data;
	bool has_matched = false;
1257
	int error;
1258 1259 1260

	GIT_UNUSED(reader);
	GIT_UNUSED(current_section);
1261

1262 1263 1264 1265 1266 1267 1268 1269
	/*
	 * If there were comments just before this variable, let's dump them as well.
	 */
	if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
		return error;

	git_buf_clear(&write_data->buffered_comment);

1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281
	/* See if we are to update this name/value pair; first examine name */
	if (write_data->in_section &&
		strcasecmp(write_data->name, var_name) == 0)
		has_matched = true;

	/* If we have a regex to match the value, see if it matches */
	if (has_matched && write_data->preg != NULL)
		has_matched = (regexec(write_data->preg, var_value, 0, NULL, 0) == 0);

	/* If this isn't the name/value we're looking for, simply dump the
	 * existing data back out and continue on.
	 */
1282 1283
	if (!has_matched)
		return write_line(write_data, line, line_len);
1284 1285 1286 1287 1288 1289 1290 1291 1292 1293

	write_data->preg_replaced = 1;

	/* If value is NULL, we are deleting this value; write nothing. */
	if (!write_data->value)
		return 0;

	return write_value(write_data);
}

1294
static int write_on_comment(git_config_parser *reader, const char *line, size_t line_len, void *data)
1295
{
1296 1297 1298 1299 1300
	struct write_data *write_data;

	GIT_UNUSED(reader);

	write_data = (struct write_data *)data;
1301
	return write_line_to(&write_data->buffered_comment, line, line_len);
1302 1303
}

1304
static int write_on_eof(
1305
	git_config_parser *reader, const char *current_section, void *data)
1306 1307 1308 1309
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

1310 1311
	GIT_UNUSED(reader);

1312 1313 1314 1315 1316 1317
	/*
	 * If we've buffered comments when reaching EOF, make sure to dump them.
	 */
	if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0)
		return result;

1318 1319 1320 1321 1322 1323
	/* If we are at the EOF and have not written our value (again, for a
	 * simple name/value set, not a multivar) then we have never seen the
	 * section in question and should create a new section and write the
	 * value.
	 */
	if ((!write_data->preg || !write_data->preg_replaced) && write_data->value) {
1324 1325
		/* write the section header unless we're already in it */
		if (!current_section || strcmp(current_section, write_data->section))
1326
			result = write_section(write_data->buf, write_data->orig_section);
1327 1328

		if (!result)
1329 1330 1331 1332 1333 1334 1335 1336 1337
			result = write_value(write_data);
	}

	return result;
}

/*
 * This is pretty much the parsing, except we write out anything we don't have
 */
1338
static int config_write(diskfile_backend *cfg, const char *orig_key, const char *key, const regex_t *preg, const char* value)
1339 1340
{
	int result;
1341
	char *orig_section, *section, *orig_name, *name, *ldot;
1342
	git_filebuf file = GIT_FILEBUF_INIT;
1343
	git_buf buf = GIT_BUF_INIT, contents = GIT_BUF_INIT;
1344
	git_config_parser reader;
1345 1346
	struct write_data write_data;

1347 1348
	memset(&reader, 0, sizeof(reader));
	reader.file = &cfg->file;
1349

1350
	if (cfg->locked) {
1351
		result = git_buf_puts(&contents, git_buf_cstr(&cfg->locked_content));
1352 1353 1354
	} else {
		/* Lock the file */
		if ((result = git_filebuf_open(
1355
			     &file, cfg->file.path, GIT_FILEBUF_HASH_CONTENTS, GIT_CONFIG_FILE_MODE)) < 0) {
1356
			git_buf_dispose(&contents);
1357
			return result;
1358
		}
1359

1360
		/* We need to read in our own config file */
1361
		result = git_futils_readbuffer(&contents, cfg->file.path);
1362
	}
1363 1364

	/* Initialise the reading position */
1365 1366
	if (result == 0 || result == GIT_ENOTFOUND) {
		git_parse_ctx_init(&reader.ctx, contents.ptr, contents.size);
1367
	} else {
1368
		git_filebuf_cleanup(&file);
1369 1370 1371 1372 1373 1374
		return -1; /* OS error when reading the file */
	}

	ldot = strrchr(key, '.');
	name = ldot + 1;
	section = git__strndup(key, ldot - key);
1375
	GITERR_CHECK_ALLOC(section);
1376

1377 1378 1379
	ldot = strrchr(orig_key, '.');
	orig_name = ldot + 1;
	orig_section = git__strndup(orig_key, ldot - orig_key);
1380
	GITERR_CHECK_ALLOC(orig_section);
1381

1382
	write_data.buf = &buf;
1383
	git_buf_init(&write_data.buffered_comment, 0);
1384
	write_data.orig_section = orig_section;
1385 1386 1387
	write_data.section = section;
	write_data.in_section = 0;
	write_data.preg_replaced = 0;
1388
	write_data.orig_name = orig_name;
1389 1390 1391 1392
	write_data.name = name;
	write_data.preg = preg;
	write_data.value = value;

1393 1394 1395 1396 1397 1398
	result = git_config_parse(&reader,
		write_on_section,
		write_on_variable,
		write_on_comment,
		write_on_eof,
		&write_data);
1399
	git__free(section);
1400
	git__free(orig_section);
1401
	git_buf_dispose(&write_data.buffered_comment);
1402 1403

	if (result < 0) {
1404 1405 1406 1407
		git_filebuf_cleanup(&file);
		goto done;
	}

1408 1409 1410
	if (cfg->locked) {
		size_t len = buf.asize;
		/* Update our copy with the modified contents */
1411
		git_buf_dispose(&cfg->locked_content);
1412 1413 1414 1415 1416
		git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len);
	} else {
		git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf));
		result = git_filebuf_commit(&file);
	}
1417 1418

done:
1419 1420
	git_buf_dispose(&buf);
	git_buf_dispose(&contents);
1421
	git_parse_ctx_clear(&reader.ctx);
1422 1423
	return result;
}