add support for parsing escaped newline characters
[project/uci.git] / file.c
1 /*
2  * libuci - Library for the Unified Configuration Interface
3  * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
4  *
5  * this program is free software; you can redistribute it and/or modify
6  * it under the terms of the gnu lesser general public license version 2.1
7  * as published by the free software foundation
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  */
14
15 /*
16  * This file contains the code for parsing uci config files
17  */
18
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 #include <stdbool.h>
22 #include <stdio.h>
23 #include <ctype.h>
24
25 #define LINEBUF 128
26 #define LINEBUF_MAX     4096
27
28 /*
29  * Fetch a new line from the input stream and resize buffer if necessary
30  */
31 static void uci_getln(struct uci_context *ctx, int offset)
32 {
33         struct uci_parse_context *pctx = ctx->pctx;
34         char *p;
35         int ofs;
36
37         if (pctx->buf == NULL) {
38                 pctx->buf = uci_malloc(ctx, LINEBUF);
39                 pctx->bufsz = LINEBUF;
40         }
41
42         ofs = offset;
43         do {
44                 p = &pctx->buf[ofs];
45                 p[ofs] = 0;
46
47                 p = fgets(p, pctx->bufsz - ofs, pctx->file);
48                 if (!p || !p[ofs])
49                         return;
50
51                 ofs += strlen(p);
52                 if (pctx->buf[ofs - 1] == '\n') {
53                         pctx->line++;
54                         pctx->buf[ofs - 1] = 0;
55                         return;
56                 }
57
58                 if (pctx->bufsz > LINEBUF_MAX/2) {
59                         pctx->reason = "line too long";
60                         pctx->byte = LINEBUF_MAX;
61                         UCI_THROW(ctx, UCI_ERR_PARSE);
62                 }
63
64                 pctx->bufsz *= 2;
65                 pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
66         } while (1);
67 }
68
69 /*
70  * Clean up all extra memory used by the parser
71  */
72 static void uci_parse_cleanup(struct uci_context *ctx)
73 {
74         struct uci_parse_context *pctx;
75
76         pctx = ctx->pctx;
77         if (!pctx)
78                 return;
79
80         ctx->pctx = NULL;
81         if (pctx->cfg) {
82                 uci_list_del(&pctx->cfg->list);
83                 uci_drop_config(pctx->cfg);
84         }
85         if (pctx->buf)
86                 free(pctx->buf);
87         if (pctx->file)
88                 fclose(pctx->file);
89
90         free(pctx);
91 }
92
93 /* 
94  * parse a character escaped by '\'
95  * returns true if the escaped character is to be parsed
96  * returns false if the escaped character is to be ignored
97  */
98 static inline bool parse_backslash(struct uci_context *ctx, char **str)
99 {
100         /* skip backslash */
101         *str += 1;
102
103         /* undecoded backslash at the end of line, fetch the next line */
104         if (!**str) {
105                 *str += 1;
106                 uci_getln(ctx, *str - ctx->pctx->buf);
107                 return false;
108         }
109
110         /* FIXME: decode escaped char, necessary? */
111         return true;
112 }
113
114 /*
115  * move the string pointer forward until a non-whitespace character or
116  * EOL is reached
117  */
118 static void skip_whitespace(struct uci_context *ctx, char **str)
119 {
120 restart:
121         while (**str && isspace(**str))
122                 *str += 1;
123
124         if (**str == '\\') {
125                 if (!parse_backslash(ctx, str))
126                         goto restart;
127         }
128 }
129
130 static inline void addc(char **dest, char **src)
131 {
132         **dest = **src;
133         *dest += 1;
134         *src += 1;
135 }
136
137 /*
138  * parse a double quoted string argument from the command line
139  */
140 static void parse_double_quote(struct uci_context *ctx, char **str, char **target)
141 {
142         char c;
143
144         /* skip quote character */
145         *str += 1;
146
147         while ((c = **str)) {
148                 switch(c) {
149                 case '"':
150                         **target = 0;
151                         *str += 1;
152                         return;
153                 case '\\':
154                         if (!parse_backslash(ctx, str))
155                                 continue;
156                         /* fall through */
157                 default:
158                         addc(target, str);
159                         break;
160                 }
161         }
162         ctx->pctx->reason = "unterminated \"";
163         ctx->pctx->byte = *str - ctx->pctx->buf;
164         UCI_THROW(ctx, UCI_ERR_PARSE);
165 }
166
167 /*
168  * parse a single quoted string argument from the command line
169  */
170 static void parse_single_quote(struct uci_context *ctx, char **str, char **target)
171 {
172         char c;
173         /* skip quote character */
174         *str += 1;
175
176         while ((c = **str)) {
177                 switch(c) {
178                 case '\'':
179                         **target = 0;
180                         *str += 1;
181                         return;
182                 default:
183                         addc(target, str);
184                 }
185         }
186         ctx->pctx->reason = "unterminated '";
187         ctx->pctx->byte = *str - ctx->pctx->buf;
188         UCI_THROW(ctx, UCI_ERR_PARSE);
189 }
190
191 /*
192  * parse a string from the command line and detect the quoting style
193  */
194 static void parse_str(struct uci_context *ctx, char **str, char **target)
195 {
196         do {
197                 switch(**str) {
198                 case '\'':
199                         parse_single_quote(ctx, str, target);
200                         break;
201                 case '"':
202                         parse_double_quote(ctx, str, target);
203                         break;
204                 case 0:
205                         goto done;
206                 case '\\':
207                         if (!parse_backslash(ctx, str))
208                                 continue;
209                         /* fall through */
210                 default:
211                         addc(target, str);
212                         break;
213                 }
214         } while (**str && !isspace(**str));
215 done:
216
217         /* 
218          * if the string was unquoted and we've stopped at a whitespace
219          * character, skip to the next one, because the whitespace will
220          * be overwritten by a null byte here
221          */
222         if (**str)
223                 *str += 1;
224
225         /* terminate the parsed string */
226         **target = 0;
227 }
228
229 /*
230  * extract the next argument from the command line
231  */
232 static char *next_arg(struct uci_context *ctx, char **str, bool required)
233 {
234         char *val;
235         char *ptr;
236
237         val = ptr = *str;
238         skip_whitespace(ctx, str);
239         parse_str(ctx, str, &ptr);
240         if (required && !*val) {
241                 ctx->pctx->reason = "insufficient arguments";
242                 ctx->pctx->byte = *str - ctx->pctx->buf;
243                 UCI_THROW(ctx, UCI_ERR_PARSE);
244         }
245
246         return uci_strdup(ctx, val);
247 }
248
249 /*
250  * verify that the end of the line or command is reached.
251  * throw an error if extra arguments are given on the command line
252  */
253 static void assert_eol(struct uci_context *ctx, char **str)
254 {
255         char *tmp;
256
257         tmp = next_arg(ctx, str, false);
258         if (tmp && *tmp) {
259                 ctx->pctx->reason = "too many arguments";
260                 ctx->pctx->byte = tmp - ctx->pctx->buf;
261                 UCI_THROW(ctx, UCI_ERR_PARSE);
262         }
263 }
264
265 /* 
266  * switch to a different config, either triggered by uci_load, or by a
267  * 'package <...>' statement in the import file
268  */
269 static void uci_switch_config(struct uci_context *ctx)
270 {
271         struct uci_parse_context *pctx;
272         const char *name;
273
274         pctx = ctx->pctx;
275         name = pctx->name;
276
277         /* add the last config to main config file list */
278         if (pctx->cfg) {
279                 uci_list_add(&ctx->root, &pctx->cfg->list);
280
281                 pctx->cfg = NULL;
282                 pctx->section = NULL;
283         }
284
285         if (!name)
286                 return;
287
288         /* 
289          * if an older config under the same name exists, unload it
290          * ignore errors here, e.g. if the config was not found
291          */
292         UCI_TRAP_SAVE(ctx, ignore);
293         uci_unload(ctx, name);
294         UCI_TRAP_RESTORE(ctx);
295 ignore:
296         ctx->errno = 0;
297
298         pctx->cfg = uci_alloc_config(ctx, name);
299 }
300
301 /*
302  * parse the 'package' uci command (next config package)
303  */
304 static void uci_parse_package(struct uci_context *ctx, char **str)
305 {
306         char *name = NULL;
307
308         /* command string null-terminated by strtok */
309         *str += strlen(*str) + 1;
310
311         UCI_TRAP_SAVE(ctx, error);
312         name = next_arg(ctx, str, true);
313         assert_eol(ctx, str);
314         ctx->pctx->name = name;
315         uci_switch_config(ctx);
316         UCI_TRAP_RESTORE(ctx);
317         return;
318
319 error:
320         if (name)
321                 free(name);
322         UCI_THROW(ctx, ctx->errno);
323 }
324
325 /*
326  * parse the 'config' uci command (open a section)
327  */
328 static void uci_parse_config(struct uci_context *ctx, char **str)
329 {
330         char *name = NULL;
331         char *type = NULL;
332
333         if (!ctx->pctx->cfg) {
334                 if (!ctx->pctx->name) {
335                         ctx->pctx->byte = *str - ctx->pctx->buf;
336                         ctx->pctx->reason = "attempting to import a file without a package name";
337                         UCI_THROW(ctx, UCI_ERR_PARSE);
338                 }
339                 uci_switch_config(ctx);
340         }
341
342         /* command string null-terminated by strtok */
343         *str += strlen(*str) + 1;
344
345         UCI_TRAP_SAVE(ctx, error);
346         type = next_arg(ctx, str, true);
347         name = next_arg(ctx, str, false);
348         assert_eol(ctx, str);
349         ctx->pctx->section = uci_add_section(ctx->pctx->cfg, type, name);
350         UCI_TRAP_RESTORE(ctx);
351         return;
352
353 error:
354         if (name)
355                 free(name);
356         if (type)
357                 free(type);
358         UCI_THROW(ctx, ctx->errno);
359 }
360
361 /*
362  * parse the 'option' uci command (open a value)
363  */
364 static void uci_parse_option(struct uci_context *ctx, char **str)
365 {
366         char *name = NULL;
367         char *value = NULL;
368
369         if (!ctx->pctx->section) {
370                 ctx->pctx->byte = *str - ctx->pctx->buf;
371                 ctx->pctx->reason = "option command found before the first section";
372                 UCI_THROW(ctx, UCI_ERR_PARSE);
373         }
374         /* command string null-terminated by strtok */
375         *str += strlen(*str) + 1;
376
377         UCI_TRAP_SAVE(ctx, error);
378         name = next_arg(ctx, str, true);
379         value = next_arg(ctx, str, true);
380         assert_eol(ctx, str);
381         uci_add_option(ctx->pctx->section, name, value);
382         UCI_TRAP_RESTORE(ctx);
383         return;
384
385 error:
386         if (name)
387                 free(name);
388         if (value)
389                 free(value);
390         UCI_THROW(ctx, ctx->errno);
391 }
392
393
394 /*
395  * parse a complete input line, split up combined commands by ';'
396  */
397 static void uci_parse_line(struct uci_context *ctx)
398 {
399         struct uci_parse_context *pctx = ctx->pctx;
400         char *word, *brk;
401
402         for (word = strtok_r(pctx->buf, ";", &brk);
403                  word;
404                  word = strtok_r(NULL, ";", &brk)) {
405
406                 char *pbrk;
407                 word = strtok_r(word, " \t", &pbrk);
408
409                 switch(word[0]) {
410                         case 'p':
411                                 if ((word[1] == 0) || !strcmp(word + 1, "ackage"))
412                                         uci_parse_package(ctx, &word);
413                                 break;
414                         case 'c':
415                                 if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
416                                         uci_parse_config(ctx, &word);
417                                 break;
418                         case 'o':
419                                 if ((word[1] == 0) || !strcmp(word + 1, "ption"))
420                                         uci_parse_option(ctx, &word);
421                                 break;
422                         default:
423                                 pctx->reason = "unterminated command";
424                                 pctx->byte = word - pctx->buf;
425                                 UCI_THROW(ctx, UCI_ERR_PARSE);
426                                 break;
427                 }
428         }
429 }
430
431 int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct uci_config **cfg)
432 {
433         struct uci_parse_context *pctx;
434
435         /* make sure no memory from previous parse attempts is leaked */
436         uci_parse_cleanup(ctx);
437
438         pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
439         ctx->pctx = pctx;
440         pctx->file = stream;
441
442         /*
443          * If 'name' was supplied, assume that the supplied stream does not contain
444          * the appropriate 'package <name>' string to specify the config name
445          * NB: the config file can still override the package name
446          */
447         if (name)
448                 pctx->name = name;
449
450         while (!feof(pctx->file)) {
451                 uci_getln(ctx, 0);
452                 if (pctx->buf[0])
453                         uci_parse_line(ctx);
454         }
455
456         if (cfg)
457                 *cfg = pctx->cfg;
458
459         pctx->name = NULL;
460         uci_switch_config(ctx);
461
462         /* no error happened, we can get rid of the parser context now */
463         uci_parse_cleanup(ctx);
464
465         return 0;
466 }
467
468 int uci_load(struct uci_context *ctx, const char *name, struct uci_config **cfg)
469 {
470         struct stat statbuf;
471         char *filename;
472         bool confpath;
473         FILE *file;
474
475         UCI_HANDLE_ERR(ctx);
476         UCI_ASSERT(ctx, name != NULL);
477
478 ignore:
479         ctx->errno = 0;
480
481         switch (name[0]) {
482         case '.':
483         case '/':
484                 /* absolute/relative path outside of /etc/config */
485                 filename = (char *) name;
486                 confpath = false;
487                 break;
488         default:
489                 filename = uci_malloc(ctx, strlen(name) + sizeof(UCI_CONFDIR) + 2);
490                 sprintf(filename, UCI_CONFDIR "/%s", name);
491                 confpath = true;
492                 break;
493         }
494
495         if ((stat(filename, &statbuf) < 0) ||
496                 ((statbuf.st_mode &  S_IFMT) != S_IFREG)) {
497                 UCI_THROW(ctx, UCI_ERR_NOTFOUND);
498         }
499
500         file = fopen(filename, "r");
501         if (filename != name)
502                 free(filename);
503
504         if (!file)
505                 UCI_THROW(ctx, UCI_ERR_IO);
506
507         return uci_import(ctx, file, name, cfg);
508 }
509