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 doPost(crow::Response &res, const crow::Request &req, 206 const std::vector<std::string> ¶ms) override 207 { 208 BMCWEB_LOG_DEBUG << "doPost..."; 209 210 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 211 212 // Setup callback for when new software detected 213 monitorForSoftwareAvailable(asyncResp, req); 214 215 std::string filepath( 216 "/tmp/images/" + 217 boost::uuids::to_string(boost::uuids::random_generator()())); 218 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 219 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 220 std::ofstream::trunc); 221 out << req.body; 222 out.close(); 223 BMCWEB_LOG_DEBUG << "file upload complete!!"; 224 } 225 }; 226 227 class SoftwareInventoryCollection : public Node 228 { 229 public: 230 template <typename CrowApp> 231 SoftwareInventoryCollection(CrowApp &app) : 232 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 233 { 234 entityPrivileges = { 235 {boost::beast::http::verb::get, {{"Login"}}}, 236 {boost::beast::http::verb::head, {{"Login"}}}, 237 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 238 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 239 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 240 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 241 } 242 243 private: 244 void doGet(crow::Response &res, const crow::Request &req, 245 const std::vector<std::string> ¶ms) override 246 { 247 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 248 res.jsonValue["@odata.type"] = 249 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 250 res.jsonValue["@odata.id"] = 251 "/redfish/v1/UpdateService/FirmwareInventory"; 252 res.jsonValue["@odata.context"] = 253 "/redfish/v1/" 254 "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection"; 255 res.jsonValue["Name"] = "Software Inventory Collection"; 256 257 crow::connections::systemBus->async_method_call( 258 [asyncResp]( 259 const boost::system::error_code ec, 260 const std::vector<std::pair< 261 std::string, std::vector<std::pair< 262 std::string, std::vector<std::string>>>>> 263 &subtree) { 264 if (ec) 265 { 266 messages::internalError(asyncResp->res); 267 return; 268 } 269 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 270 asyncResp->res.jsonValue["Members@odata.count"] = 0; 271 272 for (auto &obj : subtree) 273 { 274 const std::vector< 275 std::pair<std::string, std::vector<std::string>>> 276 &connections = obj.second; 277 278 // if can't parse fw id then return 279 std::size_t idPos; 280 if ((idPos = obj.first.rfind("/")) == std::string::npos) 281 { 282 messages::internalError(asyncResp->res); 283 BMCWEB_LOG_DEBUG << "Can't parse firmware ID!!"; 284 return; 285 } 286 std::string swId = obj.first.substr(idPos + 1); 287 288 for (auto &conn : connections) 289 { 290 const std::string &connectionName = conn.first; 291 BMCWEB_LOG_DEBUG << "connectionName = " 292 << connectionName; 293 BMCWEB_LOG_DEBUG << "obj.first = " << obj.first; 294 295 crow::connections::systemBus->async_method_call( 296 [asyncResp, 297 swId](const boost::system::error_code error_code, 298 const VariantType &activation) { 299 BMCWEB_LOG_DEBUG 300 << "safe returned in lambda function"; 301 if (error_code) 302 { 303 messages::internalError(asyncResp->res); 304 return; 305 } 306 307 const std::string *swActivationStatus = 308 std::get_if<std::string>(&activation); 309 if (swActivationStatus == nullptr) 310 { 311 messages::internalError(asyncResp->res); 312 return; 313 } 314 if (swActivationStatus != nullptr && 315 *swActivationStatus != 316 "xyz.openbmc_project.Software." 317 "Activation." 318 "Activations.Active") 319 { 320 // The activation status of this software is 321 // not currently active, so does not need to 322 // be listed in the response 323 return; 324 } 325 nlohmann::json &members = 326 asyncResp->res.jsonValue["Members"]; 327 members.push_back( 328 {{"@odata.id", "/redfish/v1/UpdateService/" 329 "FirmwareInventory/" + 330 swId}}); 331 asyncResp->res 332 .jsonValue["Members@odata.count"] = 333 members.size(); 334 }, 335 connectionName, obj.first, 336 "org.freedesktop.DBus.Properties", "Get", 337 "xyz.openbmc_project.Software.Activation", 338 "Activation"); 339 } 340 } 341 }, 342 "xyz.openbmc_project.ObjectMapper", 343 "/xyz/openbmc_project/object_mapper", 344 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 345 "/xyz/openbmc_project/software", int32_t(1), 346 std::array<const char *, 1>{ 347 "xyz.openbmc_project.Software.Version"}); 348 } 349 }; 350 351 class SoftwareInventory : public Node 352 { 353 public: 354 template <typename CrowApp> 355 SoftwareInventory(CrowApp &app) : 356 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 357 std::string()) 358 { 359 entityPrivileges = { 360 {boost::beast::http::verb::get, {{"Login"}}}, 361 {boost::beast::http::verb::head, {{"Login"}}}, 362 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 363 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 364 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 365 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 366 } 367 368 private: 369 /* Fill related item links (i.e. bmc, bios) in for inventory */ 370 static void getRelatedItems(std::shared_ptr<AsyncResp> aResp, 371 const std::string &purpose) 372 { 373 if (purpose == fw_util::bmcPurpose) 374 { 375 nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 376 members.push_back({{"@odata.id", "/redfish/v1/Managers/bmc"}}); 377 aResp->res.jsonValue["Members@odata.count"] = members.size(); 378 } 379 else if (purpose == fw_util::biosPurpose) 380 { 381 // TODO(geissonator) Need BIOS schema support added for this 382 // to be valid 383 // nlohmann::json &members = aResp->res.jsonValue["RelatedItem"]; 384 // members.push_back( 385 // {{"@odata.id", "/redfish/v1/Systems/system/BIOS"}}); 386 // aResp->res.jsonValue["Members@odata.count"] = members.size(); 387 } 388 else 389 { 390 BMCWEB_LOG_ERROR << "Unknown software purpose " << purpose; 391 } 392 } 393 394 void doGet(crow::Response &res, const crow::Request &req, 395 const std::vector<std::string> ¶ms) override 396 { 397 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 398 res.jsonValue["@odata.type"] = 399 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 400 res.jsonValue["@odata.context"] = 401 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 402 res.jsonValue["Name"] = "Software Inventory"; 403 res.jsonValue["Updateable"] = false; 404 res.jsonValue["Status"]["Health"] = "OK"; 405 res.jsonValue["Status"]["HealthRollup"] = "OK"; 406 res.jsonValue["Status"]["State"] = "Enabled"; 407 408 if (params.size() != 1) 409 { 410 messages::internalError(res); 411 res.end(); 412 return; 413 } 414 415 std::shared_ptr<std::string> swId = 416 std::make_shared<std::string>(params[0]); 417 418 res.jsonValue["@odata.id"] = 419 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 420 421 crow::connections::systemBus->async_method_call( 422 [asyncResp, swId]( 423 const boost::system::error_code ec, 424 const std::vector<std::pair< 425 std::string, std::vector<std::pair< 426 std::string, std::vector<std::string>>>>> 427 &subtree) { 428 BMCWEB_LOG_DEBUG << "doGet callback..."; 429 if (ec) 430 { 431 messages::internalError(asyncResp->res); 432 return; 433 } 434 435 for (const std::pair< 436 std::string, 437 std::vector< 438 std::pair<std::string, std::vector<std::string>>>> 439 &obj : subtree) 440 { 441 if (boost::ends_with(obj.first, *swId) != true) 442 { 443 continue; 444 } 445 446 if (obj.second.size() < 1) 447 { 448 continue; 449 } 450 451 crow::connections::systemBus->async_method_call( 452 [asyncResp, 453 swId](const boost::system::error_code error_code, 454 const boost::container::flat_map< 455 std::string, VariantType> &propertiesList) { 456 if (error_code) 457 { 458 messages::internalError(asyncResp->res); 459 return; 460 } 461 boost::container::flat_map< 462 std::string, VariantType>::const_iterator it = 463 propertiesList.find("Purpose"); 464 if (it == propertiesList.end()) 465 { 466 BMCWEB_LOG_DEBUG 467 << "Can't find property \"Purpose\"!"; 468 messages::propertyMissing(asyncResp->res, 469 "Purpose"); 470 return; 471 } 472 const std::string *swInvPurpose = 473 std::get_if<std::string>(&it->second); 474 if (swInvPurpose == nullptr) 475 { 476 BMCWEB_LOG_DEBUG 477 << "wrong types for property\"Purpose\"!"; 478 messages::propertyValueTypeError(asyncResp->res, 479 "", "Purpose"); 480 return; 481 } 482 483 BMCWEB_LOG_DEBUG << "swInvPurpose = " 484 << *swInvPurpose; 485 it = propertiesList.find("Version"); 486 if (it == propertiesList.end()) 487 { 488 BMCWEB_LOG_DEBUG 489 << "Can't find property \"Version\"!"; 490 messages::propertyMissing(asyncResp->res, 491 "Version"); 492 return; 493 } 494 495 BMCWEB_LOG_DEBUG << "Version found!"; 496 497 const std::string *version = 498 std::get_if<std::string>(&it->second); 499 500 if (version == nullptr) 501 { 502 BMCWEB_LOG_DEBUG 503 << "Can't find property \"Version\"!"; 504 505 messages::propertyValueTypeError(asyncResp->res, 506 "", "Version"); 507 return; 508 } 509 asyncResp->res.jsonValue["Version"] = *version; 510 asyncResp->res.jsonValue["Id"] = *swId; 511 512 // swInvPurpose is of format: 513 // xyz.openbmc_project.Software.Version.VersionPurpose.ABC 514 // Translate this to "ABC update" 515 size_t endDesc = swInvPurpose->rfind("."); 516 if (endDesc == std::string::npos) 517 { 518 messages::internalError(asyncResp->res); 519 return; 520 } 521 endDesc++; 522 if (endDesc >= swInvPurpose->size()) 523 { 524 messages::internalError(asyncResp->res); 525 return; 526 } 527 528 std::string formatDesc = 529 swInvPurpose->substr(endDesc); 530 asyncResp->res.jsonValue["Description"] = 531 formatDesc + " update"; 532 getRelatedItems(asyncResp, *swInvPurpose); 533 }, 534 obj.second[0].first, obj.first, 535 "org.freedesktop.DBus.Properties", "GetAll", 536 "xyz.openbmc_project.Software.Version"); 537 } 538 }, 539 "xyz.openbmc_project.ObjectMapper", 540 "/xyz/openbmc_project/object_mapper", 541 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 542 "/xyz/openbmc_project/software", int32_t(1), 543 std::array<const char *, 1>{ 544 "xyz.openbmc_project.Software.Version"}); 545 } 546 }; 547 548 } // namespace redfish 549