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