1 #include "persistent_json_storage.hpp"
2 
3 #include <phosphor-logging/log.hpp>
4 
5 #include <fstream>
6 #include <stdexcept>
7 
8 using namespace std::literals::string_literals;
9 
PersistentJsonStorage(const DirectoryPath & directory)10 PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) :
11     directory(directory)
12 {}
13 
store(const FilePath & filePath,const nlohmann::json & data)14 void PersistentJsonStorage::store(const FilePath& filePath,
15                                   const nlohmann::json& data)
16 {
17     try
18     {
19         const auto path = join(directory, filePath);
20         std::error_code ec;
21 
22         phosphor::logging::log<phosphor::logging::level::DEBUG>(
23             "Store to file", phosphor::logging::entry("PATH=%s", path.c_str()));
24 
25         std::filesystem::create_directories(path.parent_path(), ec);
26         if (ec)
27         {
28             throw std::runtime_error(
29                 "Unable to create directory for file: " + path.string() +
30                 ", ec=" + std::to_string(ec.value()) + ": " + ec.message());
31         }
32 
33         assertThatPathIsNotSymlink(path);
34 
35         std::ofstream file(path);
36         file << data;
37         if (!file)
38         {
39             throw std::runtime_error("Unable to create file: " + path.string());
40         }
41 
42         limitPermissions(path.parent_path());
43         limitPermissions(path);
44     }
45     catch (...)
46     {
47         remove(filePath);
48         throw;
49     }
50 }
51 
remove(const FilePath & filePath)52 bool PersistentJsonStorage::remove(const FilePath& filePath)
53 {
54     const auto path = join(directory, filePath);
55 
56     if (std::filesystem::is_symlink(path))
57     {
58         return false;
59     }
60 
61     std::error_code ec;
62 
63     auto removed = std::filesystem::remove(path, ec);
64     if (!removed)
65     {
66         phosphor::logging::log<phosphor::logging::level::ERR>(
67             "Unable to remove file",
68             phosphor::logging::entry("PATH=%s", path.c_str()),
69             phosphor::logging::entry("ERROR_CODE=%d", ec.value()));
70         return false;
71     }
72 
73     /* removes directory only if it is empty */
74     std::filesystem::remove(path.parent_path(), ec);
75 
76     return true;
77 }
78 
79 std::optional<nlohmann::json>
load(const FilePath & filePath) const80     PersistentJsonStorage::load(const FilePath& filePath) const
81 {
82     const auto path = join(directory, filePath);
83     if (!std::filesystem::exists(path))
84     {
85         return std::nullopt;
86     }
87 
88     nlohmann::json result;
89 
90     try
91     {
92         assertThatPathIsNotSymlink(path);
93         std::ifstream file(path);
94         file >> result;
95     }
96     catch (const std::exception& e)
97     {
98         phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
99         return std::nullopt;
100     }
101 
102     return result;
103 }
104 
105 std::vector<interfaces::JsonStorage::FilePath>
list() const106     PersistentJsonStorage::list() const
107 {
108     std::vector<interfaces::JsonStorage::FilePath> result;
109     if (!std::filesystem::exists(directory))
110     {
111         return result;
112     }
113 
114     for (const auto& p :
115          std::filesystem::recursive_directory_iterator(directory))
116     {
117         if (p.is_regular_file() && !p.is_symlink())
118         {
119             auto item = std::filesystem::relative(p.path(), directory);
120             result.emplace_back(std::move(item));
121         }
122     }
123 
124     return result;
125 }
126 
127 std::filesystem::path
join(const std::filesystem::path & left,const std::filesystem::path & right)128     PersistentJsonStorage::join(const std::filesystem::path& left,
129                                 const std::filesystem::path& right)
130 {
131     return left / right;
132 }
133 
limitPermissions(const std::filesystem::path & path)134 void PersistentJsonStorage::limitPermissions(const std::filesystem::path& path)
135 {
136     constexpr auto filePerms = std::filesystem::perms::owner_read |
137                                std::filesystem::perms::owner_write;
138     constexpr auto dirPerms = filePerms | std::filesystem::perms::owner_exec;
139     std::filesystem::permissions(
140         path, std::filesystem::is_directory(path) ? dirPerms : filePerms,
141         std::filesystem::perm_options::replace);
142 }
143 
exist(const FilePath & subPath) const144 bool PersistentJsonStorage::exist(const FilePath& subPath) const
145 {
146     return std::filesystem::exists(join(directory, subPath));
147 }
148 
assertThatPathIsNotSymlink(const std::filesystem::path & path)149 void PersistentJsonStorage::assertThatPathIsNotSymlink(
150     const std::filesystem::path& path)
151 {
152     if (std::filesystem::is_symlink(path))
153     {
154         throw std::runtime_error(
155             "Source/Target file is a symlink! Operation canceled on path "s +
156             path.c_str());
157     }
158 }
159