2022-11-02 22:41:45 +00:00
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
//
|
2022-12-05 17:58:23 +00:00
|
|
|
// SCSI Target Emulator PiSCSI
|
2022-11-02 22:41:45 +00:00
|
|
|
// for Raspberry Pi
|
|
|
|
//
|
|
|
|
// Powered by XM6 TypeG Technology.
|
|
|
|
// Copyright (C) 2016-2020 GIMONS
|
2023-01-14 18:26:49 +00:00
|
|
|
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
|
2022-11-02 22:41:45 +00:00
|
|
|
//
|
|
|
|
//---------------------------------------------------------------------------
|
|
|
|
|
2022-11-10 06:44:06 +00:00
|
|
|
#include "controllers/scsi_controller.h"
|
2022-12-05 17:58:23 +00:00
|
|
|
#include "shared/piscsi_util.h"
|
2022-11-10 06:44:06 +00:00
|
|
|
#include "shared/protobuf_util.h"
|
2022-12-05 17:58:23 +00:00
|
|
|
#include "shared/piscsi_exceptions.h"
|
|
|
|
#include "shared/piscsi_version.h"
|
|
|
|
#include "generated/piscsi_interface.pb.h"
|
|
|
|
#include "scsictl/scsictl_parser.h"
|
|
|
|
#include "scsictl/scsictl_commands.h"
|
|
|
|
#include "scsictl/scsictl_core.h"
|
2022-11-02 22:41:45 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
#include <clocale>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
using namespace std;
|
2022-12-05 17:58:23 +00:00
|
|
|
using namespace piscsi_interface;
|
|
|
|
using namespace piscsi_util;
|
2022-11-02 22:41:45 +00:00
|
|
|
using namespace protobuf_util;
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
void ScsiCtl::Banner(const vector<char *>& args) const
|
2022-11-02 22:41:45 +00:00
|
|
|
{
|
|
|
|
if (args.size() < 2) {
|
2022-12-05 17:58:23 +00:00
|
|
|
cout << piscsi_util::Banner("(Controller App)");
|
2022-11-02 22:41:45 +00:00
|
|
|
|
2022-11-10 06:44:06 +00:00
|
|
|
cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] ";
|
2022-11-02 22:41:45 +00:00
|
|
|
cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] ";
|
|
|
|
cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] ";
|
|
|
|
cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] ";
|
|
|
|
cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n";
|
2022-11-10 06:44:06 +00:00
|
|
|
cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n";
|
2022-11-02 22:41:45 +00:00
|
|
|
cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n";
|
|
|
|
cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n";
|
|
|
|
cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n";
|
|
|
|
cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n";
|
|
|
|
cout << " FILE|PARAM := image file path or device-specific parameter\n";
|
|
|
|
cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n";
|
2022-12-05 17:58:23 +00:00
|
|
|
cout << " HOST := piscsi host to connect to, default is 'localhost'\n";
|
|
|
|
cout << " PORT := piscsi port to connect to, default is 6868\n";
|
2022-11-02 22:41:45 +00:00
|
|
|
cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n";
|
|
|
|
cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n";
|
|
|
|
cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n";
|
|
|
|
cout << "Usage: " << args[0] << " -l\n";
|
|
|
|
cout << " Print device list.\n" << flush;
|
|
|
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
int ScsiCtl::run(const vector<char *>& args) const
|
2022-11-02 22:41:45 +00:00
|
|
|
{
|
|
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
|
|
|
|
|
|
Banner(args);
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
ScsictlParser parser;
|
2022-11-02 22:41:45 +00:00
|
|
|
PbCommand command;
|
|
|
|
PbDeviceDefinition* device = command.add_devices();
|
|
|
|
device->set_id(-1);
|
|
|
|
const char *hostname = "localhost";
|
|
|
|
int port = 6868;
|
|
|
|
string param;
|
|
|
|
string log_level;
|
|
|
|
string default_folder;
|
|
|
|
string reserved_ids;
|
|
|
|
string image_params;
|
|
|
|
string filename;
|
|
|
|
string token;
|
|
|
|
bool list = false;
|
|
|
|
|
|
|
|
const char *locale = setlocale(LC_MESSAGES, "");
|
|
|
|
if (locale == nullptr || !strcmp(locale, "C")) {
|
|
|
|
locale = "en";
|
|
|
|
}
|
|
|
|
|
|
|
|
opterr = 1;
|
|
|
|
int opt;
|
|
|
|
while ((opt = getopt(static_cast<int>(args.size()), args.data(),
|
2022-11-10 06:44:06 +00:00
|
|
|
"e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
|
2022-11-02 22:41:45 +00:00
|
|
|
switch (opt) {
|
2022-11-11 20:08:48 +00:00
|
|
|
case 'i':
|
|
|
|
if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) {
|
|
|
|
cerr << "Error: " << error << endl;
|
2022-11-02 22:41:45 +00:00
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'C':
|
|
|
|
command.set_operation(CREATE_IMAGE);
|
|
|
|
image_params = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'b':
|
|
|
|
int block_size;
|
2022-11-10 06:44:06 +00:00
|
|
|
if (!GetAsUnsignedInt(optarg, block_size)) {
|
2022-11-02 22:41:45 +00:00
|
|
|
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 'D':
|
|
|
|
command.set_operation(DETACH_ALL);
|
|
|
|
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(DEFAULT_IMAGE_FILES_INFO);
|
|
|
|
if (optarg) {
|
|
|
|
SetPatternParams(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 '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;
|
|
|
|
|
2022-11-11 20:08:48 +00:00
|
|
|
case 'n':
|
|
|
|
SetProductData(*device, optarg);
|
2022-11-02 22:41:45 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
2022-11-10 06:44:06 +00:00
|
|
|
if (!GetAsUnsignedInt(optarg, port) || port <= 0 || port > 65535) {
|
2022-11-02 22:41:45 +00:00
|
|
|
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) {
|
|
|
|
SetPatternParams(command, optarg);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'v':
|
2022-12-05 17:58:23 +00:00
|
|
|
cout << "scsictl version: " << piscsi_get_version_string() << endl;
|
2022-11-02 22:41:45 +00:00
|
|
|
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);
|
2023-01-14 18:26:49 +00:00
|
|
|
SetParam(command, "mode", "rascsi");
|
2022-11-02 22:41:45 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'z':
|
|
|
|
locale = optarg;
|
|
|
|
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);
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
ScsictlCommands scsictl_commands(command, hostname, port);
|
2022-11-02 22:41:45 +00:00
|
|
|
|
|
|
|
bool status;
|
|
|
|
try {
|
|
|
|
// Listing devices is a special case (rasctl backwards compatibility)
|
|
|
|
if (list) {
|
|
|
|
command.clear_devices();
|
|
|
|
command.set_operation(DEVICES_INFO);
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
status = scsictl_commands.CommandDevicesInfo();
|
2022-11-02 22:41:45 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
ParseParameters(*device, param);
|
|
|
|
|
2022-12-05 17:58:23 +00:00
|
|
|
status = scsictl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename);
|
2022-11-02 22:41:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch(const io_exception& e) {
|
|
|
|
cerr << "Error: " << e.what() << endl;
|
|
|
|
|
|
|
|
status = false;
|
|
|
|
|
|
|
|
// Fall through
|
|
|
|
}
|
|
|
|
|
|
|
|
return status ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
|
|
}
|