blob: 3a72b3adb50d26fc1c98673c05c745d507770f56 [file] [log] [blame]
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06001#pragma once
2
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08003#include "app.hpp"
Ed Tanousfaf100f2023-05-25 10:03:14 -07004#include "websocket.hpp"
Ed Tanous3ccb3ad2023-01-13 17:40:03 -08005
Ed Tanous3bfa3b22024-01-31 12:18:03 -08006#include <boost/asio/readable_pipe.hpp>
7#include <boost/asio/writable_pipe.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -06008#include <boost/beast/core/flat_static_buffer.hpp>
Ed Tanous3bfa3b22024-01-31 12:18:03 -08009#include <boost/process/v2/process.hpp>
10#include <boost/process/v2/stdio.hpp>
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060011
Gunnar Mills1214b7e2020-06-04 10:11:30 -050012#include <csignal>
13
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060014namespace crow
15{
16namespace obmc_vm
17{
18
Ed Tanouscf9e4172022-12-21 09:30:16 -080019// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060020static crow::websocket::Connection* session = nullptr;
21
22// The max network block device buffer size is 128kb plus 16bytes
23// for the message header:
24// https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#simple-reply-message
Troy Lee4ee8f0b2021-08-02 11:08:26 +080025static constexpr auto nbdBufferSize = (128 * 1024 + 16) * 4;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060026
27class Handler : public std::enable_shared_from_this<Handler>
28{
29 public:
Ed Tanous3bfa3b22024-01-31 12:18:03 -080030 Handler(const std::string& media, boost::asio::io_context& ios) :
31 pipeOut(ios), pipeIn(ios),
32 proxy(ios, "/usr/bin/nbd-proxy", {media},
33 boost::process::v2::process_stdio{
34 .in = pipeIn, .out = pipeOut, .err = nullptr}),
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060035 outputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>),
36 inputBuffer(new boost::beast::flat_static_buffer<nbdBufferSize>)
Gunnar Mills1214b7e2020-06-04 10:11:30 -050037 {}
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060038
Ed Tanous0c0084a2019-10-24 15:57:51 -070039 ~Handler() = default;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060040
Ed Tanousecd6a3a2022-01-07 09:18:40 -080041 Handler(const Handler&) = delete;
42 Handler(Handler&&) = delete;
43 Handler& operator=(const Handler&) = delete;
44 Handler& operator=(Handler&&) = delete;
45
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060046 void doClose()
47 {
48 // boost::process::child::terminate uses SIGKILL, need to send SIGTERM
49 // to allow the proxy to stop nbd-client and the USB device gadget.
50 int rc = kill(proxy.id(), SIGTERM);
Ed Tanouse662eae2022-01-25 10:39:19 -080051 if (rc != 0)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060052 {
Ed Tanous62598e32023-07-17 17:06:25 -070053 BMCWEB_LOG_ERROR("Failed to terminate nbd-proxy: {}", errno);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060054 return;
55 }
Troy Lee36ecbf32021-08-17 18:15:28 +080056
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060057 proxy.wait();
58 }
59
60 void connect()
61 {
62 std::error_code ec;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060063 if (ec)
64 {
Ed Tanous62598e32023-07-17 17:06:25 -070065 BMCWEB_LOG_ERROR("Couldn't connect to nbd-proxy: {}", ec.message());
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060066 if (session != nullptr)
67 {
68 session->close("Error connecting to nbd-proxy");
69 }
70 return;
71 }
72 doWrite();
73 doRead();
74 }
75
76 void doWrite()
77 {
78 if (doingWrite)
79 {
Ed Tanous62598e32023-07-17 17:06:25 -070080 BMCWEB_LOG_DEBUG("Already writing. Bailing out");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060081 return;
82 }
83
84 if (inputBuffer->size() == 0)
85 {
Ed Tanous62598e32023-07-17 17:06:25 -070086 BMCWEB_LOG_DEBUG("inputBuffer empty. Bailing out");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060087 return;
88 }
89
90 doingWrite = true;
91 pipeIn.async_write_some(
92 inputBuffer->data(),
Ed Tanous81c4e332023-05-18 10:30:34 -070093 [this, self(shared_from_this())](const boost::beast::error_code& ec,
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060094 std::size_t bytesWritten) {
Ed Tanous62598e32023-07-17 17:06:25 -070095 BMCWEB_LOG_DEBUG("Wrote {}bytes", bytesWritten);
Ed Tanous002d39b2022-05-31 08:59:27 -070096 doingWrite = false;
97 inputBuffer->consume(bytesWritten);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -060098
Ed Tanous002d39b2022-05-31 08:59:27 -070099 if (session == nullptr)
100 {
101 return;
102 }
103 if (ec == boost::asio::error::eof)
104 {
105 session->close("VM socket port closed");
106 return;
107 }
108 if (ec)
109 {
110 session->close("Error in writing to proxy port");
Ed Tanous62598e32023-07-17 17:06:25 -0700111 BMCWEB_LOG_ERROR("Error in VM socket write {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700112 return;
113 }
114 doWrite();
Patrick Williams5a39f772023-10-20 11:20:21 -0500115 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600116 }
117
118 void doRead()
119 {
120 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
121
122 pipeOut.async_read_some(
123 outputBuffer->prepare(bytes),
124 [this, self(shared_from_this())](
125 const boost::system::error_code& ec, std::size_t bytesRead) {
Ed Tanous62598e32023-07-17 17:06:25 -0700126 BMCWEB_LOG_DEBUG("Read done. Read {} bytes", bytesRead);
Ed Tanous002d39b2022-05-31 08:59:27 -0700127 if (ec)
128 {
Ed Tanous62598e32023-07-17 17:06:25 -0700129 BMCWEB_LOG_ERROR("Couldn't read from VM port: {}", ec);
Ed Tanous002d39b2022-05-31 08:59:27 -0700130 if (session != nullptr)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600131 {
Ed Tanous002d39b2022-05-31 08:59:27 -0700132 session->close("Error in connecting to VM port");
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600133 }
Ed Tanous002d39b2022-05-31 08:59:27 -0700134 return;
135 }
136 if (session == nullptr)
137 {
138 return;
139 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600140
Ed Tanous002d39b2022-05-31 08:59:27 -0700141 outputBuffer->commit(bytesRead);
142 std::string_view payload(
143 static_cast<const char*>(outputBuffer->data().data()),
144 bytesRead);
145 session->sendBinary(payload);
146 outputBuffer->consume(bytesRead);
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600147
Ed Tanous002d39b2022-05-31 08:59:27 -0700148 doRead();
Patrick Williams5a39f772023-10-20 11:20:21 -0500149 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600150 }
151
Ed Tanous3bfa3b22024-01-31 12:18:03 -0800152 boost::asio::readable_pipe pipeOut;
153 boost::asio::writable_pipe pipeIn;
154 boost::process::v2::process proxy;
Ed Tanousf5b191a2022-02-15 11:30:39 -0800155 bool doingWrite{false};
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600156
157 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
158 outputBuffer;
159 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
160 inputBuffer;
161};
162
Ed Tanouscf9e4172022-12-21 09:30:16 -0800163// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600164static std::shared_ptr<Handler> handler;
165
Ed Tanous23a21a12020-07-25 04:45:05 +0000166inline void requestRoutes(App& app)
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600167{
168 BMCWEB_ROUTE(app, "/vm/0/0")
Ed Tanous432a8902021-06-14 15:28:56 -0700169 .privileges({{"ConfigureComponents", "ConfigureManager"}})
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600170 .websocket()
zhanghch0577726382021-10-21 14:07:57 +0800171 .onopen([](crow::websocket::Connection& conn) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500172 BMCWEB_LOG_DEBUG("Connection {} opened", logPtr(&conn));
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600173
Patrick Williams5a39f772023-10-20 11:20:21 -0500174 if (session != nullptr)
175 {
176 conn.close("Session already connected");
177 return;
178 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600179
Patrick Williams5a39f772023-10-20 11:20:21 -0500180 if (handler != nullptr)
181 {
182 conn.close("Handler already running");
183 return;
184 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600185
Patrick Williams5a39f772023-10-20 11:20:21 -0500186 session = &conn;
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600187
Patrick Williams5a39f772023-10-20 11:20:21 -0500188 // media is the last digit of the endpoint /vm/0/0. A future
189 // enhancement can include supporting different endpoint values.
190 const char* media = "0";
191 handler = std::make_shared<Handler>(media, conn.getIoContext());
192 handler->connect();
193 })
Ed Tanouscb13a392020-07-25 19:02:03 +0000194 .onclose([](crow::websocket::Connection& conn,
195 const std::string& /*reason*/) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500196 if (&conn != session)
197 {
198 return;
199 }
Ed Tanouscb13a392020-07-25 19:02:03 +0000200
Patrick Williams5a39f772023-10-20 11:20:21 -0500201 session = nullptr;
202 handler->doClose();
203 handler->inputBuffer->clear();
204 handler->outputBuffer->clear();
205 handler.reset();
206 })
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600207 .onmessage([](crow::websocket::Connection& conn,
Ed Tanouscb13a392020-07-25 19:02:03 +0000208 const std::string& data, bool) {
Patrick Williams5a39f772023-10-20 11:20:21 -0500209 if (data.length() >
210 handler->inputBuffer->capacity() - handler->inputBuffer->size())
211 {
212 BMCWEB_LOG_ERROR("Buffer overrun when writing {} bytes",
213 data.length());
214 conn.close("Buffer overrun");
215 return;
216 }
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600217
Ed Tanous44106f32024-04-06 13:48:50 -0700218 size_t copied =
219 boost::asio::buffer_copy(handler->inputBuffer->prepare(data.size()),
220 boost::asio::buffer(data));
221 handler->inputBuffer->commit(copied);
Patrick Williams5a39f772023-10-20 11:20:21 -0500222 handler->doWrite();
223 });
Adriana Kobylak1bfbe0e2019-01-17 12:08:38 -0600224}
225
226} // namespace obmc_vm
227} // namespace crow