2 * UCW Library -- Configuration files: parsing input streams
4 * (c) 2001--2006 Robert Spalek <robert@ucw.cz>
5 * (c) 2003--2009 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 */
28 static const char *name_parse_fb;
29 static struct fastbuf *parse_fb;
33 static char line_buf[MAX_LINE];
34 static char *line = line_buf;
41 #define GBUF_PREFIX(x) split_##x
43 static split_t word_buf;
45 static uns ends_by_brace; // the line is ended by "{"
50 int err = bgets_nodie(parse_fb, line_buf, MAX_LINE);
53 *msg = err < 0 ? "Line too long" : NULL;
63 append(char *start, char *end)
65 uns len = end - start;
66 bb_grow(©_buf, copied + len + 1);
67 memcpy(copy_buf.ptr + copied, start, len);
69 copy_buf.ptr[copied-1] = 0;
73 get_word(uns is_command_name)
80 while (*line && *line != '\'')
85 copy_buf.ptr[copied-1] = '\n';
87 return msg ? : "Unterminated apostrophe word at the end";
91 } else if (*line == '"') {
93 uns start_copy = copied;
98 if (*line == '"' && !escape)
100 else if (*line == '\\')
110 copy_buf.ptr[copied-1] = '\n';
111 else // merge two lines
114 return msg ? : "Unterminated quoted word at the end";
118 char *tmp = stk_str_unesc(copy_buf.ptr + start_copy);
120 bb_grow(©_buf, start_copy + l + 1);
121 strcpy(copy_buf.ptr + start_copy, tmp);
122 copied = start_copy + l + 1;
125 // promised that *line is non-null and non-blank
127 while (*line && !Cblank(*line)
128 && *line != '{' && *line != '}' && *line != ';'
129 && (*line != '=' || !is_command_name))
131 if (*line == '=') { // nice for setting from a command-line
133 return "Assignment without a variable";
136 if (line == start) // already the first char is control
140 while (Cblank(*line))
146 get_token(uns is_command_name, char **err)
150 if (!*line || *line == '#') {
151 if (!is_command_name || !get_line(err))
153 } else if (*line == ';') {
155 if (!is_command_name || *err)
157 } else if (*line == '\\' && !line[1]) {
158 if (!get_line(err)) {
160 *err = "Last line ends by a backslash";
163 if (!*line || *line == '#')
164 msg(L_WARN, "The line %s:%d following a backslash is empty", name_parse_fb ? : "", line_num);
166 split_grow(&word_buf, words+1);
168 word_buf.ptr[words++] = copied;
169 *err = get_word(is_command_name);
170 return *err ? NULL : copy_buf.ptr + start;
178 words = copied = ends_by_brace = 0;
179 char *msg, *start_word;
180 if (!(start_word = get_token(1, &msg)))
182 if (*start_word == '{') // only one opening brace
183 return "Unexpected opening brace";
184 while (*line != '}') // stays for the next time
186 if (!(start_word = get_token(0, &msg)))
188 if (*start_word == '{') {
189 words--; // discard the brace
197 /* Parsing multiple files */
200 parse_fastbuf(const char *name_fb, struct fastbuf *fb, uns depth)
203 name_parse_fb = name_fb;
210 err = split_command();
215 char *name = copy_buf.ptr + word_buf.ptr[0];
217 for (uns i=1; i<words; i++)
218 pars[i-1] = copy_buf.ptr + word_buf.ptr[i];
219 if (!strcasecmp(name, "include"))
222 err = "Expecting one filename";
224 err = "Too many nested files";
225 else if (*line && *line != '#') // because the contents of line_buf is not re-entrant and will be cleared
226 err = "The input command must be the last one on a line";
229 struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
231 err = cf_printf("Cannot open file %s: %m", pars[0]);
235 err = parse_fastbuf(stk_strdup(pars[0]), new_fb, depth+1);
243 enum cf_operation op;
244 char *c = strchr(name, ':');
246 op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
249 switch (Clocase(*c)) {
250 case 's': op = OP_SET; break;
251 case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
252 case 'a': switch (Clocase(c[1])) {
253 case 'p': op = OP_APPEND; break;
254 case 'f': op = OP_AFTER; break;
255 default: op = OP_ALL;
257 case 'p': op = OP_PREPEND; break;
258 case 'r': op = (c[1] && Clocase(c[2]) == 'm') ? OP_REMOVE : OP_RESET; break;
259 case 'e': op = OP_EDIT; break;
260 case 'b': op = OP_BEFORE; break;
261 default: op = OP_SET; break;
263 if (strcasecmp(c, cf_op_names[op])) {
264 err = cf_printf("Unknown operation %s", c);
270 err = cf_interpret_line(name, op, words-1, pars);
276 msg(L_ERROR, "File %s, line %d: %s", name_fb, line_num, err);
277 else if (line_num == 1)
278 msg(L_ERROR, "Manual setting of configuration: %s", err);
280 msg(L_ERROR, "Manual setting of configuration, line %d: %s", line_num, err);
281 return "included from here";
284 #ifndef DEFAULT_CONFIG
285 #define DEFAULT_CONFIG NULL
287 char *cf_def_file = DEFAULT_CONFIG;
288 static int cf_def_loaded;
290 #ifndef ENV_VAR_CONFIG
291 #define ENV_VAR_CONFIG NULL
293 char *cf_env_file = ENV_VAR_CONFIG;
295 static uns postpone_commit; // only for cf_getopt()
296 static uns everything_committed; // after the 1st load, this flag is set on
301 if (cf_check_stack())
303 if (cf_commit_all(postpone_commit ? CF_NO_COMMIT : everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
305 if (!postpone_commit)
306 everything_committed = 1;
311 load_file(const char *file)
314 struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
316 msg(L_ERROR, "Cannot open %s: %m", file);
319 char *err_msg = parse_fastbuf(file, fb, 0);
321 return !!err_msg || done_stack();
325 load_string(const char *string)
329 fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
330 char *msg = parse_fastbuf(NULL, &fb, 0);
331 return !!msg || done_stack();
334 /* Safe loading and reloading */
336 struct conf_entry { /* We remember a list of actions to apply upon reload */
345 static clist conf_entries;
348 cf_remember_entry(uns type, const char *arg)
350 if (!cf_need_journal)
352 if (!postpone_commit)
354 struct conf_entry *ce = cf_malloc(sizeof(*ce));
356 ce->arg = cf_strdup(arg);
357 clist_add_tail(&conf_entries, &ce->n);
361 cf_reload(const char *file)
364 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
365 uns ec = everything_committed;
366 everything_committed = 0;
368 if (!conf_entries.head.next)
369 clist_init(&conf_entries);
371 clist_move(&old_entries, &conf_entries);
376 err = load_file(file);
378 CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
379 if (ce->type == CE_FILE)
380 err |= load_file(ce->arg);
382 err |= load_string(ce->arg);
385 cf_remember_entry(ce->type, ce->arg);
394 cf_journal_commit_transaction(1, NULL);
396 everything_committed = ec;
397 cf_journal_rollback_transaction(1, oldj);
399 clist_move(&conf_entries, &old_entries);
405 cf_load(const char *file)
407 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
408 int err = load_file(file);
410 cf_journal_commit_transaction(1, oldj);
411 cf_remember_entry(CE_FILE, file);
414 cf_journal_rollback_transaction(1, oldj);
419 cf_set(const char *string)
421 struct cf_journal_item *oldj = cf_journal_new_transaction(0);
422 int err = load_string(string);
424 cf_journal_commit_transaction(0, oldj);
425 cf_remember_entry(CE_STRING, string);
427 cf_journal_rollback_transaction(0, oldj);
431 /* Command-line parser */
441 if (cf_env_file && (env = getenv(cf_env_file)))
444 die("Cannot load config file %s", env);
446 else if (cf_load(cf_def_file))
447 die("Cannot load default config %s", cf_def_file);
451 // We need to create an empty pool and initialize all configuration items
452 struct cf_journal_item *oldj = cf_journal_new_transaction(1);
455 cf_journal_commit_transaction(1, oldj);
462 if (postpone_commit) {
465 die("Cannot commit after the initialization");
470 cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
472 clist_init(&conf_entries);
475 static int other_options = 0;
477 int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
478 if (res == 'S' || res == 'C' || res == 0x64436667)
481 die("The -S and -C options must precede all other arguments");
485 die("Cannot set %s", optarg);
486 } else if (res == 'C') {
488 die("Cannot load config file %s", optarg);
491 else { /* --dumpconfig */
494 struct fastbuf *b = bfdopen(1, 4096);
501 /* unhandled option or end of options */
502 if (res != ':' && res != '?') {