config_file.c 41.1 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
	char  *file_path;
109
} diskfile_backend;
110

111 112 113 114 115 116
typedef struct {
	diskfile_header header;

	diskfile_backend *snapshot_from;
} diskfile_readonly_backend;

117
static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth);
118
static int parse_variable(struct reader *reader, char **var_name, char **var_value);
119
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char *value);
120
static char *escape_value(const char *ptr);
121

122
int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in);
123
static int config_snapshot(git_config_backend **out, git_config_backend *in);
124

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

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

137
static void cvar_free(cvar_t *var)
138 139 140 141
{
	if (var == NULL)
		return;

142 143 144
	git__free((char*)var->entry->name);
	git__free((char *)var->entry->value);
	git__free(var->entry);
145
	git__free(var);
146 147
}

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

	if (scan == start)
		return GIT_EINVALIDSPEC;

	return 0;
}

171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
/* 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;
}

195
static void free_vars(git_strmap *values)
196
{
197
	cvar_t *var = NULL;
198

199 200
	if (values == NULL)
		return;
201

202
	git_strmap_foreach_value(values, var,
203 204 205 206 207
		while (var != NULL) {
			cvar_t *next = CVAR_LIST_NEXT(var);
			cvar_free(var);
			var = next;
		});
208

209
	git_strmap_free(values);
210 211
}

212 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
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));
249
	GITERR_CHECK_ALLOC(map);
250 251

	git_atomic_set(&map->refcount, 1);
252 253

	if ((error = git_strmap_alloc(&map->values)) < 0)
254
		git__free(map);
255 256
	else
		*out = map;
257 258 259 260

	return error;
}

261
static int config_open(git_config_backend *cfg, git_config_level_t level)
262
{
263
	int res;
264
	struct reader *reader;
265
	diskfile_backend *b = (diskfile_backend *)cfg;
266

267 268
	b->level = level;

269
	if ((res = refcounted_strmap_alloc(&b->header.values)) < 0)
270
		return res;
271

272 273
	git_array_init(b->readers);
	reader = git_array_alloc(b->readers);
274
	if (!reader) {
275
		refcounted_strmap_free(b->header.values);
276 277
		return -1;
	}
278 279 280 281 282 283
	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);
284
	res = git_futils_readbuffer_updated(
285
		&reader->buffer, b->file_path, &reader->file_mtime, &reader->file_size, NULL);
286

287
	/* It's fine if the file doesn't exist */
288 289 290
	if (res == GIT_ENOTFOUND)
		return 0;

291 292
	if (res < 0 || (res = config_parse(b->header.values->values, b, reader, level, 0)) < 0) {
		refcounted_strmap_free(b->header.values);
293
		b->header.values = NULL;
294
	}
295

296
	reader = git_array_get(b->readers, 0);
297
	git_buf_free(&reader->buffer);
298

299 300 301
	return res;
}

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

310
	if ((error = refcounted_strmap_alloc(&values)) < 0)
311 312 313
		goto out;

	reader = git_array_get(b->readers, git_array_size(b->readers) - 1);
314
	GITERR_CHECK_ALLOC(reader);
315

316
	if ((error = config_parse(values->values, b, reader, b->level, 0)) < 0)
317 318
		goto out;

319 320 321 322 323 324 325
	git_mutex_lock(&b->header.values_mutex);

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

	git_mutex_unlock(&b->header.values_mutex);
326 327

out:
328
	refcounted_strmap_free(values);
329 330
	if (reader)
		git_buf_free(&reader->buffer);
331 332 333
	return error;
}

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

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

347 348
		if (error < 0 && error != GIT_ENOTFOUND)
			return error;
349 350 351 352 353 354

		if (updated)
			any_updated = 1;
	}

	if (!any_updated)
355
		return (error == GIT_ENOTFOUND) ? 0 : error;
356

357
	return config__refresh(cfg);
358 359
}

Ben Straub committed
360
static void backend_free(git_config_backend *_backend)
361
{
362
	diskfile_backend *backend = (diskfile_backend *)_backend;
363
	uint32_t i;
364 365 366 367

	if (backend == NULL)
		return;

368 369 370 371 372 373
	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);

374
	git__free(backend->file_path);
375
	refcounted_strmap_free(backend->header.values);
376
	git_mutex_free(&backend->header.values_mutex);
377
	git__free(backend);
378 379
}

380
static void config_iterator_free(
381
	git_config_iterator* iter)
382
{
383
	iter->backend->free(iter->backend);
384 385
	git__free(iter);
}
386

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

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

403
	if (err < 0) {
404
		it->next_var = NULL;
405
		return err;
406
	}
407

408
	*entry = var->entry;
