config_file.c 44.6 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 "common.h"
#include "config.h"
10
#include "filebuf.h"
11
#include "sysdir.h"
12
#include "buffer.h"
13
#include "buf_text.h"
14
#include "git2/config.h"
15
#include "git2/sys/config.h"
16
#include "git2/types.h"
17
#include "strmap.h"
18
#include "array.h"
19

20
#include <ctype.h>
21 22
#include <sys/types.h>
#include <regex.h>
23

24
GIT__USE_STRMAP
25

26 27
typedef struct cvar_t {
	struct cvar_t *next;
28
	git_config_entry *entry;
29
	bool included; /* whether this is part of [include] */
30
} cvar_t;
31

32
typedef struct git_config_file_iter {
33
	git_config_iterator parent;
34
	git_strmap_iter iter;
35
	cvar_t* next_var;
36 37
} git_config_file_iter;

38 39
/* Max depth for [include] directives */
#define MAX_INCLUDE_DEPTH 10
40

41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
#define CVAR_LIST_HEAD(list) ((list)->head)

#define CVAR_LIST_TAIL(list) ((list)->tail)

#define CVAR_LIST_NEXT(var) ((var)->next)

#define CVAR_LIST_EMPTY(list) ((list)->head == NULL)

#define CVAR_LIST_APPEND(list, var) do {\
	if (CVAR_LIST_EMPTY(list)) {\
		CVAR_LIST_HEAD(list) = CVAR_LIST_TAIL(list) = var;\
	} else {\
		CVAR_LIST_NEXT(CVAR_LIST_TAIL(list)) = var;\
		CVAR_LIST_TAIL(list) = var;\
	}\
} while(0)

#define CVAR_LIST_REMOVE_HEAD(list) do {\
	CVAR_LIST_HEAD(list) = CVAR_LIST_NEXT(CVAR_LIST_HEAD(list));\
} while(0)

#define CVAR_LIST_REMOVE_AFTER(var) do {\
	CVAR_LIST_NEXT(var) = CVAR_LIST_NEXT(CVAR_LIST_NEXT(var));\
} while(0)

#define CVAR_LIST_FOREACH(list, iter)\
	for ((iter) = CVAR_LIST_HEAD(list);\
		 (iter) != NULL;\
		 (iter) = CVAR_LIST_NEXT(iter))

/*
 * Inspired by the FreeBSD functions
 */
#define CVAR_LIST_FOREACH_SAFE(start, iter, tmp)\
	for ((iter) = CVAR_LIST_HEAD(vars);\
		 (iter) && (((tmp) = CVAR_LIST_NEXT(iter) || 1));\
		 (iter) = (tmp))

79
struct reader {
80 81
	time_t file_mtime;
	size_t file_size;
82 83 84 85 86 87 88
	char *file_path;
	git_buf buffer;
	char *read_ptr;
	int line_number;
	int eof;
};

89
typedef struct {
90
	git_atomic refcount;
91
	git_strmap *values;
92 93 94 95 96 97 98
} refcounted_strmap;

typedef struct {
	git_config_backend parent;
	/* mutex to coordinate accessing the values */
	git_mutex values_mutex;
	refcounted_strmap *values;
99 100 101 102 103 104
} diskfile_header;

typedef struct {
	diskfile_header header;

	git_config_level_t level;
105

106
	git_array_t(struct reader) readers;
107

108 109 110 111
	bool locked;
	git_filebuf locked_buf;
	git_buf locked_content;

112
	char  *file_path;
113
} diskfile_backend;
114

115 116 117 118 119 120
typedef struct {
	diskfile_header header;

	diskfile_backend *snapshot_from;
} diskfile_readonly_backend;

121
static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
122
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
123
static char *escape_value(const char *ptr);
124

125
int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
126
static int config_snapshot(git_config_backend **out, git_config_backend *in);
127

128
static void set_parse_error(struct reader *reader, int col, const char *error_str)
129 130
{
	giterr_set(GITERR_CONFIG, "Failed to parse config file: %s (in %s:%d, column %d)",
131
		error_str, reader->file_path, reader->line_number, col);
132 133
}

134 135 136 137 138 139
static int config_error_readonly(void)
{
	giterr_set(GITERR_CONFIG, "this backend is read-only");
	return -1;
}

140
static void cvar_free(cvar_t *var)
141 142 143 144
{
	if (var == NULL)
		return;

145 146 147
	git__free((char*)var->entry->name);
	git__free((char *)var->entry->value);
	git__free(var->entry);
148
	git__free(var);
149 150
}

151 152 153 154 155 156 157 158 159 160 161 162
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))
163
			*scan = (char)git__tolower(*scan);
164 165 166 167 168 169 170 171 172 173
		else if (*scan != '-' || scan == start)
			return GIT_EINVALIDSPEC;
	}

	if (scan == start)
		return GIT_EINVALIDSPEC;

	return 0;
}

174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
/* Add or append the new config option */
static int append_entry(git_strmap *values, cvar_t *var)
{
	git_strmap_iter pos;
	cvar_t *existing;
	int error = 0;

	pos = git_strmap_lookup_index(values, var->entry->name);
	if (!git_strmap_valid_index(values, pos)) {
		git_strmap_insert(values, var->entry->name, var, error);
	} else {
		existing = git_strmap_value_at(values, pos);
		while (existing->next != NULL) {
			existing = existing->next;
		}
		existing->next = var;
	}

	if (error > 0)
		error = 0;

	return error;
}

198
static void free_vars(git_strmap *values)
199
{
200
	cvar_t *var = NULL;
201

202 203
	if (values == NULL)
		return;
204

205
	git_strmap_foreach_value(values, var,
206 207 208 209 210
		while (var != NULL) {
			cvar_t *next = CVAR_LIST_NEXT(var);
			cvar_free(var);
			var = next;
		});
211

212
	git_strmap_free(values);
213 214
}

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
static void refcounted_strmap_free(refcounted_strmap *map)
{
	if (!map)
		return;

	if (git_atomic_dec(&map->refcount) != 0)
		return;

	free_vars(map->values);
	git__free(map);
}

/**
 * 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.
 */
static refcounted_strmap *refcounted_strmap_take(diskfile_header *h)
{
	refcounted_strmap *map;

	git_mutex_lock(&h->values_mutex);

	map = h->values;
	git_atomic_inc(&map->refcount);

	git_mutex_unlock(&h->values_mutex);

	return map;
}

