]> mj.ucw.cz Git - libucw.git/blob - ucw/conf-input.c
92acb35d9baf831d377788549343730ba0fc3861
[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 #ifndef CONFIG_UCW_DEFAULT_CONFIG
430 #define CONFIG_UCW_DEFAULT_CONFIG NULL
431 #endif
432 char *cf_def_file = CONFIG_UCW_DEFAULT_CONFIG;
433
434 #ifndef CONFIG_UCW_ENV_VAR_CONFIG
435 #define CONFIG_UCW_ENV_VAR_CONFIG NULL
436 #endif
437 char *cf_env_file = CONFIG_UCW_ENV_VAR_CONFIG;
438
439 static void
440 load_default(struct cf_context *cc)
441 {
442   if (cc->def_loaded++)
443     return;
444   if (cf_def_file)
445     {
446       char *env;
447       if (cf_env_file && (env = getenv(cf_env_file)))
448         {
449           if (cf_load(env))
450             die("Cannot load config file %s", env);
451         }
452       else if (cf_load(cf_def_file))
453         die("Cannot load default config %s", cf_def_file);
454     }
455   else
456     {
457       // We need to create an empty pool and initialize all configuration items
458       struct cf_journal_item *oldj = cf_journal_new_transaction(1);
459       cf_init_stack(cc);
460       done_stack(cc);
461       cf_journal_commit_transaction(1, oldj);
462     }
463 }
464
465 static void
466 final_commit(struct cf_context *cc)
467 {
468   if (cc->postpone_commit) {
469     cc->postpone_commit = 0;
470     if (done_stack(cc))
471       die("Cannot commit after the initialization");
472   }
473 }
474
475 int
476 cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
477 {
478   struct cf_context *cc = cf_obtain_context();
479   cc->postpone_commit = 1;
480
481   while (1) {
482     int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
483     if (res == 'S' || res == 'C' || res == 0x64436667)
484     {
485       if (cc->other_options)
486         die("The -S and -C options must precede all other arguments");
487       if (res == 'S') {
488         load_default(cc);
489         if (cf_set(optarg))
490           die("Cannot set %s", optarg);
491       } else if (res == 'C') {
492         if (cf_load(optarg))
493           die("Cannot load config file %s", optarg);
494       }
495 #ifdef CONFIG_UCW_DEBUG
496       else {   /* --dumpconfig */
497         load_default(cc);
498         final_commit(cc);
499         struct fastbuf *b = bfdopen(1, 4096);
500         cf_dump_sections(b);
501         bclose(b);
502         exit(0);
503       }
504 #endif
505     } else {
506       /* unhandled option or end of options */
507       if (res != ':' && res != '?') {
508         load_default(cc);
509         final_commit(cc);
510       }
511       cc->other_options++;
512       return res;
513     }
514   }
515 }