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