From 225b4a7f7382d639c6d4da7087730bb93752d14d Mon Sep 17 00:00:00 2001 From: Uwe Seimet Date: Fri, 17 Nov 2023 10:19:37 +0100 Subject: [PATCH] Add support for protobuf text format --- cpp/scsiexec/scsi_executor.cpp | 67 +++++++++++++++++++++++++--------- cpp/scsiexec/scsi_executor.h | 7 +++- cpp/scsiexec/scsiexec_core.cpp | 63 +++++++++++++++++++++++++------- cpp/scsiexec/scsiexec_core.h | 3 +- doc/scsiexec.1 | 35 ++++++++++++------ doc/scsiexec_man_page.txt | 17 ++++++--- 6 files changed, 141 insertions(+), 51 deletions(-) diff --git a/cpp/scsiexec/scsi_executor.cpp b/cpp/scsiexec/scsi_executor.cpp index f5a724ff..e87c810e 100644 --- a/cpp/scsiexec/scsi_executor.cpp +++ b/cpp/scsiexec/scsi_executor.cpp @@ -10,46 +10,60 @@ #include "shared/scsi.h" #include "scsiexec/scsi_executor.h" #include +#include #include #include #include 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 b(length); - in.read(b.data(), length); - memcpy(buffer.data(), b.data(), length); + vector 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 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(length >> 8); cdb[8] = static_cast(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 ""; diff --git a/cpp/scsiexec/scsi_executor.h b/cpp/scsiexec/scsi_executor.h index 3387fdcb..3b7e0563 100644 --- a/cpp/scsiexec/scsi_executor.h +++ b/cpp/scsiexec/scsi_executor.h @@ -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(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) diff --git a/cpp/scsiexec/scsiexec_core.cpp b/cpp/scsiexec/scsiexec_core.cpp index cad8e3b3..90637548 100644 --- a/cpp/scsiexec/scsiexec_core.cpp +++ b/cpp/scsiexec/scsiexec_core.cpp @@ -13,6 +13,7 @@ #include "controllers/controller_manager.h" #include "shared/piscsi_util.h" #include +#include #include #include #include @@ -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 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 args) optind = 1; opterr = 0; int opt; - while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:t:bo:L:X")) != -1) { + while ((opt = getopt(static_cast(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 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 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 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(); diff --git a/cpp/scsiexec/scsiexec_core.h b/cpp/scsiexec/scsiexec_core.h index 052b01e6..eac4c58b 100644 --- a/cpp/scsiexec/scsiexec_core.h +++ b/cpp/scsiexec/scsiexec_core.h @@ -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; diff --git a/doc/scsiexec.1 b/doc/scsiexec.1 index 68db0a5a..aa52b176 100644 --- a/doc/scsiexec.1 +++ b/doc/scsiexec.1 @@ -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) diff --git a/doc/scsiexec_man_page.txt b/doc/scsiexec_man_page.txt index 07db976a..eb5ddf55 100644 --- a/doc/scsiexec_man_page.txt +++ b/doc/scsiexec_man_page.txt @@ -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.