xref: /openbmc/bmcweb/include/image_upload.hpp (revision d98a2f9388b9cab1100d30de401da43c32c98ef4)
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