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 // Make this const static so it survives outside this method 35 static boost::asio::steady_timer timeout(*req.ioService, 36 std::chrono::seconds(5)); 37 38 timeout.expires_after(std::chrono::seconds(15)); 39 40 auto timeoutHandler = [asyncResp](const boost::system::error_code& ec) { 41 fwUpdateMatcher = nullptr; 42 if (ec == boost::asio::error::operation_aborted) 43 { 44 // expected, we were canceled before the timer completed. 45 return; 46 } 47 BMCWEB_LOG_ERROR("Timed out waiting for Version interface"); 48 49 if (ec) 50 { 51 BMCWEB_LOG_ERROR("Async_wait failed {}", ec); 52 return; 53 } 54 55 asyncResp->res.result(boost::beast::http::status::bad_request); 56 asyncResp->res.jsonValue["data"]["description"] = 57 "Version already exists or failed to be extracted"; 58 asyncResp->res.jsonValue["message"] = "400 Bad Request"; 59 asyncResp->res.jsonValue["status"] = "error"; 60 }; 61 62 std::function<void(sdbusplus::message_t&)> callback = 63 [asyncResp](sdbusplus::message_t& m) { 64 BMCWEB_LOG_DEBUG("Match fired"); 65 66 sdbusplus::message::object_path path; 67 dbus::utility::DBusInterfacesMap interfaces; 68 m.read(path, interfaces); 69 70 if (std::ranges::find_if(interfaces, [](const auto& i) { 71 return i.first == "xyz.openbmc_project.Software.Version"; 72 }) != interfaces.end()) 73 { 74 timeout.cancel(); 75 std::string leaf = path.filename(); 76 if (leaf.empty()) 77 { 78 leaf = path.str; 79 } 80 81 asyncResp->res.jsonValue["data"] = leaf; 82 asyncResp->res.jsonValue["message"] = "200 OK"; 83 asyncResp->res.jsonValue["status"] = "ok"; 84 BMCWEB_LOG_DEBUG("ending response"); 85 fwUpdateMatcher = nullptr; 86 } 87 }; 88 fwUpdateMatcher = std::make_unique<sdbusplus::bus::match_t>( 89 *crow::connections::systemBus, 90 "interface='org.freedesktop.DBus.ObjectManager',type='signal'," 91 "member='InterfacesAdded',path='/xyz/openbmc_project/software'", 92 callback); 93 94 std::string filepath("/tmp/images/" + bmcweb::getRandomUUID()); 95 BMCWEB_LOG_DEBUG("Writing file to {}", filepath); 96 std::ofstream out(filepath, std::ofstream::out | std::ofstream::binary | 97 std::ofstream::trunc); 98 out << req.body(); 99 out.close(); 100 timeout.async_wait(timeoutHandler); 101 } 102 103 inline void requestRoutes(App& app) 104 { 105 BMCWEB_ROUTE(app, "/upload/image/<str>") 106 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 107 .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)( 108 [](const crow::Request& req, 109 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, 110 const std::string&) { uploadImageHandler(req, asyncResp); }); 111 112 BMCWEB_ROUTE(app, "/upload/image") 113 .privileges({{"ConfigureComponents", "ConfigureManager"}}) 114 .methods(boost::beast::http::verb::post, boost::beast::http::verb::put)( 115 [](const crow::Request& req, 116 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) { 117 uploadImageHandler(req, asyncResp); 118 }); 119 } 120 } // namespace image_upload 121 } // namespace crow 122