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 #include <boost/container/flat_map.hpp> 20 21 namespace redfish { 22 static std::unique_ptr<sdbusplus::bus::match::match> fwUpdateMatcher; 23 24 class UpdateService : public Node { 25 public: 26 UpdateService(CrowApp &app) : Node(app, "/redfish/v1/UpdateService/") { 27 Node::json["@odata.type"] = "#UpdateService.v1_2_0.UpdateService"; 28 Node::json["@odata.id"] = "/redfish/v1/UpdateService"; 29 Node::json["@odata.context"] = 30 "/redfish/v1/$metadata#UpdateService.UpdateService"; 31 Node::json["Id"] = "UpdateService"; 32 Node::json["Description"] = "Service for Software Update"; 33 Node::json["Name"] = "Update Service"; 34 Node::json["HttpPushUri"] = "/redfish/v1/UpdateService"; 35 // UpdateService cannot be disabled 36 Node::json["ServiceEnabled"] = true; 37 Node::json["FirmwareInventory"] = { 38 {"@odata.id", "/redfish/v1/UpdateService/FirmwareInventory"}}; 39 40 entityPrivileges = { 41 {boost::beast::http::verb::get, {{"Login"}}}, 42 {boost::beast::http::verb::head, {{"Login"}}}, 43 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 44 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 45 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 46 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 47 } 48 49 private: 50 void doGet(crow::Response &res, const crow::Request &req, 51 const std::vector<std::string> ¶ms) override { 52 res.jsonValue = Node::json; 53 res.end(); 54 } 55 static void activateImage(const std::string &objPath) { 56 crow::connections::systemBus->async_method_call( 57 [objPath](const boost::system::error_code error_code) { 58 if (error_code) { 59 BMCWEB_LOG_DEBUG << "error_code = " << error_code; 60 BMCWEB_LOG_DEBUG << "error msg = " << error_code.message(); 61 } 62 }, 63 "xyz.openbmc_project.Software.BMC.Updater", objPath, 64 "org.freedesktop.DBus.Properties", "Set", 65 "xyz.openbmc_project.Software.Activation", "RequestedActivation", 66 sdbusplus::message::variant<std::string>( 67 "xyz.openbmc_project.Software.Activation.RequestedActivations." 68 "Active")); 69 } 70 void doPost(crow::Response &res, const crow::Request &req, 71 const std::vector<std::string> ¶ms) override { 72 BMCWEB_LOG_DEBUG << "doPost..."; 73 74 // Only allow one FW update at a time 75 if (fwUpdateMatcher != nullptr) { 76 res.addHeader("Retry-After", "30"); 77 res.result(boost::beast::http::status::service_unavailable); 78 res.jsonValue = messages::serviceTemporarilyUnavailable("3"); 79 res.end(); 80 return; 81 } 82 // Make this const static so it survives outside this method 83 static boost::asio::deadline_timer timeout(*req.ioService, 84 boost::posix_time::seconds(5)); 85 86 timeout.expires_from_now(boost::posix_time::seconds(5)); 87 88 timeout.async_wait([&res](const boost::system::error_code &ec) { 89 fwUpdateMatcher = nullptr; 90 if (ec == boost::asio::error::operation_aborted) { 91 // expected, we were canceled before the timer completed. 92 return; 93 } 94 BMCWEB_LOG_ERROR << "Timed out waiting for firmware object being created"; 95 BMCWEB_LOG_ERROR << "FW image may has already been uploaded to server"; 96 if (ec) { 97 BMCWEB_LOG_ERROR << "Async_wait failed" << ec; 98 return; 99 } 100 101 res.result(boost::beast::http::status::internal_server_error); 102 res.jsonValue = redfish::messages::internalError(); 103 res.end(); 104 }); 105 106 auto callback = [&res](sdbusplus::message::message &m) { 107 BMCWEB_LOG_DEBUG << "Match fired"; 108 bool flag = false; 109 110 if (m.is_method_error()) { 111 BMCWEB_LOG_DEBUG << "Dbus method error!!!"; 112 res.end(); 113 return; 114 } 115 std::vector<std::pair< 116 std::string, 117 std::vector<std::pair<std::string, 118 sdbusplus::message::variant<std::string>>>>> 119 interfaces_properties; 120 121 sdbusplus::message::object_path objPath; 122 123 m.read(objPath, interfaces_properties); // Read in the object path 124 // that was just created 125 // std::string str_objpath = objPath.str; // keep a copy for 126 // constructing response message 127 BMCWEB_LOG_DEBUG << "obj path = " << objPath.str; // str_objpath; 128 for (auto &interface : interfaces_properties) { 129 BMCWEB_LOG_DEBUG << "interface = " << interface.first; 130 131 if (interface.first == "xyz.openbmc_project.Software.Activation") { 132 // cancel timer only when xyz.openbmc_project.Software.Activation 133 // interface is added 134 boost::system::error_code ec; 135 timeout.cancel(ec); 136 if (ec) { 137 BMCWEB_LOG_ERROR << "error canceling timer " << ec; 138 } 139 UpdateService::activateImage(objPath.str); // str_objpath); 140 res.jsonValue = redfish::messages::success(); 141 BMCWEB_LOG_DEBUG << "ending response"; 142 res.end(); 143 fwUpdateMatcher = nullptr; 144 } 145 } 146 }; 147 148 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match::match>( 149 *crow::connections::systemBus, 150 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 151 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 152 callback); 153 154 std::string filepath( 155 "/tmp/images/" + 156 boost::uuids::to_string(boost::uuids::random_generator()())); 157 BMCWEB_LOG_DEBUG << "Writing file to " << filepath; 158 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 159 std::ofstream::trunc); 160 out << req.body; 161 out.close(); 162 BMCWEB_LOG_DEBUG << "file upload complete!!"; 163 } 164 }; 165 166 class SoftwareInventoryCollection : public Node { 167 public: 168 template <typename CrowApp> 169 SoftwareInventoryCollection(CrowApp &app) 170 : Node(app, "/redfish/v1/UpdateService/FirmwareInventory/") { 171 Node::json["@odata.type"] = 172 "#SoftwareInventoryCollection.SoftwareInventoryCollection"; 173 Node::json["@odata.id"] = "/redfish/v1/UpdateService/FirmwareInventory"; 174 Node::json["@odata.context"] = 175 "/redfish/v1/" 176 "$metadata#SoftwareInventoryCollection.SoftwareInventoryCollection"; 177 Node::json["Name"] = "Software Inventory Collection"; 178 179 entityPrivileges = { 180 {boost::beast::http::verb::get, {{"Login"}}}, 181 {boost::beast::http::verb::head, {{"Login"}}}, 182 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 183 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 184 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 185 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 186 } 187 188 private: 189 void doGet(crow::Response &res, const crow::Request &req, 190 const std::vector<std::string> ¶ms) override { 191 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 192 res.jsonValue = Node::json; 193 194 crow::connections::systemBus->async_method_call( 195 [asyncResp]( 196 const boost::system::error_code ec, 197 const std::vector<std::pair< 198 std::string, 199 std::vector<std::pair<std::string, std::vector<std::string>>>>> 200 &subtree) { 201 if (ec) { 202 asyncResp->res.result( 203 boost::beast::http::status::internal_server_error); 204 return; 205 } 206 asyncResp->res.jsonValue["Members"] = nlohmann::json::array(); 207 asyncResp->res.jsonValue["Members@odata.count"] = 0; 208 209 for (auto &obj : subtree) { 210 const std::vector<std::pair<std::string, std::vector<std::string>>> 211 &connections = obj.second; 212 213 for (auto &conn : connections) { 214 const std::string &connectionName = conn.first; 215 BMCWEB_LOG_DEBUG << "connectionName = " << connectionName; 216 BMCWEB_LOG_DEBUG << "obj.first = " << obj.first; 217 218 crow::connections::systemBus->async_method_call( 219 [asyncResp](const boost::system::error_code error_code, 220 const VariantType &activation) { 221 BMCWEB_LOG_DEBUG << "safe returned in lambda function"; 222 if (error_code) { 223 asyncResp->res.result( 224 boost::beast::http::status::internal_server_error); 225 return; 226 } 227 228 const std::string *sw_inv_purpose = 229 mapbox::getPtr<const std::string>(activation); 230 if (sw_inv_purpose == nullptr) { 231 asyncResp->res.result( 232 boost::beast::http::status::internal_server_error); 233 return; 234 } 235 std::size_t last_pos = sw_inv_purpose->rfind("."); 236 if (last_pos == std::string::npos) { 237 asyncResp->res.result( 238 boost::beast::http::status::internal_server_error); 239 return; 240 } 241 nlohmann::json &members = 242 asyncResp->res.jsonValue["Members"]; 243 members.push_back( 244 {{"@odata.id", 245 "/redfish/v1/UpdateService/FirmwareInventory/" + 246 sw_inv_purpose->substr(last_pos + 1)}}); 247 asyncResp->res.jsonValue["Members@odata.count"] = 248 members.size(); 249 }, 250 connectionName, obj.first, "org.freedesktop.DBus.Properties", 251 "Get", "xyz.openbmc_project.Software.Activation", 252 "Activation"); 253 } 254 } 255 }, 256 "xyz.openbmc_project.ObjectMapper", 257 "/xyz/openbmc_project/object_mapper", 258 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 259 "/xyz/openbmc_project/software", int32_t(1), 260 std::array<const char *, 1>{"xyz.openbmc_project.Software.Version"}); 261 } 262 }; 263 264 class SoftwareInventory : public Node { 265 public: 266 template <typename CrowApp> 267 SoftwareInventory(CrowApp &app) 268 : Node(app, "/redfish/v1/UpdateService/FirmwareInventory/<str>/", 269 std::string()) { 270 Node::json["@odata.type"] = "#SoftwareInventory.v1_1_0.SoftwareInventory"; 271 Node::json["@odata.context"] = 272 "/redfish/v1/$metadata#SoftwareInventory.SoftwareInventory"; 273 Node::json["Name"] = "Software Inventory"; 274 Node::json["Updateable"] = false; 275 Node::json["Status"]["Health"] = "OK"; 276 Node::json["Status"]["HealthRollup"] = "OK"; 277 Node::json["Status"]["State"] = "Enabled"; 278 entityPrivileges = { 279 {boost::beast::http::verb::get, {{"Login"}}}, 280 {boost::beast::http::verb::head, {{"Login"}}}, 281 {boost::beast::http::verb::patch, {{"ConfigureComponents"}}}, 282 {boost::beast::http::verb::put, {{"ConfigureComponents"}}}, 283 {boost::beast::http::verb::delete_, {{"ConfigureComponents"}}}, 284 {boost::beast::http::verb::post, {{"ConfigureComponents"}}}}; 285 } 286 287 private: 288 void doGet(crow::Response &res, const crow::Request &req, 289 const std::vector<std::string> ¶ms) override { 290 std::shared_ptr<AsyncResp> asyncResp = std::make_shared<AsyncResp>(res); 291 res.jsonValue = Node::json; 292 293 if (params.size() != 1) { 294 res.result(boost::beast::http::status::internal_server_error); 295 res.jsonValue = messages::internalError(); 296 res.end(); 297 return; 298 } 299 300 std::shared_ptr<std::string> sw_id = 301 std::make_shared<std::string>(params[0]); 302 303 res.jsonValue["@odata.id"] = 304 "/redfish/v1/UpdateService/FirmwareInventory/" + *sw_id; 305 306 crow::connections::systemBus->async_method_call( 307 [asyncResp, sw_id]( 308 const boost::system::error_code ec, 309 const std::vector<std::pair< 310 std::string, 311 std::vector<std::pair<std::string, std::vector<std::string>>>>> 312 &subtree) { 313 BMCWEB_LOG_DEBUG << "doGet callback..."; 314 if (ec) { 315 asyncResp->res.result( 316 boost::beast::http::status::internal_server_error); 317 return; 318 } 319 320 for (const std::pair<std::string, 321 std::vector<std::pair<std::string, 322 std::vector<std::string>>>> 323 &obj : subtree) { 324 if (boost::ends_with(obj.first, *sw_id) != true) { 325 continue; 326 } 327 328 if (obj.second.size() <= 1) { 329 continue; 330 } 331 332 crow::connections::systemBus->async_method_call( 333 [asyncResp, sw_id]( 334 const boost::system::error_code error_code, 335 const boost::container::flat_map<std::string, VariantType> 336 &propertiesList) { 337 if (error_code) { 338 asyncResp->res.result( 339 boost::beast::http::status::internal_server_error); 340 return; 341 } 342 boost::container::flat_map<std::string, 343 VariantType>::const_iterator it = 344 propertiesList.find("Purpose"); 345 if (it == propertiesList.end()) { 346 BMCWEB_LOG_DEBUG << "Can't find property \"Purpose\"!"; 347 asyncResp->res.result( 348 boost::beast::http::status::internal_server_error); 349 return; 350 } 351 const std::string *sw_inv_purpose = 352 mapbox::getPtr<const std::string>(it->second); 353 if (sw_inv_purpose == nullptr) { 354 BMCWEB_LOG_DEBUG << "wrong types for property\"Purpose\"!"; 355 asyncResp->res.result( 356 boost::beast::http::status::internal_server_error); 357 return; 358 } 359 360 BMCWEB_LOG_DEBUG << "sw_inv_purpose = " << *sw_inv_purpose; 361 if (boost::ends_with(*sw_inv_purpose, "." + *sw_id)) { 362 it = propertiesList.find("Version"); 363 if (it == propertiesList.end()) { 364 BMCWEB_LOG_DEBUG << "Can't find property \"Version\"!"; 365 asyncResp->res.result( 366 boost::beast::http::status::internal_server_error); 367 return; 368 } 369 370 const std::string *version = 371 mapbox::getPtr<const std::string>(it->second); 372 373 if (version != nullptr) { 374 BMCWEB_LOG_DEBUG << "Can't find property \"Version\"!"; 375 asyncResp->res.result( 376 boost::beast::http::status::internal_server_error); 377 return; 378 } 379 asyncResp->res.jsonValue["Version"] = *version; 380 asyncResp->res.jsonValue["Id"] = *sw_id; 381 } 382 }, 383 obj.second[0].first, obj.first, 384 "org.freedesktop.DBus.Properties", "GetAll", 385 "xyz.openbmc_project.Software.Version"); 386 } 387 }, 388 "xyz.openbmc_project.ObjectMapper", 389 "/xyz/openbmc_project/object_mapper", 390 "xyz.openbmc_project.ObjectMapper", "GetSubTree", 391 "/xyz/openbmc_project/software", int32_t(1), 392 std::array<const char *, 1>{"xyz.openbmc_project.Software.Version"}); 393 } 394 }; 395 396 } // namespace redfish 397