1 /* 2 // Copyright (c) 2018 Intel 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 #pragma once 17 18 #include "node.hpp" 19 20 #include <boost/container/flat_map.hpp> 21 #include <experimental/filesystem> 22 23 namespace redfish 24 { 25 26 constexpr char const *cpuLogObject = "com.intel.CpuDebugLog"; 27 constexpr char const *cpuLogPath = "/com/intel/CpuDebugLog"; 28 constexpr char const *cpuLogImmediatePath = "/com/intel/CpuDebugLog/Immediate"; 29 constexpr char const *cpuLogInterface = "com.intel.CpuDebugLog"; 30 constexpr char const *cpuLogImmediateInterface = 31 "com.intel.CpuDebugLog.Immediate"; 32 constexpr char const *cpuLogRawPeciInterface = 33 "com.intel.CpuDebugLog.SendRawPeci"; 34 35 namespace fs = std::experimental::filesystem; 36 37 class LogServiceCollection : public Node 38 { 39 public: 40 template <typename CrowApp> 41 LogServiceCollection(CrowApp &app) : 42 Node(app, "/redfish/v1/Managers/bmc/LogServices/") 43 { 44 // Collections use static ID for SubRoute to add to its parent, but only 45 // load dynamic data so the duplicate static members don't get displayed 46 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; 47 entityPrivileges = { 48 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 49 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 50 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 51 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 52 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 53 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 54 } 55 56 private: 57 /** 58 * Functions triggers appropriate requests on DBus 59 */ 60 void doGet(crow::Response &res, const crow::Request &req, 61 const std::vector<std::string> ¶ms) override 62 { 63 // Collections don't include the static data added by SubRoute because 64 // it has a duplicate entry for members 65 res.jsonValue["@odata.type"] = 66 "#LogServiceCollection.LogServiceCollection"; 67 res.jsonValue["@odata.context"] = 68 "/redfish/v1/" 69 "$metadata#LogServiceCollection.LogServiceCollection"; 70 res.jsonValue["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices"; 71 res.jsonValue["Name"] = "Open BMC Log Services Collection"; 72 res.jsonValue["Description"] = 73 "Collection of LogServices for this Manager"; 74 nlohmann::json &logserviceArray = res.jsonValue["Members"]; 75 logserviceArray = nlohmann::json::array(); 76 #ifdef BMCWEB_ENABLE_REDFISH_CPU_LOG 77 logserviceArray.push_back( 78 {{"@odata.id", "/redfish/v1/Managers/bmc/LogServices/CpuLog"}}); 79 #endif 80 res.jsonValue["Members@odata.count"] = logserviceArray.size(); 81 res.end(); 82 } 83 }; 84 85 class CpuLogService : public Node 86 { 87 public: 88 template <typename CrowApp> 89 CpuLogService(CrowApp &app) : 90 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog") 91 { 92 // Set the id for SubRoute 93 Node::json["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/CpuLog"; 94 entityPrivileges = { 95 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 96 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 97 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 98 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 99 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 100 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 101 } 102 103 private: 104 /** 105 * Functions triggers appropriate requests on DBus 106 */ 107 void doGet(crow::Response &res, const crow::Request &req, 108 const std::vector<std::string> ¶ms) override 109 { 110 // Copy over the static data to include the entries added by SubRoute 111 res.jsonValue = Node::json; 112 res.jsonValue["@odata.type"] = "#LogService.v1_1_0.LogService"; 113 res.jsonValue["@odata.context"] = "/redfish/v1/" 114 "$metadata#LogService.LogService"; 115 res.jsonValue["Name"] = "Open BMC CPU Log Service"; 116 res.jsonValue["Description"] = "CPU Log Service"; 117 res.jsonValue["Id"] = "CPU Log"; 118 res.jsonValue["OverWritePolicy"] = "WrapsWhenFull"; 119 res.jsonValue["MaxNumberOfRecords"] = 3; 120 res.jsonValue["Actions"] = { 121 {"Oem", 122 {{"#CpuLog.Immediate", 123 {{"target", 124 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 125 "CpuLog.Immediate"}}}}}}; 126 127 #ifdef BMCWEB_ENABLE_REDFISH_RAW_PECI 128 res.jsonValue["Actions"]["Oem"].push_back( 129 {"#CpuLog.SendRawPeci", 130 {{"target", 131 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 132 "CpuLog.SendRawPeci"}}}); 133 #endif 134 res.end(); 135 } 136 }; 137 138 class CpuLogEntryCollection : public Node 139 { 140 public: 141 template <typename CrowApp> 142 CpuLogEntryCollection(CrowApp &app) : 143 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries") 144 { 145 // Collections use static ID for SubRoute to add to its parent, but only 146 // load dynamic data so the duplicate static members don't get displayed 147 Node::json["@odata.id"] = 148 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries"; 149 entityPrivileges = { 150 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 151 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 152 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 153 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 154 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 155 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 156 } 157 158 private: 159 /** 160 * Functions triggers appropriate requests on DBus 161 */ 162 void doGet(crow::Response &res, const crow::Request &req, 163 const std::vector<std::string> ¶ms) override 164 { 165 // Collections don't include the static data added by SubRoute because 166 // it has a duplicate entry for members 167 auto getLogEntriesCallback = 168 [&res](const boost::system::error_code ec, 169 const std::vector<std::string> &resp) { 170 if (ec) 171 { 172 if (ec.value() != 173 boost::system::errc::no_such_file_or_directory) 174 { 175 BMCWEB_LOG_DEBUG << "failed to get entries ec: " 176 << ec.message(); 177 res.result( 178 boost::beast::http::status::internal_server_error); 179 res.end(); 180 return; 181 } 182 } 183 res.jsonValue["@odata.type"] = 184 "#LogEntryCollection.LogEntryCollection"; 185 res.jsonValue["@odata.context"] = 186 "/redfish/v1/" 187 "$metadata#LogEntryCollection.LogEntryCollection"; 188 res.jsonValue["Name"] = "Open BMC CPU Log Entries"; 189 res.jsonValue["Description"] = "Collection of CPU Log Entries"; 190 nlohmann::json &logentryArray = res.jsonValue["Members"]; 191 logentryArray = nlohmann::json::array(); 192 for (const std::string &objpath : resp) 193 { 194 // Don't list the immediate log 195 if (objpath.compare(cpuLogImmediatePath) == 0) 196 { 197 continue; 198 } 199 std::size_t lastPos = objpath.rfind("/"); 200 if (lastPos != std::string::npos) 201 { 202 logentryArray.push_back( 203 {{"@odata.id", "/redfish/v1/Managers/bmc/" 204 "LogServices/CpuLog/Entries/" + 205 objpath.substr(lastPos + 1)}}); 206 } 207 } 208 res.jsonValue["Members@odata.count"] = logentryArray.size(); 209 res.end(); 210 }; 211 crow::connections::systemBus->async_method_call( 212 std::move(getLogEntriesCallback), 213 "xyz.openbmc_project.ObjectMapper", 214 "/xyz/openbmc_project/object_mapper", 215 "xyz.openbmc_project.ObjectMapper", "GetSubTreePaths", "", 0, 216 std::array<const char *, 1>{cpuLogInterface}); 217 } 218 }; 219 220 std::string getLogCreatedTime(const nlohmann::json &cpuLog) 221 { 222 nlohmann::json::const_iterator metaIt = cpuLog.find("metadata"); 223 if (metaIt != cpuLog.end()) 224 { 225 nlohmann::json::const_iterator tsIt = metaIt->find("timestamp"); 226 if (tsIt != metaIt->end()) 227 { 228 const std::string *logTime = tsIt->get_ptr<const std::string *>(); 229 if (logTime != nullptr) 230 { 231 return *logTime; 232 } 233 } 234 } 235 BMCWEB_LOG_DEBUG << "failed to find log timestamp"; 236 237 return std::string(); 238 } 239 240 class CpuLogEntry : public Node 241 { 242 public: 243 CpuLogEntry(CrowApp &app) : 244 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/<str>/", 245 std::string()) 246 { 247 entityPrivileges = { 248 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 249 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 250 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 251 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 252 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 253 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 254 } 255 256 private: 257 void doGet(crow::Response &res, const crow::Request &req, 258 const std::vector<std::string> ¶ms) override 259 { 260 if (params.size() != 1) 261 { 262 res.result(boost::beast::http::status::internal_server_error); 263 res.end(); 264 return; 265 } 266 const uint8_t logId = std::atoi(params[0].c_str()); 267 auto getStoredLogCallback = [&res, 268 logId](const boost::system::error_code ec, 269 const sdbusplus::message::variant< 270 std::string> &resp) { 271 if (ec) 272 { 273 BMCWEB_LOG_DEBUG << "failed to get log ec: " << ec.message(); 274 res.result(boost::beast::http::status::internal_server_error); 275 res.end(); 276 return; 277 } 278 const std::string *log = mapbox::getPtr<const std::string>(resp); 279 if (log == nullptr) 280 { 281 res.result(boost::beast::http::status::internal_server_error); 282 res.end(); 283 return; 284 } 285 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 286 if (j.is_discarded()) 287 { 288 res.result(boost::beast::http::status::internal_server_error); 289 res.end(); 290 return; 291 } 292 std::string t = getLogCreatedTime(j); 293 res.jsonValue = { 294 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 295 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 296 {"@odata.id", 297 "/redfish/v1/Managers/bmc/LogServices/CpuLog/Entries/" + 298 std::to_string(logId)}, 299 {"Name", "CPU Debug Log"}, 300 {"Id", logId}, 301 {"EntryType", "Oem"}, 302 {"OemRecordFormat", "Intel CPU Log"}, 303 {"Oem", {{"Intel", std::move(j)}}}, 304 {"Created", std::move(t)}}; 305 res.end(); 306 }; 307 crow::connections::systemBus->async_method_call( 308 std::move(getStoredLogCallback), cpuLogObject, 309 cpuLogPath + std::string("/") + std::to_string(logId), 310 "org.freedesktop.DBus.Properties", "Get", cpuLogInterface, "Log"); 311 } 312 }; 313 314 class ImmediateCpuLog : public Node 315 { 316 public: 317 ImmediateCpuLog(CrowApp &app) : 318 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 319 "CpuLog.Immediate") 320 { 321 entityPrivileges = { 322 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 323 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 324 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 325 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 326 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 327 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 328 } 329 330 private: 331 void doPost(crow::Response &res, const crow::Request &req, 332 const std::vector<std::string> ¶ms) override 333 { 334 static std::unique_ptr<sdbusplus::bus::match::match> 335 immediateLogMatcher; 336 337 // Only allow one Immediate Log request at a time 338 if (immediateLogMatcher != nullptr) 339 { 340 res.addHeader("Retry-After", "30"); 341 res.result(boost::beast::http::status::service_unavailable); 342 messages::addMessageToJson( 343 res.jsonValue, messages::serviceTemporarilyUnavailable("30"), 344 "/CpuLog.Immediate"); 345 res.end(); 346 return; 347 } 348 // Make this static so it survives outside this method 349 static boost::asio::deadline_timer timeout(*req.ioService); 350 351 timeout.expires_from_now(boost::posix_time::seconds(30)); 352 timeout.async_wait([&res](const boost::system::error_code &ec) { 353 immediateLogMatcher = nullptr; 354 if (ec) 355 { 356 // operation_aborted is expected if timer is canceled before 357 // completion. 358 if (ec != boost::asio::error::operation_aborted) 359 { 360 BMCWEB_LOG_ERROR << "Async_wait failed " << ec; 361 } 362 return; 363 } 364 BMCWEB_LOG_ERROR << "Timed out waiting for immediate log"; 365 366 res.result(boost::beast::http::status::internal_server_error); 367 res.end(); 368 }); 369 370 auto immediateLogMatcherCallback = [&res]( 371 sdbusplus::message::message &m) { 372 BMCWEB_LOG_DEBUG << "Immediate log available match fired"; 373 boost::system::error_code ec; 374 timeout.cancel(ec); 375 if (ec) 376 { 377 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 378 } 379 sdbusplus::message::object_path objPath; 380 boost::container::flat_map< 381 std::string, 382 boost::container::flat_map< 383 std::string, sdbusplus::message::variant<std::string>>> 384 interfacesAdded; 385 m.read(objPath, interfacesAdded); 386 const std::string *log = mapbox::getPtr<const std::string>( 387 interfacesAdded[cpuLogInterface]["Log"]); 388 if (log == nullptr) 389 { 390 res.result(boost::beast::http::status::internal_server_error); 391 res.end(); 392 // Careful with immediateLogMatcher. It is a unique_ptr to the 393 // match object inside which this lambda is executing. Once it 394 // is set to nullptr, the match object will be destroyed and the 395 // lambda will lose its context, including res, so it needs to 396 // be the last thing done. 397 immediateLogMatcher = nullptr; 398 return; 399 } 400 nlohmann::json j = nlohmann::json::parse(*log, nullptr, false); 401 if (j.is_discarded()) 402 { 403 res.result(boost::beast::http::status::internal_server_error); 404 res.end(); 405 // Careful with immediateLogMatcher. It is a unique_ptr to the 406 // match object inside which this lambda is executing. Once it 407 // is set to nullptr, the match object will be destroyed and the 408 // lambda will lose its context, including res, so it needs to 409 // be the last thing done. 410 immediateLogMatcher = nullptr; 411 return; 412 } 413 std::string t = getLogCreatedTime(j); 414 res.jsonValue = { 415 {"@odata.type", "#LogEntry.v1_3_0.LogEntry"}, 416 {"@odata.context", "/redfish/v1/$metadata#LogEntry.LogEntry"}, 417 {"Name", "CPU Debug Log"}, 418 {"EntryType", "Oem"}, 419 {"OemRecordFormat", "Intel CPU Log"}, 420 {"Oem", {{"Intel", std::move(j)}}}, 421 {"Created", std::move(t)}}; 422 res.end(); 423 // Careful with immediateLogMatcher. It is a unique_ptr to the 424 // match object inside which this lambda is executing. Once it is 425 // set to nullptr, the match object will be destroyed and the lambda 426 // will lose its context, including res, so it needs to be the last 427 // thing done. 428 immediateLogMatcher = nullptr; 429 }; 430 immediateLogMatcher = std::make_unique<sdbusplus::bus::match::match>( 431 *crow::connections::systemBus, 432 sdbusplus::bus::match::rules::interfacesAdded() + 433 sdbusplus::bus::match::rules::argNpath(0, cpuLogImmediatePath), 434 std::move(immediateLogMatcherCallback)); 435 436 auto generateImmediateLogCallback = 437 [&res](const boost::system::error_code ec, 438 const std::string &resp) { 439 if (ec) 440 { 441 if (ec.value() == 442 boost::system::errc::operation_not_supported) 443 { 444 messages::addMessageToJson( 445 res.jsonValue, messages::resourceInStandby(), 446 "/CpuLog.Immediate"); 447 res.result( 448 boost::beast::http::status::service_unavailable); 449 } 450 else 451 { 452 res.result( 453 boost::beast::http::status::internal_server_error); 454 } 455 res.end(); 456 boost::system::error_code timeoutec; 457 timeout.cancel(timeoutec); 458 if (timeoutec) 459 { 460 BMCWEB_LOG_ERROR << "error canceling timer " 461 << timeoutec; 462 } 463 immediateLogMatcher = nullptr; 464 return; 465 } 466 }; 467 crow::connections::systemBus->async_method_call( 468 std::move(generateImmediateLogCallback), cpuLogObject, cpuLogPath, 469 cpuLogImmediateInterface, "GenerateImmediateLog"); 470 } 471 }; 472 473 class SendRawPeci : public Node 474 { 475 public: 476 SendRawPeci(CrowApp &app) : 477 Node(app, "/redfish/v1/Managers/bmc/LogServices/CpuLog/Actions/Oem/" 478 "CpuLog.SendRawPeci") 479 { 480 entityPrivileges = { 481 {boost::beast::http::verb::get, {{"ConfigureComponents"}}}, 482 {boost::beast::http::verb::head, {{"ConfigureComponents"}}}, 483 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 484 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 485 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 486 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 487 } 488 489 private: 490 void doPost(crow::Response &res, const crow::Request &req, 491 const std::vector<std::string> ¶ms) override 492 { 493 // Get the Raw PECI command from the request 494 nlohmann::json rawPeciCmd; 495 if (!json_util::processJsonFromRequest(res, req, rawPeciCmd)) 496 { 497 return; 498 } 499 // Get the Client Address from the request 500 nlohmann::json::const_iterator caIt = rawPeciCmd.find("ClientAddress"); 501 if (caIt == rawPeciCmd.end()) 502 { 503 messages::addMessageToJson( 504 res.jsonValue, messages::propertyMissing("ClientAddress"), 505 "/ClientAddress"); 506 res.result(boost::beast::http::status::bad_request); 507 res.end(); 508 return; 509 } 510 const uint64_t *ca = caIt->get_ptr<const uint64_t *>(); 511 if (ca == nullptr) 512 { 513 messages::addMessageToJson( 514 res.jsonValue, 515 messages::propertyValueTypeError(caIt->dump(), "ClientAddress"), 516 "/ClientAddress"); 517 res.result(boost::beast::http::status::bad_request); 518 res.end(); 519 return; 520 } 521 // Get the Read Length from the request 522 const uint8_t clientAddress = static_cast<uint8_t>(*ca); 523 nlohmann::json::const_iterator rlIt = rawPeciCmd.find("ReadLength"); 524 if (rlIt == rawPeciCmd.end()) 525 { 526 messages::addMessageToJson(res.jsonValue, 527 messages::propertyMissing("ReadLength"), 528 "/ReadLength"); 529 res.result(boost::beast::http::status::bad_request); 530 res.end(); 531 return; 532 } 533 const uint64_t *rl = rlIt->get_ptr<const uint64_t *>(); 534 if (rl == nullptr) 535 { 536 messages::addMessageToJson( 537 res.jsonValue, 538 messages::propertyValueTypeError(rlIt->dump(), "ReadLength"), 539 "/ReadLength"); 540 res.result(boost::beast::http::status::bad_request); 541 res.end(); 542 return; 543 } 544 // Get the PECI Command from the request 545 const uint32_t readLength = static_cast<uint32_t>(*rl); 546 nlohmann::json::const_iterator pcIt = rawPeciCmd.find("PECICommand"); 547 if (pcIt == rawPeciCmd.end()) 548 { 549 messages::addMessageToJson(res.jsonValue, 550 messages::propertyMissing("PECICommand"), 551 "/PECICommand"); 552 res.result(boost::beast::http::status::bad_request); 553 res.end(); 554 return; 555 } 556 std::vector<uint8_t> peciCommand; 557 for (auto pc : *pcIt) 558 { 559 const uint64_t *val = pc.get_ptr<const uint64_t *>(); 560 if (val == nullptr) 561 { 562 messages::addMessageToJson( 563 res.jsonValue, 564 messages::propertyValueTypeError( 565 pc.dump(), 566 "PECICommand/" + std::to_string(peciCommand.size())), 567 "/PECICommand"); 568 res.result(boost::beast::http::status::bad_request); 569 res.end(); 570 return; 571 } 572 peciCommand.push_back(static_cast<uint8_t>(*val)); 573 } 574 // Callback to return the Raw PECI response 575 auto sendRawPeciCallback = [&res](const boost::system::error_code ec, 576 const std::vector<uint8_t> &resp) { 577 if (ec) 578 { 579 BMCWEB_LOG_DEBUG << "failed to send PECI command ec: " 580 << ec.message(); 581 res.result(boost::beast::http::status::internal_server_error); 582 res.end(); 583 return; 584 } 585 res.jsonValue = {{"Name", "PECI Command Response"}, 586 {"PECIResponse", resp}}; 587 res.end(); 588 }; 589 // Call the SendRawPECI command with the provided data 590 crow::connections::systemBus->async_method_call( 591 std::move(sendRawPeciCallback), cpuLogObject, cpuLogPath, 592 cpuLogRawPeciInterface, "SendRawPeci", clientAddress, readLength, 593 peciCommand); 594 } 595 }; 596 597 } // namespace redfish 598