Initial import from http://www.droids-corp.org/hg/libcmdline/rev/db316e4289a1
[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  * 'linestart' parameter is the beginning of the line buffer.
47  * 'linecur' 
48  * Return a pointer to the first token copied from buf in a allocated
49  * buffer which is correctly nil-terminated. Also fill *eatlen
50  * argument the length of eaten bytes. The user should free the token
51  * structure and the string after use. On error, return NULL.
52  */
53 static const char *append_token(struct line *line, const char *linestart,
54                                 const char *linecur)
55 {
56         struct token *tok;
57         const char *start, *s;
58         unsigned len;
59         char *retbuf;
60
61         tok = strictmalloc(sizeof(struct token));
62
63         /* skip spaces */
64         s = linecur;
65         while (*s != '\0' && isspace(*s))
66                 s++;
67         if (*s == '\0' || *s == '#')
68                 goto free;
69
70         tok->offset = linecur - linestart;
71
72         /* if it's a double quote, wait next double quote */
73         if (*s == '"') {
74                 s++;
75                 start = s;
76                 while (*s != '\0') {
77                         if (*s == '"' && *(s-1) != '\\')
78                                 break;
79                         s++;
80                 }
81                 if (*s == '\0')
82                         return NULL; /* XXX free */
83                 linecur = s + 1;
84         }
85         /* if it's a simple quote, wait next simple quote */
86         else if (*s == '\'') {
87                 s++;
88                 start = s;
89                 while (*s != '\0') {
90                         if (*s == '\'' && *(s-1) != '\\')
91                                 break;
92                         s++;
93                 }
94                 if (*s == '\0')
95                         goto free;
96                 linecur = s + 1;
97         }
98         /* else wait a space */
99         else {
100                 start = s;
101                 while (*s != '\0' && !isspace(*s)) {
102                         if (*s == '#')
103                                 break;
104                         s++;
105                 }
106                 linecur = s;
107         }
108         len = s - start;
109
110         /* allocate string */
111         retbuf = strictmalloc(len + 1);
112         memcpy(retbuf, start, len);
113         retbuf[len] = '\0';
114
115         /* fill token structure and append it in line */
116         tok->str = retbuf;
117         //printf("Append <%s>\n", tok->str);
118         TAILQ_INSERT_TAIL(&line->token_list, tok, next);
119         line->n_token ++;
120
121         return linecur;
122
123  free:
124         free(tok);
125         return NULL;
126 }
127
128 /*
129  * Parse the line contained in buffer 'buf'. This function fills a
130  * line structure, composed of several tokens.
131  */
132 static struct line *parse_conf_line(const char *linebuf)
133 {
134         struct line *line;
135         const char *curbuf = linebuf;
136
137         line = strictmalloc(sizeof(struct line));
138         memset(line, 0, sizeof(struct line));
139         TAILQ_INIT(&line->token_list);
140
141         if (isspace(*linebuf))
142                 line->start_with_space = 1;
143
144         /* read tokens from buf and append it to line structure */
145         while (curbuf)
146                 curbuf = append_token(line, linebuf, curbuf);
147
148         return line;
149 }
150
151 int check_opt_name(const char *name)
152 {
153         /* XXX todo */
154         (void)name;
155         return 0;
156 }
157
158 /* return true if all dependancies are met to set the enable the
159  * node */
160
161
162
163 /* parse a line to get a "help" token */
164 int parse_help(const struct line *line)
165 {
166         struct token *tok;
167
168         tok = TAILQ_FIRST(&line->token_list);
169         if (tok == NULL)
170                 return -1; /* should not happen */
171
172         if (!strcmp(tok->str, "help") && line->n_token == 1)
173                 return 0;
174         else if (!strcmp(tok->str, "---help---") && line->n_token == 1)
175                 return 0;
176         return -1;
177 }
178
179 /* look if given line contains a "source" command, and execute the
180  * command if it's the case. Return 0 on success and -1 on error. XXX */
181 const char *source_file(const struct line *line)
182 {
183         struct token *tok;
184
185         tok = TAILQ_FIRST(&line->token_list);
186         if (tok == NULL)
187                 return NULL; /* should not happen */
188
189         /* syntax is: source FILE_NAME */
190         if (!strcmp(tok->str, "source") && line->n_token == 2) {
191                 tok = TAILQ_NEXT(tok, next);
192                 //printf("source <%s>\n", tok->str);
193                 return tok->str;
194         }
195         return NULL;
196 }
197
198 /*
199  * Parse the file given as argument and return a tree describing the
200  * configuration. XXX wrong
201  */
202 int parse_conf_file(const char *filename, struct confnode **pparent,
203                     struct confnode **pcurrent)
204 {
205         char buf[BUFSIZ];
206         char *c;
207         FILE *f;
208         struct line *line;
209         struct token *tok;
210         int read_help = 0;
211         struct confnode *current = *pcurrent;
212         struct confnode *parent = *pparent;
213         const char *sourcefilename;
214         int linenum = 0;
215
216         /* XXX lookup in several PATHs */
217         f = fopen(filename, "r");
218         if (f == NULL) {
219                 printf("cannot find file <%s>\n", filename);
220                 return -1;
221         }
222         while (fgets(buf, sizeof(buf), f) != NULL) {
223                 linenum ++;
224
225                 /* printf("parent=%s: %s", parent->name, buf); */
226
227                 /* we are reading help of an option, do it until the
228                  * line does not starts with space */
229                 if (read_help && (isspace(*buf) || line_is_empty(buf))) {
230
231                         /* should not happen */
232                         if (current == NULL) {
233                                 /* remove \n, display line # */
234                                 c = strchr(buf, '\n');
235                                 if (c != NULL)
236                                         *c = '\0';
237                                 printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
238                                 exit(1);
239                         }
240
241                         /* append string in buffer */
242                         strncat(current->help, buf,
243                                 sizeof(current->help) - strlen(current->help) - 1);
244                         continue;
245                 }
246
247                 read_help = 0;
248
249                 /* skip empty / comments lines */
250                 if (line_is_empty(buf))
251                         continue;
252
253                 /* parse line */
254                 line = parse_conf_line(buf);
255
256                 /* XXX put all the following in a function */
257
258
259                 /* if the line does not start with a space, it's a new
260                  * keyword, so it's a new node */
261                 if (!line->start_with_space) {
262
263                         /* end of previous node */
264                         if (current && current->flags & CONFNODE_F_IS_DIR) {
265                                 parent = current;
266                                 current = NULL;
267                         }
268
269                         /* if it's a source command, parse the new
270                          * file */
271                         sourcefilename = source_file(line);
272                         if (sourcefilename) {
273                                 if (parse_conf_file(sourcefilename, &parent, &current)) {
274                                         /* remove \n, display line # */
275                                         c = strchr(buf, '\n');
276                                         if (c != NULL)
277                                                 *c = '\0';
278                                         printf("%s:%d invalid line <%s>\n", filename,
279                                                linenum, buf);
280                                         exit(1);
281                                 }
282                                 goto free_line;
283                         }
284
285                         /* if the command closes a menu */
286                         if (confnode_close_dir(parent, line) == 0) {
287                                 parent = parent->parent;
288                                 goto free_line;
289                         }
290
291                         /* if it's a new node command */
292                         current = confnode_new(line);
293                         if (current) {
294                                 current->parent = parent;
295                                 TAILQ_INSERT_TAIL(&parent->children, current, next);
296                                 goto free_line;
297                         }
298
299                         /* remove \n, display line # */
300                         c = strchr(buf, '\n');
301                         if (c != NULL)
302                                 *c = '\0';
303                         printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
304                         exit(1);
305                 }
306
307                 else {
308                         /* see if it's a help indication */
309                         if (parse_help(line) == 0) {
310                                 read_help = 1;
311                                 strcpy(current->help, "");
312                                 goto free_line;
313                         }
314
315                         /* parse node attributes */
316                         if (confnode_add_attr(current, line, buf) == 0)
317                                 goto free_line;
318
319                         /* remove \n, display line # */
320                         c = strchr(buf, '\n');
321                         if (c != NULL)
322                                 *c = '\0';
323                         printf("%s:%d invalid line <%s>\n", filename, linenum, buf);
324                         exit(1);
325                 }
326
327         free_line:
328                 while ( (tok = TAILQ_FIRST(&line->token_list)) ) {
329                         TAILQ_REMOVE(&line->token_list, tok, next);
330                         free(tok);
331                 }
332                 free(line);
333         }
334         fclose(f);
335
336         *pparent = parent;
337         *pcurrent = current;
338         /* XXX */
339         return 0;
340 }
341
342 /* delete all stored values in conf tree */
343 void conf_reset(struct confnode *conf)
344 {
345         struct confnode *c;
346         conf->value[0] = '\0';
347         TAILQ_FOREACH(c, &conf->children, next) {
348                 conf_reset(c);
349         }
350 }
351
352 /*
353  * Free previous conf 'prev' if not NULL, then parse the Kconfig and
354  * the .config files. Fill the htable. Return the top node of the
355  * configuration tree. Return NULL on error.
356  */
357 struct confnode *conf_reset_and_read(struct confnode *prev,
358                                      const char *dotconfig_filename)
359 {
360         struct confnode *current, *parent;
361         struct confnode *root;
362         /* XXX free prev */
363
364         confnode_register_all();
365         conf_htable_init();
366         root = confnode_new_root();
367         if (root == NULL)
368                 return NULL;
369
370         parent = root;
371         current = NULL;
372 #if 1
373         parse_conf_file("/home/zer0/projects/libcmdline/Kconfig",
374                         &parent, &current);
375 #else
376         parse_conf_file("/Users/zer0/projects/libcmdline/Kconfig",
377                         &parent, &current);
378 #endif
379         conf_htable_fill(root);
380         if (dotconfig_read(dotconfig_filename, root) < 0)
381                 printf("Cannot parse <%s>, use defaults\n", dotconfig_filename);
382
383         return root;
384 }