config.c 10.3 KB
Newer Older
1
/*
schu committed
2
 * Copyright (C) 2009-2012 the libgit2 contributors
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 10 11
 */

#include "common.h"
#include "fileops.h"
#include "hashtable.h"
#include "config.h"
12
#include "git2/config.h"
13
#include "vector.h"
14 15 16
#if GIT_WIN32
# include <windows.h>
#endif
17 18 19

#include <ctype.h>

20
typedef struct {
21
	git_config_file *file;
22
	int priority;
23
} file_internal;
24

25
static void config_free(git_config *cfg)
26
{
27
	unsigned int i;
28 29
	git_config_file *file;
	file_internal *internal;
30

31 32 33 34
	for(i = 0; i < cfg->files.length; ++i){
		internal = git_vector_get(&cfg->files, i);
		file = internal->file;
		file->free(file);
35
		git__free(internal);
36
	}
37

38
	git_vector_free(&cfg->files);
39
	git__free(cfg);
40 41
}

42 43 44 45 46 47 48 49
void git_config_free(git_config *cfg)
{
	if (cfg == NULL)
		return;

	GIT_REFCOUNT_DEC(cfg, config_free);
}

50
static int config_backend_cmp(const void *a, const void *b)
51
{
52 53
	const file_internal *bk_a = (const file_internal *)(a);
	const file_internal *bk_b = (const file_internal *)(b);
54 55

	return bk_b->priority - bk_a->priority;
56 57
}

58
int git_config_new(git_config **out)
59 60 61 62 63 64 65 66 67
{
	git_config *cfg;

	cfg = git__malloc(sizeof(git_config));
	if (cfg == NULL)
		return GIT_ENOMEM;

	memset(cfg, 0x0, sizeof(git_config));

68
	if (git_vector_init(&cfg->files, 3, config_backend_cmp) < 0) {
69
		git__free(cfg);
Vicent Martí committed
70
		return -1;
71
	}
72

73
	*out = cfg;
74
	GIT_REFCOUNT_INC(cfg);
Vicent Martí committed
75
	return 0;
76
}
77

78 79 80 81
int git_config_add_file_ondisk(git_config *cfg, const char *path, int priority)
{
	git_config_file *file = NULL;

Vicent Martí committed
82 83
	if (git_config_file__ondisk(&file, path) < 0)
		return -1;
84

Vicent Martí committed
85
	if (git_config_add_file(cfg, file, priority) < 0) {
Vicent Marti committed
86 87 88 89 90
		/*
		 * free manually; the file is not owned by the config
		 * instance yet and will not be freed on cleanup
		 */
		file->free(file);
Vicent Martí committed
91
		return -1;
92 93
	}

Vicent Martí committed
94
	return 0;
95 96 97 98
}

int git_config_open_ondisk(git_config **cfg, const char *path)
{
Vicent Martí committed
99 100
	if (git_config_new(cfg) < 0)
		return -1;
101

Vicent Martí committed
102
	if (git_config_add_file_ondisk(*cfg, path, 1) < 0) {
103
		git_config_free(*cfg);
Vicent Martí committed
104 105
		return -1;
	}
106

Vicent Martí committed
107
	return 0;
108 109
}

110
int git_config_add_file(git_config *cfg, git_config_file *file, int priority)
111
{
112
	file_internal *internal;
Vicent Martí committed
113
	int result;
114

115
	assert(cfg && file);
116

Vicent Martí committed
117 118
	if ((result = file->open(file)) < 0)
		return result;
119

120
	internal = git__malloc(sizeof(file_internal));
Vicent Martí committed
121
	GITERR_CHECK_ALLOC(internal);
122

123
	internal->file = file;
124
	internal->priority = priority;
125

126
	if (git_vector_insert(&cfg->files, internal) < 0) {
127
		git__free(internal);
Vicent Martí committed
128
		return -1;
129
	}
130

131 132
	git_vector_sort(&cfg->files);
	internal->file->cfg = cfg;
133

Vicent Martí committed
134
	return 0;
135 136
}

137 138 139 140
/*
 * Loop over all the variables
 */

