xref: /openbmc/telemetry/src/persistent_json_storage.cpp (revision 583ba441654657bb4ba9d051b747144a7258c159)
1 #include "persistent_json_storage.hpp"
2 
3 #include <phosphor-logging/log.hpp>
4 
5 #include <format>
6 #include <fstream>
7 #include <stdexcept>
8 
9 using namespace std::literals::string_literals;
10 
isAnySymlink(const std::filesystem::path & path)11 bool 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 =
19                 std::format("{} is a symlink", 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 
PersistentJsonStorage(const DirectoryPath & directory)29 PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) :
30     directory(directory)
31 {}
32 
store(const FilePath & filePath,const nlohmann::json & data)33 void 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>(
42             "Store to file", phosphor::logging::entry("PATH=%s", path.c_str()));
43 
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 
52         assertThatPathIsNotSymlink(path);
53 
54         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 
remove(const FilePath & filePath)71 bool PersistentJsonStorage::remove(const FilePath& filePath)
72 {
73     const auto path = join(directory, filePath);
74 
75     if (isAnySymlink(path))
76     {
77         return false;
78     }
79 
80     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",
87             phosphor::logging::entry("PATH=%s", path.c_str()),
88             phosphor::logging::entry("ERROR_CODE=%d", ec.value()));
89         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 
load(const FilePath & filePath) const98 std::optional<nlohmann::json> PersistentJsonStorage::load(
99     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     {
111         assertThatPathIsNotSymlink(path);
112         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 
list() const124 std::vector<interfaces::JsonStorage::FilePath> PersistentJsonStorage::list()
125     const
126 {
127     std::vector<interfaces::JsonStorage::FilePath> result;
128     if (!std::filesystem::exists(directory))
129     {
130         return result;
131     }
132 
133     for (const auto& p :
134          std::filesystem::recursive_directory_iterator(directory))
135     {
136         if (p.is_regular_file() && !isAnySymlink(p.path()))
137         {
138             auto item = std::filesystem::relative(p.path(), directory);
139             result.emplace_back(std::move(item));
140         }
141     }
142 
143     return result;
144 }
145 
join(const std::filesystem::path & left,const std::filesystem::path & right)146 std::filesystem::path PersistentJsonStorage::join(
147     const std::filesystem::path& left, const std::filesystem::path& right)
148 {
149     return left / right;
150 }
151 
limitPermissions(const std::filesystem::path & path)152 void PersistentJsonStorage::limitPermissions(const std::filesystem::path& path)
153 {
154     constexpr auto filePerms = std::filesystem::perms::owner_read |
155                                std::filesystem::perms::owner_write;
156     constexpr auto dirPerms = filePerms | std::filesystem::perms::owner_exec;
157     std::filesystem::permissions(
158         path, std::filesystem::is_directory(path) ? dirPerms : filePerms,
159         std::filesystem::perm_options::replace);
160 }
161 
exist(const FilePath & subPath) const162 bool PersistentJsonStorage::exist(const FilePath& subPath) const
163 {
164     return std::filesystem::exists(join(directory, subPath));
165 }
166 
assertThatPathIsNotSymlink(const std::filesystem::path & path)167 void PersistentJsonStorage::assertThatPathIsNotSymlink(
168     const std::filesystem::path& path)
169 {
170     if (isAnySymlink(path))
171     {
172         throw std::runtime_error(
173             "Source/Target file is a symlink! Operation canceled on path "s +
174             path.c_str());
175     }
176 }
177