conf.c 15.7 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2011 The Music Player Daemon Project
3
 * http://www.musicpd.org
Warren Dukes's avatar
Warren Dukes committed
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Warren Dukes's avatar
Warren Dukes committed
18 19
 */

20
#include "config.h"
Warren Dukes's avatar
Warren Dukes committed
21 22
#include "conf.h"
#include "utils.h"
23
#include "string_util.h"
24
#include "tokenizer.h"
Eric Wong's avatar
Eric Wong committed
25
#include "path.h"
26
#include "mpd_error.h"
Warren Dukes's avatar
Warren Dukes committed
27

28 29
#include <glib.h>

30
#include <assert.h>
Max Kellermann's avatar
Max Kellermann committed
31
#include <string.h>
32
#include <stdio.h>
33
#include <stdlib.h>
34
#include <errno.h>
Max Kellermann's avatar
Max Kellermann committed
35

36 37 38
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "config"

Eric Wong's avatar
Eric Wong committed
39
#define MAX_STRING_SIZE	MPD_PATH_MAX+80
Warren Dukes's avatar
Warren Dukes committed
40

41 42
#define CONF_COMMENT		'#'

43
struct config_entry {
44 45 46
	const char *const name;
	const bool repeatable;
	const bool block;
47 48

	GSList *params;
49
};
50

51 52 53 54 55 56 57 58 59 60
static struct config_entry config_entries[] = {
	{ .name = CONF_MUSIC_DIR, false, false },
	{ .name = CONF_PLAYLIST_DIR, false, false },
	{ .name = CONF_FOLLOW_INSIDE_SYMLINKS, false, false },
	{ .name = CONF_FOLLOW_OUTSIDE_SYMLINKS, false, false },
	{ .name = CONF_DB_FILE, false, false },
	{ .name = CONF_STICKER_FILE, false, false },
	{ .name = CONF_LOG_FILE, false, false },
	{ .name = CONF_PID_FILE, false, false },
	{ .name = CONF_STATE_FILE, false, false },
61
	{ .name = "restore_paused", false, false },
62
	{ .name = CONF_USER, false, false },
63
	{ .name = CONF_GROUP, false, false },
64 65 66 67 68 69 70 71 72 73 74 75 76
	{ .name = CONF_BIND_TO_ADDRESS, true, false },
	{ .name = CONF_PORT, false, false },
	{ .name = CONF_LOG_LEVEL, false, false },
	{ .name = CONF_ZEROCONF_NAME, false, false },
	{ .name = CONF_ZEROCONF_ENABLED, false, false },
	{ .name = CONF_PASSWORD, true, false },
	{ .name = CONF_DEFAULT_PERMS, false, false },
	{ .name = CONF_AUDIO_OUTPUT, true, true },
	{ .name = CONF_AUDIO_OUTPUT_FORMAT, false, false },
	{ .name = CONF_MIXER_TYPE, false, false },
	{ .name = CONF_REPLAYGAIN, false, false },
	{ .name = CONF_REPLAYGAIN_PREAMP, false, false },
	{ .name = CONF_REPLAYGAIN_MISSING_PREAMP, false, false },
77
	{ .name = CONF_REPLAYGAIN_LIMIT, false, false },
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
	{ .name = CONF_VOLUME_NORMALIZATION, false, false },
	{ .name = CONF_SAMPLERATE_CONVERTER, false, false },
	{ .name = CONF_AUDIO_BUFFER_SIZE, false, false },
	{ .name = CONF_BUFFER_BEFORE_PLAY, false, false },
	{ .name = CONF_HTTP_PROXY_HOST, false, false },
	{ .name = CONF_HTTP_PROXY_PORT, false, false },
	{ .name = CONF_HTTP_PROXY_USER, false, false },
	{ .name = CONF_HTTP_PROXY_PASSWORD, false, false },
	{ .name = CONF_CONN_TIMEOUT, false, false },
	{ .name = CONF_MAX_CONN, false, false },
	{ .name = CONF_MAX_PLAYLIST_LENGTH, false, false },
	{ .name = CONF_MAX_COMMAND_LIST_SIZE, false, false },
	{ .name = CONF_MAX_OUTPUT_BUFFER_SIZE, false, false },
	{ .name = CONF_FS_CHARSET, false, false },
	{ .name = CONF_ID3V1_ENCODING, false, false },
	{ .name = CONF_METADATA_TO_USE, false, false },
	{ .name = CONF_SAVE_ABSOLUTE_PATHS, false, false },
	{ .name = CONF_DECODER, true, true },
	{ .name = CONF_INPUT, true, true },
	{ .name = CONF_GAPLESS_MP3_PLAYBACK, false, false },
98
	{ .name = CONF_PLAYLIST_PLUGIN, true, true },
99
	{ .name = CONF_AUTO_UPDATE, false, false },
100
	{ .name = CONF_AUTO_UPDATE_DEPTH, false, false },
101 102 103
	{ .name = CONF_DESPOTIFY_USER, false, false },
	{ .name = CONF_DESPOTIFY_PASSWORD, false, false},
	{ .name = CONF_DESPOTIFY_HIGH_BITRATE, false, false },
104
	{ .name = "filter", true, true },
105
	{ .name = "database", false, true },
106
};
107

