diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index 6f6b8860..ea1e4c04 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,14 @@ #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" +#include #include #include #include +#include using namespace std; +using namespace google::protobuf::util; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; @@ -33,7 +37,7 @@ 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] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-j 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) << "}," @@ -76,184 +80,190 @@ int ScsiCtl::run(const vector& args) const string filename; string token; bool list = false; + bool to_json = false; string locale = GetLocale(); 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:j: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; - 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': + command.set_operation(IMAGE_FILE_INFO); + filename = optarg; + 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; + break; + + case 'j': + filename = optarg; + to_json = true; + 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) { @@ -263,6 +273,10 @@ int ScsiCtl::run(const vector& args) const SetParam(command, "token", token); SetParam(command, "locale", locale); + if (to_json) { + return ExportAsJson(command, filename); + } + ScsictlCommands scsictl_commands(command, hostname, port); bool status; @@ -277,7 +291,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 +304,20 @@ int ScsiCtl::run(const vector& args) const return status ? EXIT_SUCCESS : EXIT_FAILURE; } + +int ScsiCtl::ExportAsJson(const PbCommand &command, const string &filename) const +{ + string json; + JsonPrintOptions options; + options.add_whitespace = true; + MessageToJsonString(command, &json, options); + + ofstream out(filename); + out << json << '\n'; + if (out.fail()) { + cerr << "Error: Can't create JSON 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..05079398 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,6 @@ class ScsiCtl private: void Banner(const vector&) const; + + int ExportAsJson(const PbCommand&, const string&) const; }; diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 3df74fba..34665861 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -17,6 +17,8 @@ scsictl \- Sends management commands to the piscsi process \fB\-T\fR | \fB\-V\fR | \fB\-X\fR | +[\fB\-d\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 +119,9 @@ Shut down the piscsi process. .BR \-d\fI " "\fIFILENAME Delete an image file in the default image folder. .TP +.BR \-j\fI " "\fIFILENAME +Do not send command to piscsi but write it to a JSON 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..d494946c 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‐ - RENT_NAME:NEW_NAME] | [-z LOCALE] + | -X | [-d FILENAME] | [-j FILENAME] | [-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_PAT‐ + TERN:OPERATIONS]] | [-t TYPE] | [-x CURRENT_NAME:NEW_NAME] | [-z LO‐ + CALE] 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,9 @@ OPTIONS -d FILENAME Delete an image file in the default image folder. + -j FILENAME + Do not send command to piscsi but write it to a JSON file. + -x CURRENT_NAME:NEW_NAME Copy an image file in the default image folder. @@ -108,7 +112,7 @@ OPTIONS d(etach): Detach disk i(nsert): Insert media (removable media devices only) e(ject): Eject media (removable media devices only) - p(rotect): Write protect the medium (not for CD-ROMs, which + p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only) u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only) @@ -117,18 +121,18 @@ OPTIONS eject, protect and unprotect are idempotent. -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes. -f FILE|PARAM Device-specific: Either a path to a disk image file, or a param‐ - eter for a non-disk device. See the piscsi(1) man page for per‐ + eter for a non-disk device. See the piscsi(1) man page for per‐ mitted file types. -t TYPE - Specifies the device type. This type overrides the type derived + Specifies the device type. This type overrides the type derived from the file extension of the specified image. See the - piscsi(1) man page for the available device types. For some + piscsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is required): hd: SCSI hard disk drive rm: SCSI removable media drive @@ -140,16 +144,16 @@ OPTIONS services: Host services device -n VENDOR:PRODUCT:REVISION - The vendor, product and revision for the device, to be returned + The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks to the maxium length is au‐ - tomatically applied. Once set the name of a device cannot be + tomatically applied. Once set the name of a device cannot be changed. -u UNIT - Unit number (0-31). This will default to 0. This option is only - used when there are multiple SCSI devices on a shared SCSI con‐ + Unit number (0-31). This will default to 0. This option is only + used when there are multiple SCSI devices on a shared SCSI con‐ troller. (This is not common) EXAMPLES