From abc5c4b9ac1c74dc37b412cfe9b0555aaa8620ad Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 20 Nov 2023 07:40:53 +0100 Subject: [PATCH] scsictl: Create files with binary/JSON or text format protobuf data (#1369) --- cpp/scsictl/scsictl_core.cpp | 430 +++++++++++++++++++++-------------- cpp/scsictl/scsictl_core.h | 6 +- doc/scsictl.1 | 13 ++ doc/scsictl_man_page.txt | 43 ++-- 4 files changed, 309 insertions(+), 183 deletions(-) diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 6f6b8860..6cee760b 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -1,11 +1,12 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -18,11 +19,16 @@ #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" +#include +#include #include #include #include +#include using namespace std; +using namespace google::protobuf; +using namespace google::protobuf::util; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; @@ -33,8 +39,8 @@ void ScsiCtl::Banner(const vector& args) const cout << piscsi_util::Banner("(Controller App)") << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] " << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] " - << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] " - << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-B FILENAME] [-J FILENAME] [-T FILENAME] [-R CURRENT_NAME:NEW_NAME] " + << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-S] [-v] [-V] [-y] [-X]\n" << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" @@ -66,7 +72,7 @@ int ScsiCtl::run(const vector& args) const PbCommand command; PbDeviceDefinition* device = command.add_devices(); device->set_id(-1); - const char *hostname = "localhost"; + string hostname = "localhost"; int port = 6868; string param; string log_level; @@ -74,7 +80,10 @@ int ScsiCtl::run(const vector& args) const string reserved_ids; string image_params; string filename; - string token; + string filename_json; + string filename_binary; + string filename_text; + string token; bool list = false; string locale = GetLocale(); @@ -82,186 +91,228 @@ int ScsiCtl::run(const vector& args) const opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), - "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { - switch (opt) { - case 'i': - if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); - } - break; + "e::lmos::vDINOSTVXa:b:c:d:f:h:i:n:p:r:t:x:z:B:C:E:F:J:L:P::R:Z:")) != -1) { + switch (opt) { + case 'i': + if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); + } + break; - case 'C': - command.set_operation(CREATE_IMAGE); - image_params = optarg; - break; + case 'C': + command.set_operation(CREATE_IMAGE); + image_params = optarg; + break; - case 'b': - int block_size; - if (!GetAsUnsignedInt(optarg, block_size)) { - cerr << "Error: Invalid block size " << optarg << endl; - exit(EXIT_FAILURE); - } - device->set_block_size(block_size); - break; + case 'b': + int block_size; + if (!GetAsUnsignedInt(optarg, block_size)) { + cerr << "Error: Invalid block size " << optarg << endl; + exit(EXIT_FAILURE); + } + device->set_block_size(block_size); + break; - case 'c': - command.set_operation(parser.ParseOperation(optarg)); - if (command.operation() == NO_OPERATION) { - cerr << "Error: Unknown operation '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; + case 'c': + command.set_operation(parser.ParseOperation(optarg)); + if (command.operation() == NO_OPERATION) { + cerr << "Error: Unknown operation '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; - case 'D': - command.set_operation(DETACH_ALL); - break; + case 'D': + command.set_operation(DETACH_ALL); + break; - case 'd': - command.set_operation(DELETE_IMAGE); - image_params = optarg; - break; + case 'd': + command.set_operation(DELETE_IMAGE); + image_params = optarg; + break; - case 'E': - command.set_operation(IMAGE_FILE_INFO); - filename = optarg; - break; + case 'E': + filename = optarg; + if (filename.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + command.set_operation(IMAGE_FILE_INFO); + break; - case 'e': - command.set_operation(DEFAULT_IMAGE_FILES_INFO); - if (optarg) { - SetCommandParams(command, optarg); + case 'e': + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + if (optarg) { + SetCommandParams(command, optarg); + } + break; + + case 'F': + command.set_operation(DEFAULT_FOLDER); + default_folder = optarg; + break; + + case 'f': + param = optarg; + break; + + case 'h': + hostname = optarg; + if (hostname.empty()) { + cerr << "Error: Missing hostname" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'B': + filename_binary = optarg; + if (filename_binary.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'J': + filename_json = optarg; + if (filename_json.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'Z': + filename_text = optarg; + if (filename_text.empty()) { + cerr << "Error: Missing filename" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'I': + command.set_operation(RESERVED_IDS_INFO); + break; + + case 'L': + command.set_operation(LOG_LEVEL); + log_level = optarg; + break; + + case 'l': + list = true; + break; + + case 'm': + command.set_operation(MAPPING_INFO); + break; + + case 'N': + command.set_operation(NETWORK_INTERFACES_INFO); + break; + + case 'O': + command.set_operation(LOG_LEVEL_INFO); + break; + + case 'o': + command.set_operation(OPERATION_INFO); + break; + + case 't': + device->set_type(parser.ParseType(optarg)); + if (device->type() == UNDEFINED) { + cerr << "Error: Unknown device type '" << optarg << "'" << endl; + exit(EXIT_FAILURE); + } + break; + + case 'r': + command.set_operation(RESERVE_IDS); + reserved_ids = optarg; + break; + + case 'R': + command.set_operation(RENAME_IMAGE); + image_params = optarg; + break; + + case 'n': + SetProductData(*device, optarg); + break; + + case 'p': + if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { + cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; + exit(EXIT_FAILURE); + } + break; + + case 's': + command.set_operation(SERVER_INFO); + if (optarg) { + if (const string error = SetCommandParams(command, optarg); !error.empty()) { + cerr << "Error: " << error << endl; + exit(EXIT_FAILURE); } - break; + } + break; - case 'F': - command.set_operation(DEFAULT_FOLDER); - default_folder = optarg; - break; + case 'S': + command.set_operation(STATISTICS_INFO); + break; - case 'f': - param = optarg; - break; + case 'v': + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; + exit(EXIT_SUCCESS); + break; - case 'h': - hostname = optarg; - break; + case 'P': + token = optarg ? optarg : getpass("Password: "); + break; - case 'I': - command.set_operation(RESERVED_IDS_INFO); - break; + case 'V': + command.set_operation(VERSION_INFO); + break; - case 'L': - command.set_operation(LOG_LEVEL); - log_level = optarg; - break; + case 'x': + command.set_operation(COPY_IMAGE); + image_params = optarg; + break; - case 'l': - list = true; - break; + case 'T': + command.set_operation(DEVICE_TYPES_INFO); + break; - case 'm': - command.set_operation(MAPPING_INFO); - break; + case 'X': + command.set_operation(SHUT_DOWN); + SetParam(command, "mode", "rascsi"); + break; - case 'N': - command.set_operation(NETWORK_INTERFACES_INFO); - break; + case 'z': + locale = optarg; + break; - case 'O': - command.set_operation(LOG_LEVEL_INFO); - break; - - case 'o': - command.set_operation(OPERATION_INFO); - break; - - case 't': - device->set_type(parser.ParseType(optarg)); - if (device->type() == UNDEFINED) { - cerr << "Error: Unknown device type '" << optarg << "'" << endl; - exit(EXIT_FAILURE); - } - break; - - case 'r': - command.set_operation(RESERVE_IDS); - reserved_ids = optarg; - break; - - case 'R': - command.set_operation(RENAME_IMAGE); - image_params = optarg; - break; - - case 'n': - SetProductData(*device, optarg); - break; - - case 'p': - if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) { - cerr << "Error: Invalid port " << optarg << ", port must be between 1 and 65535" << endl; - exit(EXIT_FAILURE); - } - break; - - case 's': - command.set_operation(SERVER_INFO); - if (optarg) { - if (const string error = SetCommandParams(command, optarg); !error.empty()) { - cerr << "Error: " << error << endl; - exit(EXIT_FAILURE); - } - } - break; - - case 'S': - command.set_operation(STATISTICS_INFO); - break; - - case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << '\n'; - exit(EXIT_SUCCESS); - break; - - case 'P': - token = optarg ? optarg : getpass("Password: "); - break; - - case 'V': - command.set_operation(VERSION_INFO); - break; - - case 'x': - command.set_operation(COPY_IMAGE); - image_params = optarg; - break; - - case 'T': - command.set_operation(DEVICE_TYPES_INFO); - break; - - case 'X': - command.set_operation(SHUT_DOWN); - SetParam(command, "mode", "rascsi"); - break; - - case 'z': - locale = optarg; - break; - - default: - break; - } - } + default: + break; + } + } // For macos only 'optind != argc' appears to work, but then non-argument options do not reject arguments if (optopt) { exit(EXIT_FAILURE); } - SetParam(command, "token", token); - SetParam(command, "locale", locale); + if (!filename_json.empty()) { + return ExportAsJson(command, filename_json); + } + if (!filename_binary.empty()) { + return ExportAsBinary(command, filename_binary); + } + if (!filename_text.empty()) { + return ExportAsText(command, filename_text); + } + + SetParam(command, "token", token); + SetParam(command, "locale", locale); ScsictlCommands scsictl_commands(command, hostname, port); @@ -277,7 +328,7 @@ int ScsiCtl::run(const vector& args) const else { ParseParameters(*device, param); - status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); + status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); } } catch(const io_exception& e) { @@ -290,3 +341,48 @@ int ScsiCtl::run(const vector& args) const return status ? EXIT_SUCCESS : EXIT_FAILURE; } + +int ScsiCtl::ExportAsBinary(const PbCommand &command, const string &filename) const +{ + const string binary = command.SerializeAsString(); + + ofstream out; + out.open(filename, ios::binary); + out << binary; + if (out.fail()) { + cerr << "Error: Can't create protobuf binary file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int ScsiCtl::ExportAsJson(const PbCommand &command, const string &filename) const +{ + string json; + MessageToJsonString(command, &json); + + ofstream out(filename); + out << json; + if (out.fail()) { + cerr << "Error: Can't create protobuf JSON file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +int ScsiCtl::ExportAsText(const PbCommand &command, const string &filename) const +{ + string text; + TextFormat::PrintToString(command, &text); + + ofstream out(filename); + out << text; + if (out.fail()) { + cerr << "Error: Can't create protobuf text format file '" << filename << "'" << endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/cpp/scsictl/scsictl_core.h b/cpp/scsictl/scsictl_core.h index a96076f4..4b8025ec 100644 --- a/cpp/scsictl/scsictl_core.h +++ b/cpp/scsictl/scsictl_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,4 +28,8 @@ class ScsiCtl private: void Banner(const vector&) const; + + int ExportAsBinary(const PbCommand&, const string&) const; + int ExportAsJson(const PbCommand&, const string&) const; + int ExportAsText(const PbCommand&, const string&) const; }; diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 3df74fba..7737e939 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -17,6 +17,10 @@ scsictl \- Sends management commands to the piscsi process \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | +\fB\-Z\fR | +[\fB\-d\fR \fIFILENAME\fR] | +[\fB\-B\fR \fIFILENAME\fR] | +[\fB\-J\fR \fIFILENAME\fR] | [\fB\-C\fR \fIFILENAME:FILESIZE\fR] | [\fB\-E\fR \fIFILENAME\fR] | [\fB\-F\fR \fIIMAGE_FOLDER\fR] | @@ -117,6 +121,15 @@ Shut down the piscsi process. .BR \-d\fI " "\fIFILENAME Delete an image file in the default image folder. .TP +.BR \-B\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf binary file. +.TP +.BR \-J\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf JSON file. +.TP +.BR \-Z\fI " "\fIFILENAME +Do not send command to piscsi but write it to a protobuf text format file. +.TP .BR \-x\fI " "\fICURRENT_NAME:NEW_NAME Copy an image file in the default image folder. .TP diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index f98b7208..7dfc6e10 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -7,22 +7,23 @@ NAME SYNOPSIS scsictl -e | -l | -m | -o | -v | -D | -I | -L | -O | -P | -S | -T | -V - | -X | [-C FILENAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R - CURRENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] | - [-h HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] | - [-s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ + | -X | -Z | [-d FILENAME] | [-B FILENAME] | [-J FILENAME] | [-C FILE‐ + NAME:FILESIZE] | [-E FILENAME] | [-F IMAGE_FOLDER] | [-R CUR‐ + RENT_NAME:NEW_NAME] | [-c CMD] | [-f FILE|PARAM] | [-g LOG_LEVEL] | [-h + HOST] | [-i ID[:LUN]] | [-n NAME] | [-p PORT] | [-r RESERVED_IDS] | [-s + [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS]] | [-t TYPE] | [-x CUR‐ RENT_NAME:NEW_NAME] | [-z LOCALE] DESCRIPTION - scsictl sends commands to the piscsi process to make configuration ad‐ + scsictl sends commands to the piscsi process to make configuration ad‐ justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. - You do NOT need root privileges to use scsictl. scsictl also runs on + You do NOT need root privileges to use scsictl. scsictl also runs on non-Pi Linux platforms. - Note: The command and type arguments are case insensitive. Only the + Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the tool. OPTIONS @@ -41,7 +42,7 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warning, error, + Set the piscsi log level (trace, debug, info, warning, error, off). -h HOST @@ -49,19 +50,19 @@ OPTIONS -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are + -N Lists all available network interfaces provided that they are up. - -O Display the available piscsi server log levels and the current + -O Display the available piscsi server log levels and the current log level. - -P Prompt for the access token in case piscsi requires authentica‐ + -P Prompt for the access token in case piscsi requires authentica‐ tion. - -l List all of the devices that are currently being emulated by + -l List all of the devices that are currently being emulated by PiSCSI, as well as their current status. - -m List all file extensions recognized by PiSCSI and the device + -m List all file extensions recognized by PiSCSI and the device types they map to. -o Display operation meta data information. @@ -73,11 +74,11 @@ OPTIONS The piscsi port to connect to, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in + Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -s [FOLDER_PATTERN:FILE_PATTERN:OPERATIONS] - Display server-side settings like available images or supported + Display server-side settings like available images or supported device types. -S Display statistics. @@ -93,6 +94,18 @@ OPTIONS -d FILENAME Delete an image file in the default image folder. + -B FILENAME + Do not send command to piscsi but write it to a protobuf binary + file. + + -J FILENAME + Do not send command to piscsi but write it to a protobuf JSON + file. + + -Z FILENAME + Do not send command to piscsi but write it to a protobuf text + format file. + -x CURRENT_NAME:NEW_NAME Copy an image file in the default image folder.