1 /** 2 * Copyright © 2019 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "repository.hpp" 17 18 #include <fstream> 19 #include <phosphor-logging/log.hpp> 20 #include <xyz/openbmc_project/Common/File/error.hpp> 21 22 namespace openpower 23 { 24 namespace pels 25 { 26 27 namespace fs = std::filesystem; 28 using namespace phosphor::logging; 29 namespace file_error = sdbusplus::xyz::openbmc_project::Common::File::Error; 30 31 Repository::Repository(const std::filesystem::path& basePath) : 32 _logPath(basePath / "logs") 33 { 34 if (!fs::exists(_logPath)) 35 { 36 fs::create_directories(_logPath); 37 } 38 39 restore(); 40 } 41 42 void Repository::restore() 43 { 44 for (auto& dirEntry : fs::directory_iterator(_logPath)) 45 { 46 try 47 { 48 if (!fs::is_regular_file(dirEntry.path())) 49 { 50 continue; 51 } 52 53 std::ifstream file{dirEntry.path()}; 54 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 55 std::istreambuf_iterator<char>()}; 56 file.close(); 57 58 PEL pel{data}; 59 if (pel.valid()) 60 { 61 // If the host hasn't acked it, reset the host state so 62 // it will get sent up again. 63 if (pel.hostTransmissionState() == TransmissionState::sent) 64 { 65 pel.setHostTransmissionState(TransmissionState::newPEL); 66 try 67 { 68 write(pel, dirEntry.path()); 69 } 70 catch (std::exception& e) 71 { 72 log<level::ERR>( 73 "Failed to save PEL after updating host state", 74 entry("PELID=0x%X", pel.id())); 75 } 76 } 77 78 PELAttributes attributes{ 79 dirEntry.path(), pel.userHeader().actionFlags(), 80 pel.hostTransmissionState(), pel.hmcTransmissionState()}; 81 82 using pelID = LogID::Pel; 83 using obmcID = LogID::Obmc; 84 _pelAttributes.emplace( 85 LogID(pelID(pel.id()), obmcID(pel.obmcLogID())), 86 attributes); 87 } 88 else 89 { 90 log<level::ERR>( 91 "Found invalid PEL file while restoring. Removing.", 92 entry("FILENAME=%s", dirEntry.path().c_str())); 93 fs::remove(dirEntry.path()); 94 } 95 } 96 catch (std::exception& e) 97 { 98 log<level::ERR>("Hit exception while restoring PEL File", 99 entry("FILENAME=%s", dirEntry.path().c_str()), 100 entry("ERROR=%s", e.what())); 101 } 102 } 103 } 104 105 std::string Repository::getPELFilename(uint32_t pelID, const BCDTime& time) 106 { 107 char name[50]; 108 sprintf(name, "%.2X%.2X%.2X%.2X%.2X%.2X%.2X%.2X_%.8X", time.yearMSB, 109 time.yearLSB, time.month, time.day, time.hour, time.minutes, 110 time.seconds, time.hundredths, pelID); 111 return std::string{name}; 112 } 113 114 void Repository::add(std::unique_ptr<PEL>& pel) 115 { 116 pel->setHostTransmissionState(TransmissionState::newPEL); 117 pel->setHMCTransmissionState(TransmissionState::newPEL); 118 119 auto path = _logPath / getPELFilename(pel->id(), pel->commitTime()); 120 121 write(*(pel.get()), path); 122 123 PELAttributes attributes{path, pel->userHeader().actionFlags(), 124 pel->hostTransmissionState(), 125 pel->hmcTransmissionState()}; 126 127 using pelID = LogID::Pel; 128 using obmcID = LogID::Obmc; 129 _pelAttributes.emplace(LogID(pelID(pel->id()), obmcID(pel->obmcLogID())), 130 attributes); 131 132 processAddCallbacks(*pel); 133 } 134 135 void Repository::write(const PEL& pel, const fs::path& path) 136 { 137 std::ofstream file{path, std::ios::binary}; 138 139 if (!file.good()) 140 { 141 // If this fails, the filesystem is probably full so it isn't like 142 // we could successfully create yet another error log here. 143 auto e = errno; 144 fs::remove(path); 145 log<level::ERR>("Unable to open PEL file for writing", 146 entry("ERRNO=%d", e), entry("PATH=%s", path.c_str())); 147 throw file_error::Open(); 148 } 149 150 auto data = pel.data(); 151 file.write(reinterpret_cast<const char*>(data.data()), data.size()); 152 153 if (file.fail()) 154 { 155 // Same note as above about not being able to create an error log 156 // for this case even if we wanted. 157 auto e = errno; 158 file.close(); 159 fs::remove(path); 160 log<level::ERR>("Unable to write PEL file", entry("ERRNO=%d", e), 161 entry("PATH=%s", path.c_str())); 162 throw file_error::Write(); 163 } 164 } 165 166 void Repository::remove(const LogID& id) 167 { 168 auto pel = findPEL(id); 169 if (pel != _pelAttributes.end()) 170 { 171 log<level::DEBUG>("Removing PEL from repository", 172 entry("PEL_ID=0x%X", pel->first.pelID.id), 173 entry("OBMC_LOG_ID=%d", pel->first.obmcID.id)); 174 fs::remove(pel->second.path); 175 _pelAttributes.erase(pel); 176 177 processDeleteCallbacks(pel->first.pelID.id); 178 } 179 else 180 { 181 log<level::DEBUG>("Could not find PEL to remove", 182 entry("PEL_ID=0x%X", id.pelID.id), 183 entry("OBMC_LOG_ID=%d", id.obmcID.id)); 184 } 185 } 186 187 std::optional<std::vector<uint8_t>> Repository::getPELData(const LogID& id) 188 { 189 auto pel = findPEL(id); 190 if (pel != _pelAttributes.end()) 191 { 192 std::ifstream file{pel->second.path.c_str()}; 193 if (!file.good()) 194 { 195 auto e = errno; 196 log<level::ERR>("Unable to open PEL file", entry("ERRNO=%d", e), 197 entry("PATH=%s", pel->second.path.c_str())); 198 throw file_error::Open(); 199 } 200 201 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 202 std::istreambuf_iterator<char>()}; 203 return data; 204 } 205 206 return std::nullopt; 207 } 208 209 std::optional<sdbusplus::message::unix_fd> Repository::getPELFD(const LogID& id) 210 { 211 auto pel = findPEL(id); 212 if (pel != _pelAttributes.end()) 213 { 214 FILE* fp = fopen(pel->second.path.c_str(), "rb"); 215 216 if (fp == nullptr) 217 { 218 auto e = errno; 219 log<level::ERR>("Unable to open PEL File", entry("ERRNO=%d", e), 220 entry("PATH=%s", pel->second.path.c_str())); 221 throw file_error::Open(); 222 } 223 224 // Must leave the file open here. It will be closed by sdbusplus 225 // when it sends it back over D-Bus. 226 227 return fileno(fp); 228 } 229 return std::nullopt; 230 } 231 232 void Repository::for_each(ForEachFunc func) const 233 { 234 for (const auto& [id, attributes] : _pelAttributes) 235 { 236 std::ifstream file{attributes.path}; 237 238 if (!file.good()) 239 { 240 auto e = errno; 241 log<level::ERR>("Repository::for_each: Unable to open PEL file", 242 entry("ERRNO=%d", e), 243 entry("PATH=%s", attributes.path.c_str())); 244 continue; 245 } 246 247 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 248 std::istreambuf_iterator<char>()}; 249 file.close(); 250 251 PEL pel{data}; 252 253 try 254 { 255 if (func(pel)) 256 { 257 break; 258 } 259 } 260 catch (std::exception& e) 261 { 262 log<level::ERR>("Repository::for_each function exception", 263 entry("ERROR=%s", e.what())); 264 } 265 } 266 } 267 268 void Repository::processAddCallbacks(const PEL& pel) const 269 { 270 for (auto& [name, func] : _addSubscriptions) 271 { 272 try 273 { 274 func(pel); 275 } 276 catch (std::exception& e) 277 { 278 log<level::ERR>("PEL Repository add callback exception", 279 entry("NAME=%s", name.c_str()), 280 entry("ERROR=%s", e.what())); 281 } 282 } 283 } 284 285 void Repository::processDeleteCallbacks(uint32_t id) const 286 { 287 for (auto& [name, func] : _deleteSubscriptions) 288 { 289 try 290 { 291 func(id); 292 } 293 catch (std::exception& e) 294 { 295 log<level::ERR>("PEL Repository delete callback exception", 296 entry("NAME=%s", name.c_str()), 297 entry("ERROR=%s", e.what())); 298 } 299 } 300 } 301 302 std::optional<std::reference_wrapper<const Repository::PELAttributes>> 303 Repository::getPELAttributes(const LogID& id) const 304 { 305 auto pel = findPEL(id); 306 if (pel != _pelAttributes.end()) 307 { 308 return pel->second; 309 } 310 311 return std::nullopt; 312 } 313 314 void Repository::setPELHostTransState(uint32_t pelID, TransmissionState state) 315 { 316 LogID id{LogID::Pel{pelID}}; 317 auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(), 318 [&id](const auto& a) { return a.first == id; }); 319 320 if ((attr != _pelAttributes.end()) && (attr->second.hostState != state)) 321 { 322 PELUpdateFunc func = [state](PEL& pel) { 323 pel.setHostTransmissionState(state); 324 }; 325 326 try 327 { 328 updatePEL(attr->second.path, func); 329 330 attr->second.hostState = state; 331 } 332 catch (std::exception& e) 333 { 334 log<level::ERR>("Unable to update PEL host transmission state", 335 entry("PATH=%s", attr->second.path.c_str()), 336 entry("ERROR=%s", e.what())); 337 } 338 } 339 } 340 341 void Repository::setPELHMCTransState(uint32_t pelID, TransmissionState state) 342 { 343 LogID id{LogID::Pel{pelID}}; 344 auto attr = std::find_if(_pelAttributes.begin(), _pelAttributes.end(), 345 [&id](const auto& a) { return a.first == id; }); 346 347 if ((attr != _pelAttributes.end()) && (attr->second.hmcState != state)) 348 { 349 PELUpdateFunc func = [state](PEL& pel) { 350 pel.setHMCTransmissionState(state); 351 }; 352 353 try 354 { 355 updatePEL(attr->second.path, func); 356 357 attr->second.hmcState = state; 358 } 359 catch (std::exception& e) 360 { 361 log<level::ERR>("Unable to update PEL HMC transmission state", 362 entry("PATH=%s", attr->second.path.c_str()), 363 entry("ERROR=%s", e.what())); 364 } 365 } 366 } 367 368 void Repository::updatePEL(const fs::path& path, PELUpdateFunc updateFunc) 369 { 370 std::ifstream file{path}; 371 std::vector<uint8_t> data{std::istreambuf_iterator<char>(file), 372 std::istreambuf_iterator<char>()}; 373 file.close(); 374 375 PEL pel{data}; 376 377 if (pel.valid()) 378 { 379 updateFunc(pel); 380 381 write(pel, path); 382 } 383 else 384 { 385 throw std::runtime_error( 386 "Unable to read a valid PEL when trying to update it"); 387 } 388 } 389 390 } // namespace pels 391 } // namespace openpower 392