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