static int refcounted_strmap_alloc(refcounted_strmap **out)
{
	refcounted_strmap *map;
	int error;

	map = git__calloc(1, sizeof(refcounted_strmap));
252
	GITERR_CHECK_ALLOC(map);
253 254

	git_atomic_set(&map->refcount, 1);
255 256

	if ((error = git_strmap_alloc(&map->values)) < 0)
257
		git__free(map);
258 259
	else
		*out = map;
260 261 262 263

	return error;
}

264
static int config_open(git_config_backend *cfg, git_config_level_t level)
265
{
266
	int res;
267
	struct reader *reader;
268
	diskfile_backend *b = (diskfile_backend *)cfg;
269

270 271
	b->level = level;

272
	if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
273
		return res;
274

275 276
	git_array_init(b->readers);
	reader = git_array_alloc(b->readers);
277
	if (!reader) {
278
		refcounted_strmap_free(b->header.values);
279 280
		return -1;
	}
281 282 283 284 285 286
	memset(reader, 0, sizeof(struct reader));

	reader->file_path = git__strdup(b->file_path);
	GITERR_CHECK_ALLOC(reader->file_path);

	git_buf_init(&reader->buffer, 0);
287
	res = git_futils_readbuffer_updated(
288
		&reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
289

290
	/* It's fine if the file doesn't exist */
291 292 293
	if (res == GIT_ENOTFOUND)
		return 0;

294
	if (res < 0 || (res = config_read(b->header.values->values, b, reader, level, 0)) < 0) {
295
		refcounted_strmap_free(b->header.values);
296
		b->header.values = NULL;
297
	}
298

299
	reader = git_array_get(b->readers, 0);
300
	git_buf_free(&reader->buffer);
301

302 303 304
	return res;
}

305 306 307
/* The meat of the refresh, as we want to use it in different places */
static int config__refresh(git_config_backend *cfg)
{
308
	refcounted_strmap *values = NULL, *tmp;
309 310 311 312
	diskfile_backend *b = (diskfile_backend *)cfg;
	struct reader *reader = NULL;
	int error = 0;

313
	if ((error = refcounted_strmap_alloc(&values)) < 0)
314 315 316
		goto out;

	reader = git_array_get(b->readers, git_array_size(b->readers) - 1);
317
	GITERR_CHECK_ALLOC(reader);
318

319
	if ((error = config_read(values->values, b, reader, b->level, 0)) < 0)
320 321
		goto out;

322 323 324 325 326 327 328
	git_mutex_lock(&b->header.values_mutex);

	tmp = b->header.values;
	b->header.values = values;
	values = tmp;

	git_mutex_unlock(&b->header.values_mutex);
329 330

out:
331
	refcounted_strmap_free(values);
332 333
	if (reader)
		git_buf_free(&reader->buffer);
334 335 336
	return error;
}

Ben Straub committed
337
static int config_refresh(git_config_backend *cfg)
338
{
339
	int error = 0, updated = 0, any_updated = 0;
340
	diskfile_backend *b = (diskfile_backend *)cfg;
341
	struct reader *reader = NULL;
342
	uint32_t i;
343

344 345
	for (i = 0; i < git_array_size(b->readers); i++) {
		reader = git_array_get(b->readers, i);
346
		error = git_futils_readbuffer_updated(
347 348
			&reader->buffer, reader->file_path,
			&reader->file_mtime, &reader->file_size, &updated);
349

350 351
		if (error < 0 && error != GIT_ENOTFOUND)
			return error;
352 353 354 355 356 357

		if (updated)
			any_updated = 1;
	}

	if (!any_updated)
358
		return (error == GIT_ENOTFOUND) ? 0 : error;
359

360
	return config__refresh(cfg);
361 362
}

Ben Straub committed
363
static void backend_free(git_config_backend *_backend)
364
{
365
	diskfile_backend *backend = (diskfile_backend *)_backend;
366
	uint32_t i;
367 368 369 370

	if (backend == NULL)
		return;

371 372 373 374 375 376
	for (i = 0; i < git_array_size(backend->readers); i++) {
		struct reader *r = git_array_get(backend->readers, i);
		git__free(r->file_path);
	}
	git_array_clear(backend->readers);

377
	git__free(backend->file_path);
378
	refcounted_strmap_free(backend->header.values);
379
	git_mutex_free(&backend->header.values_mutex);
380
	git__free(backend);
381 382
}

383
static void config_iterator_free(
384
	git_config_iterator* iter)
385
{
386
	iter->backend->free(iter->backend);
387 388
	git__free(iter);
}
389

390
static int config_iterator_next(
391
	git_config_entry **entry,
392
	git_config_iterator *iter)
393
{
394
	git_config_file_iter *it = (git_config_file_iter *) iter;
395
	diskfile_header *h = (diskfile_header *) it->parent.backend;
396
	git_strmap *values = h->values->values;
397
	int err = 0;
398
	cvar_t * var;
399

400
	if (it->next_var == NULL) {
401
		err = git_strmap_next((void**) &var, &(it->iter), values);
402
	} else {
403
		var = it->next_var;
404
	}
405

406
	if (err < 0) {
407
		it->next_var = NULL;
408
		return err;
409
	}
410

411
	*entry = var->entry;
412
	it->next_var = CVAR_LIST_NEXT(var);
413

414 415
	return 0;
}
416

417 418 419 420
static int config_iterator_new(
	git_config_iterator **iter,
	struct git_config_backend* backend)
{
421 422 423 424 425
	diskfile_header *h;
	git_config_file_iter *it;
	git_config_backend *snapshot;
	diskfile_backend *b = (diskfile_backend *) backend;
	int error;
426

427 428 429 430 431 432 433
	if ((error = config_snapshot(&snapshot, backend)) < 0)
		return error;

	if ((error = snapshot->open(snapshot, b->level)) < 0)
		return error;

	it = git__calloc(1, sizeof(git_config_file_iter));
434 435
	GITERR_CHECK_ALLOC(it);

436 437
	h = (diskfile_header *)snapshot;

438 439 440
	/* strmap_begin() is currently a macro returning 0 */
	GIT_UNUSED(h);

441
	it->parent.backend = snapshot;
442
	it->iter = git_strmap_begin(h->values);
443 444 445 446 447
	it->next_var = NULL;

	it->parent.next = config_iterator_next;
	it->parent.free = config_iterator_free;
	*iter = (git_config_iterator *) it;
448 449

	return 0;
450 451
}

Ben Straub committed
452
static int config_set(git_config_backend *cfg, const char *name, const char *value)
453
{
454
	diskfile_backend *b = (diskfile_backend *)cfg;
455 456
	refcounted_strmap *map;
	git_strmap *values;
457
	char *key, *esc_value = NULL;
458
	khiter_t pos;
459
	int rval, ret;
460

461
	if ((rval = git_config__normalize_name(name, &key)) < 0)
462
		return rval;
463

464 465 466
	map = refcounted_strmap_take(&b->header);
	values = map->values;

467
	/*
468 469
	 * Try to find it in the existing values and update it if it
	 * only has one value.
470
	 */
471 472 473
	pos = git_strmap_lookup_index(values, key);
	if (git_strmap_valid_index(values, pos)) {
		cvar_t *existing = git_strmap_value_at(values, pos);
Russell Belfer committed
474

475 476
		if (existing->next != NULL) {
			giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set");
477 478
			ret = -1;
			goto out;
479
		}
480

Russell Belfer committed
481
		/* don't update if old and new values already match */
482
		if ((!existing->entry->value && !value) ||
483 484
			(existing->entry->value && value &&
			 !strcmp(existing->entry->value, value))) {
485 486
			ret = 0;
			goto out;
487
		}
488 489
	}

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

492
	if (value) {
493 494
		esc_value = escape_value(value);
		GITERR_CHECK_ALLOC(esc_value);
495 496
	}

497 498
	if ((ret = config_write(b, key, NULL, esc_value)) < 0)
		goto out;
499

500
	ret = config_refresh(cfg);
501

502
out:
503
	refcounted_strmap_free(map);
504 505 506
	git__free(esc_value);
	git__free(key);
	return ret;
507 508
}

509 510 511 512 513 514 515
/* release the map containing the entry as an equivalent to freeing it */
static void release_map(git_config_entry *entry)
{
	refcounted_strmap *map = (refcounted_strmap *) entry->payload;
	refcounted_strmap_free(map);
}

516 517 518
/*
 * Internal function that actually gets the value in string form
 */
519
static int config_get(git_config_backend *cfg, const char *key, git_config_entry **out)
520
{
521
	diskfile_header *h = (diskfile_header *)cfg;
522
	refcounted_strmap *map;
523 524
	git_strmap *values;
	khiter_t pos;
525
	cvar_t *var;
526
	int error = 0;
527

528
	if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0))
