Add options to only run INQUIRY and to scan the bus to scsidump (#1092) (#1261)

* Add options to only run INQUIRY and to scan the bus to scsidump
This commit is contained in:
Uwe Seimet 2023-10-30 11:34:07 +01:00 committed by GitHub
parent c78ba80088
commit 8f45e4f491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 325 additions and 176 deletions

View File

@ -12,9 +12,9 @@
// TODO Evaluate CHECK CONDITION after sending a command // TODO Evaluate CHECK CONDITION after sending a command
// TODO Send IDENTIFY message in order to support LUNS > 7 // TODO Send IDENTIFY message in order to support LUNS > 7
// TODO Get rid of some fields in favor of method arguments
#include "scsidump/scsidump_core.h" #include "scsidump/scsidump_core.h"
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h" #include "hal/gpiobus_factory.h"
#include "hal/systimer.h" #include "hal/systimer.h"
#include "controllers/controller_manager.h" #include "controllers/controller_manager.h"
@ -44,11 +44,11 @@ void ScsiDump::CleanUp()
} }
} }
void ScsiDump::KillHandler(int) void ScsiDump::TerminationHandler(int)
{ {
CleanUp(); CleanUp();
exit(EXIT_SUCCESS); // Process will terminate automatically
} }
bool ScsiDump::Banner(span<char *> args) const bool ScsiDump::Banner(span<char *> args) const
@ -56,7 +56,7 @@ bool ScsiDump::Banner(span<char *> args) const
cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)"); cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") { if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n" cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p] [-I] [-S]\n"
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n" << " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")." << " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
<< " Default is 0.\n" << " Default is 0.\n"
@ -67,6 +67,8 @@ bool ScsiDump::Banner(span<char *> args) const
<< " -v Enable verbose logging.\n" << " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n" << " -r Restore instead of dump.\n"
<< " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n" << " -p Generate .properties file to be used with the PiSCSI web interface. Only valid for dump mode.\n"
<< " -I Display INQUIRY data of ID[:LUN].\n"
<< " -S Scan SCSI bus for devices.\n"
<< flush; << flush;
return false; return false;
@ -77,11 +79,13 @@ bool ScsiDump::Banner(span<char *> args) const
bool ScsiDump::Init() const bool ScsiDump::Init() const
{ {
// Interrupt handler setting // Signal handler for cleaning up
if (signal(SIGINT, KillHandler) == SIG_ERR || signal(SIGHUP, KillHandler) == SIG_ERR || struct sigaction termination_handler;
signal(SIGTERM, KillHandler) == SIG_ERR) { termination_handler.sa_handler = TerminationHandler;
return false; sigemptyset(&termination_handler.sa_mask);
} termination_handler.sa_flags = 0;
sigaction(SIGTERM, &termination_handler, nullptr);
signal(SIGPIPE, SIG_IGN);
bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR); bus = GPIOBUS_Factory::Create(BUS::mode_e::INITIATOR);
@ -95,7 +99,7 @@ void ScsiDump::ParseArguments(span<char *> args)
int buffer_size = DEFAULT_BUFFER_SIZE; int buffer_size = DEFAULT_BUFFER_SIZE;
opterr = 0; opterr = 0;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvp")) != -1) { while ((opt = getopt(static_cast<int>(args.size()), args.data(), "i:f:s:t:rvpIS")) != -1) {
switch (opt) { switch (opt) {
case 'i': case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) { if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
@ -107,6 +111,10 @@ void ScsiDump::ParseArguments(span<char *> args)
filename = optarg; filename = optarg;
break; break;
case 'I':
inquiry = true;
break;
case 's': case 's':
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB"); throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
@ -114,15 +122,15 @@ void ScsiDump::ParseArguments(span<char *> args)
break; break;
case 't': { case 'S':
const string error = ProcessId(optarg, target_id, target_lun); scan_bus = true;
if (!error.empty()) { break;
case 't':
if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) {
throw parser_exception(error); throw parser_exception(error);
} }
if (target_lun == -1) { break;
target_lun = 0;
}
} break;
case 'v': case 'v':
set_level(level::debug); set_level(level::debug);
@ -141,31 +149,43 @@ void ScsiDump::ParseArguments(span<char *> args)
} }
} }
if (!scan_bus && !inquiry && filename.empty()) {
throw parser_exception("Missing filename");
}
if (!scan_bus && !inquiry && target_id == -1) {
throw parser_exception("Missing target ID");
}
if (target_id == initiator_id) { if (target_id == initiator_id) {
throw parser_exception("Target ID and PiSCSI board ID must not be identical"); throw parser_exception("Target ID and PiSCSI board ID must not be identical");
} }
if (filename.empty()) { if (target_lun == -1) {
throw parser_exception("Missing filename"); target_lun = 0;
}
if (scan_bus) {
inquiry = false;
} }
buffer = vector<uint8_t>(buffer_size); buffer = vector<uint8_t>(buffer_size);
} }
void ScsiDump::WaitPhase(phase_t phase) const void ScsiDump::WaitForPhase(phase_t phase) const
{ {
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase"); spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
// Timeout (3000ms) // Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow(); const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { while ((SysTimer::GetTimerLow() - now) < 3'000'000) {
bus->Acquire(); bus->Acquire();
if (bus->GetREQ() && bus->GetPhase() == phase) { if (bus->GetREQ() && bus->GetPhase() == phase) {
return; return;
} }
} }
throw parser_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " + throw phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
string(BUS::GetPhaseStrRaw(bus->GetPhase()))); string(BUS::GetPhaseStrRaw(bus->GetPhase())));
} }
@ -189,7 +209,7 @@ void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
Selection(); Selection();
WaitPhase(phase_t::command); WaitForPhase(phase_t::command);
cdb[0] = static_cast<uint8_t>(cmd); cdb[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5)); cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
@ -197,43 +217,43 @@ void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) { bus->SendHandShake(cdb.data(), static_cast<int>(cdb.size()), BUS::SEND_NO_DELAY)) {
BusFree(); BusFree();
throw parser_exception(command_mapping.find(cmd)->second.second + string(" failed")); throw phase_exception(command_mapping.find(cmd)->second.second + string(" failed"));
} }
} }
void ScsiDump::DataIn(int length) void ScsiDump::DataIn(int length)
{ {
WaitPhase(phase_t::datain); WaitForPhase(phase_t::datain);
if (!bus->ReceiveHandShake(buffer.data(), length)) { if (!bus->ReceiveHandShake(buffer.data(), length)) {
throw parser_exception("DATA IN failed"); throw phase_exception("DATA IN failed");
} }
} }
void ScsiDump::DataOut(int length) void ScsiDump::DataOut(int length)
{ {
WaitPhase(phase_t::dataout); WaitForPhase(phase_t::dataout);
if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) { if (!bus->SendHandShake(buffer.data(), length, BUS::SEND_NO_DELAY)) {
throw parser_exception("DATA OUT failed"); throw phase_exception("DATA OUT failed");
} }
} }
void ScsiDump::Status() const void ScsiDump::Status() const
{ {
WaitPhase(phase_t::status); WaitForPhase(phase_t::status);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("STATUS failed"); throw phase_exception("STATUS failed");
} }
} }
void ScsiDump::MessageIn() const void ScsiDump::MessageIn() const
{ {
WaitPhase(phase_t::msgin); WaitForPhase(phase_t::msgin);
if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) { if (array<uint8_t, 256> buf; bus->ReceiveHandShake(buf.data(), 1) != 1) {
throw parser_exception("MESSAGE IN failed"); throw phase_exception("MESSAGE IN failed");
} }
} }
@ -388,7 +408,7 @@ void ScsiDump::WaitForBusy() const
// Success if the target is busy // Success if the target is busy
if (!bus->GetBSY()) { if (!bus->GetBSY()) {
throw parser_exception("SELECTION failed"); throw phase_exception("SELECTION failed");
} }
} }
@ -398,23 +418,44 @@ int ScsiDump::run(span<char *> args)
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
if (!Init()) {
cerr << "Error: Initializing. Are you root?" << endl;
// Probably not root
return EPERM;
}
try { try {
ParseArguments(args); ParseArguments(args);
}
catch (const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
return EXIT_FAILURE;
}
if (getuid()) {
cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl;
return EXIT_FAILURE;
}
#ifndef USE_SEL_EVENT_ENABLE #ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl; cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
#endif #endif
return DumpRestore(); if (!Init()) {
} catch (const parser_exception& e) { cerr << "Error: Can't initialize bus" << endl;
return EXIT_FAILURE;
}
try {
if (scan_bus) {
ScanBus();
}
else if (inquiry) {
DisplayBoardId();
inquiry_info_t inq_info;
DisplayInquiry(inq_info, false);
}
else {
DumpRestore();
}
}
catch (const phase_exception& e) {
cerr << "Error: " << e.what() << endl; cerr << "Error: " << e.what() << endl;
CleanUp(); CleanUp();
@ -427,9 +468,92 @@ int ScsiDump::run(span<char *> args)
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
void ScsiDump::DisplayBoardId() const
{
cout << DIVIDER << "\nPiSCSI board ID is " << initiator_id << "\n";
}
void ScsiDump::ScanBus()
{
DisplayBoardId();
for (target_id = 0; target_id < ControllerManager::GetScsiIdMax(); target_id++) {
if (initiator_id == target_id) {
continue;
}
for (target_lun = 0; target_lun < 8; target_lun++) {
inquiry_info_t inq_info;
try {
DisplayInquiry(inq_info, false);
}
catch(const phase_exception&) {
// Continue with next ID if there is no LUN 0
if (!target_lun) {
break;
}
}
}
}
}
bool ScsiDump::DisplayInquiry(ScsiDump::inquiry_info_t& inq_info, bool check_type)
{
// Assert RST for 1 ms
bus->SetRST(true);
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus->SetRST(false);
cout << DIVIDER << "\nTarget device is " << target_id << ":" << target_lun << "\n" << flush;
Inquiry();
const auto type = static_cast<byte>(buffer[0]);
if ((type & byte{0x1f}) == byte{0x1f}) {
// Requested LUN is not available
return false;
}
array<char, 17> str = {};
memcpy(str.data(), &buffer[8], 8);
inq_info.vendor = string(str.data());
cout << "Vendor: " << inq_info.vendor << "\n";
str.fill(0);
memcpy(str.data(), &buffer[16], 16);
inq_info.product = string(str.data());
cout << "Product: " << inq_info.product << "\n";
str.fill(0);
memcpy(str.data(), &buffer[32], 4);
inq_info.revision = string(str.data());
cout << "Revision: " << inq_info.revision << "\n" << flush;
if (const auto& t = DEVICE_TYPES.find(type & byte{0x1f}); t != DEVICE_TYPES.end()) {
cout << "Device Type: " << (*t).second << "\n";
}
else {
cout << "Device Type: Unknown\n";
}
cout << "Removable: " << (((static_cast<byte>(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n";
if (check_type && type != static_cast<byte>(device_type::direct_access) &&
type != static_cast<byte>(device_type::cd_rom) && type != static_cast<byte>(device_type::optical_memory)) {
cerr << "Invalid device type, supported types for dump/restore are DIRECT ACCESS, CD-ROM/DVD/BD and OPTICAL MEMORY" << endl;
return false;
}
return true;
}
int ScsiDump::DumpRestore() int ScsiDump::DumpRestore()
{ {
const auto inq_info = GetDeviceInfo(); inquiry_info_t inq_info;
if (!GetDeviceInfo(inq_info)) {
return EXIT_FAILURE;
}
fstream fs; fstream fs;
fs.open(filename, (restore ? ios::in : ios::out) | ios::binary); fs.open(filename, (restore ? ios::in : ios::out) | ios::binary);
@ -510,58 +634,28 @@ int ScsiDump::DumpRestore()
const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count(); const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
cout << divider_str << "\n"; cout << DIVIDER << "\n";
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes [" cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n"; << inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n"; cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
<< " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024 << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
<< " KiB per second)\n"; << " KiB per second)\n";
cout << divider_str << "\n"; cout << DIVIDER << "\n";
if (properties_file && !restore) { if (properties_file && !restore) {
GeneratePropertiesFile(filename, inq_info); inq_info.GeneratePropertiesFile(filename + ".properties");
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info)
{ {
// Assert RST for 1 ms DisplayBoardId();
bus->SetRST(true);
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus->SetRST(false);
cout << divider_str << "\n"; if (!DisplayInquiry(inq_info, true)) {
cout << "PiSCSI board ID: " << initiator_id << "\n"; return false;
cout << divider_str << "\n" << flush;
cout << "Target device is " << target_id << ":" << target_lun << "\n";
Inquiry();
inquiry_info_t inq_info;
// Display INQUIRY information
array<char, 17> str = {};
memcpy(str.data(), &buffer[8], 8);
cout << "Vendor: " << str.data() << "\n";
inq_info.vendor = string(str.data());
str.fill(0);
memcpy(str.data(), &buffer[16], 16);
cout << "Product: " << str.data() << "\n";
inq_info.product = string(str.data());
str.fill(0);
memcpy(str.data(), &buffer[32], 4);
cout << "Revision: " << str.data() << "\n" << flush;
inq_info.revision = string(str.data());
if (const auto type = static_cast<device_type>(buffer[0]);
type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) {
throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY");
} }
TestUnitReady(); TestUnitReady();
@ -576,31 +670,24 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
<< "Sector size: " << sector_size << " bytes\n" << "Sector size: " << sector_size << " bytes\n"
<< "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity << "Capacity: " << sector_size * capacity / 1024 / 1024 << " MiB (" << sector_size * capacity
<< " bytes)\n" << " bytes)\n"
<< divider_str << "\n\n" << DIVIDER << "\n\n"
<< flush; << flush;
return inq_info; return true;
} }
void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) void ScsiDump::inquiry_info::GeneratePropertiesFile(const string& property_file) const
{ {
const string prop_filename = filename + ".properties"; ofstream prop_stream(property_file);
stringstream prop_stream;
prop_stream << "{" << endl; prop_stream << "{" << endl;
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; prop_stream << " \"vendor\": \"" << vendor << "\"," << endl;
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; prop_stream << " \"product\": \"" << product << "\"," << endl;
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl; prop_stream << " \"revision\": \"" << revision << "\"," << endl;
prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl; prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl;
prop_stream << "}" << endl; prop_stream << "}" << endl;
FILE* fp = fopen(prop_filename.c_str(), "w"); if (prop_stream.fail()) {
if (fp) { spdlog::warn("Unable to create properties file '" + property_file + "'");
fputs(prop_stream.str().c_str(), fp);
} else {
spdlog::warn("Unable to open output file '" + prop_filename + "'");
return;
} }
fclose(fp);
} }

View File

@ -10,45 +10,52 @@
#pragma once #pragma once
#include "hal/bus.h" #include "hal/bus.h"
#include "shared/scsi.h"
#include <memory> #include <memory>
#include <string> #include <string>
#include <span> #include <span>
#include <vector> #include <vector>
#include <unordered_map>
#include <stdexcept>
using namespace std; using namespace std;
class phase_exception : public runtime_error
{
using runtime_error::runtime_error;
};
class ScsiDump class ScsiDump
{ {
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
public: public:
ScsiDump() = default; ScsiDump() = default;
~ScsiDump() = default; ~ScsiDump() = default;
int run(const span<char *>); int run(const span<char *>);
struct inquiry_info_struct { struct inquiry_info {
string vendor; string vendor;
string product; string product;
string revision; string revision;
uint32_t sector_size; uint32_t sector_size;
uint64_t capacity; uint64_t capacity;
};
using inquiry_info_t = struct inquiry_info_struct;
protected: void GeneratePropertiesFile(const string&) const;
// Protected for testability };
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info); using inquiry_info_t = struct inquiry_info;
private: private:
bool Banner(span<char *>) const; bool Banner(span<char *>) const;
bool Init() const; bool Init() const;
void ParseArguments(span<char *>); void ParseArguments(span<char *>);
void DisplayBoardId() const;
void ScanBus();
bool DisplayInquiry(inquiry_info_t&, bool);
int DumpRestore(); int DumpRestore();
inquiry_info_t GetDeviceInfo(); bool GetDeviceInfo(inquiry_info_t&);
void WaitPhase(phase_t) const; void WaitForPhase(phase_t) const;
void Selection() const; void Selection() const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const; void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
void DataIn(int); void DataIn(int);
@ -65,7 +72,7 @@ class ScsiDump
void WaitForBusy() const; void WaitForBusy() const;
static void CleanUp(); static void CleanUp();
static void KillHandler(int); static void TerminationHandler(int);
// A static instance is needed because of the signal handler // A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus; static inline unique_ptr<BUS> bus;
@ -80,9 +87,41 @@ class ScsiDump
string filename; string filename;
bool inquiry = false;
bool scan_bus = false;
bool restore = false; bool restore = false;
bool properties_file = false; bool properties_file = false;
static inline const string divider_str = "----------------------------------------"; static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
static inline const string DIVIDER = "----------------------------------------";
static inline const unordered_map<byte, string> DEVICE_TYPES = {
{ byte{0}, "Direct Access" },
{ byte{1}, "Sequential Access" },
{ byte{2}, "Printer" },
{ byte{3}, "Processor" },
{ byte{4}, "Write-Once" },
{ byte{5}, "CD-ROM/DVD/BD/DVD-RAM" },
{ byte{6}, "Scanner" },
{ byte{7}, "Optical Memory" },
{ byte{8}, "Media Changer" },
{ byte{9}, "Communications" },
{ byte{10}, "Graphic Arts Pre-Press" },
{ byte{11}, "Graphic Arts Pre-Press" },
{ byte{12}, "Storage Array Controller" },
{ byte{13}, "Enclosure Services" },
{ byte{14}, "Simplified Direct Access" },
{ byte{15}, "Optical Card Reader/Writer" },
{ byte{16}, "Bridge Controller" },
{ byte{17}, "Object-based Storage" },
{ byte{18}, "Automation/Drive Interface" },
{ byte{19}, "Security Manager" },
{ byte{20}, "Host Managed Zoned Block" },
{ byte{30}, "Well Known Logical Unit" }
};
}; };

View File

@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges)
EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address()); EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address());
DeleteTempFile("/proc/device-tree/soc/ranges"); DeleteTempFile("/proc/device-tree/soc/ranges");
CleanUpAllTempFiles(); remove_all(test_data_temp_path);
} }
TEST(GpiobusRaspberry, GetDat) TEST(GpiobusRaspberry, GetDat)

View File

@ -1,72 +1,66 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 akuker // Copyright (C) 2022 akuker
// Copyright (C) 2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "mocks.h" #include <gtest/gtest.h>
#include "scsidump/scsidump_core.h" #include "scsidump/scsidump_core.h"
#include "test/test_shared.h" #include "test/test_shared.h"
#include <filesystem>
#include <fstream>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
class TestableScsidump : public ScsiDump
{
public:
static void PublicGeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
{
ScsiDump::GeneratePropertiesFile(filename, inq_info);
}
};
TEST(ScsiDumpTest, GeneratePropertiesFile) TEST(ScsiDumpTest, GeneratePropertiesFile)
{ {
// Basic test // Basic test
const string prop_file_name = "test.properties"; auto filename = CreateTempFile(0);
ScsiDump::inquiry_info_t test_data = { ScsiDump::inquiry_info_t test_data = {
.vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100}; .vendor = "PISCSI", .product = "TEST PRODUCT", .revision = "REV1", .sector_size = 1000, .capacity = 100};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data); test_data.GeneratePropertiesFile(filename);
string expected_str = "{\n" string expected_str = "{\n"
" \"vendor\": \"PISCSI\",\n" " \"vendor\": \"PISCSI\",\n"
" \"product\": \"TEST PRODUCT\",\n" " \"product\": \"TEST PRODUCT\",\n"
" \"revision\": \"REV1\",\n" " \"revision\": \"REV1\",\n"
" \"block_size\": \"1000\",\n}" " \"block_size\": \"1000\"\n}"
"\n"; "\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); EXPECT_EQ(expected_str, ReadTempFileToString(filename));
// Long string test // Long string test
filename = CreateTempFile(0);
test_data = {.vendor = "01234567", test_data = {.vendor = "01234567",
.product = "0123456789ABCDEF", .product = "0123456789ABCDEF",
.revision = "0123", .revision = "0123",
.sector_size = UINT32_MAX, .sector_size = UINT32_MAX,
.capacity = UINT64_MAX}; .capacity = UINT64_MAX};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data); test_data.GeneratePropertiesFile(filename);
expected_str = "{\n" expected_str = "{\n"
" \"vendor\": \"01234567\",\n" " \"vendor\": \"01234567\",\n"
" \"product\": \"0123456789ABCDEF\",\n" " \"product\": \"0123456789ABCDEF\",\n"
" \"revision\": \"0123\",\n" " \"revision\": \"0123\",\n"
" \"block_size\": \"4294967295\",\n" " \"block_size\": \"4294967295\"\n"
"}\n"; "}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); EXPECT_EQ(expected_str, ReadTempFileToString(filename));
remove(filename);
// Empty data test // Empty data test
filename = CreateTempFile(0);
test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0}; test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data); test_data.GeneratePropertiesFile(filename);
expected_str = "{\n" expected_str = "{\n"
" \"vendor\": \"\",\n" " \"vendor\": \"\",\n"
" \"product\": \"\",\n" " \"product\": \"\",\n"
" \"revision\": \"\",\n" " \"revision\": \"\",\n"
" \"block_size\": \"0\",\n" " \"block_size\": \"0\"\n"
"}\n"; "}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str); EXPECT_EQ(expected_str, ReadTempFileToString(filename));
remove(filename);
} }

View File

@ -121,6 +121,7 @@ void CreateTempFileWithData(const string& filename, vector<uint8_t>& data)
fclose(fp); fclose(fp);
} }
// TODO Move this code, it is not shared
void DeleteTempFile(const string& filename) void DeleteTempFile(const string& filename)
{ {
path temp_file = test_data_temp_path; path temp_file = test_data_temp_path;
@ -128,17 +129,12 @@ void DeleteTempFile(const string& filename)
remove(temp_file); remove(temp_file);
} }
void CleanUpAllTempFiles()
{
remove_all(test_data_temp_path);
}
string ReadTempFileToString(const string& filename) string ReadTempFileToString(const string& filename)
{ {
const path temp_file = test_data_temp_path / path(filename); const path temp_file = test_data_temp_path / path(filename);
ifstream in_fs(temp_file); ifstream in(temp_file);
stringstream buffer; stringstream buffer;
buffer << in_fs.rdbuf(); buffer << in.rdbuf();
return buffer.str(); return buffer.str();
} }

