SCSI Dump Usability Enhancements & Cleanup (#1026)

Co-authored-by: Tony Kuker <akuker@gmail.com>
This commit is contained in:
akuker 2022-12-09 09:50:45 -06:00 committed by GitHub
parent 35447cc1c6
commit 5b3626dcf5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 655 additions and 443 deletions

View File

@ -8,4 +8,5 @@ AlignEscapedNewlines: Left
AlignTrailingComments: True
AllowShortEnumsOnASingleLine: True
AlignConsecutiveAssignments: Consecutive
ColumnLimit: 120
ColumnLimit: 120
PointerAlignment: Left

View File

@ -21,24 +21,31 @@ using namespace std;
unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
{
// TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private
// so that clients cannot use it anymore but have to use the factory.
// Also make Init() private.
unique_ptr<BUS> return_ptr;
SBC_Version::Init();
if (SBC_Version::IsBananaPi()) {
LOGTRACE("Creating GPIOBUS_BananaM2p")
return_ptr = make_unique<GPIOBUS_BananaM2p>();
} else if (SBC_Version::IsRaspberryPi()) {
LOGTRACE("Creating GPIOBUS_Raspberry")
return_ptr = make_unique<GPIOBUS_Raspberry>();
} else {
LOGINFO("Creating Virtual GPIOBUS")
return_ptr = make_unique<GPIOBUS_Virtual>();
try {
// TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private
// so that clients cannot use it anymore but have to use the factory.
// Also make Init() private.
SBC_Version::Init();
if (SBC_Version::IsBananaPi()) {
LOGTRACE("Creating GPIOBUS_BananaM2p")
return_ptr = make_unique<GPIOBUS_BananaM2p>();
} else if (SBC_Version::IsRaspberryPi()) {
LOGTRACE("Creating GPIOBUS_Raspberry")
return_ptr = make_unique<GPIOBUS_Raspberry>();
} else {
LOGINFO("Creating Virtual GPIOBUS")
return_ptr = make_unique<GPIOBUS_Virtual>();
}
if (!return_ptr->Init(mode)) {
return nullptr;
}
return_ptr->Reset();
} catch (const invalid_argument&) {
LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?")
return_ptr = nullptr;
}
if (!return_ptr->Init(mode)) {
return nullptr;
}
return_ptr->Reset();
return return_ptr;
}

View File

@ -15,5 +15,5 @@ int main(int argc, char *argv[])
{
const vector<char *> args(argv, argv + argc);
return RasDump().run(args);
return ScsiDump().run(args);
}

View File

@ -6,535 +6,597 @@
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022 akuker
//
//---------------------------------------------------------------------------
// TODO Evaluate CHECK CONDITION after sending a command
// TODO Send IDENTIFY message in order to support LUNS > 7
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h"
#include "hal/gpiobus_factory.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "scsidump/scsidump_core.h"
#include <sys/stat.h>
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_version.h"
#include <chrono>
#include <csignal>
#include <cstddef>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <fstream>
#include <iostream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
using namespace spdlog;
using namespace scsi_defs;
using namespace piscsi_util;
void RasDump::CleanUp()
void ScsiDump::CleanUp()
{
if (bus != nullptr) {
bus->Cleanup();
}
if (bus != nullptr) {
bus->Cleanup();
}
}
void RasDump::KillHandler(int)
void ScsiDump::KillHandler(int)
{
CleanUp();
CleanUp();
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}
bool RasDump::Banner(const vector<char *>& args) const
bool ScsiDump::Banner(const vector<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") {
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE]\n"
<< " ID is the target device ID (0-7).\n"
<< " LUN is the optional target device LUN (0-7). Default is 0.\n"
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\n"
<< " BUFFER_SIZE is the transfer buffer size, at least "
<< to_string(MINIMUM_BUFFER_SIZE / 1024) << " KiB. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n" << flush;
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"
<< " ID is the target device ID (0-7).\n"
<< " LUN is the optional target device LUN (0-7). Default is 0.\n"
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\n"
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE)
<< " bytes. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\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"
<< flush;
return false;
}
return false;
}
return true;
return true;
}
bool RasDump::Init() const
bool ScsiDump::Init() const
{
// Interrupt handler setting
if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR ||
signal(SIGTERM, KillHandler) == SIG_ERR) {
return false;
}
// Interrupt handler setting
if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR ||
signal(SIGTERM, KillHandler) == SIG_ERR) {
return false;
}
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
return bus != nullptr;
return bus != nullptr;
}
void RasDump::ParseArguments(const vector<char *>& args)
void ScsiDump::ParseArguments(const vector<char*>& args)
{
int opt;
int opt;
int buffer_size = DEFAULT_BUFFER_SIZE;
int buffer_size = DEFAULT_BUFFER_SIZE;
opterr = 0;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rv")) != -1) {
switch (opt) {
case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
throw parser_exception("Invalid PiSCSI board ID " + to_string(initiator_id) + " (0-7)");
}
break;
opterr = 0;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvp")) != -1) {
switch (opt) {
case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
throw parser_exception("Invalid PiSCSI board ID " + to_string(initiator_id) + " (0-7)");
}
break;
case 'f':
filename = optarg;
break;
case 'f':
filename = optarg;
break;
case 's':
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");
}
case 's':
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");
}
break;
break;
case 't': {
const string error = ProcessId(optarg, 8, target_id, target_lun);
if (!error.empty()) {
throw parser_exception(error);
}
}
break;
case 't': {
const string error = ProcessId(optarg, 8, target_id, target_lun);
if (!error.empty()) {
throw parser_exception(error);
}
} break;
case 'v':
set_level(level::debug);
break;
case 'v':
set_level(level::debug);
break;
case 'r':
restore = true;
break;
case 'r':
restore = true;
break;
default:
break;
}
}
case 'p':
properties_file = true;
break;
if (target_id == initiator_id) {
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
}
default:
break;
}
}
if (filename.empty()) {
throw parser_exception("Missing filename");
}
if (target_id == initiator_id) {
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
}
buffer = vector<uint8_t>(buffer_size);
if (filename.empty()) {
throw parser_exception("Missing filename");
}
buffer = vector<uint8_t>(buffer_size);
}
void RasDump::WaitPhase(phase_t phase) const
void ScsiDump::WaitPhase(phase_t phase) const
{
LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase))
LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase))
// Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
bus->Acquire();
if (bus->GetREQ() && bus->GetPhase() == phase) {
return;
}
}
// Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
bus->Acquire();
if (bus->GetREQ() && bus->GetPhase() == phase) {
return;
}
}
throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is "
+ string(BUS::GetPhaseStrRaw(bus->GetPhase())));
throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
string(BUS::GetPhaseStrRaw(bus->GetPhase())));
}
void RasDump::Selection() const
void ScsiDump::Selection() const
{
// Set initiator and target ID
auto data = static_cast<byte>(1 << initiator_id);
data |= static_cast<byte>(1 << target_id);
bus->SetDAT(static_cast<uint8_t>(data));
// Set initiator and target ID
auto data = static_cast<byte>(1 << initiator_id);
data |= static_cast<byte>(1 << target_id);
bus->SetDAT(static_cast<uint8_t>(data));
bus->SetSEL(true);
bus->SetSEL(true);
WaitForBusy();
WaitForBusy();
bus->SetSEL(false);
bus->SetSEL(false);
}
void RasDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
{
LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second)
LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second)
Selection();
Selection();
WaitPhase(phase_t::command);
WaitPhase(phase_t::command);
cdb[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
if (static_cast<int>(cdb.size()) != bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
BusFree();
cdb[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
if (static_cast<int>(cdb.size()) !=
bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
BusFree();
throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed"));
}
throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed"));
}
}
void RasDump::DataIn(int length)
void ScsiDump::DataIn(int length)
{
WaitPhase(phase_t::datain);
WaitPhase(phase_t::datain);
if (!bus->ReceiveHandShake(buffer.data(), length)) {
throw parser_exception("DATA IN failed");
}
if (!bus->ReceiveHandShake(buffer.data(), length)) {
throw parser_exception("DATA IN failed");
}
}
void RasDump::DataOut(int length)
void ScsiDump::DataOut(int length)
{
WaitPhase(phase_t::dataout);
WaitPhase(phase_t::dataout);
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
throw parser_exception("DATA OUT failed");
}
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
throw parser_exception("DATA OUT failed");
}
}
void RasDump::Status() const
void ScsiDump::Status() const
{
WaitPhase(phase_t::status);
WaitPhase(phase_t::status);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("STATUS failed");
}
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("STATUS failed");
}
}
void RasDump::MessageIn() const
void ScsiDump::MessageIn() const
{
WaitPhase(phase_t::msgin);
WaitPhase(phase_t::msgin);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("MESSAGE IN failed");
}
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("MESSAGE IN failed");
}
}
void RasDump::BusFree() const
void ScsiDump::BusFree() const
{
bus->Reset();
bus->Reset();
}
void RasDump::TestUnitReady() const
void ScsiDump::TestUnitReady() const
{
vector<uint8_t> cdb(6);
Command(scsi_command::eCmdTestUnitReady, cdb);
vector<uint8_t> cdb(6);
Command(scsi_command::eCmdTestUnitReady, cdb);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
}
void RasDump::RequestSense()
void ScsiDump::RequestSense()
{
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdRequestSense, cdb);
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdRequestSense, cdb);
DataIn(256);
DataIn(256);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
}
void RasDump::Inquiry()
void ScsiDump::Inquiry()
{
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdInquiry, cdb);
vector<uint8_t> cdb(6);
cdb[4] = 0xff;
Command(scsi_command::eCmdInquiry, cdb);
DataIn(256);
DataIn(256);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
}
pair<uint64_t, uint32_t> RasDump::ReadCapacity()
pair<uint64_t, uint32_t> ScsiDump::ReadCapacity()
{
vector<uint8_t> cdb(10);
Command(scsi_command::eCmdReadCapacity10, cdb);
vector<uint8_t> cdb(10);
Command(scsi_command::eCmdReadCapacity10, cdb);
DataIn(8);
DataIn(8);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
uint64_t capacity = (static_cast<uint32_t>(buffer[0]) << 24) | (static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) | static_cast<uint32_t>(buffer[3]);
uint64_t capacity = (static_cast<uint32_t>(buffer[0]) << 24) | (static_cast<uint32_t>(buffer[1]) << 16) |
(static_cast<uint32_t>(buffer[2]) << 8) | static_cast<uint32_t>(buffer[3]);
int sector_size_offset = 4;
int sector_size_offset = 4;
if (static_cast<int32_t>(capacity) == -1) {
cdb.resize(16);
// READ CAPACITY(16), not READ LONG(16)
cdb[1] = 0x10;
Command(scsi_command::eCmdReadCapacity16_ReadLong16, cdb);
if (static_cast<int32_t>(capacity) == -1) {
cdb.resize(16);
// READ CAPACITY(16), not READ LONG(16)
cdb[1] = 0x10;
Command(scsi_command::eCmdReadCapacity16_ReadLong16, cdb);
DataIn(14);
DataIn(14);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
capacity = (static_cast<uint64_t>(buffer[0]) << 56) | (static_cast<uint64_t>(buffer[1]) << 48) |
(static_cast<uint64_t>(buffer[2]) << 40) | (static_cast<uint64_t>(buffer[3]) << 32) |
(static_cast<uint64_t>(buffer[4]) << 24) | (static_cast<uint64_t>(buffer[5]) << 16) |
(static_cast<uint64_t>(buffer[6]) << 8) | static_cast<uint64_t>(buffer[7]);
capacity = (static_cast<uint64_t>(buffer[0]) << 56) | (static_cast<uint64_t>(buffer[1]) << 48) |
(static_cast<uint64_t>(buffer[2]) << 40) | (static_cast<uint64_t>(buffer[3]) << 32) |
(static_cast<uint64_t>(buffer[4]) << 24) | (static_cast<uint64_t>(buffer[5]) << 16) |
(static_cast<uint64_t>(buffer[6]) << 8) | static_cast<uint64_t>(buffer[7]);
sector_size_offset = 8;
}
sector_size_offset = 8;
}
const uint32_t sector_size = (static_cast<uint32_t>(buffer[sector_size_offset]) << 24) |
(static_cast<uint32_t>(buffer[sector_size_offset + 1]) << 16) |
(static_cast<uint32_t>(buffer[sector_size_offset +2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]);
const uint32_t sector_size = (static_cast<uint32_t>(buffer[sector_size_offset]) << 24) |
(static_cast<uint32_t>(buffer[sector_size_offset + 1]) << 16) |
(static_cast<uint32_t>(buffer[sector_size_offset + 2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]);
return make_pair(capacity, sector_size);
return make_pair(capacity, sector_size);
}
void RasDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length)
void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length)
{
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdRead10, cdb);
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdRead10, cdb);
DataIn(length);
DataIn(length);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
}
void RasDump::Write10(uint32_t bstart, uint32_t blength, uint32_t length)
void ScsiDump::Write10(uint32_t bstart, uint32_t blength, uint32_t length)
{
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdWrite10, cdb);
vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdWrite10, cdb);
DataOut(length);
DataOut(length);
Status();
Status();
MessageIn();
MessageIn();
BusFree();
BusFree();
}
void RasDump::WaitForBusy() const
void ScsiDump::WaitForBusy() const
{
// Wait for busy for up to 2 s
int count = 10000;
do {
// Wait 20 ms
const timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000};
nanosleep(&ts, nullptr);
bus->Acquire();
if (bus->GetBSY()) {
break;
}
} while (count--);
// Wait for busy for up to 2 s
int count = 10000;
do {
// Wait 20 ms
const timespec ts = {.tv_sec = 0, .tv_nsec = 20 * 1000};
nanosleep(&ts, nullptr);
bus->Acquire();
if (bus->GetBSY()) {
break;
}
} while (count--);
// Success if the target is busy
if(!bus->GetBSY()) {
throw parser_exception("SELECTION failed");
}
// Success if the target is busy
if (!bus->GetBSY()) {
throw parser_exception("SELECTION failed");
}
}
int RasDump::run(const vector<char *>& args)
int ScsiDump::run(const vector<char*>& args)
{
if (!Banner(args)) {
return EXIT_SUCCESS;
}
if (!Banner(args)) {
return EXIT_SUCCESS;
}
if (!Init()) {
cerr << "Error: Initializing. Are you root?" << endl;
if (!Init()) {
cerr << "Error: Initializing. Are you root?" << endl;
// Probably not root
return EPERM;
}
// Probably not root
return EPERM;
}
try {
ParseArguments(args);
try {
ParseArguments(args);
#ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE;
cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE;
#endif
return DumpRestore();
}
catch(const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
return DumpRestore();
} catch (const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
CleanUp();
CleanUp();
return EXIT_FAILURE;
}
return EXIT_FAILURE;
}
CleanUp();
CleanUp();
return EXIT_SUCCESS;
return EXIT_SUCCESS;
}
int RasDump::DumpRestore()
int ScsiDump::DumpRestore()
{
const auto [capacity, sector_size] = GetDeviceInfo();
const auto inq_info = GetDeviceInfo();
fstream fs;
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
fstream fs;
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
if (fs.fail()) {
throw parser_exception("Can't open image file '" + filename + "'");
}
if (fs.fail()) {
throw parser_exception("Can't open image file '" + filename + "'");
}
if (restore) {
cout << "Starting restore\n" << flush;
if (restore) {
cout << "Starting restore\n" << flush;
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
off_t size;
if (struct stat st; !stat(filename.c_str(), &st)) {
size = st.st_size;
}
else {
throw parser_exception("Can't determine file size");
}
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
off_t size;
if (struct stat st; !stat(filename.c_str(), &st)) {
size = st.st_size;
} else {
throw parser_exception("Can't determine file size");
}
cout << "Restore file size: " << size << " bytes\n";
if (size > (off_t)(sector_size * capacity)) {
cout << "WARNING: File size is larger than disk size\n" << flush;
} else if (size < (off_t)(sector_size * capacity)) {
throw parser_exception("File size is smaller than disk size");
}
}
else {
cout << "Starting dump\n" << flush;
}
cout << "Restore file size: " << size << " bytes\n";
if (size > (off_t)(inq_info.sector_size * inq_info.capacity)) {
cout << "WARNING: File size is larger than disk size\n" << flush;
} else if (size < (off_t)(inq_info.sector_size * inq_info.capacity)) {
throw parser_exception("File size is smaller than disk size");
}
} else {
cout << "Starting dump\n" << flush;
}
// Dump by buffer size
auto dsiz = static_cast<int>(buffer.size());
const int duni = dsiz / sector_size;
auto dnum = static_cast<int>((capacity * sector_size) / dsiz);
// Dump by buffer size
auto dsiz = static_cast<int>(buffer.size());
const int duni = dsiz / inq_info.sector_size;
auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
int i;
for (i = 0; i < dnum; i++) {
if (restore) {
fs.read((char *)buffer.data(), dsiz);
Write10(i * duni, duni, dsiz);
}
else {
Read10(i * duni, duni, dsiz);
fs.write((const char *)buffer.data(), dsiz);
}
auto start_time = chrono::high_resolution_clock::now();
if (fs.fail()) {
throw parser_exception("File I/O failed");
}
int i;
for (i = 0; i < dnum; i++) {
if (restore) {
fs.read((char*)buffer.data(), dsiz);
Write10(i * duni, duni, dsiz);
} else {
Read10(i * duni, duni, dsiz);
fs.write((const char*)buffer.data(), dsiz);
}
cout << ((i + 1) * 100 / dnum) << "%" << " (" << ( i + 1) * duni << "/" << capacity << ")\n" << flush;
}
if (fs.fail()) {
throw parser_exception("File I/O failed");
}
// Rounding on capacity
dnum = capacity % duni;
dsiz = dnum * sector_size;
if (dnum > 0) {
if (restore) {
fs.read((char *)buffer.data(), dsiz);
if (!fs.fail()) {
Write10(i * duni, dnum, dsiz);
}
}
else {
Read10(i * duni, dnum, dsiz);
fs.write((const char *)buffer.data(), dsiz);
}
cout << ((i + 1) * 100 / dnum) << "%"
<< " (" << (i + 1) * duni << "/" << inq_info.capacity << ")\n"
<< flush;
}
if (fs.fail()) {
throw parser_exception("File I/O failed");
}
// Rounding on capacity
dnum = inq_info.capacity % duni;
dsiz = dnum * inq_info.sector_size;
if (dnum > 0) {
if (restore) {
fs.read((char*)buffer.data(), dsiz);
if (!fs.fail()) {
Write10(i * duni, dnum, dsiz);
}
} else {
Read10(i * duni, dnum, dsiz);
fs.write((const char*)buffer.data(), dsiz);
}
cout << "100% (" << capacity << "/" << capacity << ")\n" << flush;
}
if (fs.fail()) {
throw parser_exception("File I/O failed");
}
return EXIT_SUCCESS;
cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush;
}
auto stop_time = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
cout << divider_str << "\n";
cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes ["
<< to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n";
cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n";
cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration)
<< " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024)
<< " KiB per second)\n";
cout << divider_str << "\n";
if (properties_file && !restore) {
GeneratePropertiesFile(filename, inq_info);
}
return EXIT_SUCCESS;
}
pair<uint64_t, uint32_t> RasDump::GetDeviceInfo()
ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
{
// Assert RST for 1 ms
bus->SetRST(true);
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus->SetRST(false);
// 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 << "Target device ID: " << target_id << ", LUN: " << target_lun << "\n";
cout << "PiSCSI board ID: " << initiator_id << "\n" << flush;
cout << divider_str << "\n";
cout << "PiSCSI board ID: " << initiator_id << "\n";
cout << divider_str << "\n" << flush;
cout << "Target device ID: " << target_id << ", LUN: " << target_lun << "\n";
Inquiry();
Inquiry();
// Display INQUIRY information
array<char, 17> str = {};
memcpy(str.data(), &buffer[8], 8);
cout << "Vendor: " << str.data() << "\n";
str.fill(0);
memcpy(str.data(), &buffer[16], 16);
cout << "Product: " << str.data() << "\n";
str.fill(0);
memcpy(str.data(), &buffer[32], 4);
cout << "Revision: " << str.data() << "\n" << flush;
inquiry_info_t inq_info;
if (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");
}
// Display INQUIRY information
array<char, 17> str = {};
memcpy(str.data(), &buffer[8], 8);
cout << "Vendor: " << str.data() << "\n";
inq_info.vendor = string(str.data());
TestUnitReady();
str.fill(0);
memcpy(str.data(), &buffer[16], 16);
cout << "Product: " << str.data() << "\n";
inq_info.product = string(str.data());
RequestSense();
str.fill(0);
memcpy(str.data(), &buffer[32], 4);
cout << "Revision: " << str.data() << "\n" << flush;
inq_info.revision = string(str.data());
const auto [capacity, sector_size] = ReadCapacity();
if (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");
}
cout << "Number of sectors: " << capacity << "\n"
<< "Sector size: " << sector_size << " bytes\n"
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB ("
<< sector_size * capacity << " bytes)\n\n" << flush;
TestUnitReady();
return make_pair(capacity, sector_size);
RequestSense();
const auto [capacity, sector_size] = ReadCapacity();
inq_info.capacity = capacity;
inq_info.sector_size = sector_size;
cout << "Sectors: " << capacity << "\n"
<< "Sector size: " << sector_size << " bytes\n"
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity
<< " bytes)\n"
<< divider_str << "\n\n"
<< flush;
return inq_info;
}
void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
{
string prop_filename = filename + ".properties";
string prop_str;
stringstream prop_stream(prop_str);
prop_stream << "{" << endl;
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl;
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl;
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl;
prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl;
prop_stream << "}" << endl;
FILE* fp = fopen(prop_filename.c_str(), "w");
if (fp) {
fputs(prop_stream.str().c_str(), fp);
} else {
LOGWARN("Unable to open output file %s", prop_filename.c_str())
return;
}
fclose(fp);
}

View File

@ -9,64 +9,79 @@
#pragma once
#include "shared/scsi.h"
#include "hal/bus.h"
#include "shared/scsi.h"
#include <memory>
#include <string>
#include <vector>
#include <memory>
using namespace std;
class RasDump
class ScsiDump
{
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
public:
public:
ScsiDump() = default;
~ScsiDump() = default;
RasDump() = default;
~RasDump() = default;
int run(const vector<char*>&);
int run(const vector<char *>&);
struct inquiry_info_struct {
string vendor;
string product;
string revision;
uint32_t sector_size;
uint64_t capacity;
};
using inquiry_info_t = struct inquiry_info_struct;
private:
protected:
// Protected for testability
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info);
bool Banner(const vector<char *>&) const;
bool Init() const;
void ParseArguments(const vector<char *>&);
int DumpRestore();
pair<uint64_t, uint32_t> GetDeviceInfo();
void WaitPhase(phase_t) const;
void Selection() const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
void DataIn(int);
void DataOut(int);
void Status() const;
void MessageIn() const;
void BusFree() const;
void TestUnitReady() const;
void RequestSense();
void Inquiry();
pair<uint64_t, uint32_t> ReadCapacity();
void Read10(uint32_t, uint32_t, uint32_t);
void Write10(uint32_t, uint32_t, uint32_t);
void WaitForBusy() const;
private:
bool Banner(const vector<char*>&) const;
bool Init() const;
void ParseArguments(const vector<char*>&);
int DumpRestore();
inquiry_info_t GetDeviceInfo();
void WaitPhase(phase_t) const;
void Selection() const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
void DataIn(int);
void DataOut(int);
void Status() const;
void MessageIn() const;
void BusFree() const;
void TestUnitReady() const;
void RequestSense();
void Inquiry();
pair<uint64_t, uint32_t> ReadCapacity();
void Read10(uint32_t, uint32_t, uint32_t);
void Write10(uint32_t, uint32_t, uint32_t);
void WaitForBusy() const;
static void CleanUp();
static void KillHandler(int);
static void CleanUp();
static void KillHandler(int);
// A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus;
// A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus;
vector<uint8_t> buffer;
vector<uint8_t> buffer;
int target_id = -1;
int target_id = -1;
int target_lun = 0;
int target_lun = 0;
int initiator_id = 7;
int initiator_id = 7;
string filename;
string filename;
bool restore = false;
bool restore = false;
bool properties_file = false;
static inline const string divider_str = "----------------------------------------";
};