529 530
		return error;

531 532 533
	map = refcounted_strmap_take(h);
	values = map->values;

534
	pos = git_strmap_lookup_index(values, key);
535

536
	/* no error message; the config system will write one */
537 538
	if (!git_strmap_valid_index(values, pos)) {
		refcounted_strmap_free(map);
539
		return GIT_ENOTFOUND;
540
	}
541

542
	var = git_strmap_value_at(values, pos);
543 544 545 546
	while (var->next)
		var = var->next;

	*out = var->entry;
547 548 549 550
	(*out)->free = release_map;
	(*out)->payload = map;

	return error;
551 552
}

553
static int config_set_multivar(
Ben Straub committed
554
	git_config_backend *cfg, const char *name, const char *regexp, const char *value)
555 556
{
	diskfile_backend *b = (diskfile_backend *)cfg;
557 558
	refcounted_strmap *map;
	git_strmap *values;
559 560
	char *key;
	regex_t preg;
561
	int result;
562
	khiter_t pos;
563

564
	assert(regexp);
565

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

569 570 571
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

572 573
	pos = git_strmap_lookup_index(values, key);
	if (!git_strmap_valid_index(values, pos)) {
574 575
		/* If we don't have it, behave like a normal set */
		result = config_set(cfg, name, value);
576
		refcounted_strmap_free(map);
577
		git__free(key);
578
		return result;
579
	}
580

581
	result = regcomp(&preg, regexp, REG_EXTENDED);
582
	if (result != 0) {
583
		giterr_set_regex(&preg, result);
584 585
		result = -1;
		goto out;
586
	}
587

588 589 590
	/* If we do have it, set call config_write() and reload */
	if ((result = config_write(b, key, &preg, value)) < 0)
		goto out;
591

592
	result = config_refresh(cfg);
593

594
out:
595
	refcounted_strmap_free(map);
596
	git__free(key);
597
	regfree(&preg);
598 599

	return result;
600 601
}

Ben Straub committed
602
static int config_delete(git_config_backend *cfg, const char *name)
603
{
604
	cvar_t *var;
605
	diskfile_backend *b = (diskfile_backend *)cfg;
606
	refcounted_strmap *map;	git_strmap *values;
607
	char *key;
608
	int result;
609
	khiter_t pos;
610

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

614 615 616
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

617
	pos = git_strmap_lookup_index(values, key);
618
	git__free(key);
619

620
	if (!git_strmap_valid_index(values, pos)) {
621
		refcounted_strmap_free(map);
622
		giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
623
		return GIT_ENOTFOUND;
624
	}
625

626
	var = git_strmap_value_at(values, pos);
627
	refcounted_strmap_free(map);
628

629 630 631 632
	if (var->next != NULL) {
		giterr_set(GITERR_CONFIG, "Cannot delete multivar with a single delete");
		return -1;
	}
633

634 635
	if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0)
		return result;
636

637
	return config_refresh(cfg);
638 639
}

640 641 642
static int config_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
	diskfile_backend *b = (diskfile_backend *)cfg;
643 644
	refcounted_strmap *map;
	git_strmap *values;
645 646 647 648 649 650 651 652
	char *key;
	regex_t preg;
	int result;
	khiter_t pos;

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

653 654 655
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

656
	pos = git_strmap_lookup_index(values, key);
657

658
	if (!git_strmap_valid_index(values, pos)) {
659
		refcounted_strmap_free(map);
660
		git__free(key);
661
		giterr_set(GITERR_CONFIG, "Could not find key '%s' to delete", name);
662 663 664
		return GIT_ENOTFOUND;
	}

665 666
	refcounted_strmap_free(map);

667
	result = regcomp(&preg, regexp, REG_EXTENDED);
668
	if (result != 0) {
669
		giterr_set_regex(&preg, result);
670 671
		result = -1;
		goto out;
672 673
	}

674 675
	if ((result = config_write(b, key, &preg, NULL)) < 0)
		goto out;
