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
uploadImageHandler(const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)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
requestRoutes(App & app)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