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 bool flag = false; 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 void doGet(crow::Response &res, const crow::Request &req, 325 const std::vector<std::string> ¶ms) override 326 { 327 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 328 res.jsonValue["@odata.type"] = 329 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 330 res.jsonValue["@odata.context"] = 331 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 332 res.jsonValue["Name"] = "Software Inventory"; 333 res.jsonValue["Updateable"] = false; 334 res.jsonValue["Status"]["Health"] = "OK"; 335 res.jsonValue["Status"]["HealthRollup"] = "OK"; 336 res.jsonValue["Status"]["State"] = "Enabled"; 337 338 if (params.size() != 1) 339 { 340 messages::internalError(res); 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 messages::internalError(asyncResp->res); 362 return; 363 } 364 365 for (const std::pair< 366 std::string, 367 std::vector< 368 std::pair<std::string, std::vector<std::string>>>> 369 &obj : subtree) 370 { 371 if (boost::ends_with(obj.first, *swId) != true) 372 { 373 continue; 374 } 375 376 if (obj.second.size() < 1) 377 { 378 continue; 379 } 380 381 crow::connections::systemBus->async_method_call( 382 [asyncResp, 383 swId](const boost::system::error_code error_code, 384 const boost::container::flat_map< 385 std::string, VariantType> &propertiesList) { 386 if (error_code) 387 { 388 messages::internalError(asyncResp->res); 389 return; 390 } 391 boost::container::flat_map< 392 std::string, VariantType>::const_iterator it = 393 propertiesList.find("Purpose"); 394 if (it == propertiesList.end()) 395 { 396 BMCWEB_LOG_DEBUG 397 << "Can't find property \"Purpose\"!"; 398 messages::propertyMissing(asyncResp->res, 399 "Purpose"); 400 return; 401 } 402 const std::string *swInvPurpose = 403 std::get_if<std::string>(&it->second); 404 if (swInvPurpose == nullptr) 405 { 406 BMCWEB_LOG_DEBUG 407 << "wrong types for property\"Purpose\"!"; 408 messages::propertyValueTypeError(asyncResp->res, 409 "", "Purpose"); 410 return; 411 } 412 413 BMCWEB_LOG_DEBUG << "swInvPurpose = " 414 << *swInvPurpose; 415 it = propertiesList.find("Version"); 416 if (it == propertiesList.end()) 417 { 418 BMCWEB_LOG_DEBUG 419 << "Can't find property \"Version\"!"; 420 messages::propertyMissing(asyncResp->res, 421 "Version"); 422 return; 423 } 424 425 BMCWEB_LOG_DEBUG << "Version found!"; 426 427 const std::string *version = 428 std::get_if<std::string>(&it->second); 429 430 if (version == nullptr) 431 { 432 BMCWEB_LOG_DEBUG 433 << "Can't find property \"Version\"!"; 434 435 messages::propertyValueTypeError(asyncResp->res, 436 "", "Version"); 437 return; 438 } 439 asyncResp->res.jsonValue["Version"] = *version; 440 asyncResp->res.jsonValue["Id"] = *swId; 441 }, 442 obj.second[0].first, obj.first, 443 "org.freedesktop.DBus.Properties", "GetAll", 444 "xyz.openbmc_project.Software.Version"); 445 } 446 }, 447 "xyz.openbmc_project.ObjectMapper", 448 "/xyz/openbmc_project/object_mapper", 449 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 450 "/xyz/openbmc_project/software", int32_t(1), 451 std::array<const char *, 1>{ 452 "xyz.openbmc_project.Software.Version"}); 453 } 454 }; 455 456 } // namespace redfish 457