~ubuntu-branches/ubuntu/utopic/mpd/utopic-proposed

« back to all changes in this revision

Viewing changes to src/ConfigFile.cxx

  • Committer: Package Import Robot
  • Author(s): Steve Kowalik
  • Date: 2013-11-12 18:17:40 UTC
  • mfrom: (2.2.36 sid)
  • Revision ID: package-import@ubuntu.com-20131112181740-72aa4zihehoobedp
Tags: 0.18.3-1ubuntu1
* Merge from Debian unstable.  Remaining changes:
  - Add libmp3lame-dev to Build-Depends, and enable LAME.
  - Read the user for the daemon from the config file in the init script.
  - Move avahi-daemon from Suggests to Recommends.
  - Added apport hook to include user configuration file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2003-2013 The Music Player Daemon Project
 
3
 * http://www.musicpd.org
 
4
 *
 
5
 * This program is free software; you can redistribute it and/or modify
 
6
 * it under the terms of the GNU General Public License as published by
 
7
 * the Free Software Foundation; either version 2 of the License, or
 
8
 * (at your option) any later version.
 
9
 *
 
10
 * This program is distributed in the hope that it will be useful,
 
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 * GNU General Public License for more details.
 
14
 *
 
15
 * You should have received a copy of the GNU General Public License along
 
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
 
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 
18
 */
 
19
 
 
20
#include "config.h"
 
21
#include "ConfigFile.hxx"
 
22
#include "ConfigError.hxx"
 
23
#include "ConfigData.hxx"
 
24
#include "ConfigTemplates.hxx"
 
25
#include "util/Tokenizer.hxx"
 
26
#include "util/StringUtil.hxx"
 
27
#include "util/Error.hxx"
 
28
#include "util/Domain.hxx"
 
29
#include "fs/Limits.hxx"
 
30
#include "fs/Path.hxx"
 
31
#include "fs/FileSystem.hxx"
 
32
#include "Log.hxx"
 
33
 
 
34
#include <assert.h>
 
35
#include <string.h>
 
36
#include <stdio.h>
 
37
#include <errno.h>
 
38
 
 
39
#define MAX_STRING_SIZE MPD_PATH_MAX+80
 
40
 
 
41
#define CONF_COMMENT            '#'
 
42
 
 
43
static constexpr Domain config_file_domain("config_file");
 
44
 
 
45
static bool
 
46
config_read_name_value(struct config_param *param, char *input, unsigned line,
 
47
                       Error &error)
 
48
{
 
49
        Tokenizer tokenizer(input);
 
50
 
 
51
        const char *name = tokenizer.NextWord(error);
 
52
        if (name == nullptr) {
 
53
                assert(!tokenizer.IsEnd());
 
54
                return false;
 
55
        }
 
56
 
 
57
        const char *value = tokenizer.NextString(error);
 
58
        if (value == nullptr) {
 
59
                if (tokenizer.IsEnd()) {
 
60
                        error.Set(config_file_domain, "Value missing");
 
61
                } else {
 
62
                        assert(error.IsDefined());
 
63
                }
 
64
 
 
65
                return false;
 
66
        }
 
67
 
 
68
        if (!tokenizer.IsEnd() && tokenizer.CurrentChar() != CONF_COMMENT) {
 
69
                error.Set(config_file_domain, "Unknown tokens after value");
 
70
                return false;
 
71
        }
 
72
 
 
73
        const struct block_param *bp = param->GetBlockParam(name);
 
74
        if (bp != nullptr) {
 
75
                error.Format(config_file_domain,
 
76
                             "\"%s\" is duplicate, first defined on line %i",
 
77
                             name, bp->line);
 
78
                return false;
 
79
        }
 
80
 
 
81
        param->AddBlockParam(name, value, line);
 
82
        return true;
 
83
}
 
84
 
 
85
static struct config_param *
 
86
config_read_block(FILE *fp, int *count, char *string, Error &error)
 
87
{
 
88
        struct config_param *ret = new config_param(*count);
 
89
 
 
90
        while (true) {
 
91
                char *line;
 
92
 
 
93
                line = fgets(string, MAX_STRING_SIZE, fp);
 
94
                if (line == nullptr) {
 
95
                        delete ret;
 
96
                        error.Set(config_file_domain,
 
97
                                  "Expected '}' before end-of-file");
 
98
                        return nullptr;
 
99
                }
 
100
 
 
101
                (*count)++;
 
102
                line = strchug_fast(line);
 
103
                if (*line == 0 || *line == CONF_COMMENT)
 
104
                        continue;
 
105
 
 
106
                if (*line == '}') {
 
107
                        /* end of this block; return from the function
 
108
                           (and from this "while" loop) */
 
109
 
 
110
                        line = strchug_fast(line + 1);
 
111
                        if (*line != 0 && *line != CONF_COMMENT) {
 
112
                                delete ret;
 
113
                                error.Format(config_file_domain,
 
114
                                             "line %i: Unknown tokens after '}'",
 
115
                                             *count);
 
116
                                return nullptr;
 
117
                        }
 
118
 
 
119
                        return ret;
 
120
                }
 
121
 
 
122
                /* parse name and value */
 
123
 
 
124
                if (!config_read_name_value(ret, line, *count, error)) {
 
125
                        assert(*line != 0);
 
126
                        delete ret;
 
127
                        error.FormatPrefix("line %i: ", *count);
 
128
                        return nullptr;
 
129
                }
 
130
        }
 
131
}
 