View File

@ -34,16 +34,21 @@ FILE *__wrap_fopen(const char *__restrict __filename, const char *__restrict __m
#endif
{
path new_filename;
auto in_filename = path(__filename);
bool create_directory = false;
// If we're trying to open up the device tree soc ranges,
// re-direct it to a temporary local file.
if (string(__filename) == "/proc/device-tree/soc/ranges") {
if ((string(__filename) == "/proc/device-tree/soc/ranges") ||
(string(__filename).find(".properties") != string::npos)) {
create_directory = true;
new_filename = test_data_temp_path;
new_filename += path(__filename);
if (!in_filename.has_parent_path()) {
new_filename += "/";
}
new_filename += in_filename;
} else {
new_filename = path(__filename);
new_filename = in_filename;
}
if (create_directory) {

View File

@ -0,0 +1,72 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include "scsidump/scsidump_core.h"
#include "test/test_shared.h"
#include <filesystem>
#include <fstream>
using namespace std;
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)
{
// Basic test
const string prop_file_name = "test.properties";
ScsiDump::inquiry_info_t test_data = {
.vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
string expected_str = "{\n"
" \"vendor\": \"PISCSI\",\n"
" \"product\": \"TEST PRODUCT\",\n"
" \"revision\": \"REV1\",\n"
" \"block_size\": \"1000\",\n}"
"\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
// Long string test
test_data = {.vendor = "01234567",
.product = "0123456789ABCDEF",
.revision = "0123",
.sector_size = UINT32_MAX,
.capacity = UINT64_MAX};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
expected_str = "{\n"
" \"vendor\": \"01234567\",\n"
" \"product\": \"0123456789ABCDEF\",\n"
" \"revision\": \"0123\",\n"
" \"block_size\": \"4294967295\",\n"
"}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
// Empty data test
test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
expected_str = "{\n"
" \"vendor\": \"\",\n"
" \"product\": \"\",\n"
" \"revision\": \"\",\n"
" \"block_size\": \"0\",\n"
"}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
}

View File

@ -13,6 +13,8 @@
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <unistd.h>
#include <vector>
@ -26,7 +28,7 @@ const path test_data_temp_path(temp_directory_path() /
path(fmt::format("piscsi-test-{}",
getpid()))); // NOSONAR Publicly writable directory is fine here
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType type, MockAbstractController &controller, const string &extension)
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension)
{
DeviceFactory device_factory;
@ -39,21 +41,21 @@ shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType type, MockAbstractController
return device;
}
void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string &ident, int additional_length,
bool removable, const string &extension)
void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length,
bool removable, const string& extension)
{
auto bus = make_shared<MockBus>();
auto controller_manager = make_shared<ControllerManager>(*bus);
auto controller = make_shared<NiceMock<MockAbstractController>>(controller_manager, 0);
auto device = CreateDevice(type, *controller, extension);
auto &cmd = controller->GetCmd();
auto& cmd = controller->GetCmd();
// ALLOCATION LENGTH
cmd[4] = 255;
EXPECT_CALL(*controller, DataIn());
device->Dispatch(scsi_command::eCmdInquiry);
const vector<uint8_t> &buffer = controller->GetBuffer();
const vector<uint8_t>& buffer = controller->GetBuffer();
EXPECT_EQ(t, static_cast<device_type>(buffer[0]));
EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]);
EXPECT_EQ(l, static_cast<scsi_level>(buffer[2]));
@ -99,21 +101,21 @@ path CreateTempFile(int size)
// TODO Replace old-fashinoned C I/O by C++ streams I/O.
// This also avoids potential issues with data type sizes and there is no need for c_str().
void CreateTempFileWithData(const string& filename, vector<uint8_t> &data)
void CreateTempFileWithData(const string& filename, vector<uint8_t>& data)
{
path new_filename = test_data_temp_path;
new_filename += path(filename);
create_directories(new_filename.parent_path());
FILE *fp = fopen(new_filename.c_str(), "wb");
FILE* fp = fopen(new_filename.c_str(), "wb");
if (fp == nullptr) {
printf("ERROR: Unable to open file %s\n", new_filename.c_str());
return;
}
if (const size_t size_written = fwrite(&data[0], sizeof(uint8_t), data.size(), fp);
size_written != sizeof(vector<uint8_t>::value_type) * data.size()) {
size_written != sizeof(vector<uint8_t>::value_type) * data.size()) {
printf("Expected to write %zu bytes, but only wrote %zu to %s", size_written,
sizeof(vector<uint8_t>::value_type) * data.size(), filename.c_str());
}
@ -132,14 +134,24 @@ void CleanupAllTempFiles()
remove_all(test_data_temp_path);
}
int GetInt16(const vector<byte> &buf, int offset)
string ReadTempFileToString(const string& filename)
{
path temp_file = test_data_temp_path / path(filename);
ifstream in_fs(temp_file);
stringstream buffer;
buffer << in_fs.rdbuf();
return buffer.str();
}
int GetInt16(const vector<byte>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (to_integer<int>(buf[offset]) << 8) | to_integer<int>(buf[offset + 1]);
}
uint32_t GetInt32(const vector<byte> &buf, int offset)
uint32_t GetInt32(const vector<byte>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);

