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

Commit 7085f706 authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "GD-storage: Add LegacyConfigFile"

parents c654f2d3 144e5031
Loading
Loading
Loading
Loading
+10 −11
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@
#include <sys/stat.h>
#include <unistd.h>

#include <cctype>
#include <cerrno>
#include <cstring>
#include <fstream>
@@ -108,27 +107,27 @@ bool WriteToFile(const std::string& path, const std::string& data) {
    return false;
  }

  int dir_fd = open(directory_path.c_str(), O_RDONLY);
  int dir_fd = open(directory_path.c_str(), O_RDONLY | O_DIRECTORY);
  if (dir_fd < 0) {
    LOG_ERROR("unable to open dir '%s', error: %s", directory_path.c_str(), strerror(errno));
    return false;
  }

  FILE* fp = fopen(temp_path.c_str(), "wt");
  FILE* fp = std::fopen(temp_path.c_str(), "wt");
  if (!fp) {
    LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  if (fprintf(fp, "%s", data.c_str()) < 0) {
  if (std::fprintf(fp, "%s", data.c_str()) < 0) {
    LOG_ERROR("unable to write to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // Flush the stream buffer to the temp file.
  if (fflush(fp) < 0) {
  if (std::fflush(fp) != 0) {
    LOG_ERROR("unable to write flush buffer to file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
@@ -136,12 +135,12 @@ bool WriteToFile(const std::string& path, const std::string& data) {

  // Sync written temp file out to disk. fsync() is blocking until data makes it
  // to disk.
  if (fsync(fileno(fp)) < 0) {
  if (fsync(fileno(fp)) != 0) {
    LOG_WARN("unable to fsync file '%s', error: %s", temp_path.c_str(), strerror(errno));
    // Allow fsync to fail and continue
  }

  if (fclose(fp) != 0) {
  if (std::fclose(fp) != 0) {
    LOG_ERROR("unable to close file '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
@@ -149,25 +148,25 @@ bool WriteToFile(const std::string& path, const std::string& data) {
  fp = nullptr;

  // Change the file's permissions to Read/Write by User and Group
  if (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) == -1) {
  if (chmod(temp_path.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) != 0) {
    LOG_ERROR("unable to change file permissions '%s', error: %s", temp_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // Rename written temp file to the actual config file.
  if (rename(temp_path.c_str(), path.c_str()) == -1) {
  if (std::rename(temp_path.c_str(), path.c_str()) == -1) {
    LOG_ERROR("unable to commit file from '%s' to '%s', error: %s", temp_path.c_str(), path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
  }

  // This should ensure the directory is updated as well.
  if (fsync(dir_fd) < 0) {
  if (fsync(dir_fd) != 0) {
    LOG_WARN("unable to fsync dir '%s', error: %s", directory_path.c_str(), strerror(errno));
  }

  if (close(dir_fd) < 0) {
  if (close(dir_fd) != 0) {
    LOG_ERROR("unable to close dir '%s', error: %s", directory_path.c_str(), strerror(errno));
    HandleError(temp_path, &dir_fd, &fp);
    return false;
+3 −4
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <cstdio>
#include <filesystem>

namespace testing {
@@ -33,7 +32,7 @@ TEST(FilesTest, write_read_loopback_test) {
  std::string text = "Hello world!\n";
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
  EXPECT_TRUE(std::filesystem::remove(temp_file));
}

TEST(FilesTest, overwrite_test) {
@@ -45,7 +44,7 @@ TEST(FilesTest, overwrite_test) {
  text = "Foo bar!\n";
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
  EXPECT_TRUE(std::filesystem::remove(temp_file));
}

TEST(FilesTest, write_read_empty_string_test) {
@@ -54,7 +53,7 @@ TEST(FilesTest, write_read_empty_string_test) {
  std::string text;
  ASSERT_TRUE(WriteToFile(temp_file.string(), text));
  EXPECT_THAT(ReadSmallFile(temp_file.string()), Optional(StrEq(text)));
  EXPECT_EQ(remove(temp_file.c_str()), 0);
  EXPECT_TRUE(std::filesystem::remove(temp_file));
}

TEST(FilesTest, read_non_existing_file_test) {
+2 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ filegroup {
            "config_cache.cc",
            "config_cache_helper.cc",
            "legacy.cc",
            "legacy_config_file.cc",
            "legacy_osi_config.cc",
            "mutation_entry.cc",
    ],
@@ -14,6 +15,7 @@ filegroup {
    srcs: [
            "config_cache_test.cc",
            "config_cache_helper_test.cc",
            "legacy_config_file_test.cc",
            "legacy_test.cc",
            "legacy_osi_config.cc",
            "mutation_test.cc",
+93 −13
Original line number Diff line number Diff line
@@ -17,6 +17,8 @@
#include "storage/config_cache.h"

#include <ios>
#include <sstream>
#include <utility>

#include "storage/mutation.h"

@@ -40,29 +42,64 @@ namespace storage {
const std::unordered_set<std::string_view> kLinkKeyPropertyNames = {
    "LinkKey", "LE_KEY_PENC", "LE_KEY_PID", "LE_KEY_PCSRK", "LE_KEY_LENC", "LE_KEY_LCSRK"};

const std::string ConfigCache::kDefaultSectionName = "Global";

ConfigCache::ConfigCache(size_t temp_device_capacity)
    : information_sections_(), persistent_devices_(), temporary_devices_(temp_device_capacity) {}

void ConfigCache::SetPersistentConfigChangedCallback(std::function<void()> persistent_config_changed_callback) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  persistent_config_changed_callback_ = std::move(persistent_config_changed_callback);
}

ConfigCache::ConfigCache(ConfigCache&& other) noexcept
    : information_sections_(std::move(other.information_sections_)),
    : persistent_config_changed_callback_(std::move(other.persistent_config_changed_callback_)),
      information_sections_(std::move(other.information_sections_)),
      persistent_devices_(std::move(other.persistent_devices_)),
      temporary_devices_(std::move(other.temporary_devices_)) {}
      temporary_devices_(std::move(other.temporary_devices_)) {
  // std::function will be in a valid but unspecified state after std::move(), hence resetting it
  other.persistent_config_changed_callback_ = {};
}

ConfigCache& ConfigCache::operator=(ConfigCache&& other) noexcept {
  if (&other == this) {
    return *this;
  }
  std::lock_guard<std::recursive_mutex> my_lock(mutex_);
  std::lock_guard<std::recursive_mutex> others_lock(other.mutex_);
  persistent_config_changed_callback_.swap(other.persistent_config_changed_callback_);
  other.persistent_config_changed_callback_ = {};
  information_sections_ = std::move(other.information_sections_);
  persistent_devices_ = std::move(other.persistent_devices_);
  temporary_devices_ = std::move(other.temporary_devices_);
  return *this;
}

bool ConfigCache::operator==(const ConfigCache& rhs) const {
  std::lock_guard<std::recursive_mutex> my_lock(mutex_);
  std::lock_guard<std::recursive_mutex> others_lock(rhs.mutex_);
  return information_sections_ == rhs.information_sections_ && persistent_devices_ == rhs.persistent_devices_ &&
         temporary_devices_ == rhs.temporary_devices_;
}

bool ConfigCache::operator!=(const ConfigCache& rhs) const {
  return !(*this == rhs);
}

void ConfigCache::Clear() {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  if (information_sections_.size() > 0) {
    information_sections_.clear();
    PersistentConfigChangedCallback();
  }
  if (persistent_devices_.size() > 0) {
    persistent_devices_.clear();
    PersistentConfigChangedCallback();
  }
  if (temporary_devices_.size() > 0) {
    temporary_devices_.clear();
  }
}

bool ConfigCache::HasSection(const std::string& section) const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
@@ -124,6 +161,7 @@ void ConfigCache::SetProperty(std::string section, std::string property, std::st
      section_iter = information_sections_.try_emplace_back(section, common::ListMap<std::string, std::string>{}).first;
    }
    section_iter->second.insert_or_assign(property, std::move(value));
    PersistentConfigChangedCallback();
    return;
  }
  auto section_iter = persistent_devices_.find(section);
@@ -138,6 +176,7 @@ void ConfigCache::SetProperty(std::string section, std::string property, std::st
  }
  if (section_iter != persistent_devices_.end()) {
    section_iter->second.insert_or_assign(property, std::move(value));
    PersistentConfigChangedCallback();
    return;
  }
  section_iter = temporary_devices_.find(section);
@@ -151,8 +190,12 @@ void ConfigCache::SetProperty(std::string section, std::string property, std::st
bool ConfigCache::RemoveSection(const std::string& section) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  // sections are unique among all three maps, hence removing from one of them is enough
  return information_sections_.extract(section) || persistent_devices_.extract(section) ||
         temporary_devices_.extract(section);
  if (information_sections_.extract(section) || persistent_devices_.extract(section)) {
    PersistentConfigChangedCallback();
    return true;
  } else {
    return temporary_devices_.extract(section).has_value();
  }
}

bool ConfigCache::RemoveProperty(const std::string& section, const std::string& property) {
@@ -169,7 +212,12 @@ bool ConfigCache::RemoveProperty(const std::string& section, const std::string&
      auto section_properties = persistent_devices_.extract(section);
      temporary_devices_.insert_or_assign(section, std::move(section_properties->second));
    }
    return value.has_value();
    if (value.has_value()) {
      PersistentConfigChangedCallback();
      return true;
    } else {
      return false;
    }
  }
  section_iter = temporary_devices_.find(section);
  if (section_iter != temporary_devices_.end()) {
@@ -186,13 +234,30 @@ bool ConfigCache::IsLinkKeyProperty(const std::string& property) {
  return kLinkKeyPropertyNames.find(property) != kLinkKeyPropertyNames.end();
}

void ConfigCache::RemoveRestricted() {
void ConfigCache::RemoveSectionWithProperty(const std::string& property) {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  for (auto& section : persistent_devices_) {
    if (section.second.contains("Restricted")) {
      LOG_DEBUG("Removing restricted device %s", section.first.c_str());
      persistent_devices_.extract(section.first);
  size_t num_persistent_removed = 0;
  for (auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (auto it = config_section->begin(); it != config_section->end();) {
      if (it->second.contains(property)) {
        LOG_DEBUG("Removing persistent section %s with property %s", it->first.c_str(), property.c_str());
        it = config_section->erase(it);
        num_persistent_removed++;
        continue;
      }
      it++;
    }
  }
  for (auto it = temporary_devices_.begin(); it != temporary_devices_.end();) {
    if (it->second.contains(property)) {
      LOG_DEBUG("Removing temporary section %s with property %s", it->first.c_str(), property.c_str());
      it = temporary_devices_.erase(it);
      continue;
    }
    it++;
  }
  if (num_persistent_removed > 0) {
    PersistentConfigChangedCallback();
  }
}

@@ -224,5 +289,20 @@ void ConfigCache::Commit(Mutation& mutation) {
  }
}

std::string ConfigCache::SerializeToLegacyFormat() const {
  std::lock_guard<std::recursive_mutex> lock(mutex_);
  std::stringstream serialized;
  for (const auto* config_section : {&information_sections_, &persistent_devices_}) {
    for (const auto& section : *config_section) {
      serialized << "[" << section.first << "]" << std::endl;
      for (const auto& property : section.second) {
        serialized << property.first << " = " << property.second << std::endl;
      }
      serialized << std::endl;
    }
  }
  return serialized.str();
}

}  // namespace storage
}  // namespace bluetooth
 No newline at end of file
+20 −11
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
#pragma once

#include <functional>
#include <list>
#include <mutex>
#include <optional>
@@ -49,14 +50,9 @@ class ConfigCache {
  ConfigCache(ConfigCache&& other) noexcept;
  ConfigCache& operator=(ConfigCache&& other) noexcept;

  // comparison operators
  bool operator==(const ConfigCache& rhs) const {
    return information_sections_ == rhs.information_sections_ && persistent_devices_ == rhs.persistent_devices_ &&
           temporary_devices_ == rhs.temporary_devices_;
  }
  bool operator!=(const ConfigCache& rhs) const {
    return !(*this == rhs);
  }
  // comparison operators, callback doesn't count
  bool operator==(const ConfigCache& rhs) const;
  bool operator!=(const ConfigCache& rhs) const;

  // observers
  virtual bool HasSection(const std::string& section) const;
@@ -65,6 +61,8 @@ class ConfigCache {
  virtual std::optional<std::string> GetProperty(const std::string& section, const std::string& property) const;
  // Returns a copy of persistent device MAC addresses
  virtual std::vector<std::string> GetPersistentDevices() const;
  // Serialize to legacy config format
  virtual std::string SerializeToLegacyFormat() const;

  // modifiers
  // Commit all mutation entries in sequence while holding the config mutex
@@ -73,10 +71,12 @@ class ConfigCache {
  virtual bool RemoveSection(const std::string& section);
  virtual bool RemoveProperty(const std::string& section, const std::string& property);
  // TODO: have a systematic way of doing this instead of specialized methods
  // Remove devices with "Restricted" property
  virtual void RemoveRestricted();
  // Remove sections with |property| set
  virtual void RemoveSectionWithProperty(const std::string& property);
  // remove all content in this config cache, restore it to the state after the explicit constructor
  virtual void Clear();
  // Set a callback to notify interested party that a persistent config change has just happened
  virtual void SetPersistentConfigChangedCallback(std::function<void()> persistent_config_changed_callback);

  // static methods
  // Check if section is formatted as a MAC address
@@ -85,10 +85,12 @@ class ConfigCache {
  static bool IsLinkKeyProperty(const std::string& property);

  // constants
  static constexpr std::string_view kDefaultSectionName = "Global";
  static const std::string kDefaultSectionName;

 private:
  mutable std::recursive_mutex mutex_;
  // A callback to notify interested party that a persistent config change has just happened, empty by default
  std::function<void()> persistent_config_changed_callback_;
  // Common section that does not relate to remote device, will be written to disk
  common::ListMap<std::string, common::ListMap<std::string, std::string>> information_sections_;
  // Information about persistent devices, normally paired, will be written to disk
@@ -96,6 +98,13 @@ class ConfigCache {
  // Information about temporary devices, normally unpaired, will not be written to disk, will be evicted automatically
  // if capacity exceeds given value during initialization
  common::LruCache<std::string, common::ListMap<std::string, std::string>> temporary_devices_;

  // Convenience method to check if the callback is valid before calling it
  inline void PersistentConfigChangedCallback() const {
    if (persistent_config_changed_callback_) {
      persistent_config_changed_callback_();
    }
  }
};

}  // namespace storage
Loading