409
	it->next_var = CVAR_LIST_NEXT(var);
410

411 412
	return 0;
}
413

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

424 425 426 427 428 429 430
	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));
431 432
	GITERR_CHECK_ALLOC(it);

433 434
	h = (diskfile_header *)snapshot;

435 436 437
	/* strmap_begin() is currently a macro returning 0 */
	GIT_UNUSED(h);

438
	it->parent.backend = snapshot;
439
	it->iter = git_strmap_begin(h->values);
440 441 442 443 444
	it->next_var = NULL;

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

	return 0;
447 448
}

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

458
	if ((rval = git_config__normalize_name(name, &key)) < 0)
459
		return rval;
460

461 462 463
	map = refcounted_strmap_take(&b->header);
	values = map->values;

464
	/*
465 466
	 * Try to find it in the existing values and update it if it
	 * only has one value.
467
	 */
468 469 470
	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
471

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

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

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

489
	if (value) {
490 491
		esc_value = escape_value(value);
		GITERR_CHECK_ALLOC(esc_value);
492 493
	}

494 495
	if ((ret = config_write(b, key, NULL, esc_value)) < 0)
		goto out;
496

497
	ret = config_refresh(cfg);
498

499
out:
500
	refcounted_strmap_free(map);
501 502 503
	git__free(esc_value);
	git__free(key);
	return ret;
504 505
}

506 507 508 509 510 511 512
/* 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);
}

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

525
	if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0))
526 527
		return error;

528 529 530
	map = refcounted_strmap_take(h);
	values = map->values;

531
	pos = git_strmap_lookup_index(values, key);
532

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

539
	var = git_strmap_value_at(values, pos);
540 541 542 543
	while (var->next)
		var = var->next;

	*out = var->entry;
544 545 546 547
	(*out)->free = release_map;
	(*out)->payload = map;

	return error;
548 549
}

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

561
	assert(regexp);
562

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

566 567 568
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

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

578 579 580
	result = regcomp(&preg, regexp, REG_EXTENDED);
	if (result < 0) {
		giterr_set_regex(&preg, result);
581 582
		result = -1;
		goto out;
583
	}
584

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

589
	result = config_refresh(cfg);
590

591
out:
592
	refcounted_strmap_free(map);
593
	git__free(key);
594
	regfree(&preg);
595 596

	return result;
597 598
}

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

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

611 612 613
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

614
	pos = git_strmap_lookup_index(values, key);
615
	git__free(key);
616

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

623
	var = git_strmap_value_at(values, pos);
624
	refcounted_strmap_free(map);
625

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

631 632
	if ((result = config_write(b, var->entry->name, NULL, NULL)) < 0)
		return result;
633

634
	return config_refresh(cfg);
635 636
}

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

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

650 651 652
	map = refcounted_strmap_take(&b->header);
	values = b->header.values->values;

653
	pos = git_strmap_lookup_index(values, key);
654

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

662 663
	refcounted_strmap_free(map);

664 665 666
	result = regcomp(&preg, regexp, REG_EXTENDED);
	if (result < 0) {
		giterr_set_regex(&preg, result);
667 668
		result = -1;
		goto out;
669 670
	}

671 672
	if ((result = config_write(b, key, &preg, NULL)) < 0)
		goto out;
673

674
	result = config_refresh(cfg);
675

676
out:
677
	git__free(key);
678
	regfree(&preg);
679 680 681
	return result;
}

682 683 684 685 686 687 688
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);
}

Ben Straub committed
689
int git_config_file__ondisk(git_config_backend **out, const char *path)
690
{
691
	diskfile_backend *backend;
692

693
	backend = git__calloc(1, sizeof(diskfile_backend));
694
	GITERR_CHECK_ALLOC(backend);
695

696
	backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION;
697
	git_mutex_init(&backend->header.values_mutex);
698 699

	backend->file_path = git__strdup(path);
700
	GITERR_CHECK_ALLOC(backend->file_path);
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 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
	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;
	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();
}

static void backend_readonly_free(git_config_backend *_backend)
{
	diskfile_backend *backend = (diskfile_backend *)_backend;

	if (backend == NULL)
		return;

761
	refcounted_strmap_free(backend->header.values);
762
	git_mutex_free(&backend->header.values_mutex);
763 764 765 766 767 768 769
	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;
770
	diskfile_header *src_header = &src->header;
771
	refcounted_strmap *src_map;
772 773
	int error;

774
	if (!src_header->parent.readonly && (error = config_refresh(&src_header->parent)) < 0)
775
		return error;
776 777 778 779

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

780
	src_map = refcounted_strmap_take(src_header);
781
	b->header.values = src_map;
782

783
	return 0;
784 785 786 787 788 789 790 791 792 793
}

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;
794
	git_mutex_init(&backend->header.values_mutex);
795 796 797

	backend->snapshot_from = in;

798
	backend->header.parent.readonly = 1;
799 800 801 802 803 804 805 806 807
	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;
	backend->header.parent.free = backend_readonly_free;
808

Ben Straub committed
809
	*out = (git_config_backend *)backend;
810

811
	return 0;
812 813
}

814
static int reader_getchar_raw(struct reader *reader)
815 816 817
{
	int c;

818
	c = *reader->read_ptr++;
819 820 821 822 823

	/*
	Win 32 line breaks: if we find a \r\n sequence,
	return only the \n as a newline
	*/
