]> mj.ucw.cz Git - libucw.git/blob - ucw/conf-input.c
fff753ce01743606735983ca6ec7bcabef7272c5
[libucw.git] / ucw / conf-input.c
1 /*
2  *      UCW Library -- Configuration files: parsing input streams
3  *
4  *      (c) 2001--2006 Robert Spalek <robert@ucw.cz>
5  *      (c) 2003--2012 Martin Mares <mj@ucw.cz>
6  *
7  *      This software may be freely distributed and used according to the terms
8  *      of the GNU Lesser General Public License.
9  */
10
11 #include <ucw/lib.h>
12 #include <ucw/conf.h>
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>
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <fcntl.h>
25
26 /* Text file parser */
27
28 #define MAX_LINE        4096
29
30 #include <ucw/bbuf.h>
31
32 #define GBUF_TYPE       uns
33 #define GBUF_PREFIX(x)  split_##x
34 #include <ucw/gbuf.h>
35
36 struct cf_parser_state {
37   const char *name_parse_fb;
38   struct fastbuf *parse_fb;
39   uns line_num;
40   char *line;
41   split_t word_buf;
42   uns words;
43   uns ends_by_brace;            // the line is ended by "{"
44   bb_t copy_buf;
45   uns copied;
46   char line_buf[];
47 };
48
49 static int
50 get_line(struct cf_parser_state *p, char **msg)
51 {
52   int err = bgets_nodie(p->parse_fb, p->line_buf, MAX_LINE);
53   p->line_num++;
54   if (err <= 0) {
55     *msg = err < 0 ? "Line too long" : NULL;
56     return 0;
57   }
58   p->line = p->line_buf;
59   while (Cblank(*p->line))
60     p->line++;
61   return 1;
62 }
63
64 static void
65 append(struct cf_parser_state *p, char *start, char *end)
66 {
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);
70   p->copied += len + 1;
71   p->copy_buf.ptr[p->copied-1] = 0;
72 }
73
74 static char *
75 get_word(struct cf_parser_state *p, uns is_command_name)
76 {
77   char *msg;
78   char *line = p->line;
79
80   if (*line == '\'') {
81     line++;
82     while (1) {
83       char *start = line;
84       while (*line && *line != '\'')
85         line++;
86       append(p, start, line);
87       if (*line)
88         break;
89       p->copy_buf.ptr[p->copied-1] = '\n';
90       if (!get_line(p, &msg))
91         return msg ? : "Unterminated apostrophe word at the end";
92       line = p->line;
93     }
94     line++;
95
96   } else if (*line == '"') {
97     line++;
98     uns start_copy = p->copied;
99     while (1) {
100       char *start = line;
101       uns escape = 0;
102       while (*line) {
103         if (*line == '"' && !escape)
104           break;
105         else if (*line == '\\')
106           escape ^= 1;
107         else
108           escape = 0;
109         line++;
110       }
111       append(p, start, line);
112       if (*line)
113         break;
114       if (!escape)
115         p->copy_buf.ptr[p->copied-1] = '\n';
116       else // merge two lines
117         p->copied -= 2;
118       if (!get_line(p, &msg))
119         return msg ? : "Unterminated quoted word at the end";
120       line = p->line;
121     }
122     line++;
123
124     char *tmp = stk_str_unesc(p->copy_buf.ptr + start_copy);
125     uns l = strlen(tmp);
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;
129
130   } else {
131     // promised that *line is non-null and non-blank
132     char *start = line;
133     while (*line && !Cblank(*line)
134         && *line != '{' && *line != '}' && *line != ';'
135         && (*line != '=' || !is_command_name))
136       line++;
137     if (*line == '=') {                         // nice for setting from a command-line
138       if (line == start)
139         return "Assignment without a variable";
140       *line = ' ';
141     }
142     if (line == start)                          // already the first char is control
143       line++;
144     append(p, start, line);
145   }
146   while (Cblank(*line))
147     line++;
148   p->line = line;
149   return NULL;
150 }
151
152 static char *
153 get_token(struct cf_parser_state *p, uns is_command_name, char **err)
154 {
155   *err = NULL;
156   while (1) {
157     if (!*p->line || *p->line == '#') {
158       if (!is_command_name || !get_line(p, err))
159         return NULL;
160     } else if (*p->line == ';') {
161       *err = get_word(p, 0);
162       if (!is_command_name || *err)
163         return NULL;
164     } else if (*p->line == '\\' && !p->line[1]) {
165       if (!get_line(p, err)) {
166         if (!*err)
167           *err = "Last line ends by a backslash";
168         return NULL;
169       }
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);
172     } else {
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;
178     }
179   }
180 }
181
182 static char *
183 split_command(struct cf_parser_state *p)
184 {
185   p->words = p->copied = p->ends_by_brace = 0;
186   char *msg, *start_word;
187   if (!(start_word = get_token(p, 1, &msg)))
188     return msg;
189   if (*start_word == '{')                       // only one opening brace
190     return "Unexpected opening brace";
191   while (*p->line != '}')                       // stays for the next time
192   {
193     if (!(start_word = get_token(p, 0, &msg)))
194       return msg;
195     if (*start_word == '{') {
196       p->words--;                               // discard the brace
197       p->ends_by_brace = 1;
198       break;
199     }
200   }
201   return NULL;
202 }
203
204 /* Parsing multiple files */
205
206 static char *
207 parse_fastbuf(struct cf_context *cc, const char *name_fb, struct fastbuf *fb, uns depth)
208 {
209   struct cf_parser_state *p = cc->parser;
210   if (!p)
211     p = cc->parser = xmalloc_zero(sizeof(*p) + MAX_LINE);
212   p->name_parse_fb = name_fb;
213   p->parse_fb = fb;
214   p->line_num = 0;
215   p->line = p->line_buf;
216   *p->line = 0;
217
218   char *err;
219   while (1)
220   {
221     err = split_command(p);
222     if (err)
223       goto error;
224     if (!p->words)
225       return NULL;
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"))
231     {
232       if (p->words != 2)
233         err = "Expecting one filename";
234       else if (depth > 8)
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";
238       if (err)
239         goto error;
240       struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
241       if (!new_fb) {
242         err = cf_printf("Cannot open file %s: %m", pars[0]);
243         goto error;
244       }
245       uns ll = p->line_num;
246       err = parse_fastbuf(cc, stk_strdup(pars[0]), new_fb, depth+1);
247       p->line_num = ll;
248       bclose(new_fb);
249       if (err)
250         goto error;
251       p->parse_fb = fb;
252       continue;
253     }
254     enum cf_operation op;
255     char *c = strchr(name, ':');
256     if (!c)
257       op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
258     else {
259       *c++ = 0;
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;
267                   }; break;
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;
273       }
274       if (strcasecmp(c, cf_op_names[op])) {
275         err = cf_printf("Unknown operation %s", c);
276         goto error;
277       }
278     }
279     if (p->ends_by_brace)
280       op |= OP_OPEN;
281     err = cf_interpret_line(cc, name, op, p->words-1, pars);
282     if (err)
283       goto error;
284   }
285 error:
286   if (name_fb)
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);
290   else
291     msg(L_ERROR, "Manual setting of configuration, line %d: %s", p->line_num, err);
292   return "included from here";
293 }
294
295 static int
296 done_stack(struct cf_context *cc)
297 {
298   if (cf_check_stack(cc))
299     return 1;
300   if (cf_commit_all(cc->postpone_commit ? CF_NO_COMMIT : cc->everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
301     return 1;
302   if (!cc->postpone_commit)
303     cc->everything_committed = 1;
304   return 0;
305 }
306
307 static int
308 load_file(struct cf_context *cc, const char *file)
309 {
310   cf_init_stack(cc);
311   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
312   if (!fb) {
313     msg(L_ERROR, "Cannot open %s: %m", file);
314     return 1;
315   }
316   char *err_msg = parse_fastbuf(cc, file, fb, 0);
317   bclose(fb);
318   return !!err_msg || done_stack(cc);
319 }
320
321 static int
322 load_string(struct cf_context *cc, const char *string)
323 {
324   cf_init_stack(cc);
325   struct fastbuf fb;
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);
329 }
330
331 /* Safe loading and reloading */
332
333 struct conf_entry {     /* We remember a list of actions to apply upon reload */
334   cnode n;
335   enum {
336     CE_FILE = 1,
337     CE_STRING = 2,
338   } type;
339   char *arg;
340 };
341
342 static void
343 cf_remember_entry(struct cf_context *cc, uns type, const char *arg)
344 {
345   if (!cc->need_journal)
346     return;
347   if (!cc->postpone_commit)
348     return;
349   struct conf_entry *ce = cf_malloc(sizeof(*ce));
350   ce->type = type;
351   ce->arg = cf_strdup(arg);
352   clist_add_tail(&cc->conf_entries, &ce->n);
353 }
354
355 int
356 cf_reload(const char *file)
357 {
358   struct cf_context *cc = cf_get_context();
359   cf_journal_swap();
360   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
361   uns ec = cc->everything_committed;
362   cc->everything_committed = 0;
363
364   clist old_entries;
365   clist_move(&old_entries, &cc->conf_entries);
366   cc->postpone_commit = 1;
367
368   int err = 0;
369   if (file)
370     err = load_file(cc, file);
371   else
372     CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
373       if (ce->type == CE_FILE)
374         err |= load_file(cc, ce->arg);
375       else
376         err |= load_string(cc, ce->arg);
377       if (err)
378         break;
379       cf_remember_entry(cc, ce->type, ce->arg);
380     }
381
382   cc->postpone_commit = 0;
383   if (!err)
384     err |= done_stack(cc);
385
386   if (!err) {
387     cf_journal_delete();
388     cf_journal_commit_transaction(1, NULL);
389   } else {
390     cc->everything_committed = ec;
391     cf_journal_rollback_transaction(1, oldj);
392     cf_journal_swap();
393     clist_move(&cc->conf_entries, &old_entries);
394   }
395   return err;
396 }
397
398 int
399 cf_load(const char *file)
400 {
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);
404   if (!err) {
405     cf_journal_commit_transaction(1, oldj);
406     cf_remember_entry(cc, CE_FILE, file);
407     cc->def_loaded = 1;
408   } else
409     cf_journal_rollback_transaction(1, oldj);
410   return err;
411 }
412
413 int
414 cf_set(const char *string)
415 {
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);
419   if (!err) {
420     cf_journal_commit_transaction(0, oldj);
421     cf_remember_entry(cc, CE_STRING, string);
422   } else
423     cf_journal_rollback_transaction(0, oldj);
424   return err;
425 }
426
427 /* Command-line parser */
428
429 static void
430 load_default(struct cf_context *cc)
431 {
432   if (cc->def_loaded++)
433     return;
434   if (cc->def_file)
435     {
436       char *env;
437       if (cc->env_file && (env = getenv(cc->env_file)))
438         {
439           if (cf_load(env))
440             die("Cannot load config file %s", env);
441         }
442       else if (cf_load(cc->def_file))
443         die("Cannot load default config %s", cc->def_file);
444     }
445   else
446     {
447       // We need to create an empty pool and initialize all configuration items
448       struct cf_journal_item *oldj = cf_journal_new_transaction(1);
449       cf_init_stack(cc);
450       done_stack(cc);
451       cf_journal_commit_transaction(1, oldj);
452     }
453 }
454
455 static void
456 final_commit(struct cf_context *cc)
457 {
458   if (cc->postpone_commit) {
459     cc->postpone_commit = 0;
460     if (done_stack(cc))
461       die("Cannot commit after the initialization");
462   }
463 }
464
465 int
466 cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
467 {
468   struct cf_context *cc = cf_obtain_context();
469   cc->postpone_commit = 1;
470
471   while (1) {
472     int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
473     if (res == 'S' || res == 'C' || res == 0x64436667)
474     {
475       if (cc->other_options)
476         die("The -S and -C options must precede all other arguments");
477       if (res == 'S') {
478         load_default(cc);
479         if (cf_set(optarg))
480           die("Cannot set %s", optarg);
481       } else if (res == 'C') {
482         if (cf_load(optarg))
483           die("Cannot load config file %s", optarg);
484       }
485 #ifdef CONFIG_UCW_DEBUG
486       else {   /* --dumpconfig */
487         load_default(cc);
488         final_commit(cc);
489         struct fastbuf *b = bfdopen(1, 4096);
490         cf_dump_sections(b);
491         bclose(b);
492         exit(0);
493       }
494 #endif
495     } else {
496       /* unhandled option or end of options */
497       if (res != ':' && res != '?') {
498         load_default(cc);
499         final_commit(cc);
500       }
501       cc->other_options++;
502       return res;
503     }
504   }
505 }