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