1/* Copyright (C) 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 "android/utils/ini.h"
13#include <stdlib.h>
14#include <stdio.h>
15#include <string.h>
16#include <limits.h>
17#include <errno.h>
18#include "android/utils/debug.h"
19#include "android/utils/system.h" /* for ASTRDUP */
20#include "android/utils/bufprint.h"
21#include "osdep.h"
22
23/* W() is used to print warnings, D() to print debugging info */
24#define  W(...)   dwarning(__VA_ARGS__)
25#define  D(...)   VERBOSE_PRINT(avd_config,__VA_ARGS__)
26
27/* a simple .ini file parser and container for Android
28 * no sections support. see android/utils/ini.h for
29 * more details on the supported file format.
30 */
31typedef struct {
32    char*  key;
33    char*  value;
34} IniPair;
35
36struct IniFile {
37    int       numPairs;
38    int       maxPairs;
39    IniPair*  pairs;
40};
41
42void
43iniFile_free( IniFile*  i )
44{
45    int  nn;
46    for (nn = 0; nn < i->numPairs; nn++) {
47        AFREE(i->pairs[nn].key);
48        i->pairs[nn].key   = NULL;
49        i->pairs[nn].value = NULL;
50    }
51    AFREE(i->pairs);
52    AFREE(i);
53}
54
55static IniFile*
56iniFile_alloc( void )
57{
58    IniFile*  i;
59
60    ANEW0(i);
61    return i;
62}
63
64static void
65iniPair_init( IniPair* pair, const char* key, int keyLen,
66                             const char* value, int valueLen )
67{
68    AARRAY_NEW(pair->key, keyLen + valueLen + 2);
69    memcpy(pair->key, key, keyLen);
70    pair->key[keyLen] = 0;
71
72    pair->value = pair->key + keyLen + 1;
73    memcpy(pair->value, value, valueLen);
74    pair->value[valueLen] = 0;
75}
76
77static void
78iniPair_replaceValue( IniPair* pair, const char* value )
79{
80    char* key      = pair->key;
81    int   keyLen   = strlen(key);
82    int   valueLen = strlen(value);
83
84    iniPair_init(pair, key, keyLen, value, valueLen);
85    AFREE(key);
86}
87
88static void
89iniFile_addPair( IniFile*  i,
90                 const char*  key,   int  keyLen,
91                 const char*  value, int  valueLen )
92{
93    IniPair*  pair;
94
95    if (i->numPairs >= i->maxPairs) {
96        int       oldMax = i->maxPairs;
97        int       newMax = oldMax + (oldMax >> 1) + 4;
98
99        AARRAY_RENEW(i->pairs, newMax);
100        i->maxPairs = newMax;
101    }
102
103    pair = i->pairs + i->numPairs;
104    iniPair_init(pair, key, keyLen, value, valueLen);
105
106    i->numPairs += 1;
107}
108
109static IniPair*
110iniFile_getPair( IniFile* i, const char* key )
111{
112    if (i && key) {
113        int  nn;
114
115        for (nn = 0; nn < i->numPairs; nn++) {
116            if (!strcmp(i->pairs[nn].key,key))
117                return &i->pairs[nn];
118        }
119    }
120    return NULL;
121}
122
123const char*
124iniFile_getValue( IniFile*  i, const char*  key )
125{
126    IniPair* pair = iniFile_getPair(i, key);
127    if (pair)
128        return pair->value;
129    else
130        return NULL;
131}
132
133int
134iniFile_getPairCount( IniFile*  i )
135{
136    return i ? i->numPairs : 0;
137}
138
139/* NOTE: we avoid using <ctype.h> functions to avoid locale-specific
140 *       behaviour that can be the source of strange bugs.
141 */
142
143static const char*
144skipSpaces( const char* p )
145{
146    while (*p == ' ' || *p == '\t')
147        p ++;
148    return p;
149}
150
151static const char*
152skipToEOL( const char*  p )
153{
154    while (*p && (*p != '\n' && *p != '\r'))
155        p ++;
156
157    if (*p) {
158        p ++;
159        if (p[-1] == '\r' && p[0] == '\n')
160            p ++;
161    }
162    return p;
163}
164
165static int
166isKeyStartChar( int  c )
167{
168    return ((unsigned)(c-'a') < 26 ||
169            (unsigned)(c-'A') < 26 ||
170            c == '_');
171}
172
173static int
174isKeyChar( int  c )
175{
176    return isKeyStartChar(c) || ((unsigned)(c-'0') < 10) || (c == '.') || (c == '-');
177}
178
179IniFile*
180iniFile_newFromMemory( const char*  text, const char*  fileName )
181{
182    const char*  p      = text;
183    IniFile*     ini    = iniFile_alloc();
184    int          lineno = 0;
185
186    if (!fileName)
187        fileName = "<memoryFile>";
188
189    D("%s: parsing as .ini file", fileName);
190
191    while (*p) {
192        const char*  key;
193        int          keyLen;
194        const char*  value;
195        int          valueLen;
196
197        lineno += 1;
198
199        /* skip leading whitespace */
200        p = skipSpaces(p);
201
202        /* skip comments and empty lines */
203        if (*p == 0 || *p == ';' || *p == '#' || *p == '\n' || *p == '\r') {
204            p = skipToEOL(p);
205            continue;
206        }
207
208        /* check the key name */
209        key = p++;
210        if (!isKeyStartChar(*key)) {
211            p = skipToEOL(p);
212            W("%4d: key name doesn't start with valid character. line ignored",
213              lineno);
214            continue;
215        }
216
217        while (isKeyChar(*p))
218            p++;
219
220        keyLen = p - key;
221        p      = skipSpaces(p);
222
223        /* check the equal */
224        if (*p != '=') {
225            W("%4d: missing expected assignment operator (=). line ignored",
226              lineno);
227            p = skipToEOL(p);
228            continue;
229        }
230        p += 1;
231
232        /* skip spaces before the value */
233        p     = skipSpaces(p);
234        value = p;
235
236        /* find the value */
237        while (*p && (*p != '\n' && *p != '\r'))
238            p += 1;
239
240        /* remove trailing spaces */
241        while (p > value && (p[-1] == ' ' || p[-1] == '\t'))
242            p --;
243
244        valueLen = p - value;
245
246        iniFile_addPair(ini, key, keyLen, value, valueLen);
247        D("%4d: KEY='%.*s' VALUE='%.*s'", lineno,
248          keyLen, key, valueLen, value);
249
250        p = skipToEOL(p);
251    }
252
253    D("%s: parsing finished", fileName);
254
255    return ini;
256}
257
258IniFile*
259iniFile_newFromFile( const char*  filepath )
260{
261    FILE*        fp = fopen(filepath, "rt");
262    char*        text;
263    long         size;
264    IniFile*     ini = NULL;
265    size_t       len;
266
267    if (fp == NULL) {
268        D("could not open .ini file: %s: %s",
269          filepath, strerror(errno));
270        return NULL;
271    }
272
273    fseek(fp, 0, SEEK_END);
274    size = ftell(fp);
275    fseek(fp, 0, SEEK_SET);
276
277    /* avoid reading a very large file that was passed by mistake
278     * this threshold is quite liberal.
279     */
280#define  MAX_INI_FILE_SIZE  655360
281
282    if (size < 0 || size > MAX_INI_FILE_SIZE) {
283        W("hardware configuration file '%s' too large (%ld bytes)",
284          filepath, size);
285        goto EXIT;
286    }
287
288    /* read the file, add a sentinel at the end of it */
289    AARRAY_NEW(text, size+1);
290    len = fread(text, 1, size, fp);
291    text[len] = 0;
292
293    ini = iniFile_newFromMemory(text, filepath);
294    AFREE(text);
295
296EXIT:
297    fclose(fp);
298    return ini;
299}
300
301/* Common routine for saving IniFile instance to the given file.
302 * Param:
303 *  f - IniFile instance to save.
304 *  filepath - Path to a file where to save the instance.
305 *  strip - If 1, ignore (don't save) pairs with empty values. If 0, save all
306 *      pairs found in the IniFile instance, including the ones that contain
307 *      empty values.
308 * Returns:
309 *  0 on success, -1 on error (see errno for error code)
310 */
311static int
312iniFile_saveToFileCommon( IniFile*  f, const char*  filepath, int strip )
313{
314    FILE*  fp = fopen(filepath, "wt");
315    IniPair*  pair    = f->pairs;
316    IniPair*  pairEnd = pair + f->numPairs;
317    int       result  = 0;
318
319    if (fp == NULL) {
320        D("could not create .ini file: %s: %s",
321          filepath, strerror(errno));
322        return -1;
323    }
324
325    for ( ; pair < pairEnd; pair++ ) {
326        if ((pair->value && *pair->value) || !strip) {
327            char  temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
328            p = bufprint(temp, end, "%s = %s\n", pair->key, pair->value);
329            if (fwrite(temp, p - temp, 1, fp) != 1) {
330                result = -1;
331                break;
332            }
333        }
334    }
335
336    fclose(fp);
337    return result;
338}
339
340int
341iniFile_saveToFile( IniFile*  f, const char*  filepath )
342{
343    return iniFile_saveToFileCommon(f, filepath, 0);
344}
345
346int
347iniFile_saveToFileClean( IniFile*  f, const char*  filepath )
348{
349    return iniFile_saveToFileCommon(f, filepath, 1);
350}
351
352int
353iniFile_getEntry(IniFile* f, int index, char** key, char** value)
354{
355    if (index >= f->numPairs) {
356        D("Index %d exceeds the number of ini file entries %d",
357          index, f->numPairs);
358        return -1;
359    }
360
361    *key = ASTRDUP(f->pairs[index].key);
362    *value = ASTRDUP(f->pairs[index].value);
363
364    return 0;
365}
366
367char*
368iniFile_getString( IniFile*  f, const char*  key, const char* defaultValue )
369{
370    const char*  val = iniFile_getValue(f, key);
371
372    if (!val) {
373        if (!defaultValue)
374            return NULL;
375        val= defaultValue;
376    }
377
378    return ASTRDUP(val);
379}
380
381int
382iniFile_getInteger( IniFile*  f, const char*  key, int  defaultValue )
383{
384    const char*  valueStr = iniFile_getValue(f, key);
385    int          value    = defaultValue;
386
387    if (valueStr != NULL) {
388        char*  end;
389        long   l = strtol(valueStr, &end, 10);
390        if (end != NULL && end[0] == 0 && (int)l == l)
391            value = l;
392    }
393    return value;
394}
395
396double
397iniFile_getDouble( IniFile*  f, const char*  key, double  defaultValue )
398{
399    const char*  valueStr = iniFile_getValue(f, key);
400    double       value    = defaultValue;
401
402    if (valueStr != NULL) {
403        char*   end;
404        double  d = strtod(valueStr, &end);
405        if (end != NULL && end[0] == 0)
406            value = d;
407    }
408    return value;
409}
410
411int
412iniFile_getBoolean( IniFile*  f, const char*  key, const char*  defaultValue )
413{
414    const char*  value  = iniFile_getValue(f, key);
415
416    if (!value)
417        value = defaultValue;
418
419    if (!strcmp(value,"1")    ||
420        !strcmp(value,"yes")  ||
421        !strcmp(value,"YES")  ||
422        !strcmp(value,"true") ||
423        !strcmp(value,"TRUE"))
424    {
425        return 1;
426    }
427    else
428        return 0;
429}
430
431int64_t
432iniFile_getDiskSize( IniFile*  f, const char*  key, const char*  defaultValue )
433{
434    const char*  valStr = iniFile_getValue(f, key);
435    int64_t      value  = 0;
436
437    if (!valStr)
438        valStr = defaultValue;
439
440    if (valStr != NULL) {
441        char*  end;
442
443        value = strtoll(valStr, &end, 10);
444        if (*end == 'k' || *end == 'K')
445            value *= 1024ULL;
446        else if (*end == 'm' || *end == 'M')
447            value *= 1024*1024ULL;
448        else if (*end == 'g' || *end == 'G')
449            value *= 1024*1024*1024ULL;
450    }
451    return value;
452}
453
454int64_t
455iniFile_getInt64( IniFile*  f, const char*  key, int64_t  defaultValue )
456{
457    const char*  valStr = iniFile_getValue(f, key);
458    int64_t      value  = defaultValue;
459
460    if (valStr != NULL) {
461        char*    end;
462        int64_t  d;
463
464        d = strtoll(valStr, &end, 10);
465        if (end != NULL && end[0] == 0)
466            value = d;
467    }
468    return value;
469}
470
471void
472iniFile_setValue( IniFile* f, const char* key, const char* value )
473{
474    IniPair* pair;
475
476    if (f == NULL || key == NULL || value == NULL)
477        return;
478
479    pair = iniFile_getPair(f, key);
480    if (pair != NULL) {
481        iniPair_replaceValue(pair, value);
482    } else {
483        iniFile_addPair(f, key, strlen(key), value, strlen(value));
484    }
485}
486
487void
488iniFile_setInteger( IniFile* f, const char* key, int value )
489{
490    char temp[16];
491    snprintf(temp, sizeof temp, "%d", value);
492    iniFile_setValue(f, key, temp);
493}
494
495void
496iniFile_setInt64( IniFile* f, const char* key, int64_t value )
497{
498    char temp[32];
499    snprintf(temp, sizeof temp, "%" PRId64, value);
500    iniFile_setValue(f, key, temp);
501}
502
503void
504iniFile_setDouble( IniFile* f, const char* key, double value )
505{
506    char temp[32];
507    snprintf(temp, sizeof temp, "%g", value);
508    iniFile_setValue(f, key, temp);
509}
510
511void
512iniFile_setBoolean( IniFile* f, const char* key, int value )
513{
514    iniFile_setValue(f, key, value ? "yes" : "no");
515}
516
517void
518iniFile_setDiskSize( IniFile* f, const char* key, int64_t size )
519{
520    char     temp[32];
521    int64_t  divisor = 0;
522    const int64_t  kilo = 1024;
523    const int64_t  mega = 1024*kilo;
524    const int64_t  giga = 1024*mega;
525    char     suffix = '\0';
526
527    if (size >= giga && !(size % giga)) {
528        divisor = giga;
529        suffix = 'g';
530    }
531    else if (size >= mega && !(size % mega)) {
532        divisor = mega;
533        suffix  = 'm';
534    }
535    else if (size >= kilo && !(size % kilo)) {
536        divisor = kilo;
537        suffix = 'k';
538    }
539    if (divisor) {
540        snprintf(temp, sizeof temp, "%" PRId64 "%c", size/divisor, suffix);
541    } else {
542        snprintf(temp, sizeof temp, "%" PRId64, size);
543    }
544    iniFile_setValue(f, key, temp);
545}
546