824 825
	if (c == '\r' && *reader->read_ptr == '\n') {
		reader->read_ptr++;
826 827 828 829
		c = '\n';
	}

	if (c == '\n')
830
		reader->line_number++;
831 832

	if (c == 0) {
833
		reader->eof = 1;
834 835 836 837 838 839 840 841 842
		c = '\n';
	}

	return c;
}

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

843
static int reader_getchar(struct reader *reader, int flags)
844 845 846 847 848
{
	const int skip_whitespace = (flags & SKIP_WHITESPACE);
	const int skip_comments = (flags & SKIP_COMMENTS);
	int c;

849
	assert(reader->read_ptr);
850

851 852 853 854
	do {
		c = reader_getchar_raw(reader);
	} while (skip_whitespace && git__isspace(c) &&
	       !reader->eof);
855 856

	if (skip_comments && (c == '#' || c == ';')) {
857 858 859
		do {
			c = reader_getchar_raw(reader);
		} while (c != '\n');
860 861 862 863 864 865 866 867
	}

	return c;
}

/*
 * Read the next char, but don't move the reading pointer.
 */
868
static int reader_peek(struct reader *reader, int flags)
869 870 871 872 873
{
	void *old_read_ptr;
	int old_lineno, old_eof;
	int ret;

874
	assert(reader->read_ptr);
875

876 877 878
	old_read_ptr = reader->read_ptr;
	old_lineno = reader->line_number;
	old_eof = reader->eof;
879

880
	ret = reader_getchar(reader, flags);
881

882 883 884
	reader->read_ptr = old_read_ptr;
	reader->line_number = old_lineno;
	reader->eof = old_eof;
885 886 887 888 889 890 891

	return ret;
}

/*
 * Read and consume a line, returning it in newly-allocated memory.
 */
892
static char *reader_readline(struct reader *reader, bool skip_whitespace)
893 894 895
{
	char *line = NULL;
	char *line_src, *line_end;
896
	size_t line_len, alloc_len;
897

898
	line_src = reader->read_ptr;
899

900 901
	if (skip_whitespace) {
		/* Skip empty empty lines */
902
		while (git__isspace(*line_src))
903 904
			++line_src;
	}
905

906
	line_end = strchr(line_src, '\n');
907

908
	/* no newline at EOF */
909 910 911
	if (line_end == NULL)
		line_end = strchr(line_src, 0);

912
	line_len = line_end - line_src;
913

914 915
	if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, line_len, 1) ||
		(line = git__malloc(alloc_len)) == NULL) {
916
		return NULL;
917
	}
918

919
	memcpy(line, line_src, line_len);
920

921
	do line[line_len] = '\0';
922
	while (line_len-- > 0 && git__isspace(line[line_len]));
923 924 925 926 927

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

	if (*line_end == '\0')
928
		reader->eof = 1;
929

930 931
	reader->line_number++;
	reader->read_ptr = line_end;
932 933 934 935 936 937 938

	return line;
}

/*
 * Consume a line, without storing it anywhere
 */
939
static void reader_consume_line(struct reader *reader)
940 941 942
{
	char *line_start, *line_end;

943
	line_start = reader->read_ptr;
944 945 946 947 948 949 950 951 952 953
	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')
954
		reader->eof = 1;
955

956 957
	reader->line_number++;
	reader->read_ptr = line_end;
958 959
}

960
GIT_INLINE(int) config_keychar(int c)
961 962 963 964
{
	return isalnum(c) || c == '-';
}

