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 22 namespace redfish 23 { 24 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 25 26 class UpdateService : public Node 27 { 28 public: 29 UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") 30 { 31 Node::json["@odata.type"] = "#UpdateService.v1_2_0.UpdateService"; 32 Node::json["@odata.id"] = "/redfish/v1/UpdateService"; 33 Node::json["@odata.context"] = 34 "/redfish/v1/$metadata#UpdateService.UpdateService"; 35 Node::json["Id"] = "UpdateService"; 36 Node::json["Description"] = "Service for Software Update"; 37 Node::json["Name"] = "Update Service"; 38 Node::json["HttpPushUri"] = "/redfish/v1/UpdateService"; 39 // UpdateService cannot be disabled 40 Node::json["ServiceEnabled"] = true; 41 Node::json["FirmwareInventory"] = { 42 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 43 44 entityPrivileges = { 45 {boost::beast::http::verb::get, {{"Login"}}}, 46 {boost::beast::http::verb::head, {{"Login"}}}, 47 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 48 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 49 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 50 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 51 } 52 53 private: 54 void doGet(crow::Response &res, const crow::Request &req, 55 const std::vector<std::string> ¶ms) override 56 { 57 res.jsonValue = Node::json; 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 sdbusplus::message::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 res.result(boost::beast::http::status::service_unavailable); 87 res.jsonValue = messages::serviceTemporarilyUnavailable("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 res.result(boost::beast::http::status::internal_server_error); 115 res.jsonValue = redfish::messages::internalError(); 116 res.end(); 117 }); 118 119 auto callback = [&res](sdbusplus::message::message &m) { 120 BMCWEB_LOG_DEBUG << "Match fired"; 121 bool flag = false; 122 123 if (m.is_method_error()) 124 { 125 BMCWEB_LOG_DEBUG << "Dbus method error!!!"; 126 res.end(); 127 return; 128 } 129 std::vector<std::pair< 130 std::string, 131 std::vector<std::pair< 132 std::string, sdbusplus::message::variant<std::string>>>>> 133 interfaces_properties; 134 135 sdbusplus::message::object_path objPath; 136 137 m.read(objPath, interfaces_properties); // Read in the object path 138 // that was just created 139 // std::string str_objpath = objPath.str; // keep a copy for 140 // constructing response message 141 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; // str_objpath; 142 for (auto &interface : interfaces_properties) 143 { 144 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 145 146 if (interface.first == 147 "xyz.openbmc_project.Software.Activation") 148 { 149 // cancel timer only when 150 // xyz.openbmc_project.Software.Activation interface is 151 // added 152 boost::system::error_code ec; 153 timeout.cancel(ec); 154 if (ec) 155 { 156 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 157 } 158 UpdateService::activateImage(objPath.str); // str_objpath); 159 res.jsonValue = redfish::messages::success(); 160 BMCWEB_LOG_DEBUG << "ending response"; 161 res.end(); 162 fwUpdateMatcher = nullptr; 163 } 164 } 165 }; 166 167 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>( 168 *crow::connections::systemBus, 169 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 170 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 171 callback); 172 173 std::string filepath( 174 "/tmp/images/" + 175 boost::uuids::to_string(boost::uuids::random_generator()())); 176 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 177 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 178 std::ofstream::trunc); 179 out << req.body; 180 out.close(); 181 BMCWEB_LOG_DEBUG << "file upload complete!!"; 182 } 183 }; 184 185 class SoftwareInventoryCollection : public Node 186 { 187 public: 188 template <typename CrowApp> 189 SoftwareInventoryCollection(CrowApp &app) : 190 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") 191 { 192 Node::json["@odata.type"] = 193 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 194 Node::json["@odata.id"] = "/redfish/v1/UpdateService/FirmwareInventory"; 195 Node::json["@odata.context"] = 196 "/redfish/v1/" 197 "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection"; 198 Node::json["Name"] = "Software Inventory Collection"; 199 200 entityPrivileges = { 201 {boost::beast::http::verb::get, {{"Login"}}}, 202 {boost::beast::http::verb::head, {{"Login"}}}, 203 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 204 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 205 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 206 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 207 } 208 209 private: 210 void doGet(crow::Response &res, const crow::Request &req, 211 const std::vector<std::string> ¶ms) override 212 { 213 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 214 res.jsonValue = Node::json; 215 216 crow::connections::systemBus->async_method_call( 217 [asyncResp]( 218 const boost::system::error_code ec, 219 const std::vector<std::pair< 220 std::string, std::vector<std::pair< 221 std::string, std::vector<std::string>>>>> 222 &subtree) { 223 if (ec) 224 { 225 asyncResp->res.result( 226 boost::beast::http::status::internal_server_error); 227 return; 228 } 229 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 230 asyncResp->res.jsonValue["Members@odata.count"] = 0; 231 232 for (auto &obj : subtree) 233 { 234 const std::vector< 235 std::pair<std::string, std::vector<std::string>>> 236 &connections = obj.second; 237 238 for (auto &conn : connections) 239 { 240 const std::string &connectionName = conn.first; 241 BMCWEB_LOG_DEBUG << "connectionName = " 242 << connectionName; 243 BMCWEB_LOG_DEBUG << "obj.first = " << obj.first; 244 245 crow::connections::systemBus->async_method_call( 246 [asyncResp]( 247 const boost::system::error_code error_code, 248 const VariantType &activation) { 249 BMCWEB_LOG_DEBUG 250 << "safe returned in lambda function"; 251 if (error_code) 252 { 253 asyncResp->res.result( 254 boost::beast::http::status:: 255 internal_server_error); 256 return; 257 } 258 259 const std::string *sw_inv_purpose = 260 mapbox::getPtr<const std::string>( 261 activation); 262 if (sw_inv_purpose == nullptr) 263 { 264 asyncResp->res.result( 265 boost::beast::http::status:: 266 internal_server_error); 267 return; 268 } 269 std::size_t last_pos = 270 sw_inv_purpose->rfind("."); 271 if (last_pos == std::string::npos) 272 { 273 asyncResp->res.result( 274 boost::beast::http::status:: 275 internal_server_error); 276 return; 277 } 278 nlohmann::json &members = 279 asyncResp->res.jsonValue["Members"]; 280 members.push_back( 281 {{"@odata.id", "/redfish/v1/UpdateService/" 282 "FirmwareInventory/" + 283 sw_inv_purpose->substr( 284 last_pos + 1)}}); 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 Node::json["@odata.type"] = 314 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 315 Node::json["@odata.context"] = 316 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 317 Node::json["Name"] = "Software Inventory"; 318 Node::json["Updateable"] = false; 319 Node::json["Status"]["Health"] = "OK"; 320 Node::json["Status"]["HealthRollup"] = "OK"; 321 Node::json["Status"]["State"] = "Enabled"; 322 entityPrivileges = { 323 {boost::beast::http::verb::get, {{"Login"}}}, 324 {boost::beast::http::verb::head, {{"Login"}}}, 325 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 326 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 327 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 328 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 329 } 330 331 private: 332 void doGet(crow::Response &res, const crow::Request &req, 333 const std::vector<std::string> ¶ms) override 334 { 335 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 336 res.jsonValue = Node::json; 337 338 if (params.size() != 1) 339 { 340 res.result(boost::beast::http::status::internal_server_error); 341 res.jsonValue = messages::internalError(); 342 res.end(); 343 return; 344 } 345 346 std::shared_ptr<std::string> sw_id = 347 std::make_shared<std::string>(params[0]); 348 349 res.jsonValue["@odata.id"] = 350 "/redfish/v1/UpdateService/FirmwareInventory/" + *sw_id; 351 352 crow::connections::systemBus->async_method_call( 353 [asyncResp, sw_id]( 354 const boost::system::error_code ec, 355 const std::vector<std::pair< 356 std::string, std::vector<std::pair< 357 std::string, std::vector<std::string>>>>> 358 &subtree) { 359 BMCWEB_LOG_DEBUG << "doGet callback..."; 360 if (ec) 361 { 362 asyncResp->res.result( 363 boost::beast::http::status::internal_server_error); 364 return; 365 } 366 367 for (const std::pair< 368 std::string, 369 std::vector< 370 std::pair<std::string, std::vector<std::string>>>> 371 &obj : subtree) 372 { 373 if (boost::ends_with(obj.first, *sw_id) != true) 374 { 375 continue; 376 } 377 378 if (obj.second.size() <= 1) 379 { 380 continue; 381 } 382 383 crow::connections::systemBus->async_method_call( 384 [asyncResp, 385 sw_id](const boost::system::error_code error_code, 386 const boost::container::flat_map< 387 std::string, VariantType> &propertiesList) { 388 if (error_code) 389 { 390 asyncResp->res.result( 391 boost::beast::http::status:: 392 internal_server_error); 393 return; 394 } 395 boost::container::flat_map< 396 std::string, VariantType>::const_iterator it = 397 propertiesList.find("Purpose"); 398 if (it == propertiesList.end()) 399 { 400 BMCWEB_LOG_DEBUG 401 << "Can't find property \"Purpose\"!"; 402 asyncResp->res.result( 403 boost::beast::http::status:: 404 internal_server_error); 405 return; 406 } 407 const std::string *sw_inv_purpose = 408 mapbox::getPtr<const std::string>(it->second); 409 if (sw_inv_purpose == nullptr) 410 { 411 BMCWEB_LOG_DEBUG 412 << "wrong types for property\"Purpose\"!"; 413 asyncResp->res.result( 414 boost::beast::http::status:: 415 internal_server_error); 416 return; 417 } 418 419 BMCWEB_LOG_DEBUG << "sw_inv_purpose = " 420 << *sw_inv_purpose; 421 if (boost::ends_with(*sw_inv_purpose, "." + *sw_id)) 422 { 423 it = propertiesList.find("Version"); 424 if (it == propertiesList.end()) 425 { 426 BMCWEB_LOG_DEBUG 427 << "Can't find property \"Version\"!"; 428 asyncResp->res.result( 429 boost::beast::http::status:: 430 internal_server_error); 431 return; 432 } 433 434 const std::string *version = 435 mapbox::getPtr<const std::string>( 436 it->second); 437 438 if (version != nullptr) 439 { 440 BMCWEB_LOG_DEBUG 441 << "Can't find property \"Version\"!"; 442 asyncResp->res.result( 443 boost::beast::http::status:: 444 internal_server_error); 445 return; 446 } 447 asyncResp->res.jsonValue["Version"] = *version; 448 asyncResp->res.jsonValue["Id"] = *sw_id; 449 } 450 }, 451 obj.second[0].first, obj.first, 452 "org.freedesktop.DBus.Properties", "GetAll", 453 "xyz.openbmc_project.Software.Version"); 454 } 455 }, 456 "xyz.openbmc_project.ObjectMapper", 457 "/xyz/openbmc_project/object_mapper", 458 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 459 "/xyz/openbmc_project/software", int32_t(1), 460 std::array<const char *, 1>{ 461 "xyz.openbmc_project.Software.Version"}); 462 } 463 }; 464 465 } // namespace redfish 466