108 109
static bool
get_bool(const char *value, bool *value_r)
110 111 112 113
{
	static const char *t[] = { "yes", "true", "1", NULL };
	static const char *f[] = { "no", "false", "0", NULL };

114 115
	if (string_array_contains(t, value)) {
		*value_r = true;
116
		return true;
117
	}
118

119 120 121 122
	if (string_array_contains(f, value)) {
		*value_r = false;
		return true;
	}
123

124
	return false;
125 126
}

127
struct config_param *
128
config_new_param(const char *value, int line)
Avuton Olrich's avatar
Avuton Olrich committed
129
{
130
	struct config_param *ret = g_new(struct config_param, 1);
131

Avuton Olrich's avatar
Avuton Olrich committed
132 133 134
	if (!value)
		ret->value = NULL;
	else
135
		ret->value = g_strdup(value);
136 137 138

	ret->line = line;

139 140
	ret->num_block_params = 0;
	ret->block_params = NULL;
141
	ret->used = false;
142 143 144 145

	return ret;
}

146
void
147
config_param_free(struct config_param *param)
Avuton Olrich's avatar
Avuton Olrich committed
148
{
149
	g_free(param->value);
150

151
	for (unsigned i = 0; i < param->num_block_params; i++) {
152 153
		g_free(param->block_params[i].name);
		g_free(param->block_params[i].value);
154 155
	}

156 157
	if (param->num_block_params)
		g_free(param->block_params);
158

159
	g_free(param);
Warren Dukes's avatar
Warren Dukes committed
160 161
}

162 163 164 165 166 167 168 169
static void
config_param_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
	struct config_param *param = data;

	config_param_free(param);
}

170
static struct config_entry *
171 172
config_entry_get(const char *name)
{
173 174
	for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
		struct config_entry *entry = &config_entries[i];
175 176 177 178 179 180 181
		if (strcmp(entry->name, name) == 0)
			return entry;
	}

	return NULL;
}

182
void config_global_finish(void)
Avuton Olrich's avatar
Avuton Olrich committed
183
{
184 185 186
	for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
		struct config_entry *entry = &config_entries[i];

187 188
		g_slist_foreach(entry->params,
				config_param_free_callback, NULL);
189 190
		g_slist_free(entry->params);
	}
191 192
}

193
void config_global_init(void)
Avuton Olrich's avatar
Avuton Olrich committed
194
{
195 196
}

197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
static void
config_param_check(gpointer data, G_GNUC_UNUSED gpointer user_data)
{
	struct config_param *param = data;

	if (!param->used)
		/* this whole config_param was not queried at all -
		   the feature might be disabled at compile time?
		   Silently ignore it here. */
		return;

	for (unsigned i = 0; i < param->num_block_params; i++) {
		struct block_param *bp = &param->block_params[i];

		if (!bp->used)
			g_warning("option '%s' on line %i was not recognized",
				  bp->name, bp->line);
	}
}

void config_global_check(void)
{
	for (unsigned i = 0; i < G_N_ELEMENTS(config_entries); ++i) {
		struct config_entry *entry = &config_entries[i];

		g_slist_foreach(entry->params, config_param_check, NULL);
	}
}

226
void
227
config_add_block_param(struct config_param * param, const char *name,
228
		       const char *value, int line)