141
int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data)
142
{
143
	int ret = 0;
144
	unsigned int i;
145 146
	file_internal *internal;
	git_config_file *file;
147

148 149 150 151
	for(i = 0; i < cfg->files.length && ret == 0; ++i) {
		internal = git_vector_get(&cfg->files, i);
		file = internal->file;
		ret = file->foreach(file, fn, data);
152
	}
153 154 155 156

	return ret;
}

157
int git_config_delete(git_config *cfg, const char *name)
158
{
159 160 161
	file_internal *internal;
	git_config_file *file;

Vicent Martí committed
162
	assert(cfg->files.length);
163 164 165 166

	internal = git_vector_get(&cfg->files, 0);
	file = internal->file;

167
	return file->del(file, name);
168
}
169

170
/**************
171
 * Setters
172
 **************/
173

174
int git_config_set_int64(git_config *cfg, const char *name, int64_t value)
175
{
Vicent Marti committed
176
	char str_value[32]; /* All numbers should fit in here */
177
	p_snprintf(str_value, sizeof(str_value), "%" PRId64, value);
Vicent Marti committed
178
	return git_config_set_string(cfg, name, str_value);
179
}
180

181
int git_config_set_int32(git_config *cfg, const char *name, int32_t value)
182
{
183
	return git_config_set_int64(cfg, name, (int64_t)value);
184 185
}

186 187
int git_config_set_bool(git_config *cfg, const char *name, int value)
{
Vicent Marti committed
188
	return git_config_set_string(cfg, name, value ? "true" : "false");
189
}
190

191 192
int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
193 194
	file_internal *internal;
	git_config_file *file;
195

Vicent Martí committed
196
	assert(cfg->files.length);
197

198 199
	internal = git_vector_get(&cfg->files, 0);
	file = internal->file;
200

201
	return file->set(file, name, value);
202 203
}

204
int git_config_parse_bool(int *out, const char *value)
205 206 207 208
{
	/* A missing value means true */
	if (value == NULL) {
		*out = 1;
209
		return 0;
210
	}
211

212 213 214 215
	if (!strcasecmp(value, "true") ||
		!strcasecmp(value, "yes") ||
		!strcasecmp(value, "on")) {
		*out = 1;
216
		return 0;
217 218 219 220 221
	}
	if (!strcasecmp(value, "false") ||
		!strcasecmp(value, "no") ||
		!strcasecmp(value, "off")) {
		*out = 0;
222
		return 0;
223 224 225 226 227 228
	}

	return GIT_EINVALIDTYPE;
}

static int parse_int64(int64_t *out, const char *value)
229
{
230
	const char *num_end;
231
	int64_t num;
232

233
	if (git__strtol64(&num, value, &num_end, 0) < 0)
Vicent Martí committed
234
		return -1;
235 236

	switch (*num_end) {
237 238
	case 'g':
	case 'G':
239
		num *= 1024;
240 241
		/* fallthrough */

242
	case 'm':
243
	case 'M':
244 245
		num *= 1024;
		/* fallthrough */
246

247 248 249
	case 'k':
	case 'K':
		num *= 1024;
250

251 252 253
		/* check that that there are no more characters after the
		 * given modifier suffix */
		if (num_end[1] != '\0')
Vicent Martí committed
254
			return -1;
255 256 257 258 259

		/* fallthrough */

	case '\0':
		*out = num;
260
		return 0;
261 262

	default:
Vicent Martí committed
263
		return -1;
264
	}
265 266
}

267
static int parse_int32(int32_t *out, const char *value)
268
{
269 270 271 272
	int64_t tmp;
	int32_t truncate;

	if (parse_int64(&tmp, value) < 0)
Vicent Martí committed
273
		return -1;
274 275 276

	truncate = tmp & 0xFFFFFFFF;
	if (truncate != tmp)
Vicent Martí committed
277
		return -1;
278 279 280 281 282 283 284 285

	*out = truncate;
	return 0;
}

/***********
 * Getters
 ***********/
286 287
int git_config_lookup_map_value(
	git_cvar_map *maps, size_t map_n, const char *value, int *out)