676

677
	result = config_refresh(cfg);
678

679
out:
680
	git__free(key);
681
	regfree(&preg);
682 683 684
	return result;
}

685 686 687 688 689 690 691
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);
}

692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
static int config_lock(git_config_backend *_cfg)
{
	diskfile_backend *cfg = (diskfile_backend *) _cfg;
	int error;

	if ((error = git_filebuf_open(&cfg->locked_buf, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0)
		return error;

	error = git_futils_readbuffer(&cfg->locked_content, cfg->file_path);
	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);
	git_buf_free(&cfg->locked_content);
	cfg->locked = false;

	return error;
}

Ben Straub committed
728
int git_config_file__ondisk(git_config_backend **out, const char *path)
729
{
730
	diskfile_backend *backend;
731

732
	backend = git__calloc(1, sizeof(diskfile_backend));
733
	GITERR_CHECK_ALLOC(backend);
734

735
	backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
736
	git_mutex_init(&backend->header.values_mutex);
737 738

	backend->file_path = git__strdup(path);
739
	GITERR_CHECK_ALLOC(backend->file_path);
740

741 742 743 744 745 746 747 748
	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;
749 750
	backend->header.parent.lock = config_lock;
	backend->header.parent.unlock = config_unlock;
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
	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();
}

795 796 797 798 799 800 801 802 803 804 805 806 807 808 809
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();
}

810 811 812 813 814 815 816
static void backend_readonly_free(git_config_backend *_backend)
{
	diskfile_backend *backend = (diskfile_backend *)_backend;

	if (backend == NULL)
		return;

817
	refcounted_strmap_free(backend->header.values);
818
	git_mutex_free(&backend->header.values_mutex);
819 820 821 822 823 824 825
	git__free(backend);
}

static int config_readonly_open(git_config_backend *cfg, git_config_level_t level)
{
	diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg;
	diskfile_backend *src = b->snapshot_from;
826
	diskfile_header *src_header = &src->header;
827
	refcounted_strmap *src_map;
828 829
	int error;

830
	if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0)
831
		return error;
832 833 834 835

	/* We're just copying data, don't care about the level */
	GIT_UNUSED(level);

836
	src_map = refcounted_strmap_take(src_header);
837
	b->header.values = src_map;
838

839
	return 0;
840 841 842 843 844 845 846 847 848 849
}

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;
850
	git_mutex_init(&backend->header.values_mutex);
851 852 853

	backend->snapshot_from = in;

854
	backend->header.parent.readonly = 1;
855 856 857 858 859 860 861 862
	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;
863 864
	backend->header.parent.lock = config_lock_readonly;
	backend->header.parent.unlock = config_unlock_readonly;
865
	backend->header.parent.free = backend_readonly_free;
866

Ben Straub committed
867
	*out = (git_config_backend *)backend;
868

869
	return 0;
870 871
}

872
static int reader_getchar_raw(struct reader *reader)
873 874 875
{
	int c;

876
	c = *reader->read_ptr++;
877 878 879 880 881

	/*
	Win 32 line breaks: if we find a \r\n sequence,
	return only the \n as a newline
	*/
882 883
	if (c == '\r' && *reader->read_ptr == '\n') {
		reader->read_ptr++;
884 885 886 887
		c = '\n';
	}

	if (c == '\n')
888
		reader->line_number++;
889 890

	if (c == 0) {
891
		reader->eof = 1;
892
		c = '\0';
893 894 895 896 897 898 899 900
	}

	return c;
}

#define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2)

901
static int reader_getchar(struct reader *reader, int flags)
902 903 904 905 906
{
	const int skip_whitespace = (flags & SKIP_WHITESPACE);
	const int skip_comments = (flags & SKIP_COMMENTS);
	int c;

907
	assert(reader->read_ptr);
908

909 910
	do {
		c = reader_getchar_raw(reader);
911
	} while (c != '\n' && c != '\0' && skip_whitespace && git__isspace(c));
912 913

	if (skip_comments && (c == '#' || c == ';')) {
914 915
		do {
			c = reader_getchar_raw(reader);
916
		} while (c != '\n' && c != '\0');
917 918 919 920 921 922 923 924
	}

	return c;
}

/*
 * Read the next char, but don't move the reading pointer.
 */
925
static int reader_peek(struct reader *reader, int flags)
926 927 928 929 930
{
	void *old_read_ptr;
	int old_lineno, old_eof;
	int ret;

931
	assert(reader->read_ptr);
932

933 934 935
	old_read_ptr = reader->read_ptr;
	old_lineno = reader->line_number;
	old_eof = reader->eof;
936

937
	ret = reader_getchar(reader, flags);
938

939 940 941
	reader->read_ptr = old_read_ptr;
	reader->line_number = old_lineno;
	reader->eof = old_eof;
942 943 944 945 946 947 948

	return ret;
}

/*
 * Read and consume a line, returning it in newly-allocated memory.
 */
949
static char *reader_readline(struct reader *reader, bool skip_whitespace)
950 951 952
{
	char *line = NULL;
	char *line_src, *line_end;
953
	size_t line_len, alloc_len;
954

955
	line_src = reader->read_ptr;
956

957 958
	if (skip_whitespace) {
		/* Skip empty empty lines */
959
		while (git__isspace(*line_src))
960 961
			++line_src;
	}
962

963
	line_end = strchr(line_src, '\n');
964

965
	/* no newline at EOF */
966 967 968
	if (line_end == NULL)
		line_end = strchr(line_src, 0);

969
	line_len = line_end - line_src;
970

971 972
	if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, line_len, 1) ||
		(line = git__malloc(alloc_len)) == NULL) {
973
		return NULL;
974
	}
975

976
	memcpy(line, line_src, line_len);
977

978
	do line[line_len] = '\0';
979
	while (line_len-- > 0 && git__isspace(line[line_len]));
980 981 982 983 984

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
985
		reader->eof = 1;
986

987 988
	reader->line_number++;
	reader->read_ptr = line_end;
989 990 991 992 993 994 995

	return line;
}

/*
 * Consume a line, without storing it anywhere
 */
996
static void reader_consume_line(struct reader *reader)
997 998 999
{
	char *line_start, *line_end;

1000
	line_start = reader->read_ptr;
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
	line_end = strchr(line_start, '\n');
	/* No newline at EOF */
	if(line_end == NULL){
		line_end = strchr(line_start, '\0');
	}

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
1011
		reader->eof = 1;
1012

1013 1014
	reader->line_number++;
	reader->read_ptr = line_end;
1015 1016
}

