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 AlignTrailingComments: True
AllowShortEnumsOnASingleLine: True AllowShortEnumsOnASingleLine: True
AlignConsecutiveAssignments: Consecutive 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) 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; unique_ptr<BUS> return_ptr;
SBC_Version::Init();
if (SBC_Version::IsBananaPi()) { try {
LOGTRACE("Creating GPIOBUS_BananaM2p") // TODO Make the factory a friend of GPIOBUS and make the GPIOBUS constructor private
return_ptr = make_unique<GPIOBUS_BananaM2p>(); // so that clients cannot use it anymore but have to use the factory.
} else if (SBC_Version::IsRaspberryPi()) { // Also make Init() private.
LOGTRACE("Creating GPIOBUS_Raspberry") SBC_Version::Init();
return_ptr = make_unique<GPIOBUS_Raspberry>(); if (SBC_Version::IsBananaPi()) {
} else { LOGTRACE("Creating GPIOBUS_BananaM2p")
LOGINFO("Creating Virtual GPIOBUS") return_ptr = make_unique<GPIOBUS_BananaM2p>();
return_ptr = make_unique<GPIOBUS_Virtual>(); } 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; return return_ptr;
} }

View File

@ -15,5 +15,5 @@ int main(int argc, char *argv[])
{ {
const vector<char *> args(argv, argv + argc); 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. // Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS // Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022 akuker
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// TODO Evaluate CHECK CONDITION after sending a command // TODO Evaluate CHECK CONDITION after sending a command
// TODO Send IDENTIFY message in order to support LUNS > 7 // TODO Send IDENTIFY message in order to support LUNS > 7
#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 "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 <csignal>
#include <cstddef> #include <cstddef>
#include <unistd.h>
#include <cstring> #include <cstring>
#include <iostream>
#include <fstream> #include <fstream>
#include <iostream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
using namespace std; using namespace std;
using namespace spdlog; using namespace spdlog;
using namespace scsi_defs; using namespace scsi_defs;
using namespace piscsi_util; using namespace piscsi_util;
void RasDump::CleanUp() void ScsiDump::CleanUp()
{ {
if (bus != nullptr) { if (bus != nullptr) {
bus->Cleanup(); 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") { 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]\n" 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" << " ID is the target device ID (0-7).\n"
<< " LUN is the optional target device LUN (0-7). Default is 0.\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" << " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\n" << " FILE is the dump file path.\n"
<< " BUFFER_SIZE is the transfer buffer size, at least " << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE)
<< to_string(MINIMUM_BUFFER_SIZE / 1024) << " KiB. Default is 1 MiB.\n" << " bytes. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\n" << " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n" << flush; << " -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 // Interrupt handler setting
if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR || if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR ||
signal(SIGTERM, KillHandler) == SIG_ERR) { signal(SIGTERM, KillHandler) == SIG_ERR) {
return false; 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; opterr = 0;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rv")) != -1) { while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvp")) != -1) {
switch (opt) { switch (opt) {
case 'i': case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) { if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
throw parser_exception("Invalid PiSCSI board ID " + to_string(initiator_id) + " (0-7)"); throw parser_exception("Invalid PiSCSI board ID " + to_string(initiator_id) + " (0-7)");
} }
break; break;
case 'f': case 'f':
filename = optarg; filename = optarg;
break; break;
case 's': case 's':
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB"); throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB");
} }
break; break;
case 't': { case 't': {
const string error = ProcessId(optarg, 8, target_id, target_lun); const string error = ProcessId(optarg, 8, target_id, target_lun);
if (!error.empty()) { if (!error.empty()) {
throw parser_exception(error); throw parser_exception(error);
} }
} } break;
break;
case 'v': case 'v':
set_level(level::debug); set_level(level::debug);
break; break;
case 'r': case 'r':
restore = true; restore = true;
break; break;
default: case 'p':
break; properties_file = true;
} break;
}
if (target_id == initiator_id) { default:
throw parser_exception("Target ID and PiSCSI board ID must not be identical"); break;
} }
}
if (filename.empty()) { if (target_id == initiator_id) {
throw parser_exception("Missing filename"); 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) // Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow(); const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
bus->Acquire(); bus->Acquire();
if (bus->GetREQ() && bus->GetPhase() == phase) { if (bus->GetREQ() && bus->GetPhase() == phase) {
return; return;
} }
} }
throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
+ string(BUS::GetPhaseStrRaw(bus->GetPhase()))); string(BUS::GetPhaseStrRaw(bus->GetPhase())));
} }
void RasDump::Selection() const void ScsiDump::Selection() const
{ {
// Set initiator and target ID // Set initiator and target ID
auto data = static_cast<byte>(1 << initiator_id); auto data = static_cast<byte>(1 << initiator_id);
data |= static_cast<byte>(1 << target_id); data |= static_cast<byte>(1 << target_id);
bus->SetDAT(static_cast<uint8_t>(data)); 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[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5)); cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
if (static_cast<int>(cdb.size()) != bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) { if (static_cast<int>(cdb.size()) !=
BusFree(); 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)) { if (!bus->ReceiveHandShake(buffer.data(), length)) {
throw parser_exception("DATA IN failed"); 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)) { if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
throw parser_exception("DATA OUT failed"); 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) { if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("STATUS failed"); 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) { if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("MESSAGE IN failed"); 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); vector<uint8_t> cdb(6);
Command(scsi_command::eCmdTestUnitReady, cdb); Command(scsi_command::eCmdTestUnitReady, cdb);
Status(); Status();
MessageIn(); MessageIn();
BusFree(); BusFree();
} }
void RasDump::RequestSense() void ScsiDump::RequestSense()
{ {
vector<uint8_t> cdb(6); vector<uint8_t> cdb(6);
cdb[4] = 0xff; cdb[4] = 0xff;
Command(scsi_command::eCmdRequestSense, cdb); 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); vector<uint8_t> cdb(6);
cdb[4] = 0xff; cdb[4] = 0xff;
Command(scsi_command::eCmdInquiry, cdb); 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); vector<uint8_t> cdb(10);
Command(scsi_command::eCmdReadCapacity10, cdb); 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) | 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]); (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) { if (static_cast<int32_t>(capacity) == -1) {
cdb.resize(16); cdb.resize(16);
// READ CAPACITY(16), not READ LONG(16) // READ CAPACITY(16), not READ LONG(16)
cdb[1] = 0x10; cdb[1] = 0x10;
Command(scsi_command::eCmdReadCapacity16_ReadLong16, cdb); 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) | 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[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[4]) << 24) | (static_cast<uint64_t>(buffer[5]) << 16) |
(static_cast<uint64_t>(buffer[6]) << 8) | static_cast<uint64_t>(buffer[7]); (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) | 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 + 1]) << 16) |
(static_cast<uint32_t>(buffer[sector_size_offset +2]) << 8) | (static_cast<uint32_t>(buffer[sector_size_offset + 2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]); 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); vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24); cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16); cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8); cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart; cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8); cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength; cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdRead10, cdb); 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); vector<uint8_t> cdb(10);
cdb[2] = (uint8_t)(bstart >> 24); cdb[2] = (uint8_t)(bstart >> 24);
cdb[3] = (uint8_t)(bstart >> 16); cdb[3] = (uint8_t)(bstart >> 16);
cdb[4] = (uint8_t)(bstart >> 8); cdb[4] = (uint8_t)(bstart >> 8);
cdb[5] = (uint8_t)bstart; cdb[5] = (uint8_t)bstart;
cdb[7] = (uint8_t)(blength >> 8); cdb[7] = (uint8_t)(blength >> 8);
cdb[8] = (uint8_t)blength; cdb[8] = (uint8_t)blength;
Command(scsi_command::eCmdWrite10, cdb); 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 // Wait for busy for up to 2 s
int count = 10000; int count = 10000;
do { do {
// Wait 20 ms // Wait 20 ms
const timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000}; const timespec ts = {.tv_sec = 0, .tv_nsec = 20 * 1000};
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
bus->Acquire(); bus->Acquire();
if (bus->GetBSY()) { if (bus->GetBSY()) {
break; break;
} }
} while (count--); } while (count--);
// Success if the target is busy // Success if the target is busy
if(!bus->GetBSY()) { if (!bus->GetBSY()) {
throw parser_exception("SELECTION failed"); throw parser_exception("SELECTION failed");
} }
} }
int RasDump::run(const vector<char *>& args) int ScsiDump::run(const vector<char*>& args)
{ {
if (!Banner(args)) { if (!Banner(args)) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
if (!Init()) { if (!Init()) {
cerr << "Error: Initializing. Are you root?" << endl; cerr << "Error: Initializing. Are you root?" << endl;
// Probably not root // Probably not root
return EPERM; return EPERM;
} }
try { try {
ParseArguments(args); ParseArguments(args);
#ifndef USE_SEL_EVENT_ENABLE #ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl; cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
#endif #endif
return DumpRestore(); return DumpRestore();
} } catch (const parser_exception& e) {
catch(const parser_exception& e) { cerr << "Error: " << e.what() << endl;
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; fstream fs;
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary); fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
if (fs.fail()) { if (fs.fail()) {
throw parser_exception("Can't open image file '" + filename + "'"); throw parser_exception("Can't open image file '" + filename + "'");
} }
if (restore) { if (restore) {
cout << "Starting restore\n" << flush; cout << "Starting restore\n" << flush;
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
off_t size; off_t size;
if (struct stat st; !stat(filename.c_str(), &st)) { if (struct stat st; !stat(filename.c_str(), &st)) {
size = st.st_size; size = st.st_size;
} } else {
else { throw parser_exception("Can't determine file size");
throw parser_exception("Can't determine file size"); }
}
cout << "Restore file size: " << size << " bytes\n"; cout << "Restore file size: " << size << " bytes\n";
if (size > (off_t)(sector_size * capacity)) { if (size > (off_t)(inq_info.sector_size * inq_info.capacity)) {
cout << "WARNING: File size is larger than disk size\n" << flush; cout << "WARNING: File size is larger than disk size\n" << flush;
} else if (size < (off_t)(sector_size * capacity)) { } else if (size < (off_t)(inq_info.sector_size * inq_info.capacity)) {
throw parser_exception("File size is smaller than disk size"); throw parser_exception("File size is smaller than disk size");
} }
} } else {
else { cout << "Starting dump\n" << flush;
cout << "Starting dump\n" << flush; }
}
// Dump by buffer size // Dump by buffer size
auto dsiz = static_cast<int>(buffer.size()); auto dsiz = static_cast<int>(buffer.size());
const int duni = dsiz / sector_size; const int duni = dsiz / inq_info.sector_size;
auto dnum = static_cast<int>((capacity * sector_size) / dsiz); auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
int i; auto start_time = chrono::high_resolution_clock::now();
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);
}
if (fs.fail()) { int i;
throw parser_exception("File I/O failed"); 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 cout << ((i + 1) * 100 / dnum) << "%"
dnum = capacity % duni; << " (" << (i + 1) * duni << "/" << inq_info.capacity << ")\n"
dsiz = dnum * sector_size; << flush;
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);
}
if (fs.fail()) { // Rounding on capacity
throw parser_exception("File I/O failed"); 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 // Assert RST for 1 ms
bus->SetRST(true); bus->SetRST(true);
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000}; const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
bus->SetRST(false); bus->SetRST(false);
cout << "Target device ID: " << target_id << ", LUN: " << target_lun << "\n"; cout << divider_str << "\n";
cout << "PiSCSI board ID: " << initiator_id << "\n" << flush; 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 inquiry_info_t inq_info;
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;
if (auto type = static_cast<device_type>(buffer[0]); // Display INQUIRY information
type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) { array<char, 17> str = {};
throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY"); 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" TestUnitReady();
<< "Sector size: " << sector_size << " bytes\n"
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB ("
<< sector_size * capacity << " bytes)\n\n" << flush;
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 #pragma once
#include "shared/scsi.h"
#include "hal/bus.h" #include "hal/bus.h"
#include "shared/scsi.h"
#include <memory>
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
using namespace std; using namespace std;
class RasDump class ScsiDump
{ {
static const int MINIMUM_BUFFER_SIZE = 1024 * 64; static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024; static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
public: public:
ScsiDump() = default;
~ScsiDump() = default;
RasDump() = default; int run(const vector<char*>&);
~RasDump() = default;
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; private:
bool Init() const; bool Banner(const vector<char*>&) const;
void ParseArguments(const vector<char *>&); bool Init() const;
int DumpRestore(); void ParseArguments(const vector<char*>&);
pair<uint64_t, uint32_t> GetDeviceInfo(); int DumpRestore();
void WaitPhase(phase_t) const; inquiry_info_t GetDeviceInfo();
void Selection() const; void WaitPhase(phase_t) const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const; void Selection() const;
void DataIn(int); void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
void DataOut(int); void DataIn(int);
void Status() const; void DataOut(int);
void MessageIn() const; void Status() const;
void BusFree() const; void MessageIn() const;
void TestUnitReady() const; void BusFree() const;
void RequestSense(); void TestUnitReady() const;
void Inquiry(); void RequestSense();
pair<uint64_t, uint32_t> ReadCapacity(); void Inquiry();
void Read10(uint32_t, uint32_t, uint32_t); pair<uint64_t, uint32_t> ReadCapacity();
void Write10(uint32_t, uint32_t, uint32_t); void Read10(uint32_t, uint32_t, uint32_t);
void WaitForBusy() const; void Write10(uint32_t, uint32_t, uint32_t);
void WaitForBusy() const;
static void CleanUp(); static void CleanUp();
static void KillHandler(int); static void KillHandler(int);
// A static instance is needed because of the signal handler // A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus; static inline unique_ptr<BUS> bus;
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 #endif
{ {
path new_filename; path new_filename;
auto in_filename = path(__filename);
bool create_directory = false; bool create_directory = false;
// If we're trying to open up the device tree soc ranges, // If we're trying to open up the device tree soc ranges,
// re-direct it to a temporary local file. // 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; create_directory = true;
new_filename = test_data_temp_path; new_filename = test_data_temp_path;
new_filename += path(__filename); if (!in_filename.has_parent_path()) {
new_filename += "/";
}
new_filename += in_filename;
} else { } else {
new_filename = path(__filename); new_filename = in_filename;
} }
if (create_directory) { 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_exceptions.h"
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include <filesystem> #include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream> #include <sstream>
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
@ -26,7 +28,7 @@ const path test_data_temp_path(temp_directory_path() /
path(fmt::format("piscsi-test-{}", path(fmt::format("piscsi-test-{}",
getpid()))); // NOSONAR Publicly writable directory is fine here 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; DeviceFactory device_factory;
@ -39,21 +41,21 @@ shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType type, MockAbstractController
return device; return device;
} }
void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string &ident, int additional_length, void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length,
bool removable, const string &extension) bool removable, const string& extension)
{ {
auto bus = make_shared<MockBus>(); auto bus = make_shared<MockBus>();
auto controller_manager = make_shared<ControllerManager>(*bus); auto controller_manager = make_shared<ControllerManager>(*bus);
auto controller = make_shared<NiceMock<MockAbstractController>>(controller_manager, 0); auto controller = make_shared<NiceMock<MockAbstractController>>(controller_manager, 0);
auto device = CreateDevice(type, *controller, extension); auto device = CreateDevice(type, *controller, extension);
auto &cmd = controller->GetCmd(); auto& cmd = controller->GetCmd();
// ALLOCATION LENGTH // ALLOCATION LENGTH
cmd[4] = 255; cmd[4] = 255;
EXPECT_CALL(*controller, DataIn()); EXPECT_CALL(*controller, DataIn());
device->Dispatch(scsi_command::eCmdInquiry); 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(t, static_cast<device_type>(buffer[0]));
EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]); EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]);
EXPECT_EQ(l, static_cast<scsi_level>(buffer[2])); 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. // 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(). // 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; path new_filename = test_data_temp_path;
new_filename += path(filename); new_filename += path(filename);
create_directories(new_filename.parent_path()); 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) { if (fp == nullptr) {
printf("ERROR: Unable to open file %s\n", new_filename.c_str()); printf("ERROR: Unable to open file %s\n", new_filename.c_str());
return; return;
} }
if (const size_t size_written = fwrite(&data[0], sizeof(uint8_t), data.size(), fp); 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, 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()); sizeof(vector<uint8_t>::value_type) * data.size(), filename.c_str());
} }
@ -132,14 +134,24 @@ void CleanupAllTempFiles()
remove_all(test_data_temp_path); 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); assert(buf.size() > static_cast<size_t>(offset) + 1);
return (to_integer<int>(buf[offset]) << 8) | to_integer<int>(buf[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); assert(buf.size() > static_cast<size_t>(offset) + 3);

View File

@ -9,11 +9,11 @@
#pragma once #pragma once
#include "shared/scsi.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include <string> #include "shared/scsi.h"
#include <memory>
#include <filesystem> #include <filesystem>
#include <memory>
#include <string>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
@ -26,13 +26,12 @@ extern const path test_data_temp_path;
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, MockAbstractController&, const string& = ""); shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, MockAbstractController&, const string& = "");
void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool,
int, bool, const string& = ""); const string& = "");
pair<int, path> OpenTempFile(); pair<int, path> OpenTempFile();
path CreateTempFile(int); path CreateTempFile(int);
// create a file with the specified data // create a file with the specified data
void CreateTempFileWithData(const string&, vector<uint8_t>&); 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 // Call this at the end of every test case to make sure things are cleaned up
void CleanupAllTempFiles(); void CleanupAllTempFiles();
string ReadTempFileToString(const string& filename);
int GetInt16(const vector<byte>&, int); int GetInt16(const vector<byte>&, int);
uint32_t GetInt32(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\-s\fR \fIBUFFER_SIZE\fR]
[\fB\-r\fR] [\fB\-r\fR]
[\fB\-v\fR] [\fB\-v\fR]
[\fB\-p\fR]
.SH DESCRIPTION .SH DESCRIPTION
.B scsidump .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 .SH OPTIONS
.TP .TP
.BR \-t\fI " "\fIID[:LUN] .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 .TP
.BR \-i\fI " "\fIBID .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 .TP
.BR \-f\fI " "\fIFILE .BR \-f\fI " "\fIFILE
Path to the dump file. Path to the dump file.
.TP .TP
.BR \-s\fI " "\fIBUFFER_SIZE .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 .TP
.BR \-r\fI .BR \-r\fI
Run in restore mode. Run in restore mode. Defaults to dump mode if not specified.
.TP .TP
.BR \-v\fI .BR \-v\fI
Enable verbose logging. 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 .SH SEE ALSO
scsictl(1), piscsi(1), scsimon(1) scsictl(1), piscsi(1), scsimon(1)

View File

@ -2,40 +2,55 @@
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating !! ------ 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 NAME
scsidump - SCSI disk dumping tool for PiSCSI scsidump - SCSI disk dumping tool for PiSCSI
SYNOPSIS 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 DESCRIPTION
scsidump samples the data on physical SCSI storage media, including hard drives and MO drives, and stores it to an image 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.
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.
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 OPTIONS
-t ID[:LUN] -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 -f FILE
Path to the dump file. Path to the dump file.
-s BUFFER_SIZE -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. -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 SEE ALSO
scsictl(1), piscsi(1), scsimon(1) scsictl(1), piscsi(1), scsimon(1)
Full documentation is available at: <https://www.piscsi.com> Full documentation is available at: <https://www.piscsi.com>
scsidump(1) scsidump(1)