288 289 290
{
	size_t i;

291 292
	if (!value)
		return GIT_ENOTFOUND;
293 294 295 296 297

	for (i = 0; i < map_n; ++i) {
		git_cvar_map *m = maps + i;

		switch (m->cvar_type) {
298 299 300 301 302 303 304 305 306 307 308
		case GIT_CVAR_FALSE:
		case GIT_CVAR_TRUE: {
			int bool_val;

			if (git_config_parse_bool(&bool_val, value) == 0 && 
				bool_val == (int)m->cvar_type) {
				*out = m->map_value;
				return 0;
			}
			break;
		}
309

310 311 312 313
		case GIT_CVAR_INT32:
			if (parse_int32(out, value) == 0)
				return 0;
			break;
314

315 316 317 318
		case GIT_CVAR_STRING:
			if (strcasecmp(value, m->str_match) == 0) {
				*out = m->map_value;
				return 0;
319
			}
320 321 322
			break;
		}
	}
323

324 325
	return GIT_ENOTFOUND;
}
326

327 328 329 330
int git_config_get_mapped(git_config *cfg, const char *name, git_cvar_map *maps, size_t map_n, int *out)
{
	const char *value;
	int error;
331

332 333 334 335 336 337
	error = git_config_get_string(cfg, name, &value);
	if (error < 0)
		return error;

	if (!git_config_lookup_map_value(maps, map_n, value, out))
		return 0;
338

Vicent Martí committed
339
	giterr_set(GITERR_CONFIG,
340
		"Failed to map the '%s' config variable with a valid value", name);
Vicent Martí committed
341
	return -1;
342 343 344 345 346
}

int git_config_get_int64(git_config *cfg, const char *name, int64_t *out)
{
	const char *value;
347
	int ret;
348

349
	ret = git_config_get_string(cfg, name, &value);
Vicent Martí committed
350 351
	if (ret < 0)
		return ret;
352

Vicent Martí committed
353 354 355 356
	if (parse_int64(out, value) < 0) {
		giterr_set(GITERR_CONFIG, "Failed to parse '%s' as an integer", value);
		return -1;
	}
357

Vicent Martí committed
358
	return 0;
359 360 361 362 363
}

int git_config_get_int32(git_config *cfg, const char *name, int32_t *out)
{
	const char *value;
Vicent Martí committed
364
	int ret;
365

Vicent Martí committed
366 367 368
	ret = git_config_get_string(cfg, name, &value);
	if (ret < 0)
		return ret;
369

Vicent Martí committed
370 371 372 373
	if (parse_int32(out, value) < 0) {
		giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value);
		return -1;
	}
374
	
Vicent Martí committed
375
	return 0;
376 377
}

378
int git_config_get_bool(git_config *cfg, const char *name, int *out)
379
{
380
	const char *value;
Vicent Martí committed
381
	int ret;
382

Vicent Martí committed
383 384 385
	ret = git_config_get_string(cfg, name, &value);
	if (ret < 0)
		return ret;
386

387
	if (git_config_parse_bool(out, value) == 0)
Vicent Martí committed
388
		return 0;
389

390 391
	if (parse_int32(out, value) == 0) {
		*out = !!(*out);
Vicent Martí committed
392
		return 0;
393 394
	}

Vicent Martí committed
395 396
	giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a boolean value", value);
	return -1;
397 398
}

399 400
int git_config_get_string(git_config *cfg, const char *name, const char **out)
{
401 402
	file_internal *internal;
	git_config_file *file;
Vicent Martí committed
403
	int ret = GIT_ENOTFOUND;
404
	unsigned int i;
405

Vicent Martí committed
406
	assert(cfg->files.length);
407

408 409 410
	for (i = 0; i < cfg->files.length; ++i) {
		internal = git_vector_get(&cfg->files, i);
		file = internal->file;
Vicent Martí committed
411 412 413 414 415

		ret = file->get(file, name, out);
		if (ret == 0)
			return 0;

416 417
		/* File backend doesn't set error message on variable
		 * not found */
Vicent Martí committed
418 419 420 421
		if (ret == GIT_ENOTFOUND)
			continue;

		return ret;
422
	}
423

424
	giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name);
