clean up arg checks
[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 static void skip_whitespace(char **str)
84 {
85         while (**str && isspace(**str))
86                 *str += 1;
87 }
88
89 static char *parse_double_quote(char **str)
90 {
91         char *val;
92         
93         *str += 1;
94         val = *str;
95         while (**str) {
96                         
97                 /* skip escaped characters */
98                 if (**str == '\\') {
99                         *str += 2;
100                         continue;
101                 }
102                 
103                 /* check for the end of the quoted string */
104                 if (**str == '"') {
105                         **str = 0;
106                         *str += 1;
107                         return val;
108                 }
109                 *str += 1;
110         }
111
112         return NULL;
113 }
114
115 static char *parse_single_quote(char **str)
116 {
117         /* TODO: implement */
118         return NULL;
119 }
120
121 static char *parse_unquoted(char **str)
122 {
123         char *val;
124         
125         val = *str;
126
127         while (**str && !isspace(**str))
128                 *str += 1;
129
130         if (**str) {
131                 **str = 0;
132                 *str += 1;
133         }
134
135         return val;
136 }
137
138 static char *next_arg(struct uci_context *ctx, char **str, bool required)
139 {
140         char *val;
141         skip_whitespace(str);
142
143         switch (**str) {
144                 case '"':
145                         val = parse_double_quote(str);
146                 case '\'':
147                         val = parse_single_quote(str);
148                 case 0:
149                         val = NULL;
150                 default:
151                         val = parse_unquoted(str);
152         }
153         
154         if (required && !val) {
155                 ctx->pctx->byte = *str - ctx->pctx->buf;
156                 UCI_THROW(ctx, UCI_ERR_PARSE);
157         }
158
159         return val;
160 }
161
162 static void assert_eol(struct uci_context *ctx, char **str)
163 {
164         char *tmp;
165
166         tmp = next_arg(ctx, str, false);
167         if (tmp) {
168                 ctx->pctx->byte = tmp - ctx->pctx->buf;
169                 UCI_THROW(ctx, UCI_ERR_PARSE);
170         }
171 }
172
173 static void uci_parse_config(struct uci_context *ctx, char **str)
174 {
175         char *type, *name;
176         
177         *str += strlen(*str) + 1;
178         
179         if (!*str) {
180                 ctx->pctx->byte = *str - ctx->pctx->buf;
181                 UCI_THROW(ctx, UCI_ERR_PARSE);
182         }
183
184         type = next_arg(ctx, str, true);
185         name = next_arg(ctx, str, false);
186         assert_eol(ctx, str);   
187
188         DPRINTF("Section<%s>: %s\n", type, name);
189 }
190
191 static void uci_parse_option(struct uci_context *ctx, char **str)
192 {
193         char *name, *value;
194         
195         *str += strlen(*str) + 1;
196         
197         name = next_arg(ctx, str, true);
198         value = next_arg(ctx, str, true);
199         assert_eol(ctx, str);   
200
201         DPRINTF("\tOption: %s=\"%s\"\n", name, value);
202 }
203
204 static void uci_parse_line(struct uci_context *ctx)
205 {
206         struct uci_parse_context *pctx = ctx->pctx;
207         char *word, *brk;
208         
209         for (word = strtok_r(pctx->buf, ";", &brk);
210                  word;
211                  word = strtok_r(NULL, ";", &brk)) {
212                         
213                 char *pbrk;
214                 word = strtok_r(word, " \t", &pbrk);
215                 
216                 switch(word[0]) {
217                         case 'c':
218                                 if ((word[1] == 0) || !strcmp(word + 1, "onfig"))
219                                         uci_parse_config(ctx, &word);
220                                 break;
221                         case 'o':
222                                 if ((word[1] == 0) || !strcmp(word + 1, "ption"))
223                                         uci_parse_option(ctx, &word);
224                                 break;
225                         default:
226                                 pctx->byte = word - pctx->buf;
227                                 UCI_THROW(ctx, UCI_ERR_PARSE);
228                                 break;
229                 }
230         }
231 }
232
233 int uci_parse(struct uci_context *ctx, const char *name)
234 {
235         struct uci_parse_context *pctx;
236         
237         UCI_HANDLE_ERR(ctx);
238         UCI_ASSERT(ctx, name != NULL);
239
240         /* make sure no memory from previous parse attempts is leaked */
241         uci_parse_cleanup(ctx);
242
243         pctx = (struct uci_parse_context *) uci_malloc(ctx, sizeof(struct uci_parse_context));
244         ctx->pctx = pctx;
245         
246         /* TODO: use /etc/config/ */
247         pctx->file = fopen(name, "r");
248         if (!pctx->file)
249                 UCI_THROW(ctx, UCI_ERR_NOTFOUND);
250
251         while (!feof(pctx->file)) {
252                 uci_getln(ctx);
253                 if (*(pctx->buf))
254                         uci_parse_line(ctx);
255         }
256
257         /* if no error happened, we can get rid of the parser context now */
258         uci_parse_cleanup(ctx);
259
260         return 0;
261 }
262
263