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 interfacesProperties; 134 135 sdbusplus::message::object_path objPath; 136 137 m.read(objPath, interfacesProperties); // 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 : interfacesProperties) 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 *swInvPurpose = 260 mapbox::getPtr<const std::string>( 261 activation); 262 if (swInvPurpose == nullptr) 263 { 264 asyncResp->res.result( 265 boost::beast::http::status:: 266 internal_server_error); 267 return; 268 } 269 std::size_t last_pos = swInvPurpose->rfind("."); 270 if (last_pos == std::string::npos) 271 { 272 asyncResp->res.result( 273 boost::beast::http::status:: 274 internal_server_error); 275 return; 276 } 277 nlohmann::json &members = 278 asyncResp->res.jsonValue["Members"]; 279 members.push_back( 280 {{"@odata.id", 281 "/redfish/v1/UpdateService/" 282 "FirmwareInventory/" + 283 swInvPurpose->substr(last_pos + 1)}}); 284 asyncResp->res 285 .jsonValue["Members@odata.count"] = 286 members.size(); 287 }, 288 connectionName, obj.first, 289 "org.freedesktop.DBus.Properties", "Get", 290 "xyz.openbmc_project.Software.Activation", 291 "Activation"); 292 } 293 } 294 }, 295 "xyz.openbmc_project.ObjectMapper", 296 "/xyz/openbmc_project/object_mapper", 297 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 298 "/xyz/openbmc_project/software", int32_t(1), 299 std::array<const char *, 1>{ 300 "xyz.openbmc_project.Software.Version"}); 301 } 302 }; 303 304 class SoftwareInventory : public Node 305 { 306 public: 307 template <typename CrowApp> 308 SoftwareInventory(CrowApp &app) : 309 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 310 std::string()) 311 { 312 Node::json["@odata.type"] = 313 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 314 Node::json["@odata.context"] = 315 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 316 Node::json["Name"] = "Software Inventory"; 317 Node::json["Updateable"] = false; 318 Node::json["Status"]["Health"] = "OK"; 319 Node::json["Status"]["HealthRollup"] = "OK"; 320 Node::json["Status"]["State"] = "Enabled"; 321 entityPrivileges = { 322 {boost::beast::http::verb::get, {{"Login"}}}, 323 {boost::beast::http::verb::head, {{"Login"}}}, 324 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 325 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 326 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 327 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 328 } 329 330 private: 331 void doGet(crow::Response &res, const crow::Request &req, 332 const std::vector<std::string> ¶ms) override 333 { 334 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 335 res.jsonValue = Node::json; 336 337 if (params.size() != 1) 338 { 339 res.result(boost::beast::http::status::internal_server_error); 340 res.jsonValue = messages::internalError(); 341 res.end(); 342 return; 343 } 344 345 std::shared_ptr<std::string> swId = 346 std::make_shared<std::string>(params[0]); 347 348 res.jsonValue["@odata.id"] = 349 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 350 351 crow::connections::systemBus->async_method_call( 352 [asyncResp, swId]( 353 const boost::system::error_code ec, 354 const std::vector<std::pair< 355 std::string, std::vector<std::pair< 356 std::string, std::vector<std::string>>>>> 357 &subtree) { 358 BMCWEB_LOG_DEBUG << "doGet callback..."; 359 if (ec) 360 { 361 asyncResp->res.result( 362 boost::beast::http::status::internal_server_error); 363 return; 364 } 365 366 for (const std::pair< 367 std::string, 368 std::vector< 369 std::pair<std::string, std::vector<std::string>>>> 370 &obj : subtree) 371 { 372 if (boost::ends_with(obj.first, *swId) != true) 373 { 374 continue; 375 } 376 377 if (obj.second.size() <= 1) 378 { 379 continue; 380 } 381 382 crow::connections::systemBus->async_method_call( 383 [asyncResp, 384 swId](const boost::system::error_code error_code, 385 const boost::container::flat_map< 386 std::string, VariantType> &propertiesList) { 387 if (error_code) 388 { 389 asyncResp->res.result( 390 boost::beast::http::status:: 391 internal_server_error); 392 return; 393 } 394 boost::container::flat_map< 395 std::string, VariantType>::const_iterator it = 396 propertiesList.find("Purpose"); 397 if (it == propertiesList.end()) 398 { 399 BMCWEB_LOG_DEBUG 400 << "Can't find property \"Purpose\"!"; 401 asyncResp->res.result( 402 boost::beast::http::status:: 403 internal_server_error); 404 return; 405 } 406 const std::string *swInvPurpose = 407 mapbox::getPtr<const std::string>(it->second); 408 if (swInvPurpose == nullptr) 409 { 410 BMCWEB_LOG_DEBUG 411 << "wrong types for property\"Purpose\"!"; 412 asyncResp->res.result( 413 boost::beast::http::status:: 414 internal_server_error); 415 return; 416 } 417 418 BMCWEB_LOG_DEBUG << "swInvPurpose = " 419 << *swInvPurpose; 420 if (boost::ends_with(*swInvPurpose, "." + *swId)) 421 { 422 it = propertiesList.find("Version"); 423 if (it == propertiesList.end()) 424 { 425 BMCWEB_LOG_DEBUG 426 << "Can't find property \"Version\"!"; 427 asyncResp->res.result( 428 boost::beast::http::status:: 429 internal_server_error); 430 return; 431 } 432 433 const std::string *version = 434 mapbox::getPtr<const std::string>( 435 it->second); 436 437 if (version != nullptr) 438 { 439 BMCWEB_LOG_DEBUG 440 << "Can't find property \"Version\"!"; 441 asyncResp->res.result( 442 boost::beast::http::status:: 443 internal_server_error); 444 return; 445 } 446 asyncResp->res.jsonValue["Version"] = *version; 447 asyncResp->res.jsonValue["Id"] = *swId; 448 } 449 }, 450 obj.second[0].first, obj.first, 451 "org.freedesktop.DBus.Properties", "GetAll", 452 "xyz.openbmc_project.Software.Version"); 453 } 454 }, 455 "xyz.openbmc_project.ObjectMapper", 456 "/xyz/openbmc_project/object_mapper", 457 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 458 "/xyz/openbmc_project/software", int32_t(1), 459 std::array<const char *, 1>{ 460 "xyz.openbmc_project.Software.Version"}); 461 } 462 }; 463 464 } // namespace redfish 465