965
static int parse_section_header_ext(struct reader *reader, const char *line, const char *base_name, char **section_name)
966
{
967 968 969
	int c, rpos;
	char *first_quote, *last_quote;
	git_buf buf = GIT_BUF_INIT;
970
	size_t quoted_len, alloc_len, base_name_len = strlen(base_name);
971

972 973 974 975 976 977 978 979
	/*
	 * 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, '"');
980
	quoted_len = last_quote - first_quote;
981

982
	if (quoted_len == 0) {
983
		set_parse_error(reader, 0, "Missing closing quotation mark in section header");
984 985
		return -1;
	}
986

987 988
	GITERR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
	GITERR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
989

990
	git_buf_grow(&buf, alloc_len);
991
	git_buf_printf(&buf, "%s.", base_name);
992 993 994 995

	rpos = 0;

	line = first_quote;
996
	c = line[++rpos];
997 998 999 1000 1001 1002

	/*
	 * 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 {
1003

1004
		switch (c) {
1005
		case 0:
1006
			set_parse_error(reader, 0, "Unexpected end-of-line in section header");
1007 1008 1009
			git_buf_free(&buf);
			return -1;

1010
		case '"':
1011
			goto end_parse;
1012

1013
		case '\\':
1014
			c = line[++rpos];
1015

1016 1017
			if (c == 0) {
				set_parse_error(reader, rpos, "Unexpected end-of-line in section header");
1018 1019
				git_buf_free(&buf);
				return -1;
1020
			}
1021

1022 1023 1024 1025
		default:
			break;
		}

Linquize committed
1026
		git_buf_putc(&buf, (char)c);
1027 1028 1029 1030 1031 1032 1033 1034 1035
		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;
	}
1036

1037 1038
	*section_name = git_buf_detach(&buf);
	return 0;
1039 1040
}

1041
static int parse_section_header(struct reader *reader, char **section_out)
1042 1043 1044
{
	char *name, *name_end;
	int name_length, c, pos;
1045
	int result;
1046
	char *line;
1047
	size_t line_len;
1048

1049
	line = reader_readline(reader, true);
1050
	if (line == NULL)
1051
		return -1;
1052 1053

	/* find the end of the variable's name */
1054
	name_end = strrchr(line, ']');
schu committed
1055
	if (name_end == NULL) {
1056
		git__free(line);
1057
		set_parse_error(reader, 0, "Missing ']' in section header");
1058
		return -1;
schu committed
1059
	}
1060

1061
	GITERR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
1062
	name = git__malloc(line_len);
1063
	GITERR_CHECK_ALLOC(name);
1064 1065 1066 1067 1068 1069

	name_length = 0;
	pos = 0;

	/* Make sure we were given a section header */
	c = line[pos++];
1070
	assert(c == '[');
1071 1072 1073 1074

	c = line[pos++];

	do {
1075
		if (git__isspace(c)){
1076
			name[name_length] = '\0';
1077
			result = parse_section_header_ext(reader, line, name, section_out);
1078 1079
			git__free(line);
			git__free(name);
1080
			return result;
1081 1082 1083
		}

		if (!config_keychar(c) && c != '.') {
1084
			set_parse_error(reader, pos, "Unexpected character in header");
1085
			goto fail_parse;
1086 1087
		}

1088
		name[name_length++] = (char) tolower(c);
1089 1090 1091

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

schu committed
1092
	if (line[pos - 1] != ']') {
1093
		set_parse_error(reader, pos, "Unexpected end of file");
1094
		goto fail_parse;
schu committed
1095
	}
1096

1097
	git__free(line);
1098 1099

	name[name_length] = 0;
1100 1101
	*section_out = name;

1102 1103 1104
	return 0;

fail_parse:
1105 1106
	git__free(line);
	git__free(name);
1107
	return -1;
1108 1109
}

1110
static int skip_bom(struct reader *reader)
1111
{
1112 1113
	git_bom_t bom;
	int bom_offset = git_buf_text_detect_bom(&bom,
1114
		&reader->buffer, reader->read_ptr - reader->buffer.ptr);
1115

1116
	if (bom == GIT_BOM_UTF8)
1117
		reader->read_ptr += bom_offset;
1118

1119
	/* TODO: reference implementation is pretty stupid with BoM */
1120

1121
	return 0;
1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162
}

/*
	(* 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"
*/

1163
static int strip_comments(char *line, int in_quotes)
1164
{
1165
	int quote_count = in_quotes;
1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
	char *ptr;

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

		if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
			ptr[0] = '\0';
			break;
		}
	}

1178
	/* skip any space at the end */
1179
	while (ptr > line && git__isspace(ptr[-1])) {
1180
		ptr--;
1181
	}
1182 1183 1184
	ptr[0] = '\0';

	return quote_count;
1185 1186
}

