blob: 0c470b473e15062e6133b7e1f82b113c598627e4 [file] [log] [blame]
#pragma once
#include <signal.h>
#include <sys/select.h>
#include <boost/beast/core/flat_static_buffer.hpp>
#include <boost/beast/http.hpp>
#include <boost/process.hpp>
#include <cstdio>
#include <cstdlib>
namespace crow
{
namespace obmc_dump
{
inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
const std::string& entryId);
inline void resetHandler();
// The max network block device buffer size is 128kb plus 16bytes
// for the message header
static constexpr auto nbdBufferSize = 131088;
/** class Handler
* handles data transfer between nbd-client and nbd-server.
* This handler invokes nbd-proxy and reads data from socket
* and writes on to nbd-client and vice-versa
*/
class Handler : public std::enable_shared_from_this<Handler>
{
public:
Handler(const std::string& media, boost::asio::io_context& ios,
const std::string& entryID) :
pipeOut(ios),
pipeIn(ios), media(media), entryID(entryID), doingWrite(false),
negotiationDone(false), writeonnbd(false),
outputBuffer(std::make_unique<
boost::beast::flat_static_buffer<nbdBufferSize>>()),
inputBuffer(
std::make_unique<boost::beast::flat_static_buffer<nbdBufferSize>>())
{
}
~Handler()
{
}
/**
* @brief Invokes InitiateOffload method of dump manager which
* directs pldm to start writing on the nbd device.
*
* @return void
*/
void initiateOffloadOnNbdDevice()
{
crow::connections::systemBus->async_method_call(
[this,
self(shared_from_this())](const boost::system::error_code ec) {
if (ec)
{
BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
resetBuffers();
resetHandler();
return;
}
},
"xyz.openbmc_project.Dump.Manager",
"/xyz/openbmc_project/dump/entry/" + entryID,
"xyz.openbmc_project.Dump.Entry", "InitiateOffload", "/dev/nbd1");
}
/**
* @brief Kills nbd-proxy
*
* @return void
*/
void doClose()
{
int rc = kill(proxy.id(), SIGTERM);
if (rc)
{
return;
}
proxy.wait();
}
/**
* @brief Starts nbd-proxy
*
* @return void
*/
void connect()
{
std::error_code ec;
proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
boost::process::std_out > pipeOut,
boost::process::std_in < pipeIn, ec);
if (ec)
{
BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
<< ec.message();
resetHandler();
return;
}
doRead();
}
/**
* @brief Wait for data on tcp socket from nbd-server.
*
* @return void
*/
void waitForMessageOnSocket()
{
std::size_t bytes = inputBuffer->capacity() - inputBuffer->size();
(*stream).async_read_some(
inputBuffer->prepare(bytes),
[this,
self(shared_from_this())](const boost::system::error_code& ec,
std::size_t bytes_transferred) {
if (ec)
{
BMCWEB_LOG_DEBUG << "Error while reading on socket";
doClose();
resetBuffers();
resetHandler();
return;
}
inputBuffer->commit(bytes_transferred);
doWrite();
});
}
/**
* @brief Writes data on input pipe of nbd-client.
*
* @return void
*/
void doWrite()
{
if (doingWrite)
{
BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
return;
}
if (inputBuffer->size() == 0)
{
BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
return;
}
doingWrite = true;
boost::asio::async_write(
pipeIn, inputBuffer->data(),
[this, self(shared_from_this())](const boost::beast::error_code& ec,
std::size_t bytesWritten) {
if (ec)
{
BMCWEB_LOG_DEBUG << "VM socket port closed";
doClose();
resetBuffers();
resetHandler();
return;
}
doingWrite = false;
if (negotiationDone == false)
{
// "gDf" is NBD reply magic
std::string reply_magic("gDf");
std::string reply_string(
static_cast<char*>(inputBuffer->data().data()),
bytesWritten);
std::size_t found = reply_string.find(reply_magic);
if (found != std::string::npos)
{
negotiationDone = true;
writeonnbd = true;
}
}
inputBuffer->consume(bytesWritten);
waitForMessageOnSocket();
if (writeonnbd)
{
// NBD Negotiation Complete!!!!. Notify Dump manager to
// start dumping the actual data over NBD device
initiateOffloadOnNbdDevice();
writeonnbd = false;
}
});
}
/**
* @brief Reads data on output pipe of nbd-client and write on
* tcp socket.
*
* @return void
*/
void doRead()
{
std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
pipeOut.async_read_some(
outputBuffer->prepare(bytes),
[this, self(shared_from_this())](
const boost::system::error_code& ec, std::size_t bytesRead) {
if (ec)
{
BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
doClose();
resetBuffers();
resetHandler();
return;
}
outputBuffer->commit(bytesRead);
boost::asio::async_write(
*stream, outputBuffer->data(),
[this](const boost::system::error_code& ec,
std::size_t bytes_transferred) {
if (ec)
{
BMCWEB_LOG_DEBUG << "Error while writing on socket";
doClose();
resetBuffers();
resetHandler();
return;
}
outputBuffer->consume(bytes_transferred);
doRead();
});
});
}
/**
* @brief Resets input and output buffers.
* @return void
*/
void resetBuffers()
{
#if BOOST_VERSION >= 107000
this->inputBuffer->clear();
this->outputBuffer->clear();
#else
this->inputBuffer->reset();
this->outputBuffer->reset();
#endif
}
boost::process::async_pipe pipeOut;
boost::process::async_pipe pipeIn;
boost::process::child proxy;
std::string media;
std::string entryID;
bool doingWrite;
bool negotiationDone;
bool writeonnbd;
std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
outputBuffer;
std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
inputBuffer;
std::shared_ptr<crow::Request::Adaptor> stream;
};
static std::shared_ptr<Handler> handler;
inline void resetHandler()
{
handler.reset();
}
inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
const std::string& entryId)
{
// Run only one instance of Handler, one dump offload can happen at a time
if (handler != nullptr)
{
BMCWEB_LOG_ERROR << "Handler already running";
res.result(boost::beast::http::status::service_unavailable);
res.jsonValue["Description"] = "Service is already being used";
res.end();
return;
}
const char* media = "1";
boost::asio::io_context* io_con = req.ioService;
handler = std::make_shared<Handler>(media, *io_con, entryId);
handler->stream =
std::make_shared<crow::Request::Adaptor>(std::move(req.socket()));
handler->connect();
handler->waitForMessageOnSocket();
}
} // namespace obmc_dump
} // namespace crow