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