Loading system/main/shim/config.cc +59 −461 Original line number Diff line number Diff line Loading @@ -16,489 +16,87 @@ #define LOG_TAG "bt_shim_storage" #include <base/files/file_util.h> #include <ctype.h> #include <log/log.h> #include <string.h> #include <list> #include <cstdint> #include <cstring> #include <future> #include <memory> #include "btif/include/btif_config.h" #include "main/shim/config.h" #include "main/shim/entry.h" namespace { 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; } bool config_parse(FILE* fp, config_t* config) { CHECK(fp != nullptr); CHECK(config != nullptr); 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] != ']') { VLOG(1) << __func__ << ": unterminated section name on line " << line_num; return false; } strncpy(section, line_ptr + 1, len - 2); // NOLINT (len < 1024) section[len - 2] = '\0'; } else { char* split = strchr(line_ptr, '='); if (!split) { VLOG(1) << __func__ << ": no key/value separator found on line " << line_num; return false; } *split = '\0'; config_set_string(config, section, trim(line_ptr), trim(split + 1)); } } return true; } template <typename T, class = typename std::enable_if<std::is_same< config_t, typename std::remove_const<T>::type>::value>> auto section_find(T& config, const std::string& section) { return std::find_if( config.sections.begin(), config.sections.end(), [§ion](const section_t& sec) { return sec.name == section; }); } const entry_t* entry_find(const config_t& config, const std::string& section, const std::string& key) { auto sec = section_find(config, section); if (sec == config.sections.end()) return nullptr; for (const entry_t& entry : sec->entries) { if (entry.key == key) return &entry; } return nullptr; } using ::bluetooth::shim::GetStorage; namespace bluetooth { namespace shim { std::unique_ptr<config_t> config_new_empty(void) { return std::make_unique<config_t>(); } std::unique_ptr<config_t> config_new(const char* filename) { CHECK(filename != nullptr); std::unique_ptr<config_t> config = config_new_empty(); FILE* fp = fopen(filename, "rt"); if (!fp) { LOG(ERROR) << __func__ << ": unable to open file '" << filename << "': " << strerror(errno); return nullptr; } if (!config_parse(fp, config.get())) { config.reset(); } fclose(fp); return config; } std::string checksum_read(const char* filename) { base::FilePath path(filename); if (!base::PathExists(path)) { LOG(ERROR) << __func__ << ": unable to locate file '" << filename << "'"; return ""; } std::string encrypted_hash; if (!base::ReadFileToString(path, &encrypted_hash)) { LOG(ERROR) << __func__ << ": unable to read file '" << filename << "'"; } return encrypted_hash; } void config_set_string(config_t* config, const std::string& section, const std::string& key, const std::string& value) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) { config->sections.emplace_back(section_t{.name = section}); sec = std::prev(config->sections.end()); } std::string value_no_newline; size_t newline_position = value.find('\n'); if (newline_position != std::string::npos) { android_errorWriteLog(0x534e4554, "70808273"); value_no_newline = value.substr(0, newline_position); } else { value_no_newline = value; } for (entry_t& entry : sec->entries) { if (entry.key == key) { entry.value = value_no_newline; return; } } sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline}); } std::unique_ptr<config_t> config_new_clone(const config_t& src) { std::unique_ptr<config_t> ret = config_new_empty(); for (const section_t& sec : src.sections) { for (const entry_t& entry : sec.entries) { shim::config_set_string(ret.get(), sec.name, entry.key, entry.value); } } return ret; } bool config_has_section(const config_t& config, const std::string& section) { return (section_find(config, section) != config.sections.end()); } bool config_has_key(const config_t& config, const std::string& section, const std::string& key) { return (entry_find(config, section, key) != nullptr); } int config_get_int(const config_t& config, const std::string& section, const std::string& key, int def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; char* endptr; int ret = strtol(entry->value.c_str(), &endptr, 0); return (*endptr == '\0') ? ret : def_value; } uint64_t config_get_uint64(const config_t& config, const std::string& section, const std::string& key, uint64_t def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; char* endptr; uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0); return (*endptr == '\0') ? ret : def_value; } bool config_get_bool(const config_t& config, const std::string& section, const std::string& key, bool def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; if (entry->value == "true") return true; if (entry->value == "false") return false; return def_value; } const std::string* config_get_string(const config_t& config, const std::string& section, const std::string& key, const std::string* def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; return &entry->value; } void config_set_int(config_t* config, const std::string& section, const std::string& key, int value) { shim::config_set_string(config, section, key, std::to_string(value)); } void config_set_uint64(config_t* config, const std::string& section, const std::string& key, uint64_t value) { shim::config_set_string(config, section, key, std::to_string(value)); } void config_set_bool(config_t* config, const std::string& section, const std::string& key, bool value) { shim::config_set_string(config, section, key, value ? "true" : "false"); } bool config_remove_section(config_t* config, const std::string& section) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) return false; config->sections.erase(sec); return true; } bool config_remove_key(config_t* config, const std::string& section, const std::string& key) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) return false; for (auto entry = sec->entries.begin(); entry != sec->entries.end(); ++entry) { if (entry->key == key) { sec->entries.erase(entry); return true; } } return false; } bool config_save(const config_t& config, const std::string& filename) { CHECK(!filename.empty()); // Steps to ensure content of config file gets to disk: // // 1) Open and write to temp file (e.g. bt_config.conf.new). // 2) Flush the stream buffer to the temp file. // 3) Sync the temp file to disk with fsync(). // 4) Rename temp file to actual config file (e.g. bt_config.conf). // This ensures atomic update. // 5) Sync directory that has the conf file with fsync(). // This ensures directory entries are up-to-date. int dir_fd = -1; FILE* fp = nullptr; std::stringstream serialized; // Build temp config file based on config file (e.g. bt_config.conf.new). const std::string temp_filename = filename + ".new"; // Extract directory from file path (e.g. /data/misc/bluedroid). const std::string directoryname = base::FilePath(filename).DirName().value(); if (directoryname.empty()) { LOG(ERROR) << __func__ << ": error extracting directory from '" << filename << "': " << strerror(errno); goto error; } dir_fd = open(directoryname.c_str(), O_RDONLY); if (dir_fd < 0) { LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname << "': " << strerror(errno); goto error; } fp = fopen(temp_filename.c_str(), "wt"); if (!fp) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error; } for (const section_t& section : config.sections) { serialized << "[" << section.name << "]" << std::endl; for (const entry_t& entry : section.entries) serialized << entry.key << " = " << entry.value << std::endl; serialized << std::endl; } if (fprintf(fp, "%s", serialized.str().c_str()) < 0) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error; } // Flush the stream buffer to the temp file. if (fflush(fp) < 0) { LOG(ERROR) << __func__ << ": unable to write flush buffer to file '" << 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(WARNING) << __func__ << ": unable to fsync file '" << temp_filename << "': " << strerror(errno); } if (fclose(fp) == EOF) { LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename << "': " << strerror(errno); goto error; } fp = nullptr; // Change the file's permissions to Read/Write by User and Group if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { LOG(ERROR) << __func__ << ": unable to change file permissions '" << filename << "': " << strerror(errno); goto error; } // Rename written temp file to the actual config file. if (rename(temp_filename.c_str(), filename.c_str()) == -1) { LOG(ERROR) << __func__ << ": unable to commit file '" << filename << "': " << strerror(errno); goto error; } // This should ensure the directory is updated as well. if (fsync(dir_fd) < 0) { LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname << "': " << strerror(errno); } if (close(dir_fd) < 0) { LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname << "': " << strerror(errno); goto error; } return true; CHECK(filename != nullptr); error: // This indicates there is a write issue. Unlink as partial data is not // acceptable. unlink(temp_filename.c_str()); if (fp) fclose(fp); if (dir_fd != -1) close(dir_fd); return false; std::promise<std::string> promise; auto future = promise.get_future(); GetStorage()->ChecksumRead( std::string(filename), [&promise](std::string hash_value) { promise.set_value(hash_value); }); return future.get(); } bool checksum_save(const std::string& checksum, const std::string& filename) { CHECK(!checksum.empty()) << __func__ << ": checksum cannot be empty"; CHECK(!filename.empty()) << __func__ << ": filename cannot be empty"; // Steps to ensure content of config checksum file gets to disk: // // 1) Open and write to temp file (e.g. // bt_config.conf.encrypted-checksum.new). 2) Sync the temp file to disk with // fsync(). 3) Rename temp file to actual config checksum file (e.g. // bt_config.conf.encrypted-checksum). // This ensures atomic update. // 4) Sync directory that has the conf file with fsync(). // This ensures directory entries are up-to-date. FILE* fp = nullptr; int dir_fd = -1; // Build temp config checksum file based on config checksum file (e.g. // bt_config.conf.encrypted-checksum.new). const std::string temp_filename = filename + ".new"; base::FilePath path(temp_filename); // Extract directory from file path (e.g. /data/misc/bluedroid). const std::string directoryname = base::FilePath(filename).DirName().value(); if (directoryname.empty()) { LOG(ERROR) << __func__ << ": error extracting directory from '" << filename << "': " << strerror(errno); goto error2; } dir_fd = open(directoryname.c_str(), O_RDONLY); if (dir_fd < 0) { LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname << "': " << strerror(errno); goto error2; } if (base::WriteFile(path, checksum.data(), checksum.size()) != (int)checksum.size()) { LOG(ERROR) << __func__ << ": unable to write file '" << filename.c_str(); goto error2; } fp = fopen(temp_filename.c_str(), "rb"); if (!fp) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error2; } // Sync written temp file out to disk. fsync() is blocking until data makes it // to disk. if (fsync(fileno(fp)) < 0) { LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename << "': " << strerror(errno); } if (fclose(fp) == EOF) { LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename << "': " << strerror(errno); goto error2; std::promise<bool> promise; auto future = promise.get_future(); GetStorage()->ChecksumWrite(filename, checksum, [&promise](bool success) { promise.set_value(success); }); return future.get(); } fp = nullptr; // Change the file's permissions to Read/Write by User and Group if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { LOG(ERROR) << __func__ << ": unable to change file permissions '" << filename << "': " << strerror(errno); goto error2; } // Rename written temp file to the actual config file. if (rename(temp_filename.c_str(), filename.c_str()) == -1) { LOG(ERROR) << __func__ << ": unable to commit file '" << filename << "': " << strerror(errno); goto error2; } std::unique_ptr<config_t> config_new(const char* filename) { CHECK(filename != nullptr); // This should ensure the directory is updated as well. if (fsync(dir_fd) < 0) { LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname << "': " << strerror(errno); std::promise<std::unique_ptr<config_t>> promise; auto future = promise.get_future(); GetStorage()->ConfigRead(std::string(filename), [&promise](std::unique_ptr<config_t> config) { promise.set_value(std::move(config)); }); return future.get(); } if (close(dir_fd) < 0) { LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname << "': " << strerror(errno); goto error2; bool config_save(const config_t& config, const std::string& filename) { std::promise<bool> promise; auto future = promise.get_future(); GetStorage()->ConfigWrite(filename, &config, [&promise](bool success) { promise.set_value(success); }); return future.get(); } return true; error2: // This indicates there is a write issue. Unlink as partial data is not // acceptable. unlink(temp_filename.c_str()); if (fp) fclose(fp); if (dir_fd != -1) close(dir_fd); return false; } } // namespace shim } // namespace bluetooth namespace { const storage_config_t interface = { checksum_read, checksum_save, config_get_bool, config_get_int, config_get_string, config_get_uint64, config_has_key, config_has_section, config_new, config_new_clone, config_new_empty, config_remove_key, config_remove_section, config_save, config_set_bool, config_set_int, config_set_string, config_set_uint64, bluetooth::shim::checksum_read, bluetooth::shim::checksum_save, config_get_bool, config_get_int, config_get_string, config_get_uint64, config_has_key, config_has_section, bluetooth::shim::config_new, config_new_clone, config_new_empty, config_remove_key, config_remove_section, bluetooth::shim::config_save, config_set_bool, config_set_int, config_set_string, config_set_uint64, }; } // namespace shim } // namespace } const storage_config_t* bluetooth::shim::storage_config_get_interface() { return &::shim::interface; return &interface; } system/main/shim/entry.cc +4 −0 Original line number Diff line number Diff line Loading @@ -73,3 +73,7 @@ bluetooth::shim::IScanning* bluetooth::shim::GetScanning() { bluetooth::shim::ISecurity* bluetooth::shim::GetSecurity() { return GetGabeldorscheStack()->GetSecurity(); } bluetooth::shim::IStorage* bluetooth::shim::GetStorage() { return GetGabeldorscheStack()->GetStorage(); } system/main/shim/entry.h +1 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ bluetooth::shim::IName* GetName(); bluetooth::shim::IPage* GetPage(); bluetooth::shim::IScanning* GetScanning(); bluetooth::shim::ISecurity* GetSecurity(); bluetooth::shim::IStorage* GetStorage(); } // namespace shim } // namespace bluetooth Loading
system/main/shim/config.cc +59 −461 Original line number Diff line number Diff line Loading @@ -16,489 +16,87 @@ #define LOG_TAG "bt_shim_storage" #include <base/files/file_util.h> #include <ctype.h> #include <log/log.h> #include <string.h> #include <list> #include <cstdint> #include <cstring> #include <future> #include <memory> #include "btif/include/btif_config.h" #include "main/shim/config.h" #include "main/shim/entry.h" namespace { 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; } bool config_parse(FILE* fp, config_t* config) { CHECK(fp != nullptr); CHECK(config != nullptr); 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] != ']') { VLOG(1) << __func__ << ": unterminated section name on line " << line_num; return false; } strncpy(section, line_ptr + 1, len - 2); // NOLINT (len < 1024) section[len - 2] = '\0'; } else { char* split = strchr(line_ptr, '='); if (!split) { VLOG(1) << __func__ << ": no key/value separator found on line " << line_num; return false; } *split = '\0'; config_set_string(config, section, trim(line_ptr), trim(split + 1)); } } return true; } template <typename T, class = typename std::enable_if<std::is_same< config_t, typename std::remove_const<T>::type>::value>> auto section_find(T& config, const std::string& section) { return std::find_if( config.sections.begin(), config.sections.end(), [§ion](const section_t& sec) { return sec.name == section; }); } const entry_t* entry_find(const config_t& config, const std::string& section, const std::string& key) { auto sec = section_find(config, section); if (sec == config.sections.end()) return nullptr; for (const entry_t& entry : sec->entries) { if (entry.key == key) return &entry; } return nullptr; } using ::bluetooth::shim::GetStorage; namespace bluetooth { namespace shim { std::unique_ptr<config_t> config_new_empty(void) { return std::make_unique<config_t>(); } std::unique_ptr<config_t> config_new(const char* filename) { CHECK(filename != nullptr); std::unique_ptr<config_t> config = config_new_empty(); FILE* fp = fopen(filename, "rt"); if (!fp) { LOG(ERROR) << __func__ << ": unable to open file '" << filename << "': " << strerror(errno); return nullptr; } if (!config_parse(fp, config.get())) { config.reset(); } fclose(fp); return config; } std::string checksum_read(const char* filename) { base::FilePath path(filename); if (!base::PathExists(path)) { LOG(ERROR) << __func__ << ": unable to locate file '" << filename << "'"; return ""; } std::string encrypted_hash; if (!base::ReadFileToString(path, &encrypted_hash)) { LOG(ERROR) << __func__ << ": unable to read file '" << filename << "'"; } return encrypted_hash; } void config_set_string(config_t* config, const std::string& section, const std::string& key, const std::string& value) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) { config->sections.emplace_back(section_t{.name = section}); sec = std::prev(config->sections.end()); } std::string value_no_newline; size_t newline_position = value.find('\n'); if (newline_position != std::string::npos) { android_errorWriteLog(0x534e4554, "70808273"); value_no_newline = value.substr(0, newline_position); } else { value_no_newline = value; } for (entry_t& entry : sec->entries) { if (entry.key == key) { entry.value = value_no_newline; return; } } sec->entries.emplace_back(entry_t{.key = key, .value = value_no_newline}); } std::unique_ptr<config_t> config_new_clone(const config_t& src) { std::unique_ptr<config_t> ret = config_new_empty(); for (const section_t& sec : src.sections) { for (const entry_t& entry : sec.entries) { shim::config_set_string(ret.get(), sec.name, entry.key, entry.value); } } return ret; } bool config_has_section(const config_t& config, const std::string& section) { return (section_find(config, section) != config.sections.end()); } bool config_has_key(const config_t& config, const std::string& section, const std::string& key) { return (entry_find(config, section, key) != nullptr); } int config_get_int(const config_t& config, const std::string& section, const std::string& key, int def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; char* endptr; int ret = strtol(entry->value.c_str(), &endptr, 0); return (*endptr == '\0') ? ret : def_value; } uint64_t config_get_uint64(const config_t& config, const std::string& section, const std::string& key, uint64_t def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; char* endptr; uint64_t ret = strtoull(entry->value.c_str(), &endptr, 0); return (*endptr == '\0') ? ret : def_value; } bool config_get_bool(const config_t& config, const std::string& section, const std::string& key, bool def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; if (entry->value == "true") return true; if (entry->value == "false") return false; return def_value; } const std::string* config_get_string(const config_t& config, const std::string& section, const std::string& key, const std::string* def_value) { const entry_t* entry = entry_find(config, section, key); if (!entry) return def_value; return &entry->value; } void config_set_int(config_t* config, const std::string& section, const std::string& key, int value) { shim::config_set_string(config, section, key, std::to_string(value)); } void config_set_uint64(config_t* config, const std::string& section, const std::string& key, uint64_t value) { shim::config_set_string(config, section, key, std::to_string(value)); } void config_set_bool(config_t* config, const std::string& section, const std::string& key, bool value) { shim::config_set_string(config, section, key, value ? "true" : "false"); } bool config_remove_section(config_t* config, const std::string& section) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) return false; config->sections.erase(sec); return true; } bool config_remove_key(config_t* config, const std::string& section, const std::string& key) { CHECK(config); auto sec = section_find(*config, section); if (sec == config->sections.end()) return false; for (auto entry = sec->entries.begin(); entry != sec->entries.end(); ++entry) { if (entry->key == key) { sec->entries.erase(entry); return true; } } return false; } bool config_save(const config_t& config, const std::string& filename) { CHECK(!filename.empty()); // Steps to ensure content of config file gets to disk: // // 1) Open and write to temp file (e.g. bt_config.conf.new). // 2) Flush the stream buffer to the temp file. // 3) Sync the temp file to disk with fsync(). // 4) Rename temp file to actual config file (e.g. bt_config.conf). // This ensures atomic update. // 5) Sync directory that has the conf file with fsync(). // This ensures directory entries are up-to-date. int dir_fd = -1; FILE* fp = nullptr; std::stringstream serialized; // Build temp config file based on config file (e.g. bt_config.conf.new). const std::string temp_filename = filename + ".new"; // Extract directory from file path (e.g. /data/misc/bluedroid). const std::string directoryname = base::FilePath(filename).DirName().value(); if (directoryname.empty()) { LOG(ERROR) << __func__ << ": error extracting directory from '" << filename << "': " << strerror(errno); goto error; } dir_fd = open(directoryname.c_str(), O_RDONLY); if (dir_fd < 0) { LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname << "': " << strerror(errno); goto error; } fp = fopen(temp_filename.c_str(), "wt"); if (!fp) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error; } for (const section_t& section : config.sections) { serialized << "[" << section.name << "]" << std::endl; for (const entry_t& entry : section.entries) serialized << entry.key << " = " << entry.value << std::endl; serialized << std::endl; } if (fprintf(fp, "%s", serialized.str().c_str()) < 0) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error; } // Flush the stream buffer to the temp file. if (fflush(fp) < 0) { LOG(ERROR) << __func__ << ": unable to write flush buffer to file '" << 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(WARNING) << __func__ << ": unable to fsync file '" << temp_filename << "': " << strerror(errno); } if (fclose(fp) == EOF) { LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename << "': " << strerror(errno); goto error; } fp = nullptr; // Change the file's permissions to Read/Write by User and Group if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { LOG(ERROR) << __func__ << ": unable to change file permissions '" << filename << "': " << strerror(errno); goto error; } // Rename written temp file to the actual config file. if (rename(temp_filename.c_str(), filename.c_str()) == -1) { LOG(ERROR) << __func__ << ": unable to commit file '" << filename << "': " << strerror(errno); goto error; } // This should ensure the directory is updated as well. if (fsync(dir_fd) < 0) { LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname << "': " << strerror(errno); } if (close(dir_fd) < 0) { LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname << "': " << strerror(errno); goto error; } return true; CHECK(filename != nullptr); error: // This indicates there is a write issue. Unlink as partial data is not // acceptable. unlink(temp_filename.c_str()); if (fp) fclose(fp); if (dir_fd != -1) close(dir_fd); return false; std::promise<std::string> promise; auto future = promise.get_future(); GetStorage()->ChecksumRead( std::string(filename), [&promise](std::string hash_value) { promise.set_value(hash_value); }); return future.get(); } bool checksum_save(const std::string& checksum, const std::string& filename) { CHECK(!checksum.empty()) << __func__ << ": checksum cannot be empty"; CHECK(!filename.empty()) << __func__ << ": filename cannot be empty"; // Steps to ensure content of config checksum file gets to disk: // // 1) Open and write to temp file (e.g. // bt_config.conf.encrypted-checksum.new). 2) Sync the temp file to disk with // fsync(). 3) Rename temp file to actual config checksum file (e.g. // bt_config.conf.encrypted-checksum). // This ensures atomic update. // 4) Sync directory that has the conf file with fsync(). // This ensures directory entries are up-to-date. FILE* fp = nullptr; int dir_fd = -1; // Build temp config checksum file based on config checksum file (e.g. // bt_config.conf.encrypted-checksum.new). const std::string temp_filename = filename + ".new"; base::FilePath path(temp_filename); // Extract directory from file path (e.g. /data/misc/bluedroid). const std::string directoryname = base::FilePath(filename).DirName().value(); if (directoryname.empty()) { LOG(ERROR) << __func__ << ": error extracting directory from '" << filename << "': " << strerror(errno); goto error2; } dir_fd = open(directoryname.c_str(), O_RDONLY); if (dir_fd < 0) { LOG(ERROR) << __func__ << ": unable to open dir '" << directoryname << "': " << strerror(errno); goto error2; } if (base::WriteFile(path, checksum.data(), checksum.size()) != (int)checksum.size()) { LOG(ERROR) << __func__ << ": unable to write file '" << filename.c_str(); goto error2; } fp = fopen(temp_filename.c_str(), "rb"); if (!fp) { LOG(ERROR) << __func__ << ": unable to write to file '" << temp_filename << "': " << strerror(errno); goto error2; } // Sync written temp file out to disk. fsync() is blocking until data makes it // to disk. if (fsync(fileno(fp)) < 0) { LOG(WARNING) << __func__ << ": unable to fsync file '" << temp_filename << "': " << strerror(errno); } if (fclose(fp) == EOF) { LOG(ERROR) << __func__ << ": unable to close file '" << temp_filename << "': " << strerror(errno); goto error2; std::promise<bool> promise; auto future = promise.get_future(); GetStorage()->ChecksumWrite(filename, checksum, [&promise](bool success) { promise.set_value(success); }); return future.get(); } fp = nullptr; // Change the file's permissions to Read/Write by User and Group if (chmod(temp_filename.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) { LOG(ERROR) << __func__ << ": unable to change file permissions '" << filename << "': " << strerror(errno); goto error2; } // Rename written temp file to the actual config file. if (rename(temp_filename.c_str(), filename.c_str()) == -1) { LOG(ERROR) << __func__ << ": unable to commit file '" << filename << "': " << strerror(errno); goto error2; } std::unique_ptr<config_t> config_new(const char* filename) { CHECK(filename != nullptr); // This should ensure the directory is updated as well. if (fsync(dir_fd) < 0) { LOG(WARNING) << __func__ << ": unable to fsync dir '" << directoryname << "': " << strerror(errno); std::promise<std::unique_ptr<config_t>> promise; auto future = promise.get_future(); GetStorage()->ConfigRead(std::string(filename), [&promise](std::unique_ptr<config_t> config) { promise.set_value(std::move(config)); }); return future.get(); } if (close(dir_fd) < 0) { LOG(ERROR) << __func__ << ": unable to close dir '" << directoryname << "': " << strerror(errno); goto error2; bool config_save(const config_t& config, const std::string& filename) { std::promise<bool> promise; auto future = promise.get_future(); GetStorage()->ConfigWrite(filename, &config, [&promise](bool success) { promise.set_value(success); }); return future.get(); } return true; error2: // This indicates there is a write issue. Unlink as partial data is not // acceptable. unlink(temp_filename.c_str()); if (fp) fclose(fp); if (dir_fd != -1) close(dir_fd); return false; } } // namespace shim } // namespace bluetooth namespace { const storage_config_t interface = { checksum_read, checksum_save, config_get_bool, config_get_int, config_get_string, config_get_uint64, config_has_key, config_has_section, config_new, config_new_clone, config_new_empty, config_remove_key, config_remove_section, config_save, config_set_bool, config_set_int, config_set_string, config_set_uint64, bluetooth::shim::checksum_read, bluetooth::shim::checksum_save, config_get_bool, config_get_int, config_get_string, config_get_uint64, config_has_key, config_has_section, bluetooth::shim::config_new, config_new_clone, config_new_empty, config_remove_key, config_remove_section, bluetooth::shim::config_save, config_set_bool, config_set_int, config_set_string, config_set_uint64, }; } // namespace shim } // namespace } const storage_config_t* bluetooth::shim::storage_config_get_interface() { return &::shim::interface; return &interface; }
system/main/shim/entry.cc +4 −0 Original line number Diff line number Diff line Loading @@ -73,3 +73,7 @@ bluetooth::shim::IScanning* bluetooth::shim::GetScanning() { bluetooth::shim::ISecurity* bluetooth::shim::GetSecurity() { return GetGabeldorscheStack()->GetSecurity(); } bluetooth::shim::IStorage* bluetooth::shim::GetStorage() { return GetGabeldorscheStack()->GetStorage(); }
system/main/shim/entry.h +1 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ bluetooth::shim::IName* GetName(); bluetooth::shim::IPage* GetPage(); bluetooth::shim::IScanning* GetScanning(); bluetooth::shim::ISecurity* GetSecurity(); bluetooth::shim::IStorage* GetStorage(); } // namespace shim } // namespace bluetooth