1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 #pragma once 4 5 #include "app.hpp" 6 #include "dbus_singleton.hpp" 7 #include "dbus_utility.hpp" 8 #include "ossl_random.hpp" 9 10 #include <sdbusplus/bus/match.hpp> 11 12 #include <cstdio> 13 #include <fstream> 14 #include <memory> 15 #include <ranges> 16 17 namespace crow 18 { 19 namespace image_upload 20 { 21 22 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 23 static std::unique_ptr<sdbusplus::bus::match_t> fwUpdateMatcher; 24 25 inline void 26 uploadImageHandler(const crow::Request& req, 27 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) 28 { 29 // Only allow one FW update at a time 30 if (fwUpdateMatcher != nullptr) 31 { 32 asyncResp->res.addHeader("Retry-After", "30"); 33 asyncResp->res.result(boost::beast::http::status::service_unavailable); 34 return; 35 } 36 if (req.ioService == nullptr) 37 { 38 asyncResp->res.result( 39 boost::beast::http::status::internal_server_error); 40 return; 41 } 42 // Make this const static so it survives outside this method 43 static boost::asio::steady_timer timeout(*req.ioService, 44 std::chrono::seconds(5)); 45 46 timeout.expires_after(std::chrono::seconds(15)); 47 48 auto timeoutHandler = [asyncResp](const boost::system::error_code& ec) { 49 fwUpdateMatcher = nullptr; 50 if (ec == boost::asio::error::operation_aborted) 51 { 52 // expected, we were canceled before the timer completed. 53 return; 54 } 55 BMCWEB_LOG_ERROR("Timed out waiting for Version interface"); 56 57 if (ec) 58 { 59 BMCWEB_LOG_ERROR("Async_wait failed {}", ec); 60 return; 61 } 62 63 asyncResp->res.result(boost::beast::http::status::bad_request); 64 asyncResp->res.jsonValue["data"]["description"] = 65 "Version already exists or failed to be extracted"; 66 asyncResp->res.jsonValue["message"] = "400 Bad Request"; 67 asyncResp->res.jsonValue["status"] = "error"; 68 }; 69 70 std::function<void(sdbusplus::message_t&)> callback = 71 [asyncResp](sdbusplus::message_t& m) { 72 BMCWEB_LOG_DEBUG("Match fired"); 73 74 sdbusplus::message::object_path path; 75 dbus::utility::DBusInterfacesMap interfaces; 76 m.read(path, interfaces); 77 78 if (std::ranges::find_if(interfaces, [](const auto& i) { 79 return i.first == "xyz.openbmc_project.Software.Version"; 80 }) != interfaces.end()) 81 { 82 timeout.cancel(); 83 std::string leaf = path.filename(); 84 if (leaf.empty()) 85 { 86 leaf = path.str; 87 } 88 89 asyncResp->res.jsonValue["data"] = leaf; 90 asyncResp->res.jsonValue["message"] = "200 OK"; 91 asyncResp->res.jsonValue["status"] = "ok"; 92 BMCWEB_LOG_DEBUG("ending response"); 93 fwUpdateMatcher = nullptr; 94 } 95 }; 96 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 97 *crow::connections::systemBus, 98 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 99 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 100 callback); 101 102 std::string filepath("/tmp/images/" + bmcweb::getRandomUUID()); 103 BMCWEB_LOG_DEBUG("Writing file to {}", filepath); 104 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 105 std::ofstream::trunc); 106 out << req.body(); 107 out.close(); 108 timeout.async_wait(timeoutHandler); 109 } 110 111 inline void requestRoutes(App& app) 112 { 113 BMCWEB_ROUTE(app, "/upload/image/<str>") 114 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 115 .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)( 116 [](const crow::Request& req, 117 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 118 const std::string&) { uploadImageHandler(req, asyncResp); }); 119 120 BMCWEB_ROUTE(app, "/upload/image") 121 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 122 .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)( 123 [](const crow::Request& req, 124 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 125 uploadImageHandler(req, asyncResp); 126 }); 127 } 128 } // namespace image_upload 129 } // namespace crow 130