Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c9abb347 authored by Chris Manton's avatar Chris Manton
Browse files

gd: Initial entry for main legacy storage shim

Bug: 147315979
Test: bluetooth_test_gd

Change-Id: Iaa097f78e5d9406b23acbc92bd122ad17904c1aa
parent 50294fde
Loading
Loading
Loading
Loading
+59 −461
Original line number Diff line number Diff line
@@ -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(),
      [&section](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;
}
+4 −0
Original line number Diff line number Diff line
@@ -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();
}
+1 −0
Original line number Diff line number Diff line
@@ -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