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 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 29 PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) : 30 directory(directory) 31 {} 32 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 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> 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 { 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> 125 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 147 PersistentJsonStorage::join(const std::filesystem::path& left, 148 const std::filesystem::path& right) 149 { 150 return left / right; 151 } 152 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 163 bool PersistentJsonStorage::exist(const FilePath& subPath) const 164 { 165 return std::filesystem::exists(join(directory, subPath)); 166 } 167 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