xref: /openbmc/bmcweb/features/openbmc_rest/image_upload.hpp (revision 3577e44683a5ade8ad02a6418984b56f4ca2bcac)
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