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