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