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