]> mj.ucw.cz Git - libucw.git/blob - ucw/conf-input.c
b264a24b8759b4233187a535bf507c5606d2d002
[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/conf-internal.h>
14 #include <ucw/clists.h>
15 #include <ucw/mempool.h>
16 #include <ucw/fastbuf.h>
17 #include <ucw/chartype.h>
18 #include <ucw/string.h>
19 #include <ucw/stkstring.h>
20
21 #include <stdlib.h>
22 #include <string.h>
23 #include <errno.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 int
207 maybe_commit(struct cf_context *cc)
208 {
209   if (cf_commit_all(cc->postpone_commit ? CF_NO_COMMIT : cc->everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
210     return 1;
211   if (!cc->postpone_commit)
212     cc->everything_committed = 1;
213   return 0;
214 }
215
216 static char *
217 parse_fastbuf(struct cf_context *cc, const char *name_fb, struct fastbuf *fb, uns depth)
218 {
219   struct cf_parser_state *p = cc->parser;
220   if (!p)
221     p = cc->parser = xmalloc_zero(sizeof(*p) + MAX_LINE);
222   p->name_parse_fb = name_fb;
223   p->parse_fb = fb;
224   p->line_num = 0;
225   p->line = p->line_buf;
226   *p->line = 0;
227
228   if (!depth)
229     cf_init_stack(cc);
230
231   char *err = NULL;
232   while (1)
233   {
234     err = split_command(p);
235     if (err)
236       goto error;
237     if (!p->words)
238       break;
239     char *name = p->copy_buf.ptr + p->word_buf.ptr[0];
240     char *pars[p->words-1];
241     for (uns i=1; i<p->words; i++)
242       pars[i-1] = p->copy_buf.ptr + p->word_buf.ptr[i];
243     int optional_include = !strcasecmp(name, "optionalinclude");
244     if (optional_include || !strcasecmp(name, "include"))
245     {
246       if (p->words != 2)
247         err = "Expecting one filename";
248       else if (depth > 8)
249         err = "Too many nested files";
250       else if (*p->line && *p->line != '#')     // because the contents of line_buf is not re-entrant and will be cleared
251         err = "The include command must be the last one on a line";
252       if (err)
253         goto error;
254       struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
255       if (!new_fb) {
256         if (optional_include && errno == ENOENT)
257           continue;
258         err = cf_printf("Cannot open file %s: %m", pars[0]);
259         goto error;
260       }
261       uns ll = p->line_num;
262       err = parse_fastbuf(cc, stk_strdup(pars[0]), new_fb, depth+1);
263       p->line_num = ll;
264       bclose(new_fb);
265       if (err)
266         goto error;
267       p->parse_fb = fb;
268       continue;
269     }
270     enum cf_operation op;
271     char *c = strchr(name, ':');
272     if (!c)
273       op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
274     else {
275       *c++ = 0;
276       switch (Clocase(*c)) {
277         case 's': op = OP_SET; break;
278         case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
279         case 'a': switch (Clocase(c[1])) {
280                     case 'p': op = OP_APPEND; break;
281                     case 'f': op = OP_AFTER; break;
282                     default: op = OP_ALL;
283                   }; break;
284         case 'p': op = OP_PREPEND; break;
285         case 'r': op = (c[1] && Clocase(c[2]) == 'm') ? OP_REMOVE : OP_RESET; break;
286         case 'e': op = OP_EDIT; break;
287         case 'b': op = OP_BEFORE; break;
288         default: op = OP_SET; break;
289       }
290       if (strcasecmp(c, cf_op_names[op])) {
291         err = cf_printf("Unknown operation %s", c);
292         goto error;
293       }
294     }
295     if (p->ends_by_brace)
296       op |= OP_OPEN;
297     err = cf_interpret_line(cc, name, op, p->words-1, pars);
298     if (err)
299       goto error;
300   }
301
302   if (!depth)
303     {
304       if (cf_done_stack(cc))
305         err = "Unterminated block";
306       else if (maybe_commit(cc))
307         err = "Commit failed";
308     }
309   if (!err)
310     return NULL;
311
312 error:
313   if (name_fb)
314     msg(L_ERROR, "File %s, line %d: %s", name_fb, p->line_num, err);
315   else if (p->line_num == 1)
316     msg(L_ERROR, "Manual setting of configuration: %s", err);
317   else
318     msg(L_ERROR, "Manual setting of configuration, line %d: %s", p->line_num, err);
319   return "included from here";
320 }
321
322 static int
323 load_file(struct cf_context *cc, const char *file)
324 {
325   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
326   if (!fb) {
327     msg(L_ERROR, "Cannot open configuration file %s: %m", file);
328     return 1;
329   }
330   char *err_msg = parse_fastbuf(cc, file, fb, 0);
331   bclose(fb);
332   return !!err_msg;
333 }
334
335 static int
336 load_string(struct cf_context *cc, const char *string)
337 {
338   struct fastbuf fb;
339   fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
340   char *msg = parse_fastbuf(cc, NULL, &fb, 0);
341   return !!msg;
342 }
343
344 /* Safe loading and reloading */
345
346 struct conf_entry {     /* We remember a list of actions to apply upon reload */
347   cnode n;
348   enum {
349     CE_FILE = 1,
350     CE_STRING = 2,
351   } type;
352   char *arg;
353 };
354
355 static void
356 cf_remember_entry(struct cf_context *cc, uns type, const char *arg)
357 {
358   if (!cc->enable_journal)
359     return;
360   struct conf_entry *ce = cf_malloc(sizeof(*ce));
361   ce->type = type;
362   ce->arg = cf_strdup(arg);
363   clist_add_tail(&cc->conf_entries, &ce->n);
364 }
365
366 int
367 cf_reload(const char *file)
368 {
369   struct cf_context *cc = cf_get_context();
370   ASSERT(cc->enable_journal);
371   cf_journal_swap();
372   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
373   uns ec = cc->everything_committed;
374   cc->everything_committed = 0;
375
376   clist old_entries;
377   clist_move(&old_entries, &cc->conf_entries);
378   cf_open_group();
379
380   int err = 0;
381   if (file)
382     err = load_file(cc, file);
383   else
384     CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
385       if (ce->type == CE_FILE)
386         err |= load_file(cc, ce->arg);
387       else
388         err |= load_string(cc, ce->arg);
389       if (err)
390         break;
391       cf_remember_entry(cc, ce->type, ce->arg);
392     }
393
394   err |= cf_close_group();
395
396   if (!err) {
397     cf_journal_delete();
398     cf_journal_commit_transaction(1, NULL);
399   } else {
400     cc->everything_committed = ec;
401     cf_journal_rollback_transaction(1, oldj);
402     cf_journal_swap();
403     clist_move(&cc->conf_entries, &old_entries);
404   }
405   return err;
406 }
407
408 int
409 cf_load(const char *file)
410 {
411   struct cf_context *cc = cf_get_context();
412   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
413   int err = load_file(cc, file);
414   if (!err) {
415     cf_journal_commit_transaction(1, oldj);
416     cf_remember_entry(cc, CE_FILE, file);
417     cc->config_loaded = 1;
418   } else
419     cf_journal_rollback_transaction(1, oldj);
420   return err;
421 }
422
423 int
424 cf_set(const char *string)
425 {
426   struct cf_context *cc = cf_get_context();
427   struct cf_journal_item *oldj = cf_journal_new_transaction(0);
428   int err = load_string(cc, string);
429   if (!err) {
430     cf_journal_commit_transaction(0, oldj);
431     cf_remember_entry(cc, CE_STRING, string);
432   } else
433     cf_journal_rollback_transaction(0, oldj);
434   return err;
435 }
436
437 void
438 cf_revert(void)
439 {
440   cf_journal_swap();
441   cf_journal_delete();
442 }
443
444 void
445 cf_open_group(void)
446 {
447   struct cf_context *cc = cf_get_context();
448   cc->postpone_commit++;
449 }
450
451 int
452 cf_close_group(void)
453 {
454   struct cf_context *cc = cf_get_context();
455   ASSERT(cc->postpone_commit);
456   if (!--cc->postpone_commit)
457     return maybe_commit(cc);
458   else
459     return 0;
460 }