/****************************************************************************** * * Copyright (C) 2014 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ******************************************************************************/ #define LOG_TAG "bt_osi_config" #include "osi/include/config.h" #include #include #include #include #include #include #include #include #include #include #include "osi/include/allocator.h" #include "osi/include/list.h" #include "osi/include/log.h" typedef struct { char* key; char* value; } entry_t; typedef struct { char* name; list_t* entries; } section_t; struct config_t { list_t* sections; }; // Empty definition; this type is aliased to list_node_t. struct config_section_iter_t {}; static bool config_parse(FILE* fp, config_t* config); static section_t* section_new(const char* name); static void section_free(void* ptr); static section_t* section_find(const config_t* config, const char* section); static entry_t* entry_new(const char* key, const char* value); static void entry_free(void* ptr); static entry_t* entry_find(const config_t* config, const char* section, const char* key); config_t* config_new_empty(void) { config_t* config = static_cast(osi_calloc(sizeof(config_t))); config->sections = list_new(section_free); if (!config->sections) { LOG_ERROR(LOG_TAG, "%s unable to allocate list for sections.", __func__); goto error; } return config; error:; config_free(config); return NULL; } config_t* config_new(const char* filename) { CHECK(filename != NULL); config_t* config = config_new_empty(); if (!config) return NULL; FILE* fp = fopen(filename, "rt"); if (!fp) { LOG_ERROR(LOG_TAG, "%s unable to open file '%s': %s", __func__, filename, strerror(errno)); config_free(config); return NULL; } if (!config_parse(fp, config)) { config_free(config); config = NULL; } fclose(fp); return config; } config_t* config_new_clone(const config_t* src) { CHECK(src != NULL); config_t* ret = config_new_empty(); CHECK(ret != NULL); for (const list_node_t* node = list_begin(src->sections); node != list_end(src->sections); node = list_next(node)) { section_t* sec = static_cast(list_node(node)); for (const list_node_t* node_entry = list_begin(sec->entries); node_entry != list_end(sec->entries); node_entry = list_next(node_entry)) { entry_t* entry = static_cast(list_node(node_entry)); config_set_string(ret, sec->name, entry->key, entry->value); } } return ret; } void config_free(config_t* config) { if (!config) return; list_free(config->sections); osi_free(config); } bool config_has_section(const config_t* config, const char* section) { CHECK(config != NULL); CHECK(section != NULL); return (section_find(config, section) != NULL); } bool config_has_key(const config_t* config, const char* section, const char* key) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); return (entry_find(config, section, key) != NULL); } int config_get_int(const config_t* config, const char* section, const char* key, int def_value) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; char* endptr; int ret = strtol(entry->value, &endptr, 0); return (*endptr == '\0') ? ret : def_value; } bool config_get_bool(const config_t* config, const char* section, const char* key, bool def_value) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; if (!strcmp(entry->value, "true")) return true; if (!strcmp(entry->value, "false")) return false; return def_value; } const char* config_get_string(const config_t* config, const char* section, const char* key, const char* def_value) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; return entry->value; } void config_set_int(config_t* config, const char* section, const char* key, int value) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); char value_str[32] = {0}; snprintf(value_str, sizeof(value_str), "%d", value); config_set_string(config, section, key, value_str); } void config_set_bool(config_t* config, const char* section, const char* key, bool value) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); config_set_string(config, section, key, value ? "true" : "false"); } void config_set_string(config_t* config, const char* section, const char* key, const char* value) { section_t* sec = section_find(config, section); if (!sec) { sec = section_new(section); list_append(config->sections, sec); } for (const list_node_t* node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { entry_t* entry = static_cast(list_node(node)); if (!strcmp(entry->key, key)) { osi_free(entry->value); entry->value = osi_strdup(value); return; } } entry_t* entry = entry_new(key, value); list_append(sec->entries, entry); } bool config_remove_section(config_t* config, const char* section) { CHECK(config != NULL); CHECK(section != NULL); section_t* sec = section_find(config, section); if (!sec) return false; return list_remove(config->sections, sec); } bool config_remove_key(config_t* config, const char* section, const char* key) { CHECK(config != NULL); CHECK(section != NULL); CHECK(key != NULL); section_t* sec = section_find(config, section); entry_t* entry = entry_find(config, section, key); if (!sec || !entry) return false; return list_remove(sec->entries, entry); } const config_section_node_t* config_section_begin(const config_t* config) { CHECK(config != NULL); return (const config_section_node_t*)list_begin(config->sections); } const config_section_node_t* config_section_end(const config_t* config) { CHECK(config != NULL); return (const config_section_node_t*)list_end(config->sections); } const config_section_node_t* config_section_next( const config_section_node_t* node) { CHECK(node != NULL); return (const config_section_node_t*)list_next((const list_node_t*)node); } const char* config_section_name(const config_section_node_t* node) { CHECK(node != NULL); const list_node_t* lnode = (const list_node_t*)node; const section_t* section = (const section_t*)list_node(lnode); return section->name; } bool config_save(const config_t* config, const char* filename) { CHECK(config != NULL); CHECK(filename != NULL); CHECK(*filename != '\0'); // Steps to ensure content of config file gets to disk: // // 1) Open and write to temp file (e.g. bt_config.conf.new). // 2) Sync the temp file to disk with fsync(). // 3) Rename temp file to actual config file (e.g. bt_config.conf). // This ensures atomic update. // 4) Sync directory that has the conf file with fsync(). // This ensures directory entries are up-to-date. int dir_fd = -1; FILE* fp = NULL; // Build temp config file based on config file (e.g. bt_config.conf.new). static const char* temp_file_ext = ".new"; const int filename_len = strlen(filename); const int temp_filename_len = filename_len + strlen(temp_file_ext) + 1; char* temp_filename = static_cast(osi_calloc(temp_filename_len)); snprintf(temp_filename, temp_filename_len, "%s%s", filename, temp_file_ext); // Extract directory from file path (e.g. /data/misc/bluedroid). char* temp_dirname = osi_strdup(filename); const char* directoryname = dirname(temp_dirname); if (!directoryname) { LOG_ERROR(LOG_TAG, "%s error extracting directory from '%s': %s", __func__, filename, strerror(errno)); goto error; } dir_fd = open(directoryname, O_RDONLY); if (dir_fd < 0) { LOG_ERROR(LOG_TAG, "%s unable to open dir '%s': %s", __func__, directoryname, strerror(errno)); goto error; } fp = fopen(temp_filename, "wt"); if (!fp) { LOG_ERROR(LOG_TAG, "%s unable to write file '%s': %s", __func__, temp_filename, strerror(errno)); goto error; } for (const list_node_t* node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { const section_t* section = (const section_t*)list_node(node); if (fprintf(fp, "[%s]\n", section->name) < 0) { LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); goto error; } for (const list_node_t* enode = list_begin(section->entries); enode != list_end(section->entries); enode = list_next(enode)) { const entry_t* entry = (const entry_t*)list_node(enode); if (fprintf(fp, "%s = %s\n", entry->key, entry->value) < 0) { LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); goto error; } } // Only add a separating newline if there are more sections. if (list_next(node) != list_end(config->sections)) { if (fputc('\n', fp) == EOF) { LOG_ERROR(LOG_TAG, "%s unable to write to file '%s': %s", __func__, temp_filename, strerror(errno)); goto error; } } } // Sync written temp file out to disk. fsync() is blocking until data makes it // to disk. if (fsync(fileno(fp)) < 0) { LOG_WARN(LOG_TAG, "%s unable to fsync file '%s': %s", __func__, temp_filename, strerror(errno)); } if (fclose(fp) == EOF) { LOG_ERROR(LOG_TAG, "%s unable to close file '%s': %s", __func__, temp_filename, strerror(errno)); goto error; } fp = NULL; // Change the file's permissions to Read/Write by User and Group if (chmod(temp_filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { LOG_ERROR(LOG_TAG, "%s unable to change file permissions '%s': %s", __func__, filename, strerror(errno)); goto error; } // Rename written temp file to the actual config file. if (rename(temp_filename, filename) == -1) { LOG_ERROR(LOG_TAG, "%s unable to commit file '%s': %s", __func__, filename, strerror(errno)); goto error; } // This should ensure the directory is updated as well. if (fsync(dir_fd) < 0) { LOG_WARN(LOG_TAG, "%s unable to fsync dir '%s': %s", __func__, directoryname, strerror(errno)); } if (close(dir_fd) < 0) { LOG_ERROR(LOG_TAG, "%s unable to close dir '%s': %s", __func__, directoryname, strerror(errno)); goto error; } osi_free(temp_filename); osi_free(temp_dirname); return true; error: // This indicates there is a write issue. Unlink as partial data is not // acceptable. unlink(temp_filename); if (fp) fclose(fp); if (dir_fd != -1) close(dir_fd); osi_free(temp_filename); osi_free(temp_dirname); return false; } static char* trim(char* str) { while (isspace(*str)) ++str; if (!*str) return str; char* end_str = str + strlen(str) - 1; while (end_str > str && isspace(*end_str)) --end_str; end_str[1] = '\0'; return str; } static bool config_parse(FILE* fp, config_t* config) { CHECK(fp != NULL); CHECK(config != NULL); int line_num = 0; char line[1024]; char section[1024]; strcpy(section, CONFIG_DEFAULT_SECTION); while (fgets(line, sizeof(line), fp)) { char* line_ptr = trim(line); ++line_num; // Skip blank and comment lines. if (*line_ptr == '\0' || *line_ptr == '#') continue; if (*line_ptr == '[') { size_t len = strlen(line_ptr); if (line_ptr[len - 1] != ']') { LOG_DEBUG(LOG_TAG, "%s unterminated section name on line %d.", __func__, line_num); return false; } strncpy(section, line_ptr + 1, len - 2); section[len - 2] = '\0'; } else { char* split = strchr(line_ptr, '='); if (!split) { LOG_DEBUG(LOG_TAG, "%s no key/value separator found on line %d.", __func__, line_num); return false; } *split = '\0'; config_set_string(config, section, trim(line_ptr), trim(split + 1)); } } return true; } static section_t* section_new(const char* name) { section_t* section = static_cast(osi_calloc(sizeof(section_t))); section->name = osi_strdup(name); section->entries = list_new(entry_free); return section; } static void section_free(void* ptr) { if (!ptr) return; section_t* section = static_cast(ptr); osi_free(section->name); list_free(section->entries); osi_free(section); } static section_t* section_find(const config_t* config, const char* section) { for (const list_node_t* node = list_begin(config->sections); node != list_end(config->sections); node = list_next(node)) { section_t* sec = static_cast(list_node(node)); if (!strcmp(sec->name, section)) return sec; } return NULL; } static entry_t* entry_new(const char* key, const char* value) { entry_t* entry = static_cast(osi_calloc(sizeof(entry_t))); entry->key = osi_strdup(key); entry->value = osi_strdup(value); return entry; } static void entry_free(void* ptr) { if (!ptr) return; entry_t* entry = static_cast(ptr); osi_free(entry->key); osi_free(entry->value); osi_free(entry); } static entry_t* entry_find(const config_t* config, const char* section, const char* key) { section_t* sec = section_find(config, section); if (!sec) return NULL; for (const list_node_t* node = list_begin(sec->entries); node != list_end(sec->entries); node = list_next(node)) { entry_t* entry = static_cast(list_node(node)); if (!strcmp(entry->key, key)) return entry; } return NULL; }