blob: 97326fe20de3164f43f3697159b2b8b330989d6f [file] [log] [blame]
#include "persistent_json_storage.hpp"
#include <phosphor-logging/log.hpp>
#include <format>
#include <fstream>
#include <stdexcept>
using namespace std::literals::string_literals;
bool isAnySymlink(const std::filesystem::path& path)
{
auto currentPath = path;
while (currentPath != path.root_path())
{
if (std::filesystem::is_symlink(currentPath))
{
std::string warningStr = std::format("{} is a symlink",
currentPath.string());
phosphor::logging::log<phosphor::logging::level::WARNING>(
warningStr.c_str());
return true;
}
currentPath = currentPath.parent_path();
}
return false;
}
PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) :
directory(directory)
{}
void PersistentJsonStorage::store(const FilePath& filePath,
const nlohmann::json& data)
{
try
{
const auto path = join(directory, filePath);
std::error_code ec;
phosphor::logging::log<phosphor::logging::level::DEBUG>(
"Store to file", phosphor::logging::entry("PATH=%s", path.c_str()));
std::filesystem::create_directories(path.parent_path(), ec);
if (ec)
{
throw std::runtime_error(
"Unable to create directory for file: " + path.string() +
", ec=" + std::to_string(ec.value()) + ": " + ec.message());
}
assertThatPathIsNotSymlink(path);
std::ofstream file(path);
file << data;
if (!file)
{
throw std::runtime_error("Unable to create file: " + path.string());
}
limitPermissions(path.parent_path());
limitPermissions(path);
}
catch (...)
{
remove(filePath);
throw;
}
}
bool PersistentJsonStorage::remove(const FilePath& filePath)
{
const auto path = join(directory, filePath);
if (isAnySymlink(path))
{
return false;
}
std::error_code ec;
auto removed = std::filesystem::remove(path, ec);
if (!removed)
{
phosphor::logging::log<phosphor::logging::level::ERR>(
"Unable to remove file",
phosphor::logging::entry("PATH=%s", path.c_str()),
phosphor::logging::entry("ERROR_CODE=%d", ec.value()));
return false;
}
/* removes directory only if it is empty */
std::filesystem::remove(path.parent_path(), ec);
return true;
}
std::optional<nlohmann::json>
PersistentJsonStorage::load(const FilePath& filePath) const
{
const auto path = join(directory, filePath);
if (!std::filesystem::exists(path))
{
return std::nullopt;
}
nlohmann::json result;
try
{
assertThatPathIsNotSymlink(path);
std::ifstream file(path);
file >> result;
}
catch (const std::exception& e)
{
phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
return std::nullopt;
}
return result;
}
std::vector<interfaces::JsonStorage::FilePath>
PersistentJsonStorage::list() const
{
std::vector<interfaces::JsonStorage::FilePath> result;
if (!std::filesystem::exists(directory))
{
return result;
}
for (const auto& p :
std::filesystem::recursive_directory_iterator(directory))
{
if (p.is_regular_file() && !isAnySymlink(p.path()))
{
auto item = std::filesystem::relative(p.path(), directory);
result.emplace_back(std::move(item));
}
}
return result;
}
std::filesystem::path
PersistentJsonStorage::join(const std::filesystem::path& left,
const std::filesystem::path& right)
{
return left / right;
}
void PersistentJsonStorage::limitPermissions(const std::filesystem::path& path)
{
constexpr auto filePerms = std::filesystem::perms::owner_read |
std::filesystem::perms::owner_write;
constexpr auto dirPerms = filePerms | std::filesystem::perms::owner_exec;
std::filesystem::permissions(
path, std::filesystem::is_directory(path) ? dirPerms : filePerms,
std::filesystem::perm_options::replace);
}
bool PersistentJsonStorage::exist(const FilePath& subPath) const
{
return std::filesystem::exists(join(directory, subPath));
}
void PersistentJsonStorage::assertThatPathIsNotSymlink(
const std::filesystem::path& path)
{
if (isAnySymlink(path))
{
throw std::runtime_error(
"Source/Target file is a symlink! Operation canceled on path "s +
path.c_str());
}
}