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 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 "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "hal/systimer.h"
#include "controllers/controller_manager.h"
@ -44,11 +44,11 @@ void ScsiDump::CleanUp()
}
}
void ScsiDump::KillHandler(int)
void ScsiDump::TerminationHandler(int)
{
CleanUp();
exit(EXIT_SUCCESS);
// Process will terminate automatically
}
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)");
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"
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
<< " Default is 0.\n"
@ -67,6 +67,8 @@ bool ScsiDump::Banner(span<char *> args) const
<< " -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"
<< " -I Display INQUIRY data of ID[:LUN].\n"
<< " -S Scan SCSI bus for devices.\n"
<< flush;
return false;
@ -77,11 +79,13 @@ bool ScsiDump::Banner(span<char *> args) 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;
}
// Signal handler for cleaning up
struct sigaction termination_handler;
termination_handler.sa_handler = TerminationHandler;
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);
@ -95,7 +99,7 @@ void ScsiDump::ParseArguments(span<char *> args)
int buffer_size = DEFAULT_BUFFER_SIZE;
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) {
case 'i':
if (!GetAsUnsignedInt(optarg, initiator_id) || initiator_id > 7) {
@ -107,6 +111,10 @@ void ScsiDump::ParseArguments(span<char *> args)
filename = optarg;
break;
case 'I':
inquiry = true;
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");
@ -114,15 +122,15 @@ void ScsiDump::ParseArguments(span<char *> args)
break;
case 't': {
const string error = ProcessId(optarg, target_id, target_lun);
if (!error.empty()) {
case 'S':
scan_bus = true;
break;
case 't':
if (const string error = ProcessId(optarg, target_id, target_lun); !error.empty()) {
throw parser_exception(error);
}
if (target_lun == -1) {
target_lun = 0;
}
} break;
break;
case 'v':
set_level(level::debug);
@ -141,32 +149,44 @@ 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) {
throw parser_exception("Target ID and PiSCSI board ID must not be identical");
}
if (filename.empty()) {
throw parser_exception("Missing filename");
if (target_lun == -1) {
target_lun = 0;
}
if (scan_bus) {
inquiry = false;
}
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");
// Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow();
while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) {
while ((SysTimer::GetTimerLow() - now) < 3'000'000) {
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 phase_exception("Expected " + string(BUS::GetPhaseStrRaw(phase)) + " phase, actual phase is " +
string(BUS::GetPhaseStrRaw(bus->GetPhase())));
}
void ScsiDump::Selection() const
@ -189,7 +209,7 @@ void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
Selection();
WaitPhase(phase_t::command);
WaitForPhase(phase_t::command);
cdb[0] = static_cast<uint8_t>(cmd);
cdb[1] = static_cast<uint8_t>(static_cast<byte>(cdb[1]) | static_cast<byte>(target_lun << 5));
@ -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)) {
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)
{
WaitPhase(phase_t::datain);
WaitForPhase(phase_t::datain);
if (!bus->ReceiveHandShake(buffer.data(), length)) {
throw parser_exception("DATA IN failed");
throw phase_exception("DATA IN failed");
}
}
void ScsiDump::DataOut(int length)
{
WaitPhase(phase_t::dataout);
WaitForPhase(phase_t::dataout);
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
{
WaitPhase(phase_t::status);
WaitForPhase(phase_t::status);
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
{
WaitPhase(phase_t::msgin);
WaitForPhase(phase_t::msgin);
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
if (!bus->GetBSY()) {
throw parser_exception("SELECTION failed");
throw phase_exception("SELECTION failed");
}
}
@ -398,28 +418,49 @@ int ScsiDump::run(span<char *> args)
return EXIT_SUCCESS;
}
if (!Init()) {
cerr << "Error: Initializing. Are you root?" << endl;
try {
ParseArguments(args);
}
catch (const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
return EXIT_FAILURE;
}
// Probably not root
return EPERM;
if (getuid()) {
cerr << "Error: GPIO bus access requires root permissions. Are you running as root?" << endl;
return EXIT_FAILURE;
}
#ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE;
#endif
if (!Init()) {
cerr << "Error: Can't initialize bus" << endl;
return EXIT_FAILURE;
}
try {
ParseArguments(args);
if (scan_bus) {
ScanBus();
}
else if (inquiry) {
DisplayBoardId();
#ifndef USE_SEL_EVENT_ENABLE
cerr << "Error: No PiSCSI hardware support" << endl;
return EXIT_FAILURE;
#endif
inquiry_info_t inq_info;
DisplayInquiry(inq_info, false);
}
else {
DumpRestore();
}
}
catch (const phase_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();
@ -427,9 +468,92 @@ int ScsiDump::run(span<char *> args)
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()
{
const auto inq_info = GetDeviceInfo();
inquiry_info_t inq_info;
if (!GetDeviceInfo(inq_info)) {
return EXIT_FAILURE;
}
fstream fs;
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();
cout << divider_str << "\n";
cout << DIVIDER << "\n";
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
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
<< " KiB per second)\n";
cout << divider_str << "\n";
cout << DIVIDER << "\n";
if (properties_file && !restore) {
GeneratePropertiesFile(filename, inq_info);
inq_info.GeneratePropertiesFile(filename + ".properties");
}
return EXIT_SUCCESS;
}
ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
bool ScsiDump::GetDeviceInfo(inquiry_info_t& inq_info)
{
// Assert RST for 1 ms
bus->SetRST(true);
const timespec ts = {.tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus->SetRST(false);
DisplayBoardId();
cout << divider_str << "\n";
cout << "PiSCSI board ID: " << initiator_id << "\n";
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");
if (!DisplayInquiry(inq_info, true)) {
return false;
}
TestUnitReady();
@ -572,35 +666,28 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
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
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"
<< DIVIDER << "\n\n"
<< 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";
stringstream prop_stream;
ofstream prop_stream(property_file);
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\": \"" << inq_info.sector_size << "\"," << endl;
prop_stream << " \"vendor\": \"" << vendor << "\"," << endl;
prop_stream << " \"product\": \"" << product << "\"," << endl;
prop_stream << " \"revision\": \"" << revision << "\"," << endl;
prop_stream << " \"block_size\": \"" << sector_size << "\"" << endl;
prop_stream << "}" << endl;
FILE* fp = fopen(prop_filename.c_str(), "w");
if (fp) {
fputs(prop_stream.str().c_str(), fp);
} else {
spdlog::warn("Unable to open output file '" + prop_filename + "'");
return;
if (prop_stream.fail()) {
spdlog::warn("Unable to create properties file '" + property_file + "'");
}
fclose(fp);
}

View File

@ -10,45 +10,52 @@
#pragma once
#include "hal/bus.h"
#include "shared/scsi.h"
#include <memory>
#include <string>
#include <span>
#include <vector>
#include <unordered_map>
#include <stdexcept>
using namespace std;
class phase_exception : public runtime_error
{
using runtime_error::runtime_error;
};
class ScsiDump
{
static const int MINIMUM_BUFFER_SIZE = 1024 * 64;
static const int DEFAULT_BUFFER_SIZE = 1024 * 1024;
public:
ScsiDump() = default;
~ScsiDump() = default;
int run(const span<char *>);
struct inquiry_info_struct {
struct inquiry_info {
string vendor;
string product;
string revision;
uint32_t sector_size;
uint64_t capacity;
};
using inquiry_info_t = struct inquiry_info_struct;
protected:
// Protected for testability
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info);
void GeneratePropertiesFile(const string&) const;
};
using inquiry_info_t = struct inquiry_info;
private:
bool Banner(span<char *>) const;
bool Init() const;
void ParseArguments(span<char *>);
void DisplayBoardId() const;
void ScanBus();
bool DisplayInquiry(inquiry_info_t&, bool);
int DumpRestore();
inquiry_info_t GetDeviceInfo();
void WaitPhase(phase_t) const;
bool GetDeviceInfo(inquiry_info_t&);
void WaitForPhase(phase_t) const;
void Selection() const;
void Command(scsi_defs::scsi_command, vector<uint8_t>&) const;
void DataIn(int);
@ -65,7 +72,7 @@ class ScsiDump
void WaitForBusy() const;
static void CleanUp();
static void KillHandler(int);
static void TerminationHandler(int);
// A static instance is needed because of the signal handler
static inline unique_ptr<BUS> bus;
@ -80,9 +87,41 @@ class ScsiDump
string filename;
bool inquiry = false;
bool scan_bus = false;
bool restore = 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());
DeleteTempFile("/proc/device-tree/soc/ranges");
CleanUpAllTempFiles();
remove_all(test_data_temp_path);
}
TEST(GpiobusRaspberry, GetDat)

View File

@ -1,72 +1,66 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 akuker
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include <gtest/gtest.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 = {
// Basic test
auto filename = CreateTempFile(0);
ScsiDump::inquiry_info_t test_data = {
.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"
" \"vendor\": \"PISCSI\",\n"
" \"product\": \"TEST PRODUCT\",\n"
" \"revision\": \"REV1\",\n"
" \"block_size\": \"1000\",\n}"
" \"block_size\": \"1000\"\n}"
"\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
// Long string test
filename = CreateTempFile(0);
test_data = {.vendor = "01234567",
.product = "0123456789ABCDEF",
.revision = "0123",
.sector_size = UINT32_MAX,
.capacity = UINT64_MAX};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
test_data.GeneratePropertiesFile(filename);
expected_str = "{\n"
" \"vendor\": \"01234567\",\n"
" \"product\": \"0123456789ABCDEF\",\n"
" \"revision\": \"0123\",\n"
" \"block_size\": \"4294967295\",\n"
" \"block_size\": \"4294967295\"\n"
"}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
remove(filename);
// Empty data test
filename = CreateTempFile(0);
test_data = {.vendor = "", .product = "", .revision = "", .sector_size = 0, .capacity = 0};
TestableScsidump::PublicGeneratePropertiesFile("test", test_data);
test_data.GeneratePropertiesFile(filename);
expected_str = "{\n"
" \"vendor\": \"\",\n"
" \"product\": \"\",\n"
" \"revision\": \"\",\n"
" \"block_size\": \"0\",\n"
" \"block_size\": \"0\"\n"
"}\n";
EXPECT_EQ(ReadTempFileToString(prop_file_name), expected_str);
EXPECT_EQ(expected_str, ReadTempFileToString(filename));
remove(filename);
}

View File

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

View File

@ -35,8 +35,6 @@ path CreateTempFileWithData(span<const byte>);
void CreateTempFileWithData(const string&, vector<uint8_t>&);
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);

View File

@ -10,6 +10,8 @@ scsidump \- SCSI disk dumping tool for PiSCSI
[\fB\-r\fR]
[\fB\-v\fR]
[\fB\-p\fR]
[\fB\-I\fR] ID[:LUN]
[\fB\-S\fR]
.SH DESCRIPTION
.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.
@ -47,6 +49,12 @@ 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.
.TP
.BR \-I\fI " "\fIID[:LUN]
Display INQUIRY data of ID[:LUN].
.TP
.BR \-S\fI
Scan SCSI bus for devices.
.SH EXAMPLES
Dump Mode: [SCSI Drive] ---> [PiSCSI host]

View File

@ -1,51 +1,78 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating
scsidump(1) General Commands Manual scsidump(1)
!! ------ The native file is scsidump.1. Re-run 'make docs' after updating\n\n
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] [-p]
scsidump -t ID[:LUN] [-i BID] -f FILE [-s BUFFER_SIZE] [-r] [-v] [-p]
[-I] ID[:LUN] [-S]
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
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
-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
Path to the dump file.
-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.
-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
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:
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 outim
age.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:
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
SEE ALSO
@ -53,4 +80,4 @@ SEE ALSO
Full documentation is available at: <https://www.piscsi.com>
scsidump(1)
scsidump(1)