1017
GIT_INLINE(int) config_keychar(int c)
1018 1019 1020 1021
{
	return isalnum(c) || c == '-';
}

1022
static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
1023
{
1024 1025 1026
	int c, rpos;
	char *first_quote, *last_quote;
	git_buf buf = GIT_BUF_INIT;
1027
	size_t quoted_len, alloc_len, base_name_len = strlen(base_name);
1028

1029 1030 1031 1032 1033 1034 1035 1036
	/*
	 * base_name is what came before the space. We should be at the
	 * first quotation mark, except for now, line isn't being kept in
	 * sync so we only really use it to calculate the length.
	 */

	first_quote = strchr(line, '"');
	last_quote = strrchr(line, '"');
1037
	quoted_len = last_quote - first_quote;
1038

1039
	if (quoted_len == 0) {
1040
		set_parse_error(reader, 0, "Missing closing quotation mark in section header");
1041 1042
		return -1;
	}
1043

1044 1045
	GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
1046

1047
	git_buf_grow(&buf, alloc_len);
1048
	git_buf_printf(&buf, "%s.", base_name);
1049 1050 1051 1052

	rpos = 0;

	line = first_quote;
1053
	c = line[++rpos];
1054 1055 1056 1057 1058 1059

	/*
	 * At the end of each iteration, whatever is stored in c will be
	 * added to the string. In case of error, jump to out
	 */
	do {
1060

1061
		switch (c) {
1062
		case 0:
1063
			set_parse_error(reader, 0, "Unexpected end-of-line in section header");
1064 1065 1066
			git_buf_free(&buf);
			return -1;

1067
		case '"':
1068
			goto end_parse;
1069

1070
		case '\\':
1071
			c = line[++rpos];
1072

1073 1074
			if (c == 0) {
				set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
1075 1076
				git_buf_free(&buf);
				return -1;
1077
			}
1078

1079 1080 1081 1082
		default:
			break;
		}

Linquize committed
1083
		git_buf_putc(&buf, (char)c);
1084 1085 1086 1087 1088 1089 1090 1091 1092
		c = line[++rpos];
	} while (line + rpos < last_quote);

end_parse:
	if (line[rpos] != '"' || line[rpos + 1] != ']') {
		set_parse_error(reader, rpos, "Unexpected text after closing quotes");
		git_buf_free(&buf);
		return -1;
	}
1093

1094 1095
	*section_name = git_buf_detach(&buf);
	return 0;
1096 1097
}

1098
static int parse_section_header(struct reader *reader, char **section_out)
1099 1100 1101
{
	char *name, *name_end;
	int name_length, c, pos;
1102
	int result;
1103
	char *line;
1104
	size_t line_len;
1105

1106
	line = reader_readline(reader, true);
1107
	if (line == NULL)
1108
		return -1;
1109 1110

	/* find the end of the variable's name */
1111
	name_end = strrchr(line, ']');
schu committed
1112
	if (name_end == NULL) {
1113
		git__free(line);
1114
		set_parse_error(reader, 0, "Missing ']' in section header");
1115
		return -1;
schu committed
1116
	}
1117

1118
	GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
1119
	name = git__malloc(line_len);
1120
	GITERR_CHECK_ALLOC(name);
1121 1122 1123 1124 1125 1126

	name_length = 0;
	pos = 0;

	/* Make sure we were given a section header */
	c = line[pos++];
1127
	assert(c == '[');
1128 1129 1130 1131

	c = line[pos++];

	do {
1132
		if (git__isspace(c)){
1133
			name[name_length] = '\0';
1134
			result = parse_section_header_ext(reader, line, name, section_out);
1135 1136
			git__free(line);
			git__free(name);
1137
			return result;
1138 1139 1140
		}

		if (!config_keychar(c) && c != '.') {
1141
			set_parse_error(reader, pos, "Unexpected character in header");
1142
			goto fail_parse;
1143 1144
		}

1145
		name[name_length++] = (char)git__tolower(c);
1146 1147 1148

	} while ((c = line[pos++]) != ']');

schu committed
1149
	if (line[pos - 1] != ']') {
1150
		set_parse_error(reader, pos, "Unexpected end of file");
1151
		goto fail_parse;
schu committed
1152
	}
1153

1154
	git__free(line);
1155 1156

	name[name_length] = 0;
1157 1158
	*section_out = name;

1159 1160 1161
	return 0;

fail_parse:
1162 1163
	git__free(line);
	git__free(name);
1164
	return -1;
1165 1166
}

1167
static int skip_bom(struct reader *reader)
1168
{
1169 1170
	git_bom_t bom;
	int bom_offset = git_buf_text_detect_bom(&bom,
1171
		&reader->buffer, reader->read_ptr - reader->buffer.ptr);
1172

1173
	if (bom == GIT_BOM_UTF8)
1174
		reader->read_ptr += bom_offset;
1175

1176
	/* TODO: reference implementation is pretty stupid with BoM */
1177

1178
	return 0;
1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219
}

/*
	(* basic types *)
	digit = "0".."9"
	integer = digit { digit }
	alphabet = "a".."z" + "A" .. "Z"

	section_char = alphabet | "." | "-"
	extension_char = (* any character except newline *)
	any_char = (* any character *)
	variable_char = "alphabet" | "-"


	(* actual grammar *)
	config = { section }

	section = header { definition }

	header = "[" section [subsection | subsection_ext] "]"

	subsection = "." section
	subsection_ext = "\"" extension "\""

	section = section_char { section_char }
	extension = extension_char { extension_char }

	definition = variable_name ["=" variable_value] "\n"

	variable_name = variable_char { variable_char }
	variable_value = string | boolean | integer

	string = quoted_string | plain_string
	quoted_string = "\"" plain_string "\""
	plain_string = { any_char }

	boolean = boolean_true | boolean_false
	boolean_true = "yes" | "1" | "true" | "on"
	boolean_false = "no" | "0" | "false" | "off"
*/

