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 25 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 26 27 class UpdateService : public Node 28 { 29 public: 30 UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") 31 { 32 entityPrivileges = { 33 {boost::beast::http::verb::get, {{"Login"}}}, 34 {boost::beast::http::verb::head, {{"Login"}}}, 35 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 36 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 37 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 38 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 39 } 40 41 private: 42 void doGet(crow::Response &res, const crow::Request &req, 43 const std::vector<std::string> ¶ms) override 44 { 45 res.jsonValue["@odata.type"] = "#UpdateService.v1_2_0.UpdateService"; 46 res.jsonValue["@odata.id"] = "/redfish/v1/UpdateService"; 47 res.jsonValue["@odata.context"] = 48 "/redfish/v1/$metadata#UpdateService.UpdateService"; 49 res.jsonValue["Id"] = "UpdateService"; 50 res.jsonValue["Description"] = "Service for Software Update"; 51 res.jsonValue["Name"] = "Update Service"; 52 res.jsonValue["HttpPushUri"] = "/redfish/v1/UpdateService"; 53 // UpdateService cannot be disabled 54 res.jsonValue["ServiceEnabled"] = true; 55 res.jsonValue["FirmwareInventory"] = { 56 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 57 res.end(); 58 } 59 static void activateImage(const std::string &objPath) 60 { 61 crow::connections::systemBus->async_method_call( 62 [objPath](const boost::system::error_code error_code) { 63 if (error_code) 64 { 65 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 66 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); 67 } 68 }, 69 "xyz.openbmc_project.Software.BMC.Updater", objPath, 70 "org.freedesktop.DBus.Properties", "Set", 71 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 72 sdbusplus::message::variant<std::string>( 73 "xyz.openbmc_project.Software.Activation.RequestedActivations." 74 "Active")); 75 } 76 void doPost(crow::Response &res, const crow::Request &req, 77 const std::vector<std::string> ¶ms) override 78 { 79 BMCWEB_LOG_DEBUG << "doPost..."; 80 81 // Only allow one FW update at a time 82 if (fwUpdateMatcher != nullptr) 83 { 84 res.addHeader("Retry-After", "30"); 85 messages::serviceTemporarilyUnavailable(res, "3"); 86 res.end(); 87 return; 88 } 89 // Make this const static so it survives outside this method 90 static boost::asio::deadline_timer timeout( 91 *req.ioService, boost::posix_time::seconds(5)); 92 93 timeout.expires_from_now(boost::posix_time::seconds(5)); 94 95 timeout.async_wait([&res](const boost::system::error_code &ec) { 96 fwUpdateMatcher = nullptr; 97 if (ec == boost::asio::error::operation_aborted) 98 { 99 // expected, we were canceled before the timer completed. 100 return; 101 } 102 BMCWEB_LOG_ERROR 103 << "Timed out waiting for firmware object being created"; 104 BMCWEB_LOG_ERROR 105 << "FW image may has already been uploaded to server"; 106 if (ec) 107 { 108 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 109 return; 110 } 111 112 redfish::messages::internalError(res); 113 res.end(); 114 }); 115 116 auto callback = [&res](sdbusplus::message::message &m) { 117 BMCWEB_LOG_DEBUG << "Match fired"; 118 bool flag = false; 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< 129 std::string, sdbusplus::message::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 sdbusplus::message::variant_ns::get_if< 264 std::string>(&activation); 265 if (swActivationStatus == nullptr) 266 { 267 messages::internalError(asyncResp->res); 268 return; 269 } 270 if (swActivationStatus != nullptr && 271 *swActivationStatus != 272 "xyz.openbmc_project.Software." 273 "Activation." 274 "Activations.Active") 275 { 276 // The activation status of this software is 277 // not currently active, so does not need to 278 // be listed in the response 279 return; 280 } 281 nlohmann::json &members = 282 asyncResp->res.jsonValue["Members"]; 283 members.push_back( 284 {{"@odata.id", "/redfish/v1/UpdateService/" 285 "FirmwareInventory/" + 286 swId}}); 287 asyncResp->res 288 .jsonValue["Members@odata.count"] = 289 members.size(); 290 }, 291 connectionName, obj.first, 292 "org.freedesktop.DBus.Properties", "Get", 293 "xyz.openbmc_project.Software.Activation", 294 "Activation"); 295 } 296 } 297 }, 298 "xyz.openbmc_project.ObjectMapper", 299 "/xyz/openbmc_project/object_mapper", 300 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 301 "/xyz/openbmc_project/software", int32_t(1), 302 std::array<const char *, 1>{ 303 "xyz.openbmc_project.Software.Version"}); 304 } 305 }; 306 307 class SoftwareInventory : public Node 308 { 309 public: 310 template <typename CrowApp> 311 SoftwareInventory(CrowApp &app) : 312 Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 313 std::string()) 314 { 315 entityPrivileges = { 316 {boost::beast::http::verb::get, {{"Login"}}}, 317 {boost::beast::http::verb::head, {{"Login"}}}, 318 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 319 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 320 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 321 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 322 } 323 324 private: 325 void doGet(crow::Response &res, const crow::Request &req, 326 const std::vector<std::string> ¶ms) override 327 { 328 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 329 res.jsonValue["@odata.type"] = 330 "#SoftwareInventory.v1_1_0.SoftwareInventory"; 331 res.jsonValue["@odata.context"] = 332 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 333 res.jsonValue["Name"] = "Software Inventory"; 334 res.jsonValue["Updateable"] = false; 335 res.jsonValue["Status"]["Health"] = "OK"; 336 res.jsonValue["Status"]["HealthRollup"] = "OK"; 337 res.jsonValue["Status"]["State"] = "Enabled"; 338 339 if (params.size() != 1) 340 { 341 messages::internalError(res); 342 res.end(); 343 return; 344 } 345 346 std::shared_ptr<std::string> swId = 347 std::make_shared<std::string>(params[0]); 348 349 res.jsonValue["@odata.id"] = 350 "/redfish/v1/UpdateService/FirmwareInventory/" + *swId; 351 352 crow::connections::systemBus->async_method_call( 353 [asyncResp, swId]( 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 messages::internalError(asyncResp->res); 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 messages::internalError(asyncResp->res); 390 return; 391 } 392 boost::container::flat_map< 393 std::string, VariantType>::const_iterator it = 394 propertiesList.find("Purpose"); 395 if (it == propertiesList.end()) 396 { 397 BMCWEB_LOG_DEBUG 398 << "Can't find property \"Purpose\"!"; 399 messages::propertyMissing(asyncResp->res, 400 "Purpose"); 401 return; 402 } 403 const std::string *swInvPurpose = 404 sdbusplus::message::variant_ns::get_if< 405 std::string>(&it->second); 406 if (swInvPurpose == nullptr) 407 { 408 BMCWEB_LOG_DEBUG 409 << "wrong types for property\"Purpose\"!"; 410 messages::propertyValueTypeError(asyncResp->res, 411 "", "Purpose"); 412 return; 413 } 414 415 BMCWEB_LOG_DEBUG << "swInvPurpose = " 416 << *swInvPurpose; 417 it = propertiesList.find("Version"); 418 if (it == propertiesList.end()) 419 { 420 BMCWEB_LOG_DEBUG 421 << "Can't find property \"Version\"!"; 422 messages::propertyMissing(asyncResp->res, 423 "Version"); 424 return; 425 } 426 427 BMCWEB_LOG_DEBUG << "Version found!"; 428 429 const std::string *version = 430 sdbusplus::message::variant_ns::get_if< 431 std::string>(&it->second); 432 433 if (version == nullptr) 434 { 435 BMCWEB_LOG_DEBUG 436 << "Can't find property \"Version\"!"; 437 438 messages::propertyValueTypeError(asyncResp->res, 439 "", "Version"); 440 return; 441 } 442 asyncResp->res.jsonValue["Version"] = *version; 443 asyncResp->res.jsonValue["Id"] = *swId; 444 }, 445 obj.second[0].first, obj.first, 446 "org.freedesktop.DBus.Properties", "GetAll", 447 "xyz.openbmc_project.Software.Version"); 448 } 449 }, 450 "xyz.openbmc_project.ObjectMapper", 451 "/xyz/openbmc_project/object_mapper", 452 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 453 "/xyz/openbmc_project/software", int32_t(1), 454 std::array<const char *, 1>{ 455 "xyz.openbmc_project.Software.Version"}); 456 } 457 }; 458 459 } // namespace redfish 460