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