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

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

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

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

123
	return false;
124 125
}

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

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

	ret->line = line;

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

	return ret;
}

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

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

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

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

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

	config_param_free(param);
}

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

	return NULL;
}

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

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

192
void config_global_init(void)
Avuton Olrich's avatar
Avuton Olrich committed
193
{
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
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);
	}
}

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

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

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

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

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

241 242 243
	bp->name = g_strdup(name);
	bp->value = g_strdup(value);
	bp->line = line;
244
	bp->used = false;
245 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
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;
	}

276 277 278 279 280 281 282 283 284 285
	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;
286 287
}

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

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

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

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

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

314
			line = strchug_fast(line + 1);
315 316 317 318 319 320 321
			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;
			}
322

323
			return ret;
324
		}
325

326
		/* parse name and value */
327

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

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

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

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

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

361 362
		count++;

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

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

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

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

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

391
		if (entry->params != NULL && !entry->repeatable) {
392
			param = entry->params->data;
393 394 395 396
			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);
397
			fclose(fp);
398
			return false;
399 400
		}

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

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

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

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

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

			value = tokenizer_next_string(&line, &error);
			if (value == NULL) {
				if (*line == 0)
433 434 435 436 437 438 439 440 441 442
					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);
				}

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

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

			param = config_new_param(value, count);
		}
457

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

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

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

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

476
	node = entry->params;
477

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

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

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

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

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

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

	return param->value;
}

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

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

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

521
	return path;
522 523
}

524 525 526 527 528 529 530 531 532 533 534 535
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)
536 537
		MPD_ERROR("Not a valid non-negative number in line %i",
			  param->line);
538 539 540 541

	return (unsigned)value;
}

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

	if (param == NULL)
		return default_value;

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

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

	return (unsigned)value;
}

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

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

576
	return NULL;
577 578
}

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

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

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

593
	return value;
594 595
}

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

	if (bp == NULL)
		return default_value;

	return bp->value;
}

608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627
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;
}

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

	if (bp == NULL)
		return default_value;

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

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

	return (unsigned)value;
}

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

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

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

665
	return value;
666
}