1187 1188 1189 1190
static int included_path(git_buf *out, const char *dir, const char *path)
{
	/* From the user's home */
	if (path[0] == '~' && path[1] == '/')
1191
		return git_sysdir_find_global_file(out, &path[1]);
1192 1193 1194 1195

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

1196
static int config_parse(git_strmap *values, diskfile_backend *cfg_file, struct reader *reader, git_config_level_t level, int depth)
1197
{
1198
	int c;
1199 1200 1201
	char *current_section = NULL;
	char *var_name;
	char *var_value;
1202
	cvar_t *var;
1203
	git_buf buf = GIT_BUF_INIT;
1204
	int result = 0;
1205
	uint32_t reader_idx;
1206

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

1212
	reader_idx = git_array_size(cfg_file->readers) - 1;
1213
	/* Initialize the reading position */
1214 1215
	reader->read_ptr = reader->buffer.ptr;
	reader->eof = 0;
1216

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

1221
	skip_bom(reader);
1222

1223
	while (result == 0 && !reader->eof) {
1224

1225
		c = reader_peek(reader, SKIP_WHITESPACE);
1226 1227

		switch (c) {
1228
		case '\n': /* EOF when peeking, set EOF in the reader to exit the loop */
1229
			reader->eof = 1;
1230 1231 1232
			break;

		case '[': /* section header, new section begins */
1233
			git__free(current_section);
1234
			current_section = NULL;
1235
			result = parse_section_header(reader, &current_section);
1236 1237 1238 1239
			break;

		case ';':
		case '#':
1240
			reader_consume_line(reader);
1241 1242 1243
			break;

		default: /* assume variable declaration */
1244
			result = parse_variable(reader, &var_name, &var_value);
1245
			if (result < 0)
1246 1247
				break;

1248 1249 1250 1251
			git__strtolower(var_name);
			git_buf_printf(&buf, "%s.%s", current_section, var_name);
			git__free(var_name);

1252 1253
			if (git_buf_oom(&buf)) {
				git__free(var_value);
1254
				return -1;
1255
			}
1256

Jacques Germishuys committed
1257 1258 1259 1260 1261
			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);

1262 1263 1264
			var->entry->name = git_buf_detach(&buf);
			var->entry->value = var_value;
			var->entry->level = level;
1265
			var->included = !!depth;
1266

1267 1268 1269 1270

			if ((result = append_entry(values, var)) < 0)
				break;
			else
1271
				result = 0;
1272

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

1280
				r = git_array_alloc(cfg_file->readers);
1281 1282
				/* The reader may have been reallocated */
				reader = git_array_get(cfg_file->readers, reader_idx);
1283
				memset(r, 0, sizeof(struct reader));
1284 1285 1286
				if ((result = git_path_dirname_r(&path, reader->file_path)) < 0)
					break;

1287
				/* We need to know our index in the array, as the next config_parse call may realloc */
1288
				index = git_array_size(cfg_file->readers) - 1;
1289 1290 1291 1292 1293 1294 1295
				dir = git_buf_detach(&path);
				result = included_path(&path, dir, var->entry->value);
				git__free(dir);

				if (result < 0)
					break;

1296 1297
				r->file_path = git_buf_detach(&path);
				git_buf_init(&r->buffer, 0);
1298 1299 1300 1301
				result = git_futils_readbuffer_updated(&r->buffer, r->file_path, &r->file_mtime,
									    &r->file_size, NULL);

				if (result == 0) {
1302 1303
					result = config_parse(values, cfg_file, r, level, depth+1);
					r = git_array_get(cfg_file->readers, index);
1304
					reader = git_array_get(cfg_file->readers, reader_idx);
1305
				}
1306 1307 1308 1309 1310
				else if (result == GIT_ENOTFOUND) {
					giterr_clear();
					result = 0;
				}

1311
				git_buf_free(&r->buffer);
1312 1313 1314 1315 1316

				if (result < 0)
					break;
			}

1317 1318 1319 1320
			break;
		}
	}

1321
	git__free(current_section);
1322
	return result;
1323 1324
}

1325
static int write_section(git_filebuf *file, const char *key)
1326
{
1327
	int result;
1328
	const char *dot;
1329
	git_buf buf = GIT_BUF_INIT;
1330

1331
	/* All of this just for [section "subsection"] */
1332
	dot = strchr(key, '.');
1333
	git_buf_putc(&buf, '[');
1334
	if (dot == NULL) {
1335
		git_buf_puts(&buf, key);
1336
	} else {
1337
		char *escaped;
1338
		git_buf_put(&buf, key, dot - key);
1339 1340 1341 1342
		escaped = escape_value(dot + 1);
		GITERR_CHECK_ALLOC(escaped);
		git_buf_printf(&buf, " \"%s\"", escaped);
		git__free(escaped);
1343 1344
	}
	git_buf_puts(&buf, "]\n");
1345

1346
	if (git_buf_oom(&buf))
1347
		return -1;
1348

1349
	result = git_filebuf_write(file, git_buf_cstr(&buf), buf.size);
1350
	git_buf_free(&buf);
1351

1352
	return result;
1353 1354
}

