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 = 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 
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 
98 std::optional<nlohmann::json>
load(const FilePath & filePath) const99     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     {
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 
124 std::vector<interfaces::JsonStorage::FilePath>
list() const125     PersistentJsonStorage::list() 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 
146 std::filesystem::path
join(const std::filesystem::path & left,const std::filesystem::path & right)147     PersistentJsonStorage::join(const std::filesystem::path& left,
148                                 const std::filesystem::path& right)
149 {
150     return left / right;
151 }
152 
limitPermissions(const std::filesystem::path & path)153 void 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 }
162 
exist(const FilePath & subPath) const163 bool PersistentJsonStorage::exist(const FilePath& subPath) const
164 {
165     return std::filesystem::exists(join(directory, subPath));
166 }
167 
assertThatPathIsNotSymlink(const std::filesystem::path & path)168 void PersistentJsonStorage::assertThatPathIsNotSymlink(
169     const std::filesystem::path& path)
170 {
171     if (isAnySymlink(path))
172     {
173         throw std::runtime_error(
174             "Source/Target file is a symlink! Operation canceled on path "s +
175             path.c_str());
176     }
177 }
178