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 <utils/fw_utils.hpp> 22 #include <variant> 23 24 namespace redfish 25 { 26 27 // Match signals added on software path 28 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 29 // Only allow one update at a time 30 static bool fwUpdateInProgress = false; 31 // Timer for software available 32 static std::unique_ptr<boost::asio::deadline_timer> fwAvailableTimer; 33 34 static void cleanUp() 35 { 36 fwUpdateInProgress = false; 37 fwUpdateMatcher = nullptr; 38 } 39 static void activateImage(const std::string &objPath, 40 const std::string &service) 41 { 42 BMCWEB_LOG_DEBUG << "Activate image for " << objPath << " " << service; 43 crow::connections::systemBus->async_method_call( 44 [](const boost::system::error_code error_code) { 45 if (error_code) 46 { 47 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 48 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); 49 } 50 }, 51 service, objPath, "org.freedesktop.DBus.Properties", "Set", 52 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 53 std::variant<std::string>( 54 "xyz.openbmc_project.Software.Activation.RequestedActivations." 55 "Active")); 56 } 57 static void softwareInterfaceAdded(std::shared_ptr<AsyncResp> asyncResp, 58 sdbusplus::message::message &m) 59 { 60 std::vector<std::pair< 61 std::string, 62 std::vector<std::pair<std::string, std::variant<std::string>>>>> 63 interfacesProperties; 64 65 sdbusplus::message::object_path objPath; 66 67 m.read(objPath, interfacesProperties); 68 69 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; 70 for (auto &interface : interfacesProperties) 71 { 72 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 73 74 if (interface.first == "xyz.openbmc_project.Software.Activation") 75 { 76 // Found our interface, disable callbacks 77 fwUpdateMatcher = nullptr; 78 79 // Retrieve service and activate 80 crow::connections::systemBus->async_method_call( 81 [objPath, asyncResp]( 82 const boost::system::error_code error_code, 83 const std::vector<std::pair< 84 std::string, std::vector<std::string>>> &objInfo) { 85 if (error_code) 86 { 87 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 88 BMCWEB_LOG_DEBUG << "error msg = " 89 << error_code.message(); 90 messages::internalError(asyncResp->res); 91 cleanUp(); 92 return; 93 } 94 // Ensure we only got one service back 95 if (objInfo.size() != 1) 96 { 97 BMCWEB_LOG_ERROR << "Invalid Object Size " 98 << objInfo.size(); 99 messages::internalError(asyncResp->res); 100 cleanUp(); 101 return; 102 } 103 // cancel timer only when 104 // xyz.openbmc_project.Software.Activation interface 105 // is added 106 fwAvailableTimer = nullptr; 107 108 activateImage(objPath.str, objInfo[0].first); 109 redfish::messages::success(asyncResp->res); 110 fwUpdateInProgress = false; 111 }, 112 "xyz.openbmc_project.ObjectMapper", 113 "/xyz/openbmc_project/object_mapper", 114 "xyz.openbmc_project.ObjectMapper", "GetObject", objPath.str, 115 std::array<const char *, 1>{ 116 "xyz.openbmc_project.Software.Activation"}); 117 } 118 } 119 } 120 121 static void monitorForSoftwareAvailable(std::shared_ptr<AsyncResp> asyncResp, 122 const crow::Request &req) 123 { 124 // Only allow one FW update at a time 125 if (fwUpdateInProgress != false) 126 { 127 asyncResp->res.addHeader("Retry-After", "30"); 128 messages::serviceTemporarilyUnavailable(asyncResp->res, "30"); 129 return; 130 } 131 132 fwAvailableTimer = std::make_unique<boost::asio::deadline_timer>( 133 *req.ioService, boost::posix_time::seconds(5)); 134 135 fwAvailableTimer->expires_from_now(boost::posix_time::seconds(5)); 136 137 fwAvailableTimer->async_wait( 138 [asyncResp](const boost::system::error_code &ec) { 139 cleanUp(); 140 if (ec == boost::asio::error::operation_aborted) 141 { 142 // expected, we were canceled before the timer completed. 143 return; 144 } 145 BMCWEB_LOG_ERROR 146 << "Timed out waiting for firmware object being created"; 147 BMCWEB_LOG_ERROR 148 << "FW image may has already been uploaded to server"; 149 if (ec) 150 { 151 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 152 return; 153 } 154 155 redfish::messages::internalError(asyncResp->res); 156 }); 157 158 auto callback = [asyncResp](sdbusplus::message::message &m) { 159 BMCWEB_LOG_DEBUG << "Match fired"; 160 softwareInterfaceAdded(asyncResp, m); 161 }; 162 163 fwUpdateInProgress = true; 164 165 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>( 166 *crow::connections::systemBus, 167 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 168 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 169 callback); 170 } 171 172 class UpdateService : public Node 173 { 174 public: 175 UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") 176 { 177 entityPrivileges = { 178 {boost::beast::http::verb::get, {{"Login"}}}, 179 {boost::beast::http::verb::head, {{"Login"}}}, 180 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 181 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 182 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 183 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 184 } 185 186 private: 187 void doGet(crow::Response &res, const crow::Request &req, 188 const std::vector<std::string> ¶ms) override 189 { 190 res.jsonValue["@odata.type"] = "#UpdateService.v1_2_0.UpdateService"; 191 res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 192 res.jsonValue["@odata.context"] = 193 "/redfish/v1/$metadata#UpdateService.UpdateService"; 194 res.jsonValue["Id"] = "UpdateService"; 195 res.jsonValue["Description"] = "Service for Software Update"; 196 res.jsonValue["Name"] = "Update Service"; 197 res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; 198 // UpdateService cannot be disabled 199 res.jsonValue["ServiceEnabled"] = true; 200 res.jsonValue["FirmwareInventory"] = { 201 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 202 res.end(); 203 } 204 205 void doPatch(crow::Response &res, const crow::Request &req, 206 const std::vector<std::string> ¶ms) override 207 { 208 BMCWEB_LOG_DEBUG << "doPatch..."; 209 210 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 211 std::string applyTime; 212 213 if (!json_util::readJson(req, res, "ApplyTime", applyTime)) 214 { 215 return; 216 } 217 218 if ((applyTime == "Immediate") || (applyTime == "OnReset")) 219 { 220 std::string applyTimeNewVal; 221 if (applyTime == "Immediate") 222 { 223 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime." 224 "RequestedApplyTimes.Immediate"; 225 } 226 else 227 { 228 applyTimeNewVal = "xyz.openbmc_project.Software.ApplyTime." 229 "RequestedApplyTimes.OnReset"; 230 } 231 232 // Set the requested image apply time value 233 crow::connections::systemBus->async_method_call( 234 [asyncResp](const boost::system::error_code ec) { 235 if (ec) 236 { 237 BMCWEB_LOG_ERROR << "D-Bus responses error: " << ec; 238 messages::internalError(asyncResp->res); 239 return; 240 } 241 messages::success(asyncResp->res); 242 }, 243 "xyz.openbmc_project.Settings", 244 "/xyz/openbmc_project/software/apply_time", 245 "org.freedesktop.DBus.Properties", "Set", 246 "xyz.openbmc_project.Software.ApplyTime", "RequestedApplyTime", 247 std::variant<std::string>{applyTimeNewVal}); 248 } 249 else 250 { 251 BMCWEB_LOG_INFO << "ApplyTime value is not in the list of " 252 "acceptable values"; 253 messages::propertyValueNotInList(asyncResp->res, applyTime, 254 "ApplyTime"); 255 } 256 } 257 258 void doPost(crow::Response &res, const crow::Request &req, 259 const std::vector<std::string> ¶ms) override 260 { 261 BMCWEB_LOG_DEBUG << "doPost..."; 262 263 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 264 265 // Setup callback for when new software detected 266 monitorForSoftwareAvailable(asyncResp, req); 267 268 std::string filepath( 269 "/tmp/images/" + 270 boost::uuids::to_string(boost::uuids::random_generator()())); 271 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 272 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 273 std::ofstream::trunc); 274 out << req.body; 275 out.close(); 276 BMCWEB_LOG_DEBUG << "file upload complete!!"; 277 } 278 }; 279 280 class SoftwareInventoryCollection : public Node 281 { 282 public: 283 template <typename CrowApp> 284 SoftwareInventoryCollection(CrowApp &app) : 285 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 286 { 287 entityPrivileges = { 288 {boost::beast::http::verb::get, {{"Login"}}}, 289 {boost::beast::http::verb::head, {{"Login"}}}, 290 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 291 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 292 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 293 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 294 } 295 296 private: 297 void doGet(crow::Response &res, const crow::Request &req, 298 const std::vector<std::string> ¶ms) override 299 { 300 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 301 res.jsonValue["@odata.type"] = 302 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 303 res.jsonValue["@odata.id"] = 304 "/redfish/v1/UpdateService/FirmwareInventory"; 305 res.jsonValue["@odata.context"] = 306 "/redfish/v1/" 307 "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection"; 308 res.jsonValue["Name"] = "Software Inventory Collection"; 309 310 crow::connections::systemBus->async_method_call( 311 [asyncResp]( 312 const boost::system::error_code ec, 313 const std::vector<std::pair< 314 std::string, std::vector<std::pair< 315 std::string, std::vector<std::string>>>>> 316 &subtree) { 317 if (ec) 318 { 319 messages::internalError(asyncResp->res); 320 return; 321 } 322 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 323 asyncResp->res.jsonValue["Members@odata.count"] = 0; 324 325 for (auto &obj : subtree) 326 { 327 const std::vector< 328 std::pair<std::string, std::vector<std::string>>> 329 &connections = obj.second; 330 331 // if can't parse fw id then return 332 std::size_t idPos; 333 if ((idPos = obj.first.rfind("/")) == std::string::npos) 334 { 335 messages::internalError(asyncResp->res); 336 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 337 return; 338 } 339 std::string swId = obj.first.substr(idPos + 1); 340 341 for (auto &conn : connections) 342 { 343 const std::string &connectionName = conn.first; 344 BMCWEB_LOG_DEBUG << "connectionName = " 345 << connectionName; 346 BMCWEB_LOG_DEBUG << "obj.first = " << obj.first; 347 348 crow::connections::systemBus->async_method_call( 349 [asyncResp, 350 swId](const boost::system::error_code error_code, 351 const VariantType &activation) { 352 BMCWEB_LOG_DEBUG 353 << "safe returned in lambda function"; 354 if (error_code) 355 { 356 messages::internalError(asyncResp->res); 357 return; 358 } 359 360 const std::string *swActivationStatus = 361 std::get_if<std::string>(&activation); 362 if (swActivationStatus == nullptr) 363 { 364 messages::internalError(asyncResp->res); 365 return; 366 } 367 if (swActivationStatus != nullptr && 368 *swActivationStatus != 369 "xyz.openbmc_project.Software." 370 "Activation." 371 "Activations.Active") 372 { 373 // The activation status of this software is 374 // not currently active, so does not need to 375 // be listed in the response 376 return; 377 } 378 nlohmann::json &members = 379 asyncResp->res.jsonValue["Members"]; 380 members.push_back( 381 {{"@odata.id", "/redfish/v1/UpdateService/" 382 "FirmwareInventory/" + 383 swId}}); 384 asyncResp->res 385 .jsonValue["Members@odata.count"] = 386 members.size(); 387 }, 388 connectionName, obj.first, 389 "org.freedesktop.DBus.Properties", "Get", 390 "xyz.openbmc_project.Software.Activation", 391 "Activation"); 392 } 393 } 394 }, 395 "xyz.openbmc_project.ObjectMapper", 396 "/xyz/openbmc_project/object_mapper", 397 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 398 "/xyz/openbmc_project/software", int32_t(1), 399 std::array<const char *, 1>{ 400 "xyz.openbmc_project.Software.Version"}); 401 } 402 }; 403 404 class SoftwareInventory : public Node 405 { 406 public: 407 template <typename CrowApp> 408 SoftwareInventory(CrowApp &app) : 409 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 410 std::string()) 411 { 412 entityPrivileges = { 413 {boost::beast::http::verb::get, {{"Login"}}}, 414 {boost::beast::http::verb::head, {{"Login"}}}, 415 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 416 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 417 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 418 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 419 } 420 421 private: 422 /* Fill related item links (i.e. bmc, bios) in for inventory */ 423 static void getRelatedItems(std::shared_ptr<AsyncResp> aResp, 424 const std::string &purpose) 425 { 426 if (purpose == fw_util::bmcPurpose) 427 { 428 nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 429 members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}}); 430 aResp->res.jsonValue["Members@odata.count"] = members.size(); 431 } 432 else if (purpose == fw_util::biosPurpose) 433 { 434 // TODO(geissonator) Need BIOS schema support added for this 435 // to be valid 436 // nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 437 // members.push_back( 438 // {{"@odata.id", "/redfish/v1/Systems/system/BIOS"}}); 439 // aResp->res.jsonValue["Members@odata.count"] = members.size(); 440 } 441 else 442 { 443 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 444 } 445 } 446 447 void doGet(crow::Response &res, const crow::Request &req, 448 const std::vector<std::string> ¶ms) override 449 { 450 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 451 res.jsonValue["@odata.type"] = 452 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 453 res.jsonValue["@odata.context"] = 454 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 455 res.jsonValue["Name"] = "Software Inventory"; 456 res.jsonValue["Updateable"] = false; 457 res.jsonValue["Status"]["Health"] = "OK"; 458 res.jsonValue["Status"]["HealthRollup"] = "OK"; 459 res.jsonValue["Status"]["State"] = "Enabled"; 460 461 if (params.size() != 1) 462 { 463 messages::internalError(res); 464 res.end(); 465 return; 466 } 467 468 std::shared_ptr<std::string> swId = 469 std::make_shared<std::string>(params[0]); 470 471 res.jsonValue["@odata.id"] = 472 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 473 474 crow::connections::systemBus->async_method_call( 475 [asyncResp, swId]( 476 const boost::system::error_code ec, 477 const std::vector<std::pair< 478 std::string, std::vector<std::pair< 479 std::string, std::vector<std::string>>>>> 480 &subtree) { 481 BMCWEB_LOG_DEBUG << "doGet callback..."; 482 if (ec) 483 { 484 messages::internalError(asyncResp->res); 485 return; 486 } 487 488 for (const std::pair< 489 std::string, 490 std::vector< 491 std::pair<std::string, std::vector<std::string>>>> 492 &obj : subtree) 493 { 494 if (boost::ends_with(obj.first, *swId) != true) 495 { 496 continue; 497 } 498 499 if (obj.second.size() < 1) 500 { 501 continue; 502 } 503 504 crow::connections::systemBus->async_method_call( 505 [asyncResp, 506 swId](const boost::system::error_code error_code, 507 const boost::container::flat_map< 508 std::string, VariantType> &propertiesList) { 509 if (error_code) 510 { 511 messages::internalError(asyncResp->res); 512 return; 513 } 514 boost::container::flat_map< 515 std::string, VariantType>::const_iterator it = 516 propertiesList.find("Purpose"); 517 if (it == propertiesList.end()) 518 { 519 BMCWEB_LOG_DEBUG 520 << "Can't find property \"Purpose\"!"; 521 messages::propertyMissing(asyncResp->res, 522 "Purpose"); 523 return; 524 } 525 const std::string *swInvPurpose = 526 std::get_if<std::string>(&it->second); 527 if (swInvPurpose == nullptr) 528 { 529 BMCWEB_LOG_DEBUG 530 << "wrong types for property\"Purpose\"!"; 531 messages::propertyValueTypeError(asyncResp->res, 532 "", "Purpose"); 533 return; 534 } 535 536 BMCWEB_LOG_DEBUG << "swInvPurpose = " 537 << *swInvPurpose; 538 it = propertiesList.find("Version"); 539 if (it == propertiesList.end()) 540 { 541 BMCWEB_LOG_DEBUG 542 << "Can't find property \"Version\"!"; 543 messages::propertyMissing(asyncResp->res, 544 "Version"); 545 return; 546 } 547 548 BMCWEB_LOG_DEBUG << "Version found!"; 549 550 const std::string *version = 551 std::get_if<std::string>(&it->second); 552 553 if (version == nullptr) 554 { 555 BMCWEB_LOG_DEBUG 556 << "Can't find property \"Version\"!"; 557 558 messages::propertyValueTypeError(asyncResp->res, 559 "", "Version"); 560 return; 561 } 562 asyncResp->res.jsonValue["Version"] = *version; 563 asyncResp->res.jsonValue["Id"] = *swId; 564 565 // swInvPurpose is of format: 566 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 567 // Translate this to "ABC update" 568 size_t endDesc = swInvPurpose->rfind("."); 569 if (endDesc == std::string::npos) 570 { 571 messages::internalError(asyncResp->res); 572 return; 573 } 574 endDesc++; 575 if (endDesc >= swInvPurpose->size()) 576 { 577 messages::internalError(asyncResp->res); 578 return; 579 } 580 581 std::string formatDesc = 582 swInvPurpose->substr(endDesc); 583 asyncResp->res.jsonValue["Description"] = 584 formatDesc + " update"; 585 getRelatedItems(asyncResp, *swInvPurpose); 586 }, 587 obj.second[0].first, obj.first, 588 "org.freedesktop.DBus.Properties", "GetAll", 589 "xyz.openbmc_project.Software.Version"); 590 } 591 }, 592 "xyz.openbmc_project.ObjectMapper", 593 "/xyz/openbmc_project/object_mapper", 594 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 595 "/xyz/openbmc_project/software", int32_t(1), 596 std::array<const char *, 1>{ 597 "xyz.openbmc_project.Software.Version"}); 598 } 599 }; 600 601 } // namespace redfish 602