ConfigFile.cxx 6.3 KB
Newer Older
1
/*
2
 * Copyright 2003-2016 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"
21
#include "ConfigFile.hxx"
22
#include "Data.hxx"
23
#include "Param.hxx"
24
#include "Block.hxx"
25
#include "ConfigTemplates.hxx"
26
#include "util/Tokenizer.hxx"
27
#include "util/StringUtil.hxx"
28
#include "util/Domain.hxx"
29
#include "util/RuntimeError.hxx"
30
#include "fs/Path.hxx"
31 32
#include "fs/io/FileReader.hxx"
#include "fs/io/BufferedReader.hxx"
33
#include "Log.hxx"
34

35 36
#include <memory>

37
#include <assert.h>
Warren Dukes's avatar
Warren Dukes committed
38

39
static constexpr char CONF_COMMENT = '#';
40

41 42
static constexpr Domain config_file_domain("config_file");

43 44
static void
config_read_name_value(ConfigBlock &block, char *input, unsigned line)
45
{
46 47
	Tokenizer tokenizer(input);

48 49
	const char *name = tokenizer.NextWord();
	assert(name != nullptr);
50

51 52 53
	const char *value = tokenizer.NextString();
	if (value == nullptr)
		throw std::runtime_error("Value missing");
54

55 56
	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT)
		throw std::runtime_error("Unknown tokens after value");
57

58
	const BlockParam *bp = block.GetBlockParam(name);
59 60 61
	if (bp != nullptr)
		throw FormatRuntimeError("\"%s\" is duplicate, first defined on line %i",
					 name, bp->line);
62

63
	block.AddBlockParam(name, value, line);
64 65
}

66
static ConfigBlock *
67
config_read_block(BufferedReader &reader)
68
try {
69
	std::unique_ptr<ConfigBlock> block(new ConfigBlock(reader.GetLineNumber()));
70

71
	while (true) {
72
		char *line = reader.ReadLine();
73 74
		if (line == nullptr)
			throw std::runtime_error("Expected '}' before end-of-file");
75

76
		line = StripLeft(line);
77 78
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
79

80 81 82
		if (*line == '}') {
			/* end of this block; return from the function
			   (and from this "while" loop) */
83

84
			line = StripLeft(line + 1);
85 86
			if (*line != 0 && *line != CONF_COMMENT)
				throw std::runtime_error("Unknown tokens after '}'");
87

88
			return block.release();
89
		}
90

91
		/* parse name and value */
92

93 94
		config_read_name_value(*block, line,
				       reader.GetLineNumber());
95
	}
96 97
} catch (...) {
	std::throw_with_nested(FormatRuntimeError("Error in line %u", reader.GetLineNumber()));
98 99
}

100 101 102 103 104 105 106 107 108 109 110 111 112
gcc_nonnull_all
static void
Append(ConfigBlock *&head, ConfigBlock *p)
{
	assert(p->next == nullptr);

	auto **i = &head;
	while (*i != nullptr)
		i = &(*i)->next;

	*i = p;
}

113
static void
114
ReadConfigBlock(ConfigData &config_data, BufferedReader &reader,
115
		const char *name, ConfigBlockOption o,
116
		Tokenizer &tokenizer)
117 118 119 120 121 122 123
{
	const unsigned i = unsigned(o);
	const ConfigTemplate &option = config_block_templates[i];
	ConfigBlock *&head = config_data.blocks[i];

	if (head != nullptr && !option.repeatable) {
		ConfigBlock *block = head;
124 125 126 127
		throw FormatRuntimeError("config parameter \"%s\" is first defined "
					 "on line %d and redefined on line %u\n",
					 name, block->line,
					 reader.GetLineNumber());
128 129 130 131
	}

	/* now parse the block or the value */

132 133 134
	if (tokenizer.CurrentChar() != '{')
		throw FormatRuntimeError("line %u: '{' expected",
					 reader.GetLineNumber());
135 136

	char *line = StripLeft(tokenizer.Rest() + 1);
137 138 139
	if (*line != 0 && *line != CONF_COMMENT)
		throw FormatRuntimeError("line %u: Unknown tokens after '{'",
					 reader.GetLineNumber());
140

141 142
	auto *param = config_read_block(reader);
	assert(param != nullptr);
143 144 145
	Append(head, param);
}

146 147 148 149 150 151 152 153 154 155 156 157 158
gcc_nonnull_all
static void
Append(config_param *&head, config_param *p)
{
	assert(p->next == nullptr);

	config_param **i = &head;
	while (*i != nullptr)
		i = &(*i)->next;

	*i = p;
}

159
static void
160 161
ReadConfigParam(ConfigData &config_data, BufferedReader &reader,
		const char *name, ConfigOption o,
162
		Tokenizer &tokenizer)
Avuton Olrich's avatar
Avuton Olrich committed
163
{
164
	const unsigned i = unsigned(o);
165
	const ConfigTemplate &option = config_param_templates[i];
166 167 168 169
	config_param *&head = config_data.params[i];

	if (head != nullptr && !option.repeatable) {
		struct config_param *param = head;
170 171 172 173
		throw FormatRuntimeError("config parameter \"%s\" is first defined "
					 "on line %d and redefined on line %u\n",
					 name, param->line,
					 reader.GetLineNumber());
174 175 176 177
	}

	/* now parse the block or the value */

178 179 180 181
	const char *value = tokenizer.NextString();
	if (value == nullptr)
		throw FormatRuntimeError("line %u: Value missing",
					 reader.GetLineNumber());
182

183 184 185
	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT)
		throw FormatRuntimeError("line %u: Unknown tokens after value",
					 reader.GetLineNumber());
186

187
	auto *param = new config_param(value, reader.GetLineNumber());
188 189 190
	Append(head, param);
}

191 192
static void
ReadConfigFile(ConfigData &config_data, BufferedReader &reader)
193
{
194 195 196
	while (true) {
		char *line = reader.ReadLine();
		if (line == nullptr)
197
			return;
198 199

		line = StripLeft(line);
200 201
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
202

203 204
		/* the first token in each line is the name, followed
		   by either the value or '{' */
205

206
		Tokenizer tokenizer(line);
207 208
		const char *name = tokenizer.NextWord();
		assert(name != nullptr);
209

210 211
		/* get the definition of that option, and check the
		   "repeatable" flag */
212

213
		const ConfigOption o = ParseConfigOptionName(name);
214
		ConfigBlockOption bo;
215
		if (o != ConfigOption::MAX) {
216 217
			ReadConfigParam(config_data, reader, name, o,
					tokenizer);
218
		} else if ((bo = ParseConfigBlockOptionName(name)) != ConfigBlockOption::MAX) {
219 220
			ReadConfigBlock(config_data, reader, name, bo,
					tokenizer);
221
		} else {
222 223 224
			throw FormatRuntimeError("unrecognized parameter in config file at "
						 "line %u: %s\n",
						 reader.GetLineNumber(), name);
225
		}
Warren Dukes's avatar
Warren Dukes committed
226
	}
227
}
228

229 230
void
ReadConfigFile(ConfigData &config_data, Path path)
231 232 233 234
{
	assert(!path.IsNull());
	const std::string path_utf8 = path.ToUTF8();

235
	FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
236

237
	FileReader file(path);
238

239
	BufferedReader reader(file);
240
	ReadConfigFile(config_data, reader);
241
}