Vicent Marti committed
1355
static const char *quotes_for_value(const char *value)
1356
{
Vicent Marti committed
1357 1358 1359 1360 1361 1362
	const char *ptr;

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

	for (ptr = value; *ptr; ++ptr) {
1363
		if (*ptr == ';' || *ptr == '#')
Vicent Marti committed
1364
			return "\"";
1365 1366
	}

Vicent Marti committed
1367 1368 1369 1370
	if (ptr[-1] == ' ')
		return "\"";

	return "";
1371 1372
}

1373 1374 1375
/*
 * This is pretty much the parsing, except we write out anything we don't have
 */
1376
static int config_write(diskfile_backend *cfg, const char *key, const regex_t *preg, const char* value)
1377
{
1378 1379
	int result, c;
	int section_matches = 0, last_section_matched = 0, preg_replaced = 0, write_trailer = 0;
1380
	const char *pre_end = NULL, *post_start = NULL, *data_start, *write_start;
1381
	char *current_section = NULL, *section, *name, *ldot;
1382
	git_filebuf file = GIT_FILEBUF_INIT;
1383
	struct reader *reader = git_array_get(cfg->readers, 0);
1384 1385

	/* We need to read in our own config file */
1386
	result = git_futils_readbuffer(&reader->buffer, cfg->file_path);
1387 1388

	/* Initialise the reading position */
1389
	if (result == GIT_ENOTFOUND) {
1390 1391
		reader->read_ptr = NULL;
		reader->eof = 1;
1392
		data_start = NULL;
1393
		git_buf_clear(&reader->buffer);
1394
	} else if (result == 0) {
1395 1396 1397
		reader->read_ptr = reader->buffer.ptr;
		reader->eof = 0;
		data_start = reader->read_ptr;
1398 1399
	} else {
		return -1; /* OS error when reading the file */
1400
	}
1401

1402 1403
	write_start = data_start;

1404
	/* Lock the file */
nulltoken committed
1405 1406 1407 1408 1409
	if ((result = git_filebuf_open(
		&file, cfg->file_path, 0, GIT_CONFIG_FILE_MODE)) < 0) {
			git_buf_free(&reader->buffer);
			return result;
	}
1410

1411
	skip_bom(reader);
1412 1413 1414
	ldot = strrchr(key, '.');
	name = ldot + 1;
	section = git__strndup(key, ldot - key);
1415

1416 1417
	while (!reader->eof) {
		c = reader_peek(reader, SKIP_WHITESPACE);
1418

1419
		if (c == '\0') { /* We've arrived at the end of the file */
1420 1421
			break;

1422
		} else if (c == '[') { /* section header, new section begins */
1423 1424 1425 1426 1427 1428 1429
			/*
			 * We set both positions to the current one in case we
			 * need to add a variable to the end of a section. In that
			 * case, we want both variables to point just before the
			 * new section. If we actually want to replace it, the
			 * default case will take care of updating them.
			 */
1430
			pre_end = post_start = reader->read_ptr;
1431 1432

			git__free(current_section);
1433
			current_section = NULL;
1434
			if (parse_section_header(reader, &current_section) < 0)
1435
				goto rewrite_fail;
1436 1437 1438

			/* Keep track of when it stops matching */
			last_section_matched = section_matches;
1439
			section_matches = !strcmp(current_section, section);
1440
		}
1441

1442
		else if (c == ';' || c == '#') {
1443
			reader_consume_line(reader);
1444
		}
1445

1446
		else {
1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458
			/*
			 * If the section doesn't match, but the last section did,
			 * it means we need to add a variable (so skip the line
			 * otherwise). If both the section and name match, we need
			 * to overwrite the variable (so skip the line
			 * otherwise). pre_end needs to be updated each time so we
			 * don't loose that information, but we only need to
			 * update post_start if we're going to use it in this
			 * iteration.
			 */
			if (!section_matches) {
				if (!last_section_matched) {
1459
					reader_consume_line(reader);
1460
					continue;
1461 1462
				}
			} else {
1463 1464
				int has_matched = 0;
				char *var_name, *var_value;
1465

1466 1467
				pre_end = reader->read_ptr;
				if (parse_variable(reader, &var_name, &var_value) < 0)
1468
					goto rewrite_fail;
1469

1470 1471 1472
				/* First try to match the name of the variable */
				if (strcasecmp(name, var_name) == 0)
					has_matched = 1;
1473

1474 1475 1476 1477
				/* If the name matches, and we have a regex to match the
				 * value, try to match it */
				if (has_matched && preg != NULL)
					has_matched = (regexec(preg, var_value, 0, NULL, 0) == 0);
1478

1479 1480
				git__free(var_name);
				git__free(var_value);
1481

1482 1483 1484
				/* if there is no match, keep going */
				if (!has_matched)
					continue;
1485

1486
				post_start = reader->read_ptr;
1487 1488
			}

1489 1490
			/* We've found the variable we wanted to change, so
			 * write anything up to it */
1491
			git_filebuf_write(&file, write_start, pre_end - write_start);
1492
			preg_replaced = 1;
1493

1494 1495 1496
			/* Then replace the variable. If the value is NULL, it
			 * means we want to delete it, so don't write anything. */
			if (value != NULL) {
Vicent Marti committed
1497 1498
				const char *q = quotes_for_value(value);
				git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
1499 1500
			}

1501 1502 1503 1504 1505 1506 1507
			/*
			 * If we have a multivar, we should keep looking for entries,
			 * but only if we're in the right section. Otherwise we'll end up
			 * looping on the edge of a matching and a non-matching section.
			 */
			if (section_matches && preg != NULL) {
				write_start = post_start;
1508 1509 1510
				continue;
			}

1511 1512
			write_trailer = 1;
			break; /* break from the loop */
1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524
		}
	}

	/*
	 * Being here can mean that
	 *
	 * 1) our section is the last one in the file and we're
	 * adding a variable
	 *
	 * 2) we didn't find a section for us so we need to create it
	 * ourselves.
	 *
1525 1526 1527 1528 1529 1530
	 * 3) we're setting a multivar with a regex, which means we
	 * continue to search for matching values
	 *
	 * In the last case, if we've already replaced a value, we
	 * want to write the rest of the file. Otherwise we need to write
	 * out the whole file and then the new variable.
1531
	 */
1532 1533
	if (write_trailer) {
		/* Write out rest of the file */
1534
		git_filebuf_write(&file, post_start, reader->buffer.size - (post_start - data_start));
1535 1536
	} else {
		if (preg_replaced) {
1537
			git_filebuf_printf(&file, "\n%s", write_start);
1538
		} else {
Vicent Marti committed
1539 1540
			const char *q;

1541
			git_filebuf_write(&file, reader->buffer.ptr, reader->buffer.size);
1542

1543 1544 1545
			if (reader->buffer.size > 0 && *(reader->buffer.ptr + reader->buffer.size - 1) != '\n')
				git_filebuf_write(&file, "\n", 1);

1546 1547 1548 1549 1550
			/* And now if we just need to add a variable */
			if (!section_matches && write_section(&file, section) < 0)
				goto rewrite_fail;

			/* Sanity check: if we are here, and value is NULL, that means that somebody
Will Stamper committed
1551
			 * touched the config file after our initial read. We should probably assert()
1552 1553 1554
			 * this, but instead we'll handle it gracefully with an error. */
			if (value == NULL) {
				giterr_set(GITERR_CONFIG,
1555
					"race condition when writing a config file (a cvar has been removed)");
1556 1557
				goto rewrite_fail;
			}
1558

1559
			/* If we are here, there is at least a section line */
Vicent Marti committed
1560 1561
			q = quotes_for_value(value);
			git_filebuf_printf(&file, "\t%s = %s%s%s\n", name, q, value, q);
1562
		}
1563 1564
	}

1565 1566
	git__free(section);
	git__free(current_section);
1567

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

1571
	result = git_filebuf_commit(&file);
1572
	git_buf_free(&reader->buffer);
1573

1574
	return result;
1575

1576
rewrite_fail:
1577
	git__free(section);
1578
	git__free(current_section);
1579

1580
	git_filebuf_cleanup(&file);
1581
	git_buf_free(&reader->buffer);
1582
	return -1;
1583 1584
}