229
{
230 231
	struct block_param *bp;

232
	assert(config_get_block_param(param, name) == NULL);
233

234 235 236 237 238
	param->num_block_params++;

	param->block_params = g_realloc(param->block_params,
					param->num_block_params *
					sizeof(param->block_params[0]));
239

240
	bp = &param->block_params[param->num_block_params - 1];
Avuton Olrich's avatar
Avuton Olrich committed
241

242 243 244
	bp->name = g_strdup(name);
	bp->value = g_strdup(value);
	bp->line = line;
245
	bp->used = false;
246 247
}

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
static bool
config_read_name_value(struct config_param *param, char *input, unsigned line,
		       GError **error_r)
{
	const char *name = tokenizer_next_word(&input, error_r);
	if (name == NULL) {
		assert(*input != 0);
		return false;
	}

	const char *value = tokenizer_next_string(&input, error_r);
	if (value == NULL) {
		if (*input == 0) {
			assert(error_r == NULL || *error_r == NULL);
			g_set_error(error_r, config_quark(), 0,
				    "Value missing");
		} else {
			assert(error_r == NULL || *error_r != NULL);
		}

		return false;
	}

	if (*input != 0 && *input != CONF_COMMENT) {
		g_set_error(error_r, config_quark(), 0,
			    "Unknown tokens after value");
		return false;
	}

277 278 279 280 281 282 283 284 285 286
	const struct block_param *bp = config_get_block_param(param, name);
	if (bp != NULL) {
		g_set_error(error_r, config_quark(), 0,
			    "\"%s\" is duplicate, first defined on line %i",
			    name, bp->line);
		return false;
	}

	config_add_block_param(param, name, value, line);
	return true;
287 288
}

289
static struct config_param *
290
config_read_block(FILE *fp, int *count, char *string, GError **error_r)
Avuton Olrich's avatar
Avuton Olrich committed
291
{
292
	struct config_param *ret = config_new_param(NULL, *count);
293
	GError *error = NULL;
294

295 296
	while (true) {
		char *line;
297

298
		line = fgets(string, MAX_STRING_SIZE, fp);
299 300 301 302 303 304
		if (line == NULL) {
			config_param_free(ret);
			g_set_error(error_r, config_quark(), 0,
				    "Expected '}' before end-of-file");
			return NULL;
		}
305

306
		(*count)++;
307
		line = strchug_fast(line);
308 309
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
310

311 312 313
		if (*line == '}') {
			/* end of this block; return from the function
			   (and from this "while" loop) */
314

315
			line = strchug_fast(line + 1);
316 317 318 319 320 321 322
			if (*line != 0 && *line != CONF_COMMENT) {
				config_param_free(ret);
				g_set_error(error_r, config_quark(), 0,
					    "line %i: Unknown tokens after '}'",
					    *count);
				return false;
			}
323

324
			return ret;
325
		}
326

327
		/* parse name and value */
328

329
		if (!config_read_name_value(ret, line, *count, &error)) {
330
			assert(*line != 0);
331 332 333 334
			config_param_free(ret);
			g_propagate_prefixed_error(error_r, error,
						   "line %i: ", *count);
			return NULL;
335
		}
336
	}
337 338
}