132
 
 
133
gcc_nonnull_all
 
134
static void
 
135
Append(config_param *&head, config_param *p)
 
136
{
 
137
        assert(p->next == nullptr);
 
138
 
 
139
        config_param **i = &head;
 
140
        while (*i != nullptr)
 
141
                i = &(*i)->next;
 
142
 
 
143
        *i = p;
 
144
}
 
145
 
 
146
static bool
 
147
ReadConfigFile(ConfigData &config_data, FILE *fp, Error &error)
 
148
{
 
149
        assert(fp != nullptr);
 
150
 
 
151
        char string[MAX_STRING_SIZE + 1];
 
152
        int count = 0;
 
153
        struct config_param *param;
 
154
 
 
155
        while (fgets(string, MAX_STRING_SIZE, fp)) {
 
156
                char *line;
 
157
                const char *name, *value;
 
158
 
 
159
                count++;
 
160
 
 
161
                line = strchug_fast(string);
 
162
                if (*line == 0 || *line == CONF_COMMENT)
 
163
                        continue;
 
164
 
 
165
                /* the first token in each line is the name, followed
 
166
                   by either the value or '{' */
 
167
 
 
168
                Tokenizer tokenizer(line);
 
169
                name = tokenizer.NextWord(error);
 
170
                if (name == nullptr) {
 
171
                        assert(!tokenizer.IsEnd());
 
172
                        error.FormatPrefix("line %i: ", count);
 
173
                        return false;
 
174
                }
 
175
 
 
176
                /* get the definition of that option, and check the
 
177
                   "repeatable" flag */
 
178
 
 
179
                const ConfigOption o = ParseConfigOptionName(name);
 
180
                if (o == CONF_MAX) {
 
181
                        error.Format(config_file_domain,
 
182
                                     "unrecognized parameter in config file at "
 
183
                                     "line %i: %s\n", count, name);
 
184
                        return false;
 
185
                }
 
186
 
 
187
                const unsigned i = unsigned(o);
 
188
                const ConfigTemplate &option = config_templates[i];
 
189
                config_param *&head = config_data.params[i];
 
190
 
 
191
                if (head != nullptr && !option.repeatable) {
 
192
                        param = head;
 
193
                        error.Format(config_file_domain,
 
194
                                     "config parameter \"%s\" is first defined "
 
195
                                     "on line %i and redefined on line %i\n",
 
196
                                     name, param->line, count);
 
197
                        return false;
 
198
                }
 
199
 
 
200
                /* now parse the block or the value */
 
201
 
 
202
                if (option.block) {
 
203
                        /* it's a block, call config_read_block() */
 
204
 
 
205
                        if (tokenizer.CurrentChar() != '{') {
 
206
                                error.Format(config_file_domain,
 
207
                                             "line %i: '{' expected", count);
 
208
                                return false;
 
209
                        }
 
210
 
 
211
                        line = strchug_fast(tokenizer.Rest() + 1);
 
212
                        if (*line != 0 && *line != CONF_COMMENT) {
 
213
                                error.Format(config_file_domain,
 
214
                                             "line %i: Unknown tokens after '{'",
 
215
                                             count);
 
216
                                return false;
 
217
                        }
 
218
 
 
219
                        param = config_read_block(fp, &count, string, error);
 
220
                        if (param == nullptr) {
 
221
                                return false;
 
222
                        }
 
223
                } else {
 
224
                        /* a string value */
 
225
 
 
226
                        value = tokenizer.NextString(error);
 
227
                        if (value == nullptr) {
 
228
                                if (tokenizer.IsEnd())
 
229
                                        error.Format(config_file_domain,
 
230
                                                     "line %i: Value missing",
 
231
                                                     count);
 
232
                                else
 
233
                                        error.FormatPrefix("line %i: ", count);
 
234
 
 
235
                                return false;
 
236
                        }
 
237
 
 
238
                        if (!tokenizer.IsEnd() &&
 
239
                            tokenizer.CurrentChar() != CONF_COMMENT) {
 
240
                                error.Format(config_file_domain,
 
241
                                             "line %i: Unknown tokens after value",
 
242
                                             count);
 
243
                                return false;
 
244
                        }
 
245
 
 
246
                        param = new config_param(value, count);
 
247
                }
 
248
 
 
249
                Append(head, param);
 
250
        }
 
251
 
 
252
        return true;
 
253
}
 
254
 
 
255
bool
 
256
ReadConfigFile(ConfigData &config_data, Path path, Error &error)
 
257
{
 
258
        assert(!path.IsNull());
 
259
        const std::string path_utf8 = path.ToUTF8();
 
260
 
 
261
        FormatDebug(config_file_domain, "loading file %s", path_utf8.c_str());
 
262
 
 
263
        FILE *fp = FOpen(path, FOpenMode::ReadText);
 
264
        if (fp == nullptr) {
 
265
                error.FormatErrno("Failed to open %s", path_utf8.c_str());
 
266
                return false;
 
267
        }
 
268
 
 
269
        bool result = ReadConfigFile(config_data, fp, error);
 
270
        fclose(fp);
 
271
        return result;
 
272
}