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