genconf: properly read the value of the "if" node
[libcmdline.git] / src / genconf / conf_parser.c
1 /*
2  * Copyright (c) 2009, Olivier MATZ <zer0@droids-corp.org>
3  * All rights reserved.
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  *     * Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *     * Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *     * Neither the name of the University of California, Berkeley nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
17  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19  * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27
28 #include <stdio.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <ctype.h>
32 #include <unistd.h>
33 #include <sys/queue.h>
34
35 #include "strictmalloc.h"
36 #include "expression.h"
37 #include "conf_parser.h"
38 #include "confnode.h"
39 #include "conf_htable.h"
40 #include "parser_common.h"
41 #include "dotconfig.h"
42
43
44 /*
45  * Add a token in the line structure given as argument.
46  *
47  * 'linestart': pointer to the line buffer.
48  * 'linecur': current pointer in the line string (same string than linestart).
49  *
50  * Return a pointer to the first token copied from buf in a allocated
51  * buffer which is correctly nil-terminated. Also fill *eatlen
52  * argument the length of eaten bytes. The user should free the token
53  * structure and the string after use. On error, return NULL.
54  */
55 static const char *append_token(struct line *line, const char *linestart,
56                                 const char *linecur)
57 {
58         struct token *tok;
59         const char *start, *s;
60         unsigned len;
61         char *retbuf;
62
63         tok = strictmalloc(sizeof(struct token));
64
65         /* skip spaces */
66         s = linecur;
67         while (*s != '\0' && isspace(*s))
68                 s++;
69         if (*s == '\0' || *s == '#')
70                 goto free;
71
72         tok->offset = linecur - linestart;
73
74         /* if it's a double quote, wait next double quote */
75         if (*s == '"') {
76                 s++;
77                 start = s;
78                 while (*s != '\0') {
79                         if (*s == '"' && *(s-1) != '\\')
80                                 break;
81                         s++;
82                 }
83                 if (*s == '\0')
84                         return NULL; /* XXX free */
85                 linecur = s + 1;
86         }
87         /* if it's a simple quote, wait next simple quote */
88         else if (*s == '\'') {
89                 s++;
90                 start = s;
91                 while (*s != '\0') {
92                         if (*s == '\'' && *(s-1) != '\\')
93                                 break;
94                         s++;
95                 }
96                 if (*s == '\0')
97                         goto free;
98                 linecur = s + 1;
99         }
100         /* else wait a space */
101         else {
102                 start = s;
103                 while (*s != '\0' && !isspace(*s)) {
104                         if (*s == '#')
105                                 break;
106                         s++;
107                 }
108                 linecur = s;
109         }
110         len = s - start;
111
112         /* allocate string */
113         retbuf = strictmalloc(len + 1);
114         memcpy(retbuf, start, len);
115         retbuf[len] = '\0';
116
117         /* fill token structure and append it in line */
118         tok->str = retbuf;
119         //printf("Append <%s>\n", tok->str);
120         TAILQ_INSERT_TAIL(&line->token_list, tok, next);
121         line->n_token ++;
122
123         return linecur;
124
125  free:
126         free(tok);
127         return NULL;
128 }
129
130 /*
131  * Parse the line contained in buffer 'buf'. This function fills a
132  * line structure, composed of several tokens.
133  */
134 static struct line *parse_conf_line(const char *linebuf)
135 {
136         struct line *line;
137         const char *curbuf = linebuf;
138
139         line = strictmalloc(sizeof(struct line));
140         memset(line, 0, sizeof(struct line));
141         TAILQ_INIT(&line->token_list);
142
143         if (isspace(*linebuf))
144                 line->start_with_space = 1;
145
146         /* read tokens from buf and append it to line structure */
147         while (curbuf)
148                 curbuf = append_token(line, linebuf, curbuf);
149
150         return line;
151 }
152
153 int check_opt_name(const char *name)
154 {
155         /* XXX todo */
156         (void)name;
157         return 0;
158 }
159
160 /* return true if all dependancies are met to set the enable the
161  * node */
162
163
164
165 /* parse a line to get a "help" token */
166 int parse_help(const struct line *line)
167 {
168         struct token *tok;
169
170         tok = TAILQ_FIRST(&line->token_list);
171         if (tok == NULL)
172                 return -1; /* should not happen */
173
174         if (!strcmp(tok->str, "help") && line->n_token == 1)
175                 return 0;
176         else if (!strcmp(tok->str, "---help---") && line->n_token == 1)
177                 return 0;
178         return -1;
179 }
180
181 /* look if given line contains a "source" command, and execute the
182  * command if it's the case. Return 0 on success and -1 on error. XXX */
183 const char *source_file(const struct line *line)
184 {
185         struct token *tok;
186
187         tok = TAILQ_FIRST(&line->token_list);
188         if (tok == NULL)
189                 return NULL; /* should not happen */
190
191         /* syntax is: source FILE_NAME */
192         if (!strcmp(tok->str, "source") && line->n_token == 2) {
193                 tok = TAILQ_NEXT(tok, next);
194                 //printf("source <%s>\n", tok->str);
195                 return tok->str;
196         }
197         return NULL;
198 }
199
200 /*
201  * Parse the file given as argument and return a tree describing the
202  * configuration. XXX wrong
203  */
204 int parse_conf_file(const char *filename, struct confnode **pparent,
205                     struct confnode **pcurrent)
206 {
207         char buf[BUFSIZ];
208         char *c;
209         FILE *f;
210         struct line *line;
211         struct token *tok;
212         int read_help = 0;
213         struct confnode *current = *pcurrent;
214         struct confnode *parent = *pparent;
215         const char *sourcefilename;
216         int linenum = 0;
217
218         /* XXX lookup in several PATHs */
219         f = fopen(filename, "r");
220         if (f == NULL) {
221                 printf("cannot find file <%s>\n", filename);
222                 return -1;
223         }
224         while (fgets(buf, sizeof(buf), f) != NULL) {
225                 linenum ++;
226
227                 /* printf("parent=%s: %s", parent->name, buf); */
228
229                 /* we are reading help of an option, do it until the
230                  * line does not starts with space */
231                 if (read_help && (isspace(*buf) || line_is_empty(buf))) {
232
233                         /* should not happen */
234                         if (current == NULL) {
235                                 /* remove \n, display line # */
236                                 c = strchr(buf, '\n');
237                                 if (c != NULL)
238                                         *c = '\0';
239                                 printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
240                                 exit(1);
241                         }
242
243                         /* append string in buffer */
244                         strncat(current->help, buf,
245                                 sizeof(current->help) - strlen(current->help) - 1);
246                         continue;
247                 }
248
249                 read_help = 0;
250
251                 /* skip empty / comments lines */
252                 if (line_is_empty(buf))
253                         continue;
254
255                 /* parse line */
256                 line = parse_conf_line(buf);
257
258                 /* XXX put all the following in a function */
259
260
261                 /* if the line does not start with a space, it's a new
262                  * keyword, so it's a new node */
263                 if (!line->start_with_space) {
264
265                         /* end of previous node */
266                         if (current && current->flags & CONFNODE_F_IS_DIR) {
267                                 parent = current;
268                                 current = NULL;
269                         }
270
271                         /* if it's a source command, parse the new
272                          * file */
273                         sourcefilename = source_file(line);
274                         if (sourcefilename) {
275                                 if (parse_conf_file(sourcefilename, &parent, &current)) {
276                                         /* remove \n, display line # */
277                                         c = strchr(buf, '\n');
278                                         if (c != NULL)
279                                                 *c = '\0';
280                                         printf("%s:%d invalid line <%s>\n", filename,
281                                                linenum, buf);
282                                         exit(1);
283                                 }
284                                 goto free_line;
285                         }
286
287                         /* if the command closes a menu */
288                         if (confnode_close_dir(parent, line) == 0) {
289                                 parent = parent->parent;
290                                 goto free_line;
291                         }
292
293                         /* if it's a new node command */
294                         current = confnode_new(line);
295                         if (current) {
296                                 current->parent = parent;
297                                 TAILQ_INSERT_TAIL(&parent->children, current, next);
298                                 goto free_line;
299                         }
300
301                         /* remove \n, display line # */
302                         c = strchr(buf, '\n');
303                         if (c != NULL)
304                                 *c = '\0';
305                         printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
306                         exit(1);
307                 }
308
309                 else {
310                         /* see if it's a help indication */
311                         if (parse_help(line) == 0) {
312                                 read_help = 1;
313                                 strcpy(current->help, "");
314                                 goto free_line;
315                         }
316
317                         /* parse node attributes */
318                         if (confnode_add_attr(current, line, buf) == 0)
319                                 goto free_line;
320
321                         /* remove \n, display line # */
322                         c = strchr(buf, '\n');
323                         if (c != NULL)
324                                 *c = '\0';
325                         printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
326                         exit(1);
327                 }
328
329         free_line:
330                 while ( (tok = TAILQ_FIRST(&line->token_list)) ) {
331                         TAILQ_REMOVE(&line->token_list, tok, next);
332                         free(tok);
333                 }
334                 free(line);
335         }
336         fclose(f);
337
338         *pparent = parent;
339         *pcurrent = current;
340         /* XXX */
341         return 0;
342 }
343
344 /* delete all stored values in conf tree */
345 void conf_reset(struct confnode *conf)
346 {
347         struct confnode *c;
348         conf->value[0] = '\0';
349         TAILQ_FOREACH(c, &conf->children, next) {
350                 conf_reset(c);
351         }
352 }
353
354 /*
355  * Free previous conf 'prev' if not NULL, then parse the Kconfig and
356  * the .config files. Fill the htable. Return the top node of the
357  * configuration tree. Return NULL on error.
358  */
359 struct confnode *conf_reset_and_read(struct confnode *prev,
360                                      const char *dotconfig_filename)
361 {
362         struct confnode *current, *parent;
363         struct confnode *root;
364         /* XXX free prev */
365
366         confnode_register_all();
367         conf_htable_init();
368         root = confnode_new_root();
369         if (root == NULL)
370                 return NULL;
371
372         parent = root;
373         current = NULL;
374 #if 1
375         parse_conf_file("/home/zer0/projects/libcmdline/Kconfig",
376                         &parent, &current);
377 #else
378         parse_conf_file("/Users/zer0/projects/libcmdline/Kconfig",
379                         &parent, &current);
380 #endif
381         conf_htable_fill(root);
382         if (dotconfig_read(dotconfig_filename, root) < 0)
383                 printf("Cannot parse <%s>, use defaults\n", dotconfig_filename);
384
385         return root;
386 }