View File

@ -35,8 +35,6 @@ path CreateTempFileWithData(span<const byte>);
void CreateTempFileWithData(const string&, vector<uint8_t>&); void CreateTempFileWithData(const string&, vector<uint8_t>&);
void DeleteTempFile(const string&); void DeleteTempFile(const string&);
// Call this at the end of every test case to make sure things are cleaned up
void CleanUpAllTempFiles();
string ReadTempFileToString(const string& filename); string ReadTempFileToString(const string& filename);

View File

@ -10,6 +10,8 @@ scsidump \- SCSI disk dumping tool for PiSCSI
[\fB\-r\fR] [\fB\-r\fR]
[\fB\-v\fR] [\fB\-v\fR]
[\fB\-p\fR] [\fB\-p\fR]
[\fB\-I\fR] ID[:LUN]
[\fB\-S\fR]
.SH DESCRIPTION .SH DESCRIPTION
.B scsidump .B scsidump
has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives.
@ -47,6 +49,12 @@ Enable verbose logging.
.TP .TP
.BR \-p\fI .BR \-p\fI
Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi.
.TP
.BR \-I\fI " "\fIID[:LUN]
Display INQUIRY data of ID[:LUN].
.TP
.BR \-S\fI
Scan SCSI bus for devices.
.SH EXAMPLES .SH EXAMPLES
Dump Mode: [SCSI Drive] ---> [PiSCSI host] Dump Mode: [SCSI Drive] ---> [PiSCSI host]

