blob: bc885cf5734352bb1aa7c74bf6336b7e8a401a16 [file] [log] [blame]
raviteja-b4722efe2020-02-03 12:23:18 -06001#pragma once
2
3#include <signal.h>
4#include <sys/select.h>
5
6#include <boost/beast/core/flat_static_buffer.hpp>
7#include <boost/beast/http.hpp>
8#include <boost/process.hpp>
Gunnar Mills1214b7e2020-06-04 10:11:30 -05009
raviteja-b4722efe2020-02-03 12:23:18 -060010#include <cstdio>
11#include <cstdlib>
12
13namespace crow
14{
15namespace obmc_dump
16{
17
18inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
19 const std::string& entryId);
20inline void resetHandler();
21
22// The max network block device buffer size is 128kb plus 16bytes
23// for the message header
24static constexpr auto nbdBufferSize = 131088;
25
26/** class Handler
27 * handles data transfer between nbd-client and nbd-server.
28 * This handler invokes nbd-proxy and reads data from socket
29 * and writes on to nbd-client and vice-versa
30 */
31class Handler : public std::enable_shared_from_this<Handler>
32{
33 public:
Ed Tanous23a21a12020-07-25 04:45:05 +000034 Handler(const std::string& mediaIn, boost::asio::io_context& ios,
35 const std::string& entryIDIn) :
raviteja-b4722efe2020-02-03 12:23:18 -060036 pipeOut(ios),
Ed Tanous23a21a12020-07-25 04:45:05 +000037 pipeIn(ios), media(mediaIn), entryID(entryIDIn), doingWrite(false),
raviteja-b4722efe2020-02-03 12:23:18 -060038 negotiationDone(false), writeonnbd(false),
39 outputBuffer(std::make_unique<
40 boost::beast::flat_static_buffer<nbdBufferSize>>()),
41 inputBuffer(
42 std::make_unique<boost::beast::flat_static_buffer<nbdBufferSize>>())
Gunnar Mills1214b7e2020-06-04 10:11:30 -050043 {}
raviteja-b4722efe2020-02-03 12:23:18 -060044
45 ~Handler()
Gunnar Mills1214b7e2020-06-04 10:11:30 -050046 {}
raviteja-b4722efe2020-02-03 12:23:18 -060047
48 /**
49 * @brief Invokes InitiateOffload method of dump manager which
50 * directs pldm to start writing on the nbd device.
51 *
52 * @return void
53 */
54 void initiateOffloadOnNbdDevice()
55 {
56 crow::connections::systemBus->async_method_call(
57 [this,
58 self(shared_from_this())](const boost::system::error_code ec) {
59 if (ec)
60 {
61 BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
62 resetBuffers();
63 resetHandler();
64 return;
65 }
66 },
67 "xyz.openbmc_project.Dump.Manager",
68 "/xyz/openbmc_project/dump/entry/" + entryID,
Ravi Teja7870f1f2020-03-31 06:29:10 -050069 "xyz.openbmc_project.Dump.Entry", "InitiateOffload", "/dev/nbd1");
raviteja-b4722efe2020-02-03 12:23:18 -060070 }
71
72 /**
73 * @brief Kills nbd-proxy
74 *
75 * @return void
76 */
77 void doClose()
78 {
79 int rc = kill(proxy.id(), SIGTERM);
80 if (rc)
81 {
82 return;
83 }
84 proxy.wait();
85 }
86
87 /**
88 * @brief Starts nbd-proxy
89 *
90 * @return void
91 */
92 void connect()
93 {
94 std::error_code ec;
95 proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
96 boost::process::std_out > pipeOut,
97 boost::process::std_in < pipeIn, ec);
98 if (ec)
99 {
100 BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
101 << ec.message();
102 resetHandler();
103 return;
104 }
105 doRead();
106 }
107
108 /**
109 * @brief Wait for data on tcp socket from nbd-server.
110 *
111 * @return void
112 */
113 void waitForMessageOnSocket()
114 {
115
116 std::size_t bytes = inputBuffer->capacity() - inputBuffer->size();
117
118 (*stream).async_read_some(
119 inputBuffer->prepare(bytes),
120 [this,
121 self(shared_from_this())](const boost::system::error_code& ec,
122 std::size_t bytes_transferred) {
123 if (ec)
124 {
125 BMCWEB_LOG_DEBUG << "Error while reading on socket";
126 doClose();
127 resetBuffers();
128 resetHandler();
129 return;
130 }
131
132 inputBuffer->commit(bytes_transferred);
133 doWrite();
134 });
135 }
136
137 /**
138 * @brief Writes data on input pipe of nbd-client.
139 *
140 * @return void
141 */
142 void doWrite()
143 {
144
145 if (doingWrite)
146 {
147 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
148 return;
149 }
150
151 if (inputBuffer->size() == 0)
152 {
153 BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
154 return;
155 }
156
157 doingWrite = true;
158 boost::asio::async_write(
159 pipeIn, inputBuffer->data(),
160 [this, self(shared_from_this())](const boost::beast::error_code& ec,
161 std::size_t bytesWritten) {
162 if (ec)
163 {
164 BMCWEB_LOG_DEBUG << "VM socket port closed";
165 doClose();
166 resetBuffers();
167 resetHandler();
168 return;
169 }
170
171 doingWrite = false;
172
173 if (negotiationDone == false)
174 {
175 // "gDf" is NBD reply magic
176 std::string reply_magic("gDf");
177 std::string reply_string(
178 static_cast<char*>(inputBuffer->data().data()),
179 bytesWritten);
180 std::size_t found = reply_string.find(reply_magic);
181 if (found != std::string::npos)
182 {
183 negotiationDone = true;
184 writeonnbd = true;
185 }
186 }
187
188 inputBuffer->consume(bytesWritten);
189 waitForMessageOnSocket();
190 if (writeonnbd)
191 {
192 // NBD Negotiation Complete!!!!. Notify Dump manager to
193 // start dumping the actual data over NBD device
194 initiateOffloadOnNbdDevice();
195 writeonnbd = false;
196 }
197 });
198 }
199
200 /**
201 * @brief Reads data on output pipe of nbd-client and write on
202 * tcp socket.
203 *
204 * @return void
205 */
206 void doRead()
207 {
208 std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
209
210 pipeOut.async_read_some(
211 outputBuffer->prepare(bytes),
212 [this, self(shared_from_this())](
213 const boost::system::error_code& ec, std::size_t bytesRead) {
214 if (ec)
215 {
216 BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
217 doClose();
218 resetBuffers();
219 resetHandler();
220 return;
221 }
222
223 outputBuffer->commit(bytesRead);
224
225 boost::asio::async_write(
226 *stream, outputBuffer->data(),
Ed Tanous23a21a12020-07-25 04:45:05 +0000227 [this](const boost::system::error_code& ec2,
raviteja-b4722efe2020-02-03 12:23:18 -0600228 std::size_t bytes_transferred) {
Ed Tanous23a21a12020-07-25 04:45:05 +0000229 if (ec2)
raviteja-b4722efe2020-02-03 12:23:18 -0600230 {
231 BMCWEB_LOG_DEBUG << "Error while writing on socket";
232 doClose();
233 resetBuffers();
234 resetHandler();
235 return;
236 }
237
238 outputBuffer->consume(bytes_transferred);
239 doRead();
240 });
241 });
242 }
243
244 /**
245 * @brief Resets input and output buffers.
246 * @return void
247 */
248 void resetBuffers()
249 {
raviteja-b4722efe2020-02-03 12:23:18 -0600250 this->inputBuffer->clear();
251 this->outputBuffer->clear();
raviteja-b4722efe2020-02-03 12:23:18 -0600252 }
253
254 boost::process::async_pipe pipeOut;
255 boost::process::async_pipe pipeIn;
256 boost::process::child proxy;
257 std::string media;
258 std::string entryID;
259 bool doingWrite;
260 bool negotiationDone;
261 bool writeonnbd;
262 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
263 outputBuffer;
264 std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
265 inputBuffer;
266 std::shared_ptr<crow::Request::Adaptor> stream;
267};
268
269static std::shared_ptr<Handler> handler;
270inline void resetHandler()
271{
272
273 handler.reset();
274}
275inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
276 const std::string& entryId)
277{
278
279 // Run only one instance of Handler, one dump offload can happen at a time
280 if (handler != nullptr)
281 {
282 BMCWEB_LOG_ERROR << "Handler already running";
283 res.result(boost::beast::http::status::service_unavailable);
284 res.jsonValue["Description"] = "Service is already being used";
285 res.end();
286 return;
287 }
288
289 const char* media = "1";
290 boost::asio::io_context* io_con = req.ioService;
291
292 handler = std::make_shared<Handler>(media, *io_con, entryId);
293 handler->stream =
294 std::make_shared<crow::Request::Adaptor>(std::move(req.socket()));
295 handler->connect();
296 handler->waitForMessageOnSocket();
297}
298} // namespace obmc_dump
299} // namespace crow