Add JSON file support

This commit is contained in:
Uwe Seimet 2023-11-14 09:43:08 +01:00
parent 23e5286267
commit b04a96a782
4 changed files with 230 additions and 188 deletions

View File

@ -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 <google/protobuf/util/json_util.h>
#include <unistd.h>
#include <clocale>
#include <iostream>
#include <fstream>
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<char *>& 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<char *>& 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<int>(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<char *>& 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<char *>& 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<char *>& 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;
}

View File

@ -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<char *>&) const;
int ExportAsJson(const PbCommand&, const string&) const;
};

View File

@ -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

View File

@ -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