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