From 8f45e4f49138265e675e7d6ac9a79e122063e33e Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Mon, 30 Oct 2023 11:34:07 +0100 Subject: [PATCH] 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 --- cpp/scsidump/scsidump_core.cpp | 301 ++++++++++++++++++---------- cpp/scsidump/scsidump_core.h | 65 ++++-- cpp/test/gpiobus_raspberry_test.cpp | 2 +- cpp/test/scsidump_test.cpp | 44 ++-- cpp/test/test_shared.cpp | 16 +- cpp/test/test_shared.h | 2 - doc/scsidump.1 | 8 + doc/scsidump_man_page.txt | 63 ++++-- 8 files changed, 325 insertions(+), 176 deletions(-) diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 971d79b6..05737842 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -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 args) const @@ -56,7 +56,7 @@ bool ScsiDump::Banner(span 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 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 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 args) int buffer_size = DEFAULT_BUFFER_SIZE; opterr = 0; - while ((opt = getopt(static_cast(args.size()), args.data(), "i:f:s:t:rvp")) != -1) { + while ((opt = getopt(static_cast(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 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 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 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(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& cdb) const Selection(); - WaitPhase(phase_t::command); + WaitForPhase(phase_t::command); cdb[0] = static_cast(cmd); cdb[1] = static_cast(static_cast(cdb[1]) | static_cast(target_lun << 5)); @@ -197,43 +217,43 @@ void ScsiDump::Command(scsi_command cmd, vector& cdb) const 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 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 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 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 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 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(buffer[0]); + if ((type & byte{0x1f}) == byte{0x1f}) { + // Requested LUN is not available + return false; + } + + array 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(buffer[1]) & byte{0x80}) == byte{0x80}) ? "Yes" : "No") << "\n"; + + if (check_type && type != static_cast(device_type::direct_access) && + type != static_cast(device_type::cd_rom) && type != static_cast(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(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 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(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); } diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index 64a82fa8..58feb419 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -10,45 +10,52 @@ #pragma once #include "hal/bus.h" -#include "shared/scsi.h" #include #include #include #include +#include +#include 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); - 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) const; bool Init() const; void ParseArguments(span); + 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&) 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; @@ -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 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" } + }; }; diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index 6b597013..5a94d670 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -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) diff --git a/cpp/test/scsidump_test.cpp b/cpp/test/scsidump_test.cpp index 473fc7ac..2e055157 100644 --- a/cpp/test/scsidump_test.cpp +++ b/cpp/test/scsidump_test.cpp @@ -1,72 +1,66 @@ - //--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022 akuker +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "mocks.h" +#include + #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 = { + // 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); } diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index 0e913460..91e8c4f6 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -121,24 +121,20 @@ void CreateTempFileWithData(const string& filename, vector& 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(); } diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index 97d59606..8c87f294 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -35,8 +35,6 @@ path CreateTempFileWithData(span); void CreateTempFileWithData(const string&, vector&); 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); diff --git a/doc/scsidump.1 b/doc/scsidump.1 index 0fe2cb52..4ec16147 100644 --- a/doc/scsidump.1 +++ b/doc/scsidump.1 @@ -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] diff --git a/doc/scsidump_man_page.txt b/doc/scsidump_man_page.txt index a39e64bb..70d63389 100644 --- a/doc/scsidump_man_page.txt +++ b/doc/scsidump_man_page.txt @@ -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: - scsidump(1) + scsidump(1)