Add support for protobuf text format

This commit is contained in:
Uwe Seimet 2023-11-17 10:19:37 +01:00
parent 025538df5c
commit 225b4a7f73
6 changed files with 141 additions and 51 deletions

View File

@ -10,46 +10,60 @@
#include "shared/scsi.h"
#include "scsiexec/scsi_executor.h"
#include <google/protobuf/util/json_util.h>
#include <google/protobuf/text_format.h>
#include <array>
#include <fstream>
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace google::protobuf;
using namespace google::protobuf::util;
using namespace scsi_defs;
using namespace piscsi_interface;
string ScsiExecutor::Execute(const string& filename, bool binary, PbResult& result)
string ScsiExecutor::Execute(const string& filename, protobuf_format input_format, PbResult& result)
{
int length = 0;
if (binary) {
switch (input_format) {
case protobuf_format::binary: {
ifstream in(filename, ios::binary);
if (in.fail()) {
return "Can't open binary input file '" + filename + "': " + strerror(errno);
return "Can't open input file '" + filename + "': " + strerror(errno);
}
length = file_size(filename);
vector<char> b(length);
in.read(b.data(), length);
memcpy(buffer.data(), b.data(), length);
vector<char> data(length);
in.read(data.data(), length);
memcpy(buffer.data(), data.data(), length);
break;
}
else {
case protobuf_format::json:
case protobuf_format::text: {
ifstream in(filename);
if (in.fail()) {
return "Can't open JSON input file '" + filename + "': " + strerror(errno);
return "Can't open input file '" + filename + "': " + strerror(errno);
}
stringstream buf;
buf << in.rdbuf();
const string json = buf.str();
length = json.size();
memcpy(buffer.data(), json.data(), length);
const string data = buf.str();
length = data.size();
memcpy(buffer.data(), data.data(), length);
break;
}
default:
assert(false);
break;
}
array<uint8_t, 10> cdb = { };
cdb[1] = binary ? 0x00 : 0x01;
cdb[1] = input_format == protobuf_format::binary ? 0x00 : 0x01;
cdb[1] |= input_format == protobuf_format::json ? 0x00 : 0x02;
cdb[1] |= input_format == protobuf_format::text ? 0x00 : 0x04;
cdb[7] = static_cast<uint8_t>(length >> 8);
cdb[8] = static_cast<uint8_t>(length);
@ -62,18 +76,35 @@ string ScsiExecutor::Execute(const string& filename, bool binary, PbResult& resu
if (!phase_executor->Execute(scsi_command::eCmdReadOperationResult, cdb, buffer, buffer.size())) {
return "Can't read operation result";
}
}
if (binary) {
switch (input_format) {
case protobuf_format::binary: {
if (!result.ParseFromArray(buffer.data(), phase_executor->GetByteCount())) {
return "Can't parse binary protobuf data";
}
break;
}
else {
case protobuf_format::json: {
const string json((const char*) buffer.data(), phase_executor->GetByteCount());
if (!JsonStringToMessage(json, &result).ok()) {
return "Can't parse JSON protobuf data";
}
if (!JsonStringToMessage(json, &result).ok()) {
return "Can't parse JSON protobuf data";
}
break;
}
case protobuf_format::text: {
const string text((const char*) buffer.data(), phase_executor->GetByteCount());
if (!TextFormat::ParseFromString(text, &result)) {
return "Can't parse text format protobuf data";
}
break;
}
default:
assert(false);
break;
}
return "";

View File

@ -19,18 +19,23 @@ using namespace piscsi_interface;
class ScsiExecutor
{
// The SCSI Execute command supports a byte count of up to 65535 bytes
inline static const int BUFFER_SIZE = 65535;
public:
enum class protobuf_format {
binary, json,text
};
ScsiExecutor(BUS &bus, int id)
{
phase_executor = make_unique<PhaseExecutor>(bus, id);
}
~ScsiExecutor() = default;
string Execute(const string&, bool, PbResult&);
string Execute(const string&, protobuf_format, PbResult&);
bool ShutDown();
void SetTarget(int id, int lun)

View File

@ -13,6 +13,7 @@
#include "controllers/controller_manager.h"
#include "shared/piscsi_util.h"
#include <google/protobuf/util/json_util.h>
#include <google/protobuf/text_format.h>
#include <spdlog/spdlog.h>
#include <filesystem>
#include <csignal>
@ -22,6 +23,7 @@
using namespace std;
using namespace filesystem;
using namespace google::protobuf;
using namespace google::protobuf::util;
using namespace spdlog;
using namespace scsi_defs;
@ -45,20 +47,22 @@ void ScsiExec::TerminationHandler(int)
bool ScsiExec::Banner(span<char*> args) const
{
cout << piscsi_util::Banner("(SCSI Action Execution Tool)");
cout << piscsi_util::Banner("(SCSI Command Execution Tool)");
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] [-f INPUT_FILE] [-o OUTPUT_FILE]"
<< " -L LOG_LEVEL] [-b] [-X]\n"
<< " [-L LOG_LEVEL] [-b] [-B] [-F] [-T] [-X]\n"
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() - 1) << ")."
<< " Default is 0.\n"
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " INPUT_FILE is the protobuf data input file, either in binary or in JSON protobuf format.\n"
<< " OUTPUT_FILE is the protobuf data output file. If not specified the output is always JSON"
<< " and goes to stdout.\n"
<< " INPUT_FILE is the protobuf data input file, by default in JSON format.\n"
<< " OUTPUT_FILE is the protobuf data output file, by default in JSON format.\n"
<< " LOG_LEVEL is the log level {trace|debug|info|warn|err|off}, default is 'info'.\n"
<< " -b Signal that the input file is in binary protobuf format instead of JSON format.\n"
<< " -b Signals that the input file is in protobuf binary format.\n"
<< " -F Signals that the input file is in protobuf text format.\n"
<< " -B Generate a protobuf binary format file.\n"
<< " -T Generate a protobuf text format file.\n"
<< " -X Shut down piscsi.\n"
<< flush;
@ -94,7 +98,7 @@ void ScsiExec::ParseArguments(span<char*> args)
optind = 1;
opterr = 0;
int opt;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:t:bo:L:X")) != -1) {
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:t:bo:L:BFTX")) != -1) {
switch (opt) {
case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
@ -103,7 +107,19 @@ void ScsiExec::ParseArguments(span<char*> args)
break;
case 'b':
binary = true;
input_format = ScsiExecutor::protobuf_format::binary;
break;
case 'F':
input_format = ScsiExecutor::protobuf_format::text;
break;
case 'B':
output_format = ScsiExecutor::protobuf_format::binary;
break;
case 'T':
output_format = ScsiExecutor::protobuf_format::text;
break;
case 'f':
@ -198,7 +214,7 @@ int ScsiExec::run(span<char*> args, bool in_process)
}
PbResult result;
if (string error = scsi_executor->Execute(input_filename, binary, result); !error.empty()) {
if (string error = scsi_executor->Execute(input_filename, input_format, result); !error.empty()) {
cerr << "Error: " << error << endl;
CleanUp();
@ -216,24 +232,45 @@ int ScsiExec::run(span<char*> args, bool in_process)
return EXIT_SUCCESS;
}
if (binary) {
switch (output_format) {
case ScsiExecutor::protobuf_format::binary: {
ofstream out(output_filename, ios::binary);
if (out.fail()) {
cerr << "Error: " << "Can't open binary output file '" << output_filename << "'" << endl;
cerr << "Error: " << "Can't open protobuf binary output file '" << output_filename << "'" << endl;
}
const string data = result.SerializeAsString();
out.write(data.data(), data.size());
break;
}
else {
case ScsiExecutor::protobuf_format::json: {
ofstream out(output_filename);
if (out.fail()) {
cerr << "Error: " << "Can't open JSON output file '" << output_filename << "'" << endl;
cerr << "Error: " << "Can't open protobuf JSON output file '" << output_filename << "'" << endl;
}
string json;
MessageToJsonString(result, &json);
out << json << '\n';
break;
}
case ScsiExecutor::protobuf_format::text: {
ofstream out(output_filename);
if (out.fail()) {
cerr << "Error: " << "Can't open protobuf text format output file '" << output_filename << "'" << endl;
}
string text;
TextFormat::PrintToString(result, &text);
out << text << '\n';
break;
}
default:
assert(false);
break;
}
CleanUp();

View File

@ -49,7 +49,8 @@ private:
string input_filename;
string output_filename;
bool binary = false;
ScsiExecutor::protobuf_format input_format = ScsiExecutor::protobuf_format::json;
ScsiExecutor::protobuf_format output_format = ScsiExecutor::protobuf_format::json;
bool shut_down = false;

View File

@ -1,44 +1,55 @@
.TH scsidump 1
.TH scsiexec 1
.SH NAME
scsidump \- SCSI action execution tool for PiSCSI
scsiexec \- SCSI command execution tool for PiSCSI
.SH SYNOPSIS
.B scsidump
.B scsiexec
\fB\-t\fR \fIID[:LUN]\fR
[\fB\-i\fR \fIBID\fR]
\fB\-f\fR \fIFILENAME\fR
[\fB\-b\fR]
[\fB\-F\fR]
[\fB\-B\fR]
[\fB\-T\fR]
[\fB\-t\tR] ID[:LUN]
[\fB\-L\fR \fILOG_LEVEL\fR]
[\fB\-X\fR]
.SH DESCRIPTION
.B scsiexec
wraps JSON or binary input data in protobuf format into a custom PiSCSI SCSI command, has piscsi execute it and display the results in JSON format. The input data must be legal commands for the PiSCSi protobuf interface. See the file piscsi_interface.proto for details.
scsiexec helps with advanced testing. It requires two connected PiSCSI boards.
wraps protobuf input data into a custom PiSCSI SCSI command, has piscsi execute it and display the results. The input data must be a legal command for the PiSCSi protobuf interface. See the file piscsi_interface.proto for details.
.SH NOTES
.B scsidump
requires either a direct connection (one without transceivers) or a FULLSPEC PiSCSI/RaSCSI board.
.B scsiexec
helps with advanced testing. It requires two connected PiSCSI boards. The board scsiexec is running on must be a FULLSPEC board because scsiexec requires SCSI initiator mode support.
.SH OPTIONS
.TP
.BR \-t\fI " "\fIID[:LUN]
SCSI ID and optional LUN of the remote SCSI device. The remote SCSI device will be functioning as the "Target" device.
SCSI ID and optional LUN of the second PiSCSI board. The remote SCSI device will be functioning as the "Target" device.
.TP
.BR \-i\fI " "\fIBID
SCSI ID of the PiSCSI device. If not specified, the PiSCSI device will use ID 7. The PiSCSI host will be functioning as the "Initiator" device.
.TP
.BR \-f\fI " "\fIFILE
Path to the input file with JSON or binary protobuf data.
The protobuf data input file, by default in JSON format.
.TP
.BR \-b\fI
Signals that the inout file is a binary file and not a JSON file.
Signals that the input file is in protobuf binary format.
.TP
.BR \-F\fI
Signals that the input file is in protobuf text format.
.TP
.BR \-B\fI
Generate a protobuf binary format file.
.TP
.BR \-T\fI
Generate a protobuf text format file.
.TP
.BR \-L\fI " " \fILOG_LEVEL
The scsiexec log level (trace, debug, info, warning, error, off). The default log level is 'info'.
Set the log level (trace, debug, info, warning, error, off). The default log level is 'info'.
.TP
.BR \-X\fI
Shut down piscsi.
Shut down piscsi with a SCSI command.
.SH SEE ALSO
scsictl(1), piscsi(1), scsidump(1), scsimon(1)

View File

@ -6,8 +6,8 @@ NAME
scsidump - SCSI action execution tool for PiSCSI
SYNOPSIS
scsidump -t ID[:LUN] [-i BID] -f FILENAME [-b] [-tR] ID[:LUN] [-L
LOG_LEVEL] [-X]
scsidump -t ID[:LUN] [-i BID] -f FILENAME [-b] [-F] [-B] [-T] [-tR]
ID[:LUN] [-L LOG_LEVEL] [-X]
DESCRIPTION
scsiexec wraps JSON or binary input data in protobuf format into a cus
@ -31,13 +31,18 @@ OPTIONS
"Initiator" device.
-f FILE
Path to the input file with JSON or binary protobuf data.
The protobuf data input file, by default in JSON format.
-b Signals that the inout file is a binary file and not a JSON
file.
-b Signals that the input file is in protobuf binary format.
-F Signals that the input file is in protobuf text format.
-B Generate a protobuf binary format file.
-T Generate a protobuf text format file.
-L LOG_LEVEL
The scsiexec log level (trace, debug, info, warning, error,
The scsiexec log level (trace, debug, info, warning, error,
off). The default log level is 'info'.
-X Shut down piscsi.