ConfigFile.cxx 6.26 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 22
#include "ConfigFile.hxx"
#include "ConfigData.hxx"
23
#include "ConfigTemplates.hxx"
24
#include "util/Tokenizer.hxx"
25
#include "util/StringUtil.hxx"
26 27
#include "util/Error.hxx"
#include "util/Domain.hxx"
28
#include "fs/Limits.hxx"
29
#include "fs/Path.hxx"
30
#include "fs/FileSystem.hxx"
31
#include "Log.hxx"
32

33
#include <assert.h>
34
#include <stdio.h>
Max Kellermann's avatar
Max Kellermann committed
35

Eric Wong's avatar
Eric Wong committed
36
#define MAX_STRING_SIZE	MPD_PATH_MAX+80
Warren Dukes's avatar
Warren Dukes committed
37

38 39
#define CONF_COMMENT		'#'

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

42 43
static bool
config_read_name_value(struct config_param *param, char *input, unsigned line,
44
		       Error &error)
45
{
46 47
	Tokenizer tokenizer(input);

48
	const char *name = tokenizer.NextWord(error);
49
	if (name == nullptr) {
50
		assert(!tokenizer.IsEnd());
51 52 53
		return false;
	}

54
	const char *value = tokenizer.NextString(error);
55
	if (value == nullptr) {
56
		if (tokenizer.IsEnd()) {
57
			error.Set(config_file_domain, "Value missing");
58
		} else {
59
			assert(error.IsDefined());
60 61 62 63 64
		}

		return false;
	}

65
	if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
66
		error.Set(config_file_domain, "Unknown tokens after value");
67 68 69
		return false;
	}

70
	const struct block_param *bp = param->GetBlockParam(name);
71
	if (bp != nullptr) {
72 73 74
		error.Format(config_file_domain,
			     "\"%s\" is duplicate, first defined on line %i",
			     name, bp->line);
75 76 77
		return false;
	}

78
	param->AddBlockParam(name, value, line);
79
	return true;
80 81
}

82
static struct config_param *
83
config_read_block(FILE *fp, int *count, char *string, Error &error)
Avuton Olrich's avatar
Avuton Olrich committed
84
{
85
	struct config_param *ret = new config_param(*count);
86

87 88
	while (true) {
		char *line;
89

90
		line = fgets(string, MAX_STRING_SIZE, fp);
91
		if (line == nullptr) {
92
			delete ret;
93 94
			error.Set(config_file_domain,
				  "Expected '}' before end-of-file");
95
			return nullptr;
96
		}
97

98
		(*count)++;
99
		line = StripLeft(line);
100 101
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
102

103 104 105
		if (*line == '}') {
			/* end of this block; return from the function
			   (and from this "while" loop) */
106

107
			line = StripLeft(line + 1);
108
			if (*line != 0 && *line != CONF_COMMENT) {
109
				delete ret;
110 111 112
				error.Format(config_file_domain,
					     "line %i: Unknown tokens after '}'",
					     *count);
Max Kellermann's avatar
Max Kellermann committed
113
				return nullptr;
114
			}
115

116
			return ret;
117
		}
118

119
		/* parse name and value */
120

121
		if (!config_read_name_value(ret, line, *count, error)) {
122
			assert(*line != 0);
123
			delete ret;
124
			error.FormatPrefix("line %i: ", *count);
125
			return nullptr;
126
		}
127
	}
128 129
}

130 131 132 133 134 135 136 137 138 139 140 141 142
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;
}

143
static bool
144
ReadConfigFile(ConfigData &config_data, FILE *fp, Error &error)
Avuton Olrich's avatar
Avuton Olrich committed
145
{
146
	assert(fp != nullptr);
147

Avuton Olrich's avatar
Avuton Olrich committed
148
	char string[MAX_STRING_SIZE + 1];
149
	int count = 0;
150
	struct config_param *param;
Warren Dukes's avatar
Warren Dukes committed
151

152
	while (fgets(string, MAX_STRING_SIZE, fp)) {
153 154
		char *line;
		const char *name, *value;
155

156 157
		count++;

158
		line = StripLeft(string);
159 160
		if (*line == 0 || *line == CONF_COMMENT)
			continue;
161

162 163
		/* the first token in each line is the name, followed
		   by either the value or '{' */
164

165
		Tokenizer tokenizer(line);
166
		name = tokenizer.NextWord(error);
167
		if (name == nullptr) {
168
			assert(!tokenizer.IsEnd());
169
			error.FormatPrefix("line %i: ", count);
170
			return false;
171
		}
172

173 174
		/* get the definition of that option, and check the
		   "repeatable" flag */
175

176 177
		const ConfigOption o = ParseConfigOptionName(name);
		if (o == CONF_MAX) {
178 179 180
			error.Format(config_file_domain,
				     "unrecognized parameter in config file at "
				     "line %i: %s\n", count, name);
181 182
			return false;
		}
183

184
		const unsigned i = unsigned(o);
185
		const ConfigTemplate &option = config_templates[i];
186
		config_param *&head = config_data.params[i];
187

188 189
		if (head != nullptr && !option.repeatable) {
			param = head;
190 191 192 193
			error.Format(config_file_domain,
				     "config parameter \"%s\" is first defined "
				     "on line %i and redefined on line %i\n",
				     name, param->line, count);
194
			return false;
195 196
		}

197 198
		/* now parse the block or the value */

199
		if (option.block) {
200 201
			/* it's a block, call config_read_block() */

202
			if (tokenizer.CurrentChar() != '{') {
203 204
				error.Format(config_file_domain,
					     "line %i: '{' expected", count);
205 206
				return false;
			}
207

208
			line = StripLeft(tokenizer.Rest() + 1);
209
			if (*line != 0 && *line != CONF_COMMENT) {
210 211 212
				error.Format(config_file_domain,
					     "line %i: Unknown tokens after '{'",
					     count);
213 214
				return false;
			}
215

216
			param = config_read_block(fp, &count, string, error);
217
			if (param == nullptr) {
218
				return false;
219
			}
220 221 222
		} else {
			/* a string value */

223
			value = tokenizer.NextString(error);
224
			if (value == nullptr) {
225
				if (tokenizer.IsEnd())
226 227 228 229 230
					error.Format(config_file_domain,
						     "line %i: Value missing",
						     count);
				else
					error.FormatPrefix("line %i: ", count);
231 232

				return false;
233 234
			}

235 236
			if (!tokenizer.IsEnd() &&
			    tokenizer.CurrentChar() != CONF_COMMENT) {
237 238 239
				error.Format(config_file_domain,
					     "line %i: Unknown tokens after value",
					     count);
240 241
				return false;
			}
242

243
			param = new config_param(value, count);
244
		}
245

246
		Append(head, param);
Warren Dukes's avatar
Warren Dukes committed
247
	}
248 249

	return true;
250
}
251 252

bool
253
ReadConfigFile(ConfigData &config_data, Path path, Error &error)
254 255 256 257
{
	assert(!path.IsNull());
	const std::string path_utf8 = path.ToUTF8();

258
	FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
259

260
	FILE *fp = FOpen(path, FOpenMode::ReadText);
261
	if (fp == nullptr) {
262
		error.FormatErrno("Failed to open %s", path_utf8.c_str());
263 264 265
		return false;
	}

266
	bool result = ReadConfigFile(config_data, fp, error);
267 268 269
	fclose(fp);
	return result;
}