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