1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597
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);
1598 1599 1600
	if (!len)
		return git__calloc(1, sizeof(char));

1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620
	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);
}

1621 1622
/* '\"' -> '"' etc */
static char *fixup_line(const char *ptr, int quote_count)
1623
{
1624
	char *str, *out, *esc;
1625
	size_t ptr_len = strlen(ptr), alloc_len;
1626

1627 1628
	if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) ||
		(str = git__malloc(alloc_len)) == NULL) {
1629
		return NULL;
1630 1631 1632
	}

	out = str;
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 1661 1662
	while (*ptr != '\0') {
		if (*ptr == '"') {
			quote_count++;
		} else if (*ptr != '\\') {
			*out++ = *ptr;
		} else {
			/* backslash, check the next char */
			ptr++;
			/* if we're at the end, it's a multiline, so keep the backslash */
			if (*ptr == '\0') {
				*out++ = '\\';
				goto out;
			}
			if ((esc = strchr(escapes, *ptr)) != NULL) {
				*out++ = escaped[esc - escapes];
			} else {
				git__free(str);
				giterr_set(GITERR_CONFIG, "Invalid escape at %s", ptr);
				return NULL;
			}
		}
		ptr++;
	}

out:
	*out = '\0';

	return str;
}
1663 1664 1665

