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 <assert.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 <unistd.h> 32 #include <sys/stat.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, const char *key); 64 65 config_t *config_new_empty(void) { 66 config_t *config = osi_calloc(sizeof(config_t)); 67 68 config->sections = list_new(section_free); 69 if (!config->sections) { 70 LOG_ERROR(LOG_TAG, "%s unable to allocate list for sections.", __func__); 71 goto error; 72 } 73 74 return config; 75 76 error:; 77 config_free(config); 78 return NULL; 79 } 80 81 config_t *config_new(const char *filename) { 82 assert(filename != NULL); 83 84 config_t *config = config_new_empty(); 85 if (!config) 86 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, strerror(errno)); 91 config_free(config); 92 return NULL; 93 } 94 95 if (!config_parse(fp, config)) { 96 config_free(config); 97 config = NULL; 98 } 99 100 fclose(fp); 101 return config; 102 } 103 104 config_t *config_new_clone(const config_t *src) { 105 assert(src != NULL); 106 107 config_t *ret = config_new_empty(); 108 109 assert(ret != NULL); 110 111 for (const list_node_t *node = list_begin(src->sections); 112 node != list_end(src->sections); 113 node = list_next(node)) { 114 section_t *sec = 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 = 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) 130 return; 131 132 list_free(config->sections); 133 osi_free(config); 134 } 135 136 bool config_has_section(const config_t *config, const char *section) { 137 assert(config != NULL); 138 assert(section != NULL); 139 140 return (section_find(config, section) != NULL); 141 } 142 143 bool config_has_key(const config_t *config, const char *section, const char *key) { 144 assert(config != NULL); 145 assert(section != NULL); 146 assert(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, int def_value) { 152 assert(config != NULL); 153 assert(section != NULL); 154 assert(key != NULL); 155 156 entry_t *entry = entry_find(config, section, key); 157 if (!entry) 158 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, const char *key, bool def_value) { 166 assert(config != NULL); 167 assert(section != NULL); 168 assert(key != NULL); 169 170 entry_t *entry = entry_find(config, section, key); 171 if (!entry) 172 return def_value; 173 174 if (!strcmp(entry->value, "true")) 175 return true; 176 if (!strcmp(entry->value, "false")) 177 return false; 178 179 return def_value; 180 } 181 182 const char *config_get_string(const config_t *config, const char *section, const char *key, const char *def_value) { 183 assert(config != NULL); 184 assert(section != NULL); 185 assert(key != NULL); 186 187 entry_t *entry = entry_find(config, section, key); 188 if (!entry) 189 return def_value; 190 191 return entry->value; 192 } 193 194 void config_set_int(config_t *config, const char *section, const char *key, int value) { 195 assert(config != NULL); 196 assert(section != NULL); 197 assert(key != NULL); 198 199 char value_str[32] = { 0 }; 200 sprintf(value_str, "%d", value); 201 config_set_string(config, section, key, value_str); 202 } 203 204 void config_set_bool(config_t *config, const char *section, const char *key, bool value) { 205 assert(config != NULL); 206 assert(section != NULL); 207 assert(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, const char *value) { 213 section_t *sec = section_find(config, section); 214 if (!sec) { 215 sec = section_new(section); 216 list_append(config->sections, sec); 217 } 218 219 for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { 220 entry_t *entry = list_node(node); 221 if (!strcmp(entry->key, key)) { 222 osi_free(entry->value); 223 entry->value = osi_strdup(value); 224 return; 225 } 226 } 227 228 entry_t *entry = entry_new(key, value); 229 list_append(sec->entries, entry); 230 } 231 232 bool config_remove_section(config_t *config, const char *section) { 233 assert(config != NULL); 234 assert(section != NULL); 235 236 section_t *sec = section_find(config, section); 237 if (!sec) 238 return false; 239 240 return list_remove(config->sections, sec); 241 } 242 243 bool config_remove_key(config_t *config, const char *section, const char *key) { 244 assert(config != NULL); 245 assert(section != NULL); 246 assert(key != NULL); 247 248 section_t *sec = section_find(config, section); 249 entry_t *entry = entry_find(config, section, key); 250 if (!sec || !entry) 251 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 assert(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 assert(config != NULL); 263 return (const config_section_node_t *)list_end(config->sections); 264 } 265 266 const config_section_node_t *config_section_next(const config_section_node_t *node) { 267 assert(node != NULL); 268 return (const config_section_node_t *)list_next((const list_node_t *)node); 269 } 270 271 const char *config_section_name(const config_section_node_t *node) { 272 assert(node != NULL); 273 const list_node_t *lnode = (const list_node_t *)node; 274 const section_t *section = (const section_t *)list_node(lnode); 275 return section->name; 276 } 277 278 bool config_save(const config_t *config, const char *filename) { 279 assert(config != NULL); 280 assert(filename != NULL); 281 assert(*filename != '\0'); 282 283 // Steps to ensure content of config file gets to disk: 284 // 285 // 1) Open and write to temp file (e.g. bt_config.conf.new). 286 // 2) Sync the temp file to disk with fsync(). 287 // 3) Rename temp file to actual config file (e.g. bt_config.conf). 288 // This ensures atomic update. 289 // 4) Sync directory that has the conf file with fsync(). 290 // This ensures directory entries are up-to-date. 291 int dir_fd = -1; 292 FILE *fp = NULL; 293 294 // Build temp config file based on config file (e.g. bt_config.conf.new). 295 static const char *temp_file_ext = ".new"; 296 const int filename_len = strlen(filename); 297 const int temp_filename_len = filename_len + strlen(temp_file_ext) + 1; 298 char *temp_filename = osi_calloc(temp_filename_len); 299 snprintf(temp_filename, temp_filename_len, "%s%s", filename, temp_file_ext); 300 301 // Extract directory from file path (e.g. /data/misc/bluedroid). 302 char *temp_dirname = osi_strdup(filename); 303 const char *directoryname = dirname(temp_dirname); 304 if (!directoryname) { 305 LOG_ERROR(LOG_TAG, "%s error extracting directory from '%s': %s", __func__, filename, strerror(errno)); 306 goto error; 307 } 308 309 dir_fd = open(directoryname, O_RDONLY); 310 if (dir_fd < 0) { 311 LOG_ERROR(LOG_TAG, "%s unable to open dir '%s': %s", __func__, directoryname, strerror(errno)); 312 goto error; 313 } 314 315 fp = fopen(temp_filename, "wt"); 316 if (!fp) { 317 LOG_ERROR(LOG_TAG, "%s unable to write file '%s': %s", __func__, temp_filename, strerror(errno)); 318 goto error; 319 } 320 321 for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { 322 const section_t *section = (const section_t *)list_node(node); 323 if (fprintf(fp, "[%s]\n", section->name) < 0) { 324 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); 325 goto error; 326 } 327 328 for (const list_node_t *enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) { 329 const entry_t *entry = (const entry_t *)list_node(enode); 330 if (fprintf(fp, "%s = %s\n", entry->key, entry->value) < 0) { 331 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); 332 goto error; 333 } 334 } 335 336 // Only add a separating newline if there are more sections. 337 if (list_next(node) != list_end(config->sections)) { 338 if (fputc('\n', fp) == EOF) { 339 LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); 340 goto error; 341 } 342 } 343 } 344 345 // Sync written temp file out to disk. fsync() is blocking until data makes it to disk. 346 if (fsync(fileno(fp)) < 0) { 347 LOG_WARN(LOG_TAG, "%s unable to fsync file '%s': %s", __func__, temp_filename, strerror(errno)); 348 } 349 350 if (fclose(fp) == EOF) { 351 LOG_ERROR(LOG_TAG, "%s unable to close file '%s': %s", __func__, temp_filename, strerror(errno)); 352 goto error; 353 } 354 fp = NULL; 355 356 // Change the file's permissions to Read/Write by User and Group 357 if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { 358 LOG_ERROR(LOG_TAG, "%s unable to change file permissions '%s': %s", __func__, filename, strerror(errno)); 359 goto error; 360 } 361 362 // Rename written temp file to the actual config file. 363 if (rename(temp_filename, filename) == -1) { 364 LOG_ERROR(LOG_TAG, "%s unable to commit file '%s': %s", __func__, filename, strerror(errno)); 365 goto error; 366 } 367 368 // This should ensure the directory is updated as well. 369 if (fsync(dir_fd) < 0) { 370 LOG_WARN(LOG_TAG, "%s unable to fsync dir '%s': %s", __func__, directoryname, strerror(errno)); 371 } 372 373 if (close(dir_fd) < 0) { 374 LOG_ERROR(LOG_TAG, "%s unable to close dir '%s': %s", __func__, directoryname, strerror(errno)); 375 goto error; 376 } 377 378 osi_free(temp_filename); 379 osi_free(temp_dirname); 380 return true; 381 382 error: 383 // This indicates there is a write issue. Unlink as partial data is not acceptable. 384 unlink(temp_filename); 385 if (fp) 386 fclose(fp); 387 if (dir_fd != -1) 388 close(dir_fd); 389 osi_free(temp_filename); 390 osi_free(temp_dirname); 391 return false; 392 } 393 394 static char *trim(char *str) { 395 while (isspace(*str)) 396 ++str; 397 398 if (!*str) 399 return str; 400 401 char *end_str = str + strlen(str) - 1; 402 while (end_str > str && isspace(*end_str)) 403 --end_str; 404 405 end_str[1] = '\0'; 406 return str; 407 } 408 409 static bool config_parse(FILE *fp, config_t *config) { 410 assert(fp != NULL); 411 assert(config != NULL); 412 413 int line_num = 0; 414 char line[1024]; 415 char section[1024]; 416 strcpy(section, CONFIG_DEFAULT_SECTION); 417 418 while (fgets(line, sizeof(line), fp)) { 419 char *line_ptr = trim(line); 420 ++line_num; 421 422 // Skip blank and comment lines. 423 if (*line_ptr == '\0' || *line_ptr == '#') 424 continue; 425 426 if (*line_ptr == '[') { 427 size_t len = strlen(line_ptr); 428 if (line_ptr[len - 1] != ']') { 429 LOG_DEBUG(LOG_TAG, "%s unterminated section name on line %d.", __func__, line_num); 430 return false; 431 } 432 strncpy(section, line_ptr + 1, len - 2); 433 section[len - 2] = '\0'; 434 } else { 435 char *split = strchr(line_ptr, '='); 436 if (!split) { 437 LOG_DEBUG(LOG_TAG, "%s no key/value separator found on line %d.", __func__, line_num); 438 return false; 439 } 440 441 *split = '\0'; 442 config_set_string(config, section, trim(line_ptr), trim(split + 1)); 443 } 444 } 445 return true; 446 } 447 448 static section_t *section_new(const char *name) { 449 section_t *section = osi_calloc(sizeof(section_t)); 450 451 section->name = osi_strdup(name); 452 section->entries = list_new(entry_free); 453 return section; 454 } 455 456 static void section_free(void *ptr) { 457 if (!ptr) 458 return; 459 460 section_t *section = ptr; 461 osi_free(section->name); 462 list_free(section->entries); 463 osi_free(section); 464 } 465 466 static section_t *section_find(const config_t *config, const char *section) { 467 for (const list_node_t *node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { 468 section_t *sec = list_node(node); 469 if (!strcmp(sec->name, section)) 470 return sec; 471 } 472 473 return NULL; 474 } 475 476 static entry_t *entry_new(const char *key, const char *value) { 477 entry_t *entry = osi_calloc(sizeof(entry_t)); 478 479 entry->key = osi_strdup(key); 480 entry->value = osi_strdup(value); 481 return entry; 482 } 483 484 static void entry_free(void *ptr) { 485 if (!ptr) 486 return; 487 488 entry_t *entry = ptr; 489 osi_free(entry->key); 490 osi_free(entry->value); 491 osi_free(entry); 492 } 493 494 static entry_t *entry_find(const config_t *config, const char *section, const char *key) { 495 section_t *sec = section_find(config, section); 496 if (!sec) 497 return NULL; 498 499 for (const list_node_t *node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { 500 entry_t *entry = list_node(node); 501 if (!strcmp(entry->key, key)) 502 return entry; 503 } 504 505 return NULL; 506 } 507