| /****************************************************************************** |
| * |
| * 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 <base/logging.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <libgen.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #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<config_t*>(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<section_t*>(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<entry_t*>(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<entry_t*>(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<char*>(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<section_t*>(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<section_t*>(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<section_t*>(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<entry_t*>(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<entry_t*>(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<entry_t*>(list_node(node)); |
| if (!strcmp(entry->key, key)) return entry; |
| } |
| |
| return NULL; |
| } |