static int is_multiline_var(const char *str)
{
1666
	int count = 0;
1667
	const char *end = str + strlen(str);
1668 1669 1670 1671 1672 1673
	while (end > str && end[-1] == '\\') {
		count++;
		end--;
	}

	/* An odd number means last backslash wasn't escaped, so it's multiline */
1674
	return count & 1;
1675 1676
}

1677
static int parse_multiline_variable(struct reader *reader, git_buf *value, int in_quotes)
1678
{
1679 1680
	char *line = NULL, *proc_line = NULL;
	int quote_count;
1681 1682

	/* Check that the next line exists */
1683
	line = reader_readline(reader, false);
1684
	if (line == NULL)
1685
		return -1;
1686 1687 1688

	/* We've reached the end of the file, there is input missing */
	if (line[0] == '\0') {
1689
		set_parse_error(reader, 0, "Unexpected end of file while parsing multine var");
1690 1691
		git__free(line);
		return -1;
1692 1693
	}

1694
	quote_count = strip_comments(line, !!in_quotes);
1695 1696 1697

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

1703 1704 1705 1706
	/* Drop the continuation character '\': to closely follow the UNIX
	 * standard, this character **has** to be last one in the buf, with
	 * no whitespace after it */
	assert(is_multiline_var(value->ptr));
1707
	git_buf_shorten(value, 1);
1708

1709 1710 1711 1712
	proc_line = fixup_line(line, in_quotes);
	if (proc_line == NULL) {
		git__free(line);
		return -1;
1713
	}
1714
	/* add this line to the multiline var */
1715
	git_buf_puts(value, proc_line);
1716
	git__free(line);
1717
	git__free(proc_line);
1718 1719

	/*
1720 1721
	 * If we need to continue reading the next line, let's just
	 * keep putting stuff in the buffer
1722
	 */
1723
	if (is_multiline_var(value->ptr))
1724
		return parse_multiline_variable(reader, value, quote_count);
1725

1726
	return 0;
1727 1728
}

1729
static int parse_variable(struct reader *reader, char **var_name, char **var_value)
1730 1731 1732 1733
{
	const char *var_end = NULL;
	const char *value_start = NULL;
	char *line;
1734
	int quote_count;
1735

1736
	line = reader_readline(reader, true);
1737
	if (line == NULL)
1738
		return -1;
1739

1740
	quote_count = strip_comments(line, 0);
1741 1742 1743 1744 1745 1746 1747 1748

	var_end = strchr(line, '=');

	if (var_end == NULL)
		var_end = strchr(line, '\0');
	else
		value_start = var_end + 1;

Russell Belfer committed
1749
	do var_end--;
1750
	while (var_end>line && git__isspace(*var_end));
1751

1752 1753
	*var_name = git__strndup(line, var_end - line + 1);
	GITERR_CHECK_ALLOC(*var_name);
1754

1755 1756
	/* If there is no value, boolean true is assumed */
	*var_value = NULL;
1757 1758 1759 1760 1761

	/*
	 * Now, let's try to parse the value
	 */
	if (value_start != NULL) {
1762
		while (git__isspace(value_start[0]))
1763 1764 1765
			value_start++;

		if (is_multiline_var(value_start)) {
1766
			git_buf multi_value = GIT_BUF_INIT;
1767 1768 1769
			char *proc_line = fixup_line(value_start, 0);
			GITERR_CHECK_ALLOC(proc_line);
			git_buf_puts(&multi_value, proc_line);
1770
			git__free(proc_line);
1771
			if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 || git_buf_oom(&multi_value)) {
1772
				git__free(*var_name);
1773 1774 1775
				git__free(line);
				git_buf_free(&multi_value);
				return -1;
1776
			}
1777

1778
			*var_value = git_buf_detach(&multi_value);
1779

1780 1781
		}
		else if (value_start[0] != '\0') {
1782
			*var_value = fixup_line(value_start, 0);
1783
			GITERR_CHECK_ALLOC(*var_value);
1784 1785 1786
		} else { /* equals sign but missing rhs */
			*var_value = git__strdup("");
			GITERR_CHECK_ALLOC(*var_value);
1787 1788 1789
		}
	}

1790
	git__free(line);
1791
	return 0;
1792
}