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