blob: 97326fe20de3164f43f3697159b2b8b330989d6f [file] [log] [blame]
Krzysztof Grobelny73da6902020-09-24 13:42:04 +02001#include "persistent_json_storage.hpp"
2
3#include <phosphor-logging/log.hpp>
4
Szymon Dompke4d1c2ce2023-01-23 17:28:14 +01005#include <format>
Krzysztof Grobelny73da6902020-09-24 13:42:04 +02006#include <fstream>
7#include <stdexcept>
8
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +00009using namespace std::literals::string_literals;
10
Szymon Dompke4d1c2ce2023-01-23 17:28:14 +010011bool isAnySymlink(const std::filesystem::path& path)
12{
13 auto currentPath = path;
14 while (currentPath != path.root_path())
15 {
16 if (std::filesystem::is_symlink(currentPath))
17 {
18 std::string warningStr = std::format("{} is a symlink",
19 currentPath.string());
20 phosphor::logging::log<phosphor::logging::level::WARNING>(
21 warningStr.c_str());
22 return true;
23 }
24 currentPath = currentPath.parent_path();
25 }
26 return false;
27}
28
Krzysztof Grobelny73da6902020-09-24 13:42:04 +020029PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) :
30 directory(directory)
31{}
32
33void PersistentJsonStorage::store(const FilePath& filePath,
34 const nlohmann::json& data)
35{
36 try
37 {
38 const auto path = join(directory, filePath);
39 std::error_code ec;
40
41 phosphor::logging::log<phosphor::logging::level::DEBUG>(
Wludzik, Jozef982c5b52021-01-02 12:05:21 +010042 "Store to file", phosphor::logging::entry("PATH=%s", path.c_str()));
Krzysztof Grobelny73da6902020-09-24 13:42:04 +020043
44 std::filesystem::create_directories(path.parent_path(), ec);
45 if (ec)
46 {
47 throw std::runtime_error(
48 "Unable to create directory for file: " + path.string() +
49 ", ec=" + std::to_string(ec.value()) + ": " + ec.message());
50 }
51
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +000052 assertThatPathIsNotSymlink(path);
53
Krzysztof Grobelny73da6902020-09-24 13:42:04 +020054 std::ofstream file(path);
55 file << data;
56 if (!file)
57 {
58 throw std::runtime_error("Unable to create file: " + path.string());
59 }
60
61 limitPermissions(path.parent_path());
62 limitPermissions(path);
63 }
64 catch (...)
65 {
66 remove(filePath);
67 throw;
68 }
69}
70
71bool PersistentJsonStorage::remove(const FilePath& filePath)
72{
73 const auto path = join(directory, filePath);
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +000074
Szymon Dompke4d1c2ce2023-01-23 17:28:14 +010075 if (isAnySymlink(path))
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +000076 {
77 return false;
78 }
79
Krzysztof Grobelny73da6902020-09-24 13:42:04 +020080 std::error_code ec;
81
82 auto removed = std::filesystem::remove(path, ec);
83 if (!removed)
84 {
85 phosphor::logging::log<phosphor::logging::level::ERR>(
86 "Unable to remove file",
Wludzik, Jozef982c5b52021-01-02 12:05:21 +010087 phosphor::logging::entry("PATH=%s", path.c_str()),
88 phosphor::logging::entry("ERROR_CODE=%d", ec.value()));
Krzysztof Grobelny73da6902020-09-24 13:42:04 +020089 return false;
90 }
91
92 /* removes directory only if it is empty */
93 std::filesystem::remove(path.parent_path(), ec);
94
95 return true;
96}
97
98std::optional<nlohmann::json>
99 PersistentJsonStorage::load(const FilePath& filePath) const
100{
101 const auto path = join(directory, filePath);
102 if (!std::filesystem::exists(path))
103 {
104 return std::nullopt;
105 }
106
107 nlohmann::json result;
108
109 try
110 {
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +0000111 assertThatPathIsNotSymlink(path);
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200112 std::ifstream file(path);
113 file >> result;
114 }
115 catch (const std::exception& e)
116 {
117 phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
118 return std::nullopt;
119 }
120
121 return result;
122}
123
124std::vector<interfaces::JsonStorage::FilePath>
Wludzik, Jozefe2362792020-10-27 17:23:55 +0100125 PersistentJsonStorage::list() const
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200126{
Wludzik, Jozefe2362792020-10-27 17:23:55 +0100127 std::vector<interfaces::JsonStorage::FilePath> result;
128 if (!std::filesystem::exists(directory))
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200129 {
130 return result;
131 }
132
Wludzik, Jozefe2362792020-10-27 17:23:55 +0100133 for (const auto& p :
134 std::filesystem::recursive_directory_iterator(directory))
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200135 {
Szymon Dompke4d1c2ce2023-01-23 17:28:14 +0100136 if (p.is_regular_file() && !isAnySymlink(p.path()))
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200137 {
Wludzik, Jozefe2362792020-10-27 17:23:55 +0100138 auto item = std::filesystem::relative(p.path(), directory);
139 result.emplace_back(std::move(item));
Krzysztof Grobelny73da6902020-09-24 13:42:04 +0200140 }
141 }
142
143 return result;
144}
145
146std::filesystem::path
147 PersistentJsonStorage::join(const std::filesystem::path& left,
148 const std::filesystem::path& right)
149{
150 return left / right;
151}
152
153void PersistentJsonStorage::limitPermissions(const std::filesystem::path& path)
154{
155 constexpr auto filePerms = std::filesystem::perms::owner_read |
156 std::filesystem::perms::owner_write;
157 constexpr auto dirPerms = filePerms | std::filesystem::perms::owner_exec;
158 std::filesystem::permissions(
159 path, std::filesystem::is_directory(path) ? dirPerms : filePerms,
160 std::filesystem::perm_options::replace);
161}
Wludzik, Jozefe2362792020-10-27 17:23:55 +0100162
163bool PersistentJsonStorage::exist(const FilePath& subPath) const
164{
165 return std::filesystem::exists(join(directory, subPath));
166}
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +0000167
168void PersistentJsonStorage::assertThatPathIsNotSymlink(
169 const std::filesystem::path& path)
170{
Szymon Dompke4d1c2ce2023-01-23 17:28:14 +0100171 if (isAnySymlink(path))
Krzysztof Grobelnya06626d2022-11-24 15:22:40 +0000172 {
173 throw std::runtime_error(
174 "Source/Target file is a symlink! Operation canceled on path "s +
175 path.c_str());
176 }
177}