mirror of
https://github.com/akuker/RASCSI.git
synced 2025-03-06 00:29:26 +00:00
SCSI Dump Usability Enhancements & Cleanup (#1026)
Co-authored-by: Tony Kuker <akuker@gmail.com>
This commit is contained in:
parent
35447cc1c6
commit
5b3626dcf5
@ -8,4 +8,5 @@ AlignEscapedNewlines: Left
|
||||
AlignTrailingComments: True
|
||||
AllowShortEnumsOnASingleLine: True
|
||||
AlignConsecutiveAssignments: Consecutive
|
||||
ColumnLimit: 120
|
||||
ColumnLimit: 120
|
||||
PointerAlignment: Left
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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 = "----------------------------------------";
|
||||
};
|
||||
|
@ -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) {
|
||||
|
72
cpp/test/scsidump_test.cpp
Normal file
72
cpp/test/scsidump_test.cpp
Normal 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);
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user