1/* Copyright (C) 2007-2008 The Android Open Source Project
2**
3** This software is licensed under the terms of the GNU General Public
4** License version 2, as published by the Free Software Foundation, and
5** may be copied, distributed, and modified under those terms.
6**
7** This program is distributed in the hope that it will be useful,
8** but WITHOUT ANY WARRANTY; without even the implied warranty of
9** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10** GNU General Public License for more details.
11*/
12#include <string.h>
13#include <ctype.h>
14#include <stdlib.h>
15#include <fcntl.h>
16#include <unistd.h>
17
18#include "android/config.h"
19#include "android/utils/path.h"
20
21AConfig*
22aconfig_node(const char *name, const char *value)
23{
24    AConfig *n;
25
26    n = (AConfig*) calloc(sizeof(AConfig), 1);
27    n->name = name ? name : "";
28    n->value = value ? value : "";
29
30    return n;
31}
32
33static AConfig*
34_aconfig_find(AConfig *root, const char *name, int create)
35{
36    AConfig *node;
37
38    for(node = root->first_child; node; node = node->next) {
39        if(!strcmp(node->name, name)) return node;
40    }
41
42    if(create) {
43        node = (AConfig*) calloc(sizeof(AConfig), 1);
44        node->name = name;
45        node->value = "";
46
47        if(root->last_child) {
48            root->last_child->next = node;
49        } else {
50            root->first_child = node;
51        }
52        root->last_child = node;
53    }
54
55    return node;
56}
57
58AConfig*
59aconfig_find(AConfig *root, const char *name)
60{
61    return _aconfig_find(root, name, 0);
62}
63
64int
65aconfig_bool(AConfig *root, const char *name, int _default)
66{
67    AConfig *n = _aconfig_find(root, name, 0);
68    if(n == 0) {
69        return _default;
70    } else {
71        switch(n->value[0]){
72        case 'y':
73        case 'Y':
74        case '1':
75            return 1;
76        default:
77            return 0;
78        }
79    }
80}
81
82unsigned
83aconfig_unsigned(AConfig *root, const char *name, unsigned _default)
84{
85    AConfig *n = _aconfig_find(root, name, 0);
86    if(n == 0) {
87        return _default;
88    } else {
89        return strtoul(n->value, 0, 0);
90    }
91}
92
93int
94aconfig_int(AConfig *root, const char *name, int _default)
95{
96    AConfig *n = _aconfig_find(root, name, 0);
97    if(n == 0) {
98        return _default;
99    } else {
100        return strtol(n->value, 0, 0);
101    }
102}
103
104
105const char*
106aconfig_str(AConfig *root, const char *name, const char *_default)
107{
108    AConfig *n = _aconfig_find(root, name, 0);
109    if(n == 0) {
110        return _default;
111    } else {
112        return n->value;
113    }
114}
115
116void
117aconfig_set(AConfig *root, const char *name, const char *value)
118{
119    AConfig *node = _aconfig_find(root, name, 1);
120    node->value = value;
121}
122
123#define T_EOF 0
124#define T_TEXT 1
125#define T_DOT 2
126#define T_OBRACE 3
127#define T_CBRACE 4
128
129typedef struct
130{
131    char *data;
132    char *text;
133    int len;
134    char next;
135} cstate;
136
137
138static int _lex(cstate *cs, int value)
139{
140    char c;
141    char *s;
142    char *data;
143
144    data = cs->data;
145
146    if(cs->next != 0) {
147        c = cs->next;
148        cs->next = 0;
149        goto got_c;
150    }
151
152restart:
153    for(;;) {
154        c = *data++;
155    got_c:
156        if(isspace(c)) continue;
157
158        switch(c) {
159        case 0:
160            return T_EOF;
161
162        /* a sharp sign (#) starts a line comment and everything
163         * behind that is ignored until the end of line
164         */
165        case '#':
166            for(;;) {
167                switch(*data) {
168                case 0:
169                    cs->data = data;
170                    return T_EOF;
171                case '\n':
172                    cs->data = data + 1;
173                    goto restart;
174                default:
175                    data++;
176                }
177            }
178            break;
179
180        case '.':
181            cs->data = data;
182            return T_DOT;
183
184        case '{':
185            cs->data = data;
186            return T_OBRACE;
187
188        case '}':
189            cs->data = data;
190            return T_CBRACE;
191
192        default:
193            s = data - 1;
194
195            if(value) {
196               /* if we're looking for a value, then take anything
197                * until the end of line. note that sharp signs do
198                * not start comments then. the result will be stripped
199                * from trailing whitespace.
200                */
201                for(;;) {
202                    if(*data == 0) {
203                        cs->data = data;
204                        break;
205                    }
206                    if(*data == '\n') {
207                        cs->data = data + 1;
208                        *data-- = 0;
209                        break;
210                    }
211                    data++;
212                }
213
214                    /* strip trailing whitespace */
215                while(data > s){
216                    if(!isspace(*data)) break;
217                    *data-- = 0;
218                }
219
220                goto got_text;
221            } else {
222               /* looking for a key name. we stop at whitspace,
223                * EOF, of T_DOT/T_OBRACE/T_CBRACE characters.
224                * note that the name can include sharp signs
225                */
226                for(;;) {
227                    if(isspace(*data)) {
228                        *data = 0;
229                        cs->data = data + 1;
230                        goto got_text;
231                    }
232                    switch(*data) {
233                    case 0:
234                        cs->data = data;
235                        goto got_text;
236                    case '.':
237                    case '{':
238                    case '}':
239                        cs->next = *data;
240                        *data = 0;
241                        cs->data = data + 1;
242                        goto got_text;
243                    default:
244                        data++;
245                    }
246                }
247            }
248        }
249    }
250
251got_text:
252    cs->text = s;
253    return T_TEXT;
254}
255
256#if 0
257char *TOKENNAMES[] = { "EOF", "TEXT", "DOT", "OBRACE", "CBRACE" };
258
259static int lex(cstate *cs, int value)
260{
261    int tok = _lex(cs, value);
262    printf("TOKEN(%d) %s %s\n", value, TOKENNAMES[tok],
263           tok == T_TEXT ? cs->text : "");
264    return tok;
265}
266#else
267#define lex(cs,v) _lex(cs,v)
268#endif
269
270static int parse_expr(cstate *cs, AConfig *node);
271
272static int
273parse_block(cstate *cs, AConfig *node)
274{
275    for(;;){
276        switch(lex(cs, 0)){
277        case T_TEXT:
278            if(parse_expr(cs, node)) return -1;
279            continue;
280
281        case T_CBRACE:
282            return 0;
283
284        default:
285            return -1;
286        }
287    }
288}
289
290static int
291parse_expr(cstate *cs, AConfig *node)
292{
293        /* last token was T_TEXT */
294    node = _aconfig_find(node, cs->text, 1);
295
296    for(;;) {
297        switch(lex(cs, 1)) {
298        case T_DOT:
299            if(lex(cs, 0) != T_TEXT) return -1;
300            node = _aconfig_find(node, cs->text, 1);
301            continue;
302
303        case T_TEXT:
304            node->value = cs->text;
305            return 0;
306
307        case T_OBRACE:
308            return parse_block(cs, node);
309
310        default:
311            return -1;
312        }
313    }
314}
315
316void
317aconfig_load(AConfig *root, char *data)
318{
319    if(data != 0) {
320        cstate cs;
321        cs.data = data;
322        cs.next = 0;
323
324        for(;;) {
325            switch(lex(&cs, 0)){
326            case T_TEXT:
327                if(parse_expr(&cs, root)) return;
328                break;
329            default:
330                return;
331            }
332        }
333    }
334}
335
336int
337aconfig_load_file(AConfig *root, const char *fn)
338{
339    char *data;
340    data = path_load_file(fn, NULL);
341    if (data == NULL)
342        return -1;
343
344    aconfig_load(root, data);
345    return 0;
346}
347
348
349typedef struct
350{
351    char   buff[1024];
352    char*  p;
353    char*  end;
354    int    fd;
355} Writer;
356
357static int
358writer_init( Writer*  w, const char*  fn )
359{
360    w->p   = w->buff;
361    w->end = w->buff + sizeof(w->buff);
362
363    w->fd  = creat( fn, 0755 );
364    if (w->fd < 0)
365        return -1;
366
367#ifdef _WIN32
368    _setmode( w->fd, _O_BINARY );
369#endif
370    return 0;
371}
372
373static void
374writer_write( Writer*  w, const char*  src, int  len )
375{
376    while (len > 0) {
377        int  avail = w->end - w->p;
378
379        if (avail > len)
380            avail = len;
381
382        memcpy( w->p, src, avail );
383        src += avail;
384        len -= avail;
385
386        w->p += avail;
387        if (w->p == w->end) {
388            write( w->fd, w->buff, w->p - w->buff );
389            w->p = w->buff;
390        }
391    }
392}
393
394static void
395writer_done( Writer*  w )
396{
397    if (w->p > w->buff)
398        write( w->fd, w->buff, w->p - w->buff );
399    close( w->fd );
400}
401
402static void
403writer_margin( Writer*  w, int  margin)
404{
405    static const char  spaces[10] = "          ";
406    while (margin >= 10) {
407        writer_write(w,spaces,10);
408        margin -= 10;
409    }
410    if (margin > 0)
411        writer_write(w,spaces,margin);
412}
413
414static void
415writer_c(Writer*  w, char  c)
416{
417    writer_write(w, &c, 1);
418}
419
420static void
421writer_str(Writer*  w, const char*  str)
422{
423    writer_write(w, str, strlen(str));
424}
425
426static void
427writer_node(Writer*  w, AConfig*  node, int  margin)
428{
429    writer_margin(w,margin);
430    writer_str(w, node->name);
431    writer_c(w,' ');
432
433    if (node->value[0]) {
434        writer_str(w, node->value);
435        writer_c(w,'\n');
436    }
437    else
438    {
439        AConfig*  child;
440
441        writer_c(w, '{');
442        writer_c(w, '\n');
443
444        for (child = node->first_child; child; child = child->next)
445            writer_node(w,child,margin+4);
446
447        writer_margin(w,margin);
448        writer_c(w,'}');
449        writer_c(w,'\n');
450    }
451}
452
453int
454aconfig_save_file(AConfig *root, const char *fn)
455{
456    AConfig*  child;
457    Writer    w[1];
458
459    if (writer_init(w,fn) < 0)
460        return -1;
461
462    for (child = root->first_child; child; child = child->next)
463        writer_node(w,child,0);
464
465    writer_done(w);
466    return 0;
467}
468