From d768349bd3cec2ac64adb16d19bdd5eaa2c6cf72 Mon Sep 17 00:00:00 2001 From: Uwe Seimet Date: Wed, 11 Dec 2024 11:22:59 +0100 Subject: [PATCH] Merge issue_113 --- cpp/initiator/initiator_executor.cpp | 11 +- cpp/initiator/initiator_executor.h | 10 +- cpp/initiator/initiator_util.cpp | 2 +- cpp/s2pdump/disk_executor.cpp | 60 +++++++ cpp/s2pdump/disk_executor.h | 31 ++++ cpp/s2pdump/s2pdump_core.cpp | 227 +++++++++++++++++++++------ cpp/s2pdump/s2pdump_core.h | 25 ++- cpp/s2pdump/s2pdump_executor.cpp | 91 +++-------- cpp/s2pdump/s2pdump_executor.h | 11 +- cpp/s2pdump/tape_executor.cpp | 118 ++++++++++++++ cpp/s2pdump/tape_executor.h | 43 +++++ cpp/s2pexec/s2pexec_core.cpp | 2 +- cpp/s2pexec/s2pexec_executor.cpp | 4 +- cpp/s2pexec/s2pexec_executor.h | 2 +- cpp/s2pproto/s2pproto_executor.cpp | 4 +- doc/s2pdump.1 | 16 +- 16 files changed, 508 insertions(+), 149 deletions(-) create mode 100644 cpp/s2pdump/disk_executor.cpp create mode 100644 cpp/s2pdump/disk_executor.h create mode 100644 cpp/s2pdump/tape_executor.cpp create mode 100644 cpp/s2pdump/tape_executor.h diff --git a/cpp/initiator/initiator_executor.cpp b/cpp/initiator/initiator_executor.cpp index 25608b1e..690b6bda 100644 --- a/cpp/initiator/initiator_executor.cpp +++ b/cpp/initiator/initiator_executor.cpp @@ -15,13 +15,14 @@ using namespace s2p_util; using namespace initiator_util; -int InitiatorExecutor::Execute(scsi_command cmd, span cdb, span buffer, int length, int timeout) +int InitiatorExecutor::Execute(scsi_command cmd, span cdb, span buffer, int length, int timeout, + bool log) { cdb[0] = static_cast(cmd); - return Execute(cdb, buffer, length, timeout); + return Execute(cdb, buffer, length, timeout, log); } -int InitiatorExecutor::Execute(span cdb, span buffer, int length, int timeout) +int InitiatorExecutor::Execute(span cdb, span buffer, int length, int timeout, bool log) { bus.Reset(); @@ -68,7 +69,9 @@ int InitiatorExecutor::Execute(span cdb, span buffer, int leng } if (static_cast(status) != status_code::intermediate) { - LogStatus(); + if (log) { + LogStatus(); + } return status; } } diff --git a/cpp/initiator/initiator_executor.h b/cpp/initiator/initiator_executor.h index 34aba895..9e8c2acb 100644 --- a/cpp/initiator/initiator_executor.h +++ b/cpp/initiator/initiator_executor.h @@ -33,15 +33,19 @@ class InitiatorExecutor void SetTarget(int, int, bool); - // Execute command with a default timeout of 3 s - int Execute(scsi_command, span, span, int, int = 3); - int Execute(span, span, int, int = 3); + int Execute(scsi_command, span, span, int, int, bool); + int Execute(span, span, int, int, bool); int GetByteCount() const { return byte_count; } + logger& GetLogger() + { + return initiator_logger; + } + private: bool Dispatch(span, span, int&); diff --git a/cpp/initiator/initiator_util.cpp b/cpp/initiator/initiator_util.cpp index 2a5299a1..dc698105 100644 --- a/cpp/initiator/initiator_util.cpp +++ b/cpp/initiator/initiator_util.cpp @@ -26,7 +26,7 @@ tuple initiator_util::GetSenseData(InitiatorExecutor &execu array cdb = { }; cdb[4] = static_cast(buf.size()); - if (executor.Execute(scsi_command::request_sense, cdb, buf, static_cast(buf.size()))) { + if (executor.Execute(scsi_command::request_sense, cdb, buf, static_cast(buf.size()), 1, true)) { error("Can't execute REQUEST SENSE"); return {sense_key {-1}, asc {-1}, -1}; } diff --git a/cpp/s2pdump/disk_executor.cpp b/cpp/s2pdump/disk_executor.cpp new file mode 100644 index 00000000..9107f386 --- /dev/null +++ b/cpp/s2pdump/disk_executor.cpp @@ -0,0 +1,60 @@ +//--------------------------------------------------------------------------- +// +// SCSI2Pi, SCSI device emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2023-2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "disk_executor.h" +#include "shared/memory_util.h" + +using namespace spdlog; +using namespace memory_util; + +pair DiskExecutor::ReadCapacity() +{ + vector buffer(14); + vector cdb(10); + + if (initiator_executor->Execute(scsi_command::read_capacity_10, cdb, buffer, 8, 1, true)) { + return {0, 0}; + } + + uint64_t capacity = GetInt32(buffer, 0); + + int sector_size_offset = 4; + + if (static_cast(capacity) == -1) { + cdb.resize(16); + // READ CAPACITY(16), not READ LONG(16) + cdb[1] = 0x10; + + if (initiator_executor->Execute(scsi_command::read_capacity_16_read_long_16, cdb, buffer, 14, 1, true)) { + return {0, 0}; + } + + capacity = GetInt64(buffer, 0); + + sector_size_offset = 8; + } + + return {capacity + 1, GetInt32(buffer, sector_size_offset)}; +} + +bool DiskExecutor::ReadWrite(span buffer, uint32_t bstart, uint32_t blength, int length, bool is_write) +{ + vector cdb(10); + SetInt32(cdb, 2, bstart); + SetInt16(cdb, 7, blength); + + return !initiator_executor->Execute(is_write ? scsi_command::write_10 : scsi_command::read_10, cdb, buffer, length, + 10, true); +} + +void DiskExecutor::SynchronizeCache() +{ + vector cdb(10); + + initiator_executor->Execute(scsi_command::synchronize_cache_10, cdb, { }, 0, 3, true); +} diff --git a/cpp/s2pdump/disk_executor.h b/cpp/s2pdump/disk_executor.h new file mode 100644 index 00000000..986e2e30 --- /dev/null +++ b/cpp/s2pdump/disk_executor.h @@ -0,0 +1,31 @@ +//--------------------------------------------------------------------------- +// +// SCSI2Pi, SCSI device emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2023-2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "s2pdump_executor.h" + +using namespace std; + +class DiskExecutor : public S2pDumpExecutor +{ + +public: + + DiskExecutor(Bus &bus, int id, logger &l) : S2pDumpExecutor(bus, id, l) + { + } + + pair ReadCapacity(); + bool ReadWrite(span, uint32_t, uint32_t, int, bool); + void SynchronizeCache(); + +private: + + unique_ptr s2pdump_executor; +}; diff --git a/cpp/s2pdump/s2pdump_core.cpp b/cpp/s2pdump/s2pdump_core.cpp index ac8d1152..90a52bd4 100644 --- a/cpp/s2pdump/s2pdump_core.cpp +++ b/cpp/s2pdump/s2pdump_core.cpp @@ -10,18 +10,21 @@ #include #include #include -#include #include #include #include #include #include "buses/bus_factory.h" +#include "disk_executor.h" +#include "tape_executor.h" #include "initiator/initiator_util.h" #include "shared/s2p_exceptions.h" +#include "shared/simh_util.h" using namespace filesystem; using namespace s2p_util; using namespace initiator_util; +using namespace simh_util; void S2pDump::CleanUp() const { @@ -42,7 +45,7 @@ void S2pDump::TerminationHandler(int) void S2pDump::Banner(bool header) const { if (header) { - cout << "SCSI Device Emulator and SCSI Tools SCSI2Pi (Drive Dump/Restore Utility)\n" + cout << "SCSI Device Emulator and SCSI Tools SCSI2Pi (Hard Drive/Tape Drive Dump/Restore Tool)\n" << "Version " << GetVersionString() << "\n" << "Copyright (C) 2023-2024 Uwe Seimet\n"; } @@ -57,15 +60,16 @@ void S2pDump::Banner(bool header) const << " --buffer-size/-b BUFFER_SIZE Transfer buffer size, at least " << MINIMUM_BUFFER_SIZE << " bytes," << " default is 1 MiB.\n" << " --log-level/-L LOG_LEVEL Log level (trace|debug|info|warning|\n" - << " error|critical|off), default is 'info'.\n" + << " error|critical|off), default is 'warning'.\n" << " --inquiry/-I Display INQUIRY data and (SCSI only)\n" << " device properties for property files.\n" << " --scsi-scan/-s Scan bus for SCSI devices.\n" << " --sasi-scan/-t Scan bus for SASI devices.\n" << " --sasi-capacity/-c CAPACITY SASI drive capacity in sectors.\n" << " --sasi-sector-size/-z SECTOR_SIZE SASI drive sector size (256|512|1024).\n" - << " --start-sector/-S START Start sector, default is 0.\n" - << " --sector-count/-C COUNT Sector count, default is the capacity.\n" + << " --start-sector/-S START Hard drive start sector, default is 0.\n" + << " --sector-count/-C COUNT Hard drive sector count,\n" + << " default is the capacity.\n" << " --all-luns/-a Check all LUNs during bus scan,\n" << " default is LUN 0 only.\n" << " --restore/-r Restore instead of dump.\n" @@ -80,7 +84,8 @@ bool S2pDump::Init(bool in_process) return false; } - executor = make_unique(*bus, initiator_id, *default_logger()); + disk_executor = make_unique(*bus, initiator_id, *initiator_logger); + tape_executor = make_unique(*bus, initiator_id, *initiator_logger); instance = this; // Signal handler for cleaning up @@ -357,7 +362,7 @@ void S2pDump::ScanBus() continue; } - auto luns = executor->ReportLuns(); + auto luns = disk_executor->ReportLuns(); // LUN 0 has already been dealt with luns.erase(0); @@ -374,13 +379,14 @@ bool S2pDump::DisplayInquiry(bool check_type) cout << DIVIDER << "\nChecking " << (sasi ? "SASI" : "SCSI") << " target ID:LUN " << target_id << ":" << target_lun << "\n" << flush; - executor->SetTarget(target_id, target_lun, sasi); + disk_executor->SetTarget(target_id, target_lun, sasi); + tape_executor->SetTarget(target_id, target_lun, sasi); // Clear potential UNIT ATTENTION status - executor->TestUnitReady(); + disk_executor->TestUnitReady(); vector buf(36); - if (!executor->Inquiry(buf)) { + if (!disk_executor->Inquiry(buf)) { return false; } @@ -440,8 +446,12 @@ bool S2pDump::DisplayScsiInquiry(span buf, bool check_type) cout << "SCSI-2"; break; + case 3: + cout << "SPC"; + break; + default: - cout << fmt::format("{:02x}", buf[3]); + cout << "SPC-" << buf[3] - 2; break; } cout << "\n"; @@ -452,9 +462,10 @@ bool S2pDump::DisplayScsiInquiry(span buf, bool check_type) if (check_type && scsi_device_info.type != static_cast(device_type::direct_access) && scsi_device_info.type != static_cast(device_type::cd_rom) - && scsi_device_info.type != static_cast(device_type::optical_memory)) { + && scsi_device_info.type != static_cast(device_type::optical_memory) + && scsi_device_info.type != static_cast(device_type::sequential_access)) { cerr << "Error: Invalid device type for SCSI dump/restore, supported types are DIRECT ACCESS," - << " CD-ROM/DVD/BD/DVD-RAM and OPTICAL MEMORY" << endl; + << " CD-ROM/DVD/BD/DVD-RAM, OPTICAL MEMORY and SEQUENTIAL access" << endl; return false; } @@ -485,11 +496,22 @@ string S2pDump::DumpRestore() return "Can't get device information"; } - fstream fs(filename, (restore ? ios::in : ios::out) | ios::binary); - if (fs.fail()) { + fstream file(filename, (restore ? ios::in : ios::out) | ios::binary); + if (file.fail()) { return "Can't open image file '" + filename + "': " + strerror(errno); } + if (!restore) { + status(filename).permissions(perms::group_read | perms::others_read); + } + + return + scsi_device_info.type == static_cast(device_type::sequential_access) ? + DumpRestoreTape(file) : DumpRestoreDisk(file); +} + +string S2pDump::DumpRestoreDisk(fstream &file) +{ const auto effective_size = CalculateEffectiveSize(); if (effective_size < 0) { return ""; @@ -499,7 +521,7 @@ string S2pDump::DumpRestore() return ""; } - cout << "Starting " << (restore ? "restore" : "dump") << "\n" + cout << "Starting " << (restore ? "restore from '" : "dump to '") << filename << "'\n" << " Start sector is " << start << "\n" << " Sector count is " << count << "\n" << " Buffer size is " << buffer.size() << " bytes\n\n" @@ -511,7 +533,7 @@ string S2pDump::DumpRestore() auto remaining = effective_size; - const auto start_time = chrono::high_resolution_clock::now(); + const auto start_time = high_resolution_clock::now(); while (remaining) { const auto byte_count = static_cast(min(static_cast(remaining), buffer.size())); @@ -524,13 +546,13 @@ string S2pDump::DumpRestore() sector_count = 256; } - debug("Remaining bytes: " + to_string(remaining)); - debug("Current sector: " + to_string(sector_offset)); - debug("Sector count: " + to_string(sector_count)); - debug("Data transfer size: " + to_string(sector_count * sector_size)); - debug("Image file chunk size: " + to_string(byte_count)); + initiator_logger->info("Remaining bytes: {}", remaining); + initiator_logger->info("Current sector: {}", sector_offset); + initiator_logger->info("Sector count: {}", sector_count); + initiator_logger->info("Data transfer size: {}", sector_count * sector_size); + initiator_logger->info("Image file chunk size: {}", byte_count); - if (const string &error = ReadWrite(fs, sector_offset, sector_count, sector_size, byte_count); !error.empty()) { + if (const string &error = ReadWriteDisk(file, sector_offset, sector_count, sector_size, byte_count); !error.empty()) { return error; } @@ -542,45 +564,56 @@ string S2pDump::DumpRestore() << "/" << effective_size << " bytes)\n" << flush; } - auto duration = chrono::duration_cast(chrono::high_resolution_clock::now() - - start_time).count(); - if (!duration) { - duration = 1; - } - if (restore) { // Ensure that if the target device is also a SCSI2Pi instance its image file becomes complete immediately - executor->SynchronizeCache(); + disk_executor->SynchronizeCache(); } - cout << DIVIDER - << "\nTransferred " << effective_size / 1024 / 1024 << " MiB (" << effective_size << " bytes)" - << "\nTotal time: " << duration << " seconds (" << duration / 60 << " minutes)" - << "\nAverage transfer rate: " << effective_size / duration << " bytes per second (" - << effective_size / 1024 / duration << " KiB per second)\n" - << DIVIDER << "\n" << flush; + DisplayStatistics(start_time, effective_size); return ""; } -string S2pDump::ReadWrite(fstream &fs, int sector_offset, uint32_t sector_count, int sector_size, int byte_count) +string S2pDump::DumpRestoreTape(fstream &file) +{ + cout << "Starting " << (restore ? "restore from '" : "dump to '") << filename << "'\n"; + + const auto start_time = high_resolution_clock::now(); + + if (tape_executor->Rewind()) { + return "Can't rewind tape"; + } + + try { + restore ? RestoreTape(file) : DumpTape(file); + } + catch (const io_exception &e) { + return e.what(); + } + + DisplayStatistics(start_time, byte_count); + + return ""; +} + +string S2pDump::ReadWriteDisk(fstream &file, int sector_offset, uint32_t sector_count, int sector_size, int byte_count) { if (restore) { - fs.read((char*)buffer.data(), byte_count); - if (fs.fail()) { + file.read((char*)buffer.data(), byte_count); + if (file.fail()) { return "Error reading from file '" + filename + "'"; } - if (!executor->ReadWrite(buffer, sector_offset, sector_count, sector_count * sector_size, true)) { + if (!disk_executor->ReadWrite(buffer, sector_offset, sector_count, sector_count * sector_size, true)) { return "Error/interrupted while writing to device"; } } else { - if (!executor->ReadWrite(buffer, sector_offset, sector_count, sector_count * sector_size, false)) { + if (!disk_executor->ReadWrite(buffer, sector_offset, sector_count, sector_count * sector_size, false)) { return "Error/interrupted while reading from device"; } - fs.write((const char*)buffer.data(), byte_count); - if (fs.fail()) { + file.write((const char*)buffer.data(), byte_count); + if (file.fail()) { return "Error writing to file '" + filename + "'"; } } @@ -588,6 +621,87 @@ string S2pDump::ReadWrite(fstream &fs, int sector_offset, uint32_t sector_count, return ""; } +void S2pDump::DumpTape(ostream &file) +{ + while (true) { + const int length = tape_executor->ReadWrite(buffer, 0); + if (length == TapeExecutor::NO_MORE_DATA) { + break; + } + + if (length == TapeExecutor::BAD_BLOCK) { + const array bad_data = { 0x00, 0x00, 0x00, 0x80 }; + file.write((const char*)bad_data.data(), bad_data.size()); + if (file.bad()) { + throw io_exception("Can't write SIMH bad data record"); + } + } + else if (length) { + if (!WriteGoodData(file, buffer, length)) { + throw io_exception("Can't write SIMH good data record"); + } + + byte_count += length; + } + else { + if (!WriteFilemark(file)) { + throw io_exception("Can't write SIMH tape mark"); + } + } + + initiator_logger->info("Byte count: {}", byte_count); + initiator_logger->info("Block count: {}", block_count); + initiator_logger->info("Filemark count: {}", filemark_count); + } +} + +void S2pDump::RestoreTape(istream &file) +{ + while (true) { + SimhMetaData meta_data; + if (!ReadMetaData(file, meta_data)) { + break; + } + + if (meta_data.cls == simh_class::reserved_marker + && meta_data.value == static_cast(simh_marker::end_of_medium)) { + break; + } + + // Tape mark + if (meta_data.cls == simh_class::tape_mark_good_data_record && !meta_data.value) { + initiator_logger->debug("Writing filemark"); + + if (tape_executor->WriteFilemark()) { + throw io_exception("Can't write filemark"); + } + } + else if ((meta_data.cls == simh_class::tape_mark_good_data_record + || meta_data.cls == simh_class::bad_data_record) && meta_data.value) { + initiator_logger->debug("Writing {} byte(s) block", meta_data.value); + + buffer.resize(meta_data.value); + + file.read((char*)buffer.data(), buffer.size()); + if (file.bad()) { + throw io_exception("Can't read SIMH data record"); + } + + if (tape_executor->ReadWrite(buffer, meta_data.value) != static_cast(meta_data.value)) { + throw io_exception("Can't write block"); + } + + file.seekg(META_DATA_SIZE, ios::cur); + + byte_count += buffer.size(); + } + + initiator_logger->info("Byte count: {}", byte_count); + initiator_logger->info("Block count: {}", block_count); + initiator_logger->info("Filemark count: {}", filemark_count); + } +} + long S2pDump::CalculateEffectiveSize() { const auto capacity = sasi ? sasi_capacity : scsi_device_info.capacity; @@ -646,10 +760,14 @@ bool S2pDump::GetDeviceInfo() } // Clear any pending error condition, e.g. a medium just having being inserted - executor->RequestSense(); + disk_executor->RequestSense(); + + if (scsi_device_info.type == static_cast(device_type::sequential_access)) { + return true; + } if (!sasi) { - const auto [capacity, sector_size] = executor->ReadCapacity(); + const auto [capacity, sector_size] = disk_executor->ReadCapacity(); if (!capacity || !sector_size) { trace("Can't read device capacity"); return false; @@ -683,7 +801,7 @@ bool S2pDump::GetDeviceInfo() void S2pDump::DisplayProperties(int id, int lun) const { // Clear any pending error condition, e.g. a medium just having being inserted - executor->RequestSense(); + disk_executor->RequestSense(); cout << "\nDevice properties for s2p properties file:\n"; @@ -717,7 +835,7 @@ void S2pDump::DisplayProperties(int id, int lun) const vector buf(255); - if (!executor->ModeSense6(buf)) { + if (!disk_executor->ModeSense6(buf)) { cout << "Warning: Can't get mode page data, medium may be missing or drive was not ready, try again\n" << flush; return; } @@ -748,3 +866,18 @@ void S2pDump::DisplayProperties(int id, int lun) const cout << flush; } + +void S2pDump::DisplayStatistics(time_point start_time, uint64_t count) +{ + auto duration = duration_cast(high_resolution_clock::now() - start_time).count(); + if (!duration) { + duration = 1; + } + + cout << DIVIDER + << "\nTransferred " << count / 1024 / 1024 << " MiB (" << count << " bytes)" + << "\nTotal time: " << duration << " seconds (" << duration / 60 << " minutes)" + << "\nAverage transfer rate: " << count / duration << " bytes per second (" + << count / 1024 / duration << " KiB per second)\n" + << DIVIDER << "\n" << flush; +} diff --git a/cpp/s2pdump/s2pdump_core.h b/cpp/s2pdump/s2pdump_core.h index 2a66c8f6..b68fd212 100644 --- a/cpp/s2pdump/s2pdump_core.h +++ b/cpp/s2pdump/s2pdump_core.h @@ -8,11 +8,15 @@ #pragma once +#include #include #include -#include "s2pdump_executor.h" +#include +#include "disk_executor.h" +#include "tape_executor.h" using namespace std; +using namespace chrono; class S2pDump { @@ -40,7 +44,7 @@ class S2pDump bool Init(bool); bool ParseArguments(span); void DisplayBoardId() const; - string ReadWrite(fstream&, int, uint32_t, int, int); + string ReadWriteDisk(fstream&, int, uint32_t, int, int); long CalculateEffectiveSize(); void ScanBus(); bool DisplayInquiry(bool); @@ -48,6 +52,8 @@ class S2pDump bool DisplaySasiInquiry(span, bool) const; void DisplayProperties(int, int) const; string DumpRestore(); + string DumpRestoreDisk(fstream&); + string DumpRestoreTape(fstream&); bool GetDeviceInfo(); void Reset() const; @@ -55,9 +61,15 @@ class S2pDump void CleanUp() const; static void TerminationHandler(int); + void DumpTape(ostream&); + void RestoreTape(istream&); + + static void DisplayStatistics(time_point, uint64_t); + unique_ptr bus; - unique_ptr executor; + unique_ptr disk_executor; + unique_ptr tape_executor; scsi_device_info_t scsi_device_info = { }; @@ -74,11 +86,16 @@ class S2pDump string filename; - string log_level = "info"; + shared_ptr initiator_logger = stdout_color_mt("initiator"); + string log_level = "warning"; int start = 0; int count = 0; + uint64_t byte_count = 0; + uint32_t block_count = 0; + uint32_t filemark_count = 0; + bool run_inquiry = false; bool run_bus_scan = false; diff --git a/cpp/s2pdump/s2pdump_executor.cpp b/cpp/s2pdump/s2pdump_executor.cpp index aadae407..d0d1dfc5 100644 --- a/cpp/s2pdump/s2pdump_executor.cpp +++ b/cpp/s2pdump/s2pdump_executor.cpp @@ -17,113 +17,60 @@ void S2pDumpExecutor::TestUnitReady() const { vector cdb(6); - initiator_executor->Execute(scsi_command::test_unit_ready, cdb, { }, 0); + initiator_executor->Execute(scsi_command::test_unit_ready, cdb, { }, 0, 1, false); } void S2pDumpExecutor::RequestSense() const { - array buf = { }; + array buf; array cdb = { }; cdb[4] = static_cast(buf.size()); - initiator_executor->Execute(scsi_command::request_sense, cdb, buf, static_cast(buf.size())); -} - -bool S2pDumpExecutor::Inquiry(span buffer) -{ - vector cdb(6); - cdb[4] = static_cast(buffer.size()); - - return !initiator_executor->Execute(scsi_command::inquiry, cdb, buffer, static_cast(buffer.size())); -} - -pair S2pDumpExecutor::ReadCapacity() -{ - vector buffer(14); - vector cdb(10); - - if (initiator_executor->Execute(scsi_command::read_capacity_10, cdb, buffer, 8)) { - return {0, 0}; - } - - uint64_t capacity = GetInt32(buffer, 0); - - int sector_size_offset = 4; - - if (static_cast(capacity) == -1) { - cdb.resize(16); - // READ CAPACITY(16), not READ LONG(16) - cdb[1] = 0x10; - if (initiator_executor->Execute(scsi_command::read_capacity_16_read_long_16, cdb, buffer, - static_cast(buffer.size()))) { - return {0, 0}; - } - - capacity = GetInt64(buffer, 0); - - sector_size_offset = 8; - } - - const uint32_t sector_size = GetInt32(buffer, sector_size_offset); - - return {capacity + 1, sector_size}; + initiator_executor->Execute(scsi_command::request_sense, cdb, buf, static_cast(buf.size()), 1, false); } -bool S2pDumpExecutor::ReadWrite(span buffer, uint32_t bstart, uint32_t blength, int length, bool is_write) +bool S2pDumpExecutor::Inquiry(span buf) { - vector cdb(10); - cdb[2] = static_cast(bstart >> 24); - cdb[3] = static_cast(bstart >> 16); - cdb[4] = static_cast(bstart >> 8); - cdb[5] = static_cast(bstart); - cdb[7] = static_cast(blength >> 8); - cdb[8] = static_cast(blength); + vector cdb(6); + cdb[4] = static_cast(buf.size()); - return !initiator_executor->Execute(is_write ? scsi_command::write_10 : scsi_command::read_10, cdb, buffer, length); + return !initiator_executor->Execute(scsi_command::inquiry, cdb, buf, static_cast(buf.size()), 1, false); } -bool S2pDumpExecutor::ModeSense6(span buffer) +bool S2pDumpExecutor::ModeSense6(span buf) { vector cdb(6); cdb[1] = 0x08; cdb[2] = 0x3f; - cdb[4] = static_cast(buffer.size()); - - return !initiator_executor->Execute(scsi_command::mode_sense_6, cdb, buffer, static_cast(buffer.size())); -} - -void S2pDumpExecutor::SynchronizeCache() -{ - vector cdb(10); + cdb[4] = static_cast(buf.size()); - initiator_executor->Execute(scsi_command::synchronize_cache_10, cdb, { }, 0); + return !initiator_executor->Execute(scsi_command::mode_sense_6, cdb, buf, static_cast(buf.size()), 3, false); } set S2pDumpExecutor::ReportLuns() { - vector buffer(512); + vector buf(512); vector cdb(12); - cdb[8] = static_cast(buffer.size() >> 8); - cdb[9] = static_cast(buffer.size()); + SetInt16(cdb, 8, buf.size()); // Assume 8 LUNs in case REPORT LUNS is not available - if (initiator_executor->Execute(scsi_command::report_luns, cdb, buffer, static_cast(buffer.size()))) { - trace("Target does not support REPORT LUNS"); + if (initiator_executor->Execute(scsi_command::report_luns, cdb, buf, static_cast(buf.size()), 1, false)) { + GetLogger().trace("Target does not support REPORT LUNS"); return {0, 1, 2, 3, 4, 5, 6, 7}; } - const auto lun_count = (static_cast(buffer[2]) << 8) | static_cast(buffer[3]) / 8; - trace("Target reported LUN count of " + to_string(lun_count)); + const auto lun_count = (static_cast(buf[2]) << 8) | static_cast(buf[3]) / 8; + GetLogger().trace("Target reported LUN count of " + to_string(lun_count)); set luns; int offset = 8; - for (size_t i = 0; i < lun_count && static_cast(offset) + 8 < buffer.size(); i++, offset += 8) { - const uint64_t lun = GetInt64(buffer, offset); + for (size_t i = 0; i < lun_count && static_cast(offset) + 8 < buf.size(); i++, offset += 8) { + const uint64_t lun = GetInt64(buf, offset); if (lun < 32) { luns.insert(static_cast(lun)); } else { - trace("Target reported invalid LUN " + to_string(lun)); + GetLogger().trace("Target reported invalid LUN " + to_string(lun)); } } diff --git a/cpp/s2pdump/s2pdump_executor.h b/cpp/s2pdump/s2pdump_executor.h index 78c2dd1e..f9de29de 100644 --- a/cpp/s2pdump/s2pdump_executor.h +++ b/cpp/s2pdump/s2pdump_executor.h @@ -21,15 +21,11 @@ class S2pDumpExecutor S2pDumpExecutor(Bus &bus, int id, logger &l) : initiator_executor(make_unique(bus, id, l)) { } - ~S2pDumpExecutor() = default; void TestUnitReady() const; void RequestSense() const; bool Inquiry(span); - pair ReadCapacity(); - bool ReadWrite(span, uint32_t, uint32_t, int, bool); bool ModeSense6(span); - void SynchronizeCache(); set ReportLuns(); void SetTarget(int id, int lun, bool sasi) @@ -37,7 +33,12 @@ class S2pDumpExecutor initiator_executor->SetTarget(id, lun, sasi); } -private: +protected: + + logger& GetLogger() + { + return initiator_executor->GetLogger(); + } unique_ptr initiator_executor; }; diff --git a/cpp/s2pdump/tape_executor.cpp b/cpp/s2pdump/tape_executor.cpp new file mode 100644 index 00000000..235d90a6 --- /dev/null +++ b/cpp/s2pdump/tape_executor.cpp @@ -0,0 +1,118 @@ +//--------------------------------------------------------------------------- +// +// SCSI2Pi, SCSI device emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "tape_executor.h" +#include +#include "shared/memory_util.h" +#include "shared/s2p_exceptions.h" + +using namespace spdlog; +using namespace memory_util; + +int TapeExecutor::Rewind() +{ + vector cdb(6); + + return initiator_executor->Execute(scsi_command::rewind, cdb, { }, 0, LONG_TIMEOUT, true); +} + +void TapeExecutor::SpaceBack() +{ + vector cdb(6); + cdb[1] = 0b000; + SetInt24(cdb, 2, -1); + + if (initiator_executor->Execute(scsi_command::space_6, cdb, { }, 0, SHORT_TIMEOUT, false)) { + throw io_exception("Can't space back"); + } +} + +int TapeExecutor::WriteFilemark() +{ + vector cdb(6); + SetInt24(cdb, 2, 1); + + return initiator_executor->Execute(scsi_command::write_filemarks_6, cdb, { }, 0, SHORT_TIMEOUT, true); +} + +int TapeExecutor::ReadWrite(span buf, int length) +{ + vector cdb(6); + + // Restore + if (length) { + SetInt24(cdb, 2, length); + + if (initiator_executor->Execute(scsi_command::write_6, cdb, buf, length, LONG_TIMEOUT, false)) { + throw io_exception(fmt::format("Can't write block with {} byte(s)", length)); + } + + return length; + } + + // Dump + bool has_error = false; + while (true) { + SetInt24(cdb, 2, default_length); + + if (!initiator_executor->Execute(scsi_command::read_6, cdb, buf, default_length, LONG_TIMEOUT, false)) { + GetLogger().debug("Read block with {} byte(s)", default_length); + return default_length; + } + + fill_n(cdb.begin(), cdb.size(), 0); + cdb[4] = 14; + const int status = initiator_executor->Execute(scsi_command::request_sense, cdb, buf, 14, SHORT_TIMEOUT, + false); + if (status && status != 0x02) { + throw io_exception(fmt::format("Unknown error status {}", status)); + } + + const sense_key sense_key = static_cast(buf[2] & 0x0f); + + if (sense_key == sense_key::medium_error) { + if (has_error) { + return BAD_BLOCK; + } + + has_error = true; + + SpaceBack(); + + continue; + } + + if (sense_key == sense_key::blank_check) { + GetLogger().debug("No more data"); + return NO_MORE_DATA; + } + + if (buf[2] & 0x80) { + GetLogger().debug("Encountered filemark"); + return 0; + } + + if (!(buf[0] & 0x80)) { + throw io_exception("INFORMATION field is not valid"); + } + + default_length -= GetInt32(buf, 3); + + SpaceBack(); + } +} + +void TapeExecutor::SetInt24(span buf, int offset, int value) +{ + assert(buf.size() > static_cast(offset) + 2); + + buf[offset] = static_cast(value >> 16); + buf[offset + 1] = static_cast(value >> 8); + buf[offset + 2] = static_cast(value); +} + diff --git a/cpp/s2pdump/tape_executor.h b/cpp/s2pdump/tape_executor.h new file mode 100644 index 00000000..14860c8d --- /dev/null +++ b/cpp/s2pdump/tape_executor.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI2Pi, SCSI device emulator and SCSI tools for the Raspberry Pi +// +// Copyright (C) 2024 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "s2pdump_executor.h" + +using namespace std; + +class TapeExecutor : public S2pDumpExecutor +{ + +public: + + TapeExecutor(Bus &bus, int id, logger &l) : S2pDumpExecutor(bus, id, l) + { + } + + int Rewind(); + int WriteFilemark(); + int ReadWrite(span, int); + + static const int NO_MORE_DATA = -1; + static const int BAD_BLOCK = -2; + +private: + + void SpaceBack(); + + static void SetInt24(span, int, int); + + unique_ptr s2pdump_executor; + + int default_length = 0xffffff; + + static const int SHORT_TIMEOUT = 3; + static const int LONG_TIMEOUT = 300; +}; diff --git a/cpp/s2pexec/s2pexec_core.cpp b/cpp/s2pexec/s2pexec_core.cpp index e9bc68d8..a0289804 100644 --- a/cpp/s2pexec/s2pexec_core.cpp +++ b/cpp/s2pexec/s2pexec_core.cpp @@ -436,7 +436,7 @@ tuple S2pExec::ExecuteCommand() } } - const int status = executor->ExecuteCommand(cdb, buffer, timeout); + const int status = executor->ExecuteCommand(cdb, buffer, timeout, true); if (status) { if (status != 0xff) { if (request_sense) { diff --git a/cpp/s2pexec/s2pexec_executor.cpp b/cpp/s2pexec/s2pexec_executor.cpp index 7b73cc68..2dadc187 100644 --- a/cpp/s2pexec/s2pexec_executor.cpp +++ b/cpp/s2pexec/s2pexec_executor.cpp @@ -8,7 +8,7 @@ #include "s2pexec_executor.h" -int S2pExecExecutor::ExecuteCommand(vector &cdb, vector &buffer, int timeout) +int S2pExecExecutor::ExecuteCommand(vector &cdb, vector &buffer, int timeout, bool log) { - return initiator_executor->Execute(cdb, buffer, buffer.size(), timeout); + return initiator_executor->Execute(cdb, buffer, buffer.size(), timeout, log); } diff --git a/cpp/s2pexec/s2pexec_executor.h b/cpp/s2pexec/s2pexec_executor.h index 0aaa8624..9c7dd01a 100644 --- a/cpp/s2pexec/s2pexec_executor.h +++ b/cpp/s2pexec/s2pexec_executor.h @@ -29,7 +29,7 @@ class S2pExecExecutor } ~S2pExecExecutor() = default; - int ExecuteCommand(vector&, vector&, int); + int ExecuteCommand(vector&, vector&, int, bool); tuple GetSenseData() const { diff --git a/cpp/s2pproto/s2pproto_executor.cpp b/cpp/s2pproto/s2pproto_executor.cpp index 49f0a9ed..4caf8094 100644 --- a/cpp/s2pproto/s2pproto_executor.cpp +++ b/cpp/s2pproto/s2pproto_executor.cpp @@ -61,14 +61,14 @@ string S2pProtoExecutor::Execute(const string &filename, protobuf_format input_f cdb[7] = static_cast(length >> 8); cdb[8] = static_cast(length); - if (initiator_executor->Execute(scsi_command::execute_operation, cdb, buffer, length)) { + if (initiator_executor->Execute(scsi_command::execute_operation, cdb, buffer, length, 3, true)) { return "Can't execute operation"; } cdb[7] = static_cast(buffer.size() >> 8); cdb[8] = static_cast(buffer.size()); - if (initiator_executor->Execute(scsi_command::receive_operation_results, cdb, buffer, buffer.size())) { + if (initiator_executor->Execute(scsi_command::receive_operation_results, cdb, buffer, buffer.size(), 3, true)) { return "Can't read operation result"; } diff --git a/doc/s2pdump.1 b/doc/s2pdump.1 index 4cfb7410..7df8cd0d 100644 --- a/doc/s2pdump.1 +++ b/doc/s2pdump.1 @@ -1,6 +1,6 @@ .TH s2pdump 1 .SH NAME -s2pdump \- SCSI/SASI drive dump/restore tool for SCSI2Pi +s2pdump \- SCSI/SASI hard drive/tape drive dump/restore tool .SH SYNOPSIS .B s2pdump [\fB\--scsi-id/-i\fR \fIID[:LUN]\fR] @@ -21,12 +21,14 @@ s2pdump \- SCSI/SASI drive dump/restore tool for SCSI2Pi .SH DESCRIPTION .B s2pdump -has two modes of operation: dump and restore. These can be used with physical storage media like 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 storage devices like hard drives, magneto optical drives or tape drives. Dump mode can also be used with read-only media such as CD/DVD drives. When operating in dump mode, s2pdump will copy all data from a remote drive to an image on the local filesystem. Optionally it generates a .properties file for the web UI. This file can be used to more closely emulate the source drive. +When used with a tape drive, the data are dumped in a SIMH-compatible format. In restore mode s2pdump copies the data from a local image to a remote physical drive. The remote drive must be writable. - + Restoring a tape drive expects a SIMH-compatible file. + .SH NOTES .B s2pdump @@ -55,7 +57,7 @@ The transfer buffer size in bytes. The default size is 1 MiB, the minimum size i Display INQUIRY data of ID[:LUN] and (SCSI only) device properties for use with property files, e.g. /etc/s2p.conf. .TP .BR --log-level/-L\fI " " \fILOG_LEVEL -Set the log level (trace, debug, info, warning, error, critical, off). The default log level is 'info'. +Set the log level (trace, debug, info, warning, error, critical, off). The default log level is 'warning'. .TP .BR --scsi-scan/-s\fI Scan the bus for SCSI devices and display their properties. @@ -64,10 +66,10 @@ Scan the bus for SCSI devices and display their properties. Scan the bus for SASI devices and display their properties. .TP .BR --start-sector/-S\fI " " \fISTART -Start sector for the dump/restore operation, the default is 0. +Only for hard drives, not for tape drives: start sector for the dump/restore operation, the default is 0. .TP .BR --sector-count/-C\fI " " \fICOUNT -Sector count for the dump/restore operation, the default is the drive capacity. +Only for hard drives, not for tape drives: sector count for the dump/restore operation, the default is the drive capacity. .TP .BR --sasi-capacity/-c\fI " "\fISASI_CAPACITY The capacity of the SASI hard drive in sectors. This parameter only has a meaning for SASI drives. @@ -87,7 +89,7 @@ Dump Mode: [SCSI/SASI Drive] ---> [SCSI2Pi host] Launch s2pdump to dump all data from the drive with SCSI ID 3 with a dump buffer size of 128 KiB. Store it on the local filesystem as a drive image named out_image.hds. s2pdump -i 3 -f out_image.hds -b 131072 -Restore Mode: [SCSI2Pi host] ---> [SCSI/SASI Drive] +Restore Mode: [SCSI2Pi host] ---> [SCSI/SASI hard drive or SCSI tape drive] Launch s2pdump to restore/upload a drive image from the local file system to SCSI ID 0 with a restore buffer size of 1 MiB: s2pdump -r -i 0 -f in_image.hds -b 1048576