Vicent Martí committed
425
	return GIT_ENOTFOUND;
426 427
}

428 429 430 431 432
int git_config_get_multivar(git_config *cfg, const char *name, const char *regexp,
			    int (*fn)(const char *value, void *data), void *data)
{
	file_internal *internal;
	git_config_file *file;
Vicent Martí committed
433
	int ret = GIT_ENOTFOUND;
434 435
	unsigned int i;

Vicent Martí committed
436
	assert(cfg->files.length);
437 438 439 440 441 442 443 444

	/*
	 * This loop runs the "wrong" way 'round because we need to
	 * look at every value from the most general to most specific
	 */
	for (i = cfg->files.length; i > 0; --i) {
		internal = git_vector_get(&cfg->files, i - 1);
		file = internal->file;
Vicent Martí committed
445 446 447
		ret = file->get_multivar(file, name, regexp, fn, data);
		if (ret < 0 && ret != GIT_ENOTFOUND)
			return ret;
448 449
	}

Vicent Martí committed
450
	return 0;
451 452
}

453 454 455 456
int git_config_set_multivar(git_config *cfg, const char *name, const char *regexp, const char *value)
{
	file_internal *internal;
	git_config_file *file;
Vicent Martí committed
457
	int ret = GIT_ENOTFOUND;
458 459 460 461 462
	unsigned int i;

	for (i = cfg->files.length; i > 0; --i) {
		internal = git_vector_get(&cfg->files, i - 1);
		file = internal->file;
Vicent Martí committed
463
		ret = file->set_multivar(file, name, regexp, value);
464
		if (ret < 0 && ret != GIT_ENOTFOUND)
Vicent Martí committed
465
			return ret;
466 467
	}

Vicent Martí committed
468
	return 0;
469 470
}

471 472 473 474 475
int git_config_find_global_r(git_buf *path)
{
	return git_futils_find_global_file(path, GIT_CONFIG_FILENAME);
}

476 477
int git_config_find_global(char *global_config_path)
{
478
	git_buf path  = GIT_BUF_INIT;
Vicent Martí committed
479
	int     ret = git_config_find_global_r(&path);
480

Vicent Martí committed
481 482 483
	if (ret < 0) {
		git_buf_free(&path);
		return ret;
484 485
	}

Vicent Martí committed
486 487 488 489 490 491
	if (path.size > GIT_PATH_MAX) {
		git_buf_free(&path);
		giterr_set(GITERR_NOMEMORY,
			"Path is to long to fit on the given buffer");
		return -1;
	}
492

Vicent Martí committed
493 494 495
	git_buf_copy_cstr(global_config_path, GIT_PATH_MAX, &path);
	git_buf_free(&path);
	return 0;
496 497
}

498
int git_config_find_system_r(git_buf *path)
499
{
500
	return git_futils_find_system_file(path, GIT_CONFIG_FILENAME_SYSTEM);
501 502
}

503 504 505
int git_config_find_system(char *system_config_path)
{
	git_buf path  = GIT_BUF_INIT;
Vicent Martí committed
506
	int     ret = git_config_find_system_r(&path);
507

Vicent Martí committed
508 509 510
	if (ret < 0) {
		git_buf_free(&path);
		return ret;
511 512
	}

Vicent Martí committed
513 514 515 516 517 518
	if (path.size > GIT_PATH_MAX) {
		git_buf_free(&path);
		giterr_set(GITERR_NOMEMORY,
			"Path is to long to fit on the given buffer");
		return -1;
	}
519

Vicent Martí committed
520 521 522
	git_buf_copy_cstr(system_config_path, GIT_PATH_MAX, &path);
	git_buf_free(&path);
	return 0;
523 524
}

525 526 527 528 529
int git_config_open_global(git_config **out)
{
	int error;
	char global_path[GIT_PATH_MAX];

Vicent Martí committed
530
	if ((error = git_config_find_global(global_path)) < 0)
531 532 533 534 535
		return error;

	return git_config_open_ondisk(out, global_path);
}