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