2 * UCW Library -- Configuration files: parsing input streams
4 * (c) 2001--2006 Robert Spalek <robert@ucw.cz>
5 * (c) 2003--2012 Martin Mares <mj@ucw.cz>
7 * This software may be freely distributed and used according to the terms
8 * of the GNU Lesser General Public License.
13 #include <ucw/getopt.h>
14 #include <ucw/conf-internal.h>
15 #include <ucw/clists.h>
16 #include <ucw/mempool.h>
17 #include <ucw/fastbuf.h>
18 #include <ucw/chartype.h>
19 #include <ucw/string.h>
20 #include <ucw/stkstring.h>
26 /* Text file parser */
33 #define GBUF_PREFIX(x) split_##x
36 struct cf_parser_state {
37 const char *name_parse_fb;
38 struct fastbuf *parse_fb;
43 uns ends_by_brace; // the line is ended by "{"
50 get_line(struct cf_parser_state *p, char **msg)
52 int err = bgets_nodie(p->parse_fb, p->line_buf, MAX_LINE);
55 *msg = err < 0 ? "Line too long" : NULL;
58 p->line = p->line_buf;
59 while (Cblank(*p->line))
65 append(struct cf_parser_state *p, char *start, char *end)
67 uns len = end - start;
68 bb_grow(&p->copy_buf, p->copied + len + 1);
69 memcpy(p->copy_buf.ptr + p->copied, start, len);
71 p->copy_buf.ptr[p->copied-1] = 0;
75 get_word(struct cf_parser_state *p, uns is_command_name)
84 while (*line && *line != '\'')
86 append(p, start, line);
89 p->copy_buf.ptr[p->copied-1] = '\n';
90 if (!get_line(p, &msg))
91 return msg ? : "Unterminated apostrophe word at the end";
96 } else if (*line == '"') {
98 uns start_copy = p->copied;
103 if (*line == '"' && !escape)
105 else if (*line == '\\')
111 append(p, start, line);
115 p->copy_buf.ptr[p->copied-1] = '\n';
116 else // merge two lines
118 if (!get_line(p, &msg))
119 return msg ? : "Unterminated quoted word at the end";
124 char *tmp = stk_str_unesc(p->copy_buf.ptr + start_copy);
126 bb_grow(&p->copy_buf, start_copy + l + 1);
127 strcpy(p->copy_buf.ptr + start_copy, tmp);
128 p->copied = start_copy + l + 1;
131 // promised that *line is non-null and non-blank
133 while (*line && !Cblank(*line)
134 && *line != '{' && *line != '}' && *line != ';'
135 && (*line != '=' || !is_command_name))
137 if (*line == '=') { // nice for setting from a command-line
139 return "Assignment without a variable";
142 if (line == start) // already the first char is control
144 append(p, start, line);
146 while (Cblank(*line))
153 get_token(struct cf_parser_state *p, uns is_command_name, char **err)
157 if (!*p->line || *p->line == '#') {
158 if (!is_command_name || !get_line(p, err))
160 } else if (*p->line == ';') {
161 *err = get_word(p, 0);
162 if (!is_command_name || *err)
164 } else if (*p->line == '\\' && !p->line[1]) {
165 if (!get_line(p, err)) {
167 *err = "Last line ends by a backslash";
170 if (!*p->line || *p->line == '#')
171 msg(L_WARN, "The line %s:%d following a backslash is empty", p->name_parse_fb ? : "", p->line_num);
173 split_grow(&p->word_buf, p->words+1);
174 uns start = p->copied;
175 p->word_buf.ptr[p->words++] = p->copied;
176 *err = get_word(p, is_command_name);
177 return *err ? NULL : p->copy_buf.ptr + start;
183 split_command(struct cf_parser_state *p)
185 p->words = p->copied = p->ends_by_brace = 0;
186 char *msg, *start_word;
187 if (!(start_word = get_token(p, 1, &msg)))
189 if (*start_word == '{') // only one opening brace
190 return "Unexpected opening brace";
191 while (*p->line != '}') // stays for the next time
193 if (!(start_word = get_token(p, 0, &msg)))
195 if (*start_word == '{') {
196 p->words--; // discard the brace
197 p->ends_by_brace = 1;
204 /* Parsing multiple files */
207 parse_fastbuf(struct cf_context *cc, const char *name_fb, struct fastbuf *fb, uns depth)
209 struct cf_parser_state *p = cc->parser;
211 p = cc->parser = xmalloc_zero(sizeof(*p) + MAX_LINE);
212 p->name_parse_fb = name_fb;
215 p->line = p->line_buf;
221 err = split_command(p);
226 char *name = p->copy_buf.ptr + p->word_buf.ptr[0];
227 char *pars[p->words-1];
228 for (uns i=1; i<p->words; i++)
229 pars[i-1] = p->copy_buf.ptr + p->word_buf.ptr[i];
230 if (!strcasecmp(name, "include"))
233 err = "Expecting one filename";
235 err = "Too many nested files";
236 else if (*p->line && *p->line != '#') // because the contents of line_buf is not re-entrant and will be cleared
237 err = "The include command must be the last one on a line";
240 struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
242 err = cf_printf("Cannot open file %s: %m", pars[0]);
245 uns ll = p->line_num;
246 err = parse_fastbuf(cc, stk_strdup(pars[0]), new_fb, depth+1);
254 enum cf_operation op;
255 char *c = strchr(name, ':');
257 op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
260 switch (Clocase(*c)) {
261 case 's': op = OP_SET; break;
262 case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
263 case 'a': switch (Clocase(c[1])) {
264 case 'p': op = OP_APPEND; break;
265 case 'f': op = OP_AFTER; break;
266 default: op = OP_ALL;
268 case 'p': op = OP_PREPEND; break;
269 case 'r': op = (c[1] && Clocase(c[2]) == 'm') ? OP_REMOVE : OP_RESET; break;
270 case 'e': op = OP_EDIT; break;
271 case 'b': op = OP_BEFORE; break;
272 default: op = OP_SET; break;
274 if (strcasecmp(c, cf_op_names[op])) {
275 err = cf_printf("Unknown operation %s", c);
279 if (p->ends_by_brace)
281 err = cf_interpret_line(cc, name, op, p->words-1, pars);
287 msg(L_ERROR, "File %s, line %d: %s", name_fb, p->line_num, err);
288 else if (p->line_num == 1)
289 msg(L_ERROR, "Manual setting of configuration: %s", err);
291 msg(L_ERROR, "Manual setting of configuration, line %d: %s", p->line_num, err);
292 return "included from here";
296 done_stack(struct cf_context *cc)
298 if (cf_check_stack(cc))
300 if (cf_commit_all(cc->postpone_commit ? CF_NO_COMMIT : cc->everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
302 if (!cc->postpone_commit)
303 cc->everything_committed = 1;
308 load_file(struct cf_context *cc, const char *file)
311 struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
313 msg(L_ERROR, "Cannot open %s: %m", file);
316 char *err_msg = parse_fastbuf(cc, file, fb, 0);
318 return !!err_msg || done_stack(cc);
322 load_string(struct cf_context *cc, const char *string)
326 fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
327 char *msg = parse_fastbuf(cc, NULL, &fb, 0);
328 return !!msg || done_stack(cc);
331 /* Safe loading and reloading */
333 struct conf_entry { /* We remember a list of actions to apply upon reload */
343 cf_remember_entry(struct cf_context *cc, uns type, const char *arg)
345 if (!cc->need_journal)
347 if (!cc->postpone_commit)
349 struct conf_entry *ce = cf_malloc(sizeof(*ce));
351 ce->arg = cf_strdup(arg);
352 clist_add_tail(&cc->conf_entries, &ce->n);
356 cf_reload(const char *file)
358 struct cf_context *cc = cf_get_context();
360 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
361 uns ec = cc->everything_committed;
362 cc->everything_committed = 0;
365 clist_move(&old_entries, &cc->conf_entries);
366 cc->postpone_commit = 1;
370 err = load_file(cc, file);
372 CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
373 if (ce->type == CE_FILE)
374 err |= load_file(cc, ce->arg);
376 err |= load_string(cc, ce->arg);
379 cf_remember_entry(cc, ce->type, ce->arg);
382 cc->postpone_commit = 0;
384 err |= done_stack(cc);
388 cf_journal_commit_transaction(1, NULL);
390 cc->everything_committed = ec;
391 cf_journal_rollback_transaction(1, oldj);
393 clist_move(&cc->conf_entries, &old_entries);
399 cf_load(const char *file)
401 struct cf_context *cc = cf_get_context();
402 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
403 int err = load_file(cc, file);
405 cf_journal_commit_transaction(1, oldj);
406 cf_remember_entry(cc, CE_FILE, file);
409 cf_journal_rollback_transaction(1, oldj);
414 cf_set(const char *string)
416 struct cf_context *cc = cf_get_context();
417 struct cf_journal_item *oldj = cf_journal_new_transaction(0);
418 int err = load_string(cc, string);
420 cf_journal_commit_transaction(0, oldj);
421 cf_remember_entry(cc, CE_STRING, string);
423 cf_journal_rollback_transaction(0, oldj);
427 /* Command-line parser */
429 #ifndef CONFIG_UCW_DEFAULT_CONFIG
430 #define CONFIG_UCW_DEFAULT_CONFIG NULL
432 char *cf_def_file = CONFIG_UCW_DEFAULT_CONFIG;
434 #ifndef CONFIG_UCW_ENV_VAR_CONFIG
435 #define CONFIG_UCW_ENV_VAR_CONFIG NULL
437 char *cf_env_file = CONFIG_UCW_ENV_VAR_CONFIG;
440 load_default(struct cf_context *cc)
442 if (cc->def_loaded++)
447 if (cf_env_file && (env = getenv(cf_env_file)))
450 die("Cannot load config file %s", env);
452 else if (cf_load(cf_def_file))
453 die("Cannot load default config %s", cf_def_file);
457 // We need to create an empty pool and initialize all configuration items
458 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
461 cf_journal_commit_transaction(1, oldj);
466 final_commit(struct cf_context *cc)
468 if (cc->postpone_commit) {
469 cc->postpone_commit = 0;
471 die("Cannot commit after the initialization");
476 cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
478 struct cf_context *cc = cf_obtain_context();
479 cc->postpone_commit = 1;
482 int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
483 if (res == 'S' || res == 'C' || res == 0x64436667)
485 if (cc->other_options)
486 die("The -S and -C options must precede all other arguments");
490 die("Cannot set %s", optarg);
491 } else if (res == 'C') {
493 die("Cannot load config file %s", optarg);
495 #ifdef CONFIG_UCW_DEBUG
496 else { /* --dumpconfig */
499 struct fastbuf *b = bfdopen(1, 4096);
506 /* unhandled option or end of options */
507 if (res != ':' && res != '?') {