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