b15421050f088a4f75d0727ab704a95c6d795262
[project/uci.git] / parse.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 <ctype.h>
20
21 #define LINEBUF 128
22 #define LINEBUF_MAX     4096
23
24 /*
25  * Fetch a new line from the input stream and resize buffer if necessary
26  */
27 static void uci_getln(struct uci_context *ctx)
28 {
29         struct uci_parse_context *pctx = ctx->pctx;
30         char *p;
31         int ofs;
32
33         if (pctx->buf == NULL) {
34                 pctx->buf = uci_malloc(ctx, LINEBUF);
35                 pctx->bufsz = LINEBUF;
36         }
37
38         ofs = 0;
39         do {
40                 p = &pctx->buf[ofs];
41                 p[ofs] = 0;
42
43                 p = fgets(p, pctx->bufsz - ofs, pctx->file);
44                 if (!p || !p[ofs])
45                         return;
46
47                 ofs += strlen(p);
48                 if (pctx->buf[ofs - 1] == '\n') {
49                         pctx->line++;
50                         pctx->buf[ofs - 1] = 0;
51                         return;
52                 }
53
54                 if (pctx->bufsz > LINEBUF_MAX/2) {
55                         pctx->byte = LINEBUF_MAX;
56                         UCI_THROW(ctx, UCI_ERR_PARSE);
57                 }
58
59                 pctx->bufsz *= 2;
60                 pctx->buf = uci_realloc(ctx, pctx->buf, pctx->bufsz);
61         } while (1);
62 }
63
64 /*
65  * Clean up all extra memory used by the parser
66  */
67 static void uci_parse_cleanup(struct uci_context *ctx)
68 {
69         struct uci_parse_context *pctx;
70
71         pctx = ctx->pctx;
72         if (!pctx)
73                 return;
74
75         if (pctx->buf)
76                 free(pctx->buf);
77         if (pctx->file)
78                 fclose(pctx->file);
79
80         free(pctx);
81 }
82
83 /*
84  * move the string pointer forward until a non-whitespace character or
85  * EOL is reached
86  */
87 static void skip_whitespace(char **str)
88 {
89         while (**str && isspace(**str))
90                 *str += 1;
91 }
92
93 /*
94  * parse a double quoted string argument from the command line
95  */
96 static char *parse_double_quote(char **str)
97 {
98         char *val;
99
100         *str += 1;
101         val = *str;
102         while (**str) {
103
104                 /* skip escaped characters */
105                 if (**str == '\\') {
106                         *str += 2;
107                         continue;
108                 }
109
110                 /* check for the end of the quoted string */
111                 if (**str == '"') {
112                         **str = 0;
113                         *str += 1;
114                         return val;
115                 }
116                 *str += 1;
117         }
118
119         return NULL;
120 }
121
122 /*
123  * parse a single quoted string argument from the command line
124  */
125 static char *parse_single_quote(char **str)
126 {
127         /* TODO: implement */
128         return NULL;
129 }
130
131 /*
132  * extract the next word from the command line (unquoted argument)
133  */
134 static char *parse_unquoted(char **str)
135 {
136         char *val;
137
138         val = *str;
139
140         while (**str && !isspace(**str))
141                 *str += 1;
142
143         if (**str) {
144                 **str = 0;
145                 *str += 1;
146         }
147
148         return val;
149 }
150
151 /*
152  * extract the next argument from the command line
153  */
154 static char *next_arg(struct uci_context *ctx, char **str, bool required)
155 {
156         char *val;
157
158         skip_whitespace(str);
159         switch (**str) {
160                 case '"':
161                         val = parse_double_quote(str);
162                 case '\'':
163                         val = parse_single_quote(str);
164                 case 0:
165                         val = NULL;
166                 default:
167                         val = parse_unquoted(str);
168         }
169
170         if (required && !val) {
171                 ctx->pctx->byte = *str - ctx->pctx->buf;
172                 UCI_THROW(ctx, UCI_ERR_PARSE);
173         }
174
175         return val;
176 }
177
178 /*
179  * verify that the end of the line or command is reached.
180  * throw an error if extra arguments are given on the command line
181  */
182 static void assert_eol(struct uci_context *ctx, char **str)
183 {
184         char *tmp;
185
186         tmp = next_arg(ctx, str, false);
187         if (tmp && *tmp) {
188                 ctx->pctx->byte = tmp - ctx->pctx->buf;
189                 UCI_THROW(ctx, UCI_ERR_PARSE);
190         }
191 }
192
193 /*
194  * parse the 'config' uci command (open a section)
195  */
196 static void uci_parse_config(struct uci_context *ctx, char **str)
197 {
198         char *type, *name;
199
200         *str += strlen(*str) + 1;
201
202         if (!*str) {
203                 ctx->pctx->byte = *str - ctx->pctx->buf;
204                 UCI_THROW(ctx, UCI_ERR_PARSE);
205         }
206
207         type = next_arg(ctx, str, true);
208         name = next_arg(ctx, str, false);
209         assert_eol(ctx, str);
210
211         DPRINTF("Section<%s>: %s\n", type, name);
212 }
213
214 /*
215  * parse the 'option' uci command (open a value)
216  */
217 static void uci_parse_option(struct uci_context *ctx, char **str)
218 {
219         char *name, *value;
220
221         *str += strlen(*str) + 1;
222
223         name = next_arg(ctx, str, true);
224         value = next_arg(ctx, str, true);
225         assert_eol(ctx, str);
226
227         DPRINTF("\tOption: %s=\"%s\"\n", name, value);
228 }
229
230 /*
231  * parse a complete input line, split up combined commands by ';'
232  */
233 static void uci_parse_line(struct uci_context *ctx)
234 {
235         struct uci_parse_context *pctx = ctx->pctx;
236         char *word, *brk;
237
238         for (word = strtok_r(pctx->buf, ";", &brk);
239                  word;
240                  word = strtok_r(NULL, ";", &brk)) {
241
242                 char *pbrk;
243                 word = strtok_r(word, " \t", &pbrk);
244
245                 switch(word[0]) {
246                         case 'c':
247                                 if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
248                                         uci_parse_config(ctx, &word);
249                                 break;
250                         case 'o':
251                                 if ((word[1] == 0) || !strcmp(word + 1, "ption"))
252                                         uci_parse_option(ctx, &word);
253                                 break;
254                         default:
255                                 pctx->byte = word - pctx->buf;
256                                 UCI_THROW(ctx, UCI_ERR_PARSE);
257                                 break;
258                 }
259         }
260 }
261
262 int uci_parse(struct uci_context *ctx, const char *name)
263 {
264         struct uci_parse_context *pctx;
265
266         UCI_HANDLE_ERR(ctx);
267         UCI_ASSERT(ctx, name != NULL);
268
269         /* make sure no memory from previous parse attempts is leaked */
270         uci_parse_cleanup(ctx);
271
272         pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
273         ctx->pctx = pctx;
274
275         /* TODO: use /etc/config/ */
276         pctx->file = fopen(name, "r");
277         if (!pctx->file)
278                 UCI_THROW(ctx, UCI_ERR_NOTFOUND);
279
280         while (!feof(pctx->file)) {
281                 uci_getln(ctx);
282                 if (*(pctx->buf))
283                         uci_parse_line(ctx);
284         }
285
286         /* if no error happened, we can get rid of the parser context now */
287         uci_parse_cleanup(ctx);
288
289         return 0;
290 }
291
292