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