blob: 43f1321801b4b8bb768b518cc194af87cb02a64d [file] [log] [blame]
/* Copyright (C) 2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
#include "android/utils/ini.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <errno.h>
#include "android/utils/debug.h"
#include "android/utils/system.h" /* for ASTRDUP */
#include "android/utils/bufprint.h"
#include "osdep.h"
/* W() is used to print warnings, D() to print debugging info */
#define W(...) dwarning(__VA_ARGS__)
#define D(...) VERBOSE_PRINT(avd_config,__VA_ARGS__)
/* a simple .ini file parser and container for Android
* no sections support. see android/utils/ini.h for
* more details on the supported file format.
*/
typedef struct {
char* key;
char* value;
} IniPair;
struct IniFile {
int numPairs;
int maxPairs;
IniPair* pairs;
};
void
iniFile_free( IniFile* i )
{
int nn;
for (nn = 0; nn < i->numPairs; nn++) {
AFREE(i->pairs[nn].key);
i->pairs[nn].key = NULL;
i->pairs[nn].value = NULL;
}
AFREE(i->pairs);
AFREE(i);
}
static IniFile*
iniFile_alloc( void )
{
IniFile* i;
ANEW0(i);
return i;
}
static void
iniPair_init( IniPair* pair, const char* key, int keyLen,
const char* value, int valueLen )
{
AARRAY_NEW(pair->key, keyLen + valueLen + 2);
memcpy(pair->key, key, keyLen);
pair->key[keyLen] = 0;
pair->value = pair->key + keyLen + 1;
memcpy(pair->value, value, valueLen);
pair->value[valueLen] = 0;
}
static void
iniPair_replaceValue( IniPair* pair, const char* value )
{
char* key = pair->key;
int keyLen = strlen(key);
int valueLen = strlen(value);
iniPair_init(pair, key, keyLen, value, valueLen);
AFREE(key);
}
static void
iniFile_addPair( IniFile* i,
const char* key, int keyLen,
const char* value, int valueLen )
{
IniPair* pair;
if (i->numPairs >= i->maxPairs) {
int oldMax = i->maxPairs;
int newMax = oldMax + (oldMax >> 1) + 4;
AARRAY_RENEW(i->pairs, newMax);
i->maxPairs = newMax;
}
pair = i->pairs + i->numPairs;
iniPair_init(pair, key, keyLen, value, valueLen);
i->numPairs += 1;
}
static IniPair*
iniFile_getPair( IniFile* i, const char* key )
{
if (i && key) {
int nn;
for (nn = 0; nn < i->numPairs; nn++) {
if (!strcmp(i->pairs[nn].key,key))
return &i->pairs[nn];
}
}
return NULL;
}
const char*
iniFile_getValue( IniFile* i, const char* key )
{
IniPair* pair = iniFile_getPair(i, key);
if (pair)
return pair->value;
else
return NULL;
}
int
iniFile_getPairCount( IniFile* i )
{
return i ? i->numPairs : 0;
}
/* NOTE: we avoid using <ctype.h> functions to avoid locale-specific
* behaviour that can be the source of strange bugs.
*/
static const char*
skipSpaces( const char* p )
{
while (*p == ' ' || *p == '\t')
p ++;
return p;
}
static const char*
skipToEOL( const char* p )
{
while (*p && (*p != '\n' && *p != '\r'))
p ++;
if (*p) {
p ++;
if (p[-1] == '\r' && p[0] == '\n')
p ++;
}
return p;
}
static int
isKeyStartChar( int c )
{
return ((unsigned)(c-'a') < 26 ||
(unsigned)(c-'A') < 26 ||
c == '_');
}
static int
isKeyChar( int c )
{
return isKeyStartChar(c) || ((unsigned)(c-'0') < 10) || (c == '.') || (c == '-');
}
IniFile*
iniFile_newFromMemory( const char* text, const char* fileName )
{
const char* p = text;
IniFile* ini = iniFile_alloc();
int lineno = 0;
if (!fileName)
fileName = "<memoryFile>";
D("%s: parsing as .ini file", fileName);
while (*p) {
const char* key;
int keyLen;
const char* value;
int valueLen;
lineno += 1;
/* skip leading whitespace */
p = skipSpaces(p);
/* skip comments and empty lines */
if (*p == 0 || *p == ';' || *p == '#' || *p == '\n' || *p == '\r') {
p = skipToEOL(p);
continue;
}
/* check the key name */
key = p++;
if (!isKeyStartChar(*key)) {
p = skipToEOL(p);
W("%4d: key name doesn't start with valid character. line ignored",
lineno);
continue;
}
while (isKeyChar(*p))
p++;
keyLen = p - key;
p = skipSpaces(p);
/* check the equal */
if (*p != '=') {
W("%4d: missing expected assignment operator (=). line ignored",
lineno);
p = skipToEOL(p);
continue;
}
p += 1;
/* skip spaces before the value */
p = skipSpaces(p);
value = p;
/* find the value */
while (*p && (*p != '\n' && *p != '\r'))
p += 1;
/* remove trailing spaces */
while (p > value && (p[-1] == ' ' || p[-1] == '\t'))
p --;
valueLen = p - value;
iniFile_addPair(ini, key, keyLen, value, valueLen);
D("%4d: KEY='%.*s' VALUE='%.*s'", lineno,
keyLen, key, valueLen, value);
p = skipToEOL(p);
}
D("%s: parsing finished", fileName);
return ini;
}
IniFile*
iniFile_newFromFile( const char* filepath )
{
FILE* fp = fopen(filepath, "rt");
char* text;
long size;
IniFile* ini = NULL;
size_t len;
if (fp == NULL) {
D("could not open .ini file: %s: %s",
filepath, strerror(errno));
return NULL;
}
fseek(fp, 0, SEEK_END);
size = ftell(fp);
fseek(fp, 0, SEEK_SET);
/* avoid reading a very large file that was passed by mistake
* this threshold is quite liberal.
*/
#define MAX_INI_FILE_SIZE 655360
if (size < 0 || size > MAX_INI_FILE_SIZE) {
W("hardware configuration file '%s' too large (%ld bytes)",
filepath, size);
goto EXIT;
}
/* read the file, add a sentinel at the end of it */
AARRAY_NEW(text, size+1);
len = fread(text, 1, size, fp);
text[len] = 0;
ini = iniFile_newFromMemory(text, filepath);
AFREE(text);
EXIT:
fclose(fp);
return ini;
}
/* Common routine for saving IniFile instance to the given file.
* Param:
* f - IniFile instance to save.
* filepath - Path to a file where to save the instance.
* strip - If 1, ignore (don't save) pairs with empty values. If 0, save all
* pairs found in the IniFile instance, including the ones that contain
* empty values.
* Returns:
* 0 on success, -1 on error (see errno for error code)
*/
static int
iniFile_saveToFileCommon( IniFile* f, const char* filepath, int strip )
{
FILE* fp = fopen(filepath, "wt");
IniPair* pair = f->pairs;
IniPair* pairEnd = pair + f->numPairs;
int result = 0;
if (fp == NULL) {
D("could not create .ini file: %s: %s",
filepath, strerror(errno));
return -1;
}
for ( ; pair < pairEnd; pair++ ) {
if ((pair->value && *pair->value) || !strip) {
char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
p = bufprint(temp, end, "%s = %s\n", pair->key, pair->value);
if (fwrite(temp, p - temp, 1, fp) != 1) {
result = -1;
break;
}
}
}
fclose(fp);
return result;
}
int
iniFile_saveToFile( IniFile* f, const char* filepath )
{
return iniFile_saveToFileCommon(f, filepath, 0);
}
int
iniFile_saveToFileClean( IniFile* f, const char* filepath )
{
return iniFile_saveToFileCommon(f, filepath, 1);
}
int
iniFile_getEntry(IniFile* f, int index, char** key, char** value)
{
if (index >= f->numPairs) {
D("Index %d exceeds the number of ini file entries %d",
index, f->numPairs);
return -1;
}
*key = ASTRDUP(f->pairs[index].key);
*value = ASTRDUP(f->pairs[index].value);
return 0;
}
char*
iniFile_getString( IniFile* f, const char* key, const char* defaultValue )
{
const char* val = iniFile_getValue(f, key);
if (!val) {
if (!defaultValue)
return NULL;
val= defaultValue;
}
return ASTRDUP(val);
}
int
iniFile_getInteger( IniFile* f, const char* key, int defaultValue )
{
const char* valueStr = iniFile_getValue(f, key);
int value = defaultValue;
if (valueStr != NULL) {
char* end;
long l = strtol(valueStr, &end, 10);
if (end != NULL && end[0] == 0 && (int)l == l)
value = l;
}
return value;
}
double
iniFile_getDouble( IniFile* f, const char* key, double defaultValue )
{
const char* valueStr = iniFile_getValue(f, key);
double value = defaultValue;
if (valueStr != NULL) {
char* end;
double d = strtod(valueStr, &end);
if (end != NULL && end[0] == 0)
value = d;
}
return value;
}
int
iniFile_getBoolean( IniFile* f, const char* key, const char* defaultValue )
{
const char* value = iniFile_getValue(f, key);
if (!value)
value = defaultValue;
if (!strcmp(value,"1") ||
!strcmp(value,"yes") ||
!strcmp(value,"YES") ||
!strcmp(value,"true") ||
!strcmp(value,"TRUE"))
{
return 1;
}
else
return 0;
}
int64_t
iniFile_getDiskSize( IniFile* f, const char* key, const char* defaultValue )
{
const char* valStr = iniFile_getValue(f, key);
int64_t value = 0;
if (!valStr)
valStr = defaultValue;
if (valStr != NULL) {
char* end;
value = strtoll(valStr, &end, 10);
if (*end == 'k' || *end == 'K')
value *= 1024ULL;
else if (*end == 'm' || *end == 'M')
value *= 1024*1024ULL;
else if (*end == 'g' || *end == 'G')
value *= 1024*1024*1024ULL;
}
return value;
}
int64_t
iniFile_getInt64( IniFile* f, const char* key, int64_t defaultValue )
{
const char* valStr = iniFile_getValue(f, key);
int64_t value = defaultValue;
if (valStr != NULL) {
char* end;
int64_t d;
d = strtoll(valStr, &end, 10);
if (end != NULL && end[0] == 0)
value = d;
}
return value;
}
void
iniFile_setValue( IniFile* f, const char* key, const char* value )
{
IniPair* pair;
if (f == NULL || key == NULL || value == NULL)
return;
pair = iniFile_getPair(f, key);
if (pair != NULL) {
iniPair_replaceValue(pair, value);
} else {
iniFile_addPair(f, key, strlen(key), value, strlen(value));
}
}
void
iniFile_setInteger( IniFile* f, const char* key, int value )
{
char temp[16];
snprintf(temp, sizeof temp, "%d", value);
iniFile_setValue(f, key, temp);
}
void
iniFile_setInt64( IniFile* f, const char* key, int64_t value )
{
char temp[32];
snprintf(temp, sizeof temp, "%" PRId64, value);
iniFile_setValue(f, key, temp);
}
void
iniFile_setDouble( IniFile* f, const char* key, double value )
{
char temp[32];
snprintf(temp, sizeof temp, "%g", value);
iniFile_setValue(f, key, temp);
}
void
iniFile_setBoolean( IniFile* f, const char* key, int value )
{
iniFile_setValue(f, key, value ? "yes" : "no");
}
void
iniFile_setDiskSize( IniFile* f, const char* key, int64_t size )
{
char temp[32];
int64_t divisor = 0;
const int64_t kilo = 1024;
const int64_t mega = 1024*kilo;
const int64_t giga = 1024*mega;
char suffix = '\0';
if (size >= giga && !(size % giga)) {
divisor = giga;
suffix = 'g';
}
else if (size >= mega && !(size % mega)) {
divisor = mega;
suffix = 'm';
}
else if (size >= kilo && !(size % kilo)) {
divisor = kilo;
suffix = 'k';
}
if (divisor) {
snprintf(temp, sizeof temp, "%" PRId64 "%c", size/divisor, suffix);
} else {
snprintf(temp, sizeof temp, "%" PRId64, size);
}
iniFile_setValue(f, key, temp);
}