]> mj.ucw.cz Git - libucw.git/blob - ucw/conf-input.c
Updated TODO
[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 include 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 = (c[1] && Clocase(c[2]) == 'm') ? OP_REMOVE : OP_RESET; 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 CONFIG_UCW_DEFAULT_CONFIG
285 #define CONFIG_UCW_DEFAULT_CONFIG NULL
286 #endif
287 char *cf_def_file = CONFIG_UCW_DEFAULT_CONFIG;
288 static int cf_def_loaded;
289
290 #ifndef CONFIG_UCW_ENV_VAR_CONFIG
291 #define CONFIG_UCW_ENV_VAR_CONFIG NULL
292 #endif
293 char *cf_env_file = CONFIG_UCW_ENV_VAR_CONFIG;
294
295 static uns postpone_commit;                     // only for cf_getopt()
296 static uns everything_committed;                // after the 1st load, this flag is set on
297
298 static int
299 done_stack(void)
300 {
301   if (cf_check_stack())
302     return 1;
303   if (cf_commit_all(postpone_commit ? CF_NO_COMMIT : everything_committed ? CF_COMMIT : CF_COMMIT_ALL))
304     return 1;
305   if (!postpone_commit)
306     everything_committed = 1;
307   return 0;
308 }
309
310 static int
311 load_file(const char *file)
312 {
313   cf_init_stack();
314   struct fastbuf *fb = bopen_try(file, O_RDONLY, 1<<14);
315   if (!fb) {
316     msg(L_ERROR, "Cannot open %s: %m", file);
317     return 1;
318   }
319   char *err_msg = parse_fastbuf(file, fb, 0);
320   bclose(fb);
321   return !!err_msg || done_stack();
322 }
323
324 static int
325 load_string(const char *string)
326 {
327   cf_init_stack();
328   struct fastbuf fb;
329   fbbuf_init_read(&fb, (byte *)string, strlen(string), 0);
330   char *msg = parse_fastbuf(NULL, &fb, 0);
331   return !!msg || done_stack();
332 }
333
334 /* Safe loading and reloading */
335
336 struct conf_entry {     /* We remember a list of actions to apply upon reload */
337   cnode n;
338   enum {
339     CE_FILE = 1,
340     CE_STRING = 2,
341   } type;
342   char *arg;
343 };
344
345 static clist conf_entries;
346
347 static void
348 cf_remember_entry(uns type, const char *arg)
349 {
350   if (!cf_need_journal)
351     return;
352   if (!postpone_commit)
353     return;
354   struct conf_entry *ce = cf_malloc(sizeof(*ce));
355   ce->type = type;
356   ce->arg = cf_strdup(arg);
357   clist_add_tail(&conf_entries, &ce->n);
358 }
359
360 int
361 cf_reload(const char *file)
362 {
363   cf_journal_swap();
364   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
365   uns ec = everything_committed;
366   everything_committed = 0;
367
368   if (!conf_entries.head.next)
369     clist_init(&conf_entries);
370   clist old_entries;
371   clist_move(&old_entries, &conf_entries);
372   postpone_commit = 1;
373
374   int err = 0;
375   if (file)
376     err = load_file(file);
377   else
378     CLIST_FOR_EACH(struct conf_entry *, ce, old_entries) {
379       if (ce->type == CE_FILE)
380         err |= load_file(ce->arg);
381       else
382         err |= load_string(ce->arg);
383       if (err)
384         break;
385       cf_remember_entry(ce->type, ce->arg);
386     }
387
388   postpone_commit = 0;
389   if (!err)
390     err |= done_stack();
391
392   if (!err) {
393     cf_journal_delete();
394     cf_journal_commit_transaction(1, NULL);
395   } else {
396     everything_committed = ec;
397     cf_journal_rollback_transaction(1, oldj);
398     cf_journal_swap();
399     clist_move(&conf_entries, &old_entries);
400   }
401   return err;
402 }
403
404 int
405 cf_load(const char *file)
406 {
407   struct cf_journal_item *oldj = cf_journal_new_transaction(1);
408   int err = load_file(file);
409   if (!err) {
410     cf_journal_commit_transaction(1, oldj);
411     cf_remember_entry(CE_FILE, file);
412     cf_def_loaded = 1;
413   } else
414     cf_journal_rollback_transaction(1, oldj);
415   return err;
416 }
417
418 int
419 cf_set(const char *string)
420 {
421   struct cf_journal_item *oldj = cf_journal_new_transaction(0);
422   int err = load_string(string);
423   if (!err) {
424     cf_journal_commit_transaction(0, oldj);
425     cf_remember_entry(CE_STRING, string);
426   } else
427     cf_journal_rollback_transaction(0, oldj);
428   return err;
429 }
430
431 /* Command-line parser */
432
433 static void
434 load_default(void)
435 {
436   if (cf_def_loaded++)
437     return;
438   if (cf_def_file)
439     {
440       char *env;
441       if (cf_env_file && (env = getenv(cf_env_file)))
442         {
443           if (cf_load(env))
444             die("Cannot load config file %s", env);
445         }
446       else if (cf_load(cf_def_file))
447         die("Cannot load default config %s", cf_def_file);
448     }
449   else
450     {
451       // We need to create an empty pool and initialize all configuration items
452       struct cf_journal_item *oldj = cf_journal_new_transaction(1);
453       cf_init_stack();
454       done_stack();
455       cf_journal_commit_transaction(1, oldj);
456     }
457 }
458
459 static void
460 final_commit(void)
461 {
462   if (postpone_commit) {
463     postpone_commit = 0;
464     if (done_stack())
465       die("Cannot commit after the initialization");
466   }
467 }
468
469 int
470 cf_getopt(int argc, char * const argv[], const char *short_opts, const struct option *long_opts, int *long_index)
471 {
472   clist_init(&conf_entries);
473   postpone_commit = 1;
474
475   static int other_options = 0;
476   while (1) {
477     int res = getopt_long (argc, argv, short_opts, long_opts, long_index);
478     if (res == 'S' || res == 'C' || res == 0x64436667)
479     {
480       if (other_options)
481         die("The -S and -C options must precede all other arguments");
482       if (res == 'S') {
483         load_default();
484         if (cf_set(optarg))
485           die("Cannot set %s", optarg);
486       } else if (res == 'C') {
487         if (cf_load(optarg))
488           die("Cannot load config file %s", optarg);
489       }
490 #ifdef CONFIG_UCW_DEBUG
491       else {   /* --dumpconfig */
492         load_default();
493         final_commit();
494         struct fastbuf *b = bfdopen(1, 4096);
495         cf_dump_sections(b);
496         bclose(b);
497         exit(0);
498       }
499 #endif
500     } else {
501       /* unhandled option or end of options */
502       if (res != ':' && res != '?') {
503         load_default();
504         final_commit();
505       }
506       other_options++;
507       return res;
508     }
509   }
510 }