1220
static int strip_comments(char *line, int in_quotes)
1221
{
1222
	int quote_count = in_quotes, backslash_count = 0;
1223 1224 1225 1226 1227 1228
	char *ptr;

	for (ptr = line; *ptr; ++ptr) {
		if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
			quote_count++;

1229 1230 1231
		if ((ptr[0] == ';' || ptr[0] == '#') &&
			(quote_count % 2) == 0 &&
			(backslash_count % 2) == 0) {
1232 1233 1234
			ptr[0] = '\0';
			break;
		}
1235 1236 1237 1238 1239

		if (ptr[0] == '\\')
			backslash_count++;
		else
			backslash_count = 0;
1240 1241
	}

1242
	/* skip any space at the end */
1243
	while (ptr > line && git__isspace(ptr[-1])) {
1244
		ptr--;
1245
	}
1246 1247 1248
	ptr[0] = '\0';

	return quote_count;
1249 1250
}

1251 1252 1253 1254
static int included_path(git_buf *out, const char *dir, const char *path)
{
	/* From the user's home */
	if (path[0] == '~' && path[1] == '/')
1255
		return git_sysdir_find_global_file(out, &path[1]);
1256 1257 1258 1259

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

1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272
static const char *escapes = "ntb\"\\";
static const char *escaped = "\n\t\b\"\\";

/* Escape the values to write them to the file */
static char *escape_value(const char *ptr)
{
	git_buf buf = GIT_BUF_INIT;
	size_t len;
	const char *esc;

	assert(ptr);

	len = strlen(ptr);
1273 1274 1275
	if (!len)
		return git__calloc(1, sizeof(char));

1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
	git_buf_grow(&buf, len);

	while (*ptr != '\0') {
		if ((esc = strchr(escaped, *ptr)) != NULL) {
			git_buf_putc(&buf, '\\');
			git_buf_putc(&buf, escapes[esc - escaped]);
		} else {
			git_buf_putc(&buf, *ptr);
		}
		ptr++;
	}

	if (git_buf_oom(&buf)) {
		git_buf_free(&buf);
		return NULL;
	}

	return git_buf_detach(&buf);
}

1296
/* '\"' -> '"' etc */
1297 1298
static int unescape_line(
	char **out, bool *is_multi, const char *ptr, int quote_count)
1299
{
1300
	char *str, *fixed, *esc;
1301
	size_t ptr_len = strlen(ptr), alloc_len;
1302

1303 1304
	*is_multi = false;

1305 1306
	if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) ||
		(str = git__malloc(alloc_len)) == NULL) {
1307
		return -1;
1308 1309
	}

1310
	fixed = str;
1311

1312 1313 1314 1315
	while (*ptr != '\0') {
		if (*ptr == '"') {
			quote_count++;
		} else if (*ptr != '\\') {
1316
			*fixed++ = *ptr;
1317 1318 1319 1320 1321
		} else {
			/* backslash, check the next char */
			ptr++;
			/* if we're at the end, it's a multiline, so keep the backslash */
			if (*ptr == '\0') {
1322 1323
				*is_multi = true;
				goto done;
1324 1325
			}
			if ((esc = strchr(escapes, *ptr)) != NULL) {
1326
				*fixed++ = escaped[esc - escapes];
1327 1328 1329
			} else {
				git__free(str);
				giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
1330
				return -1;
1331 1332 1333 1334 1335
			}
		}
		ptr++;
	}

1336 1337 1338
done:
	*fixed = '\0';
	*out = str;
1339

1340
	return 0;
1341 1342
}

1343
static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
1344
{
1345 1346
	char *line = NULL, *proc_line = NULL;
	int quote_count;
1347
	bool multiline;
1348 1349

	/* Check that the next line exists */
1350
	line = reader_readline(reader, false);
1351
	if (line == NULL)
1352
		return -1;
1353

1354 1355 1356
	/* We've reached the end of the file, there is no continuation.
	 * (this is not an error).
	 */
1357
	if (line[0] == '\0') {
1358
		git__free(line);
1359
		return 0;
1360 1361
	}

1362
	quote_count = strip_comments(line, !!in_quotes);
1363 1364 1365

	/* If it was just a comment, pretend it didn't exist */
	if (line[0] == '\0') {
1366
		git__free(line);
1367
		return parse_multiline_variable(reader, value, quote_count);
1368
		/* TODO: unbounded recursion. This **could** be exploitable */
1369 1370
	}

1371
	if (unescape_line(&proc_line, &multiline, line, in_quotes) < 0) {
1372 1373
		git__free(line);
		return -1;
1374
	}
1375
	/* add this line to the multiline var */
1376

1377
	git_buf_puts(value, proc_line);
1378
	git__free(line);
1379
	git__free(proc_line);
1380 1381

	/*
1382 1383
	 * If we need to continue reading the next line, let's just
	 * keep putting stuff in the buffer
1384
	 */
1385
	if (multiline)
1386
		return parse_multiline_variable(reader, value, quote_count);
1387

1388
	return 0;
1389 1390
}

1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429
GIT_INLINE(bool) is_namechar(char c)
{
	return isalnum(c) || c == '-';
}

static int parse_name(
	char **name, const char **value, struct reader *reader, const char *line)
{
	const char *name_end = line, *value_start;

	*name = NULL;
	*value = NULL;

	while (*name_end && is_namechar(*name_end))
		name_end++;

	if (line == name_end) {
		set_parse_error(reader, 0, "Invalid configuration key");
		return -1;
	}

	value_start = name_end;

	while (*value_start && git__isspace(*value_start))
		value_start++;

	if (*value_start == '=') {
		*value = value_start + 1;
	} else if (*value_start) {
		set_parse_error(reader, 0, "Invalid configuration key");
		return -1;
	}

	if ((*name = git__strndup(line, name_end - line)) == NULL)
		return -1;

	return 0;
}

1430
static int parse_variable(struct reader *reader, char **var_name, char **var_value)
1431 1432 1433
{
	const char *value_start = NULL;
	char *line;
1434
	int quote_count;
1435
	bool multiline;
1436

1437
	line = reader_readline(reader, true);
1438
	if (line == NULL)
1439
		return -1;
1440

1441
	quote_count = strip_comments(line, 0);
1442

1443 1444
	/* If there is no value, boolean true is assumed */
	*var_value = NULL;
1445

1446 1447 1448
	if (parse_name(var_name, &value_start, reader, line) < 0)
		goto on_error;

1449 1450 1451 1452
	/*
	 * Now, let's try to parse the value
	 */
	if (value_start != NULL) {
1453
		while (git__isspace(value_start[0]))
1454 1455
			value_start++;

1456 1457 1458 1459
		if (unescape_line(var_value, &multiline, value_start, 0) < 0)
			goto on_error;

		if (multiline) {
1460
			git_buf multi_value = GIT_BUF_INIT;
1461 1462 1463 1464
			git_buf_attach(&multi_value, *var_value, 0);

			if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 ||
				git_buf_oom(&multi_value)) {
1465
				git_buf_free(&multi_value);
1466
				goto on_error;
1467
			}
1468

1469
			*var_value = git_buf_detach(&multi_value);
1470 1471 1472
		}
	}

