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