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 "qemu/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 */ 31 typedef struct { 32 char* key; 33 char* value; 34 } IniPair; 35 36 struct IniFile { 37 int numPairs; 38 int maxPairs; 39 IniPair* pairs; 40 }; 41 42 void 43 iniFile_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 55 static IniFile* 56 iniFile_alloc( void ) 57 { 58 IniFile* i; 59 60 ANEW0(i); 61 return i; 62 } 63 64 static void 65 iniPair_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 77 static void 78 iniPair_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 88 static void 89 iniFile_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 109 static IniPair* 110 iniFile_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 123 const char* 124 iniFile_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 133 int 134 iniFile_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 143 static const char* 144 skipSpaces( const char* p ) 145 { 146 while (*p == ' ' || *p == '\t') 147 p ++; 148 return p; 149 } 150 151 static const char* 152 skipToEOL( 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 165 static int 166 isKeyStartChar( int c ) 167 { 168 return ((unsigned)(c-'a') < 26 || 169 (unsigned)(c-'A') < 26 || 170 c == '_'); 171 } 172 173 static int 174 isKeyChar( int c ) 175 { 176 return isKeyStartChar(c) || ((unsigned)(c-'0') < 10) || (c == '.') || (c == '-'); 177 } 178 179 IniFile* 180 iniFile_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 258 IniFile* 259 iniFile_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 296 EXIT: 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 */ 311 static int 312 iniFile_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 340 int 341 iniFile_saveToFile( IniFile* f, const char* filepath ) 342 { 343 return iniFile_saveToFileCommon(f, filepath, 0); 344 } 345 346 int 347 iniFile_saveToFileClean( IniFile* f, const char* filepath ) 348 { 349 return iniFile_saveToFileCommon(f, filepath, 1); 350 } 351 352 int 353 iniFile_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 367 char* 368 iniFile_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 381 int 382 iniFile_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 396 double 397 iniFile_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 411 int 412 iniFile_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 431 int64_t 432 iniFile_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 454 int64_t 455 iniFile_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 471 void 472 iniFile_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 487 void 488 iniFile_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 495 void 496 iniFile_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 503 void 504 iniFile_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 511 void 512 iniFile_setBoolean( IniFile* f, const char* key, int value ) 513 { 514 iniFile_setValue(f, key, value ? "yes" : "no"); 515 } 516 517 void 518 iniFile_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