1473
	git__free(line);
1474
	return 0;
1475 1476 1477 1478 1479

on_error:
	git__free(*var_name);
	git__free(line);
	return -1;
1480
}
1481 1482 1483

static int config_parse(
	struct reader *reader,
1484 1485 1486
	int (*on_section)(struct reader **reader, const char *current_section, const char *line, size_t line_len, void *data),
	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),
	int (*on_comment)(struct reader **reader, const char *line, size_t line_len, void *data),
1487 1488 1489
	int (*on_eof)(struct reader **reader, void *data),
	void *data)
{
1490
	char *current_section = NULL, *var_name, *var_value, *line_start;
1491
	char c;
1492
	size_t line_len;
1493 1494 1495 1496 1497
	int result = 0;

	skip_bom(reader);

	while (result == 0 && !reader->eof) {
1498 1499
		line_start = reader->read_ptr;

1500 1501 1502
		c = reader_peek(reader, SKIP_WHITESPACE);

		switch (c) {
1503
		case '\0': /* EOF when peeking, set EOF in the reader to exit the loop */
1504 1505 1506 1507 1508 1509 1510
			reader->eof = 1;
			break;

		case '[': /* section header, new section begins */
			git__free(current_section);
			current_section = NULL;

1511 1512 1513 1514
			if ((result = parse_section_header(reader, &current_section)) == 0 && on_section) {
				line_len = reader->read_ptr - line_start;
				result = on_section(&reader, current_section, line_start, line_len, data);
			}
1515 1516
			break;

1517
		case '\n': /* comment or whitespace-only */
1518 1519 1520
		case ';':
		case '#':
			reader_consume_line(reader);
1521 1522 1523 1524 1525

			if (on_comment) {
				line_len = reader->read_ptr - line_start;
				result = on_comment(&reader, line_start, line_len, data);
			}
1526 1527 1528
			break;

		default: /* assume variable declaration */
1529 1530 1531 1532
			if ((result = parse_variable(reader, &var_name, &var_value)) == 0 && on_variable) {
				line_len = reader->read_ptr - line_start;
				result = on_variable(&reader, current_section, var_name, var_value, line_start, line_len, data);
			}
1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551
			break;
		}
	}

	if (on_eof)
		result = on_eof(&reader, data);

	git__free(current_section);
	return result;
}

struct parse_data {
	git_strmap *values;
	diskfile_backend *cfg_file;
	uint32_t reader_idx;
	git_config_level_t level;
	int depth;
};

1552 1553 1554 1555 1556 1557 1558 1559
static int read_on_variable(
	struct reader **reader,
	const char *current_section,
	char *var_name,
	char *var_value,
	const char *line,
	size_t line_len,
	void *data)
1560 1561 1562 1563 1564 1565
{
	struct parse_data *parse_data = (struct parse_data *)data;
	git_buf buf = GIT_BUF_INIT;
	cvar_t *var;
	int result = 0;

1566 1567 1568
	GIT_UNUSED(line);
	GIT_UNUSED(line_len);

1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660
	git__strtolower(var_name);
	git_buf_printf(&buf, "%s.%s", current_section, var_name);
	git__free(var_name);

	if (git_buf_oom(&buf)) {
		git__free(var_value);
		return -1;
	}

	var = git__calloc(1, sizeof(cvar_t));
	GITERR_CHECK_ALLOC(var);
	var->entry = git__calloc(1, sizeof(git_config_entry));
	GITERR_CHECK_ALLOC(var->entry);

	var->entry->name = git_buf_detach(&buf);
	var->entry->value = var_value;
	var->entry->level = parse_data->level;
	var->included = !!parse_data->depth;

	if ((result = append_entry(parse_data->values, var)) < 0)
		return result;

	result = 0;

	/* Add or append the new config option */
	if (!git__strcmp(var->entry->name, "include.path")) {
		struct reader *r;
		git_buf path = GIT_BUF_INIT;
		char *dir;
		uint32_t index;

		r = git_array_alloc(parse_data->cfg_file->readers);
		/* The reader may have been reallocated */
		*reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
		memset(r, 0, sizeof(struct reader));

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

		/* We need to know our index in the array, as the next config_parse call may realloc */
		index = git_array_size(parse_data->cfg_file->readers) - 1;
		dir = git_buf_detach(&path);
		result = included_path(&path, dir, var->entry->value);
		git__free(dir);

		if (result < 0)
			return result;

		r->file_path = git_buf_detach(&path);
		git_buf_init(&r->buffer, 0);

		result = git_futils_readbuffer_updated(
			&r->buffer, r->file_path, &r->file_mtime, &r->file_size, NULL);

		if (result == 0) {
			result = config_read(parse_data->values, parse_data->cfg_file, r, parse_data->level, parse_data->depth+1);
			r = git_array_get(parse_data->cfg_file->readers, index);
			*reader = git_array_get(parse_data->cfg_file->readers, parse_data->reader_idx);
		} else if (result == GIT_ENOTFOUND) {
			giterr_clear();
			result = 0;
		}

		git_buf_free(&r->buffer);
	}

	return result;
}

