1 /****************************************************************************** 2 * 3 * Copyright (C) 2014 Google, Inc. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at: 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 ******************************************************************************/ 18 19 #define LOG_TAG "bt_osi_config" 20 21 #include "osi/include/config.h" 22 23 #include <base/logging.h> 24 #include <ctype.h> 25 #include <errno.h> 26 #include <fcntl.h> 27 #include <libgen.h> 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <sys/stat.h> 32 #include <unistd.h> 33 34 #include "osi/include/allocator.h" 35 #include "osi/include/list.h" 36 #include "osi/include/log.h" 37 38 typedef struct { 39 char* key; 40 char* value; 41 } entry_t; 42 43 typedef struct { 44 char* name; 45 list_t* entries; 46 } section_t; 47 48 struct config_t { 49 list_t* sections; 50 }; 51 52 // Empty definition; this type is aliased to list_node_t. 53 struct config_section_iter_t {}; 54 55 static bool config_parse(FILE* fp, config_t* config); 56 57 static section_t* section_new(const char* name); 58 static void section_free(void* ptr); 59 static section_t* section_find(const config_t* config, const char* section); 60 61 static entry_t* entry_new(const char* key, const char* value); 62 static void entry_free(void* ptr); 63 static entry_t* entry_find(const config_t* config, const char* section, 64 const char* key); 65 66 config_t* config_new_empty(void) { 67 config_t* config = static_cast<config_t*>(osi_calloc(sizeof(config_t))); 68 69 config->sections = list_new(section_free); 70 if (!config->sections) { 71 LOG_ERROR(LOG_TAG, "%s unable to allocate list for sections.", __func__); 72 goto error; 73 } 74 75 return config; 76 77 error:; 78 config_free(config); 79 return NULL; 80 } 81 82 config_t* config_new(const char* filename) { 83 CHECK(filename != NULL); 84 85 config_t* config = config_new_empty(); 86 if (!config) return NULL; 87 88 FILE* fp = fopen(filename, "rt"); 89 if (!fp) { 90 LOG_ERROR(LOG_TAG, "%s unable to open file '%s': %s", __func__, filename, 91 strerror(errno)); 92 config_free(config); 93 return NULL; 94 } 95 96 if (!config_parse(fp, config)) { 97 config_free(config); 98 config = NULL; 99 } 100 101 fclose(fp); 102 return config; 103 } 104 105 config_t* config_new_clone(const config_t* src) { 106 CHECK(src != NULL); 107 108 config_t* ret = config_new_empty(); 109 110 CHECK(ret != NULL); 111 112 for (const list_node_t* node = list_begin(src->sections); 113 node != list_end(src->sections); node = list_next(node)) { 114 section_t* sec = static_cast<section_t*>(list_node(node)); 115 116 for (const list_node_t* node_entry = list_begin(sec->entries); 117 node_entry != list_end(sec->entries); 118 node_entry = list_next(node_entry)) { 119 entry_t* entry = static_cast<entry_t*>(list_node(node_entry)); 120 121 config_set_string(ret, sec->name, entry->key, entry->value); 122 } 123 } 124 125 return ret; 126 } 127 128 void config_free(config_t* config) { 129 if (!config) return; 130 131 list_free(config->sections); 132 osi_free(config); 133 } 134 135 bool config_has_section(const config_t* config, const char* section) { 136 CHECK(config != NULL); 137 CHECK(section != NULL); 138 139 return (section_find(config, section) != NULL); 140 } 141 142 bool config_has_key(const config_t* config, const char* section, 143 const char* key) { 144 CHECK(config != NULL); 145 CHECK(section != NULL); 146 CHECK(key != NULL); 147 148 return (entry_find(config, section, key) != NULL); 149 } 150 151 int config_get_int(const config_t* config, const char* section, const char* key, 152 int def_value) { 153 CHECK(config != NULL); 154 CHECK(section != NULL); 155 CHECK(key != NULL); 156 157 entry_t* entry = entry_find(config, section, key); 158 if (!entry) return def_value; 159 160 char* endptr; 161 int ret = strtol(entry->value, &endptr, 0); 162 return (*endptr == '\0') ? ret : def_value; 163 } 164 165 bool config_get_bool(const config_t* config, const char* section, 166 const char* key, bool def_value) { 167 CHECK(config != NULL); 168 CHECK(section != NULL); 169 CHECK(key != NULL); 170 171 entry_t* entry = entry_find(config, section, key); 172 if (!entry) return def_value; 173 174 if (!strcmp(entry->value, "true")) return true; 175 if (!strcmp(entry->value, "false")) return false; 176 177 return def_value; 178 } 179 180 const char* config_get_string(const config_t* config, const char* section, 181 const char* key, const char* def_value) { 182 CHECK(config != NULL); 183 CHECK(section != NULL); 184 CHECK(key != NULL); 185 186 entry_t* entry = entry_find(config, section, key); 187 if (!entry) return def_value; 188 189 return entry->value; 190 } 191 192 void config_set_int(config_t* config, const char* section, const char* key, 193 int value) { 194 CHECK(config != NULL); 195 CHECK(section != NULL); 196 CHECK(key != NULL); 197 198 char value_str[32] = {0}; 199 snprintf(value_str, sizeof(value_str), "%d", value); 200 config_set_string(config, section, key, value_str); 201 } 202 203 void config_set_bool(config_t* config, const char* section, const char* key, 204 bool value) { 205 CHECK(config != NULL); 206 CHECK(section != NULL); 207 CHECK(key != NULL); 208 209 config_set_string(config, section, key, value ? "true" : "false"); 210 } 211 212 void config_set_string(config_t* config, const char* section, const char* key, 213 const char* value) { 214 section_t* sec = section_find(config, section); 215 if (!sec) { 216 sec = section_new(section); 217 list_append(config->sections, sec); 218 } 219 220 for (const list_node_t* node = list_begin(sec->entries); 221 node != list_end(sec->entries); node = list_next(node)) { 222 entry_t* entry = static_cast<entry_t*>(list_node(node)); 223 if (!strcmp(entry->key, key)) { 224 osi_free(entry->value); 225 entry->value = osi_strdup(value); 226 return; 227 } 228 } 229 230 entry_t* entry = entry_new(key, value); 231 list_append(sec->entries, entry); 232 } 233 234 bool config_remove_section(config_t* config, const char* section) { 235 CHECK(config != NULL); 236 CHECK(section != NULL); 237 238 section_t* sec = section_find(config, section); 239 if (!sec) return false; 240 241 return list_remove(config->sections, sec); 242 } 243 244 bool config_remove_key(config_t* config, const char* section, const char* key) { 245 CHECK(config != NULL); 246 CHECK(section != NULL); 247 CHECK(key != NULL); 248 249 section_t* sec = section_find(config, section); 250 entry_t* entry = entry_find(config, section, key); 251 if (!sec || !entry) return false; 252 253 return list_remove(sec->entries, entry); 254 } 255 256 const config_section_node_t* config_section_begin(const config_t* config) { 257 CHECK(config != NULL); 258 return (const config_section_node_t*)list_begin(config->sections); 259 } 260 261 const config_section_node_t* config_section_end(const config_t* config) { 262 CHECK(config != NULL); 263 return (const config_section_node_t*)list_end(config->sections); 264 } 265 266 const config_section_node_t* config_section_next( 267 const config_section_node_t* node) { 268 CHECK(node != NULL); 269 return (const config_section_node_t*)list_next((const list_node_t*)node); 270 } 271 272 const char* config_section_name(const config_section_node_t* node) { 273 CHECK(node != NULL); 274 const list_node_t* lnode = (const list_node_t*)node; 275 const section_t* section = (const section_t*)list_node(lnode); 276 return section->name; 277 } 278 279 bool config_save(const config_t* config, const char* filename) { 280 CHECK(config != NULL); 281 CHECK(filename != NULL); 282 CHECK(*filename != '\0'); 283 284 // Steps to ensure content of config file gets to disk: 285 // 286 // 1) Open and write to temp file (e.g. bt_config.conf.new). 287 // 2) Sync the temp file to disk with fsync(). 288 // 3) Rename temp file to actual config file (e.g. bt_config.conf). 289 // This ensures atomic update. 290 // 4) Sync directory that has the conf file with fsync(). 291 // This ensures directory entries are up-to-date. 292 int dir_fd = -1; 293 FILE* fp = NULL; 294 295 // Build temp config file based on config file (e.g. bt_config.conf.new). 296 static const char* temp_file_ext = ".new"; 297 const int filename_len = strlen(filename); 298 const int temp_filename_len = filename_len + strlen(temp_file_ext) + 1; 299 char* temp_filename = static_cast<char*>(osi_calloc(temp_filename_len)); 300 snprintf(temp_filename, temp_filename_len, "%s%s", filename, temp_file_ext); 301 302 // Extract directory from file path (e.g. /data/misc/bluedroid). 303 char* temp_dirname = osi_strdup(filename); 304 const char* directoryname = dirname(temp_dirname); 305 if (!directoryname) { 306 LOG_ERROR(LOG_TAG, "%s error extracting directory from '%s': %s", __func__, 307 filename, strerror(errno)); 308 goto error; 309 } 310 311 dir_fd = open(directoryname, O_RDONLY); 312 if (dir_fd < 0) { 313 LOG_ERROR(LOG_TAG, "%s unable to open dir '%s': %s", __func__, 314 directoryname, strerror(errno)); 315 goto error; 316 } 317 318 fp = fopen(temp_filename, "wt"); 319 if (!fp) { 320 LOG_ERROR(LOG_TAG, "%s unable to write file '%s': %s", __func__, 321 temp_filename, strerror(errno)); 322 goto error; 323 } 324 325 for (const list_node_t* node = list_begin(config->sections); 326 node != list_end(config->sections); node = list_next(node)) { 327 const section_t* section = (const section_t*)list_node(node); 328 if (fprintf(fp, "[%s]\n", section->name) < 0) { 329 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, 330 temp_filename, strerror(errno)); 331 goto error; 332 } 333 334 for (const list_node_t* enode = list_begin(section->entries); 335 enode != list_end(section->entries); enode = list_next(enode)) { 336 const entry_t* entry = (const entry_t*)list_node(enode); 337 if (fprintf(fp, "%s = %s\n", entry->key, entry->value) < 0) { 338 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, 339 temp_filename, strerror(errno)); 340 goto error; 341 } 342 } 343 344 // Only add a separating newline if there are more sections. 345 if (list_next(node) != list_end(config->sections)) { 346 if (fputc('\n', fp) == EOF) { 347 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, 348 temp_filename, strerror(errno)); 349 goto error; 350 } 351 } 352 } 353 354 // Sync written temp file out to disk. fsync() is blocking until data makes it 355 // to disk. 356 if (fsync(fileno(fp)) < 0) { 357 LOG_WARN(LOG_TAG, "%s unable to fsync file '%s': %s", __func__, 358 temp_filename, strerror(errno)); 359 } 360 361 if (fclose(fp) == EOF) { 362 LOG_ERROR(LOG_TAG, "%s unable to close file '%s': %s", __func__, 363 temp_filename, strerror(errno)); 364 goto error; 365 } 366 fp = NULL; 367 368 // Change the file's permissions to Read/Write by User and Group 369 if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { 370 LOG_ERROR(LOG_TAG, "%s unable to change file permissions '%s': %s", 371 __func__, filename, strerror(errno)); 372 goto error; 373 } 374 375 // Rename written temp file to the actual config file. 376 if (rename(temp_filename, filename) == -1) { 377 LOG_ERROR(LOG_TAG, "%s unable to commit file '%s': %s", __func__, filename, 378 strerror(errno)); 379 goto error; 380 } 381 382 // This should ensure the directory is updated as well. 383 if (fsync(dir_fd) < 0) { 384 LOG_WARN(LOG_TAG, "%s unable to fsync dir '%s': %s", __func__, 385 directoryname, strerror(errno)); 386 } 387 388 if (close(dir_fd) < 0) { 389 LOG_ERROR(LOG_TAG, "%s unable to close dir '%s': %s", __func__, 390 directoryname, strerror(errno)); 391 goto error; 392 } 393 394 osi_free(temp_filename); 395 osi_free(temp_dirname); 396 return true; 397 398 error: 399 // This indicates there is a write issue. Unlink as partial data is not 400 // acceptable. 401 unlink(temp_filename); 402 if (fp) fclose(fp); 403 if (dir_fd != -1) close(dir_fd); 404 osi_free(temp_filename); 405 osi_free(temp_dirname); 406 return false; 407 } 408 409 static char* trim(char* str) { 410 while (isspace(*str)) ++str; 411 412 if (!*str) return str; 413 414 char* end_str = str + strlen(str) - 1; 415 while (end_str > str && isspace(*end_str)) --end_str; 416 417 end_str[1] = '\0'; 418 return str; 419 } 420 421 static bool config_parse(FILE* fp, config_t* config) { 422 CHECK(fp != NULL); 423 CHECK(config != NULL); 424 425 int line_num = 0; 426 char line[1024]; 427 char section[1024]; 428 strcpy(section, CONFIG_DEFAULT_SECTION); 429 430 while (fgets(line, sizeof(line), fp)) { 431 char* line_ptr = trim(line); 432 ++line_num; 433 434 // Skip blank and comment lines. 435 if (*line_ptr == '\0' || *line_ptr == '#') continue; 436 437 if (*line_ptr == '[') { 438 size_t len = strlen(line_ptr); 439 if (line_ptr[len - 1] != ']') { 440 LOG_DEBUG(LOG_TAG, "%s unterminated section name on line %d.", __func__, 441 line_num); 442 return false; 443 } 444 strncpy(section, line_ptr + 1, len - 2); 445 section[len - 2] = '\0'; 446 } else { 447 char* split = strchr(line_ptr, '='); 448 if (!split) { 449 LOG_DEBUG(LOG_TAG, "%s no key/value separator found on line %d.", 450 __func__, line_num); 451 return false; 452 } 453 454 *split = '\0'; 455 config_set_string(config, section, trim(line_ptr), trim(split + 1)); 456 } 457 } 458 return true; 459 } 460 461 static section_t* section_new(const char* name) { 462 section_t* section = static_cast<section_t*>(osi_calloc(sizeof(section_t))); 463 464 section->name = osi_strdup(name); 465 section->entries = list_new(entry_free); 466 return section; 467 } 468 469 static void section_free(void* ptr) { 470 if (!ptr) return; 471 472 section_t* section = static_cast<section_t*>(ptr); 473 osi_free(section->name); 474 list_free(section->entries); 475 osi_free(section); 476 } 477 478 static section_t* section_find(const config_t* config, const char* section) { 479 for (const list_node_t* node = list_begin(config->sections); 480 node != list_end(config->sections); node = list_next(node)) { 481 section_t* sec = static_cast<section_t*>(list_node(node)); 482 if (!strcmp(sec->name, section)) return sec; 483 } 484 485 return NULL; 486 } 487 488 static entry_t* entry_new(const char* key, const char* value) { 489 entry_t* entry = static_cast<entry_t*>(osi_calloc(sizeof(entry_t))); 490 491 entry->key = osi_strdup(key); 492 entry->value = osi_strdup(value); 493 return entry; 494 } 495 496 static void entry_free(void* ptr) { 497 if (!ptr) return; 498 499 entry_t* entry = static_cast<entry_t*>(ptr); 500 osi_free(entry->key); 501 osi_free(entry->value); 502 osi_free(entry); 503 } 504 505 static entry_t* entry_find(const config_t* config, const char* section, 506 const char* key) { 507 section_t* sec = section_find(config, section); 508 if (!sec) return NULL; 509 510 for (const list_node_t* node = list_begin(sec->entries); 511 node != list_end(sec->entries); node = list_next(node)) { 512 entry_t* entry = static_cast<entry_t*>(list_node(node)); 513 if (!strcmp(entry->key, key)) return entry; 514 } 515 516 return NULL; 517 } 518