From 5b3626dcf5c87ca4d8ee0cdcbeecec8f0551ee59 Mon Sep 17 00:00:00 2001 From: akuker <34318535+akuker@users.noreply.github.com> Date: Fri, 9 Dec 2022 09:50:45 -0600 Subject: [PATCH] SCSI Dump Usability Enhancements & Cleanup (#1026) Co-authored-by: Tony Kuker --- cpp/.clang-format | 3 +- cpp/hal/gpiobus_factory.cpp | 41 +- cpp/scsidump.cpp | 2 +- cpp/scsidump/scsidump_core.cpp | 758 ++++++++++++++++++--------------- cpp/scsidump/scsidump_core.h | 95 +++-- cpp/test/linux_os_stubs.cpp | 11 +- cpp/test/scsidump_test.cpp | 72 ++++ cpp/test/test_shared.cpp | 32 +- cpp/test/test_shared.h | 13 +- doc/scsidump.1 | 34 +- doc/scsidump_man_page.txt | 37 +- 11 files changed, 655 insertions(+), 443 deletions(-) create mode 100644 cpp/test/scsidump_test.cpp diff --git a/cpp/.clang-format b/cpp/.clang-format index 5f33f52d..7e73f97c 100644 --- a/cpp/.clang-format +++ b/cpp/.clang-format @@ -8,4 +8,5 @@ AlignEscapedNewlines: Left AlignTrailingComments: True AllowShortEnumsOnASingleLine: True AlignConsecutiveAssignments: Consecutive -ColumnLimit: 120 \ No newline at end of file +ColumnLimit: 120 +PointerAlignment: Left \ No newline at end of file diff --git a/cpp/hal/gpiobus_factory.cpp b/cpp/hal/gpiobus_factory.cpp index 30293e31..3a4a6914 100644 --- a/cpp/hal/gpiobus_factory.cpp +++ b/cpp/hal/gpiobus_factory.cpp @@ -21,24 +21,31 @@ using namespace std; unique_ptr 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 return_ptr; - SBC_Version::Init(); - if (SBC_Version::IsBananaPi()) { - LOGTRACE("Creating GPIOBUS_BananaM2p") - return_ptr = make_unique(); - } else if (SBC_Version::IsRaspberryPi()) { - LOGTRACE("Creating GPIOBUS_Raspberry") - return_ptr = make_unique(); - } else { - LOGINFO("Creating Virtual GPIOBUS") - return_ptr = make_unique(); + + 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(); + } else if (SBC_Version::IsRaspberryPi()) { + LOGTRACE("Creating GPIOBUS_Raspberry") + return_ptr = make_unique(); + } else { + LOGINFO("Creating Virtual GPIOBUS") + return_ptr = make_unique(); + } + 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; } diff --git a/cpp/scsidump.cpp b/cpp/scsidump.cpp index d8085e3b..35d7645c 100644 --- a/cpp/scsidump.cpp +++ b/cpp/scsidump.cpp @@ -15,5 +15,5 @@ int main(int argc, char *argv[]) { const vector args(argv, argv + argc); - return RasDump().run(args); + return ScsiDump().run(args); } diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index bfe01244..7d1e2060 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -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 +#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 #include #include -#include #include -#include #include +#include +#include +#include +#include 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& args) const +bool ScsiDump::Banner(const vector& 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& args) +void ScsiDump::ParseArguments(const vector& args) { - int opt; + int opt; - int buffer_size = DEFAULT_BUFFER_SIZE; + int buffer_size = DEFAULT_BUFFER_SIZE; - opterr = 0; - while ((opt = getopt(static_cast(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(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(buffer_size); + if (filename.empty()) { + throw parser_exception("Missing filename"); + } + + buffer = vector(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(1 << initiator_id); - data |= static_cast(1 << target_id); - bus->SetDAT(static_cast(data)); + // Set initiator and target ID + auto data = static_cast(1 << initiator_id); + data |= static_cast(1 << target_id); + bus->SetDAT(static_cast(data)); - bus->SetSEL(true); + bus->SetSEL(true); - WaitForBusy(); + WaitForBusy(); - bus->SetSEL(false); + bus->SetSEL(false); } -void RasDump::Command(scsi_command cmd, vector& cdb) const +void ScsiDump::Command(scsi_command cmd, vector& 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(cmd); - cdb[1] = static_cast(static_cast(cdb[1]) | static_cast(target_lun << 5)); - if (static_cast(cdb.size()) != bus->SendHandShake(cdb.data(), static_cast(cdb.size()), BUS::SEND_NO_DELAY)) { - BusFree(); + cdb[0] = static_cast(cmd); + cdb[1] = static_cast(static_cast(cdb[1]) | static_cast(target_lun << 5)); + if (static_cast(cdb.size()) != + bus->SendHandShake(cdb.data(), static_cast(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 buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("STATUS failed"); - } + if (array 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 buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { - throw parser_exception("MESSAGE IN failed"); - } + if (array 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 cdb(6); - Command(scsi_command::eCmdTestUnitReady, cdb); + vector cdb(6); + Command(scsi_command::eCmdTestUnitReady, cdb); - Status(); + Status(); - MessageIn(); + MessageIn(); - BusFree(); + BusFree(); } -void RasDump::RequestSense() +void ScsiDump::RequestSense() { - vector cdb(6); - cdb[4] = 0xff; - Command(scsi_command::eCmdRequestSense, cdb); + vector 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 cdb(6); - cdb[4] = 0xff; - Command(scsi_command::eCmdInquiry, cdb); + vector cdb(6); + cdb[4] = 0xff; + Command(scsi_command::eCmdInquiry, cdb); - DataIn(256); + DataIn(256); - Status(); + Status(); - MessageIn(); + MessageIn(); - BusFree(); + BusFree(); } -pair RasDump::ReadCapacity() +pair ScsiDump::ReadCapacity() { - vector cdb(10); - Command(scsi_command::eCmdReadCapacity10, cdb); + vector cdb(10); + Command(scsi_command::eCmdReadCapacity10, cdb); - DataIn(8); + DataIn(8); - Status(); + Status(); - MessageIn(); + MessageIn(); - BusFree(); + BusFree(); - uint64_t capacity = (static_cast(buffer[0]) << 24) | (static_cast(buffer[1]) << 16) | - (static_cast(buffer[2]) << 8) | static_cast(buffer[3]); + uint64_t capacity = (static_cast(buffer[0]) << 24) | (static_cast(buffer[1]) << 16) | + (static_cast(buffer[2]) << 8) | static_cast(buffer[3]); - int sector_size_offset = 4; + int sector_size_offset = 4; - if (static_cast(capacity) == -1) { - cdb.resize(16); - // READ CAPACITY(16), not READ LONG(16) - cdb[1] = 0x10; - Command(scsi_command::eCmdReadCapacity16_ReadLong16, cdb); + if (static_cast(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(buffer[0]) << 56) | (static_cast(buffer[1]) << 48) | - (static_cast(buffer[2]) << 40) | (static_cast(buffer[3]) << 32) | - (static_cast(buffer[4]) << 24) | (static_cast(buffer[5]) << 16) | - (static_cast(buffer[6]) << 8) | static_cast(buffer[7]); + capacity = (static_cast(buffer[0]) << 56) | (static_cast(buffer[1]) << 48) | + (static_cast(buffer[2]) << 40) | (static_cast(buffer[3]) << 32) | + (static_cast(buffer[4]) << 24) | (static_cast(buffer[5]) << 16) | + (static_cast(buffer[6]) << 8) | static_cast(buffer[7]); - sector_size_offset = 8; - } + sector_size_offset = 8; + } - const uint32_t sector_size = (static_cast(buffer[sector_size_offset]) << 24) | - (static_cast(buffer[sector_size_offset + 1]) << 16) | - (static_cast(buffer[sector_size_offset +2]) << 8) | - static_cast(buffer[sector_size_offset + 3]); + const uint32_t sector_size = (static_cast(buffer[sector_size_offset]) << 24) | + (static_cast(buffer[sector_size_offset + 1]) << 16) | + (static_cast(buffer[sector_size_offset + 2]) << 8) | + static_cast(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 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 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 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 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& args) +int ScsiDump::run(const vector& 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(buffer.size()); - const int duni = dsiz / sector_size; - auto dnum = static_cast((capacity * sector_size) / dsiz); + // Dump by buffer size + auto dsiz = static_cast(buffer.size()); + const int duni = dsiz / inq_info.sector_size; + auto dnum = static_cast((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(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 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 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(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 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(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); +} \ No newline at end of file diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index 5b4eb2f7..c46a6993 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -9,64 +9,79 @@ #pragma once -#include "shared/scsi.h" #include "hal/bus.h" +#include "shared/scsi.h" +#include #include #include -#include 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&); - int run(const vector&); + 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&) const; - bool Init() const; - void ParseArguments(const vector&); - int DumpRestore(); - pair GetDeviceInfo(); - void WaitPhase(phase_t) const; - void Selection() const; - void Command(scsi_defs::scsi_command, vector&) const; - void DataIn(int); - void DataOut(int); - void Status() const; - void MessageIn() const; - void BusFree() const; - void TestUnitReady() const; - void RequestSense(); - void Inquiry(); - pair 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&) const; + bool Init() const; + void ParseArguments(const vector&); + int DumpRestore(); + inquiry_info_t GetDeviceInfo(); + void WaitPhase(phase_t) const; + void Selection() const; + void Command(scsi_defs::scsi_command, vector&) const; + void DataIn(int); + void DataOut(int); + void Status() const; + void MessageIn() const; + void BusFree() const; + void TestUnitReady() const; + void RequestSense(); + void Inquiry(); + pair 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; + // A static instance is needed because of the signal handler + static inline unique_ptr bus; - vector buffer; + vector 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 = "----------------------------------------"; }; diff --git a/cpp/test/linux_os_stubs.cpp b/cpp/test/linux_os_stubs.cpp index 41eec33c..f5885e27 100644 --- a/cpp/test/linux_os_stubs.cpp +++ b/cpp/test/linux_os_stubs.cpp @@ -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) { diff --git a/cpp/test/scsidump_test.cpp b/cpp/test/scsidump_test.cpp new file mode 100644 index 00000000..473fc7ac --- /dev/null +++ b/cpp/test/scsidump_test.cpp @@ -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 +#include + +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); +} diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index b78a4def..743d9eb6 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -13,6 +13,8 @@ #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" #include +#include +#include #include #include #include @@ -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 CreateDevice(PbDeviceType type, MockAbstractController &controller, const string &extension) +shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension) { DeviceFactory device_factory; @@ -39,21 +41,21 @@ shared_ptr 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(); auto controller_manager = make_shared(*bus); auto controller = make_shared>(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 &buffer = controller->GetBuffer(); + const vector& buffer = controller->GetBuffer(); EXPECT_EQ(t, static_cast(buffer[0])); EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]); EXPECT_EQ(l, static_cast(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 &data) +void CreateTempFileWithData(const string& filename, vector& 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::value_type) * data.size()) { + size_written != sizeof(vector::value_type) * data.size()) { printf("Expected to write %zu bytes, but only wrote %zu to %s", size_written, sizeof(vector::value_type) * data.size(), filename.c_str()); } @@ -132,14 +134,24 @@ void CleanupAllTempFiles() remove_all(test_data_temp_path); } -int GetInt16(const vector &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& buf, int offset) { assert(buf.size() > static_cast(offset) + 1); return (to_integer(buf[offset]) << 8) | to_integer(buf[offset + 1]); } -uint32_t GetInt32(const vector &buf, int offset) +uint32_t GetInt32(const vector& buf, int offset) { assert(buf.size() > static_cast(offset) + 3); diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index c95c6b18..0c4b7c6a 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -9,11 +9,11 @@ #pragma once -#include "shared/scsi.h" #include "generated/piscsi_interface.pb.h" -#include -#include +#include "shared/scsi.h" #include +#include +#include using namespace std; using namespace filesystem; @@ -26,13 +26,12 @@ extern const path test_data_temp_path; shared_ptr 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 OpenTempFile(); path CreateTempFile(int); - // create a file with the specified data void CreateTempFileWithData(const string&, vector&); @@ -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&, int); uint32_t GetInt32(const vector&, int); diff --git a/doc/scsidump.1 b/doc/scsidump.1 index 3f8cd93d..0fe2cb52 100644 --- a/doc/scsidump.1 +++ b/doc/scsidump.1 @@ -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) diff --git a/doc/scsidump_man_page.txt b/doc/scsidump_man_page.txt index 9dcfc72b..a39e64bb 100644 --- a/doc/scsidump_man_page.txt +++ b/doc/scsidump_man_page.txt @@ -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: - scsidump(1) + scsidump(1)