static int config_read(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
{
	struct parse_data parse_data;

	if (depth >= MAX_INCLUDE_DEPTH) {
		giterr_set(GITERR_CONFIG, "Maximum config include depth reached");
		return -1;
	}

	/* Initialize the reading position */
	reader->read_ptr = reader->buffer.ptr;
	reader->eof = 0;

	/* If the file is empty, there's nothing for us to do */
	if (*reader->read_ptr == '\0')
		return 0;

	parse_data.values = values;
	parse_data.cfg_file = cfg_file;
	parse_data.reader_idx = git_array_size(cfg_file->readers) - 1;
	parse_data.level = level;
	parse_data.depth = depth;

1661
	return config_parse(reader, NULL, read_on_variable, NULL, NULL, &parse_data);
1662 1663
}

1664
static int write_section(git_buf *fbuf, const char *key)
1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687
{
	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;

1688
	result = git_buf_put(fbuf, git_buf_cstr(&buf), buf.size);
1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712
	git_buf_free(&buf);

	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 {
1713
	git_buf *buf;
1714
	git_buf buffered_comment;
1715 1716 1717 1718 1719 1720 1721 1722
	unsigned int in_section : 1,
		preg_replaced : 1;
	const char *section;
	const char *name;
	const regex_t *preg;
	const char *value;
};

1723
static int write_line_to(git_buf *buf, const char *line, size_t line_len)
1724
{
1725
	int result = git_buf_put(buf, line, line_len);
1726 1727

	if (!result && line_len && line[line_len-1] != '\n')
1728
		result = git_buf_printf(buf, "\n");
1729 1730 1731 1732

	return result;
}

1733 1734 1735 1736 1737
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);
}

1738 1739 1740 1741 1742 1743
static int write_value(struct write_data *write_data)
{
	const char *q;
	int result;

	q = quotes_for_value(write_data->value);
1744
	result = git_buf_printf(write_data->buf,
1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756
		"\t%s = %s%s%s\n", write_data->name, q, write_data->value, q);

	/* 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;
}

1757 1758 1759 1760 1761 1762
static int write_on_section(
	struct reader **reader,
	const char *current_section,
	const char *line,
	size_t line_len,
	void *data)
1763 1764 1765 1766
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

1767 1768
	GIT_UNUSED(reader);

1769 1770 1771 1772 1773 1774 1775 1776 1777 1778
	/* 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;

1779 1780 1781 1782 1783 1784 1785 1786
	/*
	 * 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);
	}

1787
	if (!result)
1788
		result = write_line(write_data, line, line_len);
1789 1790 1791 1792

	return result;
}

1793 1794 1795 1796 1797 1798 1799 1800
static int write_on_variable(
	struct reader **reader,
	const char *current_section,
	char *var_name,
	char *var_value,
	const char *line,
	size_t line_len,
	void *data)
1801 1802 1803
{
	struct write_data *write_data = (struct write_data *)data;
	bool has_matched = false;
1804
	int error;
1805 1806 1807

	GIT_UNUSED(reader);
	GIT_UNUSED(current_section);
1808

1809 1810 1811 1812 1813 1814 1815 1816
	/*
	 * 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);

1817 1818 1819 1820 1821 1822 1823 1824 1825
	/* 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);

1826 1827
	git__free(var_name);
	git__free(var_value);
1828 1829 1830 1831

	/* If this isn't the name/value we're looking for, simply dump the
	 * existing data back out and continue on.
	 */
1832 1833
	if (!has_matched)
		return write_line(write_data, line, line_len);
1834 1835 1836 1837 1838 1839 1840 1841 1842 1843

	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);
}

1844 1845
static int write_on_comment(struct reader **reader, const char *line, size_t line_len, void *data)
{
1846 1847 1848 1849 1850
	struct write_data *write_data;

	GIT_UNUSED(reader);

	write_data = (struct write_data *)data;
1851
	return write_line_to(&write_data->buffered_comment, line, line_len);
1852 1853
}

1854 1855 1856 1857 1858
static int write_on_eof(struct reader **reader, void *data)
{
	struct write_data *write_data = (struct write_data *)data;
	int result = 0;

1859 1860
	GIT_UNUSED(reader);

1861 1862 1863 1864 1865 1866
	/*
	 * 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;

1867 1868 1869 1870 1871 1872
	/* 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) {
1873
		if ((result = write_section(write_data->buf, write_data->section)) == 0)
1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885
			result = write_value(write_data);
	}

	return result;
}

/*
 * This is pretty much the parsing, except we write out anything we don't have
 */
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
{
	int result;
1886
	char *section, *name, *ldot;
1887
	git_filebuf file = GIT_FILEBUF_INIT;
1888
	git_buf buf = GIT_BUF_INIT;
1889 1890 1891
	struct reader *reader = git_array_get(cfg->readers, 0);
	struct write_data write_data;

1892 1893 1894 1895 1896 1897
	if (cfg->locked) {
		result = git_buf_puts(&reader->buffer, git_buf_cstr(&cfg->locked_content));
	} else {
		/* Lock the file */
		if ((result = git_filebuf_open(
			     &file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) {
1898 1899
			git_buf_free(&reader->buffer);
			return result;
1900
		}
1901

1902 1903 1904
		/* We need to read in our own config file */
		result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
	}
1905 1906 1907 1908 1909 1910 1911 1912 1913 1914

	/* Initialise the reading position */
	if (result == GIT_ENOTFOUND) {
		reader->read_ptr = NULL;
		reader->eof = 1;
		git_buf_clear(&reader->buffer);
	} else if (result == 0) {
		reader->read_ptr = reader->buffer.ptr;
		reader->eof = 0;
	} else {
1915
		git_filebuf_cleanup(&file);
1916 1917 1918 1919 1920 1921 1922
		return -1; /* OS error when reading the file */
	}

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

1923
	write_data.buf = &buf;
1924
	git_buf_init(&write_data.buffered_comment, 0);
1925 1926 1927 1928 1929 1930 1931
	write_data.section = section;
	write_data.in_section = 0;
	write_data.preg_replaced = 0;
	write_data.name = name;
	write_data.preg = preg;
	write_data.value = value;

1932 1933
	result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data);
	git__free(section);
1934
	git_buf_free(&write_data.buffered_comment);
1935 1936

	if (result < 0) {
1937 1938 1939 1940
		git_filebuf_cleanup(&file);
		goto done;
	}

1941 1942 1943 1944 1945 1946 1947
	if (cfg->locked) {
		size_t len = buf.asize;
		/* Update our copy with the modified contents */
		git_buf_free(&cfg->locked_content);
		git_buf_attach(&cfg->locked_content, git_buf_detach(&buf), len);
	} else {
		git_filebuf_write(&file, git_buf_cstr(&buf), git_buf_len(&buf));
1948

1949 1950
		/* refresh stats - if this errors, then commit will error too */
		(void)git_filebuf_stats(&reader->file_mtime, &reader->file_size, &file);
1951

1952 1953
		result = git_filebuf_commit(&file);
	}
1954 1955

done:
1956
	git_buf_free(&buf);
1957 1958 1959 1960
	git_buf_free(&reader->buffer);
	return result;
}