36ef34684539f9ea9ed5b26c20997acb777e95d8
[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 32
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)
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 and exporter
71 */
72 static void uci_file_cleanup(struct uci_context *ctx)
73 {
74 struct uci_parse_context *pctx;
75
76 if (ctx->buf) {
77 free(ctx->buf);
78 ctx->buf = NULL;
79 ctx->bufsz = 0;
80 }
81
82 pctx = ctx->pctx;
83 if (!pctx)
84 return;
85
86 ctx->pctx = NULL;
87 if (pctx->package)
88 uci_free_package(pctx->package);
89
90 if (pctx->buf)
91 free(pctx->buf);
92 if (pctx->file)
93 fclose(pctx->file);
94
95 free(pctx);
96 }
97
98 /*
99 * parse a character escaped by '\'
100 * returns true if the escaped character is to be parsed
101 * returns false if the escaped character is to be ignored
102 */
103 static inline bool parse_backslash(struct uci_context *ctx, char **str)
104 {
105 /* skip backslash */
106 *str += 1;
107
108 /* undecoded backslash at the end of line, fetch the next line */
109 if (!**str) {
110 *str += 1;
111 uci_getln(ctx, *str - ctx->pctx->buf);
112 return false;
113 }
114
115 /* FIXME: decode escaped char, necessary? */
116 return true;
117 }
118
119 /*
120 * move the string pointer forward until a non-whitespace character or
121 * EOL is reached
122 */
123 static void skip_whitespace(struct uci_context *ctx, char **str)
124 {
125 restart:
126 while (**str && isspace(**str))
127 *str += 1;
128
129 if (**str == '\\') {
130 if (!parse_backslash(ctx, str))
131 goto restart;
132 }
133 }
134
135 static inline void addc(char **dest, char **src)
136 {
137 **dest = **src;
138 *dest += 1;
139 *src += 1;
140 }
141
142 /*
143 * parse a double quoted string argument from the command line
144 */
145 static void parse_double_quote(struct uci_context *ctx, char **str, char **target)
146 {
147 char c;
148
149 /* skip quote character */
150 *str += 1;
151
152 while ((c = **str)) {
153 switch(c) {
154 case '"':
155 **target = 0;
156 *str += 1;
157 return;
158 case '\\':
159 if (!parse_backslash(ctx, str))
160 continue;
161 /* fall through */
162 default:
163 addc(target, str);
164 break;
165 }
166 }
167 ctx->pctx->reason = "unterminated \"";
168 ctx->pctx->byte = *str - ctx->pctx->buf;
169 UCI_THROW(ctx, UCI_ERR_PARSE);
170 }
171
172 /*
173 * parse a single quoted string argument from the command line
174 */
175 static void parse_single_quote(struct uci_context *ctx, char **str, char **target)
176 {
177 char c;
178 /* skip quote character */
179 *str += 1;
180
181 while ((c = **str)) {
182 switch(c) {
183 case '\'':
184 **target = 0;
185 *str += 1;
186 return;
187 default:
188 addc(target, str);
189 }
190 }
191 ctx->pctx->reason = "unterminated '";
192 ctx->pctx->byte = *str - ctx->pctx->buf;
193 UCI_THROW(ctx, UCI_ERR_PARSE);
194 }
195
196 /*
197 * parse a string from the command line and detect the quoting style
198 */
199 static void parse_str(struct uci_context *ctx, char **str, char **target)
200 {
201 do {
202 switch(**str) {
203 case '\'':
204 parse_single_quote(ctx, str, target);
205 break;
206 case '"':
207 parse_double_quote(ctx, str, target);
208 break;
209 case 0:
210 goto done;
211 case '\\':
212 if (!parse_backslash(ctx, str))
213 continue;
214 /* fall through */
215 default:
216 addc(target, str);
217 break;
218 }
219 } while (**str && !isspace(**str));
220 done:
221
222 /*
223 * if the string was unquoted and we've stopped at a whitespace
224 * character, skip to the next one, because the whitespace will
225 * be overwritten by a null byte here
226 */
227 if (**str)
228 *str += 1;
229
230 /* terminate the parsed string */
231 **target = 0;
232 }
233
234 /*
235 * extract the next argument from the command line
236 */
237 static char *next_arg(struct uci_context *ctx, char **str, bool required)
238 {
239 char *val;
240 char *ptr;
241
242 val = ptr = *str;
243 skip_whitespace(ctx, str);
244 parse_str(ctx, str, &ptr);
245 if (required && !*val) {
246 ctx->pctx->reason = "insufficient arguments";
247 ctx->pctx->byte = *str - ctx->pctx->buf;
248 UCI_THROW(ctx, UCI_ERR_PARSE);
249 }
250
251 return val;
252 }
253
254 /*
255 * verify that the end of the line or command is reached.
256 * throw an error if extra arguments are given on the command line
257 */
258 static void assert_eol(struct uci_context *ctx, char **str)
259 {
260 char *tmp;
261
262 tmp = next_arg(ctx, str, false);
263 if (tmp && *tmp) {
264 ctx->pctx->reason = "too many arguments";
265 ctx->pctx->byte = tmp - ctx->pctx->buf;
266 UCI_THROW(ctx, UCI_ERR_PARSE);
267 }
268 }
269
270 /*
271 * switch to a different config, either triggered by uci_load, or by a
272 * 'package <...>' statement in the import file
273 */
274 static void uci_switch_config(struct uci_context *ctx)
275 {
276 struct uci_parse_context *pctx;
277 const char *name;
278
279 pctx = ctx->pctx;
280 name = pctx->name;
281
282 /* add the last config to main config file list */
283 if (pctx->package) {
284 uci_list_add(&ctx->root, &pctx->package->e.list);
285
286 pctx->package = NULL;
287 pctx->section = NULL;
288 }
289
290 if (!name)
291 return;
292
293 /*
294 * if an older config under the same name exists, unload it
295 * ignore errors here, e.g. if the config was not found
296 */
297 UCI_TRAP_SAVE(ctx, ignore);
298 uci_unload(ctx, name);
299 UCI_TRAP_RESTORE(ctx);
300 ignore:
301 ctx->errno = 0;
302
303 pctx->package = uci_alloc_package(ctx, name);
304 }
305
306 /*
307 * parse the 'package' uci command (next config package)
308 */
309 static void uci_parse_package(struct uci_context *ctx, char **str)
310 {
311 char *name = NULL;
312
313 /* command string null-terminated by strtok */
314 *str += strlen(*str) + 1;
315
316 name = next_arg(ctx, str, true);
317 assert_eol(ctx, str);
318 ctx->pctx->name = name;
319 uci_switch_config(ctx);
320 }
321
322 /*
323 * parse the 'config' uci command (open a section)
324 */
325 static void uci_parse_config(struct uci_context *ctx, char **str)
326 {
327 char *name = NULL;
328 char *type = NULL;
329
330 if (!ctx->pctx->package) {
331 if (!ctx->pctx->name) {
332 ctx->pctx->byte = *str - ctx->pctx->buf;
333 ctx->pctx->reason = "attempting to import a file without a package name";
334 UCI_THROW(ctx, UCI_ERR_PARSE);
335 }
336 uci_switch_config(ctx);
337 }
338
339 /* command string null-terminated by strtok */
340 *str += strlen(*str) + 1;
341
342 type = next_arg(ctx, str, true);
343 name = next_arg(ctx, str, false);
344 assert_eol(ctx, str);
345 ctx->pctx->section = uci_alloc_section(ctx->pctx->package, type, name);
346 }
347
348 /*
349 * parse the 'option' uci command (open a value)
350 */
351 static void uci_parse_option(struct uci_context *ctx, char **str)
352 {
353 char *name = NULL;
354 char *value = NULL;
355
356 if (!ctx->pctx->section) {
357 ctx->pctx->byte = *str - ctx->pctx->buf;
358 ctx->pctx->reason = "option command found before the first section";
359 UCI_THROW(ctx, UCI_ERR_PARSE);
360 }
361 /* command string null-terminated by strtok */
362 *str += strlen(*str) + 1;
363
364 name = next_arg(ctx, str, true);
365 value = next_arg(ctx, str, true);
366 assert_eol(ctx, str);
367 uci_alloc_option(ctx->pctx->section, name, value);
368 }
369
370
371 /*
372 * parse a complete input line, split up combined commands by ';'
373 */
374 static void uci_parse_line(struct uci_context *ctx)
375 {
376 struct uci_parse_context *pctx = ctx->pctx;
377 char *word, *brk;
378
379 for (word = strtok_r(pctx->buf, ";", &brk);
380 word;
381 word = strtok_r(NULL, ";", &brk)) {
382
383 char *pbrk;
384 word = strtok_r(word, " \t", &pbrk);
385
386 switch(word[0]) {
387 case 'p':
388 if ((word[1] == 0) || !strcmp(word + 1, "ackage"))
389 uci_parse_package(ctx, &word);
390 break;
391 case 'c':
392 if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
393 uci_parse_config(ctx, &word);
394 break;
395 case 'o':
396 if ((word[1] == 0) || !strcmp(word + 1, "ption"))
397 uci_parse_option(ctx, &word);
398 break;
399 default:
400 pctx->reason = "unterminated command";
401 pctx->byte = word - pctx->buf;
402 UCI_THROW(ctx, UCI_ERR_PARSE);
403 break;
404 }
405 }
406 }
407
408 /* max number of characters that escaping adds to the string */
409 #define UCI_QUOTE_ESCAPE "'\\'"
410
411 /*
412 * escape an uci string for export
413 */
414 static char *uci_escape(struct uci_context *ctx, char *str)
415 {
416 char *s, *p;
417 int pos = 0;
418
419 if (!ctx->buf) {
420 ctx->bufsz = LINEBUF;
421 ctx->buf = malloc(LINEBUF);
422 }
423
424 s = str;
425 p = strchr(str, '\'');
426 if (!p)
427 return str;
428
429 do {
430 int len = p - s;
431 if (len > 0) {
432 if (p + sizeof(UCI_QUOTE_ESCAPE) - str >= ctx->bufsz) {
433 ctx->bufsz *= 2;
434 ctx->buf = realloc(ctx->buf, ctx->bufsz);
435 if (!ctx->buf)
436 UCI_THROW(ctx, UCI_ERR_MEM);
437 }
438 memcpy(&ctx->buf[pos], s, len);
439 pos += len;
440 }
441 strcpy(&ctx->buf[pos], UCI_QUOTE_ESCAPE);
442 pos += sizeof(UCI_QUOTE_ESCAPE);
443 s = p + 1;
444 } while ((p = strchr(s, '\'')));
445
446 return ctx->buf;
447 }
448
449
450 /*
451 * export a single config package to a file stream
452 */
453 static void uci_export_package(struct uci_package *p, FILE *stream)
454 {
455 struct uci_context *ctx = p->ctx;
456 struct uci_element *s, *o;
457
458 fprintf(stream, "package '%s'\n", uci_escape(ctx, p->e.name));
459 uci_foreach_element(&p->sections, s) {
460 struct uci_section *sec = uci_to_section(s);
461 fprintf(stream, "\nconfig '%s'", uci_escape(ctx, sec->type));
462 fprintf(stream, " '%s'\n", uci_escape(ctx, sec->e.name));
463 uci_foreach_element(&sec->options, o) {
464 struct uci_option *opt = uci_to_option(o);
465 fprintf(stream, "\toption '%s'", uci_escape(ctx, opt->e.name));
466 fprintf(stream, " '%s'\n", uci_escape(ctx, opt->value));
467 }
468 }
469 fprintf(stream, "\n");
470 }
471
472 int uci_export(struct uci_context *ctx, FILE *stream, struct uci_package *package)
473 {
474 struct uci_element *e;
475
476 UCI_HANDLE_ERR(ctx);
477 UCI_ASSERT(ctx, stream != NULL);
478
479 if (package) {
480 uci_export_package(package, stream);
481 goto done;
482 }
483
484 uci_foreach_element(&ctx->root, e) {
485 uci_export_package(uci_to_package(e), stream);
486 }
487 done:
488 return 0;
489 }
490
491 int uci_import(struct uci_context *ctx, FILE *stream, const char *name, struct uci_package **package)
492 {
493 struct uci_parse_context *pctx;
494
495 /* make sure no memory from previous parse attempts is leaked */
496 uci_file_cleanup(ctx);
497
498 pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
499 ctx->pctx = pctx;
500 pctx->file = stream;
501
502 /*
503 * If 'name' was supplied, assume that the supplied stream does not contain
504 * the appropriate 'package <name>' string to specify the config name
505 * NB: the config file can still override the package name
506 */
507 if (name)
508 pctx->name = name;
509
510 while (!feof(pctx->file)) {
511 uci_getln(ctx, 0);
512 if (pctx->buf[0])
513 uci_parse_line(ctx);
514 }
515
516 if (package)
517 *package = pctx->package;
518
519 pctx->name = NULL;
520 uci_switch_config(ctx);
521
522 /* no error happened, we can get rid of the parser context now */
523 uci_file_cleanup(ctx);
524
525 return 0;
526 }
527
528 int uci_load(struct uci_context *ctx, const char *name, struct uci_package **package)
529 {
530 struct stat statbuf;
531 char *filename;
532 bool confpath;
533 FILE *file;
534
535 UCI_HANDLE_ERR(ctx);
536 UCI_ASSERT(ctx, name != NULL);
537
538 switch (name[0]) {
539 case '.':
540 case '/':
541 /* absolute/relative path outside of /etc/config */
542 filename = (char *) name;
543 confpath = false;
544 break;
545 default:
546 filename = uci_malloc(ctx, strlen(name) + sizeof(UCI_CONFDIR) + 2);
547 sprintf(filename, UCI_CONFDIR "/%s", name);
548 confpath = true;
549 break;
550 }
551
552 if ((stat(filename, &statbuf) < 0) ||
553 ((statbuf.st_mode & S_IFMT) != S_IFREG)) {
554 UCI_THROW(ctx, UCI_ERR_NOTFOUND);
555 }
556
557 file = fopen(filename, "r");
558 if (filename != name)
559 free(filename);
560
561 if (!file)
562 UCI_THROW(ctx, UCI_ERR_IO);
563
564 return uci_import(ctx, file, name, package);
565 }
566