339 340
bool
config_read_file(const char *file, GError **error_r)
Avuton Olrich's avatar
Avuton Olrich committed
341 342 343
{
	FILE *fp;
	char string[MAX_STRING_SIZE + 1];
344
	int count = 0;
345 346
	struct config_entry *entry;
	struct config_param *param;
Warren Dukes's avatar
Warren Dukes committed
347

348 349
	g_debug("loading file %s", file);

Avuton Olrich's avatar
Avuton Olrich committed
350
	if (!(fp = fopen(file, "r"))) {
351 352
		g_set_error(error_r, config_quark(), errno,
			    "Failed to open %s: %s",
353
			    file, g_strerror(errno));
354
		return false;
Warren Dukes's avatar
Warren Dukes committed
355 356
	}

357
	while (fgets(string, MAX_STRING_SIZE, fp)) {
358 359 360
		char *line;
		const char *name, *value;
		GError *error = NULL;
361

362 363
		count++;

364
		line = strchug_fast(string);
365 366
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
367

368 369
		/* the first token in each line is the name, followed
		   by either the value or '{' */
370

371 372 373
		name = tokenizer_next_word(&line, &error);
		if (name == NULL) {
			assert(*line != 0);
374 375
			g_propagate_prefixed_error(error_r, error,
						   "line %i: ", count);
376
			fclose(fp);
377
			return false;
378
		}
379

380 381
		/* get the definition of that option, and check the
		   "repeatable" flag */
382

383
		entry = config_entry_get(name);
384 385 386 387
		if (entry == NULL) {
			g_set_error(error_r, config_quark(), 0,
				    "unrecognized parameter in config file at "
				    "line %i: %s\n", count, name);
388
			fclose(fp);
389 390
			return false;
		}
391

392
		if (entry->params != NULL && !entry->repeatable) {
393
			param = entry->params->data;
394 395 396 397
			g_set_error(error_r, config_quark(), 0,
				    "config parameter \"%s\" is first defined "
				    "on line %i and redefined on line %i\n",
				    name, param->line, count);
398
			fclose(fp);
399
			return false;
400 401
		}

402 403
		/* now parse the block or the value */

404
		if (entry->block) {
405 406
			/* it's a block, call config_read_block() */

407 408 409
			if (*line != '{') {
				g_set_error(error_r, config_quark(), 0,
					    "line %i: '{' expected", count);
410
				fclose(fp);
411 412
				return false;
			}
413

414
			line = strchug_fast(line + 1);
415 416 417 418
			if (*line != 0 && *line != CONF_COMMENT) {
				g_set_error(error_r, config_quark(), 0,
					    "line %i: Unknown tokens after '{'",
					    count);
419
				fclose(fp);
420 421
				return false;
			}
422

423
			param = config_read_block(fp, &count, string, error_r);
424 425
			if (param == NULL) {
				fclose(fp);
426
				return false;
427
			}
428 429 430 431 432 433
		} else {
			/* a string value */

			value = tokenizer_next_string(&line, &error);
			if (value == NULL) {
				if (*line == 0)
434 435 436 437 438 439 440 441 442 443
					g_set_error(error_r, config_quark(), 0,
						    "line %i: Value missing",
						    count);
				else {
					g_set_error(error_r, config_quark(), 0,
						    "line %i: %s", count,
						    error->message);
					g_error_free(error);
				}

444
				fclose(fp);
445
				return false;
446 447
			}

448 449 450 451
			if (*line != 0 && *line != CONF_COMMENT) {
				g_set_error(error_r, config_quark(), 0,
					    "line %i: Unknown tokens after value",
					    count);
452
				fclose(fp);
453 454
				return false;
			}
455 456 457

			param = config_new_param(value, count);
		}
458

459
		entry->params = g_slist_append(entry->params, param);
Warren Dukes's avatar
Warren Dukes committed
460
	}
461
	fclose(fp);
462 463

	return true;
464
}
Warren Dukes's avatar
Warren Dukes committed
465

466
const struct config_param *
467
config_get_next_param(const char *name, const struct config_param * last)
Avuton Olrich's avatar
Avuton Olrich committed
468
{
469
	struct config_entry *entry;
470
	GSList *node;
471
	struct config_param *param;
Warren Dukes's avatar
Warren Dukes committed
472

473 474
	entry = config_entry_get(name);
	if (entry == NULL)
Avuton Olrich's avatar
Avuton Olrich committed
475
		return NULL;
476

477
	node = entry->params;
478

Avuton Olrich's avatar
Avuton Olrich committed
479
	if (last) {
480 481 482 483 484
		node = g_slist_find(node, last);
		if (node == NULL)
			return NULL;

		node = g_slist_next(node);
Warren Dukes's avatar
Warren Dukes committed
485 486
	}

Avuton Olrich's avatar
Avuton Olrich committed
487 488
	if (node == NULL)
		return NULL;
489 490

	param = node->data;
491
	param->used = true;
492 493 494
	return param;
}

495 496
const char *
config_get_string(const char *name, const char *default_value)
Avuton Olrich's avatar
Avuton Olrich committed
497
{
498
	const struct config_param *param = config_get_param(name);
499

500 501
	if (param == NULL)
		return default_value;
502 503 504 505

	return param->value;
}

506 507
char *
config_dup_path(const char *name, GError **error_r)
508
{
509 510
	assert(error_r != NULL);
	assert(*error_r == NULL);
511

512
	const struct config_param *param = config_get_param(name);
513 514 515
	if (param == NULL)
		return NULL;

516
	char *path = parsePath(param->value, error_r);
517
	if (G_UNLIKELY(path == NULL))
518 519 520
		g_prefix_error(error_r,
			       "Invalid path in \"%s\" at line %i: ",
			       name, param->line);
521

522
	return path;
523 524
}

