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