View File

@ -9,11 +9,11 @@
#pragma once
#include "shared/scsi.h"
#include "generated/piscsi_interface.pb.h"
#include <string>
#include <memory>
#include "shared/scsi.h"
#include <filesystem>
#include <memory>
#include <string>
using namespace std;
using namespace filesystem;
@ -26,13 +26,12 @@ extern const path test_data_temp_path;
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, MockAbstractController&, const string& = "");
void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&,
int, bool, const string& = "");
void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool,
const string& = "");
pair<int, path> OpenTempFile();
path CreateTempFile(int);
// create a file with the specified data
void CreateTempFileWithData(const string&, vector<uint8_t>&);
@ -40,5 +39,7 @@ 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);
int GetInt16(const vector<byte>&, int);
uint32_t GetInt32(const vector<byte>&, int);

View File

@ -9,31 +9,53 @@ scsidump \- SCSI disk dumping tool for PiSCSI
[\fB\-s\fR \fIBUFFER_SIZE\fR]
[\fB\-r\fR]
[\fB\-v\fR]
[\fB\-p\fR]
.SH DESCRIPTION
.B scsidump
samples the data on physical SCSI storage media, including hard drives and MO drives, and stores it to an image file. It can also restore from a dumped file onto physical SCSI storage media. Can be connected directly, through a STANDARD PiSCSI board, or a FULLSPEC PiSCSI board.
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.
Set its own ID with the BID option. Defaults to 7 if ommitted.
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.
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.
.SH NOTES
.B 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 by the user to ~/images (or the location specified to the piscsi service).
.SH OPTIONS
.TP
.BR \-t\fI " "\fIID[:LUN]
SCSI ID and optional LUN of the target device.
SCSI ID and optional LUN of the remote SCSI device. The remote SCSI device will be functioning as the "Target" device.
.TP
.BR \-i\fI " "\fIBID
SCSI ID of the PiSCSI device.
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.
.TP
.BR \-f\fI " "\fIFILE
Path to the dump file.
.TP
.BR \-s\fI " "\fIBUFFER_SIZE
The transfer buffer size, at least 64 KiB. Default is 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).
.TP
.BR \-r\fI
Run in restore mode.
Run in restore mode. Defaults to dump mode if not specified.
.TP
.BR \-v\fI
Enable verbose logging.
.TP
.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.
.SH 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 information:
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:
scsidump -r -t 0 -f ./outimage.hda -s 1048576
.SH SEE ALSO
scsictl(1), piscsi(1), scsimon(1)

View File

@ -2,40 +2,55 @@
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating
scsidump(1) General Commands Manual scsidump(1)
scsidump(1) General Commands Manual scsidump(1)
NAME
scsidump - SCSI disk dumping tool for PiSCSI
SYNOPSIS
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v]
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p]
DESCRIPTION
scsidump samples the data on physical SCSI storage media, including hard drives and MO drives, and stores it to an image
file. It can also restore from a dumped file onto physical SCSI storage media. Can be connected directly, through a STAN
DARD PiSCSI board, or a FULLSPEC PiSCSI board.
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.
Set its own ID with the BID option. Defaults to 7 if ommitted.
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.
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
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).
OPTIONS
-t ID[:LUN]
SCSI ID and optional LUN of 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.
-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.
-f FILE
Path to the dump file.
-s BUFFER_SIZE
The transfer buffer size, at least 64 KiB. Default is 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.
-r Run in restore mode. Defaults to dump mode if not specified.
-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.
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
information:
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:
scsidump -r -t 0 -f ./outimage.hda -s 1048576
SEE ALSO
scsictl(1), piscsi(1), scsimon(1)
Full documentation is available at: <https://www.piscsi.com>
scsidump(1)
scsidump(1)