525 526 527 528 529 530 531 532 533 534 535 536
unsigned
config_get_unsigned(const char *name, unsigned default_value)
{
	const struct config_param *param = config_get_param(name);
	long value;
	char *endptr;

	if (param == NULL)
		return default_value;

	value = strtol(param->value, &endptr, 0);
	if (*endptr != 0 || value < 0)
537 538
		MPD_ERROR("Not a valid non-negative number in line %i",
			  param->line);
539 540 541 542

	return (unsigned)value;
}

543 544 545
unsigned
config_get_positive(const char *name, unsigned default_value)
{
546
	const struct config_param *param = config_get_param(name);
547 548 549 550 551 552 553 554
	long value;
	char *endptr;

	if (param == NULL)
		return default_value;

	value = strtol(param->value, &endptr, 0);
	if (*endptr != 0)
555
		MPD_ERROR("Not a valid number in line %i", param->line);
556 557

	if (value <= 0)
558
		MPD_ERROR("Not a positive number in line %i", param->line);
559 560 561 562

	return (unsigned)value;
}

563
const struct block_param *
564
config_get_block_param(const struct config_param * param, const char *name)
Avuton Olrich's avatar
Avuton Olrich committed
565
{
Max Kellermann's avatar
Max Kellermann committed
566 567 568
	if (param == NULL)
		return NULL;

569
	for (unsigned i = 0; i < param->num_block_params; i++) {
570
		if (0 == strcmp(name, param->block_params[i].name)) {
571
			struct block_param *bp = &param->block_params[i];
572
			bp->used = true;
573
			return bp;
Warren Dukes's avatar
Warren Dukes committed
574
		}
575 576
	}

577
	return NULL;
578 579
}

580
bool config_get_bool(const char *name, bool default_value)
581
{
582
	const struct config_param *param = config_get_param(name);
583
	bool success, value;
584

585 586
	if (param == NULL)
		return default_value;
587

588 589
	success = get_bool(param->value, &value);
	if (!success)
590 591 592
		MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
			  "(no, false, 0) on line %i\n",
			  name, param->line);
593

594
	return value;
595 596
}

597
const char *
598
config_get_block_string(const struct config_param *param, const char *name,
599 600
			const char *default_value)
{
601
	const struct block_param *bp = config_get_block_param(param, name);
602 603 604 605 606 607 608

	if (bp == NULL)
		return default_value;

	return bp->value;
}

609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
char *
config_dup_block_path(const struct config_param *param, const char *name,
		      GError **error_r)
{
	assert(error_r != NULL);
	assert(*error_r == NULL);

	const struct block_param *bp = config_get_block_param(param, name);
	if (bp == NULL)
		return NULL;

	char *path = parsePath(bp->value, error_r);
	if (G_UNLIKELY(path == NULL))
		g_prefix_error(error_r,
			       "Invalid path in \"%s\" at line %i: ",
			       name, bp->line);

	return path;
}

629
unsigned
630
config_get_block_unsigned(const struct config_param *param, const char *name,
631 632
			  unsigned default_value)
{
633
	const struct block_param *bp = config_get_block_param(param, name);
634 635 636 637 638 639 640 641
	long value;
	char *endptr;

	if (bp == NULL)
		return default_value;

	value = strtol(bp->value, &endptr, 0);
	if (*endptr != 0)
642
		MPD_ERROR("Not a valid number in line %i", bp->line);
643 644

	if (value < 0)
645
		MPD_ERROR("Not a positive number in line %i", bp->line);
646 647 648 649

	return (unsigned)value;
}

650
bool
651
config_get_block_bool(const struct config_param *param, const char *name,
652
		      bool default_value)
653
{
654
	const struct block_param *bp = config_get_block_param(param, name);
655
	bool success, value;
656

657 658
	if (bp == NULL)
		return default_value;
659

660
	success = get_bool(bp->value, &value);
661
	if (!success)
662 663 664
		MPD_ERROR("%s is not a boolean value (yes, true, 1) or "
			  "(no, false, 0) on line %i\n",
			  name, bp->line);
665

666
	return value;
667
}