View File

@ -1,7 +1,5 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating !! ------ The native file is scsidump.1. Re-run 'make docs' after updating\n\n
scsidump(1) General Commands Manual scsidump(1) scsidump(1) General Commands Manual scsidump(1)
NAME NAME
@ -9,43 +7,72 @@ NAME
SYNOPSIS SYNOPSIS
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p] scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p]
[-I] ID[:LUN] [-S]
DESCRIPTION DESCRIPTION
scsidump has two modes of operation: dump and restore. These can be used with physical storage media, including hard drives and magneto optical drives. Dump mode can be used with read-only media such as CD/DVD drives. scsidump has two modes of operation: dump and restore. These can be
used with physical storage media, including hard drives and magneto op
tical drives. Dump mode can be used with read-only media such as CD/DVD
drives.
When operating in dump mode, scsidump will copy all data from a remote SCSI drive to an image on the local filesystem. If enabled, it will also generate a .properties file that can be used to more closely emulate the source drive. When operating in dump mode, scsidump will copy all data from a remote
SCSI drive to an image on the local filesystem. If enabled, it will
also generate a .properties file that can be used to more closely emu
late the source drive.
If you are operating in restore mode, scsidump will copy the data from a local binary image to a remote physical SCSI drive. The remote SCSI drive MUST be writable. If you are operating in restore mode, scsidump will copy the data from
a local binary image to a remote physical SCSI drive. The remote SCSI
drive MUST be writable.
NOTES NOTES
scsidump requires either a direct connection (one without transceivers) or a FULLSPEC PiSCSI/RaSCSI board. scsidump requires either a direct connection (one without transceivers)
or a FULLSPEC PiSCSI/RaSCSI board.
If the generated drive image is intended to be used with PiSCSI, the drive image should be moved to ~/images (or the location specified to the piscsi service). If the generated drive image is intended to be used with PiSCSI, the
drive image should be moved by the user to ~/images (or the location
specified to the piscsi service).
OPTIONS OPTIONS
-t ID[:LUN] -t ID[:LUN]
SCSI ID and optional LUN of the remote SCSI device. The remote SCSI device will be functioning as the "Target" device. SCSI ID and optional LUN of the remote SCSI device. The remote
SCSI device will be functioning as the "Target" device.
-i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI device will use ID 7. The PiSCSI host will be functioning as the "Initiator" device. -i BID SCSI ID of the PiSCSI device. If not specified, the PiSCSI de
vice will use ID 7. The PiSCSI host will be functioning as the
"Initiator" device.
-f FILE -f FILE
Path to the dump file. Path to the dump file.
-s BUFFER_SIZE -s BUFFER_SIZE
The transfer buffer size, specified in bytes. Default is 1 MiB. This is specified in bytes with a minimum value of 65536 (64 KiB). The transfer buffer size, specified in bytes. Default is 1 MiB.
This is specified in bytes with a minimum value of 65536 (64
KiB).
-r Run in restore mode. Defaults to dump mode if not specified. -r Run in restore mode. Defaults to dump mode if not specified.
-v Enable verbose logging. -v Enable verbose logging.
-p Generate a .properties file that is compatible with the PiSCSI web interface. The output filename will match the image filename with ".properties" appended. The generated file should be moved to ~/.config/piscsi. -p Generate a .properties file that is compatible with the PiSCSI
web interface. The output filename will match the image filename
with ".properties" appended. The generated file should be moved
to ~/.config/piscsi.
-I ID[:LUN]
Display INQUIRY data of ID[:LUN].
-S Scan SCSI bus for devices.
EXAMPLES EXAMPLES
Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an all data from SCSI ID 3 with block size 64 KiB, store it to the local filesystem as a drive image named outimage.hda, and generate the outimage.hda.properties file with the drive's INQUIRY Dump Mode: [SCSI Drive] ---> [PiSCSI host] Launch scsidump to dump an
information: all data from SCSI ID 3 with block size 64 KiB, store it to the local
filesystem as a drive image named outimage.hda, and generate the outim
age.hda.properties file with the drive's INQUIRY information:
scsidump -t 3 -f ./outimage.hda -s 65536 -p scsidump -t 3 -f ./outimage.hda -s 65536 -p
Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to restore/upload a drive image from the local file system to SCSI ID 0 with block size 1MiB: Restore Mode: [PiSCSI host] ---> [SCSI Drive] Launch scsidump to re
store/upload a drive image from the local file system to SCSI ID 0 with
block size 1MiB:
scsidump -r -t 0 -f ./outimage.hda -s 1048576 scsidump -r -t 0 -f ./outimage.hda -s 1048576
SEE ALSO SEE ALSO