]> mj.ucw.cz Git - libucw.git/blob - ucw/conf-input.c
ddfef9bd621fa954e6e41e88454b7b8682a84d3f
[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 <fcntl.h>
24
25 /* Text file parser */
26
27 #define MAX_LINE        4096
28
29 #include <ucw/bbuf.h>
30
31 #define GBUF_TYPE       uns
32 #define GBUF_PREFIX(x)  split_##x
33 #include <ucw/gbuf.h>
34
35 struct cf_parser_state {
36   const char *name_parse_fb;
37   struct fastbuf *parse_fb;
38   uns line_num;
39   char *line;
40   split_t word_buf;
41   uns words;
42   uns ends_by_brace;            // the line is ended by "{"
43   bb_t copy_buf;
44   uns copied;
45   char line_buf[];
46 };
47
48 static int
49 get_line(struct cf_parser_state *p, char **msg)
50 {
51   int err = bgets_nodie(p->parse_fb, p->line_buf, MAX_LINE);
52   p->line_num++;
53   if (err <= 0) {
54     *msg = err < 0 ? "Line too long" : NULL;
55     return 0;
56   }
57   p->line = p->line_buf;
58   while (Cblank(*p->line))
59     p->line++;
60   return 1;
61 }
62
63 static void
64 append(struct cf_parser_state *p, char *start, char *end)
65 {
66   uns len = end - start;
67   bb_grow(&p->copy_buf, p->copied + len + 1);
68   memcpy(p->copy_buf.ptr + p->copied, start, len);
69   p->copied += len + 1;
70   p->copy_buf.ptr[p->copied-1] = 0;
71 }
72
73 static char *
74 get_word(struct cf_parser_state *p, uns is_command_name)
75 {
76   char *msg;
77   char *line = p->line;
78
79   if (*line == '\'') {
80     line++;
81     while (1) {
82       char *start = line;
83       while (*line && *line != '\'')
84         line++;
85       append(p, start, line);
86       if (*line)
87         break;
88       p->copy_buf.ptr[p->copied-1] = '\n';
89       if (!get_line(p, &msg))
90         return msg ? : "Unterminated apostrophe word at the end";
91       line = p->line;
92     }
93     line++;
94
95   } else if (*line == '"') {
96     line++;
97     uns start_copy = p->copied;
98     while (1) {
99       char *start = line;
100       uns escape = 0;
101       while (*line) {
102         if (*line == '"' && !escape)
103           break;
104         else if (*line == '\\')
105           escape ^= 1;
106         else
107           escape = 0;
108         line++;
109       }
110       append(p, start, line);
111       if (*line)
112         break;
113       if (!escape)
114         p->copy_buf.ptr[p->copied-1] = '\n';
115       else // merge two lines
116         p->copied -= 2;
117       if (!get_line(p, &msg))
118         return msg ? : "Unterminated quoted word at the end";
119       line = p->line;
120     }
121     line++;
122
123     char *tmp = stk_str_unesc(p->copy_buf.ptr + start_copy);
124     uns l = strlen(tmp);
125     bb_grow(&p->copy_buf, start_copy + l + 1);
126     strcpy(p->copy_buf.ptr + start_copy, tmp);
127     p->copied = start_copy + l + 1;
128
129   } else {
130     // promised that *line is non-null and non-blank
131     char *start = line;
132     while (*line && !Cblank(*line)
133         && *line != '{' && *line != '}' && *line != ';'
134         && (*line != '=' || !is_command_name))
135       line++;
136     if (*line == '=') {                         // nice for setting from a command-line
137       if (line == start)
138         return "Assignment without a variable";
139       *line = ' ';
140     }
141     if (line == start)                          // already the first char is control
142       line++;
143     append(p, start, line);
144   }
145   while (Cblank(*line))
146     line++;
147   p->line = line;
148   return NULL;
149 }
150
151 static char *
152 get_token(struct cf_parser_state *p, uns is_command_name, char **err)
153 {
154   *err = NULL;
155   while (1) {
156     if (!*p->line || *p->line == '#') {
157       if (!is_command_name || !get_line(p, err))
158         return NULL;
159     } else if (*p->line == ';') {
160       *err = get_word(p, 0);
161       if (!is_command_name || *err)
162         return NULL;
163     } else if (*p->line == '\\' && !p->line[1]) {
164       if (!get_line(p, err)) {
165         if (!*err)
166           *err = "Last line ends by a backslash";
167         return NULL;
168       }
169       if (!*p->line || *p->line == '#')
170         msg(L_WARN, "The line %s:%d following a backslash is empty", p->name_parse_fb ? : "", p->line_num);
171     } else {
172       split_grow(&p->word_buf, p->words+1);
173       uns start = p->copied;
174       p->word_buf.ptr[p->words++] = p->copied;
175       *err = get_word(p, is_command_name);
176       return *err ? NULL : p->copy_buf.ptr + start;
177     }
178   }
179 }
180
181 static char *
182 split_command(struct cf_parser_state *p)
183 {
184   p->words = p->copied = p->ends_by_brace = 0;
185   char *msg, *start_word;
186   if (!(start_word = get_token(p, 1, &msg)))
187     return msg;
188   if (*start_word == '{')                       // only one opening brace
189     return "Unexpected opening brace";
190   while (*p->line != '}')                       // stays for the next time
191   {
192     if (!(start_word = get_token(p, 0, &msg)))
193       return msg;
194     if (*start_word == '{') {
195       p->words--;                               // discard the brace
196       p->ends_by_brace = 1;
197       break;
198     }
199   }
200   return NULL;
201 }
202
203 /* Parsing multiple files */
204
205 static int
206 maybe_commit(struct cf_context *cc)
207 {
208   if (cf_commit_all(cc->postpone_commit ? CF_NO_COMMIT : cc->everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
209     return 1;
210   if (!cc->postpone_commit)
211     cc->everything_committed = 1;
212   return 0;
213 }
214
215 static char *
216 parse_fastbuf(struct cf_context *cc, const char *name_fb, struct fastbuf *fb, uns depth)
217 {
218   struct cf_parser_state *p = cc->parser;
219   if (!p)
220     p = cc->parser = xmalloc_zero(sizeof(*p) + MAX_LINE);
221   p->name_parse_fb = name_fb;
222   p->parse_fb = fb;
223   p->line_num = 0;
224   p->line = p->line_buf;
225   *p->line = 0;
226
227   if (!depth)
228     cf_init_stack(cc);
229
230   char *err = NULL;
231   while (1)
232   {
233     err = split_command(p);
234     if (err)
235       goto error;
236     if (!p->words)
237       break;
238     char *name = p->copy_buf.ptr + p->word_buf.ptr[0];
239     char *pars[p->words-1];
240     for (uns i=1; i<p->words; i++)
241       pars[i-1] = p->copy_buf.ptr + p->word_buf.ptr[i];
242     if (!strcasecmp(name, "include"))
243     {
244       if (p->words != 2)
245         err = "Expecting one filename";
246       else if (depth > 8)
247         err = "Too many nested files";
248       else if (*p->line && *p->line != '#')     // because the contents of line_buf is not re-entrant and will be cleared
249         err = "The include command must be the last one on a line";
250       if (err)
251         goto error;
252       struct fastbuf *new_fb = bopen_try(pars[0], O_RDONLY, 1<<14);
253       if (!new_fb) {
254         err = cf_printf("Cannot open file %s: %m", pars[0]);
255         goto error;
256       }
257       uns ll = p->line_num;
258       err = parse_fastbuf(cc, stk_strdup(pars[0]), new_fb, depth+1);
259       p->line_num = ll;
260       bclose(new_fb);
261       if (err)
262         goto error;
263       p->parse_fb = fb;
264       continue;
265     }
266     enum cf_operation op;
267     char *c = strchr(name, ':');
268     if (!c)
269       op = strcmp(name, "}") ? OP_SET : OP_CLOSE;
270     else {
271       *c++ = 0;
272       switch (Clocase(*c)) {
273         case 's': op = OP_SET; break;
274         case 'c': op = Clocase(c[1]) == 'l' ? OP_CLEAR: OP_COPY; break;
275         case 'a': switch (Clocase(c[1])) {
276                     case 'p': op = OP_APPEND; break;
277                     case 'f': op = OP_AFTER; break;
278                     default: op = OP_ALL;
279                   }; break;
280         case 'p': op = OP_PREPEND; break;
281         case 'r': op = (c[1] && Clocase(c[2]) == 'm') ? OP_REMOVE : OP_RESET; break;
282         case 'e': op = OP_EDIT; break;
283         case 'b': op = OP_BEFORE; break;
284         default: op = OP_SET; break;
285       }
286       if (strcasecmp(c, cf_op_names[op])) {
287         err = cf_printf("Unknown operation %s", c);
288         goto error;
289       }
290     }
291     if (p->ends_by_brace)
292       op |= OP_OPEN;
293     err = cf_interpret_line(cc, name, op, p->words-1, pars);
294     if (err)
295       goto error;
296   }
297
298   if (!depth)
299     {
300       if (cf_done_stack(cc))
301         err = "Unterminated block";
302       else if (maybe_commit(cc))
303         err = "Commit failed";
304     }
305   if (!err)
306     return NULL;
307
308 error:
309   if (name_fb)
310     msg(L_ERROR, "File %s, line %d: %s", name_fb, p->line_num, err);
311   else if (p->line_num == 1)
312     msg(L_ERROR, "Manual setting of configuration: %s", err);
313   else
314     msg(L_ERROR, "Manual setting of configuration, line %d: %s", p->line_num, err);
315   return "included from here";
316 }
317
318 static int
319 load_file(struct cf_context *cc, const char *file)
320 {
321   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
322   if (!fb) {
323     msg(L_ERROR, "Cannot open configuration file %s: %m", file);
324     return 1;
325   }
326   char *err_msg = parse_fastbuf(cc, file, fb, 0);
327   bclose(fb);
328   return !!err_msg;
329 }
330
331 static int
332 load_string(struct cf_context *cc, const char *string)
333 {
334   struct fastbuf fb;
335   fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
336   char *msg = parse_fastbuf(cc, NULL, &fb, 0);
337   return !!msg;
338 }
339
340 /* Safe loading and reloading */
341
342 struct conf_entry {     /* We remember a list of actions to apply upon reload */
343   cnode n;
344   enum {
345     CE_FILE = 1,
346     CE_STRING = 2,
347   } type;
348   char *arg;
349 };
350
351 static void
352 cf_remember_entry(struct cf_context *cc, uns type, const char *arg)
353 {
354   if (!cc->enable_journal)
355     return;
356   struct conf_entry *ce = cf_malloc(sizeof(*ce));
357   ce->type = type;
358   ce->arg = cf_strdup(arg);
359   clist_add_tail(&cc->conf_entries, &ce->n);
360 }
361
362 int
363 cf_reload(const char *file)
364 {
365   struct cf_context *cc = cf_get_context();
366   ASSERT(cc->enable_journal);
367   cf_journal_swap();
368   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
369   uns ec = cc->everything_committed;
370   cc->everything_committed = 0;
371
372   clist old_entries;
373   clist_move(&old_entries, &cc->conf_entries);
374   cf_open_group();
375
376   int err = 0;
377   if (file)
378     err = load_file(cc, file);
379   else
380     CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
381       if (ce->type == CE_FILE)
382         err |= load_file(cc, ce->arg);
383       else
384         err |= load_string(cc, ce->arg);
385       if (err)
386         break;
387       cf_remember_entry(cc, ce->type, ce->arg);
388     }
389
390   err |= cf_close_group();
391
392   if (!err) {
393     cf_journal_delete();
394     cf_journal_commit_transaction(1, NULL);
395   } else {
396     cc->everything_committed = ec;
397     cf_journal_rollback_transaction(1, oldj);
398     cf_journal_swap();
399     clist_move(&cc->conf_entries, &old_entries);
400   }
401   return err;
402 }
403
404 int
405 cf_load(const char *file)
406 {
407   struct cf_context *cc = cf_get_context();
408   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
409   int err = load_file(cc, file);
410   if (!err) {
411     cf_journal_commit_transaction(1, oldj);
412     cf_remember_entry(cc, CE_FILE, file);
413     cc->config_loaded = 1;
414   } else
415     cf_journal_rollback_transaction(1, oldj);
416   return err;
417 }
418
419 int
420 cf_set(const char *string)
421 {
422   struct cf_context *cc = cf_get_context();
423   struct cf_journal_item *oldj = cf_journal_new_transaction(0);
424   int err = load_string(cc, string);
425   if (!err) {
426     cf_journal_commit_transaction(0, oldj);
427     cf_remember_entry(cc, CE_STRING, string);
428   } else
429     cf_journal_rollback_transaction(0, oldj);
430   return err;
431 }
432
433 void
434 cf_revert(void)
435 {
436   cf_journal_swap();
437   cf_journal_delete();
438 }
439
440 void
441 cf_open_group(void)
442 {
443   struct cf_context *cc = cf_get_context();
444   cc->postpone_commit++;
445 }
446
447 int
448 cf_close_group(void)
449 {
450   struct cf_context *cc = cf_get_context();
451   ASSERT(cc->postpone_commit);
452   if (!--cc->postpone_commit)
453     return maybe_commit(cc);
454   else
455     return 0;
456 }