diff --git a/cpp/Makefile b/cpp/Makefile index 1a5f95b0..9f6f2e06 100644 --- a/cpp/Makefile +++ b/cpp/Makefile @@ -7,8 +7,6 @@ CROSS_COMPILE = CXX = $(CROSS_COMPILE)g++ -AR = $(CROSS_COMPILE)ar -RANLIB = $(CROSS_COMPILE)ranlib ## DEBUG=1 : A Debug build includes the debugger symbols ## and disables compiler optimization. Typically, @@ -16,30 +14,21 @@ RANLIB = $(CROSS_COMPILE)ranlib DEBUG ?= 0 ifeq ($(DEBUG), 1) # Debug compiler flags - CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG - BUILD_TYPE = Debug + CXXFLAGS += -Og -g -Wall -Wextra -DDEBUG else # Release compiler flags CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG - BUILD_TYPE = Release endif ifeq ("$(shell uname -s)","Linux") # -Wno-psabi might not work on non-Linux platforms CXXFLAGS += -Wno-psabi endif -# Depending on the GCC version the compilation flags differ -GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10) - -CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP +CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP ## EXTRA_FLAGS : Can be used to pass special purpose flags CXXFLAGS += $(EXTRA_FLAGS) -ifeq "$(GCCVERSION10)" "1" - CXXFLAGS += -DFMT_HEADER_ONLY -endif - ## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type ## that you are using. The typical options are @@ -86,12 +75,12 @@ SRC_PROTOC = piscsi_interface.proto SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp SRC_PROTOBUF = \ - shared/protobuf_util.cpp \ - shared/protobuf_serializer.cpp + shared/protobuf_util.cpp SRC_SHARED = \ shared/piscsi_version.cpp \ - shared/piscsi_util.cpp + shared/piscsi_util.cpp \ + shared/network_util.cpp SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp') SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp') @@ -167,7 +156,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC) protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC) mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@ -$(OBJ_GENERATED) : $(SRC_GENERATED) +$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR) $(CXX) $(CXXFLAGS) -c $< -o $@ ## Build Targets: @@ -180,10 +169,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED) ## Note that you have to run 'make clean' before switching ## between coverage and non-coverage builds. .DEFAULT_GOAL := all -.PHONY: all ALL docs test coverage lcov -all: $(BIN_ALL) docs +.PHONY: all docs test coverage lcov -test: $(BINDIR)/$(PISCSI_TEST) +all: $(SRC_GENERATED) $(BIN_ALL) docs + +test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST) $(BINDIR)/$(PISCSI_TEST) coverage: CXXFLAGS += --coverage @@ -198,10 +188,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/ $(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED) -$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs +$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) +$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf $(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR) @@ -213,8 +203,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR) $(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR) $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP) -$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) - $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest +$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR) + $(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest # Phony rules for building individual utilities .PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP) diff --git a/cpp/controllers/abstract_controller.cpp b/cpp/controllers/abstract_controller.cpp index 17e91984..36242a6c 100644 --- a/cpp/controllers/abstract_controller.cpp +++ b/cpp/controllers/abstract_controller.cpp @@ -3,16 +3,22 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "devices/primary_device.h" #include "abstract_controller.h" +#include using namespace scsi_defs; +AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns) +{ + device_logger.SetIdAndLun(target_id, -1); +} + void AbstractController::AllocateCmd(size_t size) { if (size > ctrl.cmd.size()) { @@ -40,9 +46,8 @@ unordered_set> AbstractController::GetDevices() const { unordered_set> devices; - for (const auto& [id, lun] : luns) { - devices.insert(lun); - } + // "luns | views:values" is not supported by the bullseye compiler + ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } ); return devices; } @@ -56,100 +61,66 @@ void AbstractController::Reset() { SetPhase(phase_t::busfree); - ctrl.status = status::GOOD; - ctrl.message = 0x00; - ctrl.blocks = 0; - ctrl.next = 0; - ctrl.offset = 0; - ctrl.length = 0; + ctrl = {}; + + SetByteTransfer(false); // Reset all LUNs - for (const auto& [lun, device] : luns) { + for (const auto& [_, device] : luns) { device->Reset(); } + + GetBus().Reset(); } -void AbstractController::ProcessPhase() +void AbstractController::ProcessOnController(int id_data) { - switch (GetPhase()) { - case phase_t::busfree: - BusFree(); - break; + device_logger.SetIdAndLun(GetTargetId(), -1); - case phase_t::selection: - Selection(); - break; + const int initiator_id = ExtractInitiatorId(id_data); + if (initiator_id != UNKNOWN_INITIATOR_ID) { + LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id)); + } + else { + LogTrace("++++ Starting processing for unknown initiator ID"); + } - case phase_t::dataout: - DataOut(); - break; - - case phase_t::datain: - DataIn(); - break; - - case phase_t::command: - Command(); - break; - - case phase_t::status: - Status(); - break; - - case phase_t::msgout: - MsgOut(); - break; - - case phase_t::msgin: - MsgIn(); - break; - - default: - throw scsi_exception(sense_key::ABORTED_COMMAND); - break; + while (Process(initiator_id)) { + // Handle bus phases until the bus is free for the next command } } bool AbstractController::AddDevice(shared_ptr device) { - if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) { + const int lun = device->GetLun(); + + if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) { return false; } - luns[device->GetLun()] = device; - device->SetController(shared_from_this()); + luns[lun] = device; + device->SetController(this); return true; } -bool AbstractController::RemoveDevice(shared_ptr device) +bool AbstractController::RemoveDevice(PrimaryDevice& device) { - device->SetController(nullptr); + device.CleanUp(); - return luns.erase(device->GetLun()) == 1; + return luns.erase(device.GetLun()) == 1; } bool AbstractController::HasDeviceForLun(int lun) const { - return luns.find(lun) != luns.end(); + return luns.contains(lun); } int AbstractController::ExtractInitiatorId(int id_data) const { - int initiator_id = UNKNOWN_INITIATOR_ID; - - if (int tmp = id_data - (1 << target_id); tmp) { - initiator_id = 0; - for (int j = 0; j < 8; j++) { - tmp >>= 1; - if (tmp) { - initiator_id++; - } - else { - break; - } - } + if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) { + return static_cast(log2(id_data_without_target & -id_data_without_target)); } - return initiator_id; + return UNKNOWN_INITIATOR_ID; } diff --git a/cpp/controllers/abstract_controller.h b/cpp/controllers/abstract_controller.h index ae718bbf..820b98f3 100644 --- a/cpp/controllers/abstract_controller.h +++ b/cpp/controllers/abstract_controller.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Base class for device controllers // @@ -14,17 +14,19 @@ #include "shared/scsi.h" #include "hal/bus.h" #include "phase_handler.h" -#include "controller_manager.h" +#include "devices/device_logger.h" #include #include +#include #include #include +#include using namespace std; class PrimaryDevice; -class AbstractController : public PhaseHandler, public enable_shared_from_this +class AbstractController : public PhaseHandler { public: @@ -37,19 +39,19 @@ public: RESTART_PI }; - AbstractController(shared_ptr controller_manager, int target_id, int max_luns) - : controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {} + AbstractController(BUS&, int, int); ~AbstractController() override = default; - virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0; + virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status = scsi_defs::status::check_condition) = 0; virtual void Reset(); virtual int GetInitiatorId() const = 0; // Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback virtual int GetEffectiveLun() const = 0; - virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0; + void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; } + piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; } int GetTargetId() const { return target_id; } int GetMaxLuns() const { return max_luns; } @@ -58,52 +60,59 @@ public: unordered_set> GetDevices() const; shared_ptr GetDeviceForLun(int) const; bool AddDevice(shared_ptr); - bool RemoveDevice(shared_ptr); + bool RemoveDevice(PrimaryDevice&); bool HasDeviceForLun(int) const; - int ExtractInitiatorId(int) const; + void ProcessOnController(int); // TODO These should probably be extracted into a new TransferHandler class void AllocateBuffer(size_t); - vector& GetBuffer() { return ctrl.buffer; } - scsi_defs::status GetStatus() const { return ctrl.status; } + auto& GetBuffer() { return ctrl.buffer; } + auto GetStatus() const { return ctrl.status; } void SetStatus(scsi_defs::status s) { ctrl.status = s; } - uint32_t GetLength() const { return ctrl.length; } + auto GetLength() const { return ctrl.length; } void SetLength(uint32_t l) { ctrl.length = l; } - uint32_t GetBlocks() const { return ctrl.blocks; } + bool HasBlocks() const { return ctrl.blocks; } void SetBlocks(uint32_t b) { ctrl.blocks = b; } void DecrementBlocks() { --ctrl.blocks; } - uint64_t GetNext() const { return ctrl.next; } + auto GetNext() const { return ctrl.next; } void SetNext(uint64_t n) { ctrl.next = n; } void IncrementNext() { ++ctrl.next; } int GetMessage() const { return ctrl.message; } void SetMessage(int m) { ctrl.message = m; } - vector& GetCmd() { return ctrl.cmd; } - int GetCmd(int index) const { return ctrl.cmd[index]; } + auto GetCmd() const { return ctrl.cmd; } + int GetCmdByte(int index) const { return ctrl.cmd[index]; } bool IsByteTransfer() const { return is_byte_transfer; } void SetByteTransfer(bool); - uint32_t GetBytesToTransfer() const { return bytes_to_transfer; } + auto GetBytesToTransfer() const { return bytes_to_transfer; } void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; } protected: - shared_ptr GetControllerManager() const { return controller_manager.lock(); } - inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); } + BUS& GetBus() const { return bus; } - scsi_defs::scsi_command GetOpcode() const { return static_cast(ctrl.cmd[0]); } + auto GetOpcode() const { return static_cast(ctrl.cmd[0]); } int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; } - void ProcessPhase(); - void AllocateCmd(size_t); + void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; } + // TODO These should probably be extracted into a new TransferHandler class bool HasValidLength() const { return ctrl.length != 0; } int GetOffset() const { return ctrl.offset; } void ResetOffset() { ctrl.offset = 0; } void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; } + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } + private: + int ExtractInitiatorId(int) const; + using ctrl_t = struct _ctrl_t { // Command data, dynamically resized if required vector cmd = vector(16); @@ -121,7 +130,9 @@ private: ctrl_t ctrl = {}; - weak_ptr controller_manager; + BUS& bus; + + DeviceLogger device_logger; // Logical units of this controller mapped to their LUN numbers unordered_map> luns; @@ -132,4 +143,6 @@ private: bool is_byte_transfer = false; uint32_t bytes_to_transfer = 0; + + piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE; }; diff --git a/cpp/controllers/controller_manager.cpp b/cpp/controllers/controller_manager.cpp index 9f877cdb..d7d2a540 100644 --- a/cpp/controllers/controller_manager.cpp +++ b/cpp/controllers/controller_manager.cpp @@ -3,29 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "devices/device_factory.h" #include "devices/primary_device.h" #include "scsi_controller.h" #include "controller_manager.h" using namespace std; -bool ControllerManager::AttachToScsiController(int id, shared_ptr device) +shared_ptr ControllerManager::CreateScsiController(BUS& bus, int id) const { - auto controller = FindController(id); - if (controller != nullptr) { + return make_shared(bus, id); +} + +bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr device) +{ + if (auto controller = FindController(id); controller != nullptr) { + if (controller->HasDeviceForLun(device->GetLun())) { + return false; + } + return controller->AddDevice(device); } - // If there is no LUN yet the first LUN must be LUN 0 - if (device->GetLun() == 0) { - controller = make_shared(shared_from_this(), id); - - if (controller->AddDevice(device)) { + // If this is LUN 0 create a new controller + if (!device->GetLun()) { + if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) { controllers[id] = controller; return true; @@ -35,20 +40,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr return false; } -bool ControllerManager::DeleteController(shared_ptr controller) +bool ControllerManager::DeleteController(const AbstractController& controller) { - return controllers.erase(controller->GetTargetId()) == 1; -} - -shared_ptr ControllerManager::IdentifyController(int data) const -{ - for (const auto& [id, controller] : controllers) { - if (data & (1 << controller->GetTargetId())) { - return controller; - } + for (const auto& device : controller.GetDevices()) { + device->CleanUp(); } - return nullptr; + return controllers.erase(controller.GetTargetId()) == 1; +} + +void ControllerManager::DeleteAllControllers() +{ + unordered_set> values; + ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } ); + + for (const auto& controller : values) { + DeleteController(*controller); + } + + assert(controllers.empty()); +} + +AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const +{ + if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } ); + it != controllers.end()) { + (*it).second->ProcessOnController(id_data); + + return (*it).second->GetShutdownMode(); + } + + return AbstractController::piscsi_shutdown_mode::NONE; } shared_ptr ControllerManager::FindController(int target_id) const @@ -57,11 +79,15 @@ shared_ptr ControllerManager::FindController(int target_id) return it == controllers.end() ? nullptr : it->second; } +bool ControllerManager::HasController(int target_id) const { + return controllers.contains(target_id); +} + unordered_set> ControllerManager::GetAllDevices() const { unordered_set> devices; - for (const auto& [id, controller] : controllers) { + for (const auto& [_, controller] : controllers) { const auto& d = controller->GetDevices(); devices.insert(d.begin(), d.end()); } @@ -69,12 +95,12 @@ unordered_set> ControllerManager::GetAllDevices() cons return devices; } -void ControllerManager::DeleteAllControllers() +bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const { - controllers.clear(); + return GetDeviceForIdAndLun(id, lun) != nullptr; } -shared_ptr ControllerManager::GetDeviceByIdAndLun(int id, int lun) const +shared_ptr ControllerManager::GetDeviceForIdAndLun(int id, int lun) const { if (const auto& controller = FindController(id); controller != nullptr) { return controller->GetDeviceForLun(lun); diff --git a/cpp/controllers/controller_manager.h b/cpp/controllers/controller_manager.h index dd145e07..43f6177c 100644 --- a/cpp/controllers/controller_manager.h +++ b/cpp/controllers/controller_manager.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Keeps track of and manages the controllers // @@ -11,36 +11,41 @@ #pragma once +#include "hal/bus.h" +#include "controllers/abstract_controller.h" #include #include #include -#include "hal/bus.h" using namespace std; -class AbstractController; +class ScsiController; class PrimaryDevice; -class ControllerManager : public enable_shared_from_this +class ControllerManager { - BUS& bus; - - unordered_map> controllers; - public: - explicit ControllerManager(BUS& bus) : bus(bus) {} + ControllerManager() = default; ~ControllerManager() = default; - // Maximum number of controller devices - static const int DEVICE_MAX = 8; - - inline BUS& GetBus() const { return bus; } - bool AttachToScsiController(int, shared_ptr); - bool DeleteController(shared_ptr); - shared_ptr IdentifyController(int) const; - shared_ptr FindController(int) const; - unordered_set> GetAllDevices() const; + bool AttachToController(BUS&, int, shared_ptr); + bool DeleteController(const AbstractController&); void DeleteAllControllers(); - shared_ptr GetDeviceByIdAndLun(int, int) const; + AbstractController::piscsi_shutdown_mode ProcessOnController(int) const; + shared_ptr FindController(int) const; + bool HasController(int) const; + unordered_set> GetAllDevices() const; + bool HasDeviceForIdAndLun(int, int) const; + shared_ptr GetDeviceForIdAndLun(int, int) const; + + static int GetScsiIdMax() { return 8; } + static int GetScsiLunMax() { return 32; } + +private: + + shared_ptr CreateScsiController(BUS&, int) const; + + // Controllers mapped to their device IDs + unordered_map> controllers; }; diff --git a/cpp/controllers/phase_handler.h b/cpp/controllers/phase_handler.h index a50adf39..a7c52035 100644 --- a/cpp/controllers/phase_handler.h +++ b/cpp/controllers/phase_handler.h @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once #include "shared/scsi.h" +#include "shared/piscsi_exceptions.h" +#include +#include - using namespace scsi_defs; +using namespace scsi_defs; class PhaseHandler { @@ -31,7 +34,7 @@ public: virtual void MsgIn() = 0; virtual void MsgOut() = 0; - virtual phase_t Process(int) = 0; + virtual bool Process(int) = 0; protected: @@ -45,4 +48,27 @@ protected: bool IsDataOut() const { return phase == phase_t::dataout; } bool IsMsgIn() const { return phase == phase_t::msgin; } bool IsMsgOut() const { return phase == phase_t::msgout; } + + void ProcessPhase() const + { + try { + phase_executors.at(phase)(); + } + catch(const out_of_range&) { + throw scsi_exception(sense_key::aborted_command); + } + } + +private: + + const unordered_map> phase_executors = { + { phase_t::busfree, [this] () { BusFree(); } }, + { phase_t::selection, [this] () { Selection(); } }, + { phase_t::dataout, [this] () { DataOut(); } }, + { phase_t::datain, [this] () { DataIn(); } }, + { phase_t::command, [this] () { Command(); } }, + { phase_t::status, [this] () { Status(); } }, + { phase_t::msgout, [this] () { MsgOut(); } }, + { phase_t::msgin, [this] () { MsgIn(); } }, + }; }; diff --git a/cpp/controllers/scsi_controller.cpp b/cpp/controllers/scsi_controller.cpp index 20aaa136..e6acca41 100644 --- a/cpp/controllers/scsi_controller.cpp +++ b/cpp/controllers/scsi_controller.cpp @@ -6,7 +6,7 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. @@ -16,7 +16,9 @@ #include "shared/piscsi_exceptions.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "devices/interfaces/byte_writer.h" +#include "controllers/controller_manager.h" +#include "devices/scsi_host_bridge.h" +#include "devices/scsi_daynaport.h" #include "devices/mode_page_device.h" #include "devices/disk.h" #include "scsi_controller.h" @@ -28,11 +30,8 @@ using namespace scsi_defs; -ScsiController::ScsiController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, LUN_MAX) +ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax()) { - logger.SetIdAndLun(target_id, -1); - // The initial buffer size will default to either the default buffer size OR // the size of an Ethernet message, whichever is larger. AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN)); @@ -44,26 +43,21 @@ void ScsiController::Reset() execstart = 0; identified_lun = -1; + initiator_id = UNKNOWN_INITIATOR_ID; - scsi.atnmsg = false; - scsi.msc = 0; - scsi.msb = {}; - - SetByteTransfer(false); + scsi = {}; } -phase_t ScsiController::Process(int id) +bool ScsiController::Process(int id) { GetBus().Acquire(); if (GetBus().GetRST()) { - logger.Warn("RESET signal received!"); + LogWarn("RESET signal received!"); Reset(); - GetBus().Reset(); - - return GetPhase(); + return false; } initiator_id = id; @@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id) ProcessPhase(); } catch(const scsi_exception&) { - // Any exception should have been handled during the phase processing - logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); + LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase"); Reset(); - GetBus().Reset(); BusFree(); } - return GetPhase(); + return !IsBusFree(); } void ScsiController::BusFree() { if (!IsBusFree()) { - logger.Trace("Bus free phase"); - + LogTrace("Bus Free phase"); SetPhase(phase_t::busfree); GetBus().SetREQ(false); @@ -98,7 +89,7 @@ void ScsiController::BusFree() GetBus().SetBSY(false); // Initialize status and message - SetStatus(status::GOOD); + SetStatus(status::good); SetMessage(0x00); // Initialize ATN message reception status @@ -108,39 +99,6 @@ void ScsiController::BusFree() SetByteTransfer(false); - if (shutdown_mode != piscsi_shutdown_mode::NONE) { - // Prepare the shutdown by flushing all caches - for (const auto& device : GetControllerManager()->GetAllDevices()) { - device->FlushCache(); - } - } - - // When the bus is free PiSCSI or the Pi may be shut down. - // This code has to be executed in the bus free phase and thus has to be located in the controller. - switch(shutdown_mode) { - case piscsi_shutdown_mode::STOP_PISCSI: - logger.Info("PiSCSI shutdown requested"); - exit(EXIT_SUCCESS); - break; - - case piscsi_shutdown_mode::STOP_PI: - logger.Info("Raspberry Pi shutdown requested"); - if (system("init 0") == -1) { - logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno))); - } - break; - - case piscsi_shutdown_mode::RESTART_PI: - logger.Info("Raspberry Pi restart requested"); - if (system("init 6") == -1) { - logger.Error("Raspberry Pi restart failed: " + string(strerror(errno))); - } - break; - - default: - break; - } - return; } @@ -153,18 +111,7 @@ void ScsiController::BusFree() void ScsiController::Selection() { if (!IsSelection()) { - // A different device controller was selected - if (int id = 1 << GetTargetId(); (static_cast(GetBus().GetDAT()) & id) == 0) { - return; - } - - // Abort if there is no LUN for this controller - if (!GetLunCount()) { - return; - } - - logger.Trace("Selection phase"); - + LogTrace("Selection phase"); SetPhase(phase_t::selection); // Raise BSY and respond @@ -174,6 +121,8 @@ void ScsiController::Selection() // Selection completed if (!GetBus().GetSEL() && GetBus().GetBSY()) { + LogTrace("Selection completed"); + // Message out phase if ATN=1, otherwise command phase if (GetBus().GetATN()) { MsgOut(); @@ -186,8 +135,7 @@ void ScsiController::Selection() void ScsiController::Command() { if (!IsCommand()) { - logger.Trace("Command phase"); - + LogTrace("Command phase"); SetPhase(phase_t::command); GetBus().SetMSG(false); @@ -198,9 +146,9 @@ void ScsiController::Command() if (actual_count == 0) { stringstream s; s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Trace(s.str()); + LogTrace(s.str()); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + Error(sense_key::illegal_request, asc::invalid_command_operation_code); return; } @@ -210,16 +158,16 @@ void ScsiController::Command() if (actual_count != command_byte_count) { stringstream s; s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0]; - logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received" + to_string(actual_count) + " byte(s)"); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } // Command data transfer AllocateCmd(command_byte_count); for (int i = 0; i < command_byte_count; i++) { - GetCmd()[i] = GetBuffer()[i]; + SetCmdByte(i, GetBuffer()[i]); } SetLength(0); @@ -234,9 +182,9 @@ void ScsiController::Execute() s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $" << setfill('0') << hex; for (int i = 0; i < BUS::GetCommandByteCount(static_cast(GetOpcode())); i++) { - s << setw(2) << GetCmd(i); + s << setw(2) << GetCmdByte(i); } - logger.Debug(s.str()); + LogDebug(s.str()); // Initialization for data transfer ResetOffset(); @@ -245,15 +193,15 @@ void ScsiController::Execute() // Discard pending sense data from the previous command if the current command is not REQUEST SENSE if (GetOpcode() != scsi_command::eCmdRequestSense) { - SetStatus(status::GOOD); + SetStatus(status::good); } int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) { - logger.Trace("Invalid LUN " + to_string(lun)); + LogTrace("Invalid LUN " + to_string(lun)); - Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + Error(sense_key::illegal_request, asc::invalid_lun); return; } @@ -265,7 +213,7 @@ void ScsiController::Execute() // SCSI-2 4.4.3 Incorrect logical unit handling if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) { - logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); + LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported"); GetBuffer().data()[0] = 0x7f; @@ -279,7 +227,7 @@ void ScsiController::Execute() device->SetStatusCode(0); } - if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) { + if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) { try { device->Dispatch(GetOpcode()); } @@ -288,7 +236,7 @@ void ScsiController::Execute() } } else { - Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); } } @@ -304,9 +252,8 @@ void ScsiController::Status() } stringstream s; - s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); - logger.Trace(s.str()); - + s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast(GetStatus()); + LogTrace(s.str()); SetPhase(phase_t::status); // Signal line operated by the target @@ -329,8 +276,7 @@ void ScsiController::Status() void ScsiController::MsgIn() { if (!IsMsgIn()) { - logger.Trace("Message In phase"); - + LogTrace("Message In phase"); SetPhase(phase_t::msgin); GetBus().SetMSG(true); @@ -347,8 +293,6 @@ void ScsiController::MsgIn() void ScsiController::MsgOut() { if (!IsMsgOut()) { - logger.Trace("Message Out phase"); - // process the IDENTIFY message if (IsSelection()) { scsi.atnmsg = true; @@ -356,6 +300,7 @@ void ScsiController::MsgOut() scsi.msb = {}; } + LogTrace("Message Out phase"); SetPhase(phase_t::msgout); GetBus().SetMSG(true); @@ -387,8 +332,7 @@ void ScsiController::DataIn() return; } - logger.Trace("Entering Data In phase"); - + LogTrace("Data In phase"); SetPhase(phase_t::datain); GetBus().SetMSG(false); @@ -417,11 +361,9 @@ void ScsiController::DataOut() return; } - logger.Trace("Data Out phase"); - + LogTrace("Data Out phase"); SetPhase(phase_t::dataout); - // Signal line operated by the target GetBus().SetMSG(false); GetBus().SetCD(false); GetBus().SetIO(false); @@ -441,7 +383,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) // Reset check if (GetBus().GetRST()) { Reset(); - GetBus().Reset(); return; } @@ -453,9 +394,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) } int lun = GetEffectiveLun(); - if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) { + if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) { if (!HasDeviceForLun(0)) { - logger.Error("No LUN 0"); + LogError("No LUN 0"); SetStatus(status); SetMessage(0x00); @@ -468,11 +409,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) lun = 0; } - if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) { + if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) { stringstream s; s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast(sense_key) << ", ASC $" << static_cast(asc); - logger.Debug(s.str()); + LogDebug(s.str()); // Set Sense Key and ASC for a subsequent REQUEST SENSE GetDeviceForLun(lun)->SetStatusCode((static_cast(sense_key) << 16) | (static_cast(asc) << 8)); @@ -481,7 +422,7 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) SetStatus(status); SetMessage(0x00); - logger.Trace("Error (to status phase)"); + LogTrace("Error (to status phase)"); Status(); } @@ -492,7 +433,7 @@ void ScsiController::Send() assert(GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); + LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength())); // The delay should be taken from the respective LUN, but as there are no Daynaport drivers for // LUNs other than 0 this work-around works. @@ -500,7 +441,7 @@ void ScsiController::Send() HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0); len != static_cast(GetLength())) { // If you cannot send all, move to status phase - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -510,33 +451,30 @@ void ScsiController::Send() } DecrementBlocks(); - bool result = true; // Processing after data collection (read/data-in only) - if (IsDataIn() && GetBlocks() != 0) { + if (IsDataIn() && HasBlocks()) { // set next buffer (set offset, length) - result = XferIn(GetBuffer()); - logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks())); + if (!XferIn(GetBuffer())) { + // If result FALSE, move to status phase + Error(sense_key::aborted_command); + return; + } + + LogTrace("Processing after data collection"); } - // If result FALSE, move to status phase - if (!result) { - Error(sense_key::ABORTED_COMMAND); - return; - } - - // Continue sending if block !=0 - if (GetBlocks() != 0){ - logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks())); + // Continue sending if blocks != 0 + if (HasBlocks()) { + LogTrace("Continuing to send"); assert(HasValidLength()); assert(GetOffset() == 0); return; } // Move to next phase - logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { - // Message in phase case phase_t::msgin: // Completed sending response to extended message of IDENTIFY message if (scsi.atnmsg) { @@ -551,13 +489,11 @@ void ScsiController::Send() } break; - // Data-in Phase case phase_t::datain: // status phase Status(); break; - // status phase case phase_t::status: // Message in phase SetLength(1); @@ -578,13 +514,13 @@ void ScsiController::Receive() assert(!GetBus().GetIO()); if (HasValidLength()) { - logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); + LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)"); // If not able to receive all, move to status phase if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) { - logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received " + to_string(len)); - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } } @@ -603,10 +539,10 @@ void ScsiController::Receive() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: - if (GetBlocks() == 0) { + if (!HasBlocks()) { // End with this buffer result = XferOut(false); } else { @@ -633,12 +569,12 @@ void ScsiController::Receive() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } - // Continue to receive if block != 0 - if (GetBlocks() != 0) { + // Continue to receive if blocks != 0 + if (HasBlocks()) { assert(HasValidLength()); assert(GetOffset() == 0); return; @@ -692,7 +628,7 @@ void ScsiController::ReceiveBytes() bool result = true; // Processing after receiving data (by phase) - logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); + LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase()))); switch (GetPhase()) { case phase_t::dataout: result = XferOut(false); @@ -716,7 +652,7 @@ void ScsiController::ReceiveBytes() // If result FALSE, move to status phase if (!result) { - Error(sense_key::ABORTED_COMMAND); + Error(sense_key::aborted_command); return; } @@ -752,7 +688,7 @@ bool ScsiController::XferOut(bool cont) SetByteTransfer(false); auto device = GetDeviceForLun(GetEffectiveLun()); - return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false; + return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false; } void ScsiController::DataOutNonBlockOriented() @@ -760,7 +696,6 @@ void ScsiController::DataOutNonBlockOriented() assert(IsDataOut()); switch (GetOpcode()) { - // TODO Check why these cases are needed case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: @@ -777,7 +712,7 @@ void ScsiController::DataOutNonBlockOriented() device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset()); } else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } break; @@ -790,7 +725,7 @@ void ScsiController::DataOutNonBlockOriented() stringstream s; s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Warn(s.str()); + LogWarn(s.str()); break; } } @@ -807,9 +742,9 @@ bool ScsiController::XferIn(vector& buf) stringstream s; s << "Command: $" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()); - logger.Trace(s.str()); + LogTrace(s.str()); - int lun = GetEffectiveLun(); + const int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun)) { return false; } @@ -821,7 +756,7 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead16: // Read from disk try { - SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext())); + SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(buf, GetNext())); } catch(const scsi_exception&) { // If there is an error, go to the status phase @@ -878,13 +813,23 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdWrite6: case scsi_command::eCmdWrite10: case scsi_command::eCmdWrite16: - // TODO Verify has to verify, not to write + // TODO Verify has to verify, not to write, see https://github.com/PiSCSI/piscsi/issues/807 case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: { - // Special case for SCBR and SCDP - if (auto byte_writer = dynamic_pointer_cast(device); byte_writer) { - if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) { + // TODO Get rid of this special case for SCBR + if (auto bridge = dynamic_pointer_cast(device); bridge) { + if (!bridge->ReadWrite(GetCmd(), GetBuffer())) { + return false; + } + + ResetOffset(); + break; + } + + // TODO Get rid of this special case for SCDP + if (auto daynaport = dynamic_pointer_cast(device); daynaport) { + if (!daynaport->Write(GetCmd(), GetBuffer())) { return false; } @@ -898,7 +843,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) } try { - disk->Write(GetCmd(), GetBuffer(), GetNext() - 1); + disk->Write(GetBuffer(), GetNext() - 1); } catch(const scsi_exception& e) { Error(e.get_sense_key(), e.get_asc()); @@ -918,14 +863,14 @@ bool ScsiController::XferOutBlockOriented(bool cont) } case scsi_command::eCmdSetMcastAddr: - logger.Trace("Done with DaynaPort Set Multicast Address"); + LogTrace("Done with DaynaPort Set Multicast Address"); break; default: stringstream s; s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex << static_cast(GetOpcode()) << ")"; - logger.Warn(s.str()); + LogWarn(s.str()); break; } @@ -934,15 +879,15 @@ bool ScsiController::XferOutBlockOriented(bool cont) void ScsiController::ProcessCommand() { - uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); + const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); stringstream s; s << "CDB=$" << setfill('0') << setw(2) << hex; for (uint32_t i = 0; i < len; i++) { - GetCmd()[i] = GetBuffer()[i]; - s << GetCmd(i); + SetCmdByte(i, GetBuffer()[i]); + s << GetCmdByte(i); } - logger.Trace(s.str()); + LogTrace(s.str()); Execute(); } @@ -954,13 +899,13 @@ void ScsiController::ParseMessage() const uint8_t message_type = scsi.msb[i]; if (message_type == 0x06) { - logger.Trace("Received ABORT message"); + LogTrace("Received ABORT message"); BusFree(); return; } if (message_type == 0x0C) { - logger.Trace("Received BUS DEVICE RESET message"); + LogTrace("Received BUS DEVICE RESET message"); scsi.syncoffset = 0; if (auto device = GetDeviceForLun(identified_lun); device != nullptr) { device->DiscardReservation(); @@ -971,11 +916,11 @@ void ScsiController::ParseMessage() if (message_type >= 0x80) { identified_lun = static_cast(message_type) & 0x1F; - logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun)); + LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun)); } if (message_type == 0x01) { - logger.Trace("Received EXTENDED MESSAGE"); + LogTrace("Received EXTENDED MESSAGE"); // Check only when synchronous transfer is possible if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { @@ -1046,4 +991,4 @@ void ScsiController::Sleep() SysTimer::SleepUsec(MIN_EXEC_TIME - time); } execstart = 0; -} \ No newline at end of file +} diff --git a/cpp/controllers/scsi_controller.h b/cpp/controllers/scsi_controller.h index fd5093bc..68a4be25 100644 --- a/cpp/controllers/scsi_controller.h +++ b/cpp/controllers/scsi_controller.h @@ -15,8 +15,6 @@ #pragma once #include "shared/scsi.h" -#include "controller_manager.h" -#include "devices/device_logger.h" #include "abstract_controller.h" #include @@ -52,20 +50,17 @@ class ScsiController : public AbstractController public: - // Maximum number of logical units - static inline const int LUN_MAX = 32; - - explicit ScsiController(shared_ptr, int); + ScsiController(BUS&, int); ~ScsiController() override = default; void Reset() override; - phase_t Process(int) override; + bool Process(int) override; int GetEffectiveLun() const override; - void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override; + void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information, + scsi_defs::status status = scsi_defs::status::check_condition) override; int GetInitiatorId() const override { return initiator_id; } @@ -79,16 +74,8 @@ public: void DataIn() override; void DataOut() override; - // TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC, - // e.g. by using ifdef __arm__. Currently the unit tests require this method to be public. - virtual void Execute(); - - void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; } - private: - DeviceLogger logger; - // Execution start time uint32_t execstart = 0; @@ -109,6 +96,9 @@ private: void DataOutNonBlockOriented(); void Receive(); + // TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__. + virtual void Execute(); + void ProcessCommand(); void ParseMessage(); void ProcessMessage(); @@ -116,7 +106,5 @@ private: void Sleep(); scsi_t scsi = {}; - - AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE; }; diff --git a/cpp/devices/cd_track.h b/cpp/devices/cd_track.h index cd712484..02302323 100644 --- a/cpp/devices/cd_track.h +++ b/cpp/devices/cd_track.h @@ -14,6 +14,7 @@ #pragma once +#include #include using namespace std; diff --git a/cpp/devices/cfilesystem.cpp b/cpp/devices/cfilesystem.cpp index 7531c70a..381ed933 100644 --- a/cpp/devices/cfilesystem.cpp +++ b/cpp/devices/cfilesystem.cpp @@ -17,8 +17,8 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "cfilesystem.h" +#include #include #include #include @@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const // Acquire sector data if (!m_cEntry.GetCapacityCache(nUnit, &cap)) { // Carry out an extra media check here because it may be skipped when doing a manual eject - if (!m_cEntry.isEnable(nUnit)) - goto none; - // Media check - if (m_cEntry.isMediaOffline(nUnit)) - goto none; - - // Get drive status - m_cEntry.GetCapacity(nUnit, &cap); + if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) { + cap.clusters = 4; // This is totally fine, right? + cap.sectors = 64; + cap.bytes = 512; + } + else { + // Get drive status + m_cEntry.GetCapacity(nUnit, &cap); + } } } else { - none: cap.clusters = 4; // This is totally fine, right? cap.sectors = 64; cap.bytes = 512; diff --git a/cpp/devices/cfilesystem.h b/cpp/devices/cfilesystem.h index 8c4c8703..e44339a2 100644 --- a/cpp/devices/cfilesystem.h +++ b/cpp/devices/cfilesystem.h @@ -13,6 +13,15 @@ #pragma once +#include +#include +#include +#include +#include +#include +#include +#include + using TCHAR = char; static const int FILEPATH_MAX = 260; diff --git a/cpp/devices/ctapdriver.cpp b/cpp/devices/ctapdriver.cpp index 2c868e51..c010c93c 100644 --- a/cpp/devices/ctapdriver.cpp +++ b/cpp/devices/ctapdriver.cpp @@ -9,126 +9,71 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_exceptions.h" +#include "shared/network_util.h" #include #include #include #include "ctapdriver.h" +#include #include #include #include #ifdef __linux__ -#include -#include #include #include #endif using namespace std; using namespace piscsi_util; +using namespace network_util; -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- -static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { +const string CTapDriver::BRIDGE_NAME = "piscsi_bridge"; + +static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) { #ifndef __linux__ - return false; + return "if_nametoindex: Linux is required"; #else ifreq ifr; - ifr.ifr_ifindex = if_nametoindex(ifname); + ifr.ifr_ifindex = if_nametoindex(ifname.c_str()); if (ifr.ifr_ifindex == 0) { - LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno)) - return false; + return "Can't if_nametoindex " + ifname; } - strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1); + strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) { - LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)) - return false; + return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF"); } - return true; + return ""; #endif } -CTapDriver::~CTapDriver() -{ - if (m_hTAP != -1) { - if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) - } else { - LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME) - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here - LOGWARN("Warning: Removing piscsi0 from the bridge failed.") - LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge") - } - close(br_socket_fd); - } - - // Release TAP defice - close(m_hTAP); - } - - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - - if (m_pcap != nullptr) { - pcap_close(m_pcap); - } -} - -static bool ip_link(int fd, const char* ifname, bool up) { +string ip_link(int fd, const char* ifname, bool up) { #ifndef __linux__ - return false; + return "Can't ip_link: Linux is required"; #else ifreq ifr; - strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator - int err = ioctl(fd, SIOCGIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno)) - return false; + strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (ioctl(fd, SIOCGIFFLAGS, &ifr)) { + return "Can't ioctl SIOCGIFFLAGS"; } ifr.ifr_flags &= ~IFF_UP; if (up) { ifr.ifr_flags |= IFF_UP; } - err = ioctl(fd, SIOCSIFFLAGS, &ifr); - if (err) { - LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno)) - return false; + if (ioctl(fd, SIOCSIFFLAGS, &ifr)) { + return "Can't ioctl SIOCSIFFLAGS"; } - return true; + return ""; #endif } -static bool is_interface_up(string_view interface) { - string file = "/sys/class/net/"; - file += interface; - file += "/carrier"; - - bool status = true; - FILE *fp = fopen(file.c_str(), "r"); - if (!fp || fgetc(fp) != '1') { - status = false; - } - - if (fp) { - fclose(fp); - } - - return status; -} - -bool CTapDriver::Init(const unordered_map& const_params) +bool CTapDriver::Init(const param_map& const_params) { #ifndef __linux__ return false; #else - unordered_map params = const_params; + param_map params = const_params; stringstream s(params["interface"]); string interface; while (getline(s, interface, ',')) { @@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map& const_params) } inet = params["inet"]; - LOGTRACE("Opening tap device") + spdlog::trace("Opening tap device"); // TAP device initilization if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { - LOGERROR("Can't open tun: %s", strerror(errno)) + LogErrno("Can't open tun"); return false; } - LOGTRACE("Opened tap device %d", m_hTAP) - // IFF_NO_PI for no extra packet information ifreq ifr = {}; ifr.ifr_flags = IFF_TAP | IFF_NO_PI; - string dev = "piscsi0"; - strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1); + strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe - LOGTRACE("Going to open %s", ifr.ifr_name) + spdlog::trace("Going to open " + string(ifr.ifr_name)); - int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); + const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr); if (ret < 0) { - LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)) + LogErrno("Can't ioctl TUNSETIFF"); close(m_hTAP); return false; } - LOGTRACE("Return code from ioctl was %d", ret) + spdlog::trace("Return code from ioctl was " + to_string(ret)); const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0); if (ip_fd < 0) { - LOGERROR("Can't open ip socket: %s", strerror(errno)) + LogErrno("Can't open ip socket"); close(m_hTAP); return false; @@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map& const_params) const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); if (br_socket_fd < 0) { - LOGERROR("Can't open bridge socket: %s", strerror(errno)) + LogErrno("Can't open bridge socket"); close(m_hTAP); close(ip_fd); return false; } + auto cleanUp = [&] (const string& error) { + LogErrno(error); + close(m_hTAP); + close(ip_fd); + close(br_socket_fd); + return false; + }; + // Check if the bridge has already been created - string sys_file = "/sys/class/net/"; - sys_file += BRIDGE_NAME; - if (access(sys_file.c_str(), F_OK)) { - LOGINFO("%s is not yet available", BRIDGE_NAME) + // TODO Find an alternative to accessing a file, there is most likely a system call/ioctl + if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) { + spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME); - LOGTRACE("Checking which interface is available for creating the bridge") - - string bridge_interface; - for (const string& iface : interfaces) { - if (is_interface_up(iface)) { - LOGTRACE("%s", string("Interface " + iface + " is up").c_str()) - - bridge_interface = iface; - break; - } - else { - LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str()) - } + const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } ); + if (it == interfaces.end()) { + return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME); } - if (bridge_interface.empty()) { - LOGERROR("No interface is up, not creating bridge") - return false; - } + const string bridge_interface = *it; - LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str()) + spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface); if (bridge_interface == "eth0") { - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str()) - - if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) { + return cleanUp(error); } } - else { - string address = inet; - string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe - if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) { - address = inet.substr(0, separatorPos); - - int m; - if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) { - LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str()) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - // long long is required for compatibility with 32 bit platforms - const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); - netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + - to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); - - } - - LOGTRACE("brctl addbr %s", BRIDGE_NAME) - - if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) { - LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_a; - ifr_a.ifr_addr.sa_family = AF_INET; - strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; - inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - ifreq ifr_n; - ifr_n.ifr_addr.sa_family = AF_INET; - strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ); - if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; - inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { - LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } - - LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME) - - if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { - LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; - } + else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) { + return cleanUp(error); } - LOGTRACE("ip link set dev %s up", BRIDGE_NAME) + spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up"); - if (!ip_link(ip_fd, BRIDGE_NAME, true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) { + return cleanUp(error); } } - else - { - LOGINFO("%s is already available", BRIDGE_NAME) + else { + spdlog::info(BRIDGE_NAME + " is already available"); } - LOGTRACE("ip link set piscsi0 up") + spdlog::trace(">ip link set piscsi0 up"); - if (!ip_link(ip_fd, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME) + spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0"); - if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) { - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) { + return cleanUp(error); } - // Get MAC address - LOGTRACE("Getting the MAC address") + spdlog::trace("Getting the MAC address"); ifr.ifr_addr.sa_family = AF_INET; if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) { - LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)) - - close(m_hTAP); - close(ip_fd); - close(br_socket_fd); - return false; + return cleanUp("Can't ioctl SIOCGIFHWADDR"); } - LOGTRACE("Got the MAC") // Save MAC address memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size()); @@ -353,50 +188,124 @@ bool CTapDriver::Init(const unordered_map& const_params) close(ip_fd); close(br_socket_fd); - LOGINFO("Tap device %s created", ifr.ifr_name) + spdlog::info("Tap device " + string(ifr.ifr_name) + " created"); return true; #endif } -void CTapDriver::OpenDump(const string& path) { - if (m_pcap == nullptr) { - m_pcap = pcap_open_dead(DLT_EN10MB, 65535); - } - if (m_pcap_dumper != nullptr) { - pcap_dump_close(m_pcap_dumper); - } - m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str()); - if (m_pcap_dumper == nullptr) { - LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)) - throw io_exception("Can't open pcap file"); - } +void CTapDriver::CleanUp() const +{ + if (m_hTAP != -1) { + if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) { + LogErrno("Can't open bridge socket"); + } else { + spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0"); + if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) { + spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error); + spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge"); + } + close(br_socket_fd); + } - LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str()) + // Release TAP device + close(m_hTAP); + } } -bool CTapDriver::Enable() const +pair CTapDriver::ExtractAddressAndMask(const string& s) +{ + string address = s; + string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe + if (const auto& components = Split(s, '/', 2); components.size() == 2) { + address = components[0]; + + int m; + if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) { + spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'"); + return { "", "" }; + } + + // long long is required for compatibility with 32 bit platforms + const auto mask = (long long)(pow(2, 32) - (1 << (32 - m))); + netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' + + to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff); + } + + return { address, netmask }; +} + +string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface) +{ +#ifdef __linux__ + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface); + + if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) { + return error; + } +#endif + + return ""; +} + +string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s) +{ +#ifdef __linux__ + const auto [address, netmask] = ExtractAddressAndMask(s); + if (address.empty() || netmask.empty()) { + return "Error extracting inet address and netmask"; + } + + spdlog::trace(">brctl addbr " + BRIDGE_NAME); + + if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) { + return "Can't ioctl SIOCBRADDBR"; + } + + ifreq ifr_a; + ifr_a.ifr_addr.sa_family = AF_INET; + strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr; + inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) { + return "Can't convert '" + address + "' into a network address"; + } + + ifreq ifr_n; + ifr_n.ifr_addr.sa_family = AF_INET; + strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr; + inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) { + return "Can't convert '" + netmask + "' into a netmask"; + } + + spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME); + + if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) { + return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK"; + } +#endif + + return ""; +} + +string CTapDriver::IpLink(bool enable) const { const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", true); + spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down")); + const string result = ip_link(fd, "piscsi0", enable); close(fd); return result; } -bool CTapDriver::Disable() const +void CTapDriver::Flush() const { - const int fd = socket(PF_INET, SOCK_DGRAM, 0); - LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__) - const bool result = ip_link(fd, "piscsi0", false); - close(fd); - return result; -} - -void CTapDriver::Flush() -{ - LOGTRACE("%s", __PRETTY_FUNCTION__) - while (PendingPackets()) { + while (HasPendingPackets()) { array m_garbage_buffer; (void)Receive(m_garbage_buffer.data()); } @@ -409,12 +318,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const memcpy(mac, m_MacAddr.data(), m_MacAddr.size()); } -//--------------------------------------------------------------------------- -// -// Receive -// -//--------------------------------------------------------------------------- -bool CTapDriver::PendingPackets() const +bool CTapDriver::HasPendingPackets() const { assert(m_hTAP != -1); @@ -424,20 +328,16 @@ bool CTapDriver::PendingPackets() const fds.events = POLLIN | POLLERR; fds.revents = 0; poll(&fds, 1, 0); - LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents) - if (!(fds.revents & POLLIN)) { - return false; - } else { - return true; - } + spdlog::trace(to_string(fds.revents) + " revents"); + return !(fds.revents & POLLIN); } // See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li -uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { +uint32_t CTapDriver::Crc32(span data) { uint32_t crc = 0xffffffff; - for (int i = 0; i < length; i++) { - crc ^= buf[i]; - for (int j = 0; j < 8; j++) { + for (const auto d: data) { + crc ^= d; + for (int i = 0; i < 8; i++) { const uint32_t mask = -(static_cast(crc) & 1); crc = (crc >> 1) ^ (0xEDB88320 & mask); } @@ -445,19 +345,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) { return ~crc; } -int CTapDriver::Receive(uint8_t *buf) +int CTapDriver::Receive(uint8_t *buf) const { assert(m_hTAP != -1); // Check if there is data that can be received - if (!PendingPackets()) { + if (!HasPendingPackets()) { return 0; } // Receive auto dwReceived = static_cast(read(m_hTAP, buf, ETH_FRAME_LEN)); if (dwReceived == static_cast(-1)) { - LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__) + spdlog::warn("Error occured while receiving a packet"); return 0; } @@ -466,49 +366,28 @@ int CTapDriver::Receive(uint8_t *buf) // We need to add the Frame Check Status (FCS) CRC back onto the end of the packet. // The Linux network subsystem removes it, since most software apps shouldn't ever // need it. - const int crc = Crc32(buf, dwReceived); + const int crc = Crc32(span(buf, dwReceived)); buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF); buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF); buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF); buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF); - LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]) + spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) + + " " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3])); // Add FCS size to the received message size dwReceived += 4; } - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = dwReceived, - .len = dwReceived - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]) - } - // Return the number of bytes return dwReceived; } -int CTapDriver::Send(const uint8_t *buf, int len) +int CTapDriver::Send(const uint8_t *buf, int len) const { assert(m_hTAP != -1); - if (m_pcap_dumper != nullptr) { - pcap_pkthdr h = { - .ts = {}, - .caplen = (bpf_u_int32)len, - .len = (bpf_u_int32)len, - }; - gettimeofday(&h.ts, nullptr); - pcap_dump((u_char*)m_pcap_dumper, &h, buf); - LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]) - } - // Start sending return static_cast(write(m_hTAP, buf, len)); } diff --git a/cpp/devices/ctapdriver.h b/cpp/devices/ctapdriver.h index bb3bc371..f8b3bdfd 100644 --- a/cpp/devices/ctapdriver.h +++ b/cpp/devices/ctapdriver.h @@ -11,46 +11,55 @@ #pragma once -#include -#include +#include "devices/device.h" #include #include #include #include +#include + +#ifndef ETH_FRAME_LEN +static const int ETH_FRAME_LEN = 1514; +#endif +#ifndef ETH_FCS_LEN +static const int ETH_FCS_LEN = 4; +#endif using namespace std; class CTapDriver { - static constexpr const char *BRIDGE_NAME = "piscsi_bridge"; + static const string BRIDGE_NAME; public: CTapDriver() = default; - ~CTapDriver(); + ~CTapDriver() = default; CTapDriver(CTapDriver&) = default; CTapDriver& operator=(const CTapDriver&) = default; - bool Init(const unordered_map&); - void OpenDump(const string& path); // Capture packets - void GetMacAddr(uint8_t *mac) const; - int Receive(uint8_t *buf); - int Send(const uint8_t *buf, int len); - bool PendingPackets() const; // Check if there are IP packets available - bool Enable() const; // Enable the piscsi0 interface - bool Disable() const; // Disable the piscsi0 interface - void Flush(); // Purge all of the packets that are waiting to be processed + bool Init(const param_map&); + void CleanUp() const; - static uint32_t Crc32(const uint8_t *, int); + void GetMacAddr(uint8_t *) const; + int Receive(uint8_t *) const; + int Send(const uint8_t *, int) const; + bool HasPendingPackets() const; // Check if there are IP packets available + string IpLink(bool) const; // Enable/Disable the piscsi0 interface + void Flush() const; // Purge all of the packets that are waiting to be processed + + static uint32_t Crc32(span); private: + + static string SetUpEth0(int, const string&); + static string SetUpNonEth0(int, int, const string&); + static pair ExtractAddressAndMask(const string&); + array m_MacAddr; // MAC Address int m_hTAP = -1; // File handle - pcap_t *m_pcap = nullptr; - pcap_dumper_t *m_pcap_dumper = nullptr; - // Prioritized comma-separated list of interfaces to create the bridge for vector interfaces; diff --git a/cpp/devices/device.cpp b/cpp/devices/device.cpp index 2aedf5d9..58b071f8 100644 --- a/cpp/devices/device.cpp +++ b/cpp/devices/device.cpp @@ -3,22 +3,24 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_version.h" #include "device.h" +#include #include #include #include +#include using namespace std; Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) { ostringstream os; - os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version; + os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version; revision = os.str(); } @@ -39,7 +41,7 @@ void Device::SetProtected(bool b) void Device::SetVendor(const string& v) { if (v.empty() || v.length() > 8) { - throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters"); + throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters"); } vendor = v; @@ -48,7 +50,7 @@ void Device::SetVendor(const string& v) void Device::SetProduct(const string& p, bool force) { if (p.empty() || p.length() > 16) { - throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters"); + throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters"); } // Changing vital product data is not SCSI compliant @@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force) void Device::SetRevision(const string& r) { if (r.empty() || r.length() > 4) { - throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters"); + throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters"); } revision = r; @@ -85,7 +87,7 @@ string Device::GetParam(const string& key) const return it == params.end() ? "" : it->second; } -void Device::SetParams(const unordered_map& set_params) +void Device::SetParams(const param_map& set_params) { params = default_params; @@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map& set_params) for (const auto& [key, value] : set_params) { // It is assumed that there are default parameters for all supported parameters - if (params.find(key) != params.end()) { + if (params.contains(key)) { params[key] = value; } else { - LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str()) + spdlog::warn("Ignored unknown parameter '" + key + "'"); } } } diff --git a/cpp/devices/device.h b/cpp/devices/device.h index f971bc9d..80f1a272 100644 --- a/cpp/devices/device.h +++ b/cpp/devices/device.h @@ -3,20 +3,26 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/log.h" #include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_util.h" #include #include using namespace std; using namespace piscsi_interface; +// A combination of device ID and LUN +using id_set = pair; + +// The map used for storing/passing device parameters +using param_map = unordered_map>; + class Device //NOSONAR The number of fields and methods is justified, the complexity is low { const string DEFAULT_VENDOR = "PiSCSI"; @@ -60,10 +66,10 @@ class Device //NOSONAR The number of fields and methods is justified, the comple string revision; // The parameters the device was created with - unordered_map params; + param_map params; // The default parameters - unordered_map default_params; + param_map default_params; // Sense Key and ASC // MSB Reserved (0x00) @@ -90,14 +96,15 @@ protected: int GetStatusCode() const { return status_code; } string GetParam(const string&) const; - void SetParams(const unordered_map&); + void SetParams(const param_map&); public: virtual ~Device() = default; PbDeviceType GetType() const { return type; } - const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); } + string GetTypeString() const { return PbDeviceType_Name(type); } + string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); } bool IsReady() const { return ready; } virtual void Reset(); @@ -131,8 +138,8 @@ public: bool SupportsFile() const { return supports_file; } void SupportsParams(bool b) { supports_params = b; } void SupportsFile(bool b) { supports_file = b; } - unordered_map GetParams() const { return params; } - void SetDefaultParams(const unordered_map& p) { default_params = p; } + auto GetParams() const { return params; } + void SetDefaultParams(const param_map& p) { default_params = p; } void SetStatusCode(int s) { status_code = s; } diff --git a/cpp/devices/device_factory.cpp b/cpp/devices/device_factory.cpp index 321162f7..da69fa98 100644 --- a/cpp/devices/device_factory.cpp +++ b/cpp/devices/device_factory.cpp @@ -3,11 +3,11 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/piscsi_util.h" +#include "shared/network_util.h" #include "scsihd.h" #include "scsihd_nec.h" #include "scsimo.h" @@ -17,15 +17,10 @@ #include "scsi_daynaport.h" #include "host_services.h" #include "device_factory.h" -#include -#include -#include -#include -#include using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +using namespace network_util; DeviceFactory::DeviceFactory() { @@ -34,19 +29,11 @@ DeviceFactory::DeviceFactory() sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; sector_sizes[SCCD] = { 512, 2048}; - string network_interfaces; - for (const auto& network_interface : GetNetworkInterfaces()) { - if (network_interface.rfind("dummy", 0) == string::npos) { - if (!network_interfaces.empty()) { - network_interfaces += ","; - } - network_interfaces += network_interface; - } - } + const string interfaces = Join(GetNetworkInterfaces(), ","); - default_params[SCBR]["interface"] = network_interfaces; + default_params[SCBR]["interface"] = interfaces; default_params[SCBR]["inet"] = DEFAULT_IP; - default_params[SCDP]["interface"] = network_interfaces; + default_params[SCDP]["interface"] = interfaces; default_params[SCDP]["inet"] = DEFAULT_IP; default_params[SCLP]["cmd"] = "lp -oraw %f"; @@ -96,8 +83,8 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { device = make_shared(lun); } else { - device = make_shared(lun, sector_sizes.find(SCHD)->second, false, - ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, false, + ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); // Some Apple tools require a particular drive identification if (ext == "hda") { @@ -109,18 +96,18 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun } case SCRM: - device = make_shared(lun, sector_sizes.find(SCRM)->second, true); + device = make_shared(lun, sector_sizes.find(type)->second, true); device->SetProduct("SCSI HD (REM.)"); break; case SCMO: - device = make_shared(lun, sector_sizes.find(SCMO)->second); + device = make_shared(lun, sector_sizes.find(type)->second); device->SetProduct("SCSI MO"); break; case SCCD: - device = make_shared(lun, sector_sizes.find(SCCD)->second, - GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes.find(type)->second, + GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2); device->SetProduct("SCSI CD-ROM"); break; @@ -128,7 +115,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device = make_shared(lun); // Since this is an emulation for a specific driver the product name has to be set accordingly device->SetProduct("RASCSI BRIDGE"); - device->SetDefaultParams(default_params.find(SCBR)->second); + device->SetDefaultParams(default_params.find(type)->second); break; case SCDP: @@ -137,7 +124,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); device->SetRevision("1.4a"); - device->SetDefaultParams(default_params.find(SCDP)->second); + device->SetDefaultParams(default_params.find(type)->second); break; case SCHS: @@ -150,7 +137,7 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun case SCLP: device = make_shared(lun); device->SetProduct("SCSI PRINTER"); - device->SetDefaultParams(default_params.find(SCLP)->second); + device->SetDefaultParams(default_params.find(type)->second); break; default: @@ -163,44 +150,11 @@ shared_ptr DeviceFactory::CreateDevice(PbDeviceType type, int lun const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) const { const auto& it = sector_sizes.find(type); - return it != sector_sizes.end() ? it->second : empty_set; + return it != sector_sizes.end() ? it->second : EMPTY_SET; } -const unordered_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const +const param_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const { const auto& it = default_params.find(type); - return it != default_params.end() ? it->second : empty_map; -} - -vector DeviceFactory::GetNetworkInterfaces() const -{ - vector network_interfaces; - -#ifdef __linux__ - ifaddrs *addrs; - getifaddrs(&addrs); - ifaddrs *tmp = addrs; - - while (tmp) { - if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && - strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) { - const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP); - - ifreq ifr = {}; - strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here - // Only list interfaces that are up - if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { - network_interfaces.emplace_back(tmp->ifa_name); - } - - close(fd); - } - - tmp = tmp->ifa_next; - } - - freeifaddrs(addrs); -#endif - - return network_interfaces; + return it != default_params.end() ? it->second : EMPTY_PARAM_MAP; } diff --git a/cpp/devices/device_factory.h b/cpp/devices/device_factory.h index a2865d88..cdce0ba2 100644 --- a/cpp/devices/device_factory.h +++ b/cpp/devices/device_factory.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // The DeviceFactory creates devices based on their type and the image file extension // @@ -11,8 +11,9 @@ #pragma once +#include "shared/piscsi_util.h" +#include "devices/device.h" #include -#include #include #include #include "generated/piscsi_interface.pb.h" @@ -34,20 +35,19 @@ public: shared_ptr CreateDevice(PbDeviceType, int, const string&) const; PbDeviceType GetTypeForFile(const string&) const; const unordered_set& GetSectorSizes(PbDeviceType type) const; - const unordered_map& GetDefaultParams(PbDeviceType type) const; - vector GetNetworkInterfaces() const; - const unordered_map& GetExtensionMapping() const { return extension_mapping; } + const param_map& GetDefaultParams(PbDeviceType type) const; + const auto& GetExtensionMapping() const { return extension_mapping; } private: unordered_map> sector_sizes; - unordered_map> default_params; + unordered_map default_params; - unordered_map extension_mapping; + unordered_map> extension_mapping; - unordered_map device_mapping; + unordered_map> device_mapping; - unordered_set empty_set; - unordered_map empty_map; + inline static const unordered_set EMPTY_SET; + inline static const param_map EMPTY_PARAM_MAP; }; diff --git a/cpp/devices/device_logger.cpp b/cpp/devices/device_logger.cpp index 98c6d8d3..65afb129 100644 --- a/cpp/devices/device_logger.cpp +++ b/cpp/devices/device_logger.cpp @@ -3,63 +3,52 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "device_logger.h" using namespace std; +using namespace spdlog; void DeviceLogger::Trace(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGTRACE("%s", m.c_str()) - } + Log(level::trace, message); } void DeviceLogger::Debug(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGDEBUG("%s", m.c_str()) - } + Log(level::debug, message); } void DeviceLogger::Info(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGINFO("%s", m.c_str()) - } + Log(level::info, message); } void DeviceLogger::Warn(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGWARN("%s", m.c_str()) - } + Log(level::warn, message); } void DeviceLogger::Error(const string& message) const { - if (const string m = GetLogMessage(message); !m.empty()) { - LOGERROR("%s", m.c_str()) - } + Log(level::err, message); } -string DeviceLogger::GetLogMessage(const string& message) const +void DeviceLogger::Log(level::level_enum level, const string& message) const { - if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun))) - { + if (!message.empty() && + (log_device_id == -1 || + (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) { if (lun == -1) { - return "(ID " + to_string(id) + ") - " + message; + log(level, "(ID " + to_string(id) + ") - " + message); } else { - return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message; + log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message); } } - - return ""; } void DeviceLogger::SetIdAndLun(int i, int l) diff --git a/cpp/devices/device_logger.h b/cpp/devices/device_logger.h index fb5b8f7a..dc662718 100644 --- a/cpp/devices/device_logger.h +++ b/cpp/devices/device_logger.h @@ -3,12 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include "spdlog/spdlog.h" #include using namespace std; @@ -27,14 +28,13 @@ public: void Warn(const string&) const; void Error(const string&) const; - string GetLogMessage(const string&) const; - void SetIdAndLun(int, int); - static void SetLogIdAndLun(int, int); private: + void Log(spdlog::level::level_enum, const string&) const; + int id = -1; int lun = -1; diff --git a/cpp/devices/disk.cpp b/cpp/devices/disk.cpp index 8649103f..8a32273e 100644 --- a/cpp/devices/disk.cpp +++ b/cpp/devices/disk.cpp @@ -23,15 +23,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -Disk::~Disk() -{ - // Save disk cache, only if ready - if (IsReady() && cache != nullptr) { - cache->Save(); - } -} - -bool Disk::Init(const unordered_map& params) +bool Disk::Init(const param_map& params) { StorageDevice::Init(params); @@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map& params) return true; } +void Disk::CleanUp() +{ + FlushCache(); + + StorageDevice::CleanUp(); +} + void Disk::Dispatch(scsi_command cmd) { // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY @@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd) SetMediumChanged(false); - GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change); } else { PrimaryDevice::Dispatch(cmd); @@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw) void Disk::FlushCache() { - if (cache != nullptr) { + if (cache != nullptr && IsReady()) { cache->Save(); } } @@ -103,8 +102,8 @@ void Disk::FormatUnit() CheckReady(); // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) - if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -115,9 +114,9 @@ void Disk::Read(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(start + 1); @@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 7) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard if (GetInt16(GetController()->GetCmd(), 12) != 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const void Disk::Write(access_mode mode) const { if (IsProtected()) { - throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED); + throw scsi_exception(sense_key::data_protect, asc::write_protected); } const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); @@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode) const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); if (valid) { // if BytChk=0 - if ((GetController()->GetCmd(1) & 0x02) == 0) { + if ((GetController()->GetCmdByte(1) & 0x02) == 0) { Seek(); return; } // Test reading GetController()->SetBlocks(blocks); - GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); + GetController()->SetLength(Read(GetController()->GetBuffer(), start)); // Set next block GetController()->SetNext(start + 1); @@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode) void Disk::StartStopUnit() { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (load) { - GetLogger().Trace(start ? "Loading medium" : "Ejecting medium"); + LogTrace(start ? "Loading medium" : "Ejecting medium"); } else { - GetLogger().Trace(start ? "Starting unit" : "Stopping unit"); + LogTrace(start ? "Starting unit" : "Stopping unit"); SetStopped(!start); } @@ -217,12 +216,12 @@ void Disk::StartStopUnit() if (load) { if (IsLocked()) { // Cannot be ejected because it is locked - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } // Eject if (!Eject(false)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); + throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed); } } else { @@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval() { CheckReady(); - const bool lock = GetController()->GetCmd(4) & 0x01; + const bool lock = GetController()->GetCmdByte(4) & 0x01; - GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium"); + LogTrace(lock ? "Locking medium" : "Unlocking medium"); SetLocked(lock); @@ -279,7 +278,7 @@ bool Disk::Eject(bool force) return status; } -int Disk::ModeSense6(const vector& cdb, vector& buf) const +int Disk::ModeSense6(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -315,7 +314,7 @@ int Disk::ModeSense6(const vector& cdb, vector& buf) const return size; } -int Disk::ModeSense10(const vector& cdb, vector& buf) const +int Disk::ModeSense10(cdb_t cdb, vector& buf) const { // Get length, clear buffer const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -497,27 +496,27 @@ void Disk::AddCachePage(map>& pages, bool changeable) const pages[8] = buf; } -int Disk::Read(const vector&, vector& buf, uint64_t block) +int Disk::Read(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->ReadSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT); + throw scsi_exception(sense_key::medium_error, asc::read_fault); } return GetSectorSizeInBytes(); } -void Disk::Write(const vector&, const vector& buf, uint64_t block) +void Disk::Write(span buf, uint64_t block) { assert(block < GetBlockCount()); CheckReady(); if (!cache->WriteSector(buf, static_cast(block))) { - throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT); + throw scsi_exception(sense_key::medium_error, asc::write_fault); } } @@ -553,7 +552,7 @@ void Disk::ReadCapacity10() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -580,7 +579,7 @@ void Disk::ReadCapacity16() CheckReady(); if (GetBlockCount() == 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::illegal_request, asc::medium_not_present); } vector& buf = GetController()->GetBuffer(); @@ -604,7 +603,7 @@ void Disk::ReadCapacity16() void Disk::ReadCapacity16_ReadLong16() { // The service action determines the actual command - switch (GetController()->GetCmd(1) & 0x1f) { + switch (GetController()->GetCmdByte(1) & 0x1f) { case 0x10: ReadCapacity16(); break; @@ -614,7 +613,7 @@ void Disk::ReadCapacity16_ReadLong16() break; default: - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); break; } } @@ -624,9 +623,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2); if (block > GetBlockCount()) { - GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + to_string(block)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } } @@ -638,7 +637,7 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) if (mode == RW6 || mode == SEEK6) { start = GetInt24(GetController()->GetCmd(), 1); - count = GetController()->GetCmd(4); + count = GetController()->GetCmdByte(4); if (!count) { count= 0x100; } @@ -659,13 +658,13 @@ tuple Disk::CheckAndGetStartAndCount(access_mode mode) stringstream s; s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start; - GetLogger().Trace(s.str() + ", blocks: " + to_string(count)); + LogTrace(s.str() + ", blocks: " + to_string(count)); // Check capacity if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) { - GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } // Do not process 0 blocks @@ -689,10 +688,8 @@ uint32_t Disk::GetSectorSizeInBytes() const void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { - DeviceFactory device_factory; - if (const auto& sizes = device_factory.GetSectorSizes(GetType()); - !sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) { - throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); + if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) { + throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); } size_shift_count = CalculateShiftCount(size_in_bytes); @@ -706,12 +703,11 @@ uint32_t Disk::GetConfiguredSectorSize() const bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size) { - if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); - sizes.find(configured_size) == sizes.end()) { + if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) { return false; } - configured_sector_size = configured_size; + configured_sector_size = configured_size; return true; } diff --git a/cpp/devices/disk.h b/cpp/devices/disk.h index 75af2697..1b22262d 100644 --- a/cpp/devices/disk.h +++ b/cpp/devices/disk.h @@ -22,6 +22,7 @@ #include "interfaces/scsi_block_commands.h" #include "storage_device.h" #include +#include #include #include #include @@ -43,18 +44,18 @@ class Disk : public StorageDevice, private ScsiBlockCommands public: - Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {} - ~Disk() override; + using StorageDevice::StorageDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; void Dispatch(scsi_command) override; bool Eject(bool) override; - virtual void Write(const vector&, const vector&, uint64_t); + virtual void Write(span, uint64_t); - virtual int Read(const vector&, vector& , uint64_t); + virtual int Read(span , uint64_t); uint32_t GetSectorSizeInBytes() const; bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } @@ -92,8 +93,8 @@ private: void ValidateBlockAddress(access_mode) const; tuple CheckAndGetStartAndCount(access_mode) const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; static inline const unordered_map shift_counts = { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; diff --git a/cpp/devices/disk_cache.cpp b/cpp/devices/disk_cache.cpp index 8fc126db..23bd724a 100644 --- a/cpp/devices/disk_cache.cpp +++ b/cpp/devices/disk_cache.cpp @@ -30,7 +30,7 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff bool DiskCache::Save() const { // Save valid tracks - return none_of(cache.begin(), cache.end(), [this](const cache_t& c) + return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c) { return c.disktrk != nullptr && !c.disktrk->Save(sec_path); }); } @@ -46,7 +46,7 @@ shared_ptr DiskCache::GetTrack(uint32_t block) return Assign(track); } -bool DiskCache::ReadSector(vector& buf, uint32_t block) +bool DiskCache::ReadSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { @@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector& buf, uint32_t block) return disktrk->ReadSector(buf, block & 0xff); } -bool DiskCache::WriteSector(const vector& buf, uint32_t block) +bool DiskCache::WriteSector(span buf, uint32_t block) { shared_ptr disktrk = GetTrack(block); if (disktrk == nullptr) { diff --git a/cpp/devices/disk_cache.h b/cpp/devices/disk_cache.h index d9199b5c..4400e27d 100644 --- a/cpp/devices/disk_cache.h +++ b/cpp/devices/disk_cache.h @@ -15,6 +15,7 @@ #pragma once +#include #include #include #include @@ -41,8 +42,8 @@ public: // Access bool Save() const; // Save and release all - bool ReadSector(vector&, uint32_t); // Sector Read - bool WriteSector(const vector&, uint32_t); // Sector Write + bool ReadSector(span, uint32_t); // Sector Read + bool WriteSector(span, uint32_t); // Sector Write private: diff --git a/cpp/devices/disk_track.cpp b/cpp/devices/disk_track.cpp index 2b4f4008..35e1e56b 100644 --- a/cpp/devices/disk_track.cpp +++ b/cpp/devices/disk_track.cpp @@ -14,8 +14,10 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "disk_track.h" +#include +#include +#include #include DiskTrack::~DiskTrack() @@ -75,7 +77,7 @@ bool DiskTrack::Load(const string& path) if (dt.buffer == nullptr) { if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } @@ -88,14 +90,14 @@ bool DiskTrack::Load(const string& path) if (dt.length != static_cast(length)) { free(dt.buffer); if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) { - LOGWARN("posix_memalign failed") + spdlog::warn("posix_memalign failed"); } dt.length = length; } // Resize and clear changemap dt.changemap.resize(dt.sectors); - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector ifstream in(path, ios::binary); if (in.fail()) { @@ -209,13 +211,13 @@ bool DiskTrack::Save(const string& path) } // Drop the change flag and exit - fill(dt.changemap.begin(), dt.changemap.end(), false); + fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector dt.changed = false; return true; } -bool DiskTrack::ReadSector(vector& buf, int sec) const +bool DiskTrack::ReadSector(span buf, int sec) const { assert(sec >= 0 && sec < 0x100); @@ -238,7 +240,7 @@ bool DiskTrack::ReadSector(vector& buf, int sec) const return true; } -bool DiskTrack::WriteSector(const vector& buf, int sec) +bool DiskTrack::WriteSector(span buf, int sec) { assert((sec >= 0) && (sec < 0x100)); assert(!dt.raw); diff --git a/cpp/devices/disk_track.h b/cpp/devices/disk_track.h index 786ab6e5..91ca5e86 100644 --- a/cpp/devices/disk_track.h +++ b/cpp/devices/disk_track.h @@ -16,6 +16,8 @@ #pragma once #include +#include +#include #include #include @@ -52,8 +54,8 @@ private: bool Save(const string& path); // Read / Write - bool ReadSector(vector&, int) const; // Sector Read - bool WriteSector(const vector& buf, int); // Sector Write + bool ReadSector(span, int) const; // Sector Read + bool WriteSector(span buf, int); // Sector Write int GetTrack() const { return dt.track; } // Get track }; diff --git a/cpp/devices/host_services.cpp b/cpp/devices/host_services.cpp index 5cb74b9a..3d02bbd0 100644 --- a/cpp/devices/host_services.cpp +++ b/cpp/devices/host_services.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -25,11 +25,13 @@ #include "scsi_command_util.h" #include "host_services.h" #include +#include +using namespace std::chrono; using namespace scsi_defs; using namespace scsi_command_util; -bool HostServices::Init(const unordered_map& params) +bool HostServices::Init(const param_map& params) { ModePageDevice::Init(params); @@ -49,13 +51,13 @@ void HostServices::TestUnitReady() vector HostServices::InquiryInternal() const { - return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + return HandleInquiry(device_type::processor, scsi_level::spc_3, false); } void HostServices::StartStopUnit() const { - const bool start = GetController()->GetCmd(4) & 0x01; - const bool load = GetController()->GetCmd(4) & 0x02; + const bool start = GetController()->GetCmdByte(4) & 0x01; + const bool load = GetController()->GetCmdByte(4) & 0x02; if (!start) { if (load) { @@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI); } else { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); } -int HostServices::ModeSense6(const vector& cdb, vector& buf) const +int HostServices::ModeSense6(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(cdb[4]))); @@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector& cdb, vector& buf) const return size; } -int HostServices::ModeSense10(const vector& cdb, vector& buf) const +int HostServices::ModeSense10(cdb_t cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto length = static_cast(min(buf.size(), static_cast(GetInt16(cdb, 7)))); @@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map>& pages, bool chan pages[32] = vector(10); if (!changeable) { - time_t t = time(nullptr); + const auto now = system_clock::now(); + const time_t t = system_clock::to_time_t(now); tm localtime; localtime_r(&t, &localtime); diff --git a/cpp/devices/host_services.h b/cpp/devices/host_services.h index a269b2ac..76a41f8c 100644 --- a/cpp/devices/host_services.h +++ b/cpp/devices/host_services.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Host Services with realtime clock and shutdown support // @@ -12,6 +12,7 @@ #pragma once #include "mode_page_device.h" +#include #include #include @@ -23,7 +24,7 @@ public: explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {} ~HostServices() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; vector InquiryInternal() const override; void TestUnitReady() override; @@ -48,8 +49,8 @@ private: }; void StartStopUnit() const; - int ModeSense6(const vector&, vector&) const override; - int ModeSense10(const vector&, vector&) const override; + int ModeSense6(cdb_t, vector&) const override; + int ModeSense10(cdb_t, vector&) const override; void AddRealtimeClockPage(map>&, bool) const; }; diff --git a/cpp/devices/interfaces/byte_writer.h b/cpp/devices/interfaces/byte_writer.h deleted file mode 100644 index bfbfa5ee..00000000 --- a/cpp/devices/interfaces/byte_writer.h +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Abstraction for the DaynaPort and the host bridge, which both have methods for writing byte sequences -// -//--------------------------------------------------------------------------- - -#pragma once - -#include - -using namespace std; - -class ByteWriter -{ - -public: - - ByteWriter() = default; - virtual ~ByteWriter() = default; - - virtual bool WriteBytes(const vector&, vector&, uint32_t) = 0; -}; diff --git a/cpp/devices/mode_page_device.cpp b/cpp/devices/mode_page_device.cpp index bb292097..7335d5a3 100644 --- a/cpp/devices/mode_page_device.cpp +++ b/cpp/devices/mode_page_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A basic device with mode page support, to be used for subclassing // @@ -20,7 +20,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool ModePageDevice::Init(const unordered_map& params) +bool ModePageDevice::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map& params) return true; } -int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int offset, int length, int max_size) const +int ModePageDevice::AddModePages(cdb_t cdb, vector& buf, int offset, int length, int max_size) const { const int max_length = length - offset; if (max_length < 0) { @@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i stringstream s; s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page; - GetLogger().Trace(s.str()); + LogTrace(s.str()); // Mode page data mapped to the respective page numbers, C++ maps are ordered by key map> pages; @@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i if (pages.empty()) { s << "Unsupported mode page $" << page; - GetLogger().Trace(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + LogTrace(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Holds all mode page data @@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, i } if (static_cast(result.size()) > max_size) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const auto size = static_cast(min(static_cast(max_length), result.size())); @@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const EnterDataInPhase(); } -void ModePageDevice::ModeSelect(scsi_command, const vector&, const vector&, int) const +void ModePageDevice::ModeSelect(scsi_command, cdb_t, span, int) const { - // There is no default implementation of MDOE SELECT - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + // There is no default implementation of MODE SELECT + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } void ModePageDevice::ModeSelect6() const { - SaveParametersCheck(GetController()->GetCmd(4)); + SaveParametersCheck(GetController()->GetCmdByte(4)); } void ModePageDevice::ModeSelect10() const @@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const void ModePageDevice::SaveParametersCheck(int length) const { - if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); diff --git a/cpp/devices/mode_page_device.h b/cpp/devices/mode_page_device.h index e8fc06d7..4a487fb2 100644 --- a/cpp/devices/mode_page_device.h +++ b/cpp/devices/mode_page_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,25 +11,25 @@ #include "primary_device.h" #include +#include #include #include -// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages class ModePageDevice : public PrimaryDevice { public: using PrimaryDevice::PrimaryDevice; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; - virtual void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const; protected: bool SupportsSaveParameters() const { return supports_save_parameters; } void SupportsSaveParameters(bool b) { supports_save_parameters = b; } - int AddModePages(const vector&, vector&, int, int, int) const; + int AddModePages(cdb_t, vector&, int, int, int) const; virtual void SetUpModePages(map>&, int, bool) const = 0; virtual void AddVendorPage(map>&, int, bool) const { // Nothing to add by default @@ -39,8 +39,8 @@ private: bool supports_save_parameters = false; - virtual int ModeSense6(const vector&, vector&) const = 0; - virtual int ModeSense10(const vector&, vector&) const = 0; + virtual int ModeSense6(cdb_t, vector&) const = 0; + virtual int ModeSense10(cdb_t, vector&) const = 0; void ModeSense6() const; void ModeSense10() const; diff --git a/cpp/devices/primary_device.cpp b/cpp/devices/primary_device.cpp index 10989167..4249ed4e 100644 --- a/cpp/devices/primary_device.cpp +++ b/cpp/devices/primary_device.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,7 +17,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -bool PrimaryDevice::Init(const unordered_map& params) +bool PrimaryDevice::Init(const param_map& params) { // Mandatory SCSI primary commands AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); }); @@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map& params) return true; } -void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute) +void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute) { - commands[opcode] = execute; + commands[cmd] = execute; } void PrimaryDevice::Dispatch(scsi_command cmd) @@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd) s << "$" << setfill('0') << setw(2) << hex << static_cast(cmd); if (const auto& it = commands.find(cmd); it != commands.end()) { - GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) + - " (" + s.str() + ")"); + LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")"); it->second(); } else { - GetLogger().Trace("Received unsupported command: " + s.str()); + LogTrace("Received unsupported command: " + s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); } } @@ -67,18 +66,14 @@ void PrimaryDevice::Reset() int PrimaryDevice::GetId() const { - if (GetController() == nullptr) { - GetLogger().Error("Device is missing its controller"); - } - return GetController() != nullptr ? GetController()->GetTargetId() : -1; } -void PrimaryDevice::SetController(shared_ptr c) +void PrimaryDevice::SetController(AbstractController *c) { controller = c; - logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun()); + device_logger.SetIdAndLun(GetId(), GetLun()); } void PrimaryDevice::TestUnitReady() @@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady() void PrimaryDevice::Inquiry() { // EVPD and page code check - if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } - vector buf = InquiryInternal(); + const vector buf = InquiryInternal(); const size_t allocation_length = min(buf.size(), static_cast(GetInt16(GetController()->GetCmd(), 3))); @@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry() GetController()->SetLength(static_cast(allocation_length)); // Report if the device does not support the requested LUN - if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { - GetLogger().Trace("LUN is not available"); + if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) { + LogTrace("LUN is not available"); // Signal that the requested LUN does not exist GetController()->GetBuffer().data()[0] = 0x7f; @@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry() void PrimaryDevice::ReportLuns() { // Only SELECT REPORT mode 0 is supported - if (GetController()->GetCmd(2)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(2)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6); @@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense() lun = 0; // Do not raise an exception here because the rest of the code must be executed - GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); + GetController()->Error(sense_key::illegal_request, asc::invalid_lun); - GetController()->SetStatus(status::GOOD); + GetController()->SetStatus(status::good); } vector buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense(); - const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmd(4))); + const size_t allocation_length = min(buf.size(), static_cast(GetController()->GetCmdByte(4))); memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length); GetController()->SetLength(static_cast(allocation_length)); @@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense() void PrimaryDevice::SendDiagnostic() { // Do not support PF bit - if (GetController()->GetCmd(1) & 0x10) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(1) & 0x10) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Do not support parameter list - if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) { + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterStatusPhase(); @@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady() // Not ready if reset if (IsReset()) { SetReset(false); - GetLogger().Trace("Device in reset"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET); + LogTrace("Device in reset"); + throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset); } // Not ready if it needs attention if (IsAttn()) { SetAttn(false); - GetLogger().Trace("Device in needs attention"); - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); + LogTrace("Device in needs attention"); + throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change); } // Return status if not ready if (!IsReady()) { - GetLogger().Trace("Device not ready"); - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + LogTrace("Device not ready"); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } - GetLogger().Trace("Device is ready"); + LogTrace("Device is ready"); } vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const @@ -223,8 +218,8 @@ vector PrimaryDevice::HandleInquiry(device_type type, scsi_level level, buf[0] = static_cast(type); buf[1] = is_removable ? 0x80 : 0x00; buf[2] = static_cast(level); - buf[3] = level >= scsi_level::SCSI_2 ? - static_cast(scsi_level::SCSI_2) : static_cast(scsi_level::SCSI_1_CCS); + buf[3] = level >= scsi_level::scsi_2 ? + static_cast(scsi_level::scsi_2) : static_cast(scsi_level::scsi_1_ccs); buf[4] = 0x1F; // Padded vendor, product, revision @@ -237,7 +232,7 @@ vector PrimaryDevice::HandleRequestSense() const { // Return not ready only if there are no errors if (!GetStatusCode() && !IsReady()) { - throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT); + throw scsi_exception(sense_key::not_ready, asc::medium_not_present); } // Set 18 bytes including extended sense data @@ -257,14 +252,14 @@ vector PrimaryDevice::HandleRequestSense() const << "Status $" << static_cast(GetController()->GetStatus()) << ", Sense Key $" << static_cast(buf[2]) << ", ASC $" << static_cast(buf[12]); - GetLogger().Trace(s.str()); + LogTrace(s.str()); return buf; } -bool PrimaryDevice::WriteByteSequence(vector&, uint32_t) +bool PrimaryDevice::WriteByteSequence(span) { - GetLogger().Error("Writing bytes is not supported by this device"); + LogError("Writing bytes is not supported by this device"); return false; } @@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit() reserving_initiator = GetController()->GetInitiatorId(); if (reserving_initiator != -1) { - GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator)); + LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Reserved device for unknown initiator"); + LogTrace("Reserved device for unknown initiator"); } EnterStatusPhase(); @@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit() void PrimaryDevice::ReleaseUnit() { if (reserving_initiator != -1) { - GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator)); + LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator)); } else { - GetLogger().Trace("Released device reserved by unknown initiator"); + LogTrace("Released device reserved by unknown initiator"); } DiscardReservation(); @@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr } if (initiator_id != -1) { - GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); + LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device"); } else { - GetLogger().Trace("Unknown initiator tries to access reserved device"); + LogTrace("Unknown initiator tries to access reserved device"); } return false; diff --git a/cpp/devices/primary_device.h b/cpp/devices/primary_device.h index e8b10bf1..682a4f97 100644 --- a/cpp/devices/primary_device.h +++ b/cpp/devices/primary_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // A device implementing mandatory SCSI primary commands, to be used for subclassing // @@ -18,6 +18,7 @@ #include "device_logger.h" #include #include +#include #include using namespace std; @@ -25,6 +26,8 @@ using namespace scsi_defs; class PrimaryDevice: private ScsiPrimaryCommands, public Device { + friend class AbstractController; + using operation = function; public: @@ -32,15 +35,16 @@ public: PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {} ~PrimaryDevice() override = default; - virtual bool Init(const unordered_map&); + virtual bool Init(const param_map&); + virtual void CleanUp() { + // Override if cleanup work is required for a derived device + }; virtual void Dispatch(scsi_command); int GetId() const override; - void SetController(shared_ptr); - - virtual bool WriteByteSequence(vector&, uint32_t); + virtual bool WriteByteSequence(span); int GetSendDelay() const { return send_delay; } @@ -57,8 +61,6 @@ protected: void AddCommand(scsi_command, const operation&); - const DeviceLogger& GetLogger() const { return logger; } - vector HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; virtual vector InquiryInternal() const = 0; void CheckReady(); @@ -69,16 +71,24 @@ protected: void ReserveUnit() override; void ReleaseUnit() override; - void EnterStatusPhase() const { controller.lock()->Status(); } - void EnterDataInPhase() const { controller.lock()->DataIn(); } - void EnterDataOutPhase() const { controller.lock()->DataOut(); } + void EnterStatusPhase() const { controller->Status(); } + void EnterDataInPhase() const { controller->DataIn(); } + void EnterDataOutPhase() const { controller->DataOut(); } - inline shared_ptr GetController() const { return controller.lock(); } + auto GetController() const { return controller; } + + void LogTrace(const string& s) const { device_logger.Trace(s); } + void LogDebug(const string& s) const { device_logger.Debug(s); } + void LogInfo(const string& s) const { device_logger.Info(s); } + void LogWarn(const string& s) const { device_logger.Warn(s); } + void LogError(const string& s) const { device_logger.Error(s); } private: static const int NOT_RESERVED = -2; + void SetController(AbstractController *); + void TestUnitReady() override; void RequestSense() override; void ReportLuns() override; @@ -86,9 +96,11 @@ private: vector HandleRequestSense() const; - DeviceLogger logger; + // TODO Try to remove this field and use controller->Log*() methods instead + DeviceLogger device_logger; - weak_ptr controller; + // Owned by the controller manager + AbstractController *controller = nullptr; unordered_map commands; diff --git a/cpp/devices/scsi_command_util.cpp b/cpp/devices/scsi_command_util.cpp index 346bdf6f..20bb83f7 100644 --- a/cpp/devices/scsi_command_util.cpp +++ b/cpp/devices/scsi_command_util.cpp @@ -3,31 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "device_logger.h" #include "scsi_command_util.h" +#include #include -#include #include #include using namespace scsi_defs; -void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector& cdb, - const vector& buf, int length, int sector_size) +string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length, int sector_size) { assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10); assert(length >= 0); + string result; + // PF if (!(cdb[1] & 0x10)) { // Vendor-specific parameters (SCSI-1) are not supported. // Do not report an error in order to support Apple's HD SC Setup. - return; + return result; } // Skip block descriptors @@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, // Parse the pages while (length > 0) { // Format device page - if (int page = buf[offset]; page == 0x03) { + if (const int page = buf[offset]; page == 0x03) { if (length < 14) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } // With this page the sector size for a subsequent FORMAT can be selected, but only very few @@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, if (GetInt16(buf, offset + 12) != sector_size) { // With piscsi it is not possible to permanently (by formatting) change the sector size, // because the size is an externally configurable setting only - logger.Warn("In order to change the sector size use the -b option when launching piscsi"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + spdlog::warn("In order to change the sector size use the -b option when launching piscsi"); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } has_valid_page_code = true; @@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, else { stringstream s; s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page; - logger.Warn(s.str()); + result = s.str(); } // Advance to the next page @@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, } if (!has_valid_page_code) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list); } + + return result; } void scsi_command_util::EnrichFormatPage(map>& pages, bool changeable, int sector_size) @@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map>& pages, bo // No changeable area if (!changeable) { - const char APPLE_DATA[] = "APPLE COMPUTER, INC "; + constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA)); } } -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (static_cast(buf[offset]) << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt16(const vector& buf, int offset) -{ - assert(buf.size() > static_cast(offset) + 1); - - return (buf[offset] << 8) | buf[offset + 1]; -} - -int scsi_command_util::GetInt24(const vector& buf, int offset) +int scsi_command_util::GetInt24(span buf, int offset) { assert(buf.size() > static_cast(offset) + 2); return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2]; } -uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) +uint32_t scsi_command_util::GetInt32(span buf, int offset) { assert(buf.size() > static_cast(offset) + 3); @@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector& buf, int offset) (static_cast(buf[offset + 2]) << 8) | static_cast(buf[offset + 3]); } -uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) +uint64_t scsi_command_util::GetInt64(span buf, int offset) { assert(buf.size() > static_cast(offset) + 7); @@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector& buf, int offset) (static_cast(buf[offset + 6]) << 8) | static_cast(buf[offset + 7]); } -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - -void scsi_command_util::SetInt16(vector& buf, int offset, int value) -{ - assert(buf.size() > static_cast(offset) + 1); - - buf[offset] = static_cast(value >> 8); - buf[offset + 1] = static_cast(value); -} - -void scsi_command_util::SetInt32(vector& buf, int offset, uint32_t value) -{ - assert(buf.size() > static_cast(offset) + 3); - - buf[offset] = static_cast(value >> 24); - buf[offset + 1] = static_cast(value >> 16); - buf[offset + 2] = static_cast(value >> 8); - buf[offset + 3] = static_cast(value); -} - void scsi_command_util::SetInt64(vector& buf, int offset, uint64_t value) { assert(buf.size() > static_cast(offset) + 7); diff --git a/cpp/devices/scsi_command_util.h b/cpp/devices/scsi_command_util.h index bc688252..0fecac8d 100644 --- a/cpp/devices/scsi_command_util.h +++ b/cpp/devices/scsi_command_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Shared code for SCSI command implementations // @@ -12,27 +12,49 @@ #pragma once #include "shared/scsi.h" +#include +#include +#include #include #include using namespace std; -class DeviceLogger; - namespace scsi_command_util { - void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector&, const vector&, int, int); + string ModeSelect(scsi_defs::scsi_command, cdb_t, span, int, int); void EnrichFormatPage(map>&, bool, int); void AddAppleVendorModePage(map>&, bool); - int GetInt16(const vector&, int); - int GetInt16(const vector&, int); - int GetInt24(const vector&, int); - uint32_t GetInt32(const vector&, int); - uint64_t GetInt64(const vector&, int); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); - void SetInt16(vector&, int, int); - void SetInt32(vector&, int, uint32_t); + int GetInt16(const auto buf, int offset) + { + assert(buf.size() > static_cast(offset) + 1); + + return (static_cast(buf[offset]) << 8) | buf[offset + 1]; + }; + + template + void SetInt16(vector& buf, int offset, int value) + { + assert(buf.size() > static_cast(offset) + 1); + + buf[offset] = static_cast(value >> 8); + buf[offset + 1] = static_cast(value); + } + + template + void SetInt32(vector& buf, int offset, uint32_t value) + { + assert(buf.size() > static_cast(offset) + 3); + + buf[offset] = static_cast(value >> 24); + buf[offset + 1] = static_cast(value >> 16); + buf[offset + 2] = static_cast(value >> 8); + buf[offset + 3] = static_cast(value); + } + + int GetInt24(span, int); + uint32_t GetInt32(span , int); + uint64_t GetInt64(span, int); void SetInt64(vector&, int, uint64_t); } diff --git a/cpp/devices/scsi_daynaport.cpp b/cpp/devices/scsi_daynaport.cpp index 1fab6e6e..e710ab33 100644 --- a/cpp/devices/scsi_daynaport.cpp +++ b/cpp/devices/scsi_daynaport.cpp @@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun) SupportsParams(true); } -bool SCSIDaynaPort::Init(const unordered_map& params) +bool SCSIDaynaPort::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -55,15 +55,13 @@ bool SCSIDaynaPort::Init(const unordered_map& params) SetSendDelay(DAYNAPORT_READ_HEADER_SZ); m_bTapEnable = m_tap.Init(GetParams()); - if(!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); - + if (!m_bTapEnable) { // Not terminating on regular Linux PCs is helpful for testing #if !defined(__x86_64__) && !defined(__X86__) - return false; + return false; #endif } else { - GetLogger().Trace("Tap interface created"); + LogTrace("Tap interface created"); } Reset(); @@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map& params) return true; } +void SCSIDaynaPort::CleanUp() +{ + m_tap.CleanUp(); +} + vector SCSIDaynaPort::InquiryInternal() const { - vector buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false); // The Daynaport driver for the Mac expects 37 bytes: Increase additional length and // add a vendor-specific byte in order to satisfy this driver. @@ -116,14 +119,14 @@ vector SCSIDaynaPort::InquiryInternal() const // - The SCSI/Link apparently has about 6KB buffer space for packets. // //--------------------------------------------------------------------------- -int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) +int SCSIDaynaPort::Read(cdb_t cdb, vector& buf, uint64_t) const { int rx_packet_size = 0; const auto response = (scsi_resp_read_t*)buf.data(); const int requested_length = cdb[4]; - GetLogger().Trace("Read maximum length: " + to_string(requested_length)); + LogTrace("Read maximum length: " + to_string(requested_length)); // At startup the host may send a READ(6) command with a sector count of 1 to read the root sector. // We should respond by going into the status mode with a code of 0x02. @@ -146,13 +149,13 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // If we didn't receive anything, return size of 0 if (rx_packet_size <= 0) { - GetLogger().Trace("No packet received"); + LogTrace("No packet received"); response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; } - GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); + LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count)); // This is a very basic filter to prevent unnecessary packets from // being sent to the SCSI initiator. @@ -187,11 +190,11 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) for (int i = 0 ; i < 6; i++) { s << " $" << static_cast(response->data[i]); } - GetLogger().Debug(s.str()); + LogDebug(s.str()); // If there are pending packets to be processed, we'll tell the host that the read // length was 0. - if (!m_tap.PendingPackets()) { + if (!m_tap.HasPendingPackets()) { response->length = 0; response->flags = read_data_flags_t::e_no_more_data; return DAYNAPORT_READ_HEADER_SZ; @@ -216,7 +219,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) size = 64; } SetInt16(buf, 0, size); - SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00); + SetInt32(buf, 2, m_tap.HasPendingPackets() ? 0x10 : 0x00); // Return the packet size + 2 for the length + 4 for the flag field // The CRC was already appended by the ctapdriver @@ -249,25 +252,23 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // XX XX ... is the actual packet // //--------------------------------------------------------------------------- -bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIDaynaPort::Write(cdb_t cdb, span buf) const { - const int data_format = cdb[5]; - int data_length = GetInt16(cdb, 3); - - if (data_format == 0x00) { + if (const int data_format = cdb[5]; data_format == 0x00) { + const int data_length = GetInt16(cdb, 3); m_tap.Send(buf.data(), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); + LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)"); } else if (data_format == 0x80) { // The data length is specified in the first 2 bytes of the payload - data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); + const int data_length = buf[1] + ((static_cast(buf[0]) & 0xff) << 8); m_tap.Send(&(buf.data()[4]), data_length); - GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); + LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)"); } else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } GetController()->SetBlocks(0); @@ -291,7 +292,7 @@ bool SCSIDaynaPort::WriteBytes(const vector& cdb, vector& buf, uin // - long #3: frames lost // //--------------------------------------------------------------------------- -int SCSIDaynaPort::RetrieveStats(const vector& cdb, vector& buf) const +int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector& buf) const { memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats)); @@ -304,7 +305,7 @@ void SCSIDaynaPort::TestUnitReady() EnterStatusPhase(); } -void SCSIDaynaPort::Read6() +void SCSIDaynaPort::Read6() const { // Get record number and block number const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff; @@ -312,17 +313,17 @@ void SCSIDaynaPort::Read6() // If any commands have a bogus control value, they were probably not // generated by the DaynaPort driver so ignore them - if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) { - GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5))); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) { + LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5))); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } stringstream s; s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record; - GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks())); + LogTrace(s.str()); GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record)); - GetLogger().Trace("Length is " + to_string(GetController()->GetLength())); + LogTrace("Length is " + to_string(GetController()->GetLength())); // Set next block GetController()->SetNext(record + 1); @@ -335,7 +336,7 @@ void SCSIDaynaPort::Write6() const // Ensure a sufficient buffer size (because it is not transfer for each block) GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE); - const int data_format = GetController()->GetCmd(5); + const int data_format = GetController()->GetCmdByte(5); if (data_format == 0x00) { GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3)); @@ -346,15 +347,15 @@ void SCSIDaynaPort::Write6() const else { stringstream s; s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format; - GetLogger().Warn(s.str()); + LogWarn(s.str()); } stringstream s; s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format; - GetLogger().Trace(s.str()); + LogTrace(s.str()); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -406,7 +407,7 @@ void SCSIDaynaPort::SetInterfaceMode() const // Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address" GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer())); - switch(GetController()->GetCmd(5)){ + switch(GetController()->GetCmdByte(5)){ case CMD_SCSILINK_SETMODE: // Not implemented, do nothing EnterStatusPhase(); @@ -419,21 +420,21 @@ void SCSIDaynaPort::SetInterfaceMode() const default: stringstream s; - s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code); break; } } void SCSIDaynaPort::SetMcastAddr() const { - GetController()->SetLength(GetController()->GetCmd(4)); + GetController()->SetLength(GetController()->GetCmdByte(4)); if (GetController()->GetLength() == 0) { stringstream s; - s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2); - GetLogger().Warn(s.str()); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2); + LogWarn(s.str()); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } EnterDataOutPhase(); @@ -451,27 +452,27 @@ void SCSIDaynaPort::SetMcastAddr() const // seconds // //--------------------------------------------------------------------------- -void SCSIDaynaPort::EnableInterface() +void SCSIDaynaPort::EnableInterface() const { - if (GetController()->GetCmd(5) & 0x80) { - if (!m_tap.Enable()) { - GetLogger().Warn("Unable to enable the DaynaPort Interface"); + if (GetController()->GetCmdByte(5) & 0x80) { + if (const string error = m_tap.IpLink(true); !error.empty()) { + LogWarn("Unable to enable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } m_tap.Flush(); - GetLogger().Info("The DaynaPort interface has been ENABLED"); + LogInfo("The DaynaPort interface has been ENABLED"); } else { - if (!m_tap.Disable()) { - GetLogger().Warn("Unable to disable the DaynaPort Interface"); + if (const string error = m_tap.IpLink(false); !error.empty()) { + LogWarn("Unable to disable the DaynaPort Interface: " + error); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Info("The DaynaPort interface has been DISABLED"); + LogInfo("The DaynaPort interface has been DISABLED"); } EnterStatusPhase(); diff --git a/cpp/devices/scsi_daynaport.h b/cpp/devices/scsi_daynaport.h index f0ca53f4..45d0ea50 100644 --- a/cpp/devices/scsi_daynaport.h +++ b/cpp/devices/scsi_daynaport.h @@ -29,10 +29,11 @@ #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" +#include #include +#include #include #include @@ -41,29 +42,30 @@ // DaynaPort SCSI Link // //=========================================================================== -class SCSIDaynaPort : public PrimaryDevice, public ByteWriter +class SCSIDaynaPort : public PrimaryDevice { public: explicit SCSIDaynaPort(int); ~SCSIDaynaPort() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; // Commands vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int Read(cdb_t, vector&, uint64_t) const; + bool Write(cdb_t, span) const; - int RetrieveStats(const vector&, vector&) const; + int RetrieveStats(cdb_t, vector&) const; void TestUnitReady() override; - void Read6(); + void Read6() const; void Write6() const; void RetrieveStatistics() const; void SetInterfaceMode() const; void SetMcastAddr() const; - void EnableInterface(); + void EnableInterface() const; static const int DAYNAPORT_BUFFER_SIZE = 0x1000000; diff --git a/cpp/devices/scsi_host_bridge.cpp b/cpp/devices/scsi_host_bridge.cpp index 6e2038c8..93ef1c0d 100644 --- a/cpp/devices/scsi_host_bridge.cpp +++ b/cpp/devices/scsi_host_bridge.cpp @@ -20,7 +20,6 @@ #include "scsi_command_util.h" #include "scsi_host_bridge.h" #include -#include using namespace std; using namespace scsi_defs; @@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun) SupportsParams(true); } -bool SCSIBR::Init(const unordered_map& params) +bool SCSIBR::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -43,10 +42,8 @@ bool SCSIBR::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); }); #ifdef __linux__ - // TAP Driver Generation m_bTapEnable = tap.Init(GetParams()); if (!m_bTapEnable){ - GetLogger().Error("Unable to open the TAP interface"); return false; } #endif @@ -70,9 +67,14 @@ bool SCSIBR::Init(const unordered_map& params) #endif } +void SCSIBR::CleanUp() +{ + tap.CleanUp(); +} + vector SCSIBR::InquiryInternal() const { - vector buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false); + vector buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false); // The bridge returns more additional bytes than the other devices buf.resize(0x1F + 8 + 5); @@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady() EnterStatusPhase(); } -int SCSIBR::GetMessage10(const vector& cdb, vector& buf) +int SCSIBR::GetMessage10(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector& cdb, vector& buf) return 0; } -bool SCSIBR::WriteBytes(const vector& cdb, vector& buf, uint32_t) +bool SCSIBR::ReadWrite(cdb_t cdb, vector& buf) { // Type const int type = cdb[2]; @@ -252,7 +254,7 @@ void SCSIBR::GetMessage10() GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer())); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Set next block @@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const { GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6)); if (GetController()->GetLength() <= 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Ensure a sufficient buffer size (because it is not a transfer for each block) @@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector& mac) const return static_cast(mac_addr.size()); } -void SCSIBR::SetMacAddr(const vector& mac) +void SCSIBR::SetMacAddr(span mac) { memcpy(mac_addr.data(), mac.data(), mac_addr.size()); } @@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector& buf, int index) packet_enable = false; } -void SCSIBR::SendPacket(const vector& buf, int len) +void SCSIBR::SendPacket(span buf, int len) const { tap.Send(buf.data(), len); } @@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::capacity_t cap; + Human68k::capacity_t cap = {}; fsresult = fs.GetCapacity(nUnit, &cap); cap.freearea = htons(cap.freearea); @@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector& buf) auto dp = (uint32_t*)buf.data(); const uint32_t nUnit = ntohl(*dp); - Human68k::dpb_t dpb; + Human68k::dpb_t dpb = {}; fsresult = fs.GetDPB(nUnit, &dpb); dpb.sector_size = htons(dpb.sector_size); diff --git a/cpp/devices/scsi_host_bridge.h b/cpp/devices/scsi_host_bridge.h index e5f2dbcd..f3a80096 100644 --- a/cpp/devices/scsi_host_bridge.h +++ b/cpp/devices/scsi_host_bridge.h @@ -17,16 +17,16 @@ //--------------------------------------------------------------------------- #pragma once -#include "interfaces/byte_writer.h" #include "primary_device.h" #include "ctapdriver.h" #include "cfilesystem.h" #include +#include #include using namespace std; -class SCSIBR : public PrimaryDevice, public ByteWriter +class SCSIBR : public PrimaryDevice { static constexpr const array bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; @@ -35,12 +35,13 @@ public: explicit SCSIBR(int); ~SCSIBR() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; // Commands vector InquiryInternal() const override; - int GetMessage10(const vector&, vector&); - bool WriteBytes(const vector&, vector&, uint32_t) override; + int GetMessage10(cdb_t, vector&); + bool ReadWrite(cdb_t, vector&); void TestUnitReady() override; void GetMessage10(); void SendMessage10() const; @@ -48,16 +49,16 @@ public: private: int GetMacAddr(vector&) const; // Get MAC address - void SetMacAddr(const vector&); // Set MAC address + void SetMacAddr(span); // Set MAC address void ReceivePacket(); // Receive a packet - void GetPacketBuf(vector&, int); // Get a packet - void SendPacket(const vector&, int); // Send a packet + void GetPacketBuf(vector&, int); // Get a packet + void SendPacket(span, int) const; // Send a packet CTapDriver tap; // TAP driver bool m_bTapEnable = false; // TAP valid flag - array mac_addr = {}; // MAC Address + array mac_addr = {}; // MAC Address int packet_len = 0; // Receive packet size - array packet_buf; // Receive packet buffer + array packet_buf; // Receive packet buffer bool packet_enable = false; // Received packet valid int ReadFsResult(vector&) const; // Read filesystem (result code) diff --git a/cpp/devices/scsi_printer.cpp b/cpp/devices/scsi_printer.cpp index 23046ee4..da4bff40 100644 --- a/cpp/devices/scsi_printer.cpp +++ b/cpp/devices/scsi_printer.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -32,7 +32,6 @@ #include "shared/piscsi_exceptions.h" #include "scsi_command_util.h" #include "scsi_printer.h" -#include #include using namespace std; @@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun) SupportsParams(true); } -bool SCSIPrinter::Init(const unordered_map& params) +bool SCSIPrinter::Init(const param_map& params) { PrimaryDevice::Init(params); @@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map& params) AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); }); if (GetParam("cmd").find("%f") == string::npos) { - GetLogger().Trace("Missing filename specifier %f"); + LogTrace("Missing filename specifier %f"); return false; } @@ -82,20 +81,20 @@ void SCSIPrinter::TestUnitReady() vector SCSIPrinter::InquiryInternal() const { - return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false); + return HandleInquiry(device_type::printer, scsi_level::scsi_2, false); } void SCSIPrinter::Print() { const uint32_t length = GetInt24(GetController()->GetCmd(), 2); - GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed"); + LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed"); if (length > GetController()->GetBuffer().size()) { - GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + + LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) + " bytes, " + to_string(length) + " bytes expected"); - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } GetController()->SetLength(length); @@ -107,9 +106,9 @@ void SCSIPrinter::Print() void SCSIPrinter::SynchronizeBuffer() { if (!out.is_open()) { - GetLogger().Warn("Nothing to print"); + LogWarn("Nothing to print"); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } string cmd = GetParam("cmd"); @@ -118,25 +117,24 @@ void SCSIPrinter::SynchronizeBuffer() cmd.replace(file_position, 2, filename); error_code error; + LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)"); - - GetLogger().Debug("Executing '" + cmd + "'"); + LogDebug("Executing print command '" + cmd + "'"); if (system(cmd.c_str())) { - GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured"); + LogError("Printing file '" + filename + "' failed, the printing system might not be configured"); - Cleanup(); + CleanUp(); - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - Cleanup(); + CleanUp(); EnterStatusPhase(); } -bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) +bool SCSIPrinter::WriteByteSequence(span buf) { if (!out.is_open()) { vector f(file_template.begin(), file_template.end()); @@ -145,7 +143,7 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) // There is no C++ API that generates a file with a unique name const int fd = mkstemp(f.data()); if (fd == -1) { - GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); + LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno)); return false; } close(fd); @@ -154,21 +152,23 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) out.open(filename, ios::binary); if (out.fail()) { - throw scsi_exception(sense_key::ABORTED_COMMAND); + throw scsi_exception(sense_key::aborted_command); } - GetLogger().Trace("Created printer output file '" + filename + "'"); + LogTrace("Created printer output file '" + filename + "'"); } - GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'"); + LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'"); - out.write((const char*)buf.data(), length); + out.write((const char *)buf.data(), buf.size()); return !out.fail(); } -void SCSIPrinter::Cleanup() +void SCSIPrinter::CleanUp() { + PrimaryDevice::CleanUp(); + if (out.is_open()) { out.close(); diff --git a/cpp/devices/scsi_printer.h b/cpp/devices/scsi_printer.h index bbad49d1..6e626e54 100644 --- a/cpp/devices/scsi_printer.h +++ b/cpp/devices/scsi_printer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // Implementation of a SCSI printer (see SCSI-2 specification for a command description) // @@ -15,6 +15,7 @@ #include #include #include +#include using namespace std; @@ -29,11 +30,12 @@ public: explicit SCSIPrinter(int); ~SCSIPrinter() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; + void CleanUp() override; vector InquiryInternal() const override; - bool WriteByteSequence(vector&, uint32_t) override; + bool WriteByteSequence(span) override; private: @@ -44,8 +46,6 @@ private: void Print() override; void SynchronizeBuffer(); - void Cleanup(); - string file_template; string filename; diff --git a/cpp/devices/scsicd.cpp b/cpp/devices/scsicd.cpp index bcea0386..200c36c3 100644 --- a/cpp/devices/scsicd.cpp +++ b/cpp/devices/scsicd.cpp @@ -21,7 +21,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) +SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs::scsi_level level) : Disk(SCCD, lun), scsi_level(level) { SetSectorSizes(sector_sizes); @@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set& sector_sizes, scsi_defs:: SetLockable(true); } -bool SCSICD::Init(const unordered_map& params) +bool SCSICD::Init(const param_map& params) { Disk::Init(params); @@ -116,7 +116,7 @@ void SCSICD::OpenIso() if (rawfile) { if (size % 2536) { - GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is " + to_string(size) + " bytes"); } @@ -165,7 +165,7 @@ void SCSICD::ReadToc() vector SCSICD::InquiryInternal() const { - return HandleInquiry(device_type::CD_ROM, scsi_level, true); + return HandleInquiry(device_type::cd_rom, scsi_level, true); } void SCSICD::SetUpModePages(map>& pages, int page, bool changeable) const @@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map>& pages, int page, bool changea } } -int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) +int SCSICD::Read(span buf, uint64_t block) { CheckReady(); const int index = SearchTrack(static_cast(block)); if (index < 0) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } assert(tracks[index]); @@ -241,15 +241,15 @@ int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) } assert(dataindex >= 0); - return Disk::Read(cdb, buf, block); + return Disk::Read(buf, block); } -int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) +int SCSICD::ReadTocInternal(cdb_t cdb, vector& buf) { CheckReady(); // If ready, there is at least one track - assert(tracks.size() > 0); + assert(!tracks.empty()); assert(tracks[0]); // Get allocation length, clear buffer @@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) const int last = tracks[tracks.size() - 1]->GetTrackNo(); // Except for AA if (cdb[6] > last && cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Check start index @@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) // AA if not found or internal error if (!tracks[index]) { if (cdb[6] != 0xaa) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb); } // Returns the final LBA+1 because it is AA diff --git a/cpp/devices/scsicd.h b/cpp/devices/scsicd.h index e30ab743..db2620de 100644 --- a/cpp/devices/scsicd.h +++ b/cpp/devices/scsicd.h @@ -17,20 +17,23 @@ #include "cd_track.h" #include "disk.h" #include "interfaces/scsi_mmc_commands.h" +#include +#include +#include class SCSICD : public Disk, private ScsiMmcCommands { public: - SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSICD(int, const unordered_set&, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSICD() override = default; - bool Init(const unordered_map&) override; + bool Init(const param_map&) override; void Open() override; vector InquiryInternal() const override; - int Read(const vector&, vector&, uint64_t) override; + int Read(span, uint64_t) override; protected: @@ -39,7 +42,7 @@ protected: private: - int ReadTocInternal(const vector&, vector&); + int ReadTocInternal(cdb_t, vector&); void AddCDROMPage(map>&, bool) const; void AddCDDAPage(map>&, bool) const; diff --git a/cpp/devices/scsihd.cpp b/cpp/devices/scsihd.cpp index 0b0cca89..316c7575 100644 --- a/cpp/devices/scsihd.cpp +++ b/cpp/devices/scsihd.cpp @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -14,8 +14,8 @@ //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" -#include "scsihd.h" #include "scsi_command_util.h" +#include "scsihd.h" using namespace scsi_command_util; @@ -70,7 +70,7 @@ void SCSIHD::Open() { assert(!IsReady()); - off_t size = GetFileSize(); + const off_t size = GetFileSize(); // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); @@ -81,12 +81,15 @@ void SCSIHD::Open() vector SCSIHD::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable()); + return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable()); } -void SCSIHD::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } void SCSIHD::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd.h b/cpp/devices/scsihd.h index 44cef7ed..8216ae2a 100644 --- a/cpp/devices/scsihd.h +++ b/cpp/devices/scsihd.h @@ -5,7 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -18,6 +18,7 @@ #include "shared/scsi.h" #include "disk.h" #include +#include #include #include @@ -27,7 +28,7 @@ class SCSIHD : public Disk public: - SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2); + SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::scsi_2); ~SCSIHD() override = default; void FinalizeSetup(off_t); @@ -36,7 +37,7 @@ public: // Commands vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; void AddFormatPage(map>&, bool) const override; void AddVendorPage(map>&, int, bool) const override; diff --git a/cpp/devices/scsihd_nec.cpp b/cpp/devices/scsihd_nec.cpp index 4a50d8a0..5592e95c 100644 --- a/cpp/devices/scsihd_nec.cpp +++ b/cpp/devices/scsihd_nec.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -47,7 +48,7 @@ void SCSIHD_NEC::Open() FinalizeSetup(image_offset); } -pair SCSIHD_NEC::SetParameters(const array& data, int size) +pair SCSIHD_NEC::SetParameters(span data, int size) { array root_sector = {}; memcpy(root_sector.data(), data.data(), root_sector.size()); @@ -107,12 +108,12 @@ pair SCSIHD_NEC::SetParameters(const array& data, int size) throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)"); } - return make_pair(image_size, sector_size); + return { image_size, sector_size }; } vector SCSIHD_NEC::InquiryInternal() const { - return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false); + return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false); } void SCSIHD_NEC::AddFormatPage(map>& pages, bool changeable) const diff --git a/cpp/devices/scsihd_nec.h b/cpp/devices/scsihd_nec.h index ce710347..0bf7db36 100644 --- a/cpp/devices/scsihd_nec.h +++ b/cpp/devices/scsihd_nec.h @@ -47,7 +47,7 @@ protected: private: - pair SetParameters(const array&, int); + pair SetParameters(span, int); static int GetInt16LittleEndian(const uint8_t *); static int GetInt32LittleEndian(const uint8_t *); diff --git a/cpp/devices/scsimo.cpp b/cpp/devices/scsimo.cpp index 2eed267d..167064a1 100644 --- a/cpp/devices/scsimo.cpp +++ b/cpp/devices/scsimo.cpp @@ -1,14 +1,15 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS -// Copyright (C) akuker +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. -// See LICENSE file in the project root folder. +// Licensed under the BSD 3-Clause License. +// See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk(SCMO SetSectorSizes(sector_sizes); // 128 MB, 512 bytes per sector, 248826 sectors - geometries[512 * 248826] = make_pair(512, 248826); + geometries[512 * 248826] = { 512, 248826 }; // 230 MB, 512 bytes per block, 446325 sectors - geometries[512 * 446325] = make_pair(512, 446325); + geometries[512 * 446325] = { 512, 446325 }; // 540 MB, 512 bytes per sector, 1041500 sectors - geometries[512 * 1041500] = make_pair(512, 1041500); + geometries[512 * 1041500] = { 512, 1041500 }; // 640 MB, 20248 bytes per sector, 310352 sectors - geometries[2048 * 310352] = make_pair(2048, 310352); + geometries[2048 * 310352] = { 2048, 310352 }; SetProtectable(true); SetRemovable(true); @@ -61,7 +62,7 @@ void SCSIMO::Open() vector SCSIMO::InquiryInternal() const { - return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true); + return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true); } void SCSIMO::SetUpModePages(map>& pages, int page, bool changeable) const @@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map>& pages, bool) const // Do not report update blocks } -void SCSIMO::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const +void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span buf, int length) const { - scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); + !result.empty()) { + LogWarn(result); + } } // diff --git a/cpp/devices/scsimo.h b/cpp/devices/scsimo.h index c3253efc..41735d3b 100644 --- a/cpp/devices/scsimo.h +++ b/cpp/devices/scsimo.h @@ -5,6 +5,7 @@ // // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2022-2023 Uwe Seimet // Copyright (C) akuker // // Licensed under the BSD 3-Clause License. @@ -15,6 +16,7 @@ #pragma once #include "disk.h" +#include #include #include @@ -30,7 +32,7 @@ public: void Open() override; vector InquiryInternal() const override; - void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, cdb_t, span, int) const override; protected: diff --git a/cpp/devices/storage_device.cpp b/cpp/devices/storage_device.cpp index 991c1073..41b0ad8a 100644 --- a/cpp/devices/storage_device.cpp +++ b/cpp/devices/storage_device.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/piscsi_exceptions.h" #include "storage_device.h" -#include #include -#include using namespace std; using namespace filesystem; @@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, SetStoppable(true); } +void StorageDevice::CleanUp() +{ + UnreserveFile(); + + ModePageDevice::CleanUp(); +} + +void StorageDevice::SetFilename(string_view f) +{ + filename = filesystem::path(f); + + // Permanently write-protected + SetReadOnly(IsReadOnlyFile()); + + SetProtectable(!IsReadOnlyFile()); + + if (IsReadOnlyFile()) { + SetProtected(false); + } +} + void StorageDevice::ValidateFile() { if (blocks == 0) { throw io_exception(string(GetTypeString()) + " device has 0 blocks"); } - if (!exists(path(filename))) { - throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist"); + if (!exists(filename)) { + throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist"); } if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) { - throw io_exception("Drive capacity cannot exceed 2 TiB"); + throw io_exception("Image files > 2 TiB are not supported"); } // TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp) - if (access(filename.c_str(), W_OK)) { + if (IsReadOnlyFile()) { // Permanently write-protected SetReadOnly(true); SetProtectable(false); @@ -50,28 +69,28 @@ void StorageDevice::ValidateFile() SetReady(true); } -void StorageDevice::ReserveFile(const string& file, int id, int lun) const +void StorageDevice::ReserveFile() const { - assert(!file.empty()); - assert(reserved_files.find(file) == reserved_files.end()); + assert(!filename.empty()); + assert(!reserved_files.contains(filename.string())); - reserved_files[file] = make_pair(id, lun); + reserved_files[filename.string()] = { GetId(), GetLun() }; } void StorageDevice::UnreserveFile() { - reserved_files.erase(filename); + reserved_files.erase(filename.string()); - filename = ""; + filename.clear(); } id_set StorageDevice::GetIdsForReservedFile(const string& file) { if (const auto& it = reserved_files.find(file); it != reserved_files.end()) { - return make_pair(it->second.first, it->second.second); + return { it->second.first, it->second.second }; } - return make_pair(-1, -1); + return { -1, -1 }; } void StorageDevice::UnreserveAll() @@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll() reserved_files.clear(); } -bool StorageDevice::FileExists(const string& file) +bool StorageDevice::FileExists(string_view file) { - return exists(file); + return exists(path(file)); } bool StorageDevice::IsReadOnlyFile() const @@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const off_t StorageDevice::GetFileSize() const { - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB - if (struct stat st; !stat(filename.c_str(), &st)) { - return st.st_size; + try { + return file_size(filename); + } + catch (const filesystem_error& e) { + throw io_exception("Can't get size of '" + filename.string() + "': " + e.what()); } - - throw io_exception("Can't get size of '" + filename + "'"); } diff --git a/cpp/devices/storage_device.h b/cpp/devices/storage_device.h index 10c6b37a..85fc3c6d 100644 --- a/cpp/devices/storage_device.h +++ b/cpp/devices/storage_device.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // The base class for all mass storage devices with image file support // @@ -11,14 +11,14 @@ #pragma once +#include "shared/piscsi_util.h" #include "mode_page_device.h" #include #include +#include using namespace std; -using id_set = pair; - class StorageDevice : public ModePageDevice { public: @@ -26,24 +26,27 @@ public: StorageDevice(PbDeviceType, int); ~StorageDevice() override = default; + void CleanUp() override; + virtual void Open() = 0; - string GetFilename() const { return filename; } - void SetFilename(string_view f) { filename = f; } + string GetFilename() const { return filename.string(); } + void SetFilename(string_view); uint64_t GetBlockCount() const { return blocks; } - void ReserveFile(const string&, int, int) const; + void ReserveFile() const; void UnreserveFile(); + // TODO Remove this method, it is only used by the unit tests static void UnreserveAll(); - static bool FileExists(const string&); - bool IsReadOnlyFile() const; + static bool FileExists(string_view); void SetMediumChanged(bool b) { medium_changed = b; } - static unordered_map GetReservedFiles() { return reserved_files; } - static void SetReservedFiles(const unordered_map& r) { reserved_files = r; } + static auto GetReservedFiles() { return reserved_files; } + static void SetReservedFiles(const unordered_map>& r) + { reserved_files = r; } static id_set GetIdsForReservedFile(const string&); protected: @@ -58,13 +61,14 @@ protected: private: - // Total number of blocks + bool IsReadOnlyFile() const; + uint64_t blocks = 0; - string filename; + filesystem::path filename; bool medium_changed = false; // The list of image files in use and the IDs and LUNs using these files - static inline unordered_map reserved_files; + static inline unordered_map> reserved_files; }; diff --git a/cpp/hal/data_sample.h b/cpp/hal/data_sample.h index 189c519d..c5a57e30 100644 --- a/cpp/hal/data_sample.h +++ b/cpp/hal/data_sample.h @@ -12,6 +12,7 @@ #pragma once #include "shared/scsi.h" +#include #include using namespace scsi_defs; diff --git a/cpp/hal/gpiobus.cpp b/cpp/hal/gpiobus.cpp index e028883c..a6bf2555 100644 --- a/cpp/hal/gpiobus.cpp +++ b/cpp/hal/gpiobus.cpp @@ -13,7 +13,7 @@ #include "hal/gpiobus.h" #include "hal/sbc_version.h" #include "hal/systimer.h" -#include "shared/log.h" +#include #include #include #include @@ -280,8 +280,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) if (actmode == mode_e::TARGET) { for (i = 0; i < count; i++) { if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " + + to_string(delay_after_bytes) + " bytes"); SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); } @@ -325,8 +325,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes) for (i = 0; i < count; i++) { if (i == delay_after_bytes) { - LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US, - (int)delay_after_bytes) + spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " + + to_string(delay_after_bytes) + " bytes"); SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US); } @@ -392,7 +392,7 @@ bool GPIOBUS::PollSelectEvent() return false; #else GPIO_FUNCTION_TRACE - LOGTRACE("%s", __PRETTY_FUNCTION__) + spdlog::trace(__PRETTY_FUNCTION__); errno = 0; int prev_mode = -1; if (SBC_Version::IsBananaPi()) { @@ -401,12 +401,12 @@ bool GPIOBUS::PollSelectEvent() } if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) { - LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__) + spdlog::warn("epoll_wait failed"); return false; } if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) { - LOGWARN("%s read failed", __PRETTY_FUNCTION__) + spdlog::warn("read failed"); return false; } @@ -455,4 +455,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast) // We timed out waiting for the signal return false; -} \ No newline at end of file +} diff --git a/cpp/hal/gpiobus.h b/cpp/hal/gpiobus.h index f7f927b4..74fb820b 100644 --- a/cpp/hal/gpiobus.h +++ b/cpp/hal/gpiobus.h @@ -214,7 +214,7 @@ class GPIOBUS : public BUS // SEL signal event request struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it // epoll file descriptor - int epfd; // NOSONAR: This protected so derived classes can access it + int epfd = 0; // NOSONAR: This is protected so derived classes can access it #endif }; diff --git a/cpp/hal/gpiobus_bananam2p.cpp b/cpp/hal/gpiobus_bananam2p.cpp index a1e1a332..7d42f3a5 100644 --- a/cpp/hal/gpiobus_bananam2p.cpp +++ b/cpp/hal/gpiobus_bananam2p.cpp @@ -34,7 +34,9 @@ #include #include #include +#ifdef __linux__ #include +#endif #include #include @@ -43,7 +45,7 @@ #include "hal/pi_defs/bpi-gpio.h" #include "hal/sunxi_utils.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0]))) @@ -62,7 +64,7 @@ bool GPIOBUS_BananaM2p::Init(mode_e mode) int gpio_bank = SunXI::GPIO_BANK(gpio_num); - if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) { + if (std::ranges::find(gpio_banks, gpio_bank) != gpio_banks.end()) { LOGTRACE("Duplicate bank: %d", gpio_bank) } else { @@ -723,9 +725,9 @@ void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast) } if (sunxi_gpio_state == SunXI::HIGH) - pio->DAT &= ~(1 << num); + pio->DAT = pio->DAT & ~(1 << num); else - pio->DAT |= (1 << num); + pio->DAT = pio->DAT | (1 << num); } void GPIOBUS_BananaM2p::DisableIRQ() @@ -978,9 +980,9 @@ void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value) } if (value == 0) - pio->DAT &= ~(1 << num); + pio->DAT = pio->DAT & ~(1 << num); else - pio->DAT |= (1 << num); + pio->DAT = pio->DAT | (1 << num); } void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array &mask, array &value) @@ -1043,11 +1045,11 @@ void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud) else if (pud == SunXI::PUD_UP) *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP; else // pud == PUD_OFF - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; + *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; SunXI::short_wait(); *(gpio_map + clk_offset) = 1 << shift; SunXI::short_wait(); - *(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; + *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3; *(gpio_map + clk_offset) = 0; } diff --git a/cpp/hal/gpiobus_bananam2p.h b/cpp/hal/gpiobus_bananam2p.h index 2da28f7a..a404763f 100644 --- a/cpp/hal/gpiobus_bananam2p.h +++ b/cpp/hal/gpiobus_bananam2p.h @@ -16,7 +16,6 @@ #include "hal/pi_defs/bpi-gpio.h" #include "hal/sbc_version.h" #include "hal/sunxi_utils.h" -#include "shared/log.h" #include "shared/scsi.h" #include diff --git a/cpp/hal/gpiobus_factory.cpp b/cpp/hal/gpiobus_factory.cpp index 3a4a6914..a6fa6901 100644 --- a/cpp/hal/gpiobus_factory.cpp +++ b/cpp/hal/gpiobus_factory.cpp @@ -15,7 +15,7 @@ #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_virtual.h" #include "hal/sbc_version.h" -#include "shared/log.h" +#include using namespace std; @@ -29,13 +29,13 @@ unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) // Also make Init() private. SBC_Version::Init(); if (SBC_Version::IsBananaPi()) { - LOGTRACE("Creating GPIOBUS_BananaM2p") + spdlog::trace("Creating GPIOBUS_BananaM2p"); return_ptr = make_unique(); } else if (SBC_Version::IsRaspberryPi()) { - LOGTRACE("Creating GPIOBUS_Raspberry") + spdlog::trace("Creating GPIOBUS_Raspberry"); return_ptr = make_unique(); } else { - LOGINFO("Creating Virtual GPIOBUS") + spdlog::trace("Creating Virtual GPIOBUS"); return_ptr = make_unique(); } if (!return_ptr->Init(mode)) { @@ -43,7 +43,7 @@ unique_ptr GPIOBUS_Factory::Create(BUS::mode_e mode) } return_ptr->Reset(); } catch (const invalid_argument&) { - LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?") + spdlog::error("Exception while trying to initialize GPIO bus. Are you running as root?"); return_ptr = nullptr; } diff --git a/cpp/hal/gpiobus_raspberry.cpp b/cpp/hal/gpiobus_raspberry.cpp index fb3ba249..1fdd9ea3 100644 --- a/cpp/hal/gpiobus_raspberry.cpp +++ b/cpp/hal/gpiobus_raspberry.cpp @@ -18,10 +18,12 @@ #include "hal/gpiobus_raspberry.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #include -#include +#include +#ifdef __linux__ #include +#endif #include #include #include @@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode) // Open /dev/mem int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return false; } // Map peripheral region memory void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory: %s", strerror(errno)) + spdlog::error("Error: Unable to map memory: "+ string(strerror(errno))); close(fd); return false; } diff --git a/cpp/hal/gpiobus_raspberry.h b/cpp/hal/gpiobus_raspberry.h index 3ebd0175..b0f7fd17 100644 --- a/cpp/hal/gpiobus_raspberry.h +++ b/cpp/hal/gpiobus_raspberry.h @@ -13,7 +13,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include diff --git a/cpp/hal/gpiobus_virtual.cpp b/cpp/hal/gpiobus_virtual.cpp index 5f396d18..1e15b338 100644 --- a/cpp/hal/gpiobus_virtual.cpp +++ b/cpp/hal/gpiobus_virtual.cpp @@ -13,12 +13,14 @@ #include "hal/gpiobus_virtual.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "hal/log.h" #include #include #include #include +#ifdef __linux__ #include +#endif #include #include #include diff --git a/cpp/hal/gpiobus_virtual.h b/cpp/hal/gpiobus_virtual.h index dbfcd83a..0fba1cd0 100644 --- a/cpp/hal/gpiobus_virtual.h +++ b/cpp/hal/gpiobus_virtual.h @@ -13,7 +13,6 @@ #include "hal/data_sample_raspberry.h" #include "hal/gpiobus.h" -#include "shared/log.h" #include "shared/scsi.h" #include @@ -171,4 +170,4 @@ class GPIOBUS_Virtual final : public GPIOBUS sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem; int fd_shm, fd_log; #endif -}; \ No newline at end of file +}; diff --git a/cpp/shared/log.h b/cpp/hal/log.h similarity index 86% rename from cpp/shared/log.h rename to cpp/hal/log.h index 379a7732..31286504 100644 --- a/cpp/shared/log.h +++ b/cpp/hal/log.h @@ -9,9 +9,11 @@ // //--------------------------------------------------------------------------- +// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead. + #pragma once -#include "spdlog/spdlog.h" +#include static const int LOGBUF_SIZE = 512; diff --git a/cpp/hal/sbc_version.cpp b/cpp/hal/sbc_version.cpp index 211d8c46..99971b41 100644 --- a/cpp/hal/sbc_version.cpp +++ b/cpp/hal/sbc_version.cpp @@ -10,7 +10,7 @@ //--------------------------------------------------------------------------- #include "sbc_version.h" -#include "shared/log.h" +#include "log.h" #include #include #include diff --git a/cpp/hal/sbc_version.h b/cpp/hal/sbc_version.h index d55bdf21..36922b5a 100644 --- a/cpp/hal/sbc_version.h +++ b/cpp/hal/sbc_version.h @@ -11,6 +11,7 @@ #pragma once +#include #include #include diff --git a/cpp/hal/sunxi_utils.cpp b/cpp/hal/sunxi_utils.cpp index 8c103d00..f4b150a1 100644 --- a/cpp/hal/sunxi_utils.cpp +++ b/cpp/hal/sunxi_utils.cpp @@ -40,12 +40,6 @@ using namespace std; -static const string BLACK = "\033[30m"; /* Black */ -static const string RED = "\033[31m"; /* Red */ -static const string GREEN = "\033[32m"; /* Green */ -static const string YELLOW = "\033[33m"; /* Yellow */ -static const string BLUE = "\033[34m"; /* Blue */ -static const string MAGENTA = "\033[35m"; /* Magenta */ static const string CYAN = "\033[36m"; /* Cyan */ static const string WHITE = "\033[37m"; /* White */ @@ -62,4 +56,4 @@ void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs) printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]); printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB, WHITE.c_str()); -} \ No newline at end of file +} diff --git a/cpp/hal/systimer.cpp b/cpp/hal/systimer.cpp index 8ae8635f..21dc7b22 100644 --- a/cpp/hal/systimer.cpp +++ b/cpp/hal/systimer.cpp @@ -14,13 +14,12 @@ #include "hal/systimer.h" #include "hal/systimer_allwinner.h" #include "hal/systimer_raspberry.h" +#include #include #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" - bool SysTimer::initialized = false; bool SysTimer::is_allwinnner = false; bool SysTimer::is_raspberry = false; @@ -29,7 +28,7 @@ std::unique_ptr SysTimer::systimer_ptr; void SysTimer::Init() { - LOGTRACE("%s", __PRETTY_FUNCTION__) + spdlog::trace(__PRETTY_FUNCTION__); if (!initialized) { if (SBC_Version::IsRaspberryPi()) { diff --git a/cpp/hal/systimer_allwinner.cpp b/cpp/hal/systimer_allwinner.cpp index b698c405..077dba57 100644 --- a/cpp/hal/systimer_allwinner.cpp +++ b/cpp/hal/systimer_allwinner.cpp @@ -14,7 +14,7 @@ #include "hal/gpiobus.h" -#include "shared/log.h" +#include "hal/log.h" const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem"; @@ -71,14 +71,14 @@ void SysTimer_AllWinner::enable_hs_timer() hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF) hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX; - // Select prescale value of 1, continuouse mode + // Select prescale value of 1, continuous mode hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1; // Set reload bit - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD; + hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_RELOAD; // Enable HSTimer - hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN; + hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_EN; } // TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to diff --git a/cpp/hal/systimer_raspberry.cpp b/cpp/hal/systimer_raspberry.cpp index 6ae647a4..249642cb 100644 --- a/cpp/hal/systimer_raspberry.cpp +++ b/cpp/hal/systimer_raspberry.cpp @@ -12,6 +12,7 @@ //--------------------------------------------------------------------------- #include "hal/systimer_raspberry.h" +#include #include #include #include @@ -19,8 +20,6 @@ #include "hal/gpiobus.h" #include "hal/sbc_version.h" -#include "shared/log.h" - // System timer address volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr; // ARM timer address @@ -40,14 +39,14 @@ void SysTimer_Raspberry::Init() // Open /dev/mem int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); if (mem_fd == -1) { - LOGERROR("Error: Unable to open /dev/mem. Are you running as root?") + spdlog::error("Error: Unable to open /dev/mem. Are you running as root?"); return; } // Map peripheral region memory void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr); if (map == MAP_FAILED) { - LOGERROR("Error: Unable to map memory") + spdlog::error("Error: Unable to map memory"); close(mem_fd); return; } diff --git a/cpp/monitor/sm_core.cpp b/cpp/monitor/sm_core.cpp index dbb12fff..9805bab4 100644 --- a/cpp/monitor/sm_core.cpp +++ b/cpp/monitor/sm_core.cpp @@ -10,11 +10,10 @@ //--------------------------------------------------------------------------- #include "monitor/sm_core.h" -#include "hal/data_sample.h" #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "monitor/sm_reports.h" -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include @@ -77,28 +76,28 @@ void ScsiMon::ParseArguments(const vector &args) void ScsiMon::PrintHelpText(const vector &args) const { - LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0]) - LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data") - LOGINFO(" If -i option is not specified, scsimon will read the gpio pins") - LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size) - LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)") - LOGINFO(" will be appended to this file name") + spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]"); + spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data"); + spdlog::info(" If -i option is not specified, scsimon will read the gpio pins"); + spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size)); + spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)"); + spdlog::info(" will be appended to this file name"); } void ScsiMon::Banner() const { if (import_data) { - LOGINFO("Reading input file: %s", input_file_name.c_str()) + spdlog::info("Reading input file: " + input_file_name); } else { - LOGINFO("Reading live data from the GPIO pins") - LOGINFO(" Connection type : %s", CONNECT_DESC.c_str()) + spdlog::info("Reading live data from the GPIO pins"); + spdlog::info(" Connection type: " + CONNECT_DESC); } - LOGINFO(" Data buffer size: %u", buff_size) - LOGINFO(" ") - LOGINFO("Generating output files:") - LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name.c_str()) - LOGINFO(" %s - JSON file with raw data", json_file_name.c_str()) - LOGINFO(" %s - HTML file with summary of commands", html_file_name.c_str()) + spdlog::info(" Data buffer size: " + to_string(buff_size)); + spdlog::info(" "); + spdlog::info("Generating output files:"); + spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave"); + spdlog::info(" " + json_file_name + " - JSON file with raw data"); + spdlog::info(" " + html_file_name + " - HTML file with summary of commands"); } bool ScsiMon::Init() @@ -116,7 +115,7 @@ bool ScsiMon::Init() bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { - LOGERROR("Unable to intiailize the GPIO bus. Exiting....") + spdlog::error("Unable to intiailize the GPIO bus. Exiting...."); return false; } @@ -128,14 +127,14 @@ bool ScsiMon::Init() void ScsiMon::Cleanup() const { if (!import_data) { - LOGINFO("Stopping data collection....") + spdlog::info("Stopping data collection ..."); } - LOGINFO(" ") - LOGINFO("Generating %s...", vcd_file_name.c_str()) + spdlog::info(" "); + spdlog::info("Generating " + vcd_file_name + "..."); scsimon_generate_value_change_dump(vcd_file_name, data_buffer); - LOGINFO("Generating %s...", json_file_name.c_str()) + spdlog::info("Generating " + json_file_name + "..."); scsimon_generate_json(json_file_name, data_buffer); - LOGINFO("Generating %s...", html_file_name.c_str()) + spdlog::info("Generating " + html_file_name + "..."); scsimon_generate_html(html_file_name, data_buffer); bus->Cleanup(); @@ -179,15 +178,15 @@ int ScsiMon::run(const vector &args) if (import_data) { data_idx = scsimon_read_json(input_file_name, data_buffer); if (data_idx > 0) { - LOGDEBUG("Read %d samples from %s", data_idx, input_file_name.c_str()) + spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'"); Cleanup(); } exit(0); } - LOGINFO(" ") - LOGINFO("Now collecting data.... Press CTRL-C to stop.") - LOGINFO(" ") + spdlog::info(" "); + spdlog::info("Now collecting data.... Press CTRL-C to stop."); + spdlog::info(" "); // Initialize int ret = 0; @@ -219,12 +218,12 @@ int ScsiMon::run(const vector &args) while (running) { loop_count++; if (loop_count > LLONG_MAX - 1) { - LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.") + spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating."); running = false; } if (data_idx >= (buff_size - 2)) { - LOGINFO("Internal data buffer is full. SCSIMON is terminating.") + spdlog::info("Internal data buffer is full. SCSIMON is terminating."); running = false; } @@ -249,15 +248,13 @@ int ScsiMon::run(const vector &args) timersub(&stop_time, &start_time, &time_diff); elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec); - LOGINFO("%s", ("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + - " seconds") - .c_str()) - LOGINFO("%s", ("Collected " + to_string(data_idx) + " changes").c_str()) + spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) + + " seconds"); + spdlog::info("Collected " + to_string(data_idx) + " changes"); ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count; - LOGINFO("%s", ("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + - to_string(ns_per_loop) + " ns for each read") - .c_str()) + spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " + + to_string(ns_per_loop) + " ns for each read"); Cleanup(); diff --git a/cpp/monitor/sm_core.h b/cpp/monitor/sm_core.h index 1ee25675..25c18d1e 100644 --- a/cpp/monitor/sm_core.h +++ b/cpp/monitor/sm_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "hal/data_sample.h" #include #include +#include using namespace std; @@ -37,7 +38,7 @@ class ScsiMon static void KillHandler(int); - static inline volatile bool running; + static inline atomic running; shared_ptr bus; diff --git a/cpp/monitor/sm_html_report.cpp b/cpp/monitor/sm_html_report.cpp index 7b7481f6..8b778ccd 100644 --- a/cpp/monitor/sm_html_report.cpp +++ b/cpp/monitor/sm_html_report.cpp @@ -9,7 +9,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "sm_reports.h" #include @@ -173,7 +173,7 @@ static void print_html_data(ofstream& html_fp, const vector> &data_capture_array) { - LOGINFO("Creating HTML report file (%s)", filename.c_str()) + spdlog::info("Creating HTML report file (" + filename + ")"); ofstream html_ofstream; diff --git a/cpp/monitor/sm_json_report.cpp b/cpp/monitor/sm_json_report.cpp index de06215e..faa6f0e0 100644 --- a/cpp/monitor/sm_json_report.cpp +++ b/cpp/monitor/sm_json_report.cpp @@ -10,7 +10,7 @@ //--------------------------------------------------------------------------- #include "hal/data_sample_raspberry.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_reports.h" #include "string.h" #include @@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector> &data_capture_array) { - LOGTRACE("Creating JSON file (%s)", filename.c_str()) + spdlog::trace("Creating JSON file (" + filename + ")"); ofstream json_ofstream; json_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/monitor/sm_vcd_report.cpp b/cpp/monitor/sm_vcd_report.cpp index 5f8f1d68..be6f1035 100644 --- a/cpp/monitor/sm_vcd_report.cpp +++ b/cpp/monitor/sm_vcd_report.cpp @@ -11,7 +11,7 @@ #include "hal/data_sample.h" #include "hal/gpiobus.h" -#include "shared/log.h" +#include "hal/log.h" #include "sm_core.h" #include "sm_reports.h" #include @@ -77,7 +77,7 @@ static void vcd_output_if_changed_byte(ofstream &fp, uint8_t data, int pin, char void scsimon_generate_value_change_dump(const string &filename, const vector> &data_capture_array) { - LOGTRACE("Creating Value Change Dump file (%s)", filename.c_str()) + spdlog::trace("Creating Value Change Dump file (" + filename + ")"); ofstream vcd_ofstream; vcd_ofstream.open(filename.c_str(), ios::out); diff --git a/cpp/piscsi.cpp b/cpp/piscsi.cpp index ab8a674c..9b632aec 100644 --- a/cpp/piscsi.cpp +++ b/cpp/piscsi.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return Piscsi().run(args); } diff --git a/cpp/piscsi/command_context.cpp b/cpp/piscsi/command_context.cpp index ce11e6ef..5b381d9f 100644 --- a/cpp/piscsi/command_context.cpp +++ b/cpp/piscsi/command_context.cpp @@ -3,24 +3,50 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "generated/piscsi_interface.pb.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "command_context.h" +#include #include using namespace std; using namespace piscsi_interface; +using namespace protobuf_util; -void CommandContext::Cleanup() +bool CommandContext::ReadCommand() { - if (fd != -1) { - close(fd); - fd = -1; + // Read magic string + array magic; + if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) { + if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { + throw io_exception("Invalid magic"); + } + + // Fetch the command + DeserializeMessage(fd, command); + + return true; } + + return false; +} + +void CommandContext::WriteResult(const PbResult& result) const +{ + // The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool + if (fd != -1) { + SerializeMessage(fd, result); + } +} + +void CommandContext::WriteSuccessResult(PbResult& result) const +{ + result.set_status(true); + WriteResult(result); } bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2, @@ -33,7 +59,7 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error const string& arg2, const string& arg3) const { // For the logfile always use English - LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str()) + spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3)); return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false); } @@ -42,17 +68,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er { // Do not log twice if logging has already been done in the localized error handling above if (log && !status && !msg.empty()) { - LOGERROR("%s", msg.c_str()) + spdlog::error(msg); } if (fd == -1) { if (!msg.empty()) { - if (status) { - cerr << "Error: " << msg << endl; - } - else { - cout << msg << endl; - } + cerr << "Error: " << msg << endl; } } else { @@ -60,8 +81,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er result.set_status(status); result.set_error_code(error_code); result.set_msg(msg); - serializer.SerializeMessage(fd, result); + WriteResult(result); } return status; } + +bool CommandContext::ReturnSuccessStatus() const +{ + return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true); +} + +bool CommandContext::ReturnErrorStatus(const string& msg) const +{ + return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true); +} diff --git a/cpp/piscsi/command_context.h b/cpp/piscsi/command_context.h index 3fcc4e20..dcbb2d20 100644 --- a/cpp/piscsi/command_context.h +++ b/cpp/piscsi/command_context.h @@ -3,15 +3,14 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include "localizer.h" -#include "shared/protobuf_serializer.h" +#include "generated/piscsi_interface.pb.h" #include using namespace std; @@ -19,27 +18,36 @@ using namespace piscsi_interface; class CommandContext { - const ProtobufSerializer serializer; - - const Localizer localizer; - - string locale; - - int fd; public: - CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {} + CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {} + explicit CommandContext(int f) : fd(f) {} ~CommandContext() = default; - void Cleanup(); - - const ProtobufSerializer& GetSerializer() const { return serializer; } - int GetFd() const { return fd; } - void SetFd(int f) { fd = f; } - bool IsValid() const { return fd != -1; } + string GetDefaultFolder() const { return default_folder; } + void SetDefaultFolder(string_view f) { default_folder = f; } + bool ReadCommand(); + void WriteResult(const PbResult&) const; + void WriteSuccessResult(PbResult&) const; + const PbCommand& GetCommand() const { return command; } bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const; - bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const; + bool ReturnSuccessStatus() const; + bool ReturnErrorStatus(const string&) const; + +private: + + bool ReturnStatus(bool, const string&, PbErrorCode, bool) const; + + const Localizer localizer; + + PbCommand command; + + string default_folder; + + string locale; + + int fd = -1; }; diff --git a/cpp/piscsi/localizer.cpp b/cpp/piscsi/localizer.cpp index bcb17297..17c92034 100644 --- a/cpp/piscsi/localizer.cpp +++ b/cpp/piscsi/localizer.cpp @@ -3,15 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "localizer.h" #include -#include #include -#include using namespace std; @@ -31,12 +29,12 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida"); Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido"); - Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido"); + Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID"); @@ -51,26 +49,26 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名"); - + Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo"); Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名"); - - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3"); - Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用"); - + + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2"); + Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2"); + Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'"); Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息"); - + Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved"); Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert"); Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat"); @@ -84,21 +82,21 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente"); Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令"); - + Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2"); Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令"); - + Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1"); Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1"); - + Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'"); Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'"); @@ -199,11 +197,11 @@ Localizer::Localizer() Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN"); Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。"); - Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed"); - Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen"); - Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3"); - Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló"); - Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败"); + Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed"); + Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen"); + Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 "); + Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló"); + Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable"); Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar"); @@ -235,17 +233,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value // Safeguards against empty messages, duplicate entries and unsupported locales assert(locale.size()); assert(value.size()); - assert(supported_languages.find(locale) != supported_languages.end()); + assert(supported_languages.contains(locale)); assert(localized_messages[locale][key].empty()); - localized_messages[locale][key] = value; } string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2, const string &arg3) const { - string locale_lower = locale; - transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower); + string locale_lower; + ranges::transform(locale, back_inserter(locale_lower), ::tolower); auto it = localized_messages.find(locale_lower); if (it == localized_messages.end()) { @@ -268,9 +265,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri } string message = m->second; - message = regex_replace(message, regex("%1"), arg1); - message = regex_replace(message, regex("%2"), arg2); - message = regex_replace(message, regex("%3"), arg3); + message = regex_replace(message, regex1, arg1); + message = regex_replace(message, regex2, arg2); + message = regex_replace(message, regex3, arg3); return message; } diff --git a/cpp/piscsi/localizer.h b/cpp/piscsi/localizer.h index c39f4fdb..e1153ec8 100644 --- a/cpp/piscsi/localizer.h +++ b/cpp/piscsi/localizer.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Message localization support. Currently only for messages with up to 3 string parameters. // @@ -11,9 +11,11 @@ #pragma once +#include "shared/piscsi_util.h" #include #include #include +#include using namespace std; @@ -64,8 +66,12 @@ public: private: void Add(LocalizationKey, const string&, string_view); - unordered_map> localized_messages; + unordered_map, piscsi_util::StringHash, equal_to<>> localized_messages; // Supported locales, always lower case - unordered_set supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + unordered_set> supported_languages = { "en", "de", "sv", "fr", "es", "zh" }; + + const regex regex1 = regex("%1"); + const regex regex2 = regex("%2"); + const regex regex3 = regex("%3"); }; diff --git a/cpp/piscsi/piscsi_core.cpp b/cpp/piscsi/piscsi_core.cpp index c5391ce0..e2bf056e 100644 --- a/cpp/piscsi/piscsi_core.cpp +++ b/cpp/piscsi/piscsi_core.cpp @@ -1,57 +1,52 @@ //--------------------------------------------------------------------------- // -// SCSI Target Emulator PiSCSI -// for Raspberry Pi +// SCSI Target Emulator PiSCSI +// for Raspberry Pi // -// Powered by XM6 TypeG Technology. -// Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Powered by XM6 TypeG Technology. +// Copyright (C) 2016-2020 GIMONS +// Copyright (C) 2020-2023 Contributors to the PiSCSI project +// Copyright (C) 2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "shared/config.h" -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" +#include "devices/device_logger.h" #include "devices/device_factory.h" #include "devices/storage_device.h" #include "hal/gpiobus_factory.h" #include "hal/gpiobus.h" #include "hal/systimer.h" -#include "piscsi/piscsi_executor.h" #include "piscsi/piscsi_core.h" -#include "spdlog/sinks/stdout_color_sinks.h" +#include #include #include -#include #include #include #include -#include -#include +#include using namespace std; using namespace filesystem; -using namespace spdlog; using namespace piscsi_interface; using namespace piscsi_util; using namespace protobuf_util; using namespace scsi_defs; -void Piscsi::Banner(const vector& args) const +void Piscsi::Banner(span args) const { cout << piscsi_util::Banner("(Backend Service)"); cout << "Connection type: " << CONNECT_DESC << '\n' << flush; if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){ cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n"; - cout << " ID is SCSI device ID (0-7).\n"; - cout << " LUN is the optional logical unit (0-31).\n"; + cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"; + cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n"; cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n"; cout << " Image type is detected based on file extension if no explicit type is specified.\n"; cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n"; @@ -69,59 +64,57 @@ void Piscsi::Banner(const vector& args) const } } -bool Piscsi::InitBus() const +bool Piscsi::InitBus() { bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET); if (bus == nullptr) { return false; } - auto b = bus; - controller_manager = make_shared(*b); - auto c = controller_manager; - executor = make_shared(piscsi_image, *c); + executor = make_unique(piscsi_image, *bus, controller_manager); return true; } -void Piscsi::Cleanup() +void Piscsi::CleanUp() { - executor->DetachAll(); + if (service.IsRunning()) { + service.Stop(); + } - service.Cleanup(); + executor->DetachAll(); bus->Cleanup(); } -void Piscsi::ReadAccessToken(const string& filename) const +void Piscsi::ReadAccessToken(const path& filename) { - struct stat st; - if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) { - throw parser_exception("Can't access token file '" + filename + "'"); + if (error_code error; !is_regular_file(filename, error)) { + throw parser_exception("Access token file '" + filename.string() + "' must be a regular file"); } - if (st.st_uid || st.st_gid) { - throw parser_exception("Access token file '" + filename + "' must be owned by root"); + if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) { + throw parser_exception("Access token file '" + filename.string() + "' must be owned by root"); } if (const auto perms = filesystem::status(filename).permissions(); (perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none || (perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) { - throw parser_exception("Access token file '" + filename + "' must be readable by root only"); + throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only"); } ifstream token_file(filename); if (token_file.fail()) { - throw parser_exception("Can't open access token file '" + filename + "'"); + throw parser_exception("Can't open access token file '" + filename.string() + "'"); } getline(token_file, access_token); if (token_file.fail()) { - throw parser_exception("Can't read access token file '" + filename + "'"); + throw parser_exception("Can't read access token file '" + filename.string() + "'"); } if (access_token.empty()) { - throw parser_exception("Access token file '" + filename + "' must not be empty"); + throw parser_exception("Access token file '" + filename.string() + "' must not be empty"); } } @@ -131,59 +124,74 @@ void Piscsi::LogDevices(string_view devices) const string line; while (getline(ss, line, '\n')) { - LOGINFO("%s", line.c_str()) + spdlog::info(line); } } -PbDeviceType Piscsi::ParseDeviceType(const string& value) const -{ - string t = value; - PbDeviceType type; - transform(t.begin(), t.end(), t.begin(), ::toupper); - if (!PbDeviceType_Parse(t, &type)) { - throw parser_exception("Illegal device type '" + value + "'"); - } - - return type; -} - void Piscsi::TerminationHandler(int) { - Cleanup(); + instance->CleanUp(); // Process will terminate automatically } -Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& port) const +string Piscsi::ParseArguments(span args, PbCommand& command, int& port, string& reserved_ids) { - optargs_type optargs; + string log_level = "info"; + PbDeviceType type = UNDEFINED; + int block_size = 0; string name; + string id_and_lun; + + string locale = GetLocale(); + + // Avoid duplicate messages while parsing + set_level(level::off); opterr = 1; int opt; while ((opt = getopt(static_cast(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) { switch (opt) { - // The following options can not be processed until AFTER - // the 'bus' object is created and configured + // The two options below are kind of a compound option with two letters case 'i': case 'I': - case 'b': + continue; + case 'd': case 'D': - case 'R': - case 'n': - case 'r': - case 't': - case 'F': - case 'z': - { - const string optarg_str = optarg == nullptr ? "" : optarg; - optargs.emplace_back(opt, optarg_str); + id_and_lun = optarg; + continue; + + case 'b': + if (!GetAsUnsignedInt(optarg, block_size)) { + throw parser_exception("Invalid block size " + string(optarg)); + } + continue; + + case 'z': + locale = optarg; + continue; + + case 'F': + if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) { + throw parser_exception(error); + } continue; - } case 'L': - current_log_level = optarg; + log_level = optarg; + continue; + + case 'R': + int depth; + if (!GetAsUnsignedInt(optarg, depth)) { + throw parser_exception("Invalid image file scan depth " + string(optarg)); + } + piscsi_image.SetDepth(depth); + continue; + + case 'n': + name = optarg; continue; case 'p': @@ -196,92 +204,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector& args, int& por ReadAccessToken(optarg); continue; - case 'v': - cout << piscsi_get_version_string() << endl; - exit(0); - - case 1: - { - // Encountered filename - const string optarg_str = (optarg == nullptr) ? "" : string(optarg); - optargs.emplace_back(opt, optarg_str); - continue; - } - - default: - throw parser_exception("Parser error"); - } - - if (optopt) { - throw parser_exception("Parser error"); - } - } - - return optargs; -} - -void Piscsi::CreateInitialDevices(const optargs_type& optargs) const -{ - PbCommand command; - PbDeviceType type = UNDEFINED; - int block_size = 0; - string name; - string log_level; - string id_and_lun; - - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } - - opterr = 1; - for (const auto& [option, value] : optargs) { - switch (option) { - case 'i': - case 'I': - continue; - - case 'd': - case 'D': - id_and_lun = value; - continue; - - case 'b': - if (!GetAsUnsignedInt(value, block_size)) { - throw parser_exception("Invalid block size " + value); - } - continue; - - case 'z': - locale = value.c_str(); - continue; - - case 'F': - if (const string error = piscsi_image.SetDefaultFolder(value); !error.empty()) { - throw parser_exception(error); - } - continue; - - case 'R': - int depth; - if (!GetAsUnsignedInt(value, depth)) { - throw parser_exception("Invalid image file scan depth " + value); - } - piscsi_image.SetDepth(depth); - continue; - - case 'n': - name = value; - continue; - case 'r': - if (const string error = executor->SetReservedIds(value); !error.empty()) { - throw parser_exception(error); - } + reserved_ids = optarg; continue; case 't': - type = ParseDeviceType(value); + type = ParseDeviceType(optarg); continue; case 1: @@ -292,10 +220,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const throw parser_exception("Parser error"); } - PbDeviceDefinition *device = command.add_devices(); + if (optopt) { + throw parser_exception("Parser error"); + } + + // Set up the device data + + auto device = command.add_devices(); if (!id_and_lun.empty()) { - if (const string error = SetIdAndLun(*device, id_and_lun, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) { throw parser_exception(error); } } @@ -303,7 +237,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const device->set_type(type); device->set_block_size(block_size); - ParseParameters(*device, value); + ParseParameters(*device, optarg); SetProductData(*device, name); @@ -313,182 +247,231 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const id_and_lun = ""; } - // Attach all specified devices - command.set_operation(ATTACH); - - if (CommandContext context(locale); !executor->ProcessCmd(context, command)) { - throw parser_exception("Can't execute " + PbOperation_Name(command.operation())); + if (!SetLogLevel(log_level)) { + throw parser_exception("Invalid log level '" + log_level + "'"); } - // Display and log the device list - PbServerInfo server_info; - piscsi_response.GetDevices(controller_manager->GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); - const list& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - const string device_list = ListDevices(devices); - LogDevices(device_list); - cout << device_list << flush; + return locale; } -bool Piscsi::ExecuteCommand(const CommandContext& context, const PbCommand& command) +PbDeviceType Piscsi::ParseDeviceType(const string& value) { + string t; + ranges::transform(value, back_inserter(t), ::toupper); + if (PbDeviceType type; PbDeviceType_Parse(t, &type)) { + return type; + } + + throw parser_exception("Illegal device type '" + value + "'"); +} + +bool Piscsi::SetLogLevel(const string& log_level) const +{ + int id = -1; + int lun = -1; + string level = log_level; + + if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) { + level = components[0]; + + if (components.size() > 1) { + if (const string error = ProcessId(components[1], id, lun); !error.empty()) { + spdlog::warn("Error setting log level: " + error); + return false; + } + } + } + + const level::level_enum l = level::from_str(level); + // Compensate for spdlog using 'off' for unknown levels + if (to_string_view(l) != level) { + spdlog::warn("Invalid log level '" + level + "'"); + return false; + } + + set_level(l); + DeviceLogger::SetLogIdAndLun(id, lun); + + if (id != -1) { + if (lun == -1) { + spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'"); + } + else { + spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'"); + } + } + else { + spdlog::info("Set log level to '" + level + "'"); + } + + return true; +} + +bool Piscsi::ExecuteCommand(CommandContext& context) +{ + context.SetDefaultFolder(piscsi_image.GetDefaultFolder()); + + const PbCommand& command = context.GetCommand(); + if (!access_token.empty() && access_token != GetParam(command, "token")) { return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); } if (!PbOperation_IsValid(command.operation())) { - LOGERROR("Received unknown command with operation opcode %d", command.operation()) + spdlog::error("Received unknown command with operation opcode " + to_string(command.operation())); return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION); } - LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()) + spdlog::trace("Received " + PbOperation_Name(command.operation()) + " command"); PbResult result; - ProtobufSerializer serializer; switch(command.operation()) { - case LOG_LEVEL: { - const string log_level = GetParam(command, "level"); - if (const bool status = executor->SetLogLevel(log_level); !status) { + case LOG_LEVEL: + if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) { context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); } else { - current_log_level = log_level; - - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEFAULT_FOLDER: { - if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { - context.ReturnStatus(false, status); + case DEFAULT_FOLDER: + if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) { + context.ReturnErrorStatus(error); } else { - context.ReturnStatus(); + context.ReturnSuccessStatus(); } break; - } - case DEVICES_INFO: { - piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICES_INFO: + response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder()); + context.WriteResult(result); break; - } - case DEVICE_TYPES_INFO: { - result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case DEVICE_TYPES_INFO: + response.GetDeviceTypesInfo(*result.mutable_device_types_info()); + context.WriteSuccessResult(result); break; - } - case SERVER_INFO: { - result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(), - result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(), - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case SERVER_INFO: + response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(), + executor->GetReservedIds(), piscsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case VERSION_INFO: { - result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case VERSION_INFO: + response.GetVersionInfo(*result.mutable_version_info()); + context.WriteSuccessResult(result); break; - } - case LOG_LEVEL_INFO: { - result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release()); - serializer.SerializeMessage(context.GetFd(), result); + case LOG_LEVEL_INFO: + response.GetLogLevelInfo(*result.mutable_log_level_info()); + context.WriteSuccessResult(result); break; - } - case DEFAULT_IMAGE_FILES_INFO: { - result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result, - piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), - GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case DEFAULT_IMAGE_FILES_INFO: + response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case IMAGE_FILE_INFO: { + case IMAGE_FILE_INFO: if (string filename = GetParam(command, "file"); filename.empty()) { context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); } else { auto image_file = make_unique(); - const bool status = piscsi_response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), filename); + const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), + filename); if (status) { - result.set_status(true); result.set_allocated_image_file_info(image_file.get()); - serializer.SerializeMessage(context.GetFd(), result); + result.set_status(true); + context.WriteResult(result); } else { context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); } } break; - } - case NETWORK_INTERFACES_INFO: { - result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case NETWORK_INTERFACES_INFO: + response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info()); + context.WriteSuccessResult(result); break; - } - case MAPPING_INFO: { - result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release()); - serializer.SerializeMessage(context.GetFd(), result); + case MAPPING_INFO: + response.GetMappingInfo(*result.mutable_mapping_info()); + context.WriteSuccessResult(result); break; - } - case OPERATION_INFO: { - result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result, - piscsi_image.GetDepth()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case OPERATION_INFO: + response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth()); + context.WriteSuccessResult(result); break; - } - case RESERVED_IDS_INFO: { - result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result, - executor->GetReservedIds()).release()); - serializer.SerializeMessage(context.GetFd(), result); + case RESERVED_IDS_INFO: + response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds()); + context.WriteSuccessResult(result); break; - } - case SHUT_DOWN: { + case SHUT_DOWN: if (executor->ShutDown(context, GetParam(command, "mode"))) { TerminationHandler(0); } break; - } - default: { - // Wait until we become idle + case NO_OPERATION: + context.ReturnSuccessStatus(); + break; + + // TODO The image operations below can most likely directly be executed without calling the executor, + // because they do not require the target to be idle + case CREATE_IMAGE: + case DELETE_IMAGE: + case RENAME_IMAGE: + case COPY_IMAGE: + case PROTECT_IMAGE: + case UNPROTECT_IMAGE: + case RESERVE_IDS: + return executor->ProcessCmd(context); + + // The remaining commands can only be executed when the target is idle + // TODO What happens when the target becomes active while the command is still being executed? + // A field 'mutex locker' can probably avoid SCSI commands and ProcessCmd() being executed at the same time + default: + // TODO Find a better way to wait const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000}; - while (active) { + while (target_is_active) { nanosleep(&ts, nullptr); } - - executor->ProcessCmd(context, command); - break; - } + return executor->ProcessCmd(context); } return true; } -int Piscsi::run(const vector& args) +int Piscsi::run(span args) { GOOGLE_PROTOBUF_VERIFY_VERSION; Banner(args); + // The -v option shall result in no other action except displaying the version + if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) { + cout << piscsi_get_version_string() << '\n'; + return EXIT_SUCCESS; + } + + PbCommand command; + string locale; + string reserved_ids; int port = DEFAULT_PORT; - optargs_type optargs; try { - optargs = ParseArguments(args, port); + locale = ParseArguments(args, command, port, reserved_ids); } catch(const parser_exception& e) { cerr << "Error: " << e.what() << endl; @@ -496,35 +479,51 @@ int Piscsi::run(const vector& args) return EXIT_FAILURE; } - // current_log_level may have been updated by ParseArguments() - executor->SetLogLevel(current_log_level); - - // Create a thread-safe stdout logger to process the log messages - const auto logger = stdout_color_mt("piscsi stdout logger"); - if (!InitBus()) { cerr << "Error: Can't initialize bus" << endl; return EXIT_FAILURE; } - // We need to wait to create the devices until after the bus/controller/etc objects have been created - // TODO Try to resolve dependencies so that this work-around can be removed - try { - CreateInitialDevices(optargs); - } - catch(const parser_exception& e) { - cerr << "Error: " << e.what() << endl; + if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port); + !error.empty()) { + cerr << "Error: " << error << endl; - Cleanup(); + CleanUp(); return EXIT_FAILURE; } - if (!service.Init(&ExecuteCommand, port)) { + if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) { + cerr << "Error: " << error << endl; + + CleanUp(); + return EXIT_FAILURE; } + if (command.devices_size()) { + // Attach all specified devices + command.set_operation(ATTACH); + + if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) { + cerr << "Error: Can't attach devices" << endl; + + CleanUp(); + + return EXIT_FAILURE; + } + } + + // Display and log the device list + PbServerInfo server_info; + response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder()); + const vector& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + const string device_list = ListDevices(devices); + LogDevices(device_list); + cout << device_list << flush; + + instance = this; // Signal handler to detach all devices on a KILL or TERM signal struct sigaction termination_handler; termination_handler.sa_handler = TerminationHandler; @@ -532,22 +531,30 @@ int Piscsi::run(const vector& args) termination_handler.sa_flags = 0; sigaction(SIGINT, &termination_handler, nullptr); sigaction(SIGTERM, &termination_handler, nullptr); + signal(SIGPIPE, SIG_IGN); // Set the affinity to a specific processor core FixCpu(3); - sched_param schparam; + service.Start(); + + Process(); + + return EXIT_SUCCESS; +} + +void Piscsi::Process() +{ #ifdef USE_SEL_EVENT_ENABLE // Scheduling policy setting (highest priority) + // TODO Check whether this results in any performance gain + sched_param schparam; schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); sched_setscheduler(0, SCHED_FIFO, &schparam); #else cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl; #endif - // Start execution - service.SetRunning(true); - // Main Loop while (service.IsRunning()) { #ifdef USE_SEL_EVENT_ENABLE @@ -571,81 +578,55 @@ int Piscsi::run(const vector& args) } #endif - // Wait until BSY is released as there is a possibility for the - // initiator to assert it while setting the ID (for up to 3 seconds) - WaitForNotBusy(); + // Only process the SCSI command if the bus is not busy and no other device responded + if (IsNotBusy() && bus->GetSEL()) { + target_is_active = true; - // Stop because the bus is busy or another device responded - if (bus->GetBSY() || !bus->GetSEL()) { - continue; - } - - int initiator_id = AbstractController::UNKNOWN_INITIATOR_ID; - - // The initiator and target ID - const uint8_t id_data = bus->GetDAT(); - - phase_t phase = phase_t::busfree; - - // Identify the responsible controller - auto controller = controller_manager->IdentifyController(id_data); - if (controller != nullptr) { - device_logger.SetIdAndLun(controller->GetTargetId(), -1); - - initiator_id = controller->ExtractInitiatorId(id_data); - - if (initiator_id != AbstractController::UNKNOWN_INITIATOR_ID) { - device_logger.Trace("++++ Starting processing for initiator ID " + to_string(initiator_id)); - } - else { - device_logger.Trace("++++ Starting processing for unknown initiator ID"); + // Process command on the responsible controller based on the current initiator and target ID + if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT()); + shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) { + // When the bus is free PiSCSI or the Pi may be shut down. + ShutDown(shutdown_mode); } - if (controller->Process(initiator_id) == phase_t::selection) { - phase = phase_t::selection; - } + target_is_active = false; } - - // Return to bus monitoring if the selection phase has not started - if (phase != phase_t::selection) { - continue; - } - - // Start target device - active = true; - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Scheduling policy setting (highest priority) - schparam.sched_priority = sched_get_priority_max(SCHED_FIFO); - sched_setscheduler(0, SCHED_FIFO, &schparam); -#endif - - // Loop until the bus is free - while (service.IsRunning()) { - // Target drive - phase = controller->Process(initiator_id); - - // End when the bus is free - if (phase == phase_t::busfree) { - break; - } - } - -#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__) - // Set the scheduling priority back to normal - schparam.sched_priority = 0; - sched_setscheduler(0, SCHED_OTHER, &schparam); -#endif - - // End the target travel - active = false; } - - return EXIT_SUCCESS; } -void Piscsi::WaitForNotBusy() const +void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode) { + CleanUp(); + + switch(shutdown_mode) { + case AbstractController::piscsi_shutdown_mode::STOP_PISCSI: + spdlog::info("PiSCSI shutdown requested"); + break; + + case AbstractController::piscsi_shutdown_mode::STOP_PI: + spdlog::info("Raspberry Pi shutdown requested"); + if (system("init 0") == -1) { + spdlog::error("Raspberry Pi shutdown failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::RESTART_PI: + spdlog::info("Raspberry Pi restart requested"); + if (system("init 6") == -1) { + spdlog::error("Raspberry Pi restart failed"); + } + break; + + case AbstractController::piscsi_shutdown_mode::NONE: + assert(false); + break; + } +} + +bool Piscsi::IsNotBusy() const +{ + // Wait until BSY is released as there is a possibility for the + // initiator to assert it while setting the ID (for up to 3 seconds) if (bus->GetBSY()) { const uint32_t now = SysTimer::GetTimerLow(); @@ -654,8 +635,12 @@ void Piscsi::WaitForNotBusy() const bus->Acquire(); if (!bus->GetBSY()) { - break; + return true; } } + + return false; } + + return true; } diff --git a/cpp/piscsi/piscsi_core.h b/cpp/piscsi/piscsi_core.h index 22cb158b..fea8dd73 100644 --- a/cpp/piscsi/piscsi_core.h +++ b/cpp/piscsi/piscsi_core.h @@ -3,31 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "devices/device_logger.h" +#include "controllers/controller_manager.h" +#include "controllers/abstract_controller.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" #include "piscsi/piscsi_image.h" #include "piscsi/piscsi_response.h" +#include "piscsi/piscsi_executor.h" #include "generated/piscsi_interface.pb.h" -#include +#include "spdlog/sinks/stdout_color_sinks.h" +#include #include +#include using namespace std; class BUS; -class ControllerManager; -class PiscsiExecutor; class Piscsi { - using optargs_type = vector>; - static const int DEFAULT_PORT = 6868; public: @@ -35,46 +35,47 @@ public: Piscsi() = default; ~Piscsi() = default; - int run(const vector&); + int run(span); private: - void Banner(const vector&) const; - bool InitBus() const; - static void Cleanup(); - void ReadAccessToken(const string&) const; + void Banner(span) const; + bool InitBus(); + void CleanUp(); + void ReadAccessToken(const path&); void LogDevices(string_view) const; - PbDeviceType ParseDeviceType(const string&) const; static void TerminationHandler(int); - optargs_type ParseArguments(const vector&, int&) const; - void CreateInitialDevices(const optargs_type&) const; - void WaitForNotBusy() const; + string ParseArguments(span, PbCommand&, int&, string&); + void Process(); + bool IsNotBusy() const; - // TODO Should not be static and should be moved to PiscsiService - static bool ExecuteCommand(const CommandContext&, const PbCommand&); + void ShutDown(AbstractController::piscsi_shutdown_mode); - DeviceLogger device_logger; + bool ExecuteCommand(CommandContext&); - // A static instance is needed because of the signal handler - static inline shared_ptr bus; + bool SetLogLevel(const string&) const; - // TODO These fields should not be static + const shared_ptr logger = spdlog::stdout_color_mt("piscsi stdout logger"); - static inline PiscsiService service; - - static inline PiscsiImage piscsi_image; - - const static inline PiscsiResponse piscsi_response; - - static inline shared_ptr controller_manager; - - static inline shared_ptr executor; + static PbDeviceType ParseDeviceType(const string&); // Processing flag - static inline volatile bool active; + atomic_bool target_is_active; - // Some versions of spdlog do not support get_log_level(), so we have to remember the level - static inline string current_log_level = "info"; + string access_token; - static inline string access_token; + PiscsiImage piscsi_image; + + PiscsiResponse response; + + PiscsiService service; + + unique_ptr executor; + + ControllerManager controller_manager; + + unique_ptr bus; + + // Required for the termination handler + static inline Piscsi *instance; }; diff --git a/cpp/piscsi/piscsi_executor.cpp b/cpp/piscsi/piscsi_executor.cpp index 2f984992..c5564e01 100644 --- a/cpp/piscsi/piscsi_executor.cpp +++ b/cpp/piscsi/piscsi_executor.cpp @@ -3,35 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" -#include "devices/device_logger.h" #include "devices/device_factory.h" #include "devices/primary_device.h" #include "devices/disk.h" -#include "piscsi_service.h" #include "piscsi_image.h" #include "localizer.h" #include "command_context.h" #include "piscsi_executor.h" +#include #include using namespace spdlog; using namespace protobuf_util; using namespace piscsi_util; -bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, - const PbCommand& command, bool dryRun) +bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun) { - PrintCommand(command, pb_device, dryRun); + spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device)); const int id = pb_device.id(); const int lun = pb_device.unit(); @@ -40,14 +36,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return false; } - const PbOperation operation = command.operation(); + const PbOperation operation = context.GetCommand().operation(); // For all commands except ATTACH the device and LUN must exist if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) { return false; } - auto device = controller_manager.GetDeviceByIdAndLun(id, lun); + auto device = controller_manager.GetDeviceForIdAndLun(id, lun); if (!ValidateOperationAgainstDevice(context, *device, operation)) { return false; @@ -64,7 +60,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return Attach(context, pb_device, dryRun); case DETACH: - return Detach(context, device, dryRun); + return Detach(context, *device, dryRun); case INSERT: return Insert(context, pb_device, device, dryRun); @@ -82,7 +78,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev case CHECK_AUTHENTICATION: case NO_OPERATION: // Do nothing, just log - LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) + spdlog::trace("Received " + PbOperation_Name(operation) + " command"); break; default: @@ -92,136 +88,83 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev return true; } -bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command) +bool PiscsiExecutor::ProcessCmd(const CommandContext& context) { + const PbCommand& command = context.GetCommand(); + switch (command.operation()) { case DETACH_ALL: DetachAll(); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); case RESERVE_IDS: { const string ids = GetParam(command, "ids"); if (const string error = SetReservedIds(ids); !error.empty()) { - return context.ReturnStatus(false, error); + return context.ReturnErrorStatus(error); } - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } case CREATE_IMAGE: - return piscsi_image.CreateImage(context, command); + return piscsi_image.CreateImage(context); case DELETE_IMAGE: - return piscsi_image.DeleteImage(context, command); + return piscsi_image.DeleteImage(context); case RENAME_IMAGE: - return piscsi_image.RenameImage(context, command); + return piscsi_image.RenameImage(context); case COPY_IMAGE: - return piscsi_image.CopyImage(context, command); + return piscsi_image.CopyImage(context); case PROTECT_IMAGE: case UNPROTECT_IMAGE: - return piscsi_image.SetImagePermissions(context, command); + return piscsi_image.SetImagePermissions(context); default: // This is a device-specific command handled below break; } - // Remember the list of reserved files, than run the dry run + // Remember the list of reserved files during the dry run const auto& reserved_files = StorageDevice::GetReservedFiles(); - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, true)) { - // Dry run failed, restore the file list - StorageDevice::SetReservedFiles(reserved_files); - return false; - } - } - - // Restore the list of reserved files before proceeding + const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, true); }) != command.devices().end(); StorageDevice::SetReservedFiles(reserved_files); - - if (const string result = ValidateLunSetup(command); !result.empty()) { - return context.ReturnStatus(false, result); - } - - for (const auto& device : command.devices()) { - if (!ProcessDeviceCmd(context, device, command, false)) { - return false; - } - } - - // ATTACH and DETACH return the device list - if (context.IsValid() && (command.operation() == ATTACH || command.operation() == DETACH)) { - // A new command with an empty device list is required here in order to return data for all devices - PbCommand cmd; - PbResult result; - piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, - piscsi_image.GetDefaultFolder()); - serializer.SerializeMessage(context.GetFd(), result); - return true; - } - - return context.ReturnStatus(); -} - -bool PiscsiExecutor::SetLogLevel(const string& log_level) const -{ - int id = -1; - int lun = -1; - string level = log_level; - - if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - level = log_level.substr(0, separator_pos); - - const string l = log_level.substr(separator_pos + 1); - separator_pos = l.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun); - if (!error.empty()) { - LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str()) - return false; - } - } - else if (!GetAsUnsignedInt(l, id)) { - LOGWARN("Invalid device ID specifier '%s'", l.c_str()) - return false; - } - } - - if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) { - set_level(it->second); - } - else { - LOGWARN("Invalid log level '%s'", log_level.c_str()) + if (reserved) { return false; } - DeviceLogger::SetLogIdAndLun(id, lun); - - if (id != -1) { - if (lun == -1) { - LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str()) - } - else { - LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str()) - } - } - else { - LOGINFO("Set log level to '%s'", level.c_str()) + if (const string error = EnsureLun0(command); !error.empty()) { + return context.ReturnErrorStatus(error); } - return true; + if (ranges::find_if_not(command.devices(), [&] (const auto& device) + { return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) { + return false; + } + + // ATTACH and DETACH return the device list + if (command.operation() == ATTACH || command.operation() == DETACH) { + // A new command with an empty device list is required here in order to return data for all devices + PbCommand cmd; + PbResult result; + piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, context.GetDefaultFolder()); + context.WriteResult(result); + return true; + } + + return context.ReturnSuccessStatus(); } bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Start requested for " + device.GetIdentifier()); if (!device.Start()) { - LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Starting " + device.GetIdentifier() + " failed"); } } @@ -231,7 +174,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Stop requested for " + device.GetIdentifier()); device.Stop(); } @@ -242,10 +185,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::info("Eject requested for " + device.GetIdentifier()); if (!device.Eject(true)) { - LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun()) + spdlog::warn("Ejecting " + device.GetIdentifier() + " failed"); } } @@ -255,8 +198,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write protection requested for " + device.GetIdentifier()); device.SetProtected(true); } @@ -267,8 +209,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), - device.GetLun()) + spdlog::info("Write unprotection requested for " + device.GetIdentifier()); device.SetProtected(false); } @@ -282,15 +223,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit const int lun = pb_device.unit(); const PbDeviceType type = pb_device.type(); - if (lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX)); + if (lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax())); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) { + if (controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun)); } - if (reserved_ids.find(id) != reserved_ids.end()) { + if (reserved_ids.contains(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id)); } @@ -302,8 +244,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit } // If no filename was provided the medium is considered not inserted - auto storage_device = dynamic_pointer_cast(device); - device->SetRemoved(storage_device != nullptr ? filename.empty() : false); + device->SetRemoved(device->SupportsFile() ? filename.empty() : false); if (!SetProductData(context, pb_device, *device)) { return false; @@ -313,14 +254,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return false; } - string full_path; + const auto storage_device = dynamic_pointer_cast(device); if (device->SupportsFile()) { // Only with removable media drives, CD and MO the medium (=file) may be inserted later if (!device->IsRemovable() && filename.empty()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); } - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } } @@ -336,23 +277,22 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return true; } - unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; + param_map params = { pb_device.params().begin(), pb_device.params().end() }; if (!device->SupportsFile()) { // Clients like scsictl might have sent both "file" and "interfaces" params.erase("file"); } if (!device->Init(params)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()), - to_string(id), to_string(lun)); + return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier()); + } + + if (!controller_manager.AttachToController(bus, id, device)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); } if (storage_device != nullptr) { - storage_device->ReserveFile(full_path, id, lun); - } - - if (!controller_manager.AttachToScsiController(id, device)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); + storage_device->ReserveFile(); } string msg = "Attached "; @@ -362,8 +302,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit else if (device->IsProtectable() && device->IsProtected()) { msg += "protected "; } - msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun); - LOGINFO("%s", msg.c_str()) + msg += device->GetIdentifier(); + spdlog::info(msg); return true; } @@ -371,12 +311,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, const shared_ptr& device, bool dryRun) const { - auto storage_device = dynamic_pointer_cast(device); - if (storage_device == nullptr) { + if (!device->SupportsFile()) { return false; } - if (!storage_device->IsRemoved()) { + if (!device->IsRemoved()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED); } @@ -394,56 +333,52 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit return true; } - LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", - filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit()) + spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename + + "' requested into " + device->GetIdentifier()); + // TODO It may be better to add PrimaryDevice::Insert for all device-specific insert operations + auto storage_device = dynamic_pointer_cast(device); if (!SetSectorSize(context, storage_device, pb_device.block_size())) { return false; } - string full_path; - if (!ValidateImageFile(context, *storage_device, filename, full_path)) { + if (!ValidateImageFile(context, *storage_device, filename)) { return false; } storage_device->SetProtected(pb_device.protected_()); - storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun()); + storage_device->ReserveFile(); storage_device->SetMediumChanged(true); return true; } -bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr& device, bool dryRun) const +bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun) { - auto controller = controller_manager.FindController(device->GetId()); + auto controller = controller_manager.FindController(device.GetId()); if (controller == nullptr) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // LUN 0 can only be detached if there is no other LUN anymore - if (!device->GetLun() && controller->GetLunCount() > 1) { + if (!device.GetLun() && controller->GetLunCount() > 1) { return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0); } if (!dryRun) { - // Remember the ID before it gets invalid when removing the device - const int id = device->GetId(); + // Remember the device identifier for the log message before the device data become invalid on removal + const string identifier = device.GetIdentifier(); if (!controller->RemoveDevice(device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } // If no LUN is left also delete the controller - if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) { + if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } - if (auto storage_device = dynamic_pointer_cast(device); storage_device != nullptr) { - storage_device->UnreserveFile(); - } - - LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id) - + ", unit " + to_string(device->GetLun())).c_str()) + spdlog::info("Detached " + identifier); } return true; @@ -452,9 +387,8 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr ids_to_reserve; + set ids_to_reserve; stringstream ss(ids.data()); string id; while (getline(ss, id, ',')) { - if (!id.empty()) { - ids_to_reserve.push_back(id); - } - } - - set reserved; - for (const string& id_to_reserve : ids_to_reserve) { int res_id; - if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) { - return "Invalid ID " + id_to_reserve; - } - - if (controller_manager.FindController(res_id) != nullptr) { - return "ID " + id_to_reserve + " is currently in use"; - } - - reserved.insert(res_id); - } - - reserved_ids = { reserved.begin(), reserved.end() }; - - if (!reserved_ids.empty()) { - string s; - bool isFirst = true; - for (const auto& reserved_id : reserved) { - if (!isFirst) { - s += ", "; - } - isFirst = false; - s += to_string(reserved_id); + if (!GetAsUnsignedInt(id, res_id) || res_id > 7) { + return "Invalid ID " + id; } - LOGINFO("Reserved ID(s) set to %s", s.c_str()) + if (controller_manager.HasController(res_id)) { + return "ID " + id + " is currently in use"; + } + + ids_to_reserve.insert(res_id); + } + + reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() }; + + if (!ids_to_reserve.empty()) { + spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve)); } else { - LOGINFO("Cleared reserved ID(s)") + spdlog::info("Cleared reserved ID(s)"); } return ""; } bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device, - const string& filename, string& full_path) const + const string& filename) const { if (filename.empty()) { return true; } - if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id1), to_string(lun1)); + if (!CheckForReservedFile(context, filename)) { + return false; } - string effective_filename = filename; + storage_device.SetFilename(filename); if (!StorageDevice::FileExists(filename)) { // If the file does not exist search for it in the default image folder - effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename; + const string effective_filename = context.GetDefaultFolder() + "/" + filename; - if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id2), to_string(lun2)); + if (!CheckForReservedFile(context, effective_filename)) { + return false; } - if (!StorageDevice::FileExists(effective_filename)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); - } - } - - storage_device.SetFilename(effective_filename); - - if (storage_device.IsReadOnlyFile()) { - // Permanently write-protected - storage_device.SetReadOnly(true); - storage_device.SetProtectable(false); - } - else { - storage_device.SetReadOnly(false); - storage_device.SetProtectable(true); + storage_device.SetFilename(effective_filename); } try { storage_device.Open(); } catch(const io_exception&) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); + return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename()); } - full_path = effective_filename; - return true; } -void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const +bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename) +{ + if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id) + ":" + to_string(lun)); + } + + return true; +} + +string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) const { const map> params = { command.params().begin(), command.params().end() }; ostringstream s; - s << (dryRun ? "Validating" : "Executing"); - s << ": operation=" << PbOperation_Name(command.operation()); + s << "operation=" << PbOperation_Name(command.operation()); if (!params.empty()) { s << ", command params="; bool isFirst = true; - for (const auto& [key, value]: params) { + for (const auto& [key, value] : params) { if (!isFirst) { s << ", "; } @@ -632,8 +537,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini } } - s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type=" - << PbDeviceType_Name(pb_device.type()); + s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type()); if (pb_device.params_size()) { s << ", device params="; @@ -649,13 +553,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product() << "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size(); - LOGINFO("%s", s.str().c_str()) + + return s.str(); } -string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const +string PiscsiExecutor::EnsureLun0(const PbCommand& command) const { // Mapping of available LUNs (bit vector) to devices - unordered_map luns; + unordered_map luns; // Collect LUN bit vectors of new devices for (const auto& device : command.devices()) { @@ -667,23 +572,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const luns[device->GetId()] |= 1 << device->GetLun(); } - // LUN 0 must exist for all devices - for (const auto& [id, lun]: luns) { - if (!(lun & 0x01)) { - return "LUN 0 is missing for device ID " + to_string(id); - } - } - - return ""; + const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } ); + return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first); } bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const { - if (controller_manager.FindController(id) == nullptr) { + if (!controller_manager.HasController(id)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id)); } - if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) { + if (!controller_manager.HasDeviceForIdAndLun(id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun)); } @@ -706,13 +605,13 @@ shared_ptr PiscsiExecutor::CreateDevice(const CommandContext& con return device; } -bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int block_size) const +bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int size) const { - if (block_size) { - auto disk = dynamic_pointer_cast(device); + if (size) { + const auto disk = dynamic_pointer_cast(device); if (disk != nullptr && disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, block_size)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size)); + if (!disk->SetConfiguredSectorSize(device_factory, size)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size)); } } else { @@ -725,22 +624,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr= ControllerManager::DEVICE_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); + if (id >= ControllerManager::GetScsiIdMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), + to_string(ControllerManager::GetScsiIdMax() - 1)); } - if (lun < 0 || lun >= ScsiController::LUN_MAX) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1)); + if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), + to_string(ControllerManager::GetScsiLunMax() - 1)); } return true; @@ -777,7 +681,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic } } catch(const invalid_argument& e) { - return context.ReturnStatus(false, e.what()); + return context.ReturnErrorStatus(e.what()); } return true; diff --git a/cpp/piscsi/piscsi_executor.h b/cpp/piscsi/piscsi_executor.h index cf2739a3..efa5916e 100644 --- a/cpp/piscsi/piscsi_executor.h +++ b/cpp/piscsi/piscsi_executor.h @@ -3,22 +3,21 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "spdlog/spdlog.h" -#include "shared/protobuf_serializer.h" +#include "hal/bus.h" +#include "controllers/controller_manager.h" #include "piscsi/piscsi_response.h" #include -#include class PiscsiImage; class DeviceFactory; -class ControllerManager; class PrimaryDevice; +class StorageDevice; class CommandContext; using namespace spdlog; @@ -27,15 +26,14 @@ class PiscsiExecutor { public: - PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager) - : piscsi_image(piscsi_image), controller_manager(controller_manager) {} + PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager) + : piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {} ~PiscsiExecutor() = default; - unordered_set GetReservedIds() const { return reserved_ids; } + auto GetReservedIds() const { return reserved_ids; } - bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); - bool ProcessCmd(const CommandContext&, const PbCommand&); - bool SetLogLevel(const string&) const; + bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool); + bool ProcessCmd(const CommandContext&); bool Start(PrimaryDevice&, bool) const; bool Stop(PrimaryDevice&, bool) const; bool Eject(PrimaryDevice&, bool) const; @@ -43,41 +41,34 @@ public: bool Unprotect(PrimaryDevice&, bool) const; bool Attach(const CommandContext&, const PbDeviceDefinition&, bool); bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr&, bool) const; - bool Detach(const CommandContext&, const shared_ptr&, bool) const; + bool Detach(const CommandContext&, PrimaryDevice&, bool); void DetachAll(); bool ShutDown(const CommandContext&, const string&); string SetReservedIds(string_view); - bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const; - void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; - string ValidateLunSetup(const PbCommand&) const; + bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const; + string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const; + string EnsureLun0(const PbCommand&) const; bool VerifyExistingIdAndLun(const CommandContext&, int, int) const; shared_ptr CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const; bool SetSectorSize(const CommandContext&, shared_ptr, int) const; - static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&); + static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation); static bool ValidateIdAndLun(const CommandContext&, int, int); static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&); private: + static bool CheckForReservedFile(const CommandContext&, const string&); + const PiscsiResponse piscsi_response; PiscsiImage& piscsi_image; + BUS& bus; + ControllerManager& controller_manager; const DeviceFactory device_factory; - const ProtobufSerializer serializer; - unordered_set reserved_ids; - - static inline const unordered_map log_level_mapping = { - { "trace", level::trace }, - { "debug", level::debug }, - { "info", level::info }, - { "warn", level::warn }, - { "err", level::err }, - { "off", level::off } - }; }; diff --git a/cpp/piscsi/piscsi_image.cpp b/cpp/piscsi/piscsi_image.cpp index 6f32c78d..4c7dcc42 100644 --- a/cpp/piscsi/piscsi_image.cpp +++ b/cpp/piscsi/piscsi_image.cpp @@ -3,21 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" -#include "shared/protobuf_util.h" #include "devices/disk.h" -#include "command_context.h" #include "piscsi_image.h" +#include "shared/protobuf_util.h" +#include #include #include #include -#include #include -#include using namespace std; using namespace filesystem; @@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage() bool PiscsiImage::CheckDepth(string_view filename) const { - return count(filename.begin(), filename.end(), '/') <= depth; + return ranges::count(filename, '/') <= depth; } -bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const +bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const { - if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) { - const auto folder = path(filename.substr(0, filename_start)); - + if (const auto folder = path(filename).parent_path(); !folder.string().empty()) { // Checking for existence first prevents an error if the top-level folder is a softlink if (error_code error; exists(folder, error)) { return true; @@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& return ChangeOwner(context, folder, false); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what()); } } return true; } -string PiscsiImage::SetDefaultFolder(const string& f) +string PiscsiImage::SetDefaultFolder(string_view f) { if (f.empty()) { return "Can't set default image folder: Missing folder name"; } - string folder = f; - // If a relative path is specified, the path is assumed to be relative to the user's home directory - if (folder[0] != '/') { - folder = GetHomeDir() + "/" + folder; + path folder(f); + if (folder.is_relative()) { + folder = path(GetHomeDir() + "/" + folder.string()); } - else { - if (folder.find("/home/") != 0) { - return "Default image folder must be located in '/home/'"; - } + + if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) { + return "Default image folder must be located in '" + home_root.string() + "'"; } // Resolve a potential symlink - auto p = path(folder); - if (error_code error; is_symlink(p, error)) { - p = read_symlink(p); + if (error_code error; is_symlink(folder, error)) { + folder = read_symlink(folder); } - if (error_code error; !is_directory(p, error)) { - return "'" + string(p) + "' is not a valid folder"; + if (error_code error; !is_directory(folder)) { + return string("'") + folder.string() + "' is not a valid image folder"; } - default_folder = string(p); + default_folder = folder.string(); - LOGINFO("Default image folder set to '%s'", default_folder.c_str()) + spdlog::info("Default image folder set to '" + default_folder + "'"); return ""; } -bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CreateImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Can't create image file: Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str()); } const string full_filename = GetFullName(filename); if (!IsValidDstFilename(full_filename)) { - return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists"); + return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists"); } - const string size = GetParam(command, "size"); + const string size = GetParam(context.GetCommand(), "size"); if (size.empty()) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size"); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size"); } off_t len; @@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co len = stoull(size); } catch(const invalid_argument&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } catch(const out_of_range&) { - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size); } if (len < 512 || (len & 0x1ff)) { - return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)"); + return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)"); } if (!CreateImageFolder(context, full_filename)) { return false; } - const bool read_only = GetParam(command, "read_only") == "true"; + const bool read_only = GetParam(context.GetCommand(), "read_only") == "true"; error_code error; path file(full_filename); @@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co catch(const filesystem_error& e) { remove(file, error); - return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what()); } - LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + - "' with a size of " + to_string(len) + " bytes").c_str()) + spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename + + "' with a size of " + to_string(len) + " bytes"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::DeleteImage(const CommandContext& context) const { - const string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } const auto full_filename = path(GetFullName(filename)); - if (!exists(full_filename)) { - return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist"); + return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist"); } - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); + if (!IsReservedFile(context, full_filename, "delete")) { + return false; } if (error_code error; !remove(full_filename, error)) { - return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'"); + return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'"); } // Delete empty subfolders @@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co } if (error_code error; !remove(full_folder)) { - return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'"); + return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'"); } last_slash = folder.rfind('/'); } - LOGINFO("Deleted image file '%s'", full_filename.c_str()) + spdlog::info("Deleted image file '" + full_filename.string() + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::RenameImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "rename/move", from, to)) { - return false; - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "rename/move", from, to)) { return false; } @@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co rename(path(from), path(to)); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what()); } - LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::CopyImage(const CommandContext& context) const { string from; string to; - if (!ValidateParams(context, command, "copy", from, to)) { - return false; - } - - if (access(from.c_str(), R_OK)) { - return context.ReturnStatus(false, "Can't read source image file '" + from + "'"); - } - - const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from); - if (id != -1 || lun != -1) { - return context.ReturnStatus(false, "Can't copy image file '" + from + - "', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun)); - } - - if (!CreateImageFolder(context, to)) { + if (!ValidateParams(context, "copy", from, to)) { return false; } @@ -268,119 +233,134 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm copy_symlink(f, t); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what()); } - LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } try { copy_file(f, t); - permissions(t, GetParam(command, "read_only") == "true" ? + permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what()); + return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what()); } - LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()) + spdlog::info("Copied image file '" + from + "' to '" + to + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const +bool PiscsiImage::SetImagePermissions(const CommandContext& context) const { - string filename = GetParam(command, "file"); + const string filename = GetParam(context.GetCommand(), "file"); if (filename.empty()) { - return context.ReturnStatus(false, "Missing image filename"); + return context.ReturnErrorStatus("Missing image filename"); } if (!CheckDepth(filename)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'"); } - filename = GetFullName(filename); - if (!IsValidSrcFilename(filename)) { - return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type"); + const string full_filename = GetFullName(filename); + if (!IsValidSrcFilename(full_filename)) { + return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type"); } - const bool protect = command.operation() == PROTECT_IMAGE; + const bool protect = context.GetCommand().operation() == PROTECT_IMAGE; try { - permissions(path(filename), protect ? + permissions(path(full_filename), protect ? perms::owner_read | perms::group_read | perms::others_read : perms::owner_read | perms::group_read | perms::others_read | perms::owner_write | perms::group_write); } catch(const filesystem_error& e) { - return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + - filename + "': " + e.what()); + return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" + + full_filename + "': " + e.what()); } - if (protect) { - LOGINFO("Protected image file '%s'", filename.c_str()) - } - else { - LOGINFO("Unprotected image file '%s'", filename.c_str()) - } + spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'"); - return context.ReturnStatus(); + return context.ReturnSuccessStatus(); } -bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation, - string& from, string& to) const +bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op) { - from = GetParam(command, "from"); - if (from.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename"); - } - - if (!CheckDepth(from)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); - } - - from = GetFullName(from); - if (!IsValidSrcFilename(from)) { - return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type"); - } - - to = GetParam(command, "to"); - if (to.empty()) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename"); - } - - if (!CheckDepth(to)) { - return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); - } - - to = GetFullName(to); - if (!IsValidDstFilename(to)) { - return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists"); + const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file); + if (id != -1) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + file + + "', it is currently being used by device " + to_string(id) + ":" + to_string(lun)); } return true; } -bool PiscsiImage::IsValidSrcFilename(const string& filename) +bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const +{ + from = GetParam(context.GetCommand(), "from"); + if (from.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename"); + } + + if (!CheckDepth(from)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'"); + } + + to = GetParam(context.GetCommand(), "to"); + if (to.empty()) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename"); + } + + if (!CheckDepth(to)) { + return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'"); + } + + from = GetFullName(from); + if (!IsValidSrcFilename(from)) { + return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type"); + } + + to = GetFullName(to); + if (!IsValidDstFilename(to)) { + return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists"); + } + + if (!IsReservedFile(context, from, op)) { + return false; + } + + if (!CreateImageFolder(context, to)) { + return false; + } + + return true; +} + +bool PiscsiImage::IsValidSrcFilename(string_view filename) { // Source file must exist and must be a regular file or a symlink path file(filename); - return is_regular_file(file) || is_symlink(file); + + error_code error; + return is_regular_file(file, error) || is_symlink(file, error); } -bool PiscsiImage::IsValidDstFilename(const string& filename) +bool PiscsiImage::IsValidDstFilename(string_view filename) { // Destination file must not yet exist try { return !exists(path(filename)); } catch(const filesystem_error&) { - return true; + return false; } } @@ -394,7 +374,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam error_code error; remove(filename, error); - return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e)); + return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e)); } permissions(filename, read_only ? @@ -437,5 +417,5 @@ pair PiscsiImage::GetUidAndGid() gid = pwd.pw_gid; } - return make_pair(uid, gid); + return { uid, gid }; } diff --git a/cpp/piscsi/piscsi_image.h b/cpp/piscsi/piscsi_image.h index 4a820835..f53e894d 100644 --- a/cpp/piscsi/piscsi_image.h +++ b/cpp/piscsi/piscsi_image.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -28,22 +28,23 @@ public: void SetDepth(int d) { depth = d; } int GetDepth() const { return depth; } string GetDefaultFolder() const { return default_folder; } - string SetDefaultFolder(const string&); - bool CreateImage(const CommandContext&, const PbCommand&) const; - bool DeleteImage(const CommandContext&, const PbCommand&) const; - bool RenameImage(const CommandContext&, const PbCommand&) const; - bool CopyImage(const CommandContext&, const PbCommand&) const; - bool SetImagePermissions(const CommandContext&, const PbCommand&) const; + string SetDefaultFolder(string_view); + bool CreateImage(const CommandContext&) const; + bool DeleteImage(const CommandContext&) const; + bool RenameImage(const CommandContext&) const; + bool CopyImage(const CommandContext&) const; + bool SetImagePermissions(const CommandContext&) const; private: bool CheckDepth(string_view) const; string GetFullName(const string& filename) const { return default_folder + "/" + filename; } - bool CreateImageFolder(const CommandContext&, const string&) const; - bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const; + bool CreateImageFolder(const CommandContext&, string_view) const; + static bool IsReservedFile(const CommandContext&, const string&, const string&); + bool ValidateParams(const CommandContext&, const string&, string&, string&) const; - static bool IsValidSrcFilename(const string&); - static bool IsValidDstFilename(const string&); + static bool IsValidSrcFilename(string_view); + static bool IsValidDstFilename(string_view); static bool ChangeOwner(const CommandContext&, const path&, bool); static string GetHomeDir(); static pair GetUidAndGid(); diff --git a/cpp/piscsi/piscsi_response.cpp b/cpp/piscsi/piscsi_response.cpp index 35f3ec51..6987f78f 100644 --- a/cpp/piscsi/piscsi_response.cpp +++ b/cpp/piscsi/piscsi_response.cpp @@ -3,50 +3,48 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_version.h" #include "devices/disk.h" -#include "devices/device_factory.h" -#include "generated/piscsi_interface.pb.h" #include "piscsi_response.h" +#include #include using namespace std; using namespace filesystem; using namespace piscsi_interface; +using namespace piscsi_util; +using namespace network_util; using namespace protobuf_util; -unique_ptr PiscsiResponse::GetDeviceProperties(const Device& device) const +void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const { - auto properties = make_unique(); - - // Currently there is only a SCSI controller, i.e. there can always be 32 LUNs - properties->set_luns(32); - properties->set_read_only(device.IsReadOnly()); - properties->set_protectable(device.IsProtectable()); - properties->set_stoppable(device.IsStoppable()); - properties->set_removable(device.IsRemovable()); - properties->set_lockable(device.IsLockable()); - properties->set_supports_file(device.SupportsFile()); - properties->set_supports_params(device.SupportsParams()); + properties.set_luns(ControllerManager::GetScsiLunMax()); + properties.set_read_only(device.IsReadOnly()); + properties.set_protectable(device.IsProtectable()); + properties.set_stoppable(device.IsStoppable()); + properties.set_removable(device.IsRemovable()); + properties.set_lockable(device.IsLockable()); + properties.set_supports_file(device.SupportsFile()); + properties.set_supports_params(device.SupportsParams()); if (device.SupportsParams()) { for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { - auto& map = *properties->mutable_default_params(); + auto& map = *properties.mutable_default_params(); map[key] = value; } } for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { - properties->add_block_sizes(block_size); + properties.add_block_sizes(block_size); } - - return properties; } void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const @@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf auto type_properties = device_types_info.add_properties(); type_properties->set_type(type); const auto device = device_factory.CreateDevice(type, 0, ""); - type_properties->set_allocated_properties(GetDeviceProperties(*device).release()); -} //NOSONAR The allocated memory is managed by protobuf + GetDeviceProperties(*device, *type_properties->mutable_properties()); +} -void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const +void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const { // Start with 2 instead of 1. 1 was the removed SASI drive type. int ordinal = 2; @@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const pb_device.set_revision(device.GetRevision()); pb_device.set_type(device.GetType()); - pb_device.set_allocated_properties(GetDeviceProperties(device).release()); + GetDeviceProperties(device, *pb_device.mutable_properties()); - auto status = make_unique().release(); //NOSONAR The allocated memory is managed by protobuf - pb_device.set_allocated_status(status); + auto status = pb_device.mutable_status(); status->set_protected_(device.IsProtected()); status->set_stopped(device.IsStopped()); status->set_removed(device.IsRemoved()); status->set_locked(device.IsLocked()); - if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf + if (device.SupportsParams()) { for (const auto& [key, value] : device.GetParams()) { SetParam(pb_device, key, value); } @@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const const auto storage_device = dynamic_cast(&device); if (storage_device != nullptr) { - auto image_file = make_unique().release(); - GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : ""); - pb_device.set_allocated_file(image_file); + GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : ""); } -} //NOSONAR The allocated memory is managed by protobuf +} bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const { @@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default image_file.set_name(filename); image_file.set_type(device_factory.GetTypeForFile(filename)); - const string f = filename[0] == '/' ? filename : default_folder + "/" + filename; + const path p(filename[0] == '/' ? filename : default_folder + "/" + filename); - image_file.set_read_only(access(f.c_str(), W_OK)); + image_file.set_read_only(access(p.c_str(), W_OK)); - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { - image_file.set_size(st.st_size); + error_code error; + if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) { + image_file.set_size(file_size(p)); return true; } } @@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default } void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder, - const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const + const string& folder_pattern, const string& file_pattern, int scan_depth) const { - if (scan_depth-- < 0) { + const path default_path(default_folder); + if (!is_directory(default_path)) { return; } - string folder_pattern_lower = folder_pattern; - transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower); + string folder_pattern_lower; + ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower); - string file_pattern_lower = file_pattern; - transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower); + string file_pattern_lower; + ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower); - DIR *d = opendir(folder.c_str()); - if (d == nullptr) { - return; - } - - // C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - const dirent *dir; - while ((dir = readdir(d))) { - string filename = GetNextImageFile(dir, folder); - if (filename.empty()) { + for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink); + it != recursive_directory_iterator(); it++) { + if (it.depth() > scan_depth) { + it.disable_recursion_pending(); continue; } - string name_lower = dir->d_name; - if (!file_pattern.empty()) { - transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); - } + const string parent = it->path().parent_path().string(); - if (dir->d_type == DT_DIR) { - if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) { - GetAvailableImages(image_files_info, default_folder, filename, folder_pattern, - file_pattern, scan_depth); - } + const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : ""; + if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) { continue; } - if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { - if (auto image_file = make_unique(); GetImageFile(*image_file.get(), default_folder, filename)) { - GetImageFile(*image_files_info.add_image_files(), default_folder, - filename.substr(default_folder.length() + 1)); - } + if (!ValidateImageFile(it->path())) { + continue; + } + + const string filename = folder.empty() ? + it->path().filename().string() : folder + "/" + it->path().filename().string(); + if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) { + GetImageFile(*image_files_info.add_image_files(), default_folder, filename); } } - - closedir(d); } -unique_ptr PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder, +void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = make_unique(); + image_files_info.set_default_image_folder(default_folder); + image_files_info.set_depth(scan_depth); - image_files_info->set_default_image_folder(default_folder); - image_files_info->set_depth(scan_depth); - - GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern, - file_pattern, scan_depth); - - result.set_status(true); - - return image_files_info; + GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth); } -void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder, +void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth); - image_files_info->set_default_image_folder(default_folder); - server_info.set_allocated_image_files_info(image_files_info.release()); + server_info.mutable_image_files_info()->set_default_image_folder(default_folder); - result.set_status(true); //NOSONAR The allocated memory is managed by protobuf + GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth); } -unique_ptr PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set& ids) const +void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set& ids) const { - auto reserved_ids_info = make_unique(); for (const int id : ids) { - reserved_ids_info->add_ids(id); + reserved_ids_info.add_ids(id); } - - result.set_status(true); - - return reserved_ids_info; } void PiscsiResponse::GetDevices(const unordered_set>& devices, PbServerInfo& server_info, @@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_setGetId(), device->GetLun())); + id_sets.insert({ device->GetId(), device->GetLun() }); } } // Otherwise get information on the devices provided in the command @@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set(); - + auto devices_info = result.mutable_devices_info(); for (const auto& [id, lun] : id_sets) { for (const auto& d : devices) { if (d->GetId() == id && d->GetLun() == lun) { @@ -254,244 +224,179 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const +void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set>& devices, + const unordered_set& reserved_ids, const string& default_folder, const string& folder_pattern, + const string& file_pattern, int scan_depth) const { - auto device_types_info = make_unique(); - - GetAllDeviceTypeProperties(*device_types_info); - - result.set_status(true); - - return device_types_info; + GetVersionInfo(*server_info.mutable_version_info()); + GetLogLevelInfo(*server_info.mutable_log_level_info()); + GetDeviceTypesInfo(*server_info.mutable_device_types_info()); + GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth); + GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info()); + GetMappingInfo(*server_info.mutable_mapping_info()); + GetDevices(devices, server_info, default_folder); + GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids); + GetOperationInfo(*server_info.mutable_operation_info(), scan_depth); } -unique_ptr PiscsiResponse::GetServerInfo(const unordered_set>& devices, - PbResult& result, const unordered_set& reserved_ids, const string& current_log_level, - const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const +void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const { - auto server_info = make_unique(); - - server_info->set_allocated_version_info(GetVersionInfo(result).release()); - server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf - GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf - GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth); - server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release()); - server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf - GetDevices(devices, *server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf - server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release()); - server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf - - result.set_status(true); - - return server_info; + version_info.set_major_version(piscsi_major_version); + version_info.set_minor_version(piscsi_minor_version); + version_info.set_patch_version(piscsi_patch_version); } -unique_ptr PiscsiResponse::GetVersionInfo(PbResult& result) const +void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const { - auto version_info = make_unique(); - - version_info->set_major_version(piscsi_major_version); - version_info->set_minor_version(piscsi_minor_version); - version_info->set_patch_version(piscsi_patch_version); - - result.set_status(true); - - return version_info; -} - -unique_ptr PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const -{ - auto log_level_info = make_unique(); - - for (const auto& log_level : log_levels) { - log_level_info->add_log_levels(log_level); + for (const auto& log_level : spdlog::level::level_string_views) { + log_level_info.add_log_levels(log_level.data()); } - log_level_info->set_current_log_level(current_log_level); - - result.set_status(true); - - return log_level_info; + log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data()); } -unique_ptr PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const +void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const { - auto network_interfaces_info = make_unique(); - - for (const auto& network_interface : device_factory.GetNetworkInterfaces()) { - network_interfaces_info->add_name(network_interface); + for (const auto& network_interface : GetNetworkInterfaces()) { + network_interfaces_info.add_name(network_interface); } - - result.set_status(true); - - return network_interfaces_info; } -unique_ptr PiscsiResponse::GetMappingInfo(PbResult& result) const +void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const { - auto mapping_info = make_unique(); - for (const auto& [name, type] : device_factory.GetExtensionMapping()) { - (*mapping_info->mutable_mapping())[name] = type; + (*mapping_info.mutable_mapping())[name] = type; } - - result.set_status(true); - - return mapping_info; } -unique_ptr PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const +void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const { - auto operation_info = make_unique(); + auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required"); + AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device"); + AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list"); + AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge"); + AddOperationParameter(*operation, "cmd", "Print command for the printer device"); - auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required"); - AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release(); - AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release(); - AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release(); - AddOperationParameter(*operation, "cmd", "Print command for the printer device").release(); - operation.release(); + CreateOperation(operation_info, DETACH, "Detach device, device-specific parameters are required"); - CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release(); + CreateOperation(operation_info, DETACH_ALL, "Detach all devices"); - CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release(); + CreateOperation(operation_info, START, "Start device, device-specific parameters are required"); - CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release(); + CreateOperation(operation_info, STOP, "Stop device, device-specific parameters are required"); - CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release(); + operation = CreateOperation(operation_info, INSERT, "Insert medium, device-specific parameters are required"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + CreateOperation(operation_info, EJECT, "Eject medium, device-specific parameters are required"); - CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release(); + CreateOperation(operation_info, PROTECT, "Protect medium, device-specific parameters are required"); - CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release(); + CreateOperation(operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required"); - CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release(); - - operation = CreateOperation(*operation_info, SERVER_INFO, "Get piscsi server information"); + operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information"); if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); - CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release(); + CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version"); - CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release(); + CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices"); - CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release(); + CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type"); - operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); + operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files"); if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); - operation.release(); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); - operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release(); + CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information"); - CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release(); + CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces"); - CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release(); + CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types"); - CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release(); + CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); - operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder"); - AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder"); + AddOperationParameter(*operation, "folder", "Default image file folder name", "", true); - operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level"); - AddOperationParameter(*operation, "level", "New log level", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level"); + AddOperationParameter(*operation, "level", "New log level", "", true); - operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs"); - AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs"); + AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true); - operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot"); - auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release(); - parameter->add_permitted_values("piscsi"); - // System shutdown/reboot requires root permissions - if (!getuid()) { - parameter->add_permitted_values("system"); - parameter->add_permitted_values("reboot"); + operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot"); + if (getuid()) { + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } ); + } + else { + // System shutdown/reboot requires root permissions + AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } ); } - operation.release(); - operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); + operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); + AddOperationParameter(*operation, "size", "Image file size in bytes", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); - operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); - operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file"); - AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); - AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); - parameter->add_permitted_values("true"); - parameter->add_permitted_values("false"); - operation.release(); + operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file"); + AddOperationParameter(*operation, "from", "Source image file name", "", true); + AddOperationParameter(*operation, "to", "Destination image file name", "", true); + AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } ); - operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable"); - AddOperationParameter(*operation, "file", "Image file name", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable"); + AddOperationParameter(*operation, "file", "Image file name", "", true); - operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); - AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release(); - operation.release(); + operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); + AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true); - CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release(); - - result.set_status(true); - - return operation_info; + CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data"); } -unique_ptr PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, +// This method returns a raw pointer because protobuf does not have support for smart pointers +PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, const string& description) const { - auto meta_data = make_unique(); - meta_data->set_server_side_name(PbOperation_Name(operation)); - meta_data->set_description(description); + PbOperationMetaData meta_data; + meta_data.set_server_side_name(PbOperation_Name(operation)); + meta_data.set_description(description); int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); - (*operation_info.mutable_operations())[ordinal] = *meta_data.release(); - return unique_ptr(&(*operation_info.mutable_operations())[ordinal]); + (*operation_info.mutable_operations())[ordinal] = meta_data; + return &(*operation_info.mutable_operations())[ordinal]; } -unique_ptr PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, - const string& name, const string& description, const string& default_value, bool is_mandatory) const +void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name, + const string& description, const string& default_value, bool is_mandatory, + const vector& permitted_values) const { - auto parameter = unique_ptr(meta_data.add_parameters()); + auto parameter = meta_data.add_parameters(); parameter->set_name(name); parameter->set_description(description); parameter->set_default_value(default_value); parameter->set_is_mandatory(is_mandatory); - - return parameter; + for (const auto& permitted_value : permitted_values) { + parameter->add_permitted_values(permitted_value); + } } set PiscsiResponse::MatchDevices(const unordered_set>& devices, PbResult& result, @@ -503,7 +408,7 @@ set PiscsiResponse::MatchDevices(const unordered_setGetId() == device.id() && d->GetLun() == device.unit()) { - id_sets.insert(make_pair(device.id(), device.unit())); + id_sets.insert({ device.id(), device.unit() }); has_device = true; break; } @@ -513,7 +418,7 @@ set PiscsiResponse::MatchDevices(const unordered_set PiscsiResponse::MatchDevices(const unordered_setd_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK) - || dir->d_name[0] == '.') { - return ""; + if (path.filename().string().starts_with(".")) { + return false; } - const string filename = folder + "/" + dir->d_name; + filesystem::path p(path); - const bool file_exists = exists(path(filename)); - - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB - struct stat st; - stat(filename.c_str(), &st); - if (dir->d_type == DT_REG && file_exists && !st.st_size) { - LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str()) - return ""; + // Follow symlink + if (is_symlink(p)) { + p = read_symlink(p); + if (!exists(p)) { + spdlog::warn("Image file symlink '" + path.string() + "' is broken"); + return false; + } } - if (dir->d_type == DT_LNK && !file_exists) { - LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) - return ""; + if (is_directory(p) || (is_other(p) && !is_block_file(p))) { + return false; } - return filename; + if (!is_block_file(p) && file_size(p) < 256) { + spdlog::warn("Image file '" + p.string() + "' is invalid"); + return false; + } + + return true; +} + +bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower) +{ + if (!pattern_lower.empty()) { + string name_lower; + ranges::transform(input, back_inserter(name_lower), ::tolower); + + if (name_lower.find(pattern_lower) == string::npos) { + return false; + } + } + + return true; } diff --git a/cpp/piscsi/piscsi_response.h b/cpp/piscsi/piscsi_response.h index 16a9b95f..549ea905 100644 --- a/cpp/piscsi/piscsi_response.h +++ b/cpp/piscsi/piscsi_response.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -12,52 +12,51 @@ #include "devices/device_factory.h" #include "devices/primary_device.h" #include "generated/piscsi_interface.pb.h" -#include -#include #include +#include using namespace std; +using namespace filesystem; using namespace piscsi_interface; class PiscsiResponse { - using id_set = pair; - public: PiscsiResponse() = default; ~PiscsiResponse() = default; bool GetImageFile(PbImageFile&, const string&, const string&) const; - unique_ptr GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const; - unique_ptr GetReservedIds(PbResult&, const unordered_set&) const; + void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetReservedIds(PbReservedIdsInfo&, const unordered_set&) const; void GetDevices(const unordered_set>&, PbServerInfo&, const string&) const; void GetDevicesInfo(const unordered_set>&, PbResult&, const PbCommand&, const string&) const; - unique_ptr GetDeviceTypesInfo(PbResult&) const; - unique_ptr GetVersionInfo(PbResult&) const; - unique_ptr GetServerInfo(const unordered_set>&, PbResult&, const unordered_set&, - const string&, const string&, const string&, const string&, int) const; - unique_ptr GetNetworkInterfacesInfo(PbResult&) const; - unique_ptr GetMappingInfo(PbResult&) const; - unique_ptr GetLogLevelInfo(PbResult&, const string&) const; - unique_ptr GetOperationInfo(PbResult&, int) const; + void GetDeviceTypesInfo(PbDeviceTypesInfo&) const; + void GetVersionInfo(PbVersionInfo&) const; + void GetServerInfo(PbServerInfo&, const unordered_set>&, const unordered_set&, + const string&, const string&, const string&, int) const; + void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const; + void GetMappingInfo(PbMappingInfo&) const; + void GetLogLevelInfo(PbLogLevelInfo&) const; + void GetOperationInfo(PbOperationInfo&, int) const; private: - DeviceFactory device_factory; + inline static const vector EMPTY_VECTOR; - const inline static array log_levels = { "trace", "debug", "info", "warn", "err", "off" }; + const DeviceFactory device_factory; - unique_ptr GetDeviceProperties(const Device&) const; + void GetDeviceProperties(const Device&, PbDeviceProperties&) const; void GetDevice(const Device&, PbDevice&, const string&) const; - void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; - void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const; - void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const; - unique_ptr CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; - unique_ptr AddOperationParameter(PbOperationMetaData&, const string&, const string&, - const string& = "", bool = false) const; + void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const; + void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const; + PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; + void AddOperationParameter(PbOperationMetaData&, const string&, const string&, + const string& = "", bool = false, const vector& = EMPTY_VECTOR) const; set MatchDevices(const unordered_set>&, PbResult&, const PbCommand&) const; - static string GetNextImageFile(const dirent *, const string&); + static bool ValidateImageFile(const path&); + + static bool FilterMatches(const string&, string_view); }; diff --git a/cpp/piscsi/piscsi_service.cpp b/cpp/piscsi/piscsi_service.cpp index 661b5f92..4f842def 100644 --- a/cpp/piscsi/piscsi_service.cpp +++ b/cpp/piscsi/piscsi_service.cpp @@ -3,140 +3,114 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "shared/log.h" #include "shared/piscsi_util.h" -#include "shared/protobuf_serializer.h" #include "shared/piscsi_exceptions.h" #include "command_context.h" -#include "localizer.h" #include "piscsi_service.h" +#include +#include #include +#include #include +#include -using namespace piscsi_interface; using namespace piscsi_util; -void PiscsiService::Cleanup() const +string PiscsiService::Init(const callback& cb, int port) { - running = false; + assert(service_socket == -1); - if (service_socket != -1) { - close(service_socket); - } -} - -bool PiscsiService::Init(const callback& cb, int port) -{ if (port <= 0 || port > 65535) { - return false; + return "Invalid port number " + to_string(port); } - // Create socket for monitor - sockaddr_in server = {}; service_socket = socket(PF_INET, SOCK_STREAM, 0); if (service_socket == -1) { - LOGERROR("Unable to create socket") - return false; + return "Unable to create service socket: " + string(strerror(errno)); } + if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { + Stop(); + return "Can't reuse address"; + } + + sockaddr_in server = {}; server.sin_family = PF_INET; server.sin_port = htons((uint16_t)port); - server.sin_addr.s_addr = htonl(INADDR_ANY); - - // Allow address reuse - if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { - return false; + server.sin_addr.s_addr = INADDR_ANY; + if (bind(service_socket, reinterpret_cast(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler + Stop(); + return "Port " + to_string(port) + " is in use, is piscsi already running?"; } - signal(SIGPIPE, SIG_IGN); - - if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { - cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl; - return false; + if (listen(service_socket, 2) == -1) { + Stop(); + return "Can't listen to service socket: " + string(strerror(errno)); } execute = cb; - monthread = thread(&PiscsiService::Execute, this); - monthread.detach(); + return ""; +} - // Interrupt handler settings - return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR - && signal(SIGTERM, KillHandler) != SIG_ERR; +void PiscsiService::Start() +{ + assert(service_socket != -1); + + service_thread = jthread([this] () { Execute(); } ); +} + +void PiscsiService::Stop() +{ + assert(service_socket != -1); + + shutdown(service_socket, SHUT_RD); + close(service_socket); + + service_socket = -1; } void PiscsiService::Execute() const { #ifdef __linux__ - // Scheduler Settings - sched_param schedparam; - schedparam.sched_priority = 0; + // Run this thread with very low priority + sched_param schedparam = { .sched_priority = 0 }; sched_setscheduler(0, SCHED_IDLE, &schedparam); #endif - // Set the affinity to a specific processor core - FixCpu(2); - - // Wait for the execution to start - const timespec ts = { .tv_sec = 0, .tv_nsec = 1000}; - while (!running) { - nanosleep(&ts, nullptr); - } - - // Set up the monitor socket to receive commands - listen(service_socket, 1); - - while (true) { - CommandContext context; - - try { - PbCommand command = ReadCommand(context); - if (context.IsValid()) { - execute(context, command); - } + // TODO Accept more than one command instead of closing the socket after a single command + while (service_socket != -1) { + const int fd = accept(service_socket, nullptr, nullptr); + if (fd != -1) { + ExecuteCommand(fd); + close(fd); } - catch(const io_exception& e) { - LOGWARN("%s", e.what()) - - // Fall through - } - - context.Cleanup(); } } -PbCommand PiscsiService::ReadCommand(CommandContext& context) const +void PiscsiService::ExecuteCommand(int fd) const { - // Wait for connection - sockaddr client = {}; - socklen_t socklen = sizeof(client); - const int fd = accept(service_socket, &client, &socklen); - if (fd == -1) { - throw io_exception("accept() failed"); + CommandContext context(fd); + try { + if (context.ReadCommand()) { + execute(context); + } } + catch(const io_exception& e) { + spdlog::warn(e.what()); - PbCommand command; - - // Read magic string - vector magic(6); - const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic); - if (!bytes_read) { - return command; + // Try to return an error message (this may fail if the exception was caused when returning the actual result) + PbResult result; + result.set_msg(e.what()); + try { + context.WriteResult(result); + } + catch(const io_exception&) { //NOSONAR Not handled on purpose + // Ignore + } } - - if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { - throw io_exception("Invalid magic"); - } - - // Fetch the command - context.GetSerializer().DeserializeMessage(fd, command); - - context.SetFd(fd); - - return command; } - diff --git a/cpp/piscsi/piscsi_service.h b/cpp/piscsi/piscsi_service.h index a880c6c8..03a956d7 100644 --- a/cpp/piscsi/piscsi_service.h +++ b/cpp/piscsi/piscsi_service.h @@ -3,49 +3,42 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "generated/piscsi_interface.pb.h" #include #include +#include class CommandContext; using namespace std; -using namespace piscsi_interface; class PiscsiService { - using callback = function; - - callback execute; - - int service_socket = -1; - - thread monthread; - - static inline volatile bool running = false; + using callback = function; public: PiscsiService() = default; ~PiscsiService() = default; - bool Init(const callback&, int); - void Cleanup() const; - - bool IsRunning() const { return running; } - void SetRunning(bool b) const { running = b; } + string Init(const callback&, int); + void Start(); + void Stop(); + bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); } private: void Execute() const; + void ExecuteCommand(int) const; - PbCommand ReadCommand(CommandContext&) const; + callback execute; - static void KillHandler(int) { running = false; } + jthread service_thread; + + int service_socket = -1; }; diff --git a/cpp/piscsi_interface.proto b/cpp/piscsi_interface.proto index d99ead12..1a1be45c 100644 --- a/cpp/piscsi_interface.proto +++ b/cpp/piscsi_interface.proto @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,7 +23,7 @@ package piscsi_interface; // The available device types enum PbDeviceType { UNDEFINED = 0; - // Non-removable SASI drive, not supported anymore + // Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility SAHD = 1 [deprecated = true]; // Non-removable SCSI drive SCHD = 2; diff --git a/cpp/scsictl/scsictl_commands.cpp b/cpp/scsictl/scsictl_commands.cpp index 99adb09e..8cb55f60 100644 --- a/cpp/scsictl/scsictl_commands.cpp +++ b/cpp/scsictl/scsictl_commands.cpp @@ -3,26 +3,31 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/network_util.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "scsictl_commands.h" +#include +#include +#include #include #include #include -#include +#include using namespace std; using namespace piscsi_interface; +using namespace network_util; using namespace piscsi_util; using namespace protobuf_util; -bool ScsictlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids, - const string& image_params, const string& filename) +bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, string_view reserved_ids, + string_view image_params, string_view filename) { switch(command.operation()) { case LOG_LEVEL: @@ -79,6 +84,9 @@ bool ScsictlCommands::Execute(const string& log_level, const string& default_fol case OPERATION_INFO: return CommandOperationInfo(); + case NO_OPERATION: + return false; + default: return SendCommand(); } @@ -112,8 +120,8 @@ bool ScsictlCommands::SendCommand() throw io_exception("Can't write magic"); } - serializer.SerializeMessage(fd, command); - serializer.DeserializeMessage(fd, result); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); close(fd); @@ -137,27 +145,23 @@ bool ScsictlCommands::CommandDevicesInfo() return true; } -bool ScsictlCommands::CommandLogLevel(const string& log_level) +bool ScsictlCommands::CommandLogLevel(string_view log_level) { SetParam(command, "level", log_level); return SendCommand(); } -bool ScsictlCommands::CommandReserveIds(const string& reserved_ids) +bool ScsictlCommands::CommandReserveIds(string_view reserved_ids) { SetParam(command, "ids", reserved_ids); return SendCommand(); } -bool ScsictlCommands::CommandCreateImage(const string& image_params) +bool ScsictlCommands::CommandCreateImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "file", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "size", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "file", "size")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl; return false; @@ -168,20 +172,16 @@ bool ScsictlCommands::CommandCreateImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDeleteImage(const string& filename) +bool ScsictlCommands::CommandDeleteImage(string_view filename) { SetParam(command, "file", filename); return SendCommand(); } -bool ScsictlCommands::CommandRenameImage(const string& image_params) +bool ScsictlCommands::CommandRenameImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -190,13 +190,9 @@ bool ScsictlCommands::CommandRenameImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandCopyImage(const string& image_params) +bool ScsictlCommands::CommandCopyImage(string_view image_params) { - if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); - SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); - } - else { + if (!EvaluateParams(image_params, "from", "to")) { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; return false; @@ -205,7 +201,7 @@ bool ScsictlCommands::CommandCopyImage(const string& image_params) return SendCommand(); } -bool ScsictlCommands::CommandDefaultImageFolder(const string& folder) +bool ScsictlCommands::CommandDefaultImageFolder(string_view folder) { SetParam(command, "folder", folder); @@ -259,8 +255,8 @@ bool ScsictlCommands::CommandServerInfo() cout << scsictl_display.DisplayOperationInfo(server_info.operation_info()); if (server_info.devices_info().devices_size()) { - list sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; - sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + vector sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; + ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); cout << "Attached devices:\n"; @@ -283,7 +279,7 @@ bool ScsictlCommands::CommandDefaultImageFilesInfo() return true; } -bool ScsictlCommands::CommandImageFileInfo(const string& filename) +bool ScsictlCommands::CommandImageFileInfo(string_view filename) { SetParam(command, "file", filename); @@ -339,15 +335,12 @@ bool ScsictlCommands::CommandOperationInfo() return true; } -bool ScsictlCommands::ResolveHostName(const string& host, sockaddr_in *addr) +bool ScsictlCommands::EvaluateParams(string_view image_params, const string& key1, const string& key2) { - addrinfo hints = {}; - hints.ai_family = AF_INET; - hints.ai_socktype = SOCK_STREAM; + if (const auto& components = Split(string(image_params), COMPONENT_SEPARATOR, 2); components.size() == 2) { + SetParam(command, key1, components[0]); + SetParam(command, key2, components[1]); - if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { - *addr = *(sockaddr_in *)(result->ai_addr); - freeaddrinfo(result); return true; } diff --git a/cpp/scsictl/scsictl_commands.h b/cpp/scsictl/scsictl_commands.h index b9e80963..11aa51fa 100644 --- a/cpp/scsictl/scsictl_commands.h +++ b/cpp/scsictl/scsictl_commands.h @@ -3,13 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once -#include "shared/protobuf_serializer.h" #include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include @@ -26,40 +25,38 @@ public: : command(command), hostname(hostname), port(port) {} ~ScsictlCommands() = default; - bool Execute(const string&, const string&, const string&, const string&, const string&); + bool Execute(string_view, string_view, string_view, string_view, string_view); bool CommandDevicesInfo(); private: - bool CommandLogLevel(const string&); - bool CommandReserveIds(const string&); - bool CommandCreateImage(const string&); - bool CommandDeleteImage(const string&); - bool CommandRenameImage(const string&); - bool CommandCopyImage(const string&); - bool CommandDefaultImageFolder(const string&); + bool CommandLogLevel(string_view); + bool CommandReserveIds(string_view); + bool CommandCreateImage(string_view); + bool CommandDeleteImage(string_view); + bool CommandRenameImage(string_view); + bool CommandCopyImage(string_view); + bool CommandDefaultImageFolder(string_view); bool CommandDeviceInfo(); bool CommandDeviceTypesInfo(); bool CommandVersionInfo(); bool CommandServerInfo(); bool CommandDefaultImageFilesInfo(); - bool CommandImageFileInfo(const string&); + bool CommandImageFileInfo(string_view); bool CommandNetworkInterfacesInfo(); bool CommandLogLevelInfo(); bool CommandReservedIdsInfo(); bool CommandMappingInfo(); bool CommandOperationInfo(); bool SendCommand(); + bool EvaluateParams(string_view, const string&, const string&); - static bool ResolveHostName(const string&, sockaddr_in *); - - ProtobufSerializer serializer; PbCommand& command; string hostname; int port; PbResult result; - ScsictlDisplay scsictl_display; + [[no_unique_address]] const ScsictlDisplay scsictl_display; }; diff --git a/cpp/scsictl/scsictl_core.cpp b/cpp/scsictl/scsictl_core.cpp index db2dea62..ff24d455 100644 --- a/cpp/scsictl/scsictl_core.cpp +++ b/cpp/scsictl/scsictl_core.cpp @@ -9,12 +9,12 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h" #include "shared/piscsi_util.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_core.h" @@ -30,27 +30,27 @@ using namespace protobuf_util; void ScsiCtl::Banner(const vector& args) const { if (args.size() < 2) { - cout << piscsi_util::Banner("(Controller App)"); - - cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; - cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; - cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; - cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; - cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; - cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n"; - cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; - cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"; - cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"; - cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; - cout << " FILE|PARAM := image file path or device-specific parameter\n"; - cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; - cout << " HOST := piscsi host to connect to, default is 'localhost'\n"; - cout << " PORT := piscsi port to connect to, default is 6868\n"; - cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; - cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; - cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; - cout << "Usage: " << args[0] << " -l\n"; - cout << " Print device list.\n" << flush; + cout << piscsi_util::Banner("(Controller App)") + << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] " + << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] " + << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] " + << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] " + << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n" + << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "}," + << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n" + << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n" + << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n" + << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n" + << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n" + << " FILE|PARAM := image file path or device-specific parameter\n" + << " IMAGE_FOLDER := default location for image files, default is '~/images'\n" + << " HOST := piscsi host to connect to, default is 'localhost'\n" + << " PORT := piscsi port to connect to, default is 6868\n" + << " RESERVED_IDS := comma-separated list of IDs to reserve\n" + << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n" + << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n" + << "Usage: " << args[0] << " -l\n" + << " Print device list.\n" << flush; exit(EXIT_SUCCESS); } @@ -77,10 +77,7 @@ int ScsiCtl::run(const vector& args) const string token; bool list = false; - const char *locale = setlocale(LC_MESSAGES, ""); - if (locale == nullptr || !strcmp(locale, "C")) { - locale = "en"; - } + string locale = GetLocale(); opterr = 1; int opt; @@ -88,7 +85,7 @@ int ScsiCtl::run(const vector& args) const "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) { switch (opt) { case 'i': - if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) { + if (const string error = SetIdAndLun(*device, optarg); !error.empty()) { cerr << "Error: " << error << endl; exit(EXIT_FAILURE); } @@ -216,7 +213,7 @@ int ScsiCtl::run(const vector& args) const break; case 'v': - cout << "scsictl version: " << piscsi_get_version_string() << endl; + cout << "scsictl version: " << piscsi_get_version_string() << '\n'; exit(EXIT_SUCCESS); break; @@ -263,7 +260,7 @@ int ScsiCtl::run(const vector& args) const bool status; try { - // Listing devices is a special case (rasctl backwards compatibility) + // Listing devices is a special case (legacy rasctl backwards compatibility) if (list) { command.clear_devices(); command.set_operation(DEVICES_INFO); diff --git a/cpp/scsictl/scsictl_display.cpp b/cpp/scsictl/scsictl_display.cpp index 705fa6a2..b71782c6 100644 --- a/cpp/scsictl/scsictl_display.cpp +++ b/cpp/scsictl/scsictl_display.cpp @@ -3,26 +3,29 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- +#include "shared/piscsi_util.h" #include "shared/protobuf_util.h" -#include "generated/piscsi_interface.pb.h" #include "scsictl_display.h" #include -#include +#include +#include +#include #include using namespace std; using namespace piscsi_interface; +using namespace piscsi_util; using namespace protobuf_util; string ScsictlDisplay::DisplayDevicesInfo(const PbDevicesInfo& devices_info) const { ostringstream s; - const list& devices = { devices_info.devices().begin(), devices_info.devices().end() }; + const vector devices(devices_info.devices().begin(), devices_info.devices().end()); s << ListDevices(devices); @@ -50,51 +53,33 @@ string ScsictlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) const s << " "; - bool hasProperty = false; + vector properties; if (pb_device.properties().read_only()) { - s << "read-only"; - hasProperty = true; + properties.emplace_back("read-only"); } if (pb_device.properties().protectable() && pb_device.status().protected_()) { - if (hasProperty) { - s << ", "; - } - s << "protected"; - hasProperty = true; + properties.emplace_back("protected"); } if (pb_device.properties().stoppable() && pb_device.status().stopped()) { - if (hasProperty) { - s << ", "; - } - s << "stopped"; - hasProperty = true; + properties.emplace_back("stopped"); } if (pb_device.properties().removable() && pb_device.status().removed()) { - if (hasProperty) { - s << ", "; - } - s << "removed"; - hasProperty = true; + properties.emplace_back("removed"); } if (pb_device.properties().lockable() && pb_device.status().locked()) { - if (hasProperty) { - s << ", "; - } - s << "locked"; + properties.emplace_back("locked"); } - if (hasProperty) { - s << " "; + if (!properties.empty()) { + s << Join(properties) << " "; } - DisplayParams(s, pb_device); - - s << '\n'; + s << DisplayParams(pb_device) << '\n'; return s.str(); } @@ -103,8 +88,8 @@ string ScsictlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) con { ostringstream s; - s << "piscsi server version: " << setw(2) << setfill('0') << version_info.major_version() << "." - << setw(2) << setfill('0') << version_info.minor_version(); + s << "piscsi server version: " << setfill('0') << setw(2) << version_info.major_version() << "." + << setw(2) << version_info.minor_version(); if (version_info.patch_version() > 0) { s << "." << version_info.patch_version(); @@ -149,7 +134,7 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty const PbDeviceProperties& properties = device_type_info.properties(); - DisplayAttributes(s, properties); + s << DisplayAttributes(properties); if (properties.supports_file()) { s << "Image file support\n "; @@ -159,9 +144,9 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty s << "Parameter support\n "; } - DisplayDefaultParameters(s, properties); + s << DisplayDefaultParameters(properties); - DisplayBlockSizes(s, properties); + s << DisplayBlockSizes(properties); } s << '\n'; @@ -174,20 +159,8 @@ string ScsictlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_ ostringstream s; if (reserved_ids_info.ids_size()) { - s << "Reserved device IDs: "; - - for (int i = 0; i < reserved_ids_info.ids_size(); i++) { - if(i) { - s << ", "; - } - - s << reserved_ids_info.ids(i); - } - - s << '\n'; - } - else { - s << "No reserved device IDs\n"; + const set sorted_ids(reserved_ids_info.ids().begin(), reserved_ids_info.ids().end()); + s << "Reserved device IDs: " << Join(sorted_ids) << '\n'; } return s.str(); @@ -219,12 +192,9 @@ string ScsictlDisplay::DisplayImageFilesInfo(const PbImageFilesInfo& image_files s << "Default image file folder: " << image_files_info.default_image_folder() << '\n'; s << "Supported folder depth: " << image_files_info.depth() << '\n'; - if (image_files_info.image_files().empty()) { - s << " No image files available\n"; - } - else { - list image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() }; - image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + if (!image_files_info.image_files().empty()) { + vector image_files(image_files_info.image_files().begin(), image_files_info.image_files().end()); + ranges::sort(image_files, [](const auto& a, const auto& b) { return a.name() < b.name(); }); s << "Available image files:\n"; for (const auto& image_file : image_files) { @@ -241,24 +211,8 @@ string ScsictlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& n { ostringstream s; - s << "Available (up) network interfaces:\n"; - - const list sorted_interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() }; - - bool isFirst = true; - for (const auto& interface : sorted_interfaces) { - if (!isFirst) { - s << ", "; - } - else { - s << " "; - } - - isFirst = false; - s << interface; - } - - s << '\n'; + const set> sorted_interfaces(network_interfaces_info.name().begin(), network_interfaces_info.name().end()); + s << "Available (up) network interfaces: " << Join(sorted_interfaces) << '\n'; return s.str(); } @@ -269,9 +223,8 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con s << "Supported image file extension to device type mappings:\n"; - const map> sorted_mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() }; - - for (const auto& [extension, type] : sorted_mappings) { + for (const map> sorted_mappings(mapping_info.mapping().begin(), mapping_info.mapping().end()); + const auto& [extension, type] : sorted_mappings) { s << " " << extension << "->" << PbDeviceType_Name(type) << '\n'; } @@ -282,7 +235,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf { ostringstream s; - const map> operations = { operation_info.operations().begin(), operation_info.operations().end() }; + const map> operations(operation_info.operations().begin(), operation_info.operations().end()); // Copies result into a map sorted by operation name auto unknown_operation = make_unique(); @@ -308,7 +261,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf } s << '\n'; - DisplayParameters(s, meta_data); + s << DisplayParameters(meta_data); } else { s << " " << name << " (Unknown server-side operation)\n"; @@ -318,96 +271,82 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf return s.str(); } -void ScsictlDisplay::DisplayParams(ostringstream& s, const PbDevice& pb_device) const +string ScsictlDisplay::DisplayParams(const PbDevice& pb_device) const { - const map> sorted_params = { pb_device.params().begin(), pb_device.params().end() }; + ostringstream s; - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << ":"; - } - - isFirst = false; - s << key << "=" << value; - } -} - -void ScsictlDisplay::DisplayAttributes(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.lockable()) { - s << "Properties: "; - - bool has_property = false; - - if (properties.read_only()) { - s << "read-only"; - has_property = true; - } - - if (properties.protectable()) { - s << (has_property ? ", " : "") << "protectable"; - has_property = true; - } - if (properties.stoppable()) { - s << (has_property ? ", " : "") << "stoppable"; - has_property = true; - } - if (properties.removable()) { - s << (has_property ? ", " : "") << "removable"; - has_property = true; - } - if (properties.lockable()) { - s << (has_property ? ", " : "") << "lockable"; - } - - s << "\n "; - } -} - -void ScsictlDisplay::DisplayDefaultParameters(ostringstream& s, const PbDeviceProperties& properties) const -{ - if (properties.supports_params() && properties.default_params_size()) { - s << "Default parameters: "; - - const map> sorted_params = { properties.default_params().begin(), properties.default_params().end() }; - - bool isFirst = true; - for (const auto& [key, value] : sorted_params) { - if (!isFirst) { - s << "\n "; - } - s << key << "=" << value; - - isFirst = false; - } + set> params; + for (const auto& [key, value] : pb_device.params()) { + params.insert(key + "=" + value); } + s << Join(params, ":"); + + return s.str(); } -void ScsictlDisplay::DisplayBlockSizes(ostringstream& s, const PbDeviceProperties& properties) const +string ScsictlDisplay::DisplayAttributes(const PbDeviceProperties& props) const { + ostringstream s; + + vector properties; + if (props.read_only()) { + properties.emplace_back("read-only"); + } + if (props.protectable()) { + properties.emplace_back("protectable"); + } + if (props.stoppable()) { + properties.emplace_back("stoppable"); + } + if (props.removable()) { + properties.emplace_back("removable"); + } + if (props.lockable()) { + properties.emplace_back("lockable"); + } + + if (!properties.empty()) { + s << "Properties: " << Join(properties) << "\n "; + } + + return s.str(); +} + +string ScsictlDisplay::DisplayDefaultParameters(const PbDeviceProperties& properties) const +{ + ostringstream s; + + if (!properties.default_params().empty()) { + set> params; + for (const auto& [key, value] : properties.default_params()) { + params.insert(key + "=" + value); + } + + s << "Default parameters: " << Join(params, "\n "); + } + + return s.str(); +} + +string ScsictlDisplay::DisplayBlockSizes(const PbDeviceProperties& properties) const +{ + ostringstream s; + if (properties.block_sizes_size()) { - s << "Configurable block sizes in bytes: "; - - const set sorted_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() }; - - bool isFirst = true; - for (const auto& size : sorted_sizes) { - if (!isFirst) { - s << ", "; - } - s << size; - - isFirst = false; - } + const set sorted_sizes(properties.block_sizes().begin(), properties.block_sizes().end()); + s << "Configurable block sizes in bytes: " << Join(sorted_sizes); } + + return s.str(); } -void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaData& meta_data) const +string ScsictlDisplay::DisplayParameters(const PbOperationMetaData& meta_data) const { - list sorted_parameters = { meta_data.parameters().begin(), meta_data.parameters().end() }; - sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); }); + vector sorted_parameters(meta_data.parameters().begin(), meta_data.parameters().end()); + ranges::sort(sorted_parameters, [](const auto& a, const auto& b) { return a.name() < b.name(); }); + + ostringstream s; for (const auto& parameter : sorted_parameters) { s << " " << parameter.name() << ": " @@ -418,30 +357,23 @@ void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaDa } s << '\n'; - DisplayPermittedValues(s, parameter); + s << DisplayPermittedValues(parameter); if (!parameter.default_value().empty()) { s << " Default value: " << parameter.default_value() << '\n'; } } + + return s.str(); } -void ScsictlDisplay::DisplayPermittedValues(ostringstream& s, const PbOperationParameter& parameter) const +string ScsictlDisplay::DisplayPermittedValues(const PbOperationParameter& parameter) const { + ostringstream s; if (parameter.permitted_values_size()) { - s << " Permitted values: "; - - bool isFirst = true; - - for (const auto& permitted_value : parameter.permitted_values()) { - if (!isFirst) { - s << ", "; - } - - isFirst = false; - s << permitted_value; - } - - s << '\n'; + const set> sorted_values(parameter.permitted_values().begin(), parameter.permitted_values().end()); + s << " Permitted values: " << Join(parameter.permitted_values()) << '\n'; } + + return s.str(); } diff --git a/cpp/scsictl/scsictl_display.h b/cpp/scsictl/scsictl_display.h index 6a7f4c55..1caddf48 100644 --- a/cpp/scsictl/scsictl_display.h +++ b/cpp/scsictl/scsictl_display.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,7 +11,6 @@ #include "generated/piscsi_interface.pb.h" #include -#include using namespace std; using namespace piscsi_interface; @@ -37,10 +36,10 @@ public: private: - void DisplayParams(ostringstream&, const PbDevice&) const; - void DisplayAttributes(ostringstream&, const PbDeviceProperties&) const; - void DisplayDefaultParameters(ostringstream&, const PbDeviceProperties&) const; - void DisplayBlockSizes(ostringstream&, const PbDeviceProperties&) const; - void DisplayParameters(ostringstream&, const PbOperationMetaData&) const; - void DisplayPermittedValues(ostringstream&, const PbOperationParameter&) const; + string DisplayParams(const PbDevice&) const; + string DisplayAttributes(const PbDeviceProperties&) const; + string DisplayDefaultParameters(const PbDeviceProperties&) const; + string DisplayBlockSizes(const PbDeviceProperties&) const; + string DisplayParameters(const PbOperationMetaData&) const; + string DisplayPermittedValues(const PbOperationParameter&) const; }; diff --git a/cpp/scsictl/scsictl_parser.cpp b/cpp/scsictl/scsictl_parser.cpp index cd9ef35c..175b5ba8 100644 --- a/cpp/scsictl/scsictl_parser.cpp +++ b/cpp/scsictl/scsictl_parser.cpp @@ -3,13 +3,13 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "scsictl_parser.h" -PbOperation ScsictlParser::ParseOperation(const string& operation) const +PbOperation ScsictlParser::ParseOperation(string_view operation) const { const auto& it = operations.find(tolower(operation[0])); return it != operations.end() ? it->second : NO_OPERATION; @@ -17,8 +17,8 @@ PbOperation ScsictlParser::ParseOperation(const string& operation) const PbDeviceType ScsictlParser::ParseType(const string& type) const { - string t = type; - transform(t.begin(), t.end(), t.begin(), ::toupper); + string t; + ranges::transform(type, back_inserter(t), ::toupper); if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) { return parsed_type; diff --git a/cpp/scsictl/scsictl_parser.h b/cpp/scsictl/scsictl_parser.h index a09dc11a..ec5f2ee2 100644 --- a/cpp/scsictl/scsictl_parser.h +++ b/cpp/scsictl/scsictl_parser.h @@ -22,12 +22,12 @@ public: ScsictlParser() = default; ~ScsictlParser() = default; - PbOperation ParseOperation(const string&) const; + PbOperation ParseOperation(string_view) const; PbDeviceType ParseType(const string&) const; private: - unordered_map operations = { + const unordered_map operations = { { 'a', ATTACH }, { 'd', DETACH }, { 'e', EJECT }, @@ -37,7 +37,7 @@ private: { 'u', UNPROTECT } }; - unordered_map device_types = { + const unordered_map device_types = { { 'b', SCBR }, { 'c', SCCD }, { 'd', SCDP }, diff --git a/cpp/scsidump.cpp b/cpp/scsidump.cpp index 35d7645c..2f885529 100644 --- a/cpp/scsidump.cpp +++ b/cpp/scsidump.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,7 +13,7 @@ using namespace std; int main(int argc, char *argv[]) { - const vector args(argv, argv + argc); + vector args(argv, argv + argc); return ScsiDump().run(args); } diff --git a/cpp/scsidump/scsidump_core.cpp b/cpp/scsidump/scsidump_core.cpp index 7d1e2060..9df536ca 100644 --- a/cpp/scsidump/scsidump_core.cpp +++ b/cpp/scsidump/scsidump_core.cpp @@ -5,8 +5,8 @@ // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS -// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 akuker +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,10 +17,11 @@ #include "hal/gpiobus.h" #include "hal/gpiobus_factory.h" #include "hal/systimer.h" -#include "shared/log.h" +#include "controllers/controller_manager.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_util.h" -#include "shared/piscsi_version.h" +#include +#include #include #include #include @@ -28,10 +29,10 @@ #include #include #include -#include #include using namespace std; +using namespace filesystem; using namespace spdlog; using namespace scsi_defs; using namespace piscsi_util; @@ -50,17 +51,18 @@ void ScsiDump::KillHandler(int) exit(EXIT_SUCCESS); } -bool ScsiDump::Banner(const vector& args) const +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" - << " ID is the target device ID (0-7).\n" - << " LUN is the optional target device LUN (0-7). Default is 0.\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" << " BID is the PiSCSI board ID (0-7). Default is 7.\n" << " FILE is the dump file path.\n" - << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE) + << " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE << " bytes. Default is 1 MiB.\n" << " -v Enable verbose logging.\n" << " -r Restore instead of dump.\n" @@ -86,7 +88,7 @@ bool ScsiDump::Init() const return bus != nullptr; } -void ScsiDump::ParseArguments(const vector& args) +void ScsiDump::ParseArguments(span args) { int opt; @@ -107,13 +109,13 @@ void ScsiDump::ParseArguments(const vector& args) 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"); + throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB"); } break; case 't': { - const string error = ProcessId(optarg, 8, target_id, target_lun); + const string error = ProcessId(optarg, target_id, target_lun); if (!error.empty()) { throw parser_exception(error); } @@ -149,7 +151,7 @@ void ScsiDump::ParseArguments(const vector& args) void ScsiDump::WaitPhase(phase_t phase) const { - LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase)) + spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase"); // Timeout (3000ms) const uint32_t now = SysTimer::GetTimerLow(); @@ -180,7 +182,7 @@ void ScsiDump::Selection() const void ScsiDump::Command(scsi_command cmd, vector& cdb) const { - LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second) + spdlog::debug("Executing " + command_mapping.find(cmd)->second.second); Selection(); @@ -324,7 +326,7 @@ pair ScsiDump::ReadCapacity() (static_cast(buffer[sector_size_offset + 2]) << 8) | static_cast(buffer[sector_size_offset + 3]); - return make_pair(capacity, sector_size); + return { capacity, sector_size }; } void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length) @@ -387,7 +389,7 @@ void ScsiDump::WaitForBusy() const } } -int ScsiDump::run(const vector& args) +int ScsiDump::run(span args) { if (!Banner(args)) { return EXIT_SUCCESS; @@ -436,12 +438,12 @@ int ScsiDump::DumpRestore() if (restore) { cout << "Starting restore\n" << flush; - // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB off_t size; - if (struct stat st; !stat(filename.c_str(), &st)) { - size = st.st_size; - } else { - throw parser_exception("Can't determine file size"); + try { + size = file_size(path(filename)); + } + catch (const filesystem_error& e) { + throw parser_exception(string("Can't determine file size: ") + e.what()); } cout << "Restore file size: " << size << " bytes\n"; @@ -455,11 +457,11 @@ int ScsiDump::DumpRestore() } // Dump by buffer size - auto dsiz = static_cast(buffer.size()); + auto dsiz = static_cast(buffer.size()); const int duni = dsiz / inq_info.sector_size; - auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); + auto dnum = static_cast((inq_info.capacity * inq_info.sector_size) / dsiz); - auto start_time = chrono::high_resolution_clock::now(); + const auto start_time = chrono::high_resolution_clock::now(); int i; for (i = 0; i < dnum; i++) { @@ -501,16 +503,16 @@ int ScsiDump::DumpRestore() cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush; } - auto stop_time = chrono::high_resolution_clock::now(); + const auto stop_time = chrono::high_resolution_clock::now(); - auto duration = chrono::duration_cast(stop_time - start_time).count(); + const auto duration = chrono::duration_cast(stop_time - start_time).count(); cout << divider_str << "\n"; - cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes [" - << to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n"; - cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n"; - cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration) - << " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024) + 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"; @@ -554,8 +556,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() cout << "Revision: " << str.data() << "\n" << flush; inq_info.revision = string(str.data()); - if (auto type = static_cast(buffer[0]); - type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) { + 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"); } @@ -564,8 +566,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() RequestSense(); const auto [capacity, sector_size] = ReadCapacity(); - inq_info.capacity = capacity; - inq_info.sector_size = sector_size; + inq_info.capacity = capacity; + inq_info.sector_size = sector_size; cout << "Sectors: " << capacity << "\n" << "Sector size: " << sector_size << " bytes\n" @@ -579,24 +581,23 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo() void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info) { - string prop_filename = filename + ".properties"; - string prop_str; - stringstream prop_stream(prop_str); + const string prop_filename = filename + ".properties"; + stringstream prop_stream; prop_stream << "{" << endl; prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl; - prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl; + prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl; prop_stream << "}" << endl; FILE* fp = fopen(prop_filename.c_str(), "w"); if (fp) { fputs(prop_stream.str().c_str(), fp); } else { - LOGWARN("Unable to open output file %s", prop_filename.c_str()) + spdlog::warn("Unable to open output file '" + prop_filename + "'"); return; } fclose(fp); -} \ No newline at end of file +} diff --git a/cpp/scsidump/scsidump_core.h b/cpp/scsidump/scsidump_core.h index c46a6993..64a82fa8 100644 --- a/cpp/scsidump/scsidump_core.h +++ b/cpp/scsidump/scsidump_core.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "shared/scsi.h" #include #include +#include #include using namespace std; @@ -26,7 +27,7 @@ class ScsiDump ScsiDump() = default; ~ScsiDump() = default; - int run(const vector&); + int run(const span); struct inquiry_info_struct { string vendor; @@ -42,9 +43,9 @@ class ScsiDump static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info); private: - bool Banner(const vector&) const; + bool Banner(span) const; bool Init() const; - void ParseArguments(const vector&); + void ParseArguments(span); int DumpRestore(); inquiry_info_t GetDeviceInfo(); void WaitPhase(phase_t) const; diff --git a/cpp/scsiloop/scsiloop_core.cpp b/cpp/scsiloop/scsiloop_core.cpp index b6181d64..c5806bf2 100644 --- a/cpp/scsiloop/scsiloop_core.cpp +++ b/cpp/scsiloop/scsiloop_core.cpp @@ -12,7 +12,7 @@ // //--------------------------------------------------------------------------- -#include "shared/log.h" +#include "hal/log.h" #include "shared/piscsi_version.h" #include "shared/piscsi_util.h" #include "spdlog/sinks/stdout_color_sinks.h" diff --git a/cpp/scsiloop/scsiloop_cout.cpp b/cpp/scsiloop/scsiloop_cout.cpp index b681fac6..8ee99332 100644 --- a/cpp/scsiloop/scsiloop_cout.cpp +++ b/cpp/scsiloop/scsiloop_cout.cpp @@ -30,11 +30,11 @@ void ScsiLoop_Cout::FinishTest(const string &test_name, int failures) } } -void ScsiLoop_Cout::PrintErrors(vector &test_errors) +void ScsiLoop_Cout::PrintErrors(const vector &test_errors) { - if (test_errors.size() > 0) { - for (auto err_string : test_errors) { + if (!test_errors.empty()) { + for (auto& err_string : test_errors) { cout << RED << err_string << endl; } } -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_cout.h b/cpp/scsiloop/scsiloop_cout.h index 35d0bc4d..6ac5a12a 100644 --- a/cpp/scsiloop/scsiloop_cout.h +++ b/cpp/scsiloop/scsiloop_cout.h @@ -18,7 +18,7 @@ class ScsiLoop_Cout static void StartTest(const string &test_name); static void PrintUpdate(); static void FinishTest(const string &test_name, int failures); - static void PrintErrors(vector &test_errors); + static void PrintErrors(const vector &test_errors); private: const static inline string RESET = "\033[0m"; @@ -30,4 +30,4 @@ class ScsiLoop_Cout const static inline string MAGENTA = "\033[35m"; /* Magenta */ const static inline string CYAN = "\033[36m"; /* Cyan */ const static inline string WHITE = "\033[37m"; /* White */ -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_gpio.cpp b/cpp/scsiloop/scsiloop_gpio.cpp index c4a86a96..8032eadf 100644 --- a/cpp/scsiloop/scsiloop_gpio.cpp +++ b/cpp/scsiloop/scsiloop_gpio.cpp @@ -11,7 +11,7 @@ #include "hal/gpiobus_factory.h" #include "hal/sbc_version.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" #if defined CONNECT_TYPE_STANDARD #include "hal/connection_type/connection_standard.h" @@ -589,4 +589,4 @@ int ScsiLoop_GPIO::RunDataOutputTest(vector &error_list) ScsiLoop_Cout::FinishTest("DAT Outputs", err_count); return err_count; -} \ No newline at end of file +} diff --git a/cpp/scsiloop/scsiloop_gpio.h b/cpp/scsiloop/scsiloop_gpio.h index d4365682..a61ae19e 100644 --- a/cpp/scsiloop/scsiloop_gpio.h +++ b/cpp/scsiloop/scsiloop_gpio.h @@ -31,7 +31,7 @@ class ScsiLoop_GPIO int connected_pin; int dir_ctrl_pin; }; - typedef loopback_connections_struct loopback_connection; + using loopback_connection = loopback_connections_struct; std::map pin_name_lookup; std::vector loopback_conn_table; @@ -78,4 +78,4 @@ class ScsiLoop_GPIO int local_pin_dp = -1; shared_ptr bus; -}; \ No newline at end of file +}; diff --git a/cpp/scsiloop/scsiloop_timer.cpp b/cpp/scsiloop/scsiloop_timer.cpp index 293c268a..4cfe627a 100644 --- a/cpp/scsiloop/scsiloop_timer.cpp +++ b/cpp/scsiloop/scsiloop_timer.cpp @@ -10,7 +10,7 @@ #include "scsiloop_timer.h" #include "hal/systimer.h" #include "scsiloop/scsiloop_cout.h" -#include "shared/log.h" +#include "hal/log.h" int ScsiLoop_Timer::RunTimerTest(vector &error_list) { @@ -56,7 +56,7 @@ int ScsiLoop_Timer::RunTimerTest(vector &error_list) } after = SysTimer::GetTimerLow(); elapsed_nanosecs = after - before; - LOGDEBUG("SysTimer::SleepUsec() Average %d", (uint32_t)(elapsed_nanosecs / 100)); + LOGDEBUG("SysTimer::SleepUsec() Average %d", elapsed_nanosecs / 100); if ((elapsed_nanosecs > expected_usec_result * (1.0 + timer_tolerance_percent)) || (elapsed_nanosecs < expected_usec_result * (1.0 - timer_tolerance_percent))) { diff --git a/cpp/shared/network_util.cpp b/cpp/shared/network_util.cpp new file mode 100644 index 00000000..31aa1831 --- /dev/null +++ b/cpp/shared/network_util.cpp @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "network_util.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +bool network_util::IsInterfaceUp(const string& interface) +{ + ifreq ifr = {}; + strncpy(ifr.ifr_name, interface.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe + const int fd = socket(PF_INET6, SOCK_DGRAM, IPPROTO_IP); + + if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) { + close(fd); + return true; + } + + close(fd); + return false; +} + +set> network_util::GetNetworkInterfaces() +{ + set> network_interfaces; + +#ifdef __linux__ + ifaddrs *addrs; + getifaddrs(&addrs); + ifaddrs *tmp = addrs; + + while (tmp) { + if (const string name = tmp->ifa_name; tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET && + name != "lo" && name != "piscsi_bridge" && !name.starts_with("dummy") && IsInterfaceUp(name)) { + // Only list interfaces that are up + network_interfaces.insert(name); + } + + tmp = tmp->ifa_next; + } + + freeifaddrs(addrs); +#endif + + return network_interfaces; +} + +bool network_util::ResolveHostName(const string& host, sockaddr_in *addr) +{ + addrinfo hints = {}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) { + *addr = *reinterpret_cast(result->ai_addr); //NOSONAR bit_cast is not supported by the bullseye compiler + freeaddrinfo(result); + return true; + } + + return false; +} diff --git a/cpp/shared/network_util.h b/cpp/shared/network_util.h new file mode 100644 index 00000000..86d0adf9 --- /dev/null +++ b/cpp/shared/network_util.h @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include +#include + +using namespace std; + +struct sockaddr_in; + +namespace network_util +{ + bool IsInterfaceUp(const string&); + set> GetNetworkInterfaces(); + bool ResolveHostName(const string&, sockaddr_in *); +} diff --git a/cpp/shared/piscsi_exceptions.h b/cpp/shared/piscsi_exceptions.h index 34c58591..be003822 100644 --- a/cpp/shared/piscsi_exceptions.h +++ b/cpp/shared/piscsi_exceptions.h @@ -36,7 +36,7 @@ class scsi_exception : public exception public: - scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION) + scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information) : sense_key(sense_key), asc(asc) {} ~scsi_exception() override = default; diff --git a/cpp/shared/piscsi_util.cpp b/cpp/shared/piscsi_util.cpp index 2808ee6c..0cdccf02 100644 --- a/cpp/shared/piscsi_util.cpp +++ b/cpp/shared/piscsi_util.cpp @@ -7,13 +7,48 @@ // //--------------------------------------------------------------------------- +#include "controllers/controller_manager.h" #include "piscsi_version.h" #include "piscsi_util.h" +#include #include +#include #include +#include #include using namespace std; +using namespace filesystem; + +vector piscsi_util::Split(const string& s, char separator, int limit) +{ + assert(limit >= 0); + + string component; + vector result; + stringstream str(s); + + while (--limit > 0 && getline(str, component, separator)) { + result.push_back(component); + } + + if (!str.eof()) { + getline(str, component); + result.push_back(component); + } + + return result; +} + +string piscsi_util::GetLocale() +{ + const char *locale = setlocale(LC_MESSAGES, ""); + if (locale == nullptr || !strcmp(locale, "C")) { + locale = "en"; + } + + return locale; +} bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) { @@ -35,10 +70,8 @@ bool piscsi_util::GetAsUnsignedInt(const string& value, int& result) return true; } -string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& lun) +string piscsi_util::ProcessId(const string& id_spec, int& id, int& lun) { - assert(max_luns > 0); - id = -1; lun = -1; @@ -46,27 +79,32 @@ string piscsi_util::ProcessId(const string& id_spec, int max_luns, int& id, int& return "Missing device ID"; } - if (const size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { - if (!GetAsUnsignedInt(id_spec, id) || id >= 8) { - id = -1; + const int id_max = ControllerManager::GetScsiIdMax(); + const int lun_max = ControllerManager::GetScsiLunMax(); - return "Invalid device ID (0-7)"; + if (const auto& components = Split(id_spec, COMPONENT_SEPARATOR, 2); !components.empty()) { + if (components.size() == 1) { + if (!GetAsUnsignedInt(components[0], id) || id >= id_max) { + id = -1; + + return "Invalid device ID (0-" + to_string(ControllerManager::GetScsiIdMax() - 1) + ")"; + } + + return ""; } - lun = 0; - } - else if (!GetAsUnsignedInt(id_spec.substr(0, separator_pos), id) || id > 7 || - !GetAsUnsignedInt(id_spec.substr(separator_pos + 1), lun) || lun >= max_luns) { - id = -1; - lun = -1; + if (!GetAsUnsignedInt(components[0], id) || id >= id_max || !GetAsUnsignedInt(components[1], lun) || lun >= lun_max) { + id = -1; + lun = -1; - return "Invalid LUN (0-" + to_string(max_luns - 1) + ")"; + return "Invalid LUN (0-" + to_string(lun_max - 1) + ")"; + } } return ""; } -string piscsi_util::Banner(const string& app) +string piscsi_util::Banner(string_view app) { ostringstream s; @@ -79,15 +117,18 @@ string piscsi_util::Banner(const string& app) return s.str(); } -string piscsi_util::GetExtensionLowerCase(const string& filename) +string piscsi_util::GetExtensionLowerCase(string_view filename) { string ext; - if (const size_t separator = filename.rfind('.'); separator != string::npos) { - ext = filename.substr(separator + 1); - } - transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + ranges::transform(path(filename).extension().string(), back_inserter(ext), ::tolower); - return ext; + // Remove the leading dot + return ext.empty() ? "" : ext.substr(1); +} + +void piscsi_util::LogErrno(const string& msg) +{ + spdlog::error(errno ? msg + ": " + string(strerror(errno)) : msg); } // Pin the thread to a specific CPU diff --git a/cpp/shared/piscsi_util.h b/cpp/shared/piscsi_util.h index 762db353..477a8cab 100644 --- a/cpp/shared/piscsi_util.h +++ b/cpp/shared/piscsi_util.h @@ -3,13 +3,16 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include +#include using namespace std; @@ -18,11 +21,38 @@ namespace piscsi_util // Separator for compound options like ID:LUN static const char COMPONENT_SEPARATOR = ':'; - bool GetAsUnsignedInt(const string&, int&); - string ProcessId(const string&, int, int&, int&); - string Banner(const string&); + struct StringHash { + using is_transparent = void; - string GetExtensionLowerCase(const string&); + size_t operator()(string_view sv) const { + hash hasher; + return hasher(sv); + } + }; + + string Join(const auto& collection, const string_view separator = ", ") { + ostringstream s; + + for (const auto& element : collection) { + if (s.tellp()) { + s << separator; + } + + s << element; + } + + return s.str(); + } + + vector Split(const string&, char, int = INT_MAX); + string GetLocale(); + bool GetAsUnsignedInt(const string&, int&); + string ProcessId(const string&, int&, int&); + string Banner(string_view); + + string GetExtensionLowerCase(string_view); + + void LogErrno(const string&); void FixCpu(int); } diff --git a/cpp/shared/piscsi_version.cpp b/cpp/shared/piscsi_version.cpp index 63dfe058..05c297fd 100644 --- a/cpp/shared/piscsi_version.cpp +++ b/cpp/shared/piscsi_version.cpp @@ -4,7 +4,6 @@ // for Raspberry Pi // // Copyright (C) 2020 akuker -// [ Define the version string ] // //--------------------------------------------------------------------------- @@ -14,8 +13,8 @@ // The following should be updated for each release const int piscsi_major_version = 23; // Last two digits of year -const int piscsi_minor_version = 5; // Month -const int piscsi_patch_version = -1; // Patch number - increment for each update +const int piscsi_minor_version = 10; // Month +const int piscsi_patch_version = -1; // Patch number - increment for each update using namespace std; diff --git a/cpp/shared/protobuf_serializer.cpp b/cpp/shared/protobuf_serializer.cpp deleted file mode 100644 index ffca4e23..00000000 --- a/cpp/shared/protobuf_serializer.cpp +++ /dev/null @@ -1,80 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace std; -using namespace piscsi_interface; - -//--------------------------------------------------------------------------- -// -// Serialize/Deserialize protobuf message: Length followed by the actual data. -// A little endian platform is assumed. -// -//--------------------------------------------------------------------------- - -void ProtobufSerializer::SerializeMessage(int fd, const google::protobuf::Message& message) const -{ - string data; - message.SerializeToString(&data); - - // Write the size of the protobuf data as a header - auto size = static_cast(data.length()); - if (write(fd, &size, sizeof(size)) != sizeof(size)) { - throw io_exception("Can't write protobuf message header"); - } - - // Write the actual protobuf data - if (write(fd, data.data(), size) != size) { - throw io_exception("Can't write protobuf message data"); - } -} - -void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const -{ - // Read the header with the size of the protobuf data - vector header_buf(4); - if (ReadBytes(fd, header_buf) < header_buf.size()) { - throw io_exception("Invalid protobuf message header"); - } - - const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) - + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); - if (size < 0) { - throw io_exception("Invalid protobuf message header"); - } - - // Read the binary protobuf data - vector data_buf(size); - if (ReadBytes(fd, data_buf) < data_buf.size()) { - throw io_exception("Missing protobuf message data"); - } - - // Create protobuf message - string data((const char *)data_buf.data(), size); - message.ParseFromString(data); -} - -size_t ProtobufSerializer::ReadBytes(int fd, vector& buf) const -{ - size_t offset = 0; - while (offset < buf.size()) { - const ssize_t len = read(fd, &buf.data()[offset], buf.size() - offset); - if (len <= 0) { - return len; - } - - offset += len; - } - - return offset; -} diff --git a/cpp/shared/protobuf_serializer.h b/cpp/shared/protobuf_serializer.h deleted file mode 100644 index b9a67302..00000000 --- a/cpp/shared/protobuf_serializer.h +++ /dev/null @@ -1,29 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -// Helper for serializing/deserializing protobuf messages -// -//--------------------------------------------------------------------------- - -#pragma once - -#include "google/protobuf/message.h" -#include - -using namespace std; - -class ProtobufSerializer -{ -public: - - ProtobufSerializer() = default; - ~ProtobufSerializer() = default; - - void SerializeMessage(int, const google::protobuf::Message&) const; - void DeserializeMessage(int, google::protobuf::Message&) const; - size_t ReadBytes(int, vector&) const; -}; diff --git a/cpp/shared/protobuf_util.cpp b/cpp/shared/protobuf_util.cpp index 9557b5bc..1cdff94b 100644 --- a/cpp/shared/protobuf_util.cpp +++ b/cpp/shared/protobuf_util.cpp @@ -3,15 +3,18 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "log.h" +#include "shared/piscsi_exceptions.h" #include "piscsi_util.h" -#include "protobuf_serializer.h" #include "protobuf_util.h" +#include #include +#include +#include + using namespace std; using namespace piscsi_util; @@ -32,61 +35,21 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa return; } - stringstream ss(params); - string p; - while (getline(ss, p, COMPONENT_SEPARATOR)) { - if (!p.empty()) { - const size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); - if (separator_pos != string::npos) { - SetParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); - } + for (const auto& p : Split(params, COMPONENT_SEPARATOR)) { + if (const auto& param = Split(p, KEY_VALUE_SEPARATOR, 2); param.size() == 2) { + SetParam(device, param[0], param[1]); } } } -string protobuf_util::GetParam(const PbCommand& command, const string& key) -{ - const auto& it = command.params().find(key); - return it != command.params().end() ? it->second : ""; -} - -string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& key) -{ - const auto& it = device.params().find(key); - return it != device.params().end() ? it->second : ""; -} - -void protobuf_util::SetParam(PbCommand& command, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *command.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetParam(PbDevice& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetParam(PbDeviceDefinition& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) +void protobuf_util::SetPatternParams(PbCommand& command, const string& patterns) { string folder_pattern; string file_pattern; - if (const size_t separator_pos = patterns.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - folder_pattern = patterns.substr(0, separator_pos); - file_pattern = patterns.substr(separator_pos + 1); + + if (const auto& components = Split(patterns, ':', 2); components.size() == 2) { + folder_pattern = components[0]; + file_pattern = components[1]; } else { file_pattern = patterns; @@ -98,40 +61,40 @@ void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) void protobuf_util::SetProductData(PbDeviceDefinition& device, const string& data) { - string name = data; + const auto& components = Split(data, COMPONENT_SEPARATOR, 3); + switch (components.size()) { + case 3: + device.set_revision(components[2]); + [[fallthrough]]; - if (size_t separator_pos = name.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - device.set_vendor(name.substr(0, separator_pos)); - name = name.substr(separator_pos + 1); - separator_pos = name.find(COMPONENT_SEPARATOR); - if (separator_pos != string::npos) { - device.set_product(name.substr(0, separator_pos)); - device.set_revision(name.substr(separator_pos + 1)); - } - else { - device.set_product(name); - } - } - else { - device.set_vendor(name); + case 2: + device.set_product(components[1]); + [[fallthrough]]; + + case 1: + device.set_vendor(components[0]); + break; + + default: + break; } } -string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value, int max_luns) +string protobuf_util::SetIdAndLun(PbDeviceDefinition& device, const string& value) { int id; int lun; - if (const string error = ProcessId(value, max_luns, id, lun); !error.empty()) { + if (const string error = ProcessId(value, id, lun); !error.empty()) { return error; } device.set_id(id); - device.set_unit(lun); + device.set_unit(lun != -1 ? lun : 0); return ""; } -string protobuf_util::ListDevices(const list& pb_devices) +string protobuf_util::ListDevices(const vector& pb_devices) { if (pb_devices.empty()) { return "No devices currently attached.\n"; @@ -142,8 +105,8 @@ string protobuf_util::ListDevices(const list& pb_devices) << "| ID | LUN | TYPE | IMAGE FILE\n" << "+----+-----+------+-------------------------------------\n"; - list devices = pb_devices; - devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); + vector devices = pb_devices; + ranges::sort(devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); for (const auto& device : devices) { string filename; @@ -179,3 +142,68 @@ string protobuf_util::ListDevices(const list& pb_devices) return s.str(); } + +//--------------------------------------------------------------------------- +// +// Serialize/Deserialize protobuf message: Length followed by the actual data. +// A little endian platform is assumed. +// +//--------------------------------------------------------------------------- + +void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message& message) +{ + const string data = message.SerializeAsString(); + + // Write the size of the protobuf data as a header + const auto size = static_cast(data.length()); + if (write(fd, &size, sizeof(size)) != sizeof(size)) { + throw io_exception("Can't write protobuf message size"); + } + + // Write the actual protobuf data + if (write(fd, data.data(), size) != size) { + throw io_exception("Can't write protobuf message data"); + } +} + +void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message& message) +{ + // Read the header with the size of the protobuf data + array header_buf; + if (ReadBytes(fd, header_buf) < header_buf.size()) { + throw io_exception("Can't read protobuf message size"); + } + + const int size = (static_cast(header_buf[3]) << 24) + (static_cast(header_buf[2]) << 16) + + (static_cast(header_buf[1]) << 8) + static_cast(header_buf[0]); + if (size < 0) { + throw io_exception("Invalid protobuf message size"); + } + + // Read the binary protobuf data + vector data_buf(size); + if (ReadBytes(fd, data_buf) != data_buf.size()) { + throw io_exception("Invalid protobuf message data"); + } + + message.ParseFromArray(data_buf.data(), size); +} + +size_t protobuf_util::ReadBytes(int fd, span buf) +{ + size_t offset = 0; + while (offset < buf.size()) { + const auto len = read(fd, &buf.data()[offset], buf.size() - offset); + if (len == -1) { + throw io_exception("Read error: " + string(strerror(errno))); + } + + if (!len) { + break; + } + + offset += len; + } + + return offset; +} diff --git a/cpp/shared/protobuf_util.h b/cpp/shared/protobuf_util.h index a0dcd760..0e5b2a78 100644 --- a/cpp/shared/protobuf_util.h +++ b/cpp/shared/protobuf_util.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2021-2022 Uwe Seimet +// Copyright (C) 2021-2023 Uwe Seimet // // Helper methods for setting up/evaluating protobuf messages // @@ -11,10 +11,10 @@ #pragma once -#include "google/protobuf/message.h" -#include "generated/piscsi_interface.pb.h" #include -#include +#include +#include +#include "generated/piscsi_interface.pb.h" using namespace std; using namespace piscsi_interface; @@ -23,14 +23,27 @@ namespace protobuf_util { static const char KEY_VALUE_SEPARATOR = '='; + string GetParam(const auto& item, const string& key) + { + const auto& it = item.params().find(key); + return it != item.params().end() ? it->second : ""; + } + + void SetParam(auto& item, const string& key, string_view value) + { + if (!key.empty() && !value.empty()) { + auto& map = *item.mutable_params(); + map[key] = value; + } + } + void ParseParameters(PbDeviceDefinition&, const string&); - string GetParam(const PbCommand&, const string&); - string GetParam(const PbDeviceDefinition&, const string&); - void SetParam(PbCommand&, const string&, string_view); - void SetParam(PbDevice&, const string&, string_view); - void SetParam(PbDeviceDefinition&, const string&, string_view); - void SetPatternParams(PbCommand&, string_view); + void SetPatternParams(PbCommand&, const string&); void SetProductData(PbDeviceDefinition&, const string&); - string SetIdAndLun(PbDeviceDefinition&, const string&, int); - string ListDevices(const list&); + string SetIdAndLun(PbDeviceDefinition&, const string&); + string ListDevices(const vector&); + + void SerializeMessage(int, const google::protobuf::Message&); + void DeserializeMessage(int, google::protobuf::Message&); + size_t ReadBytes(int, span); } diff --git a/cpp/shared/scsi.h b/cpp/shared/scsi.h index c8a4279b..b5611512 100644 --- a/cpp/shared/scsi.h +++ b/cpp/shared/scsi.h @@ -1,33 +1,39 @@ //--------------------------------------------------------------------------- // -// X68000 EMULATOR "XM6" +// X68000 EMULATOR "XM6" // -// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) -// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// Copyright (C) 2021-2023 Uwe Seimet // //--------------------------------------------------------------------------- #pragma once +#include #include +#include using namespace std; +// Command Descriptor Block +using cdb_t = span; + namespace scsi_defs { -enum class scsi_level : int { - SCSI_1_CCS = 1, - SCSI_2 = 2, - SPC = 3, - SPC_2 = 4, - SPC_3 = 5, - SPC_4 = 6, - SPC_5 = 7, - SPC_6 = 8 +enum class scsi_level { + scsi_1_ccs = 1, + scsi_2 = 2, + spc = 3, + spc_2 = 4, + spc_3 = 5, + spc_4 = 6, + spc_5 = 7, + spc_6 = 8 }; -// Phase definitions -enum class phase_t : int { +// Phase definitions +enum class phase_t { busfree, arbitration, selection, @@ -41,16 +47,16 @@ enum class phase_t : int { reserved }; -enum class device_type : int { - DIRECT_ACCESS = 0, - PRINTER = 2, - PROCESSOR = 3, - CD_ROM = 5, - OPTICAL_MEMORY = 7, - COMMUNICATIONS = 9 +enum class device_type { + direct_access = 0, + printer = 2, + processor = 3, + cd_rom = 5, + optical_memory = 7, + communications = 9 }; -enum class scsi_command : int { +enum class scsi_command { eCmdTestUnitReady = 0x00, eCmdRezero = 0x01, eCmdRequestSense = 0x03, @@ -104,35 +110,39 @@ enum class scsi_command : int { eCmdReportLuns = 0xA0 }; -enum class status : int { GOOD = 0x00, CHECK_CONDITION = 0x02, RESERVATION_CONFLICT = 0x18 }; - -enum class sense_key : int { - NO_SENSE = 0x00, - NOT_READY = 0x02, - MEDIUM_ERROR = 0x03, - ILLEGAL_REQUEST = 0x05, - UNIT_ATTENTION = 0x06, - DATA_PROTECT = 0x07, - ABORTED_COMMAND = 0x0b +enum class status { + good = 0x00, + check_condition = 0x02, + reservation_conflict = 0x18 }; -enum class asc : int { - NO_ADDITIONAL_SENSE_INFORMATION = 0x00, - WRITE_FAULT = 0x03, - READ_FAULT = 0x11, - INVALID_COMMAND_OPERATION_CODE = 0x20, - LBA_OUT_OF_RANGE = 0x21, - INVALID_FIELD_IN_CDB = 0x24, - INVALID_LUN = 0x25, - INVALID_FIELD_IN_PARAMETER_LIST = 0x26, - WRITE_PROTECTED = 0x27, - NOT_READY_TO_READY_CHANGE = 0x28, - POWER_ON_OR_RESET = 0x29, - MEDIUM_NOT_PRESENT = 0x3a, - LOAD_OR_EJECT_FAILED = 0x53 +enum class sense_key { + no_sense = 0x00, + not_ready = 0x02, + medium_error = 0x03, + illegal_request = 0x05, + unit_attention = 0x06, + data_protect = 0x07, + aborted_command = 0x0b }; -static const unordered_map> command_mapping = { +enum class asc { + no_additional_sense_information = 0x00, + write_fault = 0x03, + read_fault = 0x11, + invalid_command_operation_code = 0x20, + lba_out_of_range = 0x21, + invalid_field_in_cdb = 0x24, + invalid_lun = 0x25, + invalid_field_in_parameter_list = 0x26, + write_protected = 0x27, + not_ready_to_ready_change = 0x28, + power_on_or_reset = 0x29, + medium_not_present = 0x3a, + load_or_eject_failed = 0x53 +}; + +static const unordered_map> command_mapping = { {scsi_command::eCmdTestUnitReady, make_pair(6, "TestUnitReady")}, {scsi_command::eCmdRezero, make_pair(6, "Rezero")}, {scsi_command::eCmdRequestSense, make_pair(6, "RequestSense")}, diff --git a/cpp/test/abstract_controller_test.cpp b/cpp/test/abstract_controller_test.cpp index 8e1e5352..41270b4d 100644 --- a/cpp/test/abstract_controller_test.cpp +++ b/cpp/test/abstract_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -15,9 +15,7 @@ using namespace scsi_defs; TEST(AbstractControllerTest, AllocateCmd) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(16, controller.GetCmd().size()); controller.AllocateCmd(1234); @@ -26,9 +24,7 @@ TEST(AbstractControllerTest, AllocateCmd) TEST(AbstractControllerTest, AllocateBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.AllocateBuffer(1); EXPECT_LE(1, controller.GetBuffer().size()); @@ -39,25 +35,23 @@ TEST(AbstractControllerTest, AllocateBuffer) TEST(AbstractControllerTest, Reset) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); controller->AddDevice(device); controller->SetPhase(phase_t::status); EXPECT_EQ(phase_t::status, controller->GetPhase()); + EXPECT_CALL(*bus, Reset()); controller->Reset(); EXPECT_TRUE(controller->IsBusFree()); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_EQ(0, controller->GetLength()); } TEST(AbstractControllerTest, Next) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetNext(0x1234); EXPECT_EQ(0x1234, controller.GetNext()); @@ -67,9 +61,7 @@ TEST(AbstractControllerTest, Next) TEST(AbstractControllerTest, Message) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetMessage(0x12); EXPECT_EQ(0x12, controller.GetMessage()); @@ -77,9 +69,7 @@ TEST(AbstractControllerTest, Message) TEST(AbstractControllerTest, ByteTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetByteTransfer(false); EXPECT_FALSE(controller.IsByteTransfer()); @@ -89,9 +79,7 @@ TEST(AbstractControllerTest, ByteTransfer) TEST(AbstractControllerTest, BytesToTransfer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBytesToTransfer(0x1234); EXPECT_EQ(0x1234, controller.GetBytesToTransfer()); @@ -101,70 +89,17 @@ TEST(AbstractControllerTest, BytesToTransfer) TEST(AbstractControllerTest, GetMaxLuns) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_EQ(32, controller.GetMaxLuns()); } TEST(AbstractControllerTest, Status) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - controller.SetStatus(status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); -} - -TEST(AbstractControllerTest, ProcessPhase) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - - controller.SetPhase(phase_t::selection); - EXPECT_CALL(controller, Selection); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::busfree); - EXPECT_CALL(controller, BusFree); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::datain); - EXPECT_CALL(controller, DataIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::dataout); - EXPECT_CALL(controller, DataOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::command); - EXPECT_CALL(controller, Command); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::status); - EXPECT_CALL(controller, Status); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgin); - EXPECT_CALL(controller, MsgIn); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::msgout); - EXPECT_CALL(controller, MsgOut); - controller.ProcessPhase(); - - controller.SetPhase(phase_t::reselection); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); - - controller.SetPhase(phase_t::reserved); - EXPECT_THAT([&] { controller.ProcessPhase(); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller.SetStatus(status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); } TEST(AbstractControllerTest, DeviceLunLifeCycle) @@ -172,9 +107,7 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) const int ID = 1; const int LUN = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); + auto controller = make_shared>(ID); auto device1 = make_shared(LUN); auto device2 = make_shared(32); @@ -190,33 +123,16 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) EXPECT_FALSE(controller->HasDeviceForLun(0)); EXPECT_NE(nullptr, controller->GetDeviceForLun(LUN)); EXPECT_EQ(nullptr, controller->GetDeviceForLun(0)); - EXPECT_TRUE(controller->RemoveDevice(device1)); + EXPECT_TRUE(controller->RemoveDevice(*device1)); EXPECT_EQ(0, controller->GetLunCount()); - EXPECT_FALSE(controller->RemoveDevice(device1)); -} - -TEST(AbstractControllerTest, ExtractInitiatorId) -{ - const int ID = 1; - const int INITIATOR_ID = 7; - - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); - - EXPECT_EQ(INITIATOR_ID, controller.ExtractInitiatorId((1 << INITIATOR_ID) | ( 1 << ID))); - EXPECT_EQ(AbstractController::UNKNOWN_INITIATOR_ID, controller.ExtractInitiatorId(1 << ID)); + EXPECT_FALSE(controller->RemoveDevice(*device1)); } TEST(AbstractControllerTest, GetOpcode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[0] = static_cast(scsi_command::eCmdInquiry); + controller.SetCmdByte(0, static_cast(scsi_command::eCmdInquiry)); EXPECT_EQ(scsi_command::eCmdInquiry, controller.GetOpcode()); } @@ -224,33 +140,25 @@ TEST(AbstractControllerTest, GetLun) { const int LUN = 3; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; - auto& cmd = controller.GetCmd(); - - cmd[1] = LUN << 5; + controller.SetCmdByte(1, LUN << 5); EXPECT_EQ(LUN, controller.GetLun()); } TEST(AbstractControllerTest, Blocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.SetBlocks(1); - EXPECT_EQ(1, controller.GetBlocks()); + EXPECT_TRUE(controller.HasBlocks()); controller.DecrementBlocks(); - EXPECT_EQ(0, controller.GetBlocks()); + EXPECT_FALSE(controller.HasBlocks()); } TEST(AbstractControllerTest, Length) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -261,9 +169,7 @@ TEST(AbstractControllerTest, Length) TEST(AbstractControllerTest, UpdateOffsetAndLength) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; EXPECT_FALSE(controller.HasValidLength()); @@ -273,9 +179,7 @@ TEST(AbstractControllerTest, UpdateOffsetAndLength) TEST(AbstractControllerTest, Offset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + MockAbstractController controller; controller.ResetOffset(); EXPECT_EQ(0, controller.GetOffset()); diff --git a/cpp/test/command_context_test.cpp b/cpp/test/command_context_test.cpp index d99a1d59..f1f9772d 100644 --- a/cpp/test/command_context_test.cpp +++ b/cpp/test/command_context_test.cpp @@ -3,52 +3,149 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include +#include "test/test_shared.h" +#include "shared/piscsi_exceptions.h" +#include "shared/protobuf_util.h" #include "piscsi/command_context.h" +#include +#include -TEST(CommandContext, GetSerializer) +using namespace protobuf_util; + +TEST(CommandContext, SetGetDefaultFolder) { - CommandContext context("", -1); + PbCommand command; + CommandContext context(command, "folder1", ""); - // There is nothing more that can be tested - context.GetSerializer(); + EXPECT_EQ("folder1", context.GetDefaultFolder()); + context.SetDefaultFolder("folder2"); + EXPECT_EQ("folder2", context.GetDefaultFolder()); } -TEST(CommandContext, IsValid) +TEST(CommandContext, ReadCommand) { - CommandContext context("", -1); + int fd = open(CreateTempFile(0).string().c_str(), O_RDONLY); + CommandContext context1(fd); + EXPECT_FALSE(context1.ReadCommand()); + close(fd); - EXPECT_FALSE(context.IsValid()); + // Invalid magic with wrong length + vector data = { byte{'1'}, byte{'2'}, byte{'3'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context2(fd); + EXPECT_THROW(context2.ReadCommand(), io_exception); + close(fd); - context.SetFd(1); - EXPECT_TRUE(context.IsValid()); + // Invalid magic with right length + data = { byte{'1'}, byte{'2'}, byte{'3'}, byte{'4'}, byte{'5'}, byte{'6'} }; + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context3(fd); + EXPECT_THROW(context3.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'}, byte{'1'} }; + // Valid magic but invalid command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context4(fd); + EXPECT_THROW(context4.ReadCommand(), io_exception); + close(fd); + + data = { byte{'R'}, byte{'A'}, byte{'S'}, byte{'C'}, byte{'S'}, byte{'I'} }; + // Valid magic but missing command + fd = open(CreateTempFileWithData(data).string().c_str(), O_RDONLY); + CommandContext context5(fd); + EXPECT_THROW(context5.ReadCommand(), io_exception); + close(fd); + + const string filename = CreateTempFileWithData(data).string(); + fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + SerializeMessage(fd, command); + close(fd); + fd = open(filename.c_str(), O_RDONLY); + CommandContext context6(fd); + EXPECT_TRUE(context6.ReadCommand()); + close(fd); + EXPECT_EQ(PbOperation::SERVER_INFO, context6.GetCommand().operation()); } -TEST(CommandContext, Cleanup) +TEST(CommandContext, GetCommand) { - CommandContext context("", 0); + PbCommand command; + command.set_operation(PbOperation::SERVER_INFO); + CommandContext context(command, "", ""); + EXPECT_EQ(PbOperation::SERVER_INFO, context.GetCommand().operation()); +} - EXPECT_EQ(0, context.GetFd()); - context.Cleanup(); - EXPECT_EQ(-1, context.GetFd()); +TEST(CommandContext, WriteResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + result.set_error_code(PbErrorCode::UNAUTHORIZED); + CommandContext context(fd); + context.WriteResult(result); + close(fd); + EXPECT_FALSE(result.status()); + + fd = open(filename.c_str(), O_RDONLY); + result.set_status(true); + DeserializeMessage(fd, result); + close(fd); + EXPECT_FALSE(result.status()); + EXPECT_EQ(PbErrorCode::UNAUTHORIZED, result.error_code()); +} + +TEST(CommandContext, WriteSuccessResult) +{ + const string filename = CreateTempFile(0); + int fd = open(filename.c_str(), O_RDWR | O_APPEND); + PbResult result; + result.set_status(false); + CommandContext context(fd); + context.WriteSuccessResult(result); + close(fd); + EXPECT_TRUE(result.status()); } TEST(CommandContext, ReturnLocalizedError) { - CommandContext context("en_US", -1); + PbCommand command; + CommandContext context(command, "", "en_US"); EXPECT_FALSE(context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL)); } -TEST(CommandContext, ReturnStatus) +TEST(CommandContext, ReturnSuccessStatus) { - CommandContext context("", -1); + PbCommand command; - EXPECT_TRUE(context.ReturnStatus(true, "status")); - EXPECT_FALSE(context.ReturnStatus(false, "status")); + CommandContext context1(command, "", ""); + EXPECT_TRUE(context1.ReturnSuccessStatus()); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_TRUE(context2.ReturnSuccessStatus()); + close(fd); +} + +TEST(CommandContext, ReturnErrorStatus) +{ + PbCommand command; + + CommandContext context1(command, "", ""); + EXPECT_FALSE(context1.ReturnErrorStatus("error")); + + const int fd = open("/dev/null", O_RDWR); + CommandContext context2(fd); + EXPECT_FALSE(context2.ReturnErrorStatus("error")); + close(fd); } diff --git a/cpp/test/controller_manager_test.cpp b/cpp/test/controller_manager_test.cpp index 9ae770a4..22a329e8 100644 --- a/cpp/test/controller_manager_test.cpp +++ b/cpp/test/controller_manager_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,54 +13,75 @@ TEST(ControllerManagerTest, LifeCycle) { - const int ID = 4; + const int ID1 = 4; + const int ID2 = 5; const int LUN1 = 0; const int LUN2 = 3; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device = device_factory.CreateDevice(SCHS, -1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID1, device)); device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - auto controller = controller_manager->FindController(ID); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + auto controller = controller_manager.FindController(ID1); EXPECT_NE(nullptr, controller); EXPECT_EQ(1, controller->GetLunCount()); - EXPECT_NE(nullptr, controller_manager->IdentifyController(1 << ID)); - EXPECT_EQ(nullptr, controller_manager->IdentifyController(0)); - EXPECT_EQ(nullptr, controller_manager->FindController(0)); - EXPECT_NE(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(0, 0)); + EXPECT_FALSE(controller_manager.HasController(0)); + EXPECT_EQ(nullptr, controller_manager.FindController(0)); + EXPECT_TRUE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_NE(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(0, 0)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(0, 0)); device = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - controller = controller_manager->FindController(ID); - EXPECT_TRUE(controller_manager->DeleteController(controller)); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID1, device)); + EXPECT_TRUE(controller_manager.HasController(ID1)); + controller = controller_manager.FindController(ID1); + EXPECT_NE(nullptr, controller_manager.FindController(ID1)); + EXPECT_TRUE(controller_manager.DeleteController(*controller)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); - controller_manager->DeleteAllControllers(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_EQ(nullptr, controller_manager->GetDeviceByIdAndLun(ID, LUN1)); + auto disk = make_shared(); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID2, disk)); + EXPECT_CALL(*disk, FlushCache); + controller_manager.DeleteAllControllers(); + EXPECT_FALSE(controller_manager.HasController(ID1)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID1)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID1, LUN1)); + EXPECT_FALSE(controller_manager.HasController(ID2)); + EXPECT_EQ(nullptr, controller_manager.FindController(ID2)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceForIdAndLun(ID2, LUN1)); + EXPECT_FALSE(controller_manager.HasDeviceForIdAndLun(ID2, LUN1)); } -TEST(ControllerManagerTest, AttachToScsiController) +TEST(ControllerManagerTest, AttachToController) { const int ID = 4; const int LUN1 = 3; const int LUN2 = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)) << "LUN 0 is missing"; + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)) << "LUN 0 is missing"; auto device2 = device_factory.CreateDevice(SCLP, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); - EXPECT_FALSE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); + EXPECT_FALSE(controller_manager.AttachToController(*bus, ID, device1)); +} + +TEST(ControllerManager, ProcessOnController) +{ + ControllerManager controller_manager; + + EXPECT_EQ(AbstractController::piscsi_shutdown_mode::NONE, controller_manager.ProcessOnController(0)); } diff --git a/cpp/test/ctapdriver_test.cpp b/cpp/test/ctapdriver_test.cpp index 031e1acc..b587d471 100644 --- a/cpp/test/ctapdriver_test.cpp +++ b/cpp/test/ctapdriver_test.cpp @@ -3,11 +3,12 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include #include "devices/ctapdriver.h" TEST(CTapDriverTest, Crc32) @@ -15,27 +16,27 @@ TEST(CTapDriverTest, Crc32) array buf; buf.fill(0x00); - EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0xff); - EXPECT_EQ(0x814765f4, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x814765f4, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x10); - EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x7f); - EXPECT_EQ(0x4b543477, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x4b543477, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); buf.fill(0x80); - EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = 0; i < buf.size(); i++) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); for (size_t i = buf.size() - 1; i > 0; i--) { buf[i] = (uint8_t)i; } - EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN)); + EXPECT_EQ(0xe7870705, CTapDriver::Crc32(span(buf.data(), ETH_FRAME_LEN))); } diff --git a/cpp/test/device_factory_test.cpp b/cpp/test/device_factory_test.cpp index def637b6..b37c9bc3 100644 --- a/cpp/test/device_factory_test.cpp +++ b/cpp/test/device_factory_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -42,44 +42,43 @@ TEST(DeviceFactoryTest, GetTypeForFile) TEST(DeviceFactoryTest, GetSectorSizes) { DeviceFactory device_factory; - unordered_set sector_sizes; - sector_sizes = device_factory.GetSectorSizes(SCHD); + unordered_set sector_sizes = device_factory.GetSectorSizes(SCHD); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCRM); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCMO); EXPECT_EQ(4, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(1024)); + EXPECT_TRUE(sector_sizes.contains(2048)); + EXPECT_TRUE(sector_sizes.contains(4096)); sector_sizes = device_factory.GetSectorSizes(SCCD); EXPECT_EQ(2, sector_sizes.size()); - EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end()); - EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); + EXPECT_TRUE(sector_sizes.contains(512)); + EXPECT_TRUE(sector_sizes.contains(2048)); } TEST(DeviceFactoryTest, GetExtensionMapping) { DeviceFactory device_factory; - unordered_map mapping = device_factory.GetExtensionMapping(); + auto mapping = device_factory.GetExtensionMapping(); EXPECT_EQ(10, mapping.size()); EXPECT_EQ(SCHD, mapping["hd1"]); EXPECT_EQ(SCHD, mapping["hds"]); @@ -97,7 +96,7 @@ TEST(DeviceFactoryTest, GetDefaultParams) { DeviceFactory device_factory; - unordered_map params = device_factory.GetDefaultParams(SCHD); + param_map params = device_factory.GetDefaultParams(SCHD); EXPECT_TRUE(params.empty()); params = device_factory.GetDefaultParams(SCRM); diff --git a/cpp/test/device_test.cpp b/cpp/test/device_test.cpp index 740adfbf..0662b67e 100644 --- a/cpp/test/device_test.cpp +++ b/cpp/test/device_test.cpp @@ -3,12 +3,11 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" -#include "shared/piscsi_exceptions.h" #include "devices/device.h" TEST(DeviceTest, Properties) @@ -118,28 +117,36 @@ TEST(DeviceTest, Properties) TEST(DeviceTest, GetTypeString) { MockDevice schd(SCHD); - EXPECT_STREQ("SCHD", schd.GetTypeString()); + EXPECT_EQ("SCHD", schd.GetTypeString()); MockDevice scrm(SCRM); - EXPECT_STREQ("SCRM", scrm.GetTypeString()); + EXPECT_EQ("SCRM", scrm.GetTypeString()); MockDevice scmo(SCMO); - EXPECT_STREQ("SCMO", scmo.GetTypeString()); + EXPECT_EQ("SCMO", scmo.GetTypeString()); MockDevice sccd(SCCD); - EXPECT_STREQ("SCCD", sccd.GetTypeString()); + EXPECT_EQ("SCCD", sccd.GetTypeString()); MockDevice schs(SCHS); - EXPECT_STREQ("SCHS", schs.GetTypeString()); + EXPECT_EQ("SCHS", schs.GetTypeString()); MockDevice scbr(SCBR); - EXPECT_STREQ("SCBR", scbr.GetTypeString()); + EXPECT_EQ("SCBR", scbr.GetTypeString()); MockDevice scdp(SCDP); - EXPECT_STREQ("SCDP", scdp.GetTypeString()); + EXPECT_EQ("SCDP", scdp.GetTypeString()); MockDevice sclp(SCLP); - EXPECT_STREQ("SCLP", sclp.GetTypeString()); + EXPECT_EQ("SCLP", sclp.GetTypeString()); +} + +TEST(DeviceTest, GetIdentifier) +{ + MockDevice device(1); + + EXPECT_CALL(device, GetId()); + EXPECT_EQ("UNDEFINED 0:1", device.GetIdentifier()); } TEST(DeviceTest, Vendor) @@ -188,7 +195,7 @@ TEST(DeviceTest, GetPaddedName) TEST(DeviceTest, Params) { MockDevice device(0); - unordered_map params; + param_map params; params["key"] = "value"; EXPECT_EQ("", device.GetParam("key")); @@ -196,7 +203,7 @@ TEST(DeviceTest, Params) device.SetParams(params); EXPECT_EQ("", device.GetParam("key")); - unordered_map default_params; + param_map default_params; default_params["key"] = "value"; device.SetDefaultParams(default_params); EXPECT_EQ("", device.GetParam("key")); diff --git a/cpp/test/disk_test.cpp b/cpp/test/disk_test.cpp index 82c5a3c5..b0206403 100644 --- a/cpp/test/disk_test.cpp +++ b/cpp/test/disk_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -16,23 +16,26 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreateDisk() +{ + auto controller = make_shared>(0); + auto disk = make_shared(); + EXPECT_TRUE(disk->Init({})); + EXPECT_TRUE(controller->AddDevice(disk)); + + return { controller, disk }; +} + TEST(DiskTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); disk->SetRemovable(true); disk->SetMediumChanged(false); disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetMediumChanged(true); EXPECT_CALL(*controller, Error); @@ -42,167 +45,132 @@ TEST(DiskTest, Dispatch) TEST(DiskTest, Rezero) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRezero); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REZERO must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRezero); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, FormatUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "FORMAT UNIT must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdFormatUnit); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(1, 0x10); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdFormatUnit); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(DiskTest, ReassignBlocks) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReassignBlocks); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REASSIGN must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReassignBlocks); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[4] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(6) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, Seek10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "SEEK(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); // Block count - cmd[5] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(5, 1); + + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSeek10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "SEEK(10) must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSeek10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadCapacity10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(10) must fail because the medium has no capacity"; disk->SetBlockCount(0x12345678); @@ -222,34 +190,28 @@ TEST(DiskTest, ReadCapacity10) TEST(DiskTest, ReadCapacity16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); + controller->SetCmdByte(1, 0x00); - auto& cmd = controller->GetCmd(); - - cmd[1] = 0x00; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Neither READ CAPACITY(16) nor READ LONG(16)"; // READ CAPACITY(16), not READ LONG(16) - cmd[1] = 0x10; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because drive is not ready"; disk->SetReady(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "READ CAPACITY(16) must fail because the medium has no capacity"; disk->SetBlockCount(0x1234567887654321); @@ -267,18 +229,13 @@ TEST(DiskTest, ReadCapacity16) TEST(DiskTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(6) must fail for a medium with 0 blocks"; // Further testing requires filesystem access @@ -286,389 +243,328 @@ TEST(DiskTest, Read6) TEST(DiskTest, Read10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Read16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdRead16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(6) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); disk->SetReady(true); disk->SetProtectable(true); disk->SetProtected(true); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::DATA_PROTECT), - Property(&scsi_exception::get_asc, asc::WRITE_PROTECTED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::data_protect), + Property(&scsi_exception::get_asc, asc::write_protected)))); // Further testing requires filesystem access } TEST(DiskTest, Write10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Write16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE(16) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWrite16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(10) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(8, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify10); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, Verify16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdVerify16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "VERIFY(16) must fail for a medium with 0 blocks"; + disk->SetReady(true); + // Verify 0 sectors disk->SetBlockCount(1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdVerify16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); + + // Verify 1 sector with BytChk=0 + controller->SetCmdByte(13, 1); + EXPECT_CALL(*controller, Status); + disk->Dispatch(scsi_command::eCmdVerify16); + EXPECT_EQ(status::good, controller->GetStatus()); // Further testing requires filesystem access } TEST(DiskTest, ReadLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, ReadLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; // READ LONG(16), not READ CAPACITY(16) - cmd[1] = 0x11; - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(1, 0x11); + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "READ LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(10) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); - cmd[7] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(7, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, WriteLong16) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - cmd[2] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LBA_OUT_OF_RANGE)))) + controller->SetCmdByte(2, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::lba_out_of_range)))) << "WRITE LONG(16) must fail because the capacity is exceeded"; - cmd[2] = 0; + controller->SetCmdByte(2, 0); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdWriteLong16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[13] = 1; - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(13, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWriteLong16); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; disk->SetRemovable(true); - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - // Stop/Unload disk->SetReady(true); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsStopped()); // Stop/Load - cmd[4] = 0x02; + controller->SetCmdByte(4, 0x02); disk->SetReady(true); disk->SetLocked(false); EXPECT_CALL(*controller, Status); EXPECT_CALL(*disk, FlushCache); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); disk->SetReady(false); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); disk->SetReady(true); disk->SetLocked(true); EXPECT_CALL(*disk, FlushCache).Times(0); - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::LOAD_OR_EJECT_FAILED)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::load_or_eject_failed)))); // Start/Unload - cmd[4] = 0x01; + controller->SetCmdByte(4, 0x01); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsStopped()); // Start/Load - cmd[4] = 0x03; + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, PreventAllowMediumRemoval) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); + auto [controller, disk] = CreateDisk(); + // Required by the bullseye clang++ compiler + auto d = disk; - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))) << "REMOVAL must fail because drive is not ready"; disk->SetReady(true); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_FALSE(disk->IsLocked()); - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdPreventAllowMediumRemoval); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_TRUE(disk->IsLocked()); } @@ -701,9 +597,9 @@ TEST(DiskTest, Eject) EXPECT_TRUE(disk.Eject(true)); } -void DiskTest_ValidateFormatPage(shared_ptr controller, int offset) +void DiskTest_ValidateFormatPage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x08, buf[offset + 3]) << "Wrong number of trackes in one zone"; EXPECT_EQ(25, GetInt16(buf, offset + 10)) << "Wrong number of sectors per track"; EXPECT_EQ(1024, GetInt16(buf, offset + 12)) << "Wrong number of bytes per sector"; @@ -714,18 +610,18 @@ void DiskTest_ValidateFormatPage(shared_ptr controller, int EXPECT_TRUE(buf[offset + 20] & 0x40) << "Wrong hard-sectored flag"; } -void DiskTest_ValidateDrivePage(shared_ptr controller, int offset) +void DiskTest_ValidateDrivePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0x17, buf[offset + 2]); EXPECT_EQ(0x4d3b, GetInt16(buf, offset + 3)); EXPECT_EQ(8, buf[offset + 5]) << "Wrong number of heads"; EXPECT_EQ(7200, GetInt16(buf, offset + 20)) << "Wrong medium rotation rate"; } -void DiskTest_ValidateCachePage(shared_ptr controller, int offset) +void DiskTest_ValidateCachePage(AbstractController& controller, int offset) { - const auto& buf = controller->GetBuffer(); + const auto& buf = controller.GetBuffer(); EXPECT_EQ(0xffff, GetInt16(buf, offset + 4)) << "Wrong pre-fetch transfer length"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 8)) << "Wrong maximum pre-fetch"; EXPECT_EQ(0xffff, GetInt16(buf, offset + 10)) << "Wrong maximum pre-fetch ceiling"; @@ -733,28 +629,19 @@ void DiskTest_ValidateCachePage(shared_ptr controller, int o TEST(DiskTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x08, controller->GetBuffer()[3]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense6); EXPECT_EQ(0x00, controller->GetBuffer()[2]) << "Wrong device-specific parameter"; @@ -766,50 +653,41 @@ TEST(DiskTest, ModeSense6) EXPECT_EQ(0x80, buf[2]) << "Wrong device-specific parameter"; // Return block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateFormatPage(controller, 12); + DiskTest_ValidateFormatPage(*controller, 12); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateDrivePage(controller, 12); + DiskTest_ValidateDrivePage(*controller, 12); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense6); - DiskTest_ValidateCachePage(controller, 12); + DiskTest_ValidateCachePage(*controller, 12); } TEST(DiskTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); - - auto& cmd = controller->GetCmd(); + auto [controller, disk] = CreateDisk(); // Drive must be ready on order to return all data disk->SetReady(true); - cmd[2] = 0x3f; + controller->SetCmdByte(2, 0x3f); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); disk->Dispatch(scsi_command::eCmdModeSense10); EXPECT_EQ(0x08, controller->GetBuffer()[7]) << "Wrong block descriptor length"; // No block descriptor - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); disk->Dispatch(scsi_command::eCmdModeSense10); auto& buf = controller->GetBuffer(); EXPECT_EQ(0x00, controller->GetBuffer()[3]) << "Wrong device-specific parameter"; @@ -822,7 +700,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x80, buf[3]) << "Wrong device-specific parameter"; // Return short block descriptor - cmd[1] = 0x00; + controller->SetCmdByte(1, 0x00); disk->SetBlockCount(0x1234); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); @@ -835,7 +713,7 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(1024, GetInt16(buf, 14)); // Return long block descriptor - cmd[1] = 0x10; + controller->SetCmdByte(1, 0x10); disk->SetBlockCount((uint64_t)0xffffffff + 1); disk->Dispatch(scsi_command::eCmdModeSense10); buf = controller->GetBuffer(); @@ -847,62 +725,49 @@ TEST(DiskTest, ModeSense10) EXPECT_EQ(0x00, GetInt16(buf, 14)); EXPECT_EQ(0x00, GetInt16(buf, 20)); EXPECT_EQ(1024, GetInt16(buf, 22)); - cmd[1] = 0x00; + + controller->SetCmdByte(1, 0x00); // Format page - cmd[2] = 3; + controller->SetCmdByte(2, 3); disk->SetSectorSizeInBytes(1024); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateFormatPage(controller, 16); + DiskTest_ValidateFormatPage(*controller, 16); // Rigid disk drive page - cmd[2] = 4; + controller->SetCmdByte(2, 4); disk->SetBlockCount(0x12345678); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateDrivePage(controller, 16); + DiskTest_ValidateDrivePage(*controller, 16); // Cache page - cmd[2] = 8; + controller->SetCmdByte(2, 8); disk->Dispatch(scsi_command::eCmdModeSense10); - DiskTest_ValidateCachePage(controller, 16); + DiskTest_ValidateCachePage(*controller, 16); } TEST(DiskTest, SynchronizeCache) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); EXPECT_CALL(*disk, FlushCache); EXPECT_CALL(*controller, Status); disk->Dispatch(scsi_command::eCmdSynchronizeCache16); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, ReadDefectData) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto disk = make_shared(); - const unordered_map params; - disk->Init(params); - - controller->AddDevice(disk); + auto [controller, disk] = CreateDisk(); EXPECT_CALL(*controller, DataIn); disk->Dispatch(scsi_command::eCmdReadDefectData10); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(DiskTest, SectorSize) @@ -951,8 +816,7 @@ TEST(DiskTest, SectorSize) TEST(DiskTest, ConfiguredSectorSize) { DeviceFactory device_factory; - const unordered_set sector_sizes; - MockSCSIHD disk(0, sector_sizes, false); + MockSCSIHD disk(0, {}, false); EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); diff --git a/cpp/test/gpiobus_raspberry_test.cpp b/cpp/test/gpiobus_raspberry_test.cpp index a96e4b7c..6b597013 100644 --- a/cpp/test/gpiobus_raspberry_test.cpp +++ b/cpp/test/gpiobus_raspberry_test.cpp @@ -9,7 +9,7 @@ #include "hal/gpiobus_raspberry.h" #include "mocks.h" -#include "stdlib.h" +#include #include "test/test_shared.h" class SetableGpiobusRaspberry : public GPIOBUS_Raspberry @@ -23,9 +23,9 @@ class SetableGpiobusRaspberry : public GPIOBUS_Raspberry { // Level is inverted logic if (!value) { - *level |= (1 << pin); + *level = *level | (1 << pin); } else { - *level &= ~(1 << pin); + *level = *level & ~(1 << pin); } } SetableGpiobusRaspberry() @@ -65,7 +65,7 @@ TEST(GpiobusRaspberry, GetDtRanges) EXPECT_EQ(0x20000000, GPIOBUS_Raspberry::bcm_host_get_peripheral_address()); DeleteTempFile("/proc/device-tree/soc/ranges"); - CleanupAllTempFiles(); + CleanUpAllTempFiles(); } TEST(GpiobusRaspberry, GetDat) diff --git a/cpp/test/host_services_test.cpp b/cpp/test/host_services_test.cpp index bb069a9c..5bcf7ab4 100644 --- a/cpp/test/host_services_test.cpp +++ b/cpp/test/host_services_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/host_services.h" using namespace std; @@ -22,82 +21,70 @@ void HostServices_SetUpModePages(map>& pages) TEST(HostServicesTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); + auto [controller, services] = CreateDevice(SCHS); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(HostServicesTest, Inquiry) { - TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, "PiSCSI Host Services ", 0x1f, false); + TestInquiry::Inquiry(SCHS, device_type::processor, scsi_level::spc_3, "PiSCSI Host Services ", 0x1f, false); } TEST(HostServicesTest, StartStopUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; // STOP - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI)); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // LOAD - cmd[4] = 0x02; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PI)); + controller->SetCmdByte(4, 0x02); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // UNLOAD - cmd[4] = 0x03; - EXPECT_CALL(*controller, ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI)); + controller->SetCmdByte(4, 0x03); EXPECT_CALL(*controller, Status()); services->Dispatch(scsi_command::eCmdStartStop); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); // START - cmd[4] = 0x01; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))); + controller->SetCmdByte(4, 0x01); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdStartStop); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))); } TEST(HostServicesTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; - auto& cmd = controller->GetCmd(); + EXPECT_TRUE(services->Init({})); - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); vector& buffer = controller->GetBuffer(); @@ -111,7 +98,7 @@ TEST(HostServicesTest, ModeSense6) EXPECT_NE(0x00, buffer[10]); // ALLOCATION LENGTH - cmd[4] = 2; + controller->SetCmdByte(4, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense6); buffer = controller->GetBuffer(); @@ -120,29 +107,26 @@ TEST(HostServicesTest, ModeSense6) TEST(HostServicesTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto services = CreateDevice(SCHS, *controller); - const unordered_map params; - services->Init(params); + auto [controller, services] = CreateDevice(SCHS); + // Required by the bullseye clang++ compiler + auto s = services; + + EXPECT_TRUE(services->Init({})); - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Unsupported mode page was returned"; - cmd[2] = 0x20; - EXPECT_THAT([&] { services->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(2, 0x20); + EXPECT_THAT([&] { s->Dispatch(scsi_command::eCmdModeSense10); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Block descriptors are not supported"; - cmd[1] = 0x08; + controller->SetCmdByte(1, 0x08); // ALLOCATION LENGTH - cmd[8] = 255; + controller->SetCmdByte(8, 255); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); vector& buffer = controller->GetBuffer(); @@ -156,7 +140,7 @@ TEST(HostServicesTest, ModeSense10) EXPECT_NE(0x00, buffer[14]); // ALLOCATION LENGTH - cmd[8] = 2; + controller->SetCmdByte(8, 2); EXPECT_CALL(*controller, DataIn()); services->Dispatch(scsi_command::eCmdModeSense10); buffer = controller->GetBuffer(); diff --git a/cpp/test/linux_os_stubs.cpp b/cpp/test/linux_os_stubs.cpp index f5885e27..98cdbdb7 100644 --- a/cpp/test/linux_os_stubs.cpp +++ b/cpp/test/linux_os_stubs.cpp @@ -13,11 +13,13 @@ #include #include -#include -#include -#include +#include +#include +#include #include +#ifdef __linux__ #include +#endif #include #include #include @@ -61,4 +63,4 @@ FILE *__wrap_fopen(const char *__restrict __filename, const char *__restrict __m #endif } -} // end extern "C" \ No newline at end of file +} // end extern "C" diff --git a/cpp/test/mocks.h b/cpp/test/mocks.h index f654054d..9b315392 100644 --- a/cpp/test/mocks.h +++ b/cpp/test/mocks.h @@ -81,10 +81,11 @@ public: class MockPhaseHandler : public PhaseHandler { FRIEND_TEST(PhaseHandlerTest, Phases); + FRIEND_TEST(PhaseHandlerTest, ProcessPhase); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(void, Status, (), ()); MOCK_METHOD(void, DataIn, (), ()); MOCK_METHOD(void, DataOut, (), ()); @@ -97,15 +98,16 @@ public: using PhaseHandler::PhaseHandler; }; +inline static const auto mock_bus = make_shared(); + class MockAbstractController : public AbstractController //NOSONAR Having many fields/methods cannot be avoided { + friend class TestInquiry; + friend shared_ptr CreateDevice(piscsi_interface::PbDeviceType, AbstractController&, int); - friend void TestInquiry(piscsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, - scsi_defs::scsi_level, const std::string&, int, bool); FRIEND_TEST(AbstractControllerTest, AllocateCmd); FRIEND_TEST(AbstractControllerTest, Reset); - FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, GetOpcode); @@ -148,10 +150,26 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); + FRIEND_TEST(DiskTest, StartStopUnit); + FRIEND_TEST(DiskTest, ModeSense6); + FRIEND_TEST(DiskTest, ModeSense10); + FRIEND_TEST(ScsiDaynaportTest, Read); + FRIEND_TEST(ScsiDaynaportTest, Write); + FRIEND_TEST(ScsiDaynaportTest, Read6); + FRIEND_TEST(ScsiDaynaportTest, Write6); + FRIEND_TEST(ScsiDaynaportTest, TestRetrieveStats); + FRIEND_TEST(ScsiDaynaportTest, SetInterfaceMode); + FRIEND_TEST(ScsiDaynaportTest, SetMcastAddr); + FRIEND_TEST(ScsiDaynaportTest, EnableInterface); + FRIEND_TEST(HostServicesTest, StartStopUnit); + FRIEND_TEST(HostServicesTest, ModeSense6); + FRIEND_TEST(HostServicesTest, ModeSense10); + FRIEND_TEST(HostServicesTest, SetUpModePages); + FRIEND_TEST(ScsiPrinterTest, Print); public: - MOCK_METHOD(phase_t, Process, (int), (override)); + MOCK_METHOD(bool, Process, (int), (override)); MOCK_METHOD(int, GetEffectiveLun, (), (const override)); MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override)); MOCK_METHOD(int, GetInitiatorId, (), (const override)); @@ -163,10 +181,12 @@ public: MOCK_METHOD(void, Command, (), ()); MOCK_METHOD(void, MsgIn, (), ()); MOCK_METHOD(void, MsgOut, (), ()); - MOCK_METHOD(void, ScheduleShutdown, (piscsi_shutdown_mode), (override)); - explicit MockAbstractController(shared_ptr controller_manager, int target_id) - : AbstractController(controller_manager, target_id, 32) { + MockAbstractController() : AbstractController(*mock_bus, 0, 32) {} + explicit MockAbstractController(int target_id) : AbstractController(*mock_bus, target_id, 32) { + AllocateBuffer(512); + } + MockAbstractController(shared_ptr bus, int target_id) : AbstractController(*bus, target_id, 32) { AllocateBuffer(512); } ~MockAbstractController() override = default; @@ -193,10 +213,8 @@ public: MOCK_METHOD(void, Execute, (), ()); using ScsiController::ScsiController; - MockScsiController(shared_ptr controller_manager, int target_id) - : ScsiController(controller_manager, target_id) {} - explicit MockScsiController(shared_ptr controller_manager) - : ScsiController(controller_manager, 0) {} + MockScsiController(shared_ptr bus, int target_id) : ScsiController(*bus, target_id) {} + explicit MockScsiController(shared_ptr bus) : ScsiController(*bus, 0) {} ~MockScsiController() override = default; }; @@ -233,6 +251,7 @@ class MockPrimaryDevice : public PrimaryDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); + MOCK_METHOD(void, FlushCache, (), ()); explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {} ~MockPrimaryDevice() override = default; @@ -247,8 +266,8 @@ class MockModePageDevice : public ModePageDevice public: MOCK_METHOD(vector, InquiryInternal, (), (const)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {} ~MockModePageDevice() override = default; @@ -290,8 +309,8 @@ public: MOCK_METHOD(vector, InquiryInternal, (), (const)); MOCK_METHOD(void, Open, (), (override)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense6, (span, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (span, vector&), (const override)); MOCK_METHOD(void, SetUpModePages, ((map>&), int, bool), (const override)); MockStorageDevice() : StorageDevice(UNDEFINED, 0) {} @@ -391,16 +410,6 @@ class MockHostServices : public HostServices using HostServices::HostServices; }; -class MockCommandContext : public CommandContext -{ -public: - - MockCommandContext() { - SetFd(open("/dev/null", O_WRONLY)); - } - ~MockCommandContext() = default; -}; - class MockPiscsiExecutor : public PiscsiExecutor { public: diff --git a/cpp/test/mode_page_device_test.cpp b/cpp/test/mode_page_device_test.cpp index 31d0a2a9..8515ec55 100644 --- a/cpp/test/mode_page_device_test.cpp +++ b/cpp/test/mode_page_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -33,8 +33,8 @@ TEST(ModePageDeviceTest, AddModePages) // Page 0 cdb[2] = 0x00; EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, 255); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Data were returned for non-existing mode page 0"; // All pages, non changeable @@ -42,8 +42,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; // All pages, changeable @@ -51,8 +51,8 @@ TEST(ModePageDeviceTest, AddModePages) EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); EXPECT_THAT([&] { device.AddModePages(cdb, buf, 0, 12, -1); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Maximum size was ignored"; } @@ -80,12 +80,9 @@ TEST(ModePageDeviceTest, AddVendorPage) TEST(ModePageDeviceTest, ModeSense6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -95,12 +92,9 @@ TEST(ModePageDeviceTest, ModeSense6) TEST(ModePageDeviceTest, ModeSense10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared>(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); @@ -115,57 +109,47 @@ TEST(ModePageDeviceTest, ModeSelect) vector buf; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(6) default implementation"; EXPECT_THAT([&] { device.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 0); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))) << "Unexpected MODE SELECT(10) default implementation"; } TEST(ModePageDeviceTest, ModeSelect6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect6); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported by base class"; } TEST(ModePageDeviceTest, ModeSelect10) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device = make_shared(); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); - EXPECT_CALL(*controller, DataOut()); device->Dispatch(scsi_command::eCmdModeSelect10); - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdModeSelect10); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Saving parameters is not supported for by base class"; } diff --git a/cpp/test/network_util_test.cpp b/cpp/test/network_util_test.cpp new file mode 100644 index 00000000..d6f49d62 --- /dev/null +++ b/cpp/test/network_util_test.cpp @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator PiSCSI +// for Raspberry Pi +// +// Copyright (C) 2023 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include + +#include +#include +#include "shared/network_util.h" + +using namespace network_util; + +TEST(NetworkUtilTest, IsInterfaceUp) +{ + EXPECT_FALSE(IsInterfaceUp("foo_bar")); +} + +TEST(NetworkUtilTest, GetNetworkInterfaces) +{ + EXPECT_FALSE(GetNetworkInterfaces().empty()); +} + +TEST(NetworkUtilTest, ResolveHostName) +{ + sockaddr_in server_addr = {}; + EXPECT_FALSE(ResolveHostName("foo.foobar", &server_addr)); + EXPECT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); +} diff --git a/cpp/test/phase_handler_test.cpp b/cpp/test/phase_handler_test.cpp index 7af4f6b1..70532b54 100644 --- a/cpp/test/phase_handler_test.cpp +++ b/cpp/test/phase_handler_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -94,3 +94,50 @@ TEST(PhaseHandlerTest, Phases) EXPECT_FALSE(handler.IsDataOut()); EXPECT_FALSE(handler.IsMsgIn()); } + +TEST(PhaseHandlerTest, ProcessPhase) +{ + MockPhaseHandler handler; + + handler.SetPhase(phase_t::selection); + EXPECT_CALL(handler, Selection); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::busfree); + EXPECT_CALL(handler, BusFree); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::datain); + EXPECT_CALL(handler, DataIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::dataout); + EXPECT_CALL(handler, DataOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::command); + EXPECT_CALL(handler, Command); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::status); + EXPECT_CALL(handler, Status); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgin); + EXPECT_CALL(handler, MsgIn); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::msgout); + EXPECT_CALL(handler, MsgOut); + handler.ProcessPhase(); + + handler.SetPhase(phase_t::reselection); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); + + handler.SetPhase(phase_t::reserved); + EXPECT_THAT([&] { handler.ProcessPhase(); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); +} diff --git a/cpp/test/piscsi_exceptions_test.cpp b/cpp/test/piscsi_exceptions_test.cpp index 6def9716..a0b338e5 100644 --- a/cpp/test/piscsi_exceptions_test.cpp +++ b/cpp/test/piscsi_exceptions_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -36,18 +36,18 @@ TEST(PiscsiExceptionsTest, FileNotFoundException) TEST(PiscsiExceptionsTest, ScsiErrorException) { try { - throw scsi_exception(sense_key::UNIT_ATTENTION); + throw scsi_exception(sense_key::unit_attention); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); + EXPECT_EQ(sense_key::unit_attention, e.get_sense_key()); + EXPECT_EQ(asc::no_additional_sense_information, e.get_asc()); } try { - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE); + throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range); } catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); + EXPECT_EQ(sense_key::illegal_request, e.get_sense_key()); + EXPECT_EQ(asc::lba_out_of_range, e.get_asc()); } } diff --git a/cpp/test/piscsi_executor_test.cpp b/cpp/test/piscsi_executor_test.cpp index 9b038c49..a4a917af 100644 --- a/cpp/test/piscsi_executor_test.cpp +++ b/cpp/test/piscsi_executor_test.cpp @@ -3,11 +3,10 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- -#include "spdlog/spdlog.h" #include "mocks.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" @@ -24,224 +23,194 @@ using namespace filesystem; using namespace piscsi_interface; using namespace protobuf_util; -const extern bool enable_logging; - -// This test fixture is required in order to reset the log level changed by the log level tests -class PiscsiExecutorTest : public Test -{ - void TearDown() override { - spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); - } -}; - -TEST_F(PiscsiExecutorTest, ProcessDeviceCmd) +TEST(PiscsiExecutorTest, ProcessDeviceCmd) { const int ID = 3; const int LUN = 0; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, ID); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); + auto executor = make_shared(piscsi_image, *bus, controller_manager); PbDeviceDefinition definition; PbCommand command; - MockCommandContext context; + CommandContext context(command, "", ""); definition.set_id(8); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID and LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID and LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID must fail"; definition.set_id(ID); definition.set_unit(32); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Unknown operation must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Unknown operation must fail"; command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation for unknown device type must fail"; + CommandContext context_attach(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "Operation for unknown device type must fail"; auto device1 = make_shared(LUN); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); definition.set_type(SCHS); command.set_operation(INSERT); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation unsupported by device must fail"; - controller_manager->DeleteAllControllers(); + CommandContext context_insert1(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert1, definition, true)) << "Operation unsupported by device must fail"; + controller_manager.DeleteAllControllers(); definition.set_type(SCRM); auto device2 = make_shared(LUN); device2->SetRemovable(true); device2->SetProtectable(true); device2->SetReady(true); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - command.set_operation(ATTACH); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "ID and LUN already exist"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "ID and LUN already exist"; command.set_operation(START); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_start(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, false)); command.set_operation(PROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_protect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, false)); command.set_operation(UNPROTECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_unprotect(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, false)); command.set_operation(STOP); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_stop(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, false)); command.set_operation(EJECT); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + CommandContext context_eject(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, false)); command.set_operation(INSERT); SetParam(definition, "file", "filename"); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Non-existing file"; - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, false)) << "Non-existing file"; + CommandContext context_insert2(command, "", ""); + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, true)) << "Non-existing file"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, false)) << "Non-existing file"; command.set_operation(DETACH); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); - - command.set_operation(CHECK_AUTHENTICATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - command.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); - EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); - - // The operations below are not related to a device - - command.set_operation(DETACH_ALL); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RESERVE_IDS); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); - - command.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + CommandContext context_detach(command, "", ""); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, false)); } -TEST_F(PiscsiExecutorTest, ProcessCmd) +TEST(PiscsiExecutorTest, ProcessCmd) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); + ControllerManager controller_manager; + MockAbstractController controller(bus, 0); PiscsiImage piscsi_image; - auto executor = make_shared(piscsi_image, *controller_manager); - PbCommand command1; - PbCommand command2; - MockCommandContext context; + auto executor = make_shared(piscsi_image, *bus, controller_manager); - command1.set_operation(DETACH_ALL); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_detach_all; + command_detach_all.set_operation(DETACH_ALL); + CommandContext context_detach_all(command_detach_all, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_detach_all)); - command1.set_operation(RESERVE_IDS); - SetParam(command1, "ids", "2,3"); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_reserve_ids1; + command_reserve_ids1.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids1, "ids", "2,3"); + CommandContext context_reserve_ids1(command_reserve_ids1, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids1)); const unordered_set ids = executor->GetReservedIds(); EXPECT_EQ(2, ids.size()); - EXPECT_NE(ids.end(), ids.find(2)); - EXPECT_NE(ids.end(), ids.find(3)); - command2.set_operation(RESERVE_IDS); - EXPECT_TRUE(executor->ProcessCmd(context, command2)); + EXPECT_TRUE(ids.contains(2)); + EXPECT_TRUE(ids.contains(3)); + + PbCommand command_reserve_ids2; + command_reserve_ids2.set_operation(RESERVE_IDS); + CommandContext context_reserve_ids2(command_reserve_ids2, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids2)); EXPECT_TRUE(executor->GetReservedIds().empty()); - SetParam(command2, "ids", "-1"); - EXPECT_FALSE(executor->ProcessCmd(context, command2)); + PbCommand command_reserve_ids3; + command_reserve_ids3.set_operation(RESERVE_IDS); + SetParam(command_reserve_ids3, "ids", "-1"); + CommandContext context_reserve_ids3(command_reserve_ids3, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_reserve_ids3)); EXPECT_TRUE(executor->GetReservedIds().empty()); - command1.set_operation(NO_OPERATION); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_no_operation; + command_no_operation.set_operation(NO_OPERATION); + CommandContext context_no_operation(command_no_operation, "", ""); + EXPECT_TRUE(executor->ProcessCmd(context_no_operation)); - command1.set_operation(ATTACH); - auto device = command1.add_devices(); - device->set_type(SCHS); - device->set_id(-1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); - device->set_id(0); - device->set_unit(1); - EXPECT_FALSE(executor->ProcessCmd(context, command1)) << "LUN 0 is missing"; - device->set_unit(0); - EXPECT_TRUE(executor->ProcessCmd(context, command1)); + PbCommand command_attach1; + command_attach1.set_operation(ATTACH); + auto device1 = command_attach1.add_devices(); + device1->set_type(SCHS); + device1->set_id(-1); + CommandContext context_attach1(command_attach1, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach1)); + + PbCommand command_attach2; + command_attach2.set_operation(ATTACH); + auto device2 = command_attach2.add_devices(); + device2->set_type(SCHS); + device2->set_id(0); + device2->set_unit(1); + CommandContext context_attach2(command_attach2, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_attach2)) << "LUN 0 is missing"; // The operations below must fail because of missing parameters. // The respective functionality is tested in piscsi_image_test.cpp. - command1.set_operation(CREATE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + PbCommand command; - command1.set_operation(DELETE_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(CREATE_IMAGE); + CommandContext context_create_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_create_image)); - command1.set_operation(RENAME_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(DELETE_IMAGE); + CommandContext context_delete_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_delete_image)); - command1.set_operation(COPY_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(RENAME_IMAGE); + CommandContext context_rename_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_rename_image)); - command1.set_operation(PROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(COPY_IMAGE); + CommandContext context_copy_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_copy_image)); - command1.set_operation(UNPROTECT_IMAGE); - EXPECT_FALSE(executor->ProcessCmd(context, command1)); + command.set_operation(PROTECT_IMAGE); + CommandContext context_protect_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_protect_image)); + + command.set_operation(UNPROTECT_IMAGE); + CommandContext context_unprotect_image(command, "", ""); + EXPECT_FALSE(executor->ProcessCmd(context_unprotect_image)); } -TEST_F(PiscsiExecutorTest, SetLogLevel) -{ - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - MockAbstractController controller(controller_manager, 0); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - - EXPECT_TRUE(executor.SetLogLevel("trace")); - EXPECT_TRUE(executor.SetLogLevel("debug")); - EXPECT_TRUE(executor.SetLogLevel("info")); - EXPECT_TRUE(executor.SetLogLevel("warn")); - EXPECT_TRUE(executor.SetLogLevel("err")); - EXPECT_TRUE(executor.SetLogLevel("off")); - EXPECT_FALSE(executor.SetLogLevel("xyz")); -} - -TEST_F(PiscsiExecutorTest, Attach) +TEST(PiscsiExecutorTest, Attach) { const int ID = 3; const int LUN = 0; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); PbDeviceDefinition definition; - MockCommandContext context; + PbCommand command; + CommandContext context(command, "", ""); definition.set_unit(32); EXPECT_FALSE(executor.Attach(context, definition, false)); @@ -258,7 +227,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCHS); EXPECT_TRUE(executor.Attach(context, definition, false)); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); definition.set_type(PbDeviceType::SCHD); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; @@ -277,19 +246,19 @@ TEST_F(PiscsiExecutorTest, Attach) EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected"; path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); bool result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); filename = CreateTempFile(513); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -297,7 +266,7 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCCD); definition.set_unit(LUN + 1); filename = CreateTempFile(2048); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); @@ -306,25 +275,26 @@ TEST_F(PiscsiExecutorTest, Attach) definition.set_unit(LUN + 2); SetParam(definition, "read_only", "true"); filename = CreateTempFile(4096); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); - controller_manager->DeleteAllControllers(); + controller_manager.DeleteAllControllers(); } -TEST_F(PiscsiExecutorTest, Insert) +TEST(PiscsiExecutorTest, Insert) { DeviceFactory device_factory; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - PbDeviceDefinition definition; - MockCommandContext context; - auto device = device_factory.CreateDevice(SCRM, 0, "test"); + auto bus = make_shared(); + ControllerManager controller_manager; + auto [controller, device] = CreateDevice(SCHD); + PiscsiImage piscsi_image; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbDeviceDefinition definition; + PbCommand command; + CommandContext context(command, "", ""); device->SetRemoved(false); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Medium is not removed"; @@ -358,18 +328,18 @@ TEST_F(PiscsiExecutorTest, Insert) EXPECT_FALSE(executor.Insert(context, definition, device, false)); path filename = CreateTempFile(1); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); - SetParam(definition, "file", filename.c_str()); + SetParam(definition, "file", filename.string()); const bool result = executor.Insert(context, definition, device, false); remove(filename); EXPECT_TRUE(result); } -TEST_F(PiscsiExecutorTest, Detach) +TEST(PiscsiExecutorTest, Detach) { const int ID = 3; const int LUN1 = 0; @@ -377,68 +347,72 @@ TEST_F(PiscsiExecutorTest, Detach) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); auto device2 = device_factory.CreateDevice(SCHS, LUN2, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - auto d1 = controller_manager->GetDeviceByIdAndLun(ID, LUN1); - EXPECT_FALSE(executor.Detach(context, d1, false)) << "LUNs > 0 have to be detached first"; - auto d2 = controller_manager->GetDeviceByIdAndLun(ID, LUN2); - EXPECT_TRUE(executor.Detach(context, d2, false)); - EXPECT_TRUE(executor.Detach(context, d1, false)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + auto d1 = controller_manager.GetDeviceForIdAndLun(ID, LUN1); + EXPECT_FALSE(executor.Detach(context, *d1, false)) << "LUNs > 0 have to be detached first"; + auto d2 = controller_manager.GetDeviceForIdAndLun(ID, LUN2); + EXPECT_TRUE(executor.Detach(context, *d2, false)); + EXPECT_TRUE(executor.Detach(context, *d1, false)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); - EXPECT_FALSE(executor.Detach(context, d1, false)); + EXPECT_FALSE(executor.Detach(context, *d1, false)); } -TEST_F(PiscsiExecutorTest, DetachAll) +TEST(PiscsiExecutorTest, DetachAll) { const int ID = 4; DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); - EXPECT_NE(nullptr, controller_manager->FindController(ID)); - EXPECT_FALSE(controller_manager->GetAllDevices().empty()); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + EXPECT_TRUE(controller_manager.HasController(ID)); + EXPECT_FALSE(controller_manager.GetAllDevices().empty()); executor.DetachAll(); - EXPECT_EQ(nullptr, controller_manager->FindController(ID)); - EXPECT_TRUE(controller_manager->GetAllDevices().empty()); + EXPECT_EQ(nullptr, controller_manager.FindController(ID)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } -TEST_F(PiscsiExecutorTest, ShutDown) +TEST(PiscsiExecutorTest, ShutDown) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + command.set_operation(SHUT_DOWN); + CommandContext context(command, "", ""); EXPECT_FALSE(executor.ShutDown(context, "")); EXPECT_FALSE(executor.ShutDown(context, "xyz")); + + EXPECT_FALSE(executor.ShutDown(context, "system")) << "Only available for the root user"; + EXPECT_FALSE(executor.ShutDown(context, "reboot")) << "Only available for the root user"; EXPECT_TRUE(executor.ShutDown(context, "rascsi")); - EXPECT_FALSE(executor.ShutDown(context, "system")); - EXPECT_FALSE(executor.ShutDown(context, "reboot")); } -TEST_F(PiscsiExecutorTest, SetReservedIds) +TEST(PiscsiExecutorTest, SetReservedIds) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); string error = executor.SetReservedIds("xyz"); EXPECT_FALSE(error.empty()); @@ -460,61 +434,85 @@ TEST_F(PiscsiExecutorTest, SetReservedIds) EXPECT_TRUE(error.empty()); unordered_set reserved_ids = executor.GetReservedIds(); EXPECT_EQ(5, reserved_ids.size()); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(1)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(2)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(3)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(5)); - EXPECT_NE(reserved_ids.end(), reserved_ids.find(7)); + EXPECT_TRUE(reserved_ids.contains(1)); + EXPECT_TRUE(reserved_ids.contains(2)); + EXPECT_TRUE(reserved_ids.contains(3)); + EXPECT_TRUE(reserved_ids.contains(5)); + EXPECT_TRUE(reserved_ids.contains(7)); auto device = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(5, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 5, device)); error = executor.SetReservedIds("5"); EXPECT_FALSE(error.empty()); } -TEST_F(PiscsiExecutorTest, ValidateImageFile) +TEST(PiscsiExecutorTest, ValidateImageFile) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); - string full_path; auto device = dynamic_pointer_cast(device_factory.CreateDevice(SCHD, 0, "test")); - EXPECT_TRUE(executor.ValidateImageFile(context, *device, "", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_TRUE(executor.ValidateImageFile(context, *device, "")); - EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file", full_path)); - EXPECT_TRUE(full_path.empty()); + EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file")); } -TEST_F(PiscsiExecutorTest, ValidateLunSetup) +TEST(PiscsiExecutorTest, PrintCommand) +{ + auto bus = make_shared(); + ControllerManager controller_manager; + PiscsiImage piscsi_image; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbDeviceDefinition definition; + + PbCommand command; + string s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_EQ(s.find("key1=value1"), string::npos); + EXPECT_EQ(s.find("key2=value2"), string::npos); + + SetParam(command, "key1", "value1"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + + SetParam(command, "key2", "value2"); + s = executor.PrintCommand(command, definition); + EXPECT_NE(s.find("operation="), string::npos); + EXPECT_NE(s.find("key1=value1"), string::npos); + EXPECT_NE(s.find("key2=value2"), string::npos); +} + +TEST(PiscsiExecutorTest, EnsureLun0) { DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); PbCommand command; auto device1 = command.add_devices(); device1->set_unit(0); - string error = executor.ValidateLunSetup(command); + string error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); device1->set_unit(1); - error = executor.ValidateLunSetup(command); + error = executor.EnsureLun0(command); EXPECT_FALSE(error.empty()); auto device2 = device_factory.CreateDevice(SCHS, 0, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, device2)); - error = executor.ValidateLunSetup(command); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, device2)); + error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); } -TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) +TEST(PiscsiExecutorTest, VerifyExistingIdAndLun) { const int ID = 1; const int LUN1 = 0; @@ -522,25 +520,27 @@ TEST_F(PiscsiExecutorTest, VerifyExistingIdAndLun) DeviceFactory device_factory; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); auto device = device_factory.CreateDevice(SCHS, LUN1, ""); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); } -TEST_F(PiscsiExecutorTest, CreateDevice) +TEST(PiscsiExecutorTest, CreateDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_EQ(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "")); #pragma GCC diagnostic push @@ -551,13 +551,14 @@ TEST_F(PiscsiExecutorTest, CreateDevice) EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); } -TEST_F(PiscsiExecutorTest, SetSectorSize) +TEST(PiscsiExecutorTest, SetSectorSize) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); unordered_set sizes; auto hd = make_shared(0, sizes, false); @@ -570,13 +571,14 @@ TEST_F(PiscsiExecutorTest, SetSectorSize) EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); } -TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) +TEST(PiscsiExecutorTest, ValidateOperationAgainstDevice) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); auto device = make_shared(0); @@ -623,13 +625,14 @@ TEST_F(PiscsiExecutorTest, ValidateOperationAgainstDevice) EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); } -TEST_F(PiscsiExecutorTest, ValidateIdAndLun) +TEST(PiscsiExecutorTest, ValidateIdAndLun) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); EXPECT_FALSE(executor.ValidateIdAndLun(context, -1, 0)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 8, 0)); @@ -639,13 +642,14 @@ TEST_F(PiscsiExecutorTest, ValidateIdAndLun) EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); } -TEST_F(PiscsiExecutorTest, SetProductData) +TEST(PiscsiExecutorTest, SetProductData) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiImage piscsi_image; - PiscsiExecutor executor(piscsi_image, *controller_manager); - MockCommandContext context; + PiscsiExecutor executor(piscsi_image, *bus, controller_manager); + PbCommand command; + CommandContext context(command, "", ""); PbDeviceDefinition definition; auto device = make_shared(0); diff --git a/cpp/test/piscsi_image_test.cpp b/cpp/test/piscsi_image_test.cpp index ac48aa2b..aa5bbdab 100644 --- a/cpp/test/piscsi_image_test.cpp +++ b/cpp/test/piscsi_image_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -35,104 +35,129 @@ TEST(PiscsiImageTest, SetGetDefaultFolder) TEST(PiscsiImageTest, CreateImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CreateImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CreateImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CreateImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - SetParam(command, "size", "-1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command3; + SetParam(command3, "file", "filename"); + SetParam(command3, "size", "-1"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CreateImage(context3)) << "Size must be reported as invalid"; - SetParam(command, "size", "1"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + PbCommand command4; + SetParam(command4, "size", "1"); + CommandContext context4(command4, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as invalid"; - SetParam(command, "size", "513"); - EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as not a multiple of 512"; + PbCommand command5; + SetParam(command5, "size", "513"); + CommandContext context5(command5, "", ""); + EXPECT_FALSE(image.CreateImage(context4)) << "Size must be reported as not a multiple of 512"; // Further tests would modify the filesystem } TEST(PiscsiImageTest, DeleteImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.DeleteImage(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.DeleteImage(context2)) << "Depth must be reported as invalid"; MockStorageDevice device; - device.ReserveFile("filename", 0, 0); - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.DeleteImage(context, command)) << "File must be reported as in use"; + device.SetFilename("filename"); + device.ReserveFile(); + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.DeleteImage(context3)) << "File must be reported as in use"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, RenameImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.RenameImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.RenameImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.RenameImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.RenameImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, CopyImage) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.CopyImage(context1)) << "Source filename must be reported as missing"; - SetParam(command, "from", "/a/b/c/filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "from", "/a/b/c/filename_from"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.CopyImage(context2)) << "Depth must be reported as invalid"; - SetParam(command, "from", "filename_from"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "from", "filename_from"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } TEST(PiscsiImageTest, SetImagePermissions) { - MockCommandContext context; - PbCommand command; PiscsiImage image; StorageDevice::UnreserveAll(); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Filename must be reported as missing"; + PbCommand command1; + CommandContext context1(command1, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context1)) << "Filename must be reported as missing"; - SetParam(command, "file", "/a/b/c/filename"); - EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Depth must be reported as invalid"; + PbCommand command2; + SetParam(command2, "file", "/a/b/c/filename"); + CommandContext context2(command2, "", ""); + EXPECT_FALSE(image.SetImagePermissions(context2)) << "Depth must be reported as invalid"; - SetParam(command, "file", "filename"); - EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + PbCommand command3; + SetParam(command3, "file", "filename"); + CommandContext context3(command3, "", ""); + EXPECT_FALSE(image.CopyImage(context3)) << "Source file must be reported as missing"; // Further testing would modify the filesystem } diff --git a/cpp/test/piscsi_response_test.cpp b/cpp/test/piscsi_response_test.cpp index 168eb5bc..19ebb49c 100644 --- a/cpp/test/piscsi_response_test.cpp +++ b/cpp/test/piscsi_response_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,32 +13,33 @@ #include "devices/device_factory.h" #include "generated/piscsi_interface.pb.h" #include "piscsi/piscsi_response.h" +#include using namespace piscsi_interface; TEST(PiscsiResponseTest, Operation_Count) { PiscsiResponse response; - PbResult result; - const auto info = response.GetOperationInfo(result, 0); - EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info->operations_size()); + PbOperationInfo info; + response.GetOperationInfo(info, 0); + EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info.operations_size()); } void TestNonDiskDevice(PbDeviceType type, int default_param_count) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; DeviceFactory device_factory; PiscsiResponse response; auto d = device_factory.CreateDevice(type, 0, ""); - const unordered_map params; + const param_map params; d->Init(params); - EXPECT_TRUE(controller_manager->AttachToScsiController(0, d)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, d)); PbServerInfo info; - response.GetDevices(controller_manager->GetAllDevices(), info, "image_folder"); + response.GetDevices(controller_manager.GetAllDevices(), info, "image_folder"); EXPECT_EQ(1, info.devices_info().devices().size()); @@ -81,21 +82,29 @@ TEST(PiscsiResponseTest, GetImageFile) EXPECT_EQ(SCHD, image_file.type()); } +TEST(PiscsiResponseTest, GetImageFilesInfo) +{ + PiscsiResponse response; + + PbImageFilesInfo info; + response.GetImageFilesInfo(info, "default_folder", "", "", 1); + EXPECT_TRUE(info.image_files().empty()); +} + TEST(PiscsiResponseTest, GetReservedIds) { PiscsiResponse response; unordered_set ids; - PbResult result; - const auto& info1 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(info1->ids().empty()); + PbReservedIdsInfo info1; + response.GetReservedIds(info1, ids); + EXPECT_TRUE(info1.ids().empty()); ids.insert(3); - const auto& info2 = response.GetReservedIds(result, ids); - EXPECT_TRUE(result.status()); - EXPECT_EQ(1, info2->ids().size()); - EXPECT_EQ(3, info2->ids()[0]); + PbReservedIdsInfo info2; + response.GetReservedIds(info2, ids); + EXPECT_EQ(1, info2.ids().size()); + EXPECT_EQ(3, info2.ids()[0]); } TEST(PiscsiResponseTest, GetDevicesInfo) @@ -106,40 +115,42 @@ TEST(PiscsiResponseTest, GetDevicesInfo) const int LUN3 = 6; auto bus = make_shared(); - auto controller_manager = make_shared(*bus); + ControllerManager controller_manager; PiscsiResponse response; PbCommand command; - PbResult result; - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - EXPECT_TRUE(result.devices_info().devices().empty()); + PbResult result1; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + EXPECT_TRUE(result1.devices_info().devices().empty()); auto device1 = make_shared(LUN1); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device1)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices1 = result.devices_info().devices(); + response.GetDevicesInfo(controller_manager.GetAllDevices(), result1, command, ""); + EXPECT_TRUE(result1.status()); + auto& devices1 = result1.devices_info().devices(); EXPECT_EQ(1, devices1.size()); EXPECT_EQ(SCHS, devices1[0].type()); EXPECT_EQ(ID, devices1[0].id()); EXPECT_EQ(LUN1, devices1[0].unit()); auto device2 = make_shared(LUN2); - EXPECT_TRUE(controller_manager->AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices2 = result.devices_info().devices(); - EXPECT_EQ(2, devices2.size()) << "Data for all devices must be returned"; + PbResult result2; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result2, command, ""); + EXPECT_TRUE(result2.status()); + auto& devices2 = result2.devices_info().devices(); + EXPECT_EQ(2, devices2.size()) << "Device count mismatch"; auto requested_device = command.add_devices(); requested_device->set_id(ID); requested_device->set_unit(LUN1); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_TRUE(result.status()); - auto& devices3 = result.devices_info().devices(); + PbResult result3; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result3, command, ""); + EXPECT_TRUE(result3.status()); + auto& devices3 = result3.devices_info().devices(); EXPECT_EQ(1, devices3.size()) << "Only data for the specified ID and LUN must be returned"; EXPECT_EQ(SCHS, devices3[0].type()); EXPECT_EQ(ID, devices3[0].id()); @@ -147,79 +158,73 @@ TEST(PiscsiResponseTest, GetDevicesInfo) requested_device->set_id(ID); requested_device->set_unit(LUN3); - response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, ""); - EXPECT_FALSE(result.status()) << "Only data for the specified ID and LUN must be returned"; + PbResult result4; + response.GetDevicesInfo(controller_manager.GetAllDevices(), result4, command, ""); + EXPECT_FALSE(result4.status()) << "Only data for the specified ID and LUN must be returned"; } TEST(PiscsiResponseTest, GetDeviceTypesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetDeviceTypesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(8, info->properties().size()); + PbDeviceTypesInfo info; + response.GetDeviceTypesInfo(info); + EXPECT_EQ(8, info.properties().size()); } TEST(PiscsiResponseTest, GetServerInfo) { auto bus = make_shared(); - auto controller_manager = make_shared(*bus); PiscsiResponse response; const unordered_set> devices; const unordered_set ids = { 1, 3 }; - PbResult result; - const auto& info = response.GetServerInfo(devices, result, ids, "log_level", "default_folder", "", "", 1234); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->version_info().major_version()); - EXPECT_EQ(piscsi_minor_version, info->version_info().minor_version()); - EXPECT_EQ(piscsi_patch_version, info->version_info().patch_version()); - EXPECT_EQ("log_level", info->log_level_info().current_log_level()); - EXPECT_EQ("default_folder", info->image_files_info().default_image_folder()); - EXPECT_EQ(1234, info->image_files_info().depth()); - EXPECT_EQ(2, info->reserved_ids_info().ids().size()); + PbServerInfo info; + response.GetServerInfo(info, devices, ids, "default_folder", "", "", 1234); + EXPECT_EQ(piscsi_major_version, info.version_info().major_version()); + EXPECT_EQ(piscsi_minor_version, info.version_info().minor_version()); + EXPECT_EQ(piscsi_patch_version, info.version_info().patch_version()); + EXPECT_EQ(level::level_string_views[get_level()], info.log_level_info().current_log_level()); + EXPECT_EQ("default_folder", info.image_files_info().default_image_folder()); + EXPECT_EQ(1234, info.image_files_info().depth()); + EXPECT_EQ(2, info.reserved_ids_info().ids().size()); } TEST(PiscsiResponseTest, GetVersionInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetVersionInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(piscsi_major_version, info->major_version()); - EXPECT_EQ(piscsi_minor_version, info->minor_version()); - EXPECT_EQ(piscsi_patch_version, info->patch_version()); + PbVersionInfo info; + response.GetVersionInfo(info); + EXPECT_EQ(piscsi_major_version, info.major_version()); + EXPECT_EQ(piscsi_minor_version, info.minor_version()); + EXPECT_EQ(piscsi_patch_version, info.patch_version()); } TEST(PiscsiResponseTest, GetLogLevelInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetLogLevelInfo(result, "level"); - EXPECT_TRUE(result.status()); - EXPECT_EQ("level", info->current_log_level()); - EXPECT_EQ(6, info->log_levels().size()); + PbLogLevelInfo info; + response.GetLogLevelInfo(info); + EXPECT_EQ(level::level_string_views[get_level()], info.current_log_level()); + EXPECT_EQ(7, info.log_levels().size()); } TEST(PiscsiResponseTest, GetNetworkInterfacesInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetNetworkInterfacesInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_FALSE(info->name().empty()); + PbNetworkInterfacesInfo info; + response.GetNetworkInterfacesInfo(info); + EXPECT_FALSE(info.name().empty()); } TEST(PiscsiResponseTest, GetMappingInfo) { PiscsiResponse response; - PbResult result; - const auto& info = response.GetMappingInfo(result); - EXPECT_TRUE(result.status()); - EXPECT_EQ(10, info->mapping().size()); + PbMappingInfo info; + response.GetMappingInfo(info); + EXPECT_EQ(10, info.mapping().size()); } diff --git a/cpp/test/piscsi_service_test.cpp b/cpp/test/piscsi_service_test.cpp index a29a8172..80a1c8c6 100644 --- a/cpp/test/piscsi_service_test.cpp +++ b/cpp/test/piscsi_service_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test up the point where a network connection is required. // @@ -11,16 +11,100 @@ #include +#include "generated/piscsi_interface.pb.h" +#include "shared/protobuf_util.h" +#include "shared/network_util.h" +#include "shared/piscsi_exceptions.h" +#include "piscsi/command_context.h" #include "piscsi/piscsi_service.h" +#include +#include +#include +#include +#include -TEST(PiscsiServiceTest, LifeCycle) +using namespace piscsi_interface; +using namespace protobuf_util; +using namespace network_util; + +void SendCommand(const PbCommand& command, PbResult& result) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + server_addr.sin_port = htons(uint16_t(9999)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + EXPECT_TRUE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + ASSERT_EQ(6, write(fd, "RASCSI", 6)); + SerializeMessage(fd, command); + DeserializeMessage(fd, result); + close(fd); +} + +TEST(PiscsiServiceTest, Init) { PiscsiService service; - EXPECT_TRUE(service.Init(nullptr, 65535)); - EXPECT_FALSE(service.Init(nullptr, 65536)); - EXPECT_FALSE(service.Init(nullptr, 0)); - EXPECT_FALSE(service.Init(nullptr, -1)); - - service.Cleanup(); + EXPECT_FALSE(service.Init(nullptr, 65536).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 0).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, -1).empty()) << "Illegal port number"; + EXPECT_FALSE(service.Init(nullptr, 1).empty()) << "Port 1 is only available for the root user"; + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + service.Stop(); +} + +TEST(PiscsiServiceTest, IsRunning) +{ + PiscsiService service; + EXPECT_FALSE(service.IsRunning()); + EXPECT_TRUE(service.Init(nullptr, 9999).empty()) << "Port 9999 is expected not to be in use for this test"; + EXPECT_FALSE(service.IsRunning()); + + service.Start(); + EXPECT_TRUE(service.IsRunning()); + service.Stop(); + EXPECT_FALSE(service.IsRunning()); +} + +TEST(PiscsiServiceTest, Execute) +{ + sockaddr_in server_addr = {}; + ASSERT_TRUE(ResolveHostName("127.0.0.1", &server_addr)); + + const int fd = socket(AF_INET, SOCK_STREAM, 0); + ASSERT_NE(-1, fd); + + server_addr.sin_port = htons(uint16_t(9999)); + EXPECT_FALSE(connect(fd, reinterpret_cast(&server_addr), sizeof(server_addr)) >= 0) << "Service should not be running"; //NOSONAR bit_cast is not supported by the bullseye clang++ compiler + + close(fd); + + PiscsiService service; + service.Init([] (const CommandContext& context) { + if (context.GetCommand().operation() == PbOperation::NO_OPERATION) { + PbResult result; + result.set_status(true); + context.WriteResult(result); + } + else { + throw io_exception("error"); + } + return true; + }, 9999); + + service.Start(); + + PbCommand command; + PbResult result; + + SendCommand(command, result); + command.set_operation(PbOperation::NO_OPERATION); + EXPECT_TRUE(result.status()) << "Command should have been successful"; + + command.set_operation(PbOperation::EJECT); + SendCommand(command, result); + EXPECT_FALSE(result.status()) << "Exception should have been raised"; + + service.Stop(); } diff --git a/cpp/test/piscsi_util_test.cpp b/cpp/test/piscsi_util_test.cpp index bfe2bdd1..03a5133e 100644 --- a/cpp/test/piscsi_util_test.cpp +++ b/cpp/test/piscsi_util_test.cpp @@ -3,53 +3,109 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include #include "shared/piscsi_util.h" -#include "generated/piscsi_interface.pb.h" #ifdef __linux__ #include #endif using namespace std; -using namespace piscsi_interface; using namespace piscsi_util; +TEST(PiscsiUtilTest, Split) +{ + auto v = Split("this_is_a_test", '_'); + EXPECT_EQ(4, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is", v[1]); + EXPECT_EQ("a", v[2]); + EXPECT_EQ("test", v[3]); + v = Split("test", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":test", ':'); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("", v[0]); + EXPECT_EQ("test", v[1]); + v = Split("test:", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("test", v[0]); + v = Split(":", ':'); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("", v[0]); + v = Split("", ':'); + EXPECT_EQ(0, v.size()); + + v = Split("this:is:a:test", ':', 1); + EXPECT_EQ(1, v.size()); + EXPECT_EQ("this:is:a:test", v[0]); + v = Split("this:is:a:test", ':', 2); + EXPECT_EQ(2, v.size()); + EXPECT_EQ("this", v[0]); + EXPECT_EQ("is:a:test", v[1]); +} + +TEST(PiscsiUtilTest, GetLocale) +{ + EXPECT_LE(2, GetLocale().size()); +} + TEST(PiscsiUtilTest, ProcessId) { int id = -1; int lun = -1; - string error = ProcessId("", 32, id, lun); + string error = ProcessId("", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:32", 32, id, lun); + error = ProcessId("8", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("-1:", 32, id, lun); + error = ProcessId("0:32", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0:-1", 32, id, lun); + error = ProcessId("-1:", id, lun); EXPECT_FALSE(error.empty()); EXPECT_EQ(-1, id); EXPECT_EQ(-1, lun); - error = ProcessId("0", 32, id, lun); + error = ProcessId("0:-1", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("a:0", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0:a", id, lun); + EXPECT_FALSE(error.empty()); + EXPECT_EQ(-1, id); + EXPECT_EQ(-1, lun); + + error = ProcessId("0", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(0, id); - EXPECT_EQ(0, lun); + EXPECT_EQ(-1, lun); - error = ProcessId("7:31", 32, id, lun); + error = ProcessId("7:31", id, lun); EXPECT_TRUE(error.empty()); EXPECT_EQ(7, id); EXPECT_EQ(31, lun); @@ -78,10 +134,10 @@ TEST(PiscsiUtilTest, GetExtensionLowerCase) { EXPECT_EQ("", GetExtensionLowerCase("")); EXPECT_EQ("", GetExtensionLowerCase(".")); + EXPECT_EQ("", GetExtensionLowerCase(".ext")); + EXPECT_EQ("", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase("file.ext")); EXPECT_EQ("ext", GetExtensionLowerCase("FILE.EXT")); - EXPECT_EQ("ext", GetExtensionLowerCase(".ext")); - EXPECT_EQ("ext_long", GetExtensionLowerCase(".ext_long")); EXPECT_EQ("ext", GetExtensionLowerCase(".XYZ.EXT")); } diff --git a/cpp/test/primary_device_test.cpp b/cpp/test/primary_device_test.cpp index 233e5f38..0a3739a0 100644 --- a/cpp/test/primary_device_test.cpp +++ b/cpp/test/primary_device_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -17,33 +17,28 @@ using namespace scsi_defs; using namespace scsi_command_util; +pair, shared_ptr> CreatePrimaryDevice(int id = 0) +{ + auto controller = make_shared>(id); + auto device = make_shared(0); + EXPECT_TRUE(device->Init({})); + EXPECT_TRUE(controller->AddDevice(device)); + + return { controller, device }; +} + TEST(PrimaryDeviceTest, GetId) { const int ID = 5; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, ID); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(ID); - EXPECT_EQ(-1, device->GetId()) << "Device ID cannot be known without assignment to a controller"; - - controller->AddDevice(device); EXPECT_EQ(ID, device->GetId()); } TEST(PrimaryDeviceTest, PhaseChange) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_CALL(*controller, Status); device->EnterStatusPhase(); @@ -57,14 +52,7 @@ TEST(PrimaryDeviceTest, PhaseChange) TEST(PrimaryDeviceTest, Reset) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -76,14 +64,7 @@ TEST(PrimaryDeviceTest, Reset) TEST(PrimaryDeviceTest, CheckReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) << "Device must not be reserved for initiator ID 0"; @@ -110,14 +91,7 @@ TEST(PrimaryDeviceTest, CheckReservation) TEST(PrimaryDeviceTest, ReserveReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -139,14 +113,7 @@ TEST(PrimaryDeviceTest, ReserveReleaseUnit) TEST(PrimaryDeviceTest, DiscardReservation) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); device->Dispatch(scsi_command::eCmdReserve6); EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) @@ -158,73 +125,61 @@ TEST(PrimaryDeviceTest, DiscardReservation) TEST(PrimaryDeviceTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; device->SetReset(true); device->SetAttn(true); device->SetReady(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetReset(true); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::POWER_ON_OR_RESET)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::power_on_or_reset)))); device->SetReset(false); device->SetAttn(true); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::UNIT_ATTENTION), - Property(&scsi_exception::get_asc, asc::NOT_READY_TO_READY_CHANGE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::unit_attention), + Property(&scsi_exception::get_asc, asc::not_ready_to_ready_change)))); device->SetAttn(false); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdTestUnitReady); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + ON_CALL(*d, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::processor, scsi_level::spc_3, false); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); @@ -237,42 +192,42 @@ TEST(PrimaryDeviceTest, Inquiry) EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::PROCESSOR, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::processor, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable"; - EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::spc_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { - return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); + ON_CALL(*device, InquiryInternal()).WillByDefault([&d]() { + return d->HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, true); }); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); - EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(device_type::direct_access, (device_type)controller->GetBuffer()[0]); EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::scsi_1_ccs, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; - cmd[1] = 0x01; + controller->SetCmdByte(1, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "EVPD bit is not supported"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_CALL(*controller, DataIn).Times(0); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&d] { d->Dispatch(scsi_command::eCmdInquiry); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "PAGE CODE field is not supported"; - cmd[1] = 0x00; - cmd[2] = 0x00; + controller->SetCmdByte(1, 0); + controller->SetCmdByte(2, 0); // ALLOCATION LENGTH - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*device, InquiryInternal); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdInquiry); @@ -282,64 +237,51 @@ TEST(PrimaryDeviceTest, Inquiry) TEST(PrimaryDeviceTest, RequestSense) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); device->SetReady(false); - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRequestSense); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); device->SetReady(true); EXPECT_CALL(*controller, DataIn); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(PrimaryDeviceTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreatePrimaryDevice(); + // Required by the bullseye clang++ compiler + auto d = device; EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[1] = 0x10; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(1, 0x10); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because PF bit is not supported"; - cmd[1] = 0; + controller->SetCmdByte(1, 0); - cmd[3] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; - cmd[3] = 0; - cmd[4] = 1; - EXPECT_THAT([&] { device->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 1); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSendDiagnostic); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; } @@ -348,23 +290,19 @@ TEST(PrimaryDeviceTest, ReportLuns) const int LUN1 = 1; const int LUN2 = 4; - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(0); auto device1 = make_shared(LUN1); auto device2 = make_shared(LUN2); - const unordered_map params; - device1->Init(params); - device2->Init(params); + EXPECT_TRUE(device1->Init({})); + EXPECT_TRUE(device2->Init({})); controller->AddDevice(device1); EXPECT_TRUE(controller->HasDeviceForLun(LUN1)); controller->AddDevice(device2); EXPECT_TRUE(controller->HasDeviceForLun(LUN2)); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[9] = 255; + controller->SetCmdByte(9, 255); EXPECT_CALL(*controller, DataIn); device1->Dispatch(scsi_command::eCmdReportLuns); @@ -380,33 +318,25 @@ TEST(PrimaryDeviceTest, ReportLuns) EXPECT_EQ(0, GetInt16(buffer, 20)) << "Wrong LUN2 number"; EXPECT_EQ(LUN2, GetInt16(buffer, 22)) << "Wrong LUN2 number"; - cmd[2] = 0x01; + controller->SetCmdByte(2, 0x01); EXPECT_THAT([&] { device1->Dispatch(scsi_command::eCmdReportLuns); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Only SELECT REPORT mode 0 is supported"; } TEST(PrimaryDeviceTest, Dispatch) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto device = make_shared(0); - const unordered_map params; - device->Init(params); - - controller->AddDevice(device); + auto [controller, device] = CreatePrimaryDevice(); EXPECT_THROW(device->Dispatch(static_cast(0x1f)), scsi_exception) << "Unknown command"; } TEST(PrimaryDeviceTest, WriteByteSequence) { - vector data; - MockPrimaryDevice device(0); + auto [controller, device] = CreatePrimaryDevice(); - EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device does not support writing byte sequences"; + EXPECT_FALSE(device->WriteByteSequence({})) << "Primary device does not support writing byte sequences"; } TEST(PrimaryDeviceTest, GetSetSendDelay) @@ -420,16 +350,8 @@ TEST(PrimaryDeviceTest, GetSetSendDelay) TEST(PrimaryDeviceTest, Init) { - unordered_map params; + param_map params; MockPrimaryDevice device(0); EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail"; } - -TEST(PrimaryDeviceTest, FlushCache) -{ - MockPrimaryDevice device(0); - - // Method must be present - device.FlushCache(); -} diff --git a/cpp/test/protobuf_serializer_test.cpp b/cpp/test/protobuf_serializer_test.cpp deleted file mode 100644 index 6cc4e671..00000000 --- a/cpp/test/protobuf_serializer_test.cpp +++ /dev/null @@ -1,98 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator PiSCSI -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "mocks.h" -#include "shared/protobuf_serializer.h" -#include "shared/piscsi_exceptions.h" -#include "generated/piscsi_interface.pb.h" -#include - -using namespace filesystem; -using namespace piscsi_interface; - -TEST(ProtobufSerializerTest, SerializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - - const int fd = open("/dev/null", O_WRONLY); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - EXPECT_THROW(serializer.SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; - close(fd); -} - -TEST(ProtobufSerializerTest, DeserializeMessage) -{ - PbResult result; - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; - close(fd); - - auto [fd1, filename1] = OpenTempFile(); - // Data size -1 - buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; - EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); - close(fd1); - fd1 = open(filename1.c_str(), O_RDONLY); - EXPECT_NE(-1, fd1); - EXPECT_THROW(serializer.DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; - remove(filename1); - - auto [fd2, filename2] = OpenTempFile(); - // Data size 2 - buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; - EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); - close(fd2); - fd2 = open(filename2.c_str(), O_RDONLY); - EXPECT_NE(-1, fd2); - EXPECT_THROW(serializer.DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; - remove(filename2); -} - -TEST(ProtobufSerializerTest, SerializeDeserializeMessage) -{ - PbResult result; - result.set_status(true); - ProtobufSerializer serializer; - - auto [fd, filename] = OpenTempFile(); - EXPECT_NE(-1, fd); - serializer.SerializeMessage(fd, result); - close(fd); - - result.set_status(false); - fd = open(filename.c_str(), O_RDONLY); - EXPECT_NE(-1, fd); - serializer.DeserializeMessage(fd, result); - close(fd); - remove(filename); - - EXPECT_TRUE(result.status()); -} - -TEST(ProtobufSerializerTest, ReadBytes) -{ - ProtobufSerializer serializer; - vector buf(1); - - int fd = open("/dev/null", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(0, serializer.ReadBytes(fd, buf)); - close(fd); - - fd = open("/dev/zero", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_EQ(1, serializer.ReadBytes(fd, buf)); - close(fd); -} diff --git a/cpp/test/protobuf_util_test.cpp b/cpp/test/protobuf_util_test.cpp index afd71148..1c4a06e6 100644 --- a/cpp/test/protobuf_util_test.cpp +++ b/cpp/test/protobuf_util_test.cpp @@ -3,13 +3,15 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/protobuf_util.h" +#include "shared/piscsi_exceptions.h" #include "generated/piscsi_interface.pb.h" +#include using namespace piscsi_interface; using namespace protobuf_util; @@ -22,22 +24,14 @@ void TestSpecialDevice(const string& name) EXPECT_EQ("", GetParam(device, "interfaces")); } -TEST(ProtobufUtil, AddGetParam) +TEST(ProtobufUtil, GetSetParam) { + // The implementation is a function template, testing one possible T is sufficient PbCommand command; SetParam(command, "key", "value"); EXPECT_EQ("value", GetParam(command, "key")); EXPECT_EQ("", GetParam(command, "xyz")); - - PbDeviceDefinition definition; - SetParam(definition, "key", "value"); - EXPECT_EQ("value", GetParam(definition, "key")); - EXPECT_EQ("", GetParam(definition, "xyz")); - - PbDevice device; - SetParam(device, "key", "value"); - const auto& it = device.params().find("key"); - EXPECT_EQ("value", it->second); + EXPECT_EQ("", GetParam(command, "")); } TEST(ProtobufUtil, ParseParameters) @@ -84,7 +78,7 @@ TEST(ProtobufUtil, SetPatternParams) TEST(ProtobufUtil, ListDevices) { - list devices; + vector devices; EXPECT_FALSE(ListDevices(devices).empty()); @@ -136,10 +130,90 @@ TEST(ProtobufUtil, SetIdAndLun) { PbDeviceDefinition device; - EXPECT_NE("", SetIdAndLun(device, "", 32)); - EXPECT_EQ("", SetIdAndLun(device, "1", 32)); + EXPECT_NE("", SetIdAndLun(device, "")); + EXPECT_EQ("", SetIdAndLun(device, "1")); EXPECT_EQ(1, device.id()); - EXPECT_EQ("", SetIdAndLun(device, "2:0", 32)); + EXPECT_EQ("", SetIdAndLun(device, "2:0")); EXPECT_EQ(2, device.id()); EXPECT_EQ(0, device.unit()); } + +TEST(ProtobufUtil, SerializeMessage) +{ + PbResult result; + + const int fd = open("/dev/null", O_WRONLY); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + EXPECT_THROW(SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; +} + +TEST(ProtobufUtil, DeserializeMessage) +{ + PbResult result; + vector buf(1); + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_THROW(DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; + close(fd); + + auto [fd1, filename1] = OpenTempFile(); + // Data size -1 + buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; + EXPECT_EQ(buf.size(), write(fd1, buf.data(), buf.size())); + close(fd1); + fd1 = open(filename1.c_str(), O_RDONLY); + ASSERT_NE(-1, fd1); + EXPECT_THROW(DeserializeMessage(fd1, result), io_exception) << "Invalid header was not rejected"; + remove(filename1); + + auto [fd2, filename2] = OpenTempFile(); + // Data size 2 + buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; + EXPECT_EQ(buf.size(), write(fd2, buf.data(), buf.size())); + close(fd2); + fd2 = open(filename2.c_str(), O_RDONLY); + EXPECT_NE(-1, fd2); + EXPECT_THROW(DeserializeMessage(fd2, result), io_exception) << "Invalid data were not rejected"; + remove(filename2); +} + +TEST(ProtobufUtil, SerializeDeserializeMessage) +{ + PbResult result; + result.set_status(true); + + auto [fd, filename] = OpenTempFile(); + ASSERT_NE(-1, fd); + SerializeMessage(fd, result); + close(fd); + + result.set_status(false); + fd = open(filename.c_str(), O_RDONLY); + ASSERT_NE(-1, fd); + DeserializeMessage(fd, result); + close(fd); + remove(filename); + + EXPECT_TRUE(result.status()); +} + +TEST(ProtobufUtil, ReadBytes) +{ + vector buf1(1); + vector buf2; + + int fd = open("/dev/null", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(0, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); + + fd = open("/dev/zero", O_RDONLY); + ASSERT_NE(-1, fd); + EXPECT_EQ(1, ReadBytes(fd, buf1)); + EXPECT_EQ(0, ReadBytes(fd, buf2)); + close(fd); +} diff --git a/cpp/test/scsi_command_util_test.cpp b/cpp/test/scsi_command_util_test.cpp index dbfdffa7..20afa389 100644 --- a/cpp/test/scsi_command_util_test.cpp +++ b/cpp/test/scsi_command_util_test.cpp @@ -10,7 +10,6 @@ #include "mocks.h" #include "shared/scsi.h" #include "shared/piscsi_exceptions.h" -#include "devices/device_logger.h" #include "devices/scsi_command_util.h" using namespace scsi_command_util; @@ -18,14 +17,13 @@ using namespace scsi_command_util; TEST(ScsiCommandUtilTest, ModeSelect6) { const int LENGTH = 26; - DeviceLogger logger; vector cdb(6); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0); cdb[0] = 0x15; // PF (standard parameter format) @@ -34,50 +32,49 @@ TEST(ScsiCommandUtilTest, ModeSelect6) buf[9] = 0x00; buf[10] = 0x02; buf[11] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[12] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[12] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[24] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, ModeSelect10) { const int LENGTH = 30; - DeviceLogger logger; vector cdb(10); vector buf(LENGTH); // PF (vendor-specific parameter format) must not fail but be ignored cdb[1] = 0x00; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); + ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0); // PF (standard parameter format) cdb[1] = 0x10; @@ -85,37 +82,37 @@ TEST(ScsiCommandUtilTest, ModeSelect10) buf[13] = 0x00; buf[14] = 0x02; buf[15] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Page 0 buf[16] = 0x00; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[16] = 0x03; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[28] = 0x02; - EXPECT_THAT([&] { ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, + EXPECT_THAT([&] { ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_PARAMETER_LIST)))) + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_parameter_list)))) << "Not enough command parameters"; - ModeSelect(logger, scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); + EXPECT_FALSE(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512).empty()); } TEST(ScsiCommandUtilTest, EnrichFormatPage) diff --git a/cpp/test/scsi_controller_test.cpp b/cpp/test/scsi_controller_test.cpp index 60a2623f..4060bc3a 100644 --- a/cpp/test/scsi_controller_test.cpp +++ b/cpp/test/scsi_controller_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -19,8 +19,7 @@ TEST(ScsiControllerTest, GetInitiatorId) const int ID = 2; auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.Process(ID); EXPECT_EQ(ID, controller.GetInitiatorId()); @@ -31,46 +30,42 @@ TEST(ScsiControllerTest, GetInitiatorId) TEST(ScsiControllerTest, Process) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::reserved, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::busfree); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - EXPECT_EQ(phase_t::busfree, controller.Process(0)); + EXPECT_FALSE(controller.Process(0)); } TEST(ScsiControllerTest, BusFree) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::busfree); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - controller.SetStatus(status::CHECK_CONDITION); + controller.SetStatus(status::check_condition); controller.SetPhase(phase_t::reserved); controller.BusFree(); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_EQ(status::good, controller.GetStatus()); controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::NONE); controller.SetPhase(phase_t::reserved); @@ -86,14 +81,13 @@ TEST(ScsiControllerTest, BusFree) controller.ScheduleShutdown(AbstractController::piscsi_shutdown_mode::STOP_PISCSI); controller.SetPhase(phase_t::reserved); - EXPECT_EXIT(controller.BusFree(), ExitedWithCode(EXIT_SUCCESS), ""); + controller.BusFree(); } TEST(ScsiControllerTest, Selection) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); controller->SetPhase(phase_t::selection); ON_CALL(*bus, GetSEL).WillByDefault(Return(true)); @@ -130,17 +124,7 @@ TEST(ScsiControllerTest, Selection) controller->Selection(); EXPECT_EQ(phase_t::msgout, controller->GetPhase()); - controller->SetPhase(phase_t::reserved); - ON_CALL(*bus, GetDAT).WillByDefault(Return(0)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()); - ON_CALL(*bus, GetDAT).WillByDefault(Return(1)); - controller->Selection(); - EXPECT_EQ(phase_t::reserved, controller->GetPhase()) << "There is no device that can be selected"; - - auto device = make_shared(0); - controller->AddDevice(device); EXPECT_CALL(*bus, SetBSY(true)); controller->Selection(); EXPECT_EQ(phase_t::selection, controller->GetPhase()); @@ -149,8 +133,7 @@ TEST(ScsiControllerTest, Selection) TEST(ScsiControllerTest, Command) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::command); EXPECT_CALL(controller, Status); @@ -177,8 +160,7 @@ TEST(ScsiControllerTest, Command) TEST(ScsiControllerTest, MsgIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -193,8 +175,7 @@ TEST(ScsiControllerTest, MsgIn) TEST(ScsiControllerTest, MsgOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, SetMSG(true)); @@ -209,8 +190,7 @@ TEST(ScsiControllerTest, MsgOut) TEST(ScsiControllerTest, DataIn) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -230,8 +210,7 @@ TEST(ScsiControllerTest, DataIn) TEST(ScsiControllerTest, DataOut) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); controller.SetPhase(phase_t::reserved); controller.SetLength(0); @@ -251,66 +230,58 @@ TEST(ScsiControllerTest, DataOut) TEST(ScsiControllerTest, Error) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - MockScsiController controller(controller_manager, 0); + MockScsiController controller(bus, 0); ON_CALL(*bus, GetRST).WillByDefault(Return(true)); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset); EXPECT_CALL(controller, Reset); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::GOOD, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::good, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); ON_CALL(*bus, GetRST).WillByDefault(Return(false)); controller.SetPhase(phase_t::status); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::msgin); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); EXPECT_EQ(phase_t::busfree, controller.GetPhase()); controller.SetPhase(phase_t::reserved); EXPECT_CALL(*bus, Acquire); EXPECT_CALL(*bus, GetRST()); - EXPECT_CALL(*bus, Reset).Times(0); EXPECT_CALL(controller, Reset).Times(0); EXPECT_CALL(controller, Status); - controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); - EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); + controller.Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict); + EXPECT_EQ(status::reservation_conflict, controller.GetStatus()); EXPECT_EQ(phase_t::reserved, controller.GetPhase()); } TEST(ScsiControllerTest, RequestSense) { auto bus = make_shared>(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(bus, 0); auto device = make_shared(0); - const unordered_map params; - device->Init(params); + EXPECT_TRUE(device->Init({})); controller->AddDevice(device); - auto& cmd = controller->GetCmd(); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); // Non-existing LUN - cmd[1] = 0x20; + controller->SetCmdByte(1, 0x20); device->SetReady(true); EXPECT_CALL(*controller, Status); device->Dispatch(scsi_command::eCmdRequestSense); - EXPECT_EQ(status::GOOD, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; + EXPECT_EQ(status::good, controller->GetStatus()) << "Wrong CHECK CONDITION for non-existing LUN"; } diff --git a/cpp/test/scsi_daynaport_test.cpp b/cpp/test/scsi_daynaport_test.cpp index 122ff69b..7b54a3f3 100644 --- a/cpp/test/scsi_daynaport_test.cpp +++ b/cpp/test/scsi_daynaport_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,202 +13,168 @@ TEST(ScsiDaynaportTest, Inquiry) { - TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, "Dayna SCSI/Link 1.4a", 0x20, false); + TestInquiry::Inquiry(SCDP, device_type::processor, scsi_level::scsi_2, "Dayna SCSI/Link 1.4a", 0x20, false); } TEST(ScsiDaynaportTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiDaynaportTest, Read) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 1; - EXPECT_EQ(0, daynaport->Read(cmd, buf, 0)) << "Trying to read the root sector must fail"; + controller->SetCmdByte(4, 1); + vector buf(0); + EXPECT_EQ(0, dynamic_pointer_cast(daynaport)->Read(controller->GetCmd(), buf, 0)) << "Trying to read the root sector must fail"; } -TEST(ScsiDaynaportTest, WriteBytes) +TEST(ScsiDaynaportTest, Write) { - vector buf(0); - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, *controller)); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // Unknown data format - cmd[5] = 0xff; - EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0)); + controller->SetCmdByte(5, 0xff); + vector buf(0); + EXPECT_TRUE(dynamic_pointer_cast(daynaport)->Write(controller->GetCmd(), buf)); } TEST(ScsiDaynaportTest, Read6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdRead6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid data format"; } TEST(ScsiDaynaportTest, Write6) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - cmd[5] = 0x00; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = -1; - cmd[4] = -8; - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, -1); + controller->SetCmdByte(4, -8); + controller->SetCmdByte(5, 0x08); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; - cmd[3] = 0; - cmd[4] = 0; - cmd[5] = 0xff; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0); + controller->SetCmdByte(4, 0); + controller->SetCmdByte(5, 0xff); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdWrite6); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Invalid transfer length"; } TEST(ScsiDaynaportTest, TestRetrieveStats) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); daynaport->Dispatch(scsi_command::eCmdRetrieveStats); } TEST(ScsiDaynaportTest, SetInterfaceMode) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Unknown interface command - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented, do nothing - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMODE; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMODE); EXPECT_CALL(*controller, Status()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMAC; + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SETMAC); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_STATS; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_STATS); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_ENABLE; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_ENABLE); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); // Not implemented - cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SET; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_COMMAND_OPERATION_CODE)))); + controller->SetCmdByte(5, SCSIDaynaPort::CMD_SCSILINK_SET); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetIfaceMode); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_command_operation_code)))); } TEST(ScsiDaynaportTest, SetMcastAddr) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; - auto& cmd = controller->GetCmd(); - - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdSetMcastAddr); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Length of 0 is not supported"; - cmd[4] = 1; + controller->SetCmdByte(4, 1); EXPECT_CALL(*controller, DataOut()); daynaport->Dispatch(scsi_command::eCmdSetMcastAddr); } TEST(ScsiDaynaportTest, EnableInterface) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); - auto daynaport = CreateDevice(SCDP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, daynaport] = CreateDevice(SCDP); + // Required by the bullseye clang++ compiler + auto d = daynaport; // Enable - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); // Disable - cmd[5] = 0x80; - EXPECT_THAT([&] { daynaport->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))); + controller->SetCmdByte(5, 0x00); + EXPECT_THAT([&] { d->Dispatch(scsi_command::eCmdEnableInterface); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))); } TEST(ScsiDaynaportTest, GetSendDelay) { SCSIDaynaPort daynaport(0); - const unordered_map params; - daynaport.Init(params); + daynaport.Init({}); EXPECT_EQ(6, daynaport.GetSendDelay()); } diff --git a/cpp/test/scsi_host_bridge_test.cpp b/cpp/test/scsi_host_bridge_test.cpp index 42a584da..f0b2843f 100644 --- a/cpp/test/scsi_host_bridge_test.cpp +++ b/cpp/test/scsi_host_bridge_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,5 +11,5 @@ TEST(ScsiHostBridgeTest, Inquiry) { - TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); + TestInquiry::Inquiry(SCBR, device_type::communications, scsi_level::scsi_2, "PiSCSI RASCSI BRIDGE ", 0x27, false); } diff --git a/cpp/test/scsi_printer_test.cpp b/cpp/test/scsi_printer_test.cpp index 821d5c5a..7f29f4cf 100644 --- a/cpp/test/scsi_printer_test.cpp +++ b/cpp/test/scsi_printer_test.cpp @@ -3,27 +3,22 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsi_printer.h" using namespace std; TEST(ScsiPrinterTest, Init) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - unordered_map params; - EXPECT_TRUE(printer->Init(params)); + auto [controller, printer] = CreateDevice(SCLP); + EXPECT_TRUE(printer->Init({})); + param_map params; params["cmd"] = "missing_filename_specifier"; EXPECT_FALSE(printer->Init(params)); @@ -33,99 +28,80 @@ TEST(ScsiPrinterTest, Init) TEST(ScsiPrinterTest, TestUnitReady) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdTestUnitReady); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Inquiry) { - TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, "PiSCSI SCSI PRINTER ", 0x1f, false); + TestInquiry::Inquiry(SCLP, device_type::printer, scsi_level::scsi_2, "PiSCSI SCSI PRINTER ", 0x1f, false); } TEST(ScsiPrinterTest, ReserveUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdReserve6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, ReleaseUnit) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdRelease6); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SendDiagnostic) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()).Times(1); printer->Dispatch(scsi_command::eCmdSendDiagnostic); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, Print) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); - - auto& cmd = controller->GetCmd(); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; EXPECT_CALL(*controller, DataOut()); printer->Dispatch(scsi_command::eCmdPrint); - cmd[3] = 0xff; - cmd[4] = 0xff; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ILLEGAL_REQUEST), - Property(&scsi_exception::get_asc, asc::INVALID_FIELD_IN_CDB)))) + controller->SetCmdByte(3, 0xff); + controller->SetCmdByte(4, 0xff); + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdPrint); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::illegal_request), + Property(&scsi_exception::get_asc, asc::invalid_field_in_cdb)))) << "Buffer overflow was not reported"; } TEST(ScsiPrinterTest, StopPrint) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); EXPECT_CALL(*controller, Status()); printer->Dispatch(scsi_command::eCmdStopPrint); - EXPECT_EQ(status::GOOD, controller->GetStatus()); + EXPECT_EQ(status::good, controller->GetStatus()); } TEST(ScsiPrinterTest, SynchronizeBuffer) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); + // Required by the bullseye clang++ compiler + auto p = printer; - EXPECT_THAT([&] { printer->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::ABORTED_COMMAND), - Property(&scsi_exception::get_asc, asc::NO_ADDITIONAL_SENSE_INFORMATION)))) + EXPECT_THAT([&] { p->Dispatch(scsi_command::eCmdSynchronizeBuffer); }, Throws(AllOf( + Property(&scsi_exception::get_sense_key, sense_key::aborted_command), + Property(&scsi_exception::get_asc, asc::no_additional_sense_information)))) << "Nothing to print"; // Further testing would use the printing system @@ -133,11 +109,8 @@ TEST(ScsiPrinterTest, SynchronizeBuffer) TEST(ScsiPrinterTest, WriteByteSequence) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto printer = CreateDevice(SCLP, *controller); + auto [controller, printer] = CreateDevice(SCLP); - vector buf(1); - EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size())); + const vector buf(1); + EXPECT_TRUE(printer->WriteByteSequence(buf)); } diff --git a/cpp/test/scsicd_test.cpp b/cpp/test/scsicd_test.cpp index 925ad06d..fa5d8743 100644 --- a/cpp/test/scsicd_test.cpp +++ b/cpp/test/scsicd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -29,16 +29,15 @@ void ScsiCdTest_SetUpModePages(map>& pages) TEST(ScsiCdTest, Inquiry) { - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_2, "PiSCSI SCSI CD-ROM ", 0x1f, true); - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_1_CCS, "PiSCSI SCSI CD-ROM ", 0x1f, true, ".is1"); + TestInquiry::Inquiry(SCCD, device_type::cd_rom, scsi_level::scsi_1_ccs, "PiSCSI SCSI CD-ROM ", 0x1f, true, "file.is1"); } TEST(ScsiCdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSICD cd(0, sector_sizes); + MockSCSICD cd(0, {}); // Non changeable cd.SetUpModePages(pages, 0x3f, false); @@ -52,11 +51,10 @@ TEST(ScsiCdTest, SetUpModePages) TEST(ScsiCdTest, Open) { - const unordered_set sector_sizes; - MockSCSICD cd_iso(0, sector_sizes); - MockSCSICD cd_cue(0, sector_sizes); - MockSCSICD cd_raw(0, sector_sizes); - MockSCSICD cd_physical(0, sector_sizes); + MockSCSICD cd_iso(0, {}); + MockSCSICD cd_cue(0, {}); + MockSCSICD cd_raw(0, {}); + MockSCSICD cd_physical(0, {}); EXPECT_THROW(cd_iso.Open(), io_exception) << "Missing filename"; @@ -112,19 +110,16 @@ TEST(ScsiCdTest, Open) TEST(ScsiCdTest, ReadToc) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared(controller_manager, 0); + auto controller = make_shared(); const unordered_set sector_sizes; auto cd = make_shared(0, sector_sizes); - const unordered_map params; - cd->Init(params); + EXPECT_TRUE(cd->Init({})); controller->AddDevice(cd); EXPECT_THAT([&] { cd->Dispatch(scsi_command::eCmdReadToc); }, Throws(AllOf( - Property(&scsi_exception::get_sense_key, sense_key::NOT_READY), - Property(&scsi_exception::get_asc, asc::MEDIUM_NOT_PRESENT)))); + Property(&scsi_exception::get_sense_key, sense_key::not_ready), + Property(&scsi_exception::get_asc, asc::medium_not_present)))); // Further testing requires filesystem access } diff --git a/cpp/test/scsictl_commands_test.cpp b/cpp/test/scsictl_commands_test.cpp index b9394dd1..036f464c 100644 --- a/cpp/test/scsictl_commands_test.cpp +++ b/cpp/test/scsictl_commands_test.cpp @@ -92,8 +92,11 @@ TEST(ScsictlCommandsTest, Execute) command.set_operation(OPERATION_INFO); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); - command.set_operation(NO_OPERATION); + command.set_operation(DETACH_ALL); EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(NO_OPERATION); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); } TEST(ScsictlCommandsTest, CommandDevicesInfo) diff --git a/cpp/test/scsictl_display_test.cpp b/cpp/test/scsictl_display_test.cpp index 4fd8f71c..e5c53269 100644 --- a/cpp/test/scsictl_display_test.cpp +++ b/cpp/test/scsictl_display_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // // These tests only test key aspects of the expected output, because the output may change over time. // @@ -51,10 +51,9 @@ TEST(ScsictlDisplayTest, DisplayDeviceInfo) EXPECT_NE(string::npos, s.find(to_string(1234 *4321))); device.mutable_properties()->set_supports_file(true); - auto file = make_unique(); + auto file = device.mutable_file(); file->set_name("filename"); - device.set_allocated_file(file.release()); - s = display.DisplayDeviceInfo(device); //NOSONAR The allocated memory is managed by protobuf + s = display.DisplayDeviceInfo(device); EXPECT_FALSE(s.empty()); EXPECT_NE(string::npos, s.find("filename")); @@ -154,7 +153,7 @@ TEST(ScsictlDisplayTest, DisplayReservedIdsInfo) PbReservedIdsInfo info; string s = display.DisplayReservedIdsInfo(info); - EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.empty()); info.mutable_ids()->Add(5); s = display.DisplayReservedIdsInfo(info); diff --git a/cpp/test/scsihd_nec_test.cpp b/cpp/test/scsihd_nec_test.cpp index cfff8485..6b5cbd7c 100644 --- a/cpp/test/scsihd_nec_test.cpp +++ b/cpp/test/scsihd_nec_test.cpp @@ -9,7 +9,6 @@ #include "mocks.h" #include "shared/piscsi_exceptions.h" -#include "controllers/controller_manager.h" #include "devices/scsihd_nec.h" #include #include @@ -29,7 +28,7 @@ void ScsiHdNecTest_SetUpModePages(map>& pages) TEST(ScsiHdNecTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hdn"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hdn"); } TEST(ScsiHdNecTest, SetUpModePages) @@ -134,19 +133,19 @@ TEST(ScsiHdNecTest, SetParameters) ofstream out; out.open(hdi); - const array cylinders1 = { 1, 0, 0, 0 }; + const array cylinders1 = { 1, 0, 0, 0 }; out.seekp(28); out.write(cylinders1.data(), cylinders1.size()); - const array heads1 = { 1, 0, 0, 0 }; + const array heads1 = { 1, 0, 0, 0 }; out.seekp(24); out.write(heads1.data(), heads1.size()); - const array sectors1 = { 1, 0, 0, 0 }; + const array sectors1 = { 1, 0, 0, 0 }; out.seekp(20); out.write(sectors1.data(), sectors1.size()); - const array sector_size1 = { 0, 2, 0, 0 }; + const array sector_size1 = { 0, 2, 0, 0 }; out.seekp(16); out.write(sector_size1.data(), sector_size1.size()); - const array image_size = { 0, 2, 0, 0 }; + const array image_size = { 0, 2, 0, 0 }; out.seekp(12); out.write(image_size.data(), image_size.size()); out.close(); @@ -179,18 +178,18 @@ TEST(ScsiHdNecTest, SetParameters) out.open(nhd); out << "T98HDDIMAGE.R0"; - const array cylinders2 = { 1, 0 }; + const array cylinders2 = { 1, 0 }; out.seekp(0x114); out.write(cylinders2.data(), cylinders2.size()); - const array heads2 = { 1, 0 }; + const array heads2 = { 1, 0 }; out.seekp(0x118); out.write(heads2.data(), heads2.size()); - const array sectors2 = { 1, 0 }; + const array sectors2 = { 1, 0 }; out.seekp(0x11a); out.write(sectors2.data(), sectors2.size()); out.seekp(0x11c); out.write(sector_size2.data(), sector_size2.size()); - const array image_offset = { 1, 0, 0, 0 }; + const array image_offset = { 1, 0, 0, 0 }; out.seekp(0x110); out.write(image_offset.data(), image_offset.size()); out.close(); diff --git a/cpp/test/scsihd_test.cpp b/cpp/test/scsihd_test.cpp index 179c7170..1cdacad0 100644 --- a/cpp/test/scsihd_test.cpp +++ b/cpp/test/scsihd_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,23 +23,21 @@ void ScsiHdTest_SetUpModePages(map>& pages) TEST(ScsiHdTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "PiSCSI ", 0x1f, false); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_2, "PiSCSI ", 0x1f, false); - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "PiSCSI ", 0x1f, false, ".hd1"); + TestInquiry::Inquiry(SCHD, device_type::direct_access, scsi_level::scsi_1_ccs, "PiSCSI ", 0x1f, false, "file.hd1"); } TEST(ScsiHdTest, SupportsSaveParameters) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); EXPECT_TRUE(hd.SupportsSaveParameters()); } TEST(ScsiHdTest, FinalizeSetup) { - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); hd.SetSectorSizeInBytes(1024); EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; @@ -47,10 +45,9 @@ TEST(ScsiHdTest, FinalizeSetup) TEST(ScsiHdTest, GetProductData) { - const unordered_set sector_sizes; - MockSCSIHD hd_kb(0, sector_sizes, false); - MockSCSIHD hd_mb(0, sector_sizes, false); - MockSCSIHD hd_gb(0, sector_sizes, false); + MockSCSIHD hd_kb(0, {}, false); + MockSCSIHD hd_mb(0, {}, false); + MockSCSIHD hd_gb(0, {}, false); const path filename = CreateTempFile(1); hd_kb.SetFilename(string(filename)); @@ -79,8 +76,7 @@ TEST(ScsiHdTest, GetProductData) TEST(ScsiHdTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, {}, false); // Non changeable hd.SetUpModePages(pages, 0x3f, false); @@ -94,8 +90,7 @@ TEST(ScsiHdTest, SetUpModePages) TEST(ScsiHdTest, ModeSelect) { - const unordered_set sector_sizes = { 512 }; - MockSCSIHD hd(0, sector_sizes, false); + MockSCSIHD hd(0, { 512 }, false); vector cmd(10); vector buf(255); diff --git a/cpp/test/scsimo_test.cpp b/cpp/test/scsimo_test.cpp index 21447696..2422c479 100644 --- a/cpp/test/scsimo_test.cpp +++ b/cpp/test/scsimo_test.cpp @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -22,14 +22,13 @@ void ScsiMo_SetUpModePages(map>& pages) TEST(ScsiMoTest, Inquiry) { - TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "PiSCSI SCSI MO ", 0x1f, true); + TestInquiry::Inquiry(SCMO, device_type::optical_memory, scsi_level::scsi_2, "PiSCSI SCSI MO ", 0x1f, true); } TEST(ScsiMoTest, SupportsSaveParameters) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); EXPECT_TRUE(mo.SupportsSaveParameters()); } @@ -37,8 +36,7 @@ TEST(ScsiMoTest, SupportsSaveParameters) TEST(ScsiMoTest, SetUpModePages) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); // Non changeable mo.SetUpModePages(pages, 0x3f, false); @@ -53,8 +51,7 @@ TEST(ScsiMoTest, SetUpModePages) TEST(ScsiMoTest, TestAddVendorPage) { map> pages; - const unordered_set sector_sizes; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, {}); mo.SetReady(true); mo.SetUpModePages(pages, 0x21, false); @@ -125,8 +122,7 @@ TEST(ScsiMoTest, TestAddVendorPage) TEST(ScsiMoTest, ModeSelect) { - const unordered_set sector_sizes = { 1024, 2048 }; - MockSCSIMO mo(0, sector_sizes); + MockSCSIMO mo(0, { 1024, 2048 }); vector cmd(10); vector buf(255); diff --git a/cpp/test/storage_device_test.cpp b/cpp/test/storage_device_test.cpp index 9fbed528..033766f4 100644 --- a/cpp/test/storage_device_test.cpp +++ b/cpp/test/storage_device_test.cpp @@ -3,18 +3,19 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" +#include "shared/piscsi_util.h" #include "shared/piscsi_exceptions.h" #include "devices/storage_device.h" #include using namespace filesystem; -TEST(StorageDeviceTest, Filename) +TEST(StorageDeviceTest, SetGetFilename) { MockStorageDevice device; @@ -62,6 +63,8 @@ TEST(StorageDeviceTest, MediumChanged) { MockStorageDevice device; + EXPECT_FALSE(device.IsMediumChanged()); + device.SetMediumChanged(true); EXPECT_TRUE(device.IsMediumChanged()); @@ -72,22 +75,26 @@ TEST(StorageDeviceTest, MediumChanged) TEST(StorageDeviceTest, GetIdsForReservedFile) { const int ID = 1; - const int LUN = 2; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); StorageDevice::UnreserveAll(); - MockStorageDevice device; - device.SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id1); EXPECT_EQ(-1, lun1); - device.ReserveFile("filename", ID, LUN); + device->ReserveFile(); const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(ID, id2); EXPECT_EQ(LUN, lun2); - device.UnreserveFile(); + device->UnreserveFile(); const auto [id3, lun3] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id3); EXPECT_EQ(-1, lun3); @@ -95,9 +102,17 @@ TEST(StorageDeviceTest, GetIdsForReservedFile) TEST(StorageDeviceTest, UnreserveAll) { - MockStorageDevice device; - device.ReserveFile("filename", 2, 31); + const int ID = 1; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); + + device->ReserveFile(); StorageDevice::UnreserveAll(); const auto [id, lun] = StorageDevice::GetIdsForReservedFile("filename"); EXPECT_EQ(-1, id); @@ -107,18 +122,24 @@ TEST(StorageDeviceTest, UnreserveAll) TEST(StorageDeviceTest, GetSetReservedFiles) { const int ID = 1; - const int LUN = 16; + const int LUN = 0; + auto bus = make_shared(); + ControllerManager controller_manager; + MockAbstractController controller(bus, ID); + auto device = make_shared(LUN); + device->SetFilename("filename"); - MockStorageDevice device; - device.ReserveFile("filename", ID, LUN); + EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); - const unordered_map reserved_files = StorageDevice::GetReservedFiles(); + device->ReserveFile(); + + const auto& reserved_files = StorageDevice::GetReservedFiles(); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); StorageDevice::SetReservedFiles(reserved_files); EXPECT_EQ(1, reserved_files.size()); - EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + EXPECT_TRUE(reserved_files.contains("filename")); } TEST(StorageDeviceTest, FileExists) @@ -127,17 +148,6 @@ TEST(StorageDeviceTest, FileExists) EXPECT_TRUE(StorageDevice::FileExists("/dev/null")); } -TEST(StorageDeviceTest, IsReadOnlyFile) -{ - MockStorageDevice device; - - device.SetFilename("/dev/null"); - EXPECT_FALSE(device.IsReadOnlyFile()); - - device.SetFilename("/dev/mem"); - EXPECT_TRUE(device.IsReadOnlyFile()); -} - TEST(StorageDeviceTest, GetFileSize) { MockStorageDevice device; diff --git a/cpp/test/test_setup.cpp b/cpp/test/test_setup.cpp index cfa0c20d..63dc4e29 100644 --- a/cpp/test/test_setup.cpp +++ b/cpp/test/test_setup.cpp @@ -3,35 +3,34 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include -#include "spdlog/spdlog.h" - -// Also used by the PiscsiExecutor tests -bool enable_logging; //NOSONAR Must be global in order to be shared with the tests - -class Environment final : public ::testing::Environment -{ -public: - - Environment() = default; - ~Environment() override = default; - - void SetUp() override { spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); } -}; +#include int main(int argc, char *[]) { - // If any argument is provided the log level is set to trace - enable_logging = argc > 1; + const bool disable_logging = argc <= 1; - testing::AddGlobalTestEnvironment(new Environment()); + // If any argument is provided the log level is set to trace + spdlog::set_level(disable_logging ? spdlog::level::off : spdlog::level::trace); + + int fd = -1; + if (disable_logging) { + fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + } testing::InitGoogleTest(); - return RUN_ALL_TESTS(); + const int result = RUN_ALL_TESTS(); + + if (fd != -1) { + close(fd); + } + + return result; } diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index bcce2600..0e913460 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -8,7 +8,6 @@ //--------------------------------------------------------------------------- #include "test_shared.h" -#include "controllers/controller_manager.h" #include "mocks.h" #include "shared/piscsi_exceptions.h" #include "shared/piscsi_version.h" @@ -28,38 +27,33 @@ const path test_data_temp_path(temp_directory_path() / path(fmt::format("piscsi-test-{}", getpid()))); // NOSONAR Publicly writable directory is fine here -shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension) +pair, shared_ptr> CreateDevice(PbDeviceType type, const string& extension) { DeviceFactory device_factory; + auto controller = make_shared>(0); auto device = device_factory.CreateDevice(type, 0, extension); - unordered_map params; - device->Init(params); + device->Init({}); - controller.AddDevice(device); + EXPECT_TRUE(controller->AddDevice(device)); - return device; + return { controller, device }; } -void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, - bool removable, const string& extension) +void TestInquiry::Inquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, + bool removable, const string& extension) { - auto bus = make_shared(); - auto controller_manager = make_shared(*bus); - auto controller = make_shared>(controller_manager, 0); - auto device = CreateDevice(type, *controller, extension); - - auto& cmd = controller->GetCmd(); + auto [controller, device] = CreateDevice(type, extension); // ALLOCATION LENGTH - cmd[4] = 255; + controller->SetCmdByte(4, 255); EXPECT_CALL(*controller, DataIn()); device->Dispatch(scsi_command::eCmdInquiry); const vector& buffer = controller->GetBuffer(); EXPECT_EQ(t, static_cast(buffer[0])); EXPECT_EQ(removable ? 0x80 : 0x00, buffer[1]); EXPECT_EQ(l, static_cast(buffer[2])); - EXPECT_EQ(l > scsi_level::SCSI_2 ? scsi_level::SCSI_2 : l, static_cast(buffer[3])); + EXPECT_EQ(l > scsi_level::scsi_2 ? scsi_level::scsi_2 : l, static_cast(buffer[3])); EXPECT_EQ(additional_length, buffer[4]); string product_data; if (ident.size() == 24) { @@ -88,13 +82,18 @@ pair OpenTempFile() } path CreateTempFile(int size) +{ + const auto data = vector(size); + return CreateTempFileWithData(data); +} + +path CreateTempFileWithData(const span data) { const auto [fd, filename] = OpenTempFile(); - vector data(size); const size_t count = write(fd, data.data(), data.size()); - close(fd); EXPECT_EQ(count, data.size()) << "Couldn't create temporary file '" << string(filename) << "'"; + close(fd); return path(filename); } @@ -110,14 +109,14 @@ void CreateTempFileWithData(const string& filename, vector& data) FILE* fp = fopen(new_filename.c_str(), "wb"); if (fp == nullptr) { - printf("ERROR: Unable to open file %s\n", new_filename.c_str()); + cerr << "ERROR: Unable to open file '" << new_filename << "'"; return; } if (const size_t size_written = fwrite(&data[0], sizeof(uint8_t), data.size(), fp); size_written != sizeof(vector::value_type) * data.size()) { - printf("Expected to write %zu bytes, but only wrote %zu to %s", size_written, - sizeof(vector::value_type) * data.size(), filename.c_str()); + cerr << "ERROR: Expected to write " << sizeof(vector::value_type) * data.size() << " bytes" + << ", but only wrote " << data.size() << " to '" << filename << "'"; } fclose(fp); } @@ -129,14 +128,14 @@ void DeleteTempFile(const string& filename) remove(temp_file); } -void CleanupAllTempFiles() +void CleanUpAllTempFiles() { remove_all(test_data_temp_path); } string ReadTempFileToString(const string& filename) { - path temp_file = test_data_temp_path / path(filename); + const path temp_file = test_data_temp_path / path(filename); ifstream in_fs(temp_file); stringstream buffer; buffer << in_fs.rdbuf(); diff --git a/cpp/test/test_shared.h b/cpp/test/test_shared.h index 0c4b7c6a..97d59606 100644 --- a/cpp/test/test_shared.h +++ b/cpp/test/test_shared.h @@ -3,7 +3,7 @@ // SCSI Target Emulator PiSCSI // for Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- @@ -13,6 +13,7 @@ #include "shared/scsi.h" #include #include +#include #include using namespace std; @@ -24,22 +25,27 @@ class MockAbstractController; extern const path test_data_temp_path; -shared_ptr CreateDevice(PbDeviceType, MockAbstractController&, const string& = ""); - -void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, - const string& = ""); +pair, shared_ptr> CreateDevice(PbDeviceType, const string& = ""); pair OpenTempFile(); path CreateTempFile(int); +path CreateTempFileWithData(span); // create a file with the specified data 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(); +void CleanUpAllTempFiles(); string ReadTempFileToString(const string& filename); int GetInt16(const vector&, int); uint32_t GetInt32(const vector&, int); + +// This class is needed in order to be declared as friend, required to have access to AbstractController::SetCmdByte +class TestInquiry { +public: + static void Inquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, int, bool, + const string& = ""); +}; diff --git a/doc/piscsi.1 b/doc/piscsi.1 index a40418e2..5bc58f0f 100644 --- a/doc/piscsi.1 +++ b/doc/piscsi.1 @@ -56,7 +56,7 @@ The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 5 The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'. .TP .BR \-L\fI " " \fILOG_LEVEL[:ID:[LUN]] -The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. +The piscsi log level (trace, debug, info, warning, error, off). The default log level is 'info' for all devices unless a particular device ID and an optional LUN was provided. .TP .BR \-P\fI " " \fIACCESS_TOKEN_FILE Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only. diff --git a/doc/piscsi_man_page.txt b/doc/piscsi_man_page.txt index 727a4982..4816dfb7 100644 --- a/doc/piscsi_man_page.txt +++ b/doc/piscsi_man_page.txt @@ -1,96 +1,125 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is piscsi.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is piscsi.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME piscsi - Emulates SCSI devices using the Raspberry Pi GPIO pins SYNOPSIS - piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p - PORT] [-r RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]... + piscsi [-F FOLDER] [-L LOG_LEVEL[:ID:[LUN]]] [-P ACCESS_TOKEN_FILE] [-R + SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r RE‐ + SERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] + FILE]... DESCRIPTION piscsi emulates SCSI devices using the Raspberry Pi GPIO pins. - In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be specified. The number (n) after the ID or HD iden‐ - tifier specifies the ID number for that device. The optional number (u) specifies the LUN (logical unit) for that device. - The default LUN is 0. For SCSI: The ID is limited from 0-7. However, typically SCSI ID 7 is reserved for the "initiator" - (the host computer). The LUN is limited from 0-31. + In the arguments to PiSCSI, one or more SCSI (-IDn[:u]) devices can be + specified. The number (n) after the ID or HD identifier specifies the + ID number for that device. The optional number (u) specifies the LUN + (logical unit) for that device. The default LUN is 0. For SCSI: The ID + is limited from 0-7. However, typically SCSI ID 7 is reserved for the + "initiator" (the host computer). The LUN is limited from 0-31. - PiSCSI will determine the type of device based upon the file extension of the FILE argument. + PiSCSI will determine the type of device based upon the file extension + of the FILE argument. hd1: SCSI Hard Disk image (generic, non-removable, SCSI-1) hds: SCSI Hard Disk image (generic, non-removable) hdr: SCSI Hard Disk image (generic, removable) - hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 computers) - hdi: SCSI Hard Disk image (Anex86 proprietary - only used with PC-98 computers) - nhd: SCSI Hard Disk image (T98Next proprietary - only used with PC-98 computers) - hda: SCSI Hard Disk image (Apple compatible - typically used with Macintosh computers) - mos: SCSI Magneto-Optical image (generic - typically used with NeXT, X68000, etc.) + hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 + computers) + hdi: SCSI Hard Disk image (Anex86 proprietary - only used with + PC-98 computers) + nhd: SCSI Hard Disk image (T98Next proprietary - only used with + PC-98 computers) + hda: SCSI Hard Disk image (Apple compatible - typically used with + Macintosh computers) + mos: SCSI Magneto-Optical image (generic - typically used with + NeXT, X68000, etc.) iso: SCSI CD-ROM or DVD-ROM image (ISO 9660 image) is1: SCSI CD-ROM or DVD-ROM image (ISO 9660 image, SCSI-1) - For example, if you want to specify an Apple-compatible HD image on ID 0, you can use the following command: + For example, if you want to specify an Apple-compatible HD image on ID + 0, you can use the following command: sudo piscsi -ID0 /path/to/drive/hdimage.hda - Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on the same system. + Note: PiSCSI is a fork of RaSCSI. The two cannot be run in parallel on + the same system. - Once PiSCSI starts, it will open a socket (default port is 6868) to allow external management commands. If another process - is using this port, PiSCSI will terminate, since it is likely another instance of PiSCSI or RaSCSI. Once PiSCSI has ini‐ - tialized, the scsictl utility can be used to send commands. + Once PiSCSI starts, it will open a socket (default port is 6868) to al‐ + low external management commands. If another process is using this + port, PiSCSI will terminate, since it is likely another instance of + PiSCSI or RaSCSI. Once PiSCSI has initialized, the scsictl utility can + be used to send commands. - To quit PiSCSI, press Control + C. If it is running in the background, you can kill it using an INT signal. + To quit PiSCSI, press Control + C. If it is running in the background, + you can kill it using an INT signal. OPTIONS -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + Default size is 512 bytes. -F FOLDER - The default folder for image files. For files in this folder no absolute path needs to be specified. The initial de‐ - fault folder is '~/images'. + The default folder for image files. For files in this folder no + absolute path needs to be specified. The initial default folder + is '~/images'. -L LOG_LEVEL[:ID:[LUN]] - The piscsi log level (trace, debug, info, warn, err, off). The default log level is 'info' for all devices unless a - particular device ID and an optional LUN was provided. + The piscsi log level (trace, debug, info, warning, error, off). + The default log level is 'info' for all devices unless a partic‐ + ular device ID and an optional LUN was provided. -P ACCESS_TOKEN_FILE - Enable authentication and read the access token from the specified file. The access token file must be owned by root - and must be readable by root only. + Enable authentication and read the access token from the speci‐ + fied file. The access token file must be owned by root and must + be readable by root only. -R SCAN_DEPTH - Scan for image files recursively, up to a depth of SCAN_DEPTH. Depth 0 means to ignore any folders within the de‐ - fault image filder. Be careful when using this option with many sub-folders in the default image folder. The default - depth is 1. + Scan for image files recursively, up to a depth of SCAN_DEPTH. + Depth 0 means to ignore any folders within the default image + filder. Be careful when using this option with many sub-folders + in the default image folder. The default depth is 1. -h Show a help page. -n VENDOR:PRODUCT:REVISION - Set the vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name - components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with - blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed. + Set the vendor, product and revision for the device, to be re‐ + turned with the INQUIRY data. A complete set of name components + must be provided. VENDOR may have up to 8, PRODUCT up to 16, RE‐ + VISION up to 4 characters. Padding with blanks to the maxium + length is automatically applied. Once set the name of a device + cannot be changed. -p PORT The piscsi server port, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -p TYPE The optional - case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCLP, SCHS). If no type is specified for de‐ - vices that support an image file, piscsi tries to derive the type from the file extension. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. -p TYPE The optional case-insen‐ + sitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, + SCLP, SCHS). If no type is specified for devices that support an + image file, piscsi tries to derive the type from the file exten‐ + sion. -v Display the piscsi version. -z LOCALE - Overrides the default locale for client-faces error messages. The client can override the locale. + Overrides the default locale for client-faces error messages. + The client can override the locale. -IDn[:u] FILE - n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0. + n is the SCSI ID number (0-7). u (0-31) is the optional LUN + (logical unit). The default LUN is 0. - FILE is the name of the image file to use for the SCSI device. For devices that do not support an image file (SCBR, - SCDP, SCLP, SCHS) the filename may have a special meaning or a dummy name can be provided. For SCBR and SCDP it is - an optioinal prioritized list of network interfaces, an optional IP address and netmask, e.g. "inter‐ - face=eth0,eth1,wlan0:inet=10.10.20.1/24". For SCLP it is the print command to be used and a reservation timeout in - seconds, e.g. "cmd=lp -oraw %f:timeout=60". + FILE is the name of the image file to use for the SCSI device. + For devices that do not support an image file (SCBR, SCDP, SCLP, + SCHS) the filename may have a special meaning or a dummy name + can be provided. For SCBR and SCDP it is an optioinal priori‐ + tized list of network interfaces, an optional IP address and + netmask, e.g. "interface=eth0,eth1,wlan0:inet=10.10.20.1/24". + For SCLP it is the print command to be used and a reservation + timeout in seconds, e.g. "cmd=lp -oraw %f:timeout=60". FILE is the name of the image file to use for the SCSI device. @@ -98,17 +127,20 @@ EXAMPLES Launch PiSCSI with no emulated drives attached: piscsi - Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2 + Launch PiSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID + 2 piscsi -ID0 /path/to/harddrive.hda -ID2 /path/to/cdimage.iso - Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw device file /dev/hdb (e.g. a USB stick) and a DaynaPort - network adapter as ID 6: + Launch PiSCSI with a removable SCSI drive image as ID 0 and the raw de‐ + vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter + as ID 6: piscsi -ID0 -t scrm /dev/hdb -ID6 -t scdp daynaport To create an empty, 100MiB HD image, use the following command: dd if=/dev/zero of=/path/to/newimage.hda bs=512 count=204800 - In case the fallocate command is available a much faster alternative to the dd command is: + In case the fallocate command is available a much faster alternative to + the dd command is: fallocate -l 104857600 /path/to/newimage.hda SEE ALSO @@ -116,4 +148,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/doc/scsictl.1 b/doc/scsictl.1 index 98a12f05..188c6930 100644 --- a/doc/scsictl.1 +++ b/doc/scsictl.1 @@ -60,7 +60,7 @@ Set the default image folder. Gets the list of reserved device IDs. .TP .BR \-L\fI " "\fILOG_LEVEL -Set the piscsi log level (trace, debug, info, warn, err, off). +Set the piscsi log level (trace, debug, info, warning, error, off). .TP .BR \-h\fI " " \fIHOST The piscsi host to connect to, default is 'localhost'. diff --git a/doc/scsictl_man_page.txt b/doc/scsictl_man_page.txt index 9281242c..1b5108fd 100644 --- a/doc/scsictl_man_page.txt +++ b/doc/scsictl_man_page.txt @@ -1,31 +1,32 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is scsictl.1. Re-run 'make docs' after updating - - -piscsi(1) General Commands Manual piscsi(1) +!! ------ The native file is scsictl.1. Re-run 'make docs' after updating\n\n +piscsi(1) General Commands Manual piscsi(1) NAME scsictl - Sends management commands to the piscsi process SYNOPSIS - scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IM‐ - AGE_FOLDER] [-R CURRENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] [-i ID[:LUN] [-n NAME] [-p PORT] - [-r RESERVED_IDS] [-t TYPE] [-x CURRENT_NAME:NEW_NAME] [-z LOCALE] + scsictl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V + | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR‐ + RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] + [-i ID[:LUN] [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-x CUR‐ + RENT_NAME:NEW_NAME] [-z LOCALE] DESCRIPTION - scsictl sends commands to the piscsi process to make configuration adjustments at runtime or to check the status of the de‐ - vices. + scsictl sends commands to the piscsi process to make configuration ad‐ + justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. You do NOT need root privileges to use scsictl. - Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the - tool. + Note: The command and type arguments are case insensitive. Only the + first letter of the command/type is evaluated by the tool. OPTIONS -C FILENAME:FILESIZE - Create an image file in the default image folder with the specified name and size in bytes. + Create an image file in the default image folder with the speci‐ + fied name and size in bytes. -D Detach all devices. @@ -38,22 +39,28 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the piscsi log level (trace, debug, info, warn, err, off). + Set the piscsi log level (trace, debug, info, warning, error, + off). -h HOST The piscsi host to connect to, default is 'localhost'. -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are up. + -N Lists all available network interfaces provided that they are + up. - -O Display the available piscsi server log levels and the current log level. + -O Display the available piscsi server log levels and the current + log level. - -P Prompt for the access token in case piscsi requires authentication. + -P Prompt for the access token in case piscsi requires authentica‐ + tion. - -l List all of the devices that are currently being emulated by PiSCSI, as well as their current status. + -l List all of the devices that are currently being emulated by + PiSCSI, as well as their current status. - -m List all file extensions recognized by PiSCSI and the device types they map to. + -m List all file extensions recognized by PiSCSI and the device + types they map to. -o Display operation meta data information. @@ -64,9 +71,11 @@ OPTIONS The piscsi port to connect to, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. - -s Display server-side settings like available images or supported device types. + -s Display server-side settings like available images or supported + device types. -T Display all device types and their properties. @@ -86,30 +95,36 @@ OPTIONS Overrides the default locale for client-facing error messages. -i ID[:LUN] - The SCSI ID and optional LUN that you want to control. (0-7:0-31) + The SCSI ID and optional LUN that you want to control. + (0-7:0-31) -c CMD Command is the operation being requested. Options are: a(ttach): Attach disk d(etach): Detach disk i(nsert): Insert media (removable media devices only) e(ject): Eject media (removable media devices only) - p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only) - u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only) + p(rotect): Write protect the medium (not for CD-ROMs, which + are always read-only) + u(nprotect): Remove write protection from the medium (not for + CD-ROMs, which are always read-only) s(how): Display device information eject, protect and unprotect are idempotent. -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + The default size is 512 bytes. -f FILE|PARAM - Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the piscsi(1) man - page for permitted file types. + Device-specific: Either a path to a disk image file, or a param‐ + eter for a non-disk device. See the piscsi(1) man page for per‐ + mitted file types. -t TYPE - Specifies the device type. This type overrides the type derived from the file extension of the specified image. See - the piscsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is - required): + Specifies the device type. This type overrides the type derived + from the file extension of the specified image. See the + piscsi(1) man page for the available device types. For some + types there are shortcuts (only the first letter is required): hd: SCSI hard disk drive rm: SCSI removable media drive cd: CD-ROM @@ -120,13 +135,17 @@ OPTIONS services: Host services device -n VENDOR:PRODUCT:REVISION - The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name compo‐ - nents must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks - to the maxium length is automatically applied. Once set the name of a device cannot be changed. + The vendor, product and revision for the device, to be returned + with the INQUIRY data. A complete set of name components must be + provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up + to 4 characters. Padding with blanks to the maxium length is au‐ + tomatically applied. Once set the name of a device cannot be + changed. -u UNIT - Unit number (0-31). This will default to 0. This option is only used when there are multiple SCSI devices on a - shared SCSI controller. (This is not common) + Unit number (0-31). This will default to 0. This option is only + used when there are multiple SCSI devices on a shared SCSI con‐ + troller. (This is not common) EXAMPLES Show a listing of all of the SCSI devices and their current status. @@ -139,8 +158,8 @@ EXAMPLES | 0 | 1 | SCHD | /home/pi/harddisk.hda +----+-----+------+------------------------------------- - Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM‐ - AGE0.HDS". + Request the PiSCSI process to attach a disk (assumed) to SCSI ID 0 with + the contents of the file system image "HDIIMAGE0.HDS". scsictl -i 0 -f HDIIMAGE0.HDS SEE ALSO @@ -148,4 +167,4 @@ SEE ALSO Full documentation is available at: - piscsi(1) + piscsi(1) diff --git a/docker/README.md b/docker/README.md index afb007a3..736ac6ef 100644 --- a/docker/README.md +++ b/docker/README.md @@ -36,7 +36,7 @@ The following environment variables are available when using Docker Compose: | Environment Variable | Default | | -------------------- |----------| -| `OS_VERSION` | buster | +| `OS_VERSION` | bullseye | | `WEB_HTTP_PORT` | 8080 | | `WEB_HTTPS_PORT` | 8443 | | `WEB_LOG_LEVEL` | info | diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4ae30d27..e3ea35a2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -21,13 +21,13 @@ services: web: container_name: piscsi_web - image: piscsi-web:${OS_VERSION:-buster} + image: piscsi-web:${OS_VERSION:-bullseye} pull_policy: never build: context: .. dockerfile: docker/web/Dockerfile args: - - OS_VERSION=${OS_VERSION:-buster} + - OS_VERSION=${OS_VERSION:-bullseye} volumes: - ./volumes/images:/home/pi/images:delegated - ./volumes/config:/home/pi/.config/piscsi:delegated diff --git a/docker/web/Dockerfile b/docker/web/Dockerfile index 4d235eca..1d74a423 100644 --- a/docker/web/Dockerfile +++ b/docker/web/Dockerfile @@ -1,5 +1,5 @@ ARG DEBIAN_FRONTEND=noninteractive -ARG OS_VERSION=buster +ARG OS_VERSION=bullseye FROM "debian:${OS_VERSION}-slim" diff --git a/easyinstall.sh b/easyinstall.sh index 28da5f8a..7e1754fe 100755 --- a/easyinstall.sh +++ b/easyinstall.sh @@ -47,14 +47,13 @@ echo -e $logo } CONNECT_TYPE="FULLSPEC" -# clang v11 is the latest distributed by Buster -COMPILER="clang++-11" +COMPILER="clang++" MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}') CORES=`expr $MEM / 450000` if [ $CORES -gt $(nproc) ]; then - CORES=$(nproc) + CORES=$(nproc) elif [ $CORES -lt 1 ]; then - CORES=1 + CORES=1 fi USER=$(whoami) BASE=$(dirname "$(readlink -f "${0}")") @@ -78,7 +77,7 @@ FILE_SHARE_PATH="$HOME/shared_files" FILE_SHARE_NAME="Pi File Server" APT_PACKAGES_COMMON="build-essential git protobuf-compiler bridge-utils" -APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang-11" +APT_PACKAGES_BACKEND="libspdlog-dev libpcap-dev libprotobuf-dev protobuf-compiler libgmock-dev clang" APT_PACKAGES_PYTHON="python3 python3-dev python3-pip python3-venv python3-setuptools python3-wheel libev-dev libevdev2" APT_PACKAGES_WEB="nginx-light genisoimage man2html hfsutils dosfstools kpartx unzip unar disktype gettext"