mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-21 23:29:39 +00:00
* Add options to only run INQUIRY and to scan the bus to scsidump
This commit is contained in:
parent
c78ba80088
commit
8f45e4f491
@ -12,9 +12,9 @@
|
|||||||
|
|
||||||
// TODO Evaluate CHECK CONDITION after sending a command
|
// TODO Evaluate CHECK CONDITION after sending a command
|
||||||
// TODO Send IDENTIFY message in order to support LUNS > 7
|
// TODO Send IDENTIFY message in order to support LUNS > 7
|
||||||
|
// TODO Get rid of some fields in favor of method arguments
|
||||||
|
|
||||||
#include "scsidump/scsidump_core.h"
|
#include "scsidump/scsidump_core.h"
|
||||||
#include "hal/gpiobus.h"
|
|
||||||
#include "hal/gpiobus_factory.h"
|
#include "hal/gpiobus_factory.h"
|
||||||
#include "hal/systimer.h"
|
#include "hal/systimer.h"
|
||||||
#include "controllers/controller_manager.h"
|
#include "controllers/controller_manager.h"
|
||||||
@ -44,11 +44,11 @@ void ScsiDump::CleanUp()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::KillHandler(int)
|
void ScsiDump::TerminationHandler(int)
|
||||||
{
|
{
|
||||||
CleanUp();
|
CleanUp();
|
||||||
|
|
||||||
exit(EXIT_SUCCESS);
|
// Process will terminate automatically
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScsiDump::Banner(span<char *> args) const
|
bool ScsiDump::Banner(span<char *> args) const
|
||||||
@ -56,7 +56,7 @@ bool ScsiDump::Banner(span<char *> args) const
|
|||||||
cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
|
cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
|
||||||
|
|
||||||
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
|
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
|
||||||
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n"
|
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p] [-I] [-S]\n"
|
||||||
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
|
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
|
||||||
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
|
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
|
||||||
<< " Default is 0.\n"
|
<< " Default is 0.\n"
|
||||||
@ -67,6 +67,8 @@ bool ScsiDump::Banner(span<char *> args) const
|
|||||||
<< " -v Enable verbose logging.\n"
|
<< " -v Enable verbose logging.\n"
|
||||||
<< " -r Restore instead of dump.\n"
|
<< " -r Restore instead of dump.\n"
|
||||||
<< " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n"
|
<< " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n"
|
||||||
|
<< " -I Display INQUIRY data of ID[:LUN].\n"
|
||||||
|
<< " -S Scan SCSI bus for devices.\n"
|
||||||
<< flush;
|
<< flush;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -77,11 +79,13 @@ bool ScsiDump::Banner(span<char *> args) const
|
|||||||
|
|
||||||
bool ScsiDump::Init() const
|
bool ScsiDump::Init() const
|
||||||
{
|
{
|
||||||
// Interrupt handler setting
|
// Signal handler for cleaning up
|
||||||
if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR ||
|
struct sigaction termination_handler;
|
||||||
signal(SIGTERM, KillHandler) == SIG_ERR) {
|
termination_handler.sa_handler = TerminationHandler;
|
||||||
return false;
|
sigemptyset(&termination_handler.sa_mask);
|
||||||
}
|
termination_handler.sa_flags = 0;
|
||||||
|
sigaction(SIGTERM, &termination_handler, nullptr);
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
|
||||||
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
|
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
|
||||||
|
|
||||||
@ -95,7 +99,7 @@ void ScsiDump::ParseArguments(span<char *> args)
|
|||||||
int buffer_size = DEFAULT_BUFFER_SIZE;
|
int buffer_size = DEFAULT_BUFFER_SIZE;
|
||||||
|
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvp")) != -1) {
|
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvpIS")) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'i':
|
case 'i':
|
||||||
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
|
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
|
||||||
@ -107,6 +111,10 @@ void ScsiDump::ParseArguments(span<char *> args)
|
|||||||
filename = optarg;
|
filename = optarg;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'I':
|
||||||
|
inquiry = true;
|
||||||
|
break;
|
||||||
|
|
||||||
case 's':
|
case 's':
|
||||||
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
|
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
|
||||||
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
|
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
|
||||||
@ -114,15 +122,15 @@ void ScsiDump::ParseArguments(span<char *> args)
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't': {
|
case 'S':
|
||||||
const string error = ProcessId(optarg, target_id, target_lun);
|
scan_bus = true;
|
||||||
if (!error.empty()) {
|
break;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) {
|
||||||
throw parser_exception(error);
|
throw parser_exception(error);
|
||||||
}
|
}
|
||||||
if (target_lun == -1) {
|
break;
|
||||||
target_lun = 0;
|
|
||||||
}
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
set_level(level::debug);
|
set_level(level::debug);
|
||||||
@ -141,31 +149,43 @@ void ScsiDump::ParseArguments(span<char *> args)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!scan_bus && !inquiry && filename.empty()) {
|
||||||
|
throw parser_exception("Missing filename");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scan_bus && !inquiry && target_id == -1) {
|
||||||
|
throw parser_exception("Missing target ID");
|
||||||
|
}
|
||||||
|
|
||||||
if (target_id == initiator_id) {
|
if (target_id == initiator_id) {
|
||||||
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
|
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filename.empty()) {
|
if (target_lun == -1) {
|
||||||
throw parser_exception("Missing filename");
|
target_lun = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scan_bus) {
|
||||||
|
inquiry = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer = vector<uint8_t>(buffer_size);
|
buffer = vector<uint8_t>(buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::WaitPhase(phase_t phase) const
|
void ScsiDump::WaitForPhase(phase_t phase) const
|
||||||
{
|
{
|
||||||
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
|
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
|
||||||
|
|
||||||
// Timeout (3000ms)
|
// Timeout (3000ms)
|
||||||
const uint32_t now = SysTimer::GetTimerLow();
|
const uint32_t now = SysTimer::GetTimerLow();
|
||||||
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
|
while ((SysTimer::GetTimerLow() - now) < 3'000'000) {
|
||||||
bus->Acquire();
|
bus->Acquire();
|
||||||
if (bus->GetREQ() && bus->GetPhase() == phase) {
|
if (bus->GetREQ() && bus->GetPhase() == phase) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
|
throw phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
|
||||||
string(BUS::GetPhaseStrRaw(bus->GetPhase())));
|
string(BUS::GetPhaseStrRaw(bus->GetPhase())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +209,7 @@ void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
|
|||||||
|
|
||||||
Selection();
|
Selection();
|
||||||
|
|
||||||
WaitPhase(phase_t::command);
|
WaitForPhase(phase_t::command);
|
||||||
|
|
||||||
cdb[0] = static_cast<uint8_t>(cmd);
|
cdb[0] = static_cast<uint8_t>(cmd);
|
||||||
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
|
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
|
||||||
@ -197,43 +217,43 @@ void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
|
|||||||
bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
|
bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
|
||||||
BusFree();
|
BusFree();
|
||||||
|
|
||||||
throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed"));
|
throw phase_exception(command_mapping.find(cmd)->second.second + string(" failed"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::DataIn(int length)
|
void ScsiDump::DataIn(int length)
|
||||||
{
|
{
|
||||||
WaitPhase(phase_t::datain);
|
WaitForPhase(phase_t::datain);
|
||||||
|
|
||||||
if (!bus->ReceiveHandShake(buffer.data(), length)) {
|
if (!bus->ReceiveHandShake(buffer.data(), length)) {
|
||||||
throw parser_exception("DATA IN failed");
|
throw phase_exception("DATA IN failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::DataOut(int length)
|
void ScsiDump::DataOut(int length)
|
||||||
{
|
{
|
||||||
WaitPhase(phase_t::dataout);
|
WaitForPhase(phase_t::dataout);
|
||||||
|
|
||||||
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
|
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
|
||||||
throw parser_exception("DATA OUT failed");
|
throw phase_exception("DATA OUT failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::Status() const
|
void ScsiDump::Status() const
|
||||||
{
|
{
|
||||||
WaitPhase(phase_t::status);
|
WaitForPhase(phase_t::status);
|
||||||
|
|
||||||
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
|
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
|
||||||
throw parser_exception("STATUS failed");
|
throw phase_exception("STATUS failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::MessageIn() const
|
void ScsiDump::MessageIn() const
|
||||||
{
|
{
|
||||||
WaitPhase(phase_t::msgin);
|
WaitForPhase(phase_t::msgin);
|
||||||
|
|
||||||
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
|
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
|
||||||
throw parser_exception("MESSAGE IN failed");
|
throw phase_exception("MESSAGE IN failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -388,7 +408,7 @@ void ScsiDump::WaitForBusy() const
|
|||||||
|
|
||||||
// Success if the target is busy
|
// Success if the target is busy
|
||||||
if (!bus->GetBSY()) {
|
if (!bus->GetBSY()) {
|
||||||
throw parser_exception("SELECTION failed");
|
throw phase_exception("SELECTION failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,23 +418,44 @@ int ScsiDump::run(span<char *> args)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Init()) {
|
|
||||||
cerr << "Error: Initializing. Are you root?" << endl;
|
|
||||||
|
|
||||||
// Probably not root
|
|
||||||
return EPERM;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ParseArguments(args);
|
ParseArguments(args);
|
||||||
|
}
|
||||||
|
catch (const parser_exception& e) {
|
||||||
|
cerr << "Error: " << e.what() << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getuid()) {
|
||||||
|
cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef USE_SEL_EVENT_ENABLE
|
#ifndef USE_SEL_EVENT_ENABLE
|
||||||
cerr << "Error: No PiSCSI hardware support" << endl;
|
cerr << "Error: No PiSCSI hardware support" << endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return DumpRestore();
|
if (!Init()) {
|
||||||
} catch (const parser_exception& e) {
|
cerr << "Error: Can't initialize bus" << endl;
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (scan_bus) {
|
||||||
|
ScanBus();
|
||||||
|
}
|
||||||
|
else if (inquiry) {
|
||||||
|
DisplayBoardId();
|
||||||
|
|
||||||
|
inquiry_info_t inq_info;
|
||||||
|
DisplayInquiry(inq_info, false);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DumpRestore();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const phase_exception& e) {
|
||||||
cerr << "Error: " << e.what() << endl;
|
cerr << "Error: " << e.what() << endl;
|
||||||
|
|
||||||
CleanUp();
|
CleanUp();
|
||||||
@ -427,9 +468,92 @@ int ScsiDump::run(span<char *> args)
|
|||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ScsiDump::DisplayBoardId() const
|
||||||
|
{
|
||||||
|
cout << DIVIDER << "\nPiSCSI board ID is " << initiator_id << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScsiDump::ScanBus()
|
||||||
|
{
|
||||||
|
DisplayBoardId();
|
||||||
|
|
||||||
|
for (target_id = 0; target_id < ControllerManager::GetScsiIdMax(); target_id++) {
|
||||||
|
if (initiator_id == target_id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (target_lun = 0; target_lun < 8; target_lun++) {
|
||||||
|
inquiry_info_t inq_info;
|
||||||
|
try {
|
||||||
|
DisplayInquiry(inq_info, false);
|
||||||
|
}
|
||||||
|
catch(const phase_exception&) {
|
||||||
|
// Continue with next ID if there is no LUN 0
|
||||||
|
if (!target_lun) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScsiDump::DisplayInquiry(ScsiDump::inquiry_info_t& inq_info, bool check_type)
|
||||||
|
{
|
||||||
|
// Assert RST for 1 ms
|
||||||
|
bus->SetRST(true);
|
||||||
|
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
|
||||||
|
nanosleep(&ts, nullptr);
|
||||||
|
bus->SetRST(false);
|
||||||
|
|
||||||
|
cout << DIVIDER << "\nTarget device is " << target_id << ":" << target_lun << "\n" << flush;
|
||||||
|
|
||||||
|
Inquiry();
|
||||||
|
|
||||||
|
const auto type = static_cast<byte>(buffer[0]);
|
||||||
|
if ((type & byte{0x1f}) == byte{0x1f}) {
|
||||||
|
// Requested LUN is not available
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
array<char, 17> str = {};
|
||||||
|
memcpy(str.data(), &buffer[8], 8);
|
||||||
|
inq_info.vendor = string(str.data());
|
||||||
|
cout << "Vendor: " << inq_info.vendor << "\n";
|
||||||
|
|
||||||
|
str.fill(0);
|
||||||
|
memcpy(str.data(), &buffer[16], 16);
|
||||||
|
inq_info.product = string(str.data());
|
||||||
|
cout << "Product: " << inq_info.product << "\n";
|
||||||
|
|
||||||
|
str.fill(0);
|
||||||
|
memcpy(str.data(), &buffer[32], 4);
|
||||||
|
inq_info.revision = string(str.data());
|
||||||
|
cout << "Revision: " << inq_info.revision << "\n" << flush;
|
||||||
|
|
||||||
|
if (const auto& t = DEVICE_TYPES.find(type & byte{0x1f}); t != DEVICE_TYPES.end()) {
|
||||||
|
cout << "Device Type: " << (*t).second << "\n";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cout << "Device Type: Unknown\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
cout << "Removable: " << (((static_cast<byte>(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n";
|
||||||
|
|
||||||
|
if (check_type && type != static_cast<byte>(device_type::direct_access) &&
|
||||||
|
type != static_cast<byte>(device_type::cd_rom) && type != static_cast<byte>(device_type::optical_memory)) {
|
||||||
|
cerr << "Invalid device type, supported types for dump/restore are DIRECT ACCESS, CD-ROM/DVD/BD and OPTICAL MEMORY" << endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int ScsiDump::DumpRestore()
|
int ScsiDump::DumpRestore()
|
||||||
{
|
{
|
||||||
const auto inq_info = GetDeviceInfo();
|
inquiry_info_t inq_info;
|
||||||
|
if (!GetDeviceInfo(inq_info)) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
fstream fs;
|
fstream fs;
|
||||||
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
|
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
|
||||||
@ -510,58 +634,28 @@ int ScsiDump::DumpRestore()
|
|||||||
|
|
||||||
const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
|
const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
|
||||||
|
|
||||||
cout << divider_str << "\n";
|
cout << DIVIDER << "\n";
|
||||||
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
|
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
|
||||||
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
|
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
|
||||||
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
|
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
|
||||||
cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
|
cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
|
||||||
<< " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
|
<< " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
|
||||||
<< " KiB per second)\n";
|
<< " KiB per second)\n";
|
||||||
cout << divider_str << "\n";
|
cout << DIVIDER << "\n";
|
||||||
|
|
||||||
if (properties_file && !restore) {
|
if (properties_file && !restore) {
|
||||||
GeneratePropertiesFile(filename, inq_info);
|
inq_info.GeneratePropertiesFile(filename + ".properties");
|
||||||
}
|
}
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
|
bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info)
|
||||||
{
|
{
|
||||||
// Assert RST for 1 ms
|
DisplayBoardId();
|
||||||
bus->SetRST(true);
|
|
||||||
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
|
|
||||||
nanosleep(&ts, nullptr);
|
|
||||||
bus->SetRST(false);
|
|
||||||
|
|
||||||
cout << divider_str << "\n";
|
if (!DisplayInquiry(inq_info, true)) {
|
||||||
cout << "PiSCSI board ID: " << initiator_id << "\n";
|
return false;
|
||||||
cout << divider_str << "\n" << flush;
|
|
||||||
cout << "Target device is " << target_id << ":" << target_lun << "\n";
|
|
||||||
|
|
||||||
Inquiry();
|
|
||||||
|
|
||||||
inquiry_info_t inq_info;
|
|
||||||
|
|
||||||
// Display INQUIRY information
|
|
||||||
array<char, 17> str = {};
|
|
||||||
memcpy(str.data(), &buffer[8], 8);
|
|
||||||
cout << "Vendor: " << str.data() << "\n";
|
|
||||||
inq_info.vendor = string(str.data());
|
|
||||||
|
|
||||||
str.fill(0);
|
|
||||||
memcpy(str.data(), &buffer[16], 16);
|
|
||||||
cout << "Product: " << str.data() << "\n";
|
|
||||||
inq_info.product = string(str.data());
|
|
||||||
|
|
||||||
str.fill(0);
|
|
||||||
memcpy(str.data(), &buffer[32], 4);
|
|
||||||
cout << "Revision: " << str.data() << "\n" << flush;
|
|
||||||
inq_info.revision = string(str.data());
|
|
||||||
|
|
||||||
if (const auto type = static_cast<device_type>(buffer[0]);
|
|
||||||
type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) {
|
|
||||||
throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TestUnitReady();
|
TestUnitReady();
|
||||||
@ -576,31 +670,24 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
|
|||||||
<< "Sector size: " << sector_size << " bytes\n"
|
<< "Sector size: " << sector_size << " bytes\n"
|
||||||
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity
|
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity
|
||||||
<< " bytes)\n"
|
<< " bytes)\n"
|
||||||
<< divider_str << "\n\n"
|
<< DIVIDER << "\n\n"
|
||||||
<< flush;
|
<< flush;
|
||||||
|
|
||||||
return inq_info;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
|
void ScsiDump::inquiry_info::GeneratePropertiesFile(const string& property_file) const
|
||||||
{
|
{
|
||||||
const string prop_filename = filename + ".properties";
|
ofstream prop_stream(property_file);
|
||||||
stringstream prop_stream;
|
|
||||||
|
|
||||||
prop_stream << "{" << endl;
|
prop_stream << "{" << endl;
|
||||||
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl;
|
prop_stream << " \"vendor\": \"" << vendor << "\"," << endl;
|
||||||
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl;
|
prop_stream << " \"product\": \"" << product << "\"," << endl;
|
||||||
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl;
|
prop_stream << " \"revision\": \"" << revision << "\"," << endl;
|
||||||
prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl;
|
prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl;
|
||||||
prop_stream << "}" << endl;
|
prop_stream << "}" << endl;
|
||||||
|
|
||||||
FILE* fp = fopen(prop_filename.c_str(), "w");
|
if (prop_stream.fail()) {
|
||||||
if (fp) {
|
spdlog::warn("Unable to create properties file '" + property_file + "'");
|
||||||
fputs(prop_stream.str().c_str(), fp);
|
|
||||||
} else {
|
|
||||||
spdlog::warn("Unable to open output file '" + prop_filename + "'");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(fp);
|
|
||||||
}
|
}
|
||||||
|
@ -10,45 +10,52 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "hal/bus.h"
|
#include "hal/bus.h"
|
||||||
#include "shared/scsi.h"
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
class phase_exception : public runtime_error
|
||||||
|
{
|
||||||
|
using runtime_error::runtime_error;
|
||||||
|
};
|
||||||
|
|
||||||
class ScsiDump
|
class ScsiDump
|
||||||
{
|
{
|
||||||
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
|
|
||||||
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
ScsiDump() = default;
|
ScsiDump() = default;
|
||||||
~ScsiDump() = default;
|
~ScsiDump() = default;
|
||||||
|
|
||||||
int run(const span<char *>);
|
int run(const span<char *>);
|
||||||
|
|
||||||
struct inquiry_info_struct {
|
struct inquiry_info {
|
||||||
string vendor;
|
string vendor;
|
||||||
string product;
|
string product;
|
||||||
string revision;
|
string revision;
|
||||||
uint32_t sector_size;
|
uint32_t sector_size;
|
||||||
uint64_t capacity;
|
uint64_t capacity;
|
||||||
};
|
|
||||||
using inquiry_info_t = struct inquiry_info_struct;
|
|
||||||
|
|
||||||
protected:
|
void GeneratePropertiesFile(const string&) const;
|
||||||
// Protected for testability
|
};
|
||||||
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info);
|
using inquiry_info_t = struct inquiry_info;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool Banner(span<char *>) const;
|
bool Banner(span<char *>) const;
|
||||||
bool Init() const;
|
bool Init() const;
|
||||||
void ParseArguments(span<char *>);
|
void ParseArguments(span<char *>);
|
||||||
|
void DisplayBoardId() const;
|
||||||
|
void ScanBus();
|
||||||
|
bool DisplayInquiry(inquiry_info_t&, bool);
|
||||||
int DumpRestore();
|
int DumpRestore();
|
||||||
inquiry_info_t GetDeviceInfo();
|
bool GetDeviceInfo(inquiry_info_t&);
|
||||||
void WaitPhase(phase_t) const;
|
void WaitForPhase(phase_t) const;
|
||||||
void Selection() const;
|
void Selection() const;
|
||||||
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
|
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
|
||||||
void DataIn(int);
|
void DataIn(int);
|
||||||
@ -65,7 +72,7 @@ class ScsiDump
|
|||||||
void WaitForBusy() const;
|
void WaitForBusy() const;
|
||||||
|
|
||||||
static void CleanUp();
|
static void CleanUp();
|
||||||
static void KillHandler(int);
|
static void TerminationHandler(int);
|
||||||
|
|
||||||
// A static instance is needed because of the signal handler
|
// A static instance is needed because of the signal handler
|
||||||
static inline unique_ptr<BUS> bus;
|
static inline unique_ptr<BUS> bus;
|
||||||
@ -80,9 +87,41 @@ class ScsiDump
|
|||||||
|
|
||||||
string filename;
|
string filename;
|
||||||
|
|
||||||
|
bool inquiry = false;
|
||||||
|
|
||||||
|
bool scan_bus = false;
|
||||||
|
|
||||||
bool restore = false;
|
bool restore = false;
|
||||||
|
|
||||||
bool properties_file = false;
|
bool properties_file = false;
|
||||||
|
|
||||||
static inline const string divider_str = "----------------------------------------";
|
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
|
||||||
|
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
|
||||||
|
|
||||||
|
static inline const string DIVIDER = "----------------------------------------";
|
||||||
|
|
||||||
|
static inline const unordered_map<byte, string> DEVICE_TYPES = {
|
||||||
|
{ byte{0}, "Direct Access" },
|
||||||
|
{ byte{1}, "Sequential Access" },
|
||||||
|
{ byte{2}, "Printer" },
|
||||||
|
{ byte{3}, "Processor" },
|
||||||
|
{ byte{4}, "Write-Once" },
|
||||||
|
{ byte{5}, "CD-ROM/DVD/BD/DVD-RAM" },
|
||||||
|
{ byte{6}, "Scanner" },
|
||||||
|
{ byte{7}, "Optical Memory" },
|
||||||
|
{ byte{8}, "Media Changer" },
|
||||||
|
{ byte{9}, "Communications" },
|
||||||
|
{ byte{10}, "Graphic Arts Pre-Press" },
|
||||||
|
{ byte{11}, "Graphic Arts Pre-Press" },
|
||||||
|
{ byte{12}, "Storage Array Controller" },
|
||||||
|
{ byte{13}, "Enclosure Services" },
|
||||||
|
{ byte{14}, "Simplified Direct Access" },
|
||||||
|
{ byte{15}, "Optical Card Reader/Writer" },
|
||||||
|
{ byte{16}, "Bridge Controller" },
|
||||||
|
{ byte{17}, "Object-based Storage" },
|
||||||
|
{ byte{18}, "Automation/Drive Interface" },
|
||||||
|
{ byte{19}, "Security Manager" },
|
||||||
|
{ byte{20}, "Host Managed Zoned Block" },
|
||||||
|
{ byte{30}, "Well Known Logical Unit" }
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges)
|
|||||||
EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address());
|
EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address());
|
||||||
DeleteTempFile("/proc/device-tree/soc/ranges");
|
DeleteTempFile("/proc/device-tree/soc/ranges");
|
||||||
|
|
||||||
CleanUpAllTempFiles();
|
remove_all(test_data_temp_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(GpiobusRaspberry, GetDat)
|
TEST(GpiobusRaspberry, GetDat)
|
||||||
|
@ -1,72 +1,66 @@
|
|||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
//
|
//
|
||||||
// SCSI Target Emulator PiSCSI
|
// SCSI Target Emulator PiSCSI
|
||||||
// for Raspberry Pi
|
// for Raspberry Pi
|
||||||
//
|
//
|
||||||
// Copyright (C) 2022 akuker
|
// Copyright (C) 2022 akuker
|
||||||
|
// Copyright (C) 2023 Uwe Seimet
|
||||||
//
|
//
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
#include "mocks.h"
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include "scsidump/scsidump_core.h"
|
#include "scsidump/scsidump_core.h"
|
||||||
#include "test/test_shared.h"
|
#include "test/test_shared.h"
|
||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace filesystem;
|
using namespace filesystem;
|
||||||
|
|
||||||
class TestableScsidump : public ScsiDump
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static void PublicGeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
|
|
||||||
{
|
|
||||||
ScsiDump::GeneratePropertiesFile(filename, inq_info);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST(ScsiDumpTest, GeneratePropertiesFile)
|
TEST(ScsiDumpTest, GeneratePropertiesFile)
|
||||||
{
|
{
|
||||||
// Basic test
|
// Basic test
|
||||||
const string prop_file_name = "test.properties";
|
auto filename = CreateTempFile(0);
|
||||||
ScsiDump::inquiry_info_t test_data = {
|
ScsiDump::inquiry_info_t test_data = {
|
||||||
.vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100};
|
.vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100};
|
||||||
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
|
test_data.GeneratePropertiesFile(filename);
|
||||||
|
|
||||||
string expected_str = "{\n"
|
string expected_str = "{\n"
|
||||||
" \"vendor\": \"PISCSI\",\n"
|
" \"vendor\": \"PISCSI\",\n"
|
||||||
" \"product\": \"TEST PRODUCT\",\n"
|
" \"product\": \"TEST PRODUCT\",\n"
|
||||||
" \"revision\": \"REV1\",\n"
|
" \"revision\": \"REV1\",\n"
|
||||||
" \"block_size\": \"1000\",\n}"
|
" \"block_size\": \"1000\"\n}"
|
||||||
"\n";
|
"\n";
|
||||||
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
|
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
|
||||||
|
|
||||||
// Long string test
|
// Long string test
|
||||||
|
filename = CreateTempFile(0);
|
||||||
test_data = {.vendor = "01234567",
|
test_data = {.vendor = "01234567",
|
||||||
.product = "0123456789ABCDEF",
|
.product = "0123456789ABCDEF",
|
||||||
.revision = "0123",
|
.revision = "0123",
|
||||||
.sector_size = UINT32_MAX,
|
.sector_size = UINT32_MAX,
|
||||||
.capacity = UINT64_MAX};
|
.capacity = UINT64_MAX};
|
||||||
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
|
test_data.GeneratePropertiesFile(filename);
|
||||||
|
|
||||||
expected_str = "{\n"
|
expected_str = "{\n"
|
||||||
" \"vendor\": \"01234567\",\n"
|
" \"vendor\": \"01234567\",\n"
|
||||||
" \"product\": \"0123456789ABCDEF\",\n"
|
" \"product\": \"0123456789ABCDEF\",\n"
|
||||||
" \"revision\": \"0123\",\n"
|
" \"revision\": \"0123\",\n"
|
||||||
" \"block_size\": \"4294967295\",\n"
|
" \"block_size\": \"4294967295\"\n"
|
||||||
"}\n";
|
"}\n";
|
||||||
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
|
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
|
||||||
|
remove(filename);
|
||||||
|
|
||||||
// Empty data test
|
// Empty data test
|
||||||
|
filename = CreateTempFile(0);
|
||||||
test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0};
|
test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0};
|
||||||
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
|
test_data.GeneratePropertiesFile(filename);
|
||||||
|
|
||||||
expected_str = "{\n"
|
expected_str = "{\n"
|
||||||
" \"vendor\": \"\",\n"
|
" \"vendor\": \"\",\n"
|
||||||
" \"product\": \"\",\n"
|
" \"product\": \"\",\n"
|
||||||
" \"revision\": \"\",\n"
|
" \"revision\": \"\",\n"
|
||||||
" \"block_size\": \"0\",\n"
|
" \"block_size\": \"0\"\n"
|
||||||
"}\n";
|
"}\n";
|
||||||
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
|
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
|
||||||
|
remove(filename);
|
||||||
}
|
}
|
||||||
|
@ -121,6 +121,7 @@ void CreateTempFileWithData(const string& filename, vector<uint8_t>& data)
|
|||||||
fclose(fp);
|
fclose(fp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Move this code, it is not shared
|
||||||
void DeleteTempFile(const string& filename)
|
void DeleteTempFile(const string& filename)
|
||||||
{
|
{
|
||||||
path temp_file = test_data_temp_path;
|
path temp_file = test_data_temp_path;
|
||||||
@ -128,17 +129,12 @@ void DeleteTempFile(const string& filename)
|
|||||||
remove(temp_file);
|
remove(temp_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CleanUpAllTempFiles()
|
|
||||||
{
|
|
||||||
remove_all(test_data_temp_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
string ReadTempFileToString(const string& filename)
|
string ReadTempFileToString(const string& filename)
|
||||||
{
|
{
|
||||||
const path temp_file = test_data_temp_path / path(filename);
|
const path temp_file = test_data_temp_path / path(filename);
|
||||||
ifstream in_fs(temp_file);
|
ifstream in(temp_file);
|
||||||
stringstream buffer;
|
stringstream buffer;
|
||||||
buffer << in_fs.rdbuf();
|
buffer << in.rdbuf();
|
||||||
|
|
||||||
return buffer.str();
|
return buffer.str();
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,6 @@ path CreateTempFileWithData(span<const byte>);
|
|||||||
void CreateTempFileWithData(const string&, vector<uint8_t>&);
|
void CreateTempFileWithData(const string&, vector<uint8_t>&);
|
||||||
|
|
||||||
void DeleteTempFile(const string&);
|
void DeleteTempFile(const string&);
|
||||||
// Call this at the end of every test case to make sure things are cleaned up
|
|
||||||
void CleanUpAllTempFiles();
|
|
||||||
|
|
||||||
string ReadTempFileToString(const string& filename);
|
string ReadTempFileToString(const string& filename);
|
||||||
|
|
||||||
|
@ -10,6 +10,8 @@ scsidump \- SCSI disk dumping tool for PiSCSI
|
|||||||
[\fB\-r\fR]
|
[\fB\-r\fR]
|
||||||
[\fB\-v\fR]
|
[\fB\-v\fR]
|
||||||
[\fB\-p\fR]
|
[\fB\-p\fR]
|
||||||
|
[\fB\-I\fR] ID[:LUN]
|
||||||
|
[\fB\-S\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
.B scsidump
|
.B scsidump
|
||||||
has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives.
|
has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives.
|
||||||
@ -47,6 +49,12 @@ Enable verbose logging.
|
|||||||
.TP
|
.TP
|
||||||
.BR \-p\fI
|
.BR \-p\fI
|
||||||
Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi.
|
Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi.
|
||||||
|
.TP
|
||||||
|
.BR \-I\fI " "\fIID[:LUN]
|
||||||
|
Display INQUIRY data of ID[:LUN].
|
||||||
|
.TP
|
||||||
|
.BR \-S\fI
|
||||||
|
Scan SCSI bus for devices.
|
||||||
|
|
||||||
.SH EXAMPLES
|
.SH EXAMPLES
|
||||||
Dump Mode: [SCSI Drive] ---> [PiSCSI host]
|
Dump Mode: [SCSI Drive] ---> [PiSCSI host]
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
|
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
|
||||||
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating
|
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating\n\n
|
||||||
|
|
||||||
|
|
||||||
scsidump(1) General Commands Manual scsidump(1)
|
scsidump(1) General Commands Manual scsidump(1)
|
||||||
|
|
||||||
NAME
|
NAME
|
||||||
@ -9,43 +7,72 @@ NAME
|
|||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p]
|
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p]
|
||||||
|
[-I] ID[:LUN] [-S]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives.
|
scsidump has two modes of operation: dump and restore. These can be
|
||||||
|
used with physical storage media, including hard drives and magneto op‐
|
||||||
|
tical drives. Dump mode can be used with read-only media such as CD/DVD
|
||||||
|
drives.
|
||||||
|
|
||||||
When operating in dump mode, scsidump will copy all data from a remote SCSI drive to an image on the local filesystem. If enabled, it will also generate a .properties file that can be used to more closely emulate the source drive.
|
When operating in dump mode, scsidump will copy all data from a remote
|
||||||
|
SCSI drive to an image on the local filesystem. If enabled, it will
|
||||||
|
also generate a .properties file that can be used to more closely emu‐
|
||||||
|
late the source drive.
|
||||||
|
|
||||||
If you are operating in restore mode, scsidump will copy the data from a local binary image to a remote physical SCSI drive. The remote SCSI drive MUST be writable.
|
If you are operating in restore mode, scsidump will copy the data from
|
||||||
|
a local binary image to a remote physical SCSI drive. The remote SCSI
|
||||||
|
drive MUST be writable.
|
||||||
|
|
||||||
NOTES
|
NOTES
|
||||||
scsidump requires either a direct connection (one without transceivers) or a FULLSPEC PiSCSI/RaSCSI board.
|
scsidump requires either a direct connection (one without transceivers)
|
||||||
|
or a FULLSPEC PiSCSI/RaSCSI board.
|
||||||
|
|
||||||
If the generated drive image is intended to be used with PiSCSI, the drive image should be moved to ~/images (or the location specified to the piscsi service).
|
If the generated drive image is intended to be used with PiSCSI, the
|
||||||
|
drive image should be moved by the user to ~/images (or the location
|
||||||
|
specified to the piscsi service).
|
||||||
|
|
||||||
OPTIONS
|
OPTIONS
|
||||||
-t ID[:LUN]
|
-t ID[: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 remote SCSI device. The remote
|
||||||
|
SCSI device will be functioning as the "Target" device.
|
||||||
|
|
||||||
-i BID 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.
|
-i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI de‐
|
||||||
|
vice will use ID 7. The PiSCSI host will be functioning as the
|
||||||
|
"Initiator" device.
|
||||||
|
|
||||||
-f FILE
|
-f FILE
|
||||||
Path to the dump file.
|
Path to the dump file.
|
||||||
|
|
||||||
-s BUFFER_SIZE
|
-s BUFFER_SIZE
|
||||||
The transfer buffer size, specified in bytes. Default is 1 MiB. This is specified in bytes with a minimum value of 65536 (64 KiB).
|
The transfer buffer size, specified in bytes. Default is 1 MiB.
|
||||||
|
This is specified in bytes with a minimum value of 65536 (64
|
||||||
|
KiB).
|
||||||
|
|
||||||
-r Run in restore mode. Defaults to dump mode if not specified.
|
-r Run in restore mode. Defaults to dump mode if not specified.
|
||||||
|
|
||||||
-v Enable verbose logging.
|
-v Enable verbose logging.
|
||||||
|
|
||||||
-p Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi.
|
-p Generate a .properties file that is compatible with the PiSCSI
|
||||||
|
web interface. The output filename will match the image filename
|
||||||
|
with ".properties" appended. The generated file should be moved
|
||||||
|
to ~/.config/piscsi.
|
||||||
|
|
||||||
|
-I ID[:LUN]
|
||||||
|
Display INQUIRY data of ID[:LUN].
|
||||||
|
|
||||||
|
-S Scan SCSI bus for devices.
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an all data from SCSI ID 3 with block size 64 KiB, store it to the local filesystem as a drive image named outimage.hda, and generate the outimage.hda.properties file with the drive's INQUIRY
|
Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an
|
||||||
information:
|
all data from SCSI ID 3 with block size 64 KiB, store it to the local
|
||||||
|
filesystem as a drive image named outimage.hda, and generate the outim‐
|
||||||
|
age.hda.properties file with the drive's INQUIRY information:
|
||||||
scsidump -t 3 -f ./outimage.hda -s 65536 -p
|
scsidump -t 3 -f ./outimage.hda -s 65536 -p
|
||||||
|
|
||||||
Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to restore/upload a drive image from the local file system to SCSI ID 0 with block size 1MiB:
|
Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to re‐
|
||||||
|
store/upload a drive image from the local file system to SCSI ID 0 with
|
||||||
|
block size 1MiB:
|
||||||
scsidump -r -t 0 -f ./outimage.hda -s 1048576
|
scsidump -r -t 0 -f ./outimage.hda -s 1048576
|
||||||
|
|
||||||
SEE ALSO
|
SEE ALSO
|
||||||
|
Loading…
Reference in New Issue
Block a user