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