diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index e525146c..00b3718f 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -56,5 +56,5 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="tests/*" --define sonar.python.version=3 + cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="**/test/**" --define sonar.cpd.exclusions="**test/**" --define sonar.python.version=3 diff --git a/src/raspberrypi/Makefile b/src/raspberrypi/Makefile index 55627446..6eadeacf 100644 --- a/src/raspberrypi/Makefile +++ b/src/raspberrypi/Makefile @@ -92,16 +92,18 @@ SRC_RASCSI_CORE = \ rascsi_version.cpp \ rascsi_image.cpp \ rascsi_response.cpp \ + rascsi_executor.cpp \ rasutil.cpp \ command_util.cpp \ - socket_connector.cpp \ + protobuf_serializer.cpp \ localizer.cpp SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp') SRC_RASCSI_CORE += $(SRC_PROTOBUF) -SRC_RASCSI = rascsi.cpp +SRC_RASCSI = rascsi.cpp \ + rascsi_service.cpp SRC_SCSIMON = \ scsimon.cpp \ @@ -117,7 +119,7 @@ SRC_RASCTL = \ rascsi_version.cpp \ rasutil.cpp \ command_util.cpp \ - socket_connector.cpp \ + protobuf_serializer.cpp \ localizer.cpp SRC_RASCTL += $(SRC_PROTOBUF) diff --git a/src/raspberrypi/bus.h b/src/raspberrypi/bus.h new file mode 100644 index 00000000..8310c5c4 --- /dev/null +++ b/src/raspberrypi/bus.h @@ -0,0 +1,113 @@ +//--------------------------------------------------------------------------- +// +// X68000 EMULATOR "XM6" +// +// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) +// Copyright (C) 2014-2020 GIMONS +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "os.h" +#include "scsi.h" +#include +#include + +using namespace std; + +class BUS +{ +public: + // Operation modes definition + enum class mode_e { + TARGET = 0, + INITIATOR = 1, + MONITOR = 2, + }; + + // Phase definitions + enum class phase_t : int { + busfree, + arbitration, + selection, + reselection, + command, + datain, + dataout, + status, + msgin, + msgout, + reserved + }; + + BUS() = default; + virtual ~BUS() = default; + + // Basic Functions + virtual bool Init(mode_e mode) = 0; + virtual void Reset() = 0; + virtual void Cleanup() = 0; + phase_t GetPhase(); + + static phase_t GetPhase(int mci) + { + return phase_table[mci]; + } + + // Get the string phase name, based upon the raw data + static const char* GetPhaseStrRaw(phase_t current_phase); + + // Extract as specific pin field from a raw data capture + static inline DWORD GetPinRaw(DWORD raw_data, DWORD pin_num) + { + return ((raw_data >> pin_num) & 1); + } + + virtual bool GetBSY() const = 0; + virtual void SetBSY(bool ast) = 0; + + virtual bool GetSEL() const = 0; + virtual void SetSEL(bool ast) = 0; + + virtual bool GetATN() const = 0; + virtual void SetATN(bool ast) = 0; + + virtual bool GetACK() const = 0; + virtual void SetACK(bool ast) = 0; + + virtual bool GetRST() const = 0; + virtual void SetRST(bool ast) = 0; + + virtual bool GetMSG() const = 0; + virtual void SetMSG(bool ast) = 0; + + virtual bool GetCD() const = 0; + virtual void SetCD(bool ast) = 0; + + virtual bool GetIO() = 0; + virtual void SetIO(bool ast) = 0; + + virtual bool GetREQ() const = 0; + virtual void SetREQ(bool ast) = 0; + + virtual BYTE GetDAT() = 0; + virtual void SetDAT(BYTE dat) = 0; + virtual bool GetDP() const = 0; // Get parity signal + + virtual uint32_t Acquire() = 0; + virtual int CommandHandShake(BYTE *buf) = 0; + virtual int ReceiveHandShake(BYTE *buf, int count) = 0; + virtual int SendHandShake(BYTE *buf, int count, int delay_after_bytes) = 0; + + virtual bool GetSignal(int pin) const = 0; + // Get SCSI input signal value + virtual void SetSignal(int pin, bool ast) = 0; + // Set SCSI output signal value + static const int SEND_NO_DELAY = -1; + // Passed into SendHandShake when we don't want to delay +private: + static const array phase_table; + + static const unordered_map phase_str_mapping; +}; diff --git a/src/raspberrypi/command_context.h b/src/raspberrypi/command_context.h index 7f613dc1..ad3add93 100644 --- a/src/raspberrypi/command_context.h +++ b/src/raspberrypi/command_context.h @@ -3,7 +3,7 @@ // SCSI Target Emulator RaSCSI Reloaded // for Raspberry Pi // -// Copyright (C) 2021 Uwe Seimet +// Copyright (C) 2021-2022 Uwe Seimet // //--------------------------------------------------------------------------- @@ -11,16 +11,18 @@ #include -class SocketConnector; +class ProtobufSerializer; class Localizer; -struct CommandContext +class CommandContext { - CommandContext(const SocketConnector& c, const Localizer& l, int f, const std::string& s) - : connector(c), localizer(l), fd(f), locale(s) {} +public: + + CommandContext(const ProtobufSerializer& c, const Localizer& l, int f, const std::string& s) + : serializer(c), localizer(l), fd(f), locale(s) {} ~CommandContext() = default; - const SocketConnector& connector; + const ProtobufSerializer& serializer; const Localizer& localizer; int fd; std::string locale; diff --git a/src/raspberrypi/command_util.cpp b/src/raspberrypi/command_util.cpp index ca9884be..8c6bbf01 100644 --- a/src/raspberrypi/command_util.cpp +++ b/src/raspberrypi/command_util.cpp @@ -10,7 +10,7 @@ #include "log.h" #include "rascsi_interface.pb.h" #include "localizer.h" -#include "socket_connector.h" +#include "protobuf_serializer.h" #include "command_util.h" #include @@ -129,7 +129,7 @@ bool command_util::ReturnStatus(const CommandContext& context, bool status, cons result.set_status(status); result.set_error_code(error_code); result.set_msg(msg); - context.connector.SerializeMessage(context.fd, result); + context.serializer.SerializeMessage(context.fd, result); } return status; diff --git a/src/raspberrypi/command_util.h b/src/raspberrypi/command_util.h index 91327d11..1f87c8aa 100644 --- a/src/raspberrypi/command_util.h +++ b/src/raspberrypi/command_util.h @@ -17,7 +17,7 @@ #include "localizer.h" #include -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace rascsi_interface; namespace command_util { diff --git a/src/raspberrypi/config.h b/src/raspberrypi/config.h index ae740ac5..c25566ec 100644 --- a/src/raspberrypi/config.h +++ b/src/raspberrypi/config.h @@ -20,6 +20,6 @@ #define USE_SEL_EVENT_ENABLE // Check SEL signal by event // This avoids an indefinite loop with warnings if there is no RaSCSI hardware // and thus helps with running rasctl and unit test on x86 hardware. -#if defined(__x86_64__) || defined(__X86__) || !defined(__linux) +#if defined(__x86_64__) || defined(__X86__) || !defined(__linux__) #undef USE_SEL_EVENT_ENABLE #endif diff --git a/src/raspberrypi/controllers/abstract_controller.cpp b/src/raspberrypi/controllers/abstract_controller.cpp index ba0a1d33..bb1065df 100644 --- a/src/raspberrypi/controllers/abstract_controller.cpp +++ b/src/raspberrypi/controllers/abstract_controller.cpp @@ -7,8 +7,9 @@ // //--------------------------------------------------------------------------- -#include "abstract_controller.h" +#include "rascsi_exceptions.h" #include "devices/primary_device.h" +#include "abstract_controller.h" void AbstractController::AllocateBuffer(size_t size) { @@ -17,7 +18,18 @@ void AbstractController::AllocateBuffer(size_t size) } } -PrimaryDevice *AbstractController::GetDeviceForLun(int lun) const { +unordered_set> AbstractController::GetDevices() const +{ + unordered_set> devices; + + for (const auto& [id, lun] : luns) { + devices.insert(lun); + } + + return devices; +} + +shared_ptr AbstractController::GetDeviceForLun(int lun) const { const auto& it = luns.find(lun); return it == luns.end() ? nullptr : it->second; } @@ -26,7 +38,7 @@ void AbstractController::Reset() { SetPhase(BUS::phase_t::busfree); - ctrl.status = 0x00; + ctrl.status = status::GOOD; ctrl.message = 0x00; ctrl.blocks = 0; ctrl.next = 0; @@ -75,18 +87,15 @@ void AbstractController::ProcessPhase() break; default: - assert(false); + LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase())) + throw scsi_error_exception(); break; } } -bool AbstractController::AddDevice(PrimaryDevice *device) +bool AbstractController::AddDevice(shared_ptr device) { - if (device->GetLun() >= GetMaxLuns()) { - return false; - } - - if (HasDeviceForLun(device->GetLun())) { + if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) { return false; } @@ -96,7 +105,7 @@ bool AbstractController::AddDevice(PrimaryDevice *device) return true; } -bool AbstractController::DeleteDevice(const PrimaryDevice *device) +bool AbstractController::DeleteDevice(const shared_ptr device) { return luns.erase(device->GetLun()) == 1; } diff --git a/src/raspberrypi/controllers/abstract_controller.h b/src/raspberrypi/controllers/abstract_controller.h index e0bb7df7..4692d0d7 100644 --- a/src/raspberrypi/controllers/abstract_controller.h +++ b/src/raspberrypi/controllers/abstract_controller.h @@ -11,24 +11,24 @@ #pragma once -#include "scsi.h" +#include "bus.h" +#include "phase_handler.h" +#include #include #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class PrimaryDevice; -class AbstractController +class AbstractController : public PhaseHandler { friend class PrimaryDevice; friend class ScsiController; - BUS::phase_t phase = BUS::phase_t::busfree; - - // Logical units of this device controller mapped to their LUN numbers - unordered_map luns; + // Logical units of this controller mapped to their LUN numbers + unordered_map> luns; public: @@ -41,7 +41,7 @@ public: using ctrl_t = struct _ctrl_t { vector cmd; // Command data, dynamically allocated per received command - uint32_t status; // Status data + scsi_defs::status status; // Status data int message; // Message data // Transfer @@ -52,21 +52,8 @@ public: uint32_t length; // Transfer remaining length }; - AbstractController(shared_ptr bus, int target_id, int luns) : target_id(target_id), bus(bus), max_luns(luns) {} - virtual ~AbstractController() = default; - AbstractController(AbstractController&) = delete; - AbstractController& operator=(const AbstractController&) = delete; - - virtual void BusFree() = 0; - virtual void Selection() = 0; - virtual void Command() = 0; - virtual void Status() = 0; - virtual void DataIn() = 0; - virtual void DataOut() = 0; - virtual void MsgIn() = 0; - virtual void MsgOut() = 0; - - virtual BUS::phase_t Process(int) = 0; + AbstractController(BUS& bus, int target_id, int max_luns) : target_id(target_id), bus(bus), max_luns(max_luns) {} + ~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; @@ -81,19 +68,19 @@ public: int GetTargetId() const { return target_id; } int GetMaxLuns() const { return max_luns; } - bool HasLuns() const { return !luns.empty(); } + int GetLunCount() const { return (int)luns.size(); } - PrimaryDevice *GetDeviceForLun(int) const; - bool AddDevice(PrimaryDevice *); - bool DeleteDevice(const PrimaryDevice *); + unordered_set> GetDevices() const; + shared_ptr GetDeviceForLun(int) const; + bool AddDevice(shared_ptr); + bool DeleteDevice(const shared_ptr); bool HasDeviceForLun(int) const; - int ExtractInitiatorId(int id_data) const; + int ExtractInitiatorId(int) const; void AllocateBuffer(size_t); vector& GetBuffer() { return ctrl.buffer; } - size_t GetBufferSize() const { return ctrl.buffer.size(); } - uint32_t GetStatus() const { return ctrl.status; } - void SetStatus(uint32_t s) { ctrl.status = s; } + scsi_defs::status GetStatus() const { return ctrl.status; } + void SetStatus(scsi_defs::status s) { ctrl.status = s; } uint32_t GetLength() const { return ctrl.length; } protected: @@ -106,25 +93,15 @@ protected: vector& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; } 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; } - BUS::phase_t GetPhase() const { return phase; } - void SetPhase(BUS::phase_t p) { phase = p; } - bool IsSelection() const { return phase == BUS::phase_t::selection; } - bool IsBusFree() const { return phase == BUS::phase_t::busfree; } - bool IsCommand() const { return phase == BUS::phase_t::command; } - bool IsStatus() const { return phase == BUS::phase_t::status; } - bool IsDataIn() const { return phase == BUS::phase_t::datain; } - bool IsDataOut() const { return phase == BUS::phase_t::dataout; } - bool IsMsgIn() const { return phase == BUS::phase_t::msgin; } - bool IsMsgOut() const { return phase == BUS::phase_t::msgout; } - private: int target_id; - shared_ptr bus; + BUS& bus; int max_luns; diff --git a/src/raspberrypi/controllers/controller_manager.cpp b/src/raspberrypi/controllers/controller_manager.cpp index abf5496d..712ad6f8 100644 --- a/src/raspberrypi/controllers/controller_manager.cpp +++ b/src/raspberrypi/controllers/controller_manager.cpp @@ -15,17 +15,22 @@ using namespace std; -bool ControllerManager::CreateScsiController(shared_ptr bus, PrimaryDevice *device) +bool ControllerManager::AttachToScsiController(int id, shared_ptr device) { - shared_ptr controller = FindController(device->GetId()); + auto controller = FindController(id); if (controller == nullptr) { - controller = make_shared(bus, device->GetId()); - controllers[device->GetId()] = controller; + controller = make_shared(bus, id); + controllers[id] = controller; } return controller->AddDevice(device); } +void ControllerManager::DeleteController(shared_ptr controller) +{ + controllers.erase(controller->GetTargetId()); +} + shared_ptr ControllerManager::IdentifyController(int data) const { for (const auto& [id, controller] : controllers) { @@ -43,6 +48,18 @@ shared_ptr ControllerManager::FindController(int target_id) return it == controllers.end() ? nullptr : it->second; } +unordered_set> ControllerManager::GetAllDevices() const +{ + unordered_set> devices; + + for (const auto& [id, controller] : controllers) { + auto d = controller->GetDevices(); + devices.insert(d.begin(), d.end()); + } + + return devices; +} + void ControllerManager::DeleteAllControllers() { controllers.clear(); @@ -55,7 +72,7 @@ void ControllerManager::ResetAllControllers() const } } -PrimaryDevice *ControllerManager::GetDeviceByIdAndLun(int id, int lun) const +shared_ptr ControllerManager::GetDeviceByIdAndLun(int id, int lun) const { if (const auto controller = FindController(id); controller != nullptr) { return controller->GetDeviceForLun(lun); diff --git a/src/raspberrypi/controllers/controller_manager.h b/src/raspberrypi/controllers/controller_manager.h index 88787856..16e5459c 100644 --- a/src/raspberrypi/controllers/controller_manager.h +++ b/src/raspberrypi/controllers/controller_manager.h @@ -12,9 +12,10 @@ #pragma once #include +#include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class BUS; class AbstractController; @@ -22,23 +23,24 @@ class PrimaryDevice; class ControllerManager { + BUS& bus; + + unordered_map> controllers; public: - ControllerManager() = default; + explicit ControllerManager(BUS& bus) : bus(bus) {} ~ControllerManager() = default; - ControllerManager(ControllerManager&) = delete; - ControllerManager& operator=(const ControllerManager&) = delete; // Maximum number of controller devices static const int DEVICE_MAX = 8; - bool CreateScsiController(shared_ptr, PrimaryDevice *); + bool AttachToScsiController(int, shared_ptr); + void DeleteController(shared_ptr); shared_ptr IdentifyController(int) const; shared_ptr FindController(int) const; + unordered_set> GetAllDevices() const; void DeleteAllControllers(); void ResetAllControllers() const; - PrimaryDevice *GetDeviceByIdAndLun(int, int) const; - - unordered_map> controllers; + shared_ptr GetDeviceByIdAndLun(int, int) const; }; diff --git a/src/raspberrypi/controllers/phase_handler.h b/src/raspberrypi/controllers/phase_handler.h new file mode 100644 index 00000000..3cdd4606 --- /dev/null +++ b/src/raspberrypi/controllers/phase_handler.h @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "scsi.h" + +class PhaseHandler +{ + BUS::phase_t phase = BUS::phase_t::busfree; + +public: + + PhaseHandler() = default; + virtual ~PhaseHandler() = default; + + virtual void BusFree() = 0; + virtual void Selection() = 0; + virtual void Command() = 0; + virtual void Status() = 0; + virtual void DataIn() = 0; + virtual void DataOut() = 0; + virtual void MsgIn() = 0; + virtual void MsgOut() = 0; + + virtual BUS::phase_t Process(int) = 0; + +protected: + + BUS::phase_t GetPhase() const { return phase; } + void SetPhase(BUS::phase_t p) { phase = p; } + bool IsSelection() const { return phase == BUS::phase_t::selection; } + bool IsBusFree() const { return phase == BUS::phase_t::busfree; } + bool IsCommand() const { return phase == BUS::phase_t::command; } + bool IsStatus() const { return phase == BUS::phase_t::status; } + bool IsDataIn() const { return phase == BUS::phase_t::datain; } + bool IsDataOut() const { return phase == BUS::phase_t::dataout; } + bool IsMsgIn() const { return phase == BUS::phase_t::msgin; } + bool IsMsgOut() const { return phase == BUS::phase_t::msgout; } +}; diff --git a/src/raspberrypi/controllers/scsi_controller.cpp b/src/raspberrypi/controllers/scsi_controller.cpp index c1d1167b..63d171db 100644 --- a/src/raspberrypi/controllers/scsi_controller.cpp +++ b/src/raspberrypi/controllers/scsi_controller.cpp @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -21,13 +21,13 @@ #include "scsi_controller.h" #include #include -#ifdef __linux +#ifdef __linux__ #include #endif using namespace scsi_defs; -ScsiController::ScsiController(shared_ptr bus, int target_id) : AbstractController(bus, target_id, LUN_MAX) +ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, LUN_MAX) { // The initial buffer size will default to either the default buffer size OR // the size of an Ethernet message, whichever is larger. @@ -52,17 +52,17 @@ void ScsiController::Reset() BUS::phase_t ScsiController::Process(int id) { // Get bus information - bus->Acquire(); + bus.Acquire(); // Check to see if the reset signal was asserted - if (bus->GetRST()) { + if (bus.GetRST()) { LOGWARN("RESET signal received!") // Reset the controller Reset(); // Reset the bus - bus->Reset(); + bus.Reset(); return GetPhase(); } @@ -86,7 +86,7 @@ BUS::phase_t ScsiController::Process(int id) LOGERROR("%s Unhandled SCSI error, resetting controller and bus and entering bus free phase", __PRETTY_FUNCTION__) Reset(); - bus->Reset(); + bus.Reset(); BusFree(); } @@ -101,14 +101,14 @@ void ScsiController::BusFree() SetPhase(BUS::phase_t::busfree); - bus->SetREQ(false); - bus->SetMSG(false); - bus->SetCD(false); - bus->SetIO(false); - bus->SetBSY(false); + bus.SetREQ(false); + bus.SetMSG(false); + bus.SetCD(false); + bus.SetIO(false); + bus.SetBSY(false); // Initialize status and message - SetStatus(0); + SetStatus(status::GOOD); ctrl.message = 0x00; // Initialize ATN message reception status @@ -120,21 +120,21 @@ void ScsiController::BusFree() bytes_to_transfer = 0; // When the bus is free RaSCSI or the Pi may be shut down. - // TODO Try to find a better place for this code without breaking encapsulation + // This code has to be executed in the bus free phase and thus has to be located in the controller. switch(shutdown_mode) { - case AbstractController::rascsi_shutdown_mode::STOP_RASCSI: + case rascsi_shutdown_mode::STOP_RASCSI: LOGINFO("RaSCSI shutdown requested") exit(0); break; - case AbstractController::rascsi_shutdown_mode::STOP_PI: + case rascsi_shutdown_mode::STOP_PI: LOGINFO("Raspberry Pi shutdown requested") if (system("init 0") == -1) { LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno)) } break; - case AbstractController::rascsi_shutdown_mode::RESTART_PI: + case rascsi_shutdown_mode::RESTART_PI: LOGINFO("Raspberry Pi restart requested") if (system("init 6") == -1) { LOGERROR("Raspberry Pi restart failed: %s", strerror(errno)) @@ -149,7 +149,7 @@ void ScsiController::BusFree() } // Move to selection phase - if (bus->GetSEL() && !bus->GetBSY()) { + if (bus.GetSEL() && !bus.GetBSY()) { Selection(); } } @@ -158,12 +158,12 @@ void ScsiController::Selection() { if (!IsSelection()) { // A different device controller was selected - if (int id = 1 << GetTargetId(); ((int)bus->GetDAT() & id) == 0) { + if (int id = 1 << GetTargetId(); ((int)bus.GetDAT() & id) == 0) { return; } // Abort if there is no LUN for this controller - if (!HasLuns()) { + if (!GetLunCount()) { return; } @@ -172,14 +172,14 @@ void ScsiController::Selection() SetPhase(BUS::phase_t::selection); // Raise BSY and respond - bus->SetBSY(true); + bus.SetBSY(true); return; } // Selection completed - if (!bus->GetSEL() && bus->GetBSY()) { + if (!bus.GetSEL() && bus.GetBSY()) { // Message out phase if ATN=1, otherwise command phase - if (bus->GetATN()) { + if (bus.GetATN()) { MsgOut(); } else { Command(); @@ -194,11 +194,11 @@ void ScsiController::Command() SetPhase(BUS::phase_t::command); - bus->SetMSG(false); - bus->SetCD(true); - bus->SetIO(false); + bus.SetMSG(false); + bus.SetCD(true); + bus.SetIO(false); - int actual_count = bus->CommandHandShake(GetBuffer().data()); + int actual_count = bus.CommandHandShake(GetBuffer().data()); int command_byte_count = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); // If not able to receive all, move to the status phase @@ -229,8 +229,6 @@ void ScsiController::Execute() { LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode()) - SetPhase(BUS::phase_t::execute); - // Initialization for data transfer ResetOffset(); ctrl.blocks = 1; @@ -238,7 +236,7 @@ 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(0); + SetStatus(status::GOOD); } int lun = GetEffectiveLun(); @@ -258,13 +256,13 @@ void ScsiController::Execute() } } - PrimaryDevice *device = GetDeviceForLun(lun); + auto device = GetDeviceForLun(lun); // Discard pending sense data from the previous command if the current command is not REQUEST SENSE if (GetOpcode() != scsi_command::eCmdRequestSense) { device->SetStatusCode(0); } - + try { if (!device->Dispatch(GetOpcode())) { LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode()) @@ -298,14 +296,14 @@ void ScsiController::Status() SysTimer::SleepUsec(5); } - LOGTRACE("%s Status Phase $%02X",__PRETTY_FUNCTION__, GetStatus()) + LOGTRACE("%s Status Phase, status is $%02X",__PRETTY_FUNCTION__, (int)GetStatus()) SetPhase(BUS::phase_t::status); // Signal line operated by the target - bus->SetMSG(false); - bus->SetCD(true); - bus->SetIO(true); + bus.SetMSG(false); + bus.SetCD(true); + bus.SetIO(true); // Data transfer is 1 byte x 1 block ResetOffset(); @@ -326,9 +324,9 @@ void ScsiController::MsgIn() SetPhase(BUS::phase_t::msgin); - bus->SetMSG(true); - bus->SetCD(true); - bus->SetIO(true); + bus.SetMSG(true); + bus.SetCD(true); + bus.SetIO(true); // length, blocks are already set assert(HasValidLength()); @@ -357,9 +355,9 @@ void ScsiController::MsgOut() SetPhase(BUS::phase_t::msgout); - bus->SetMSG(true); - bus->SetCD(true); - bus->SetIO(false); + bus.SetMSG(true); + bus.SetCD(true); + bus.SetIO(false); // Data transfer is 1 byte x 1 block ResetOffset(); @@ -390,9 +388,9 @@ void ScsiController::DataIn() SetPhase(BUS::phase_t::datain); - bus->SetMSG(false); - bus->SetCD(false); - bus->SetIO(true); + bus.SetMSG(false); + bus.SetCD(false); + bus.SetIO(true); // length, blocks are already set assert(ctrl.blocks > 0); @@ -423,9 +421,9 @@ void ScsiController::DataOut() SetPhase(BUS::phase_t::dataout); // Signal line operated by the target - bus->SetMSG(false); - bus->SetCD(false); - bus->SetIO(false); + bus.SetMSG(false); + bus.SetCD(false); + bus.SetIO(false); ResetOffset(); return; @@ -437,12 +435,12 @@ void ScsiController::DataOut() void ScsiController::Error(sense_key sense_key, asc asc, status status) { // Get bus information - bus->Acquire(); + bus.Acquire(); // Reset check - if (bus->GetRST()) { + if (bus.GetRST()) { Reset(); - bus->Reset(); + bus.Reset(); return; } @@ -461,11 +459,14 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) } if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) { - // Set Sense Key and ASC for a subsequent REQUEST SENSE + LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X", + (int)sense_key << 16, (int)asc << 8, (int)asc & 0xff) + + // Set Sense Key and ASC for a subsequent REQUEST SENSE GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8)); } - SetStatus((uint32_t)status); + SetStatus(status); ctrl.message = 0x00; LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__) @@ -475,16 +476,16 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) void ScsiController::Send() { - assert(!bus->GetREQ()); - assert(bus->GetIO()); + assert(!bus.GetREQ()); + assert(bus.GetIO()); if (HasValidLength()) { - LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length " + LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(GetOffset()) + ", length " + to_string(ctrl.length)).c_str()) // TODO The delay has to be taken from ctrl.unit[lun], but as there are currently no Daynaport drivers for // LUNs other than 0 this work-around works. - if (int len = bus->SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length, + if (int len = bus.SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length, HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0); len != (int)ctrl.length) { // If you cannot send all, move to status phase @@ -571,15 +572,15 @@ void ScsiController::Receive() LOGTRACE("%s",__PRETTY_FUNCTION__) // REQ is low - assert(!bus->GetREQ()); - assert(!bus->GetIO()); + assert(!bus.GetREQ()); + assert(!bus.GetIO()); // Length != 0 if received if (HasValidLength()) { LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length) // If not able to receive all, move to status phase - if (int len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length); + if (int len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length); len != (int)ctrl.length) { LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len) Error(sense_key::ABORTED_COMMAND); @@ -675,14 +676,14 @@ bool ScsiController::XferMsg(int msg) void ScsiController::ReceiveBytes() { - assert(!bus->GetREQ()); - assert(!bus->GetIO()); + assert(!bus.GetREQ()); + assert(!bus.GetIO()); if (HasValidLength()) { LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length) // If not able to receive all, move to status phase - if (uint32_t len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length); + if (uint32_t len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length); len != ctrl.length) { LOGERROR("%s Not able to receive %d bytes of data, only received %d", __PRETTY_FUNCTION__, ctrl.length, len) @@ -773,7 +774,7 @@ void ScsiController::FlushUnit() { assert(IsDataOut()); - auto disk = dynamic_cast(GetDeviceForLun(GetEffectiveLun())); + auto disk = dynamic_pointer_cast(GetDeviceForLun(GetEffectiveLun())); if (disk == nullptr) { return; } @@ -795,7 +796,7 @@ void ScsiController::FlushUnit() // Without it we would not need this method at all. // ModeSelect is already handled in XferOutBlockOriented(). Why would it have to be handled once more? try { - disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset); + disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset()); } catch(const scsi_error_exception& e) { LOGWARN("Error occured while processing Mode Select command %02X\n", (int)GetOpcode()) @@ -838,7 +839,7 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead16: // Read from disk try { - ctrl.length = (static_cast(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next); + ctrl.length = (dynamic_pointer_cast(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next); } catch(const scsi_error_exception&) { // If there is an error, go to the status phase @@ -868,7 +869,7 @@ bool ScsiController::XferIn(vector& buf) //--------------------------------------------------------------------------- bool ScsiController::XferOutBlockOriented(bool cont) { - auto disk = dynamic_cast(GetDeviceForLun(GetEffectiveLun())); + auto disk = dynamic_pointer_cast(GetDeviceForLun(GetEffectiveLun())); if (disk == nullptr) { return false; } @@ -878,7 +879,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdModeSelect6: case scsi_command::eCmdModeSelect10: try { - disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset); + disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset()); } catch(const scsi_error_exception& e) { Error(e.get_sense_key(), e.get_asc(), e.get_status()); @@ -895,7 +896,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) { // Special case Write function for brige // TODO This class must not know about SCSIBR - if (auto bridge = dynamic_cast(disk); bridge) { + if (auto bridge = dynamic_pointer_cast(disk); bridge) { if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) { // Write failed return false; @@ -907,7 +908,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) // Special case Write function for DaynaPort // TODO This class must not know about DaynaPort - if (auto daynaport = dynamic_cast(disk); daynaport) { + if (auto daynaport = dynamic_pointer_cast(disk); daynaport) { daynaport->WriteBytes(ctrl.cmd, GetBuffer(), 0); ResetOffset(); @@ -961,9 +962,10 @@ void ScsiController::ProcessCommand() uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); stringstream s; + s << setfill('0') << setw(2) << hex; for (uint32_t i = 0; i < len; i++) { ctrl.cmd[i] = GetBuffer()[i]; - s << setfill('0') << setw(2) << hex << ctrl.cmd[i]; + s << ctrl.cmd[i]; } LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str()) @@ -1036,7 +1038,7 @@ void ScsiController::ParseMessage() void ScsiController::ProcessMessage() { // Continue message out phase as long as ATN keeps asserting - if (bus->GetATN()) { + if (bus.GetATN()) { // Data transfer is 1 byte x 1 block ResetOffset(); ctrl.length = 1; diff --git a/src/raspberrypi/controllers/scsi_controller.h b/src/raspberrypi/controllers/scsi_controller.h index 47cf3d22..d12bd98a 100644 --- a/src/raspberrypi/controllers/scsi_controller.h +++ b/src/raspberrypi/controllers/scsi_controller.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -54,10 +54,8 @@ public: // Maximum number of logical units static const int LUN_MAX = 32; - ScsiController(shared_ptr, int); + ScsiController(BUS&, int); ~ScsiController() override = default; - ScsiController(ScsiController&) = delete; - ScsiController& operator=(const ScsiController&) = delete; void Reset() override; diff --git a/src/raspberrypi/devices/cd_track.h b/src/raspberrypi/devices/cd_track.h index 56e9fd21..20660c4f 100644 --- a/src/raspberrypi/devices/cd_track.h +++ b/src/raspberrypi/devices/cd_track.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // //--------------------------------------------------------------------------- @@ -22,8 +22,6 @@ public: CDTrack() = default; ~CDTrack() = default; - CDTrack(CDTrack&) = delete; - CDTrack& operator=(const CDTrack&) = delete; void Init(int track, DWORD first, DWORD last); diff --git a/src/raspberrypi/devices/cfilesystem.cpp b/src/raspberrypi/devices/cfilesystem.cpp index d60215d1..be8673bc 100644 --- a/src/raspberrypi/devices/cfilesystem.cpp +++ b/src/raspberrypi/devices/cfilesystem.cpp @@ -2381,18 +2381,16 @@ bool CHostFcb::Truncate() const /// Return -1 if error is thrown. // //--------------------------------------------------------------------------- -DWORD CHostFcb::Seek(DWORD nOffset, DWORD nHumanSeek) +DWORD CHostFcb::Seek(DWORD nOffset, Human68k::seek_t nHumanSeek) { - assert(nHumanSeek == Human68k::SK_BEGIN || - nHumanSeek == Human68k::SK_CURRENT || nHumanSeek == Human68k::SK_END); assert(m_pFile); int nSeek; switch (nHumanSeek) { - case Human68k::SK_BEGIN: + case Human68k::seek_t::SK_BEGIN: nSeek = SEEK_SET; break; - case Human68k::SK_CURRENT: + case Human68k::seek_t::SK_CURRENT: nSeek = SEEK_CUR; break; // case SK_END: @@ -3356,13 +3354,13 @@ int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset) return FS_NOTOPENED; // Parameter check - if (nSeek > Human68k::SK_END) { + if (nSeek > (DWORD)Human68k::seek_t::SK_END) { m_cFcb.Free(pHostFcb); return FS_INVALIDPRM; } // File seek - DWORD nResult = pHostFcb->Seek(nOffset, nSeek); + DWORD nResult = pHostFcb->Seek(nOffset, (Human68k::seek_t)nSeek); if (nResult == (DWORD)-1) { m_cFcb.Free(pHostFcb); return FS_CANTSEEK; diff --git a/src/raspberrypi/devices/cfilesystem.h b/src/raspberrypi/devices/cfilesystem.h index 38954d60..d60353be 100644 --- a/src/raspberrypi/devices/cfilesystem.h +++ b/src/raspberrypi/devices/cfilesystem.h @@ -92,39 +92,14 @@ namespace Human68k { }; /// Seek types - enum seek_t { + enum class seek_t { SK_BEGIN = 0, ///< From the beginning of a file SK_CURRENT = 1, ///< From the current location SK_END = 2, ///< From the end of the file }; - /// Media byte - enum media_t { - MEDIA_2DD_10 = 0xE0, ///< 2DD/10 sector - MEDIA_1D_9 = 0xE5, ///< 1D/9 sector - MEDIA_2D_9 = 0xE6, ///< 2D/9 sector - MEDIA_1D_8 = 0xE7, ///< 1D/8 sector - MEDIA_2D_8 = 0xE8, ///< 2D/8 sector - MEDIA_2HT = 0xEA, ///< 2HT - MEDIA_2HS = 0xEB, ///< 2HS - MEDIA_2HDE = 0xEC, ///< 2DDE - MEDIA_1DD_9 = 0xEE, ///< 1DD/9 sector - MEDIA_1DD_8 = 0xEF, ///< 1DD/8 sector - MEDIA_MANUAL = 0xF1, ///< Remote drive (manual eject) - MEDIA_REMOVABLE = 0xF2, ///< Remote drive (removable) - MEDIA_REMOTE = 0xF3, ///< Remote drive - MEDIA_DAT = 0xF4, ///< SCSI-DAT - MEDIA_CDROM = 0xF5, ///< SCSI-CDROM - MEDIA_MO = 0xF6, ///< SCSI-MO - MEDIA_SCSI_HD = 0xF7, ///< SCSI-HD - MEDIA_SASI_HD = 0xF8, ///< SASI-HD - MEDIA_RAMDISK = 0xF9, ///< RAM disk - MEDIA_2HQ = 0xFA, ///< 2HQ - MEDIA_2DD_8 = 0xFB, ///< 2DD/8 sector - MEDIA_2DD_9 = 0xFC, ///< 2DD/9 sector - MEDIA_2HC = 0xFD, ///< 2HC - MEDIA_2HD = 0xFE, ///< 2HD - }; + // Media byte + const static int MEDIA_REMOTE = 0xF3; ///< Remote drive struct namests_t { BYTE wildcard; ///< Wildcard character length @@ -220,7 +195,7 @@ namespace Human68k { /// Command line parameter struct /** - The driver itself is included in the beginning of the argument, + The driver itself is included in the beginning of the argument, so setting to a length longer than HUMAN68K_PATH_MAX */ struct argument_t { @@ -257,9 +232,9 @@ static const int XM6_HOST_PSEUDO_CLUSTER_MAX = 10; /// Number of caches for directory entries /** -Human68k carries out a large number of checks of directory entries when doing an operation -inside a subdirectory. This specifies the number of caches used to speed up this operation. -Cache is allocated per drive. The more you add the faster it gets, but use too many +Human68k carries out a large number of checks of directory entries when doing an operation +inside a subdirectory. This specifies the number of caches used to speed up this operation. +Cache is allocated per drive. The more you add the faster it gets, but use too many and the host OS gets under a heavy load, so be careful. Default is 16. @@ -268,10 +243,10 @@ static const int XM6_HOST_DIRENTRY_CACHE_MAX = 16; /// Max number of entries that can be stored per directory /** -When a large number of files are stored in a directory, a larger amount of data than -contemporanous applications can handle will be returned. This may lead to errors such as +When a large number of files are stored in a directory, a larger amount of data than +contemporanous applications can handle will be returned. This may lead to errors such as partial data being recognized, performance dropping significantly, or OOM crashes. -To guard against this, an upper limit is defined here. In the case of a particular +To guard against this, an upper limit is defined here. In the case of a particular file manager, the upper limit is 2560 files. This is one good example to use as reference. Default is around 60000 entries. (Upper limit of the FAT root directory) @@ -280,17 +255,17 @@ static const int XM6_HOST_DIRENTRY_FILE_MAX = 65535; /// Max number of patterns for file name deduplication /** -The file names on the Human68k side are automatically created based on the file system on -the host side. However, Human68k have stricter file name length restrictions than the host has. -Because of this, there is a risk that file name duplication will occur. When this happens, -WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve -the duplication. Theoretically, there are over 60 million (36^5) unique file names that -can be generated by this method. However, in reality any more than a few hundred -deduplications will take excessive processing time. So here an upper limit to deduplication -is set in order to maintain system performance. If a system is operated with common sense, -you should only need a few dozen deduplication patterns, so this value can be kept low -to further improve performance. In the case deduplication is not carried out, multiple files -with the same name will be created. When trying to access such files, +The file names on the Human68k side are automatically created based on the file system on +the host side. However, Human68k have stricter file name length restrictions than the host has. +Because of this, there is a risk that file name duplication will occur. When this happens, +WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve +the duplication. Theoretically, there are over 60 million (36^5) unique file names that +can be generated by this method. However, in reality any more than a few hundred +deduplications will take excessive processing time. So here an upper limit to deduplication +is set in order to maintain system performance. If a system is operated with common sense, +you should only need a few dozen deduplication patterns, so this value can be kept low +to further improve performance. In the case deduplication is not carried out, multiple files +with the same name will be created. When trying to access such files, only the first entry will ever be accessed. Default is 36 patterns. @@ -338,11 +313,7 @@ Normal is 0. Becomes 1 if attempting to mount in read-only mode. Reserving the other values for future use. Insurance against hard-to-detect devices such as homemade USB storage. */ -enum { - FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect - FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media - FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject -}; +static const DWORD FSFLAG_WRITE_PROTECT = 0x00000001; ///< Bit0: Force write protect //=========================================================================== // @@ -357,8 +328,8 @@ class CRing { public: CRing() { Init(); } ~CRing() { Remove(); } - CRing(CRing&) = delete; - CRing& operator=(const CRing&) = delete; + CRing(CRing&) = default; + CRing& operator=(const CRing&) = default; void Init() { next = prev = this; } @@ -442,8 +413,6 @@ class CHostFilename { public: CHostFilename() = default; ~CHostFilename() = default; - CHostFilename(CHostFilename&) = delete; - CHostFilename& operator=(const CHostFilename&) = delete; static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location @@ -468,7 +437,7 @@ public: { m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry void SetEntryCluster(WORD nHumanCluster) { m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry - const Human68k::dirent_t* GetEntry() const + const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; } ///< Get Human68k directory entry int CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes bool isSameEntry(const Human68k::dirent_t* pdirHuman) const @@ -505,7 +474,7 @@ get updated for new directories created as a result of file operations, which triggers updates to directory entires. However, on the host file system, new directories do typically get an updated time stamp. -The unfortunate outcome is that when copying a directory for instance, the time stamp +The unfortunate outcome is that when copying a directory for instance, the time stamp will get overwritten even if the application did not intend for the time stamp to get updated. Here follows an implementation of a directory cache FAT time stamp emulation feature. @@ -532,8 +501,8 @@ public: CHostPath() = default; ~CHostPath(); - CHostPath(CHostPath&) = delete; - CHostPath& operator=(const CHostPath&) = delete; + CHostPath(CHostPath&) = default; + CHostPath& operator=(const CHostPath&) = default; void Clean(); ///< Initialialize for reuse @@ -575,9 +544,9 @@ private: // /// File search processing /// -/// It's pretty much impossible to process Human68k file names as Unicode internally. +/// It's pretty much impossible to process Human68k file names as Unicode internally. /// So, we carry out binary conversion for processing. We leave it up to the -/// directory entry cache to handle the conversion, which allows WINDRV to read +/// directory entry cache to handle the conversion, which allows WINDRV to read /// everything as Shift-JIS. Additionally, it allows Human68k names to be /// fully independent of base path assignments. /// @@ -596,8 +565,6 @@ class CHostFiles { public: CHostFiles() = default; ~CHostFiles() = default; - CHostFiles(CHostFiles&) = delete; - CHostFiles& operator=(const CHostFiles&) = delete; void Init(); @@ -675,8 +642,8 @@ class CHostFcb { public: CHostFcb() = default; ~CHostFcb() { Close(); } - CHostFcb(CHostFcb&) = delete; - CHostFcb& operator=(const CHostFcb&) = delete; + CHostFcb(CHostFcb&) = default; + CHostFcb& operator=(const CHostFcb&) = default; void Init(); @@ -694,7 +661,7 @@ public: DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file bool Truncate() const; ///< Truncate file - DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file + DWORD Seek(DWORD nOffset, Human68k::seek_t nHumanSeek); ///< Seek file bool TimeStamp(DWORD nHumanTime) const; ///< Set file time stamp void Close(); ///< Close file @@ -747,8 +714,8 @@ class CHostDrv public: CHostDrv() = default; ~CHostDrv(); - CHostDrv(CHostDrv&) = delete; - CHostDrv& operator=(const CHostDrv&) = delete; + CHostDrv(CHostDrv&) = default; + CHostDrv& operator=(const CHostDrv&) = default; void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load) @@ -811,8 +778,8 @@ public: CHostEntry() = default; ~CHostEntry(); - CHostEntry(CHostEntry&) = delete; - CHostEntry& operator=(const CHostEntry&) = delete; + CHostEntry(CHostEntry&) = default; + CHostEntry& operator=(const CHostEntry&) = default; void Init() const; ///< Initialization (when the driver is installed) void Clean(); ///< Release (when starting up or resetting) @@ -854,13 +821,13 @@ private: /** @note Current state of affairs: -While it violates the design philosophy of XM6, we should find a way for -'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'. +While it violates the design philosophy of XM6, we should find a way for +'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'. This way, we get the following benefits. Benefit no. 1 -Makes it possible to manage a large number of command handler methods in one place. -There is a high chance the command handlers will change drastically because of +Makes it possible to manage a large number of command handler methods in one place. +There is a high chance the command handlers will change drastically because of host system architectural changes, so we will save a huge amount of maintenance work in the long run. @@ -870,7 +837,7 @@ It is not feasible to implement code in XM6 for simultaneous use of file system Therefore file system object polymorphism is a waste of CPU cycles. I made the change as an experiment. Performance did improve. -The improvement was obvious from looking at the source the compiler spit out +The improvement was obvious from looking at the source the compiler spit out after changing the FILESYS_FAST_STRUCTURE value in windrv.h. You may understand now why I decided to rant here. diff --git a/src/raspberrypi/devices/ctapdriver.cpp b/src/raspberrypi/devices/ctapdriver.cpp index a36a5b36..4c13b36c 100644 --- a/src/raspberrypi/devices/ctapdriver.cpp +++ b/src/raspberrypi/devices/ctapdriver.cpp @@ -37,7 +37,7 @@ using namespace ras_util; // //--------------------------------------------------------------------------- static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { -#ifndef __linux +#ifndef __linux__ return false; #else ifreq ifr; @@ -83,7 +83,7 @@ CTapDriver::~CTapDriver() } static bool ip_link(int fd, const char* ifname, bool up) { -#ifndef __linux +#ifndef __linux__ return false; #else ifreq ifr; @@ -126,7 +126,7 @@ static bool is_interface_up(string_view interface) { bool CTapDriver::Init(const unordered_map& const_params) { -#ifndef __linux +#ifndef __linux__ return false; #else unordered_map params = const_params; @@ -158,7 +158,7 @@ bool CTapDriver::Init(const unordered_map& const_params) } 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; diff --git a/src/raspberrypi/devices/ctapdriver.h b/src/raspberrypi/devices/ctapdriver.h index fa1a895d..79577979 100644 --- a/src/raspberrypi/devices/ctapdriver.h +++ b/src/raspberrypi/devices/ctapdriver.h @@ -19,7 +19,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class CTapDriver { @@ -30,8 +30,8 @@ class CTapDriver CTapDriver() = default; ~CTapDriver(); - CTapDriver(CTapDriver&) = delete; - CTapDriver& operator=(const CTapDriver&) = delete; + CTapDriver(CTapDriver&) = default; + CTapDriver& operator=(const CTapDriver&) = default; bool Init(const unordered_map&); diff --git a/src/raspberrypi/devices/device.cpp b/src/raspberrypi/devices/device.cpp index 055fa665..720c6aff 100644 --- a/src/raspberrypi/devices/device.cpp +++ b/src/raspberrypi/devices/device.cpp @@ -7,17 +7,16 @@ // //--------------------------------------------------------------------------- -#include #include "rascsi_version.h" #include "log.h" -#include "rascsi_exceptions.h" #include "device.h" +#include #include #include using namespace std; -Device::Device(const string& t) : type(t) +Device::Device(const string& type, int lun) : type(type), lun(lun) { assert(type.length() == 4); @@ -43,7 +42,7 @@ void Device::SetProtected(bool b) void Device::SetVendor(const string& v) { if (v.empty() || v.length() > 8) { - throw illegal_argument_exception("Vendor '" + v + "' must be between 1 and 8 characters"); + throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters"); } vendor = v; @@ -52,10 +51,10 @@ void Device::SetVendor(const string& v) void Device::SetProduct(const string& p, bool force) { if (p.empty() || p.length() > 16) { - throw illegal_argument_exception("Product '" + p + "' must be between 1 and 16 characters"); + throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters"); } - // Changing the device name is not SCSI compliant + // Changing vital product data is not SCSI compliant if (!product.empty() && !force) { return; } @@ -66,7 +65,7 @@ void Device::SetProduct(const string& p, bool force) void Device::SetRevision(const string& r) { if (r.empty() || r.length() > 4) { - throw illegal_argument_exception("Revision '" + r + "' must be between 1 and 4 characters"); + throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters"); } revision = r; @@ -74,13 +73,10 @@ void Device::SetRevision(const string& r) string Device::GetPaddedName() const { - string name = vendor; - name.append(8 - vendor.length(), ' '); - name += product; - name.append(16 - product.length(), ' '); - name += revision; - name.append(4 - revision.length(), ' '); + ostringstream os; + os << left << setfill(' ') << setw(8) << vendor << setw(16) << product << setw(4) << revision; + string name = os.str(); assert(name.length() == 28); return name; @@ -107,15 +103,6 @@ void Device::SetParams(const unordered_map& set_params) } } -void Device::SetStatusCode(int s) -{ - if (s) { - LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X", s >> 16, (s >> 8 &0xff), s & 0xff) - } - - status_code = s; -} - bool Device::Start() { if (!ready) { diff --git a/src/raspberrypi/devices/device.h b/src/raspberrypi/devices/device.h index 421bc40b..edda7904 100644 --- a/src/raspberrypi/devices/device.h +++ b/src/raspberrypi/devices/device.h @@ -9,16 +9,13 @@ #pragma once -#include "scsi.h" #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; -class Device +class Device //NOSONAR The number of fields and methods is justified, the complexity is low { - friend class DeviceFactory; - const string DEFAULT_VENDOR = "RaSCSI"; string type; @@ -45,15 +42,11 @@ class Device bool lockable = false; bool locked = false; - // The block size is configurable - bool block_size_configurable = false; - // Device can be created with parameters bool supports_params = false; - // Device ID and LUN - int32_t id = 0; - int32_t lun = 0; + // Immutable LUN + int lun; // Device identifier (for INQUIRY) string vendor = DEFAULT_VENDOR; @@ -86,21 +79,16 @@ protected: string GetParam(const string&) const; void SetParams(const unordered_map&); - explicit Device(const string&); + Device(const string&, int); public: virtual ~Device() = default; - Device(Device&) = delete; - Device& operator=(const Device&) = delete; - - // Override for device specific initializations, to be called after all device properties have been set - virtual bool Init(const unordered_map&) { return true; }; const string& GetType() const { return type; } bool IsReady() const { return ready; } - void Reset(); + virtual void Reset(); bool IsProtectable() const { return protectable; } void SetProtectable(bool b) { protectable = b; } @@ -123,10 +111,8 @@ public: bool IsLocked() const { return locked; } void SetLocked(bool b) { locked = b; } - int32_t GetId() const { return id; } - void SetId(int32_t i) { id = i; } - int32_t GetLun() const { return lun; } - void SetLun(int32_t l) { lun = l; } + virtual int GetId() const = 0; + int GetLun() const { return lun; } string GetVendor() const { return vendor; } void SetVendor(const string&); @@ -142,12 +128,9 @@ public: unordered_map GetParams() const { return params; } void SetDefaultParams(const unordered_map& p) { default_params = p; } - void SetStatusCode(int); + void SetStatusCode(int s) { status_code = s; } bool Start(); void Stop(); virtual bool Eject(bool); - virtual void FlushCache() { - // Devices with a cache have to implement this method - } }; diff --git a/src/raspberrypi/devices/device_factory.cpp b/src/raspberrypi/devices/device_factory.cpp index 58268389..11379293 100644 --- a/src/raspberrypi/devices/device_factory.cpp +++ b/src/raspberrypi/devices/device_factory.cpp @@ -25,8 +25,6 @@ using namespace std; using namespace rascsi_interface; -multimap> DeviceFactory::devices; - DeviceFactory::DeviceFactory() { sector_sizes[SCHD] = { 512, 1024, 2048, 4096 }; @@ -60,45 +58,6 @@ DeviceFactory::DeviceFactory() extension_mapping["iso"] = SCCD; } -void DeviceFactory::DeleteDevice(const PrimaryDevice& device) const -{ - auto [begin, end] = devices.equal_range(device.GetId()); - for (auto& it = begin; it != end; ++it) { - if (it->second->GetLun() == device.GetLun()) { - devices.erase(it); - - break; - } - } -} - -void DeviceFactory::DeleteAllDevices() const -{ - devices.clear(); -} - -const PrimaryDevice *DeviceFactory::GetDeviceByIdAndLun(int i, int lun) const -{ - for (const auto& [id, device] : devices) { - if (device->GetId() == i && device->GetLun() == lun) { - return device.get(); - } - } - - return nullptr; -} - -list DeviceFactory::GetAllDevices() const -{ - list result; - - for (const auto& [id, device] : devices) { - result.push_back(device.get()); - } - - return result; -} - string DeviceFactory::GetExtension(const string& filename) const { string ext; @@ -132,7 +91,8 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const } // ID -1 is used by rascsi to create a temporary device -PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename, int id) +shared_ptr DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type, + int lun, const string& filename) { // If no type was specified try to derive the device type from the filename if (type == UNDEFINED) { @@ -142,13 +102,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file } } - unique_ptr device; + shared_ptr device; switch (type) { case SCHD: { if (string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { - device = make_unique(); + device = make_shared(lun); } else { - device = make_unique(sector_sizes[SCHD], false, ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); + device = make_shared(lun, sector_sizes[SCHD], false, + ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); // Some Apple tools require a particular drive identification if (ext == "hda") { @@ -162,7 +123,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file } case SCRM: - device = make_unique(sector_sizes[SCRM], true); + device = make_shared(lun, sector_sizes[SCRM], true); device->SetProtectable(true); device->SetStoppable(true); device->SetRemovable(true); @@ -171,7 +132,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file break; case SCMO: - device = make_unique(sector_sizes[SCMO]); + device = make_shared(lun, sector_sizes[SCMO]); device->SetProtectable(true); device->SetStoppable(true); device->SetRemovable(true); @@ -180,7 +141,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file break; case SCCD: - device = make_unique(sector_sizes[SCCD]); + device = make_shared(lun, sector_sizes[SCCD]); device->SetReadOnly(true); device->SetStoppable(true); device->SetRemovable(true); @@ -189,14 +150,15 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file break; case SCBR: - device = make_unique(); + 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->SupportsParams(true); device->SetDefaultParams(default_params[SCBR]); break; case SCDP: - device = make_unique(); + device = make_shared(lun); // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); @@ -206,14 +168,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file break; case SCHS: - device = make_unique(*this); + device = make_shared(lun, controller_manager); // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly device->SetVendor("RaSCSI"); device->SetProduct("Host Services"); break; case SCLP: - device = make_unique(); + device = make_shared(lun); device->SetProduct("SCSI PRINTER"); device->SupportsParams(true); device->SetDefaultParams(default_params[SCLP]); @@ -223,17 +185,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file break; } - if (device != nullptr) { - PrimaryDevice *d = device.release(); - - d->SetId(id); - - devices.emplace(id, d); - - return d; - } - - return nullptr; + return device; } const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) const @@ -260,7 +212,7 @@ list DeviceFactory::GetNetworkInterfaces() const { list network_interfaces; -#ifdef __linux +#ifdef __linux__ ifaddrs *addrs; getifaddrs(&addrs); ifaddrs *tmp = addrs; diff --git a/src/raspberrypi/devices/device_factory.h b/src/raspberrypi/devices/device_factory.h index 61fe34b0..1b6c9931 100644 --- a/src/raspberrypi/devices/device_factory.h +++ b/src/raspberrypi/devices/device_factory.h @@ -18,9 +18,10 @@ #include #include "rascsi_interface.pb.h" -using namespace std; //NOSONAR Not relevant for rascsi -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace std; +using namespace rascsi_interface; +class ControllerManager; class PrimaryDevice; class DeviceFactory @@ -29,14 +30,8 @@ public: DeviceFactory(); ~DeviceFactory() = default; - DeviceFactory(DeviceFactory&) = delete; - DeviceFactory& operator=(const DeviceFactory&) = delete; - PrimaryDevice *CreateDevice(PbDeviceType, const string&, int); - void DeleteDevice(const PrimaryDevice&) const; - void DeleteAllDevices() const; - const PrimaryDevice *GetDeviceByIdAndLun(int, int) const; - list GetAllDevices() const; + shared_ptr CreateDevice(const ControllerManager&, PbDeviceType, int, const string&); PbDeviceType GetTypeForFile(const string&) const; const unordered_set& GetSectorSizes(PbDeviceType type) const; const unordered_set& GetSectorSizes(const string&) const; @@ -54,8 +49,6 @@ private: string GetExtension(const string&) const; - static std::multimap> devices; - unordered_set empty_set; unordered_map empty_map; }; diff --git a/src/raspberrypi/devices/disk.cpp b/src/raspberrypi/devices/disk.cpp index aa8200e4..13e5f886 100644 --- a/src/raspberrypi/devices/disk.cpp +++ b/src/raspberrypi/devices/disk.cpp @@ -25,7 +25,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -Disk::Disk(const string& id) : ModePageDevice(id) +Disk::Disk(const string& type, int lun) : ModePageDevice(type, lun) { dispatcher.Add(scsi_command::eCmdRezero, "Rezero", &Disk::Rezero); dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit); @@ -88,7 +88,9 @@ bool Disk::Dispatch(scsi_command cmd) //--------------------------------------------------------------------------- void Disk::Open(const Filepath& path) { - assert(blocks > 0); + if (blocks == 0) { + throw io_exception("Disk has 0 blocks"); + } SetReady(true); @@ -108,10 +110,17 @@ void Disk::Open(const Filepath& path) SetLocked(false); } -void Disk::SetUpCache(const Filepath& path, off_t image_offset) +void Disk::SetUpCache(const Filepath& path, off_t image_offset, bool raw) { assert(cache == nullptr); cache = make_unique(path, size_shift_count, (uint32_t)blocks, image_offset); + cache->SetRawMode(raw); +} + +void Disk::ResizeCache(const Filepath& path, bool raw) +{ + cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)blocks)); + cache->SetRawMode(raw); } void Disk::FlushCache() @@ -266,10 +275,10 @@ void Disk::StartStopUnit() bool load = ctrl->cmd[4] & 0x02; if (load) { - LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium") + LOGTRACE(start ? "Loading medium" : "Ejecting medium") } else { - LOGTRACE("%s", start ? "Starting unit" : "Stopping unit") + LOGTRACE(start ? "Starting unit" : "Stopping unit") SetStopped(!start); } @@ -315,7 +324,7 @@ void Disk::PreventAllowMediumRemoval() bool lock = ctrl->cmd[4] & 0x01; - LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium") + LOGTRACE(lock ? "Locking medium" : "Unlocking medium") SetLocked(lock); @@ -346,7 +355,7 @@ void Disk::MediumChanged() is_medium_changed = true; } else { - LOGWARN("%s Medium change requested for non-reomvable medium", __PRETTY_FUNCTION__) + LOGERROR("Medium change requested for non-removable medium") } } @@ -366,10 +375,10 @@ bool Disk::Eject(bool force) return status; } -int Disk::ModeSense6(const vector& cdb, vector& buf, int max_length) const +int Disk::ModeSense6(const vector& cdb, vector& buf) const { // Get length, clear buffer - auto length = (int)min((size_t)max_length, (size_t)cdb[4]); + auto length = (int)min(buf.size(), (size_t)cdb[4]); fill_n(buf.begin(), length, 0); // DEVICE SPECIFIC PARAMETER @@ -378,7 +387,7 @@ int Disk::ModeSense6(const vector& cdb, vector& buf, int max_length) } // Basic information - int info_size = 4; + int size = 4; // Add block descriptor if DBD is 0 if ((cdb[1] & 0x08) == 0) { @@ -388,33 +397,33 @@ int Disk::ModeSense6(const vector& cdb, vector& buf, int max_length) // Only if ready if (IsReady()) { // Short LBA mode parameter block descriptor (number of blocks and block length) - SetInt32(buf, 4, (uint32_t)GetBlockCount()); + SetInt32(buf, 4, (uint32_t)blocks); SetInt32(buf, 8, GetSectorSizeInBytes()); } - info_size = 12; + size = 12; } - info_size += super::AddModePages(cdb, buf, info_size, length - info_size); - if (info_size > 255) { + size += super::AddModePages(cdb, buf, size, length - size); + if (size > 255) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Do not return more than ALLOCATION LENGTH bytes - if (info_size > length) { - info_size = length; + if (size > length) { + size = length; } // Final setting of mode data length - buf[0] = (BYTE)info_size; + buf[0] = (BYTE)size; - return info_size; + return size; } -int Disk::ModeSense10(const vector& cdb, vector& buf, int max_length) const +int Disk::ModeSense10(const vector& cdb, vector& buf) const { // Get length, clear buffer - auto length = (int)min((size_t)max_length, (size_t)GetInt16(cdb, 7)); + auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7)); fill_n(buf.begin(), length, 0); // DEVICE SPECIFIC PARAMETER @@ -423,11 +432,11 @@ int Disk::ModeSense10(const vector& cdb, vector& buf, int max_length) } // Basic Information - int info_size = 8; + int size = 8; // Add block descriptor if DBD is 0, only if ready if ((cdb[1] & 0x08) == 0 && IsReady()) { - uint64_t disk_blocks = GetBlockCount(); + uint64_t disk_blocks = blocks; uint32_t disk_size = GetSectorSizeInBytes(); // Check LLBAA for short or long block descriptor @@ -439,7 +448,7 @@ int Disk::ModeSense10(const vector& cdb, vector& buf, int max_length) SetInt32(buf, 8, (uint32_t)disk_blocks); SetInt32(buf, 12, disk_size); - info_size = 16; + size = 16; } else { // Mode parameter header, LONGLBA @@ -452,24 +461,24 @@ int Disk::ModeSense10(const vector& cdb, vector& buf, int max_length) SetInt64(buf, 8, disk_blocks); SetInt32(buf, 20, disk_size); - info_size = 24; + size = 24; } } - info_size += super::AddModePages(cdb, buf, info_size, length - info_size); - if (info_size > 65535) { + size += super::AddModePages(cdb, buf, size, length - size); + if (size > 65535) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Do not return more than ALLOCATION LENGTH bytes - if (info_size > length) { - info_size = length; + if (size > length) { + size = length; } // Final setting of mode data length - SetInt16(buf, 0, info_size); + SetInt16(buf, 0, size); - return info_size; + return size; } void Disk::SetUpModePages(map>& pages, int page, bool changeable) const @@ -798,7 +807,7 @@ void Disk::ValidateBlockAddress(access_mode mode) const { uint64_t block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2); - uint64_t capacity = GetBlockCount(); + uint64_t capacity = blocks; if (block > capacity) { LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " @@ -834,7 +843,7 @@ bool Disk::CheckAndGetStartAndCount(uint64_t& start, uint32_t& count, access_mod LOGTRACE("%s READ/WRITE/VERIFY/SEEK command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count) // Check capacity - if (uint64_t capacity = GetBlockCount(); start > capacity || start + count > capacity) { + if (uint64_t capacity = blocks; start > capacity || start + count > capacity) { LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)).c_str()) throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); @@ -879,7 +888,7 @@ void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) break; default: - assert(false); + throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes"); break; } } diff --git a/src/raspberrypi/devices/disk.h b/src/raspberrypi/devices/disk.h index 18873c1d..6edcaa54 100644 --- a/src/raspberrypi/devices/disk.h +++ b/src/raspberrypi/devices/disk.h @@ -31,6 +31,8 @@ class Disk : public ModePageDevice, public ScsiBlockCommands Dispatcher dispatcher; + unique_ptr cache; + // The supported configurable sector sizes, empty if not configurable unordered_set sector_sizes; uint32_t configured_sector_size = 0; @@ -45,10 +47,8 @@ class Disk : public ModePageDevice, public ScsiBlockCommands public: - explicit Disk(const string&); + Disk(const string&, int); ~Disk() override; - Disk(Disk&) = delete; - Disk& operator=(const Disk&) = delete; bool Dispatch(scsi_command) override; @@ -105,13 +105,14 @@ private: void ValidateBlockAddress(access_mode) const; bool CheckAndGetStartAndCount(uint64_t&, uint32_t&, access_mode) const; - int ModeSense6(const vector&, vector&, int) const override; - int ModeSense10(const vector&, vector&, int) const override; + int ModeSense6(const vector&, vector&) const override; + int ModeSense10(const vector&, vector&) const override; protected: virtual void Open(const Filepath&); - void SetUpCache(const Filepath&, off_t = 0); + void SetUpCache(const Filepath&, off_t, bool = false); + void ResizeCache(const Filepath&, bool); void SetUpModePages(map>&, int, bool) const override; virtual void AddErrorPage(map>&, bool) const; @@ -126,6 +127,4 @@ protected: void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } uint32_t GetConfiguredSectorSize() const; void SetBlockCount(uint64_t b) { blocks = b; } - - unique_ptr cache; }; diff --git a/src/raspberrypi/devices/disk_cache.h b/src/raspberrypi/devices/disk_cache.h index 501dea7f..822481b1 100644 --- a/src/raspberrypi/devices/disk_cache.h +++ b/src/raspberrypi/devices/disk_cache.h @@ -19,7 +19,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class DiskCache { @@ -36,8 +36,6 @@ public: DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0); ~DiskCache() = default; - DiskCache(DiskCache&) = delete; - DiskCache& operator=(const DiskCache&) = delete; void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting diff --git a/src/raspberrypi/devices/disk_track.cpp b/src/raspberrypi/devices/disk_track.cpp index be43c489..065c1281 100644 --- a/src/raspberrypi/devices/disk_track.cpp +++ b/src/raspberrypi/devices/disk_track.cpp @@ -21,9 +21,7 @@ DiskTrack::~DiskTrack() { // Release memory, but do not save automatically - if (dt.buffer) { - free(dt.buffer); - } + free(dt.buffer); } void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff) diff --git a/src/raspberrypi/devices/disk_track.h b/src/raspberrypi/devices/disk_track.h index 0f13a98a..19e8ce84 100644 --- a/src/raspberrypi/devices/disk_track.h +++ b/src/raspberrypi/devices/disk_track.h @@ -18,7 +18,7 @@ #include "filepath.h" #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class DiskTrack { diff --git a/src/raspberrypi/devices/dispatcher.h b/src/raspberrypi/devices/dispatcher.h index 71aa6d07..da0e0d1e 100644 --- a/src/raspberrypi/devices/dispatcher.h +++ b/src/raspberrypi/devices/dispatcher.h @@ -14,8 +14,8 @@ #include "log.h" #include -using namespace std; //NOSONAR Not relevant for rascsi -using namespace scsi_defs; //NOSONAR Not relevant for rascsi +using namespace std; +using namespace scsi_defs; template class Dispatcher @@ -24,8 +24,6 @@ public: Dispatcher() = default; ~Dispatcher() = default; - Dispatcher(Dispatcher&) = delete; - Dispatcher& operator=(const Dispatcher&) = delete; using operation = void (T::*)(); using command_t = struct _command_t { diff --git a/src/raspberrypi/devices/file_support.cpp b/src/raspberrypi/devices/file_support.cpp index 6ec0ed46..c6b476ac 100644 --- a/src/raspberrypi/devices/file_support.cpp +++ b/src/raspberrypi/devices/file_support.cpp @@ -7,6 +7,7 @@ // //--------------------------------------------------------------------------- +#include "rascsi_exceptions.h" #include "file_support.h" using namespace std; @@ -39,3 +40,16 @@ void FileSupport::UnreserveAll() { reserved_files.clear(); } + +bool FileSupport::FileExists(const Filepath& filepath) +{ + try { + // Disk::Open closes the file in case it exists + Open(filepath); + } + catch(const file_not_found_exception&) { + return false; + } + + return true; +} diff --git a/src/raspberrypi/devices/file_support.h b/src/raspberrypi/devices/file_support.h index 32af8ee3..051ef1dc 100644 --- a/src/raspberrypi/devices/file_support.h +++ b/src/raspberrypi/devices/file_support.h @@ -15,7 +15,7 @@ #include #include "filepath.h" -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; using id_set = pair; @@ -30,8 +30,6 @@ public: FileSupport() = default; virtual ~FileSupport() = default; - FileSupport(FileSupport&) = delete; - FileSupport& operator=(const FileSupport&) = delete; void GetPath(Filepath& path) const { path = diskpath; } void SetPath(const Filepath& path) { diskpath = path; } @@ -39,6 +37,7 @@ public: void ReserveFile(const Filepath&, int, int) const; void UnreserveFile() const; static void UnreserveAll(); + bool FileExists(const Filepath&); static unordered_map GetReservedFiles() { return reserved_files; } static void SetReservedFiles(const unordered_map& files_in_use) diff --git a/src/raspberrypi/devices/host_services.cpp b/src/raspberrypi/devices/host_services.cpp index 33ddca93..2ff99fa2 100644 --- a/src/raspberrypi/devices/host_services.cpp +++ b/src/raspberrypi/devices/host_services.cpp @@ -20,8 +20,9 @@ // c) start && load (LOAD): Reboot the Raspberry Pi // +#include "controllers/controller_manager.h" +#include "controllers/scsi_controller.h" #include "rascsi_exceptions.h" -#include "device_factory.h" #include "scsi_command_util.h" #include "dispatcher.h" #include "host_services.h" @@ -30,7 +31,8 @@ using namespace scsi_defs; using namespace scsi_command_util; -HostServices::HostServices(const DeviceFactory& factory) : ModePageDevice("SCHS"), device_factory(factory) +HostServices::HostServices(int lun, const ControllerManager& manager) + : ModePageDevice("SCHS", lun), controller_manager(manager) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady); dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit); @@ -60,40 +62,35 @@ void HostServices::StartStopUnit() if (!start) { // Flush any caches - for (PrimaryDevice *device : device_factory.GetAllDevices()) { + for (const auto& device : controller_manager.GetAllDevices()) { device->FlushCache(); } if (load) { - controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_PI); + controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI); } else { - controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_RASCSI); + controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI); } - - EnterStatusPhase(); - return; + } + else if (load) { + controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI); } else { - if (load) { - controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::RESTART_PI); - - EnterStatusPhase(); - return; - } + throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } - throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + EnterStatusPhase(); } -int HostServices::ModeSense6(const vector& cdb, vector& buf, int max_length) const +int HostServices::ModeSense6(const vector& cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } - auto length = (int)min((size_t)max_length, (size_t)cdb[4]); + auto length = (int)min(buf.size(), (size_t)cdb[4]); fill_n(buf.begin(), length, 0); // Basic Information @@ -114,14 +111,14 @@ int HostServices::ModeSense6(const vector& cdb, vector& buf, int max_ return size; } -int HostServices::ModeSense10(const vector& cdb, vector& buf, int max_length) const +int HostServices::ModeSense10(const vector& cdb, vector& buf) const { // Block descriptors cannot be returned if (!(cdb[1] & 0x08)) { throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } - auto length = (int)min((size_t)max_length, (size_t)GetInt16(cdb, 7)); + auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7)); fill_n(buf.begin(), length, 0); // Basic Information diff --git a/src/raspberrypi/devices/host_services.h b/src/raspberrypi/devices/host_services.h index 217de0fb..a5c42338 100644 --- a/src/raspberrypi/devices/host_services.h +++ b/src/raspberrypi/devices/host_services.h @@ -15,17 +15,15 @@ #include #include -class DeviceFactory; +class ControllerManager; class HostServices: public ModePageDevice { public: - explicit HostServices(const DeviceFactory&); + HostServices(int, const ControllerManager&); ~HostServices() override = default; - HostServices(HostServices&) = delete; - HostServices& operator=(const HostServices&) = delete; bool Dispatch(scsi_command) override; @@ -58,10 +56,10 @@ private: Dispatcher dispatcher; - int ModeSense6(const vector&, vector&, int) const override; - int ModeSense10(const vector&, vector&, int) const override; + const ControllerManager& controller_manager; + + int ModeSense6(const vector&, vector&) const override; + int ModeSense10(const vector&, vector&) const override; void AddRealtimeClockPage(map>&, bool) const; - - const DeviceFactory& device_factory; }; diff --git a/src/raspberrypi/devices/mode_page_device.cpp b/src/raspberrypi/devices/mode_page_device.cpp index 10011752..c0c4b860 100644 --- a/src/raspberrypi/devices/mode_page_device.cpp +++ b/src/raspberrypi/devices/mode_page_device.cpp @@ -20,7 +20,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -ModePageDevice::ModePageDevice(const string& id) : PrimaryDevice(id) +ModePageDevice::ModePageDevice(const string& type, int lun) : PrimaryDevice(type, lun) { dispatcher.Add(scsi_command::eCmdModeSense6, "ModeSense6", &ModePageDevice::ModeSense6); dispatcher.Add(scsi_command::eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10); @@ -63,14 +63,14 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int for (auto const& [index, data] : pages) { // The specification mandates that page 0 must be returned after all others if (index) { - size_t offset = result.size(); + size_t off = result.size(); // Page data result.insert(result.end(), data.begin(), data.end()); // Page code, PS bit may already have been set - result[offset] |= (byte)index; + result[off] |= (byte)index; // Page payload size - result[offset + 1] = (byte)(data.size() - 2); + result[off + 1] = (byte)(data.size() - 2); } else { page0 = data; @@ -79,10 +79,12 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int // Page 0 must be last if (!page0.empty()) { + size_t off = result.size(); + // Page data result.insert(result.end(), page0.begin(), page0.end()); // Page payload size - result[result.size() + 1] = (byte)(page0.size() - 2); + result[off + 1] = (byte)(page0.size() - 2); } // Do not return more than the requested number of bytes @@ -94,14 +96,14 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int void ModePageDevice::ModeSense6() { - ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize()); + ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer()); EnterDataInPhase(); } void ModePageDevice::ModeSense10() { - ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize()); + ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer()); EnterDataInPhase(); } @@ -145,7 +147,7 @@ int ModePageDevice::ModeSelectCheck6() const int ModePageDevice::ModeSelectCheck10() const { // Receive the data specified by the parameter length - size_t length = min(controller->GetBufferSize(), (size_t)GetInt16(ctrl->cmd, 7)); + size_t length = min(controller->GetBuffer().size(), (size_t)GetInt16(ctrl->cmd, 7)); return ModeSelectCheck((int)length); } diff --git a/src/raspberrypi/devices/mode_page_device.h b/src/raspberrypi/devices/mode_page_device.h index e660087e..c1c1d4fe 100644 --- a/src/raspberrypi/devices/mode_page_device.h +++ b/src/raspberrypi/devices/mode_page_device.h @@ -18,10 +18,8 @@ class ModePageDevice: public PrimaryDevice { public: - explicit ModePageDevice(const string&); + ModePageDevice(const string&, int); ~ModePageDevice()override = default; - ModePageDevice(ModePageDevice&) = delete; - ModePageDevice& operator=(const ModePageDevice&) = delete; bool Dispatch(scsi_command) override; @@ -38,8 +36,8 @@ private: Dispatcher dispatcher; - virtual int ModeSense6(const vector&, vector&, int) const = 0; - virtual int ModeSense10(const vector&, vector&, int) const = 0; + virtual int ModeSense6(const vector&, vector&) const = 0; + virtual int ModeSense10(const vector&, vector&) const = 0; void ModeSense6(); void ModeSense10(); diff --git a/src/raspberrypi/devices/primary_device.cpp b/src/raspberrypi/devices/primary_device.cpp index f7f754d3..60820f05 100644 --- a/src/raspberrypi/devices/primary_device.cpp +++ b/src/raspberrypi/devices/primary_device.cpp @@ -17,7 +17,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -PrimaryDevice::PrimaryDevice(const string& id) : Device(id) +PrimaryDevice::PrimaryDevice(const string& type, int lun) : Device(type, lun) { // Mandatory SCSI primary commands dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); @@ -33,6 +33,15 @@ bool PrimaryDevice::Dispatch(scsi_command cmd) return dispatcher.Dispatch(this, cmd); } +int PrimaryDevice::GetId() const +{ + if (controller == nullptr) { + LOGERROR("Device is missing its controller") + } + + return controller != nullptr ? controller->GetTargetId() : -1; +} + void PrimaryDevice::SetController(AbstractController *c) { controller = c; @@ -65,7 +74,7 @@ void PrimaryDevice::Inquiry() LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId()) // Signal that the requested LUN does not exist - controller->GetBuffer()[0] |= 0x7f; + controller->GetBuffer().data()[0] = 0x7f; } EnterDataInPhase(); @@ -81,7 +90,7 @@ void PrimaryDevice::ReportLuns() uint32_t allocation_length = GetInt32(ctrl->cmd, 6); vector& buf = controller->GetBuffer(); - fill_n(buf.begin(), min(controller->GetBufferSize(), (size_t)allocation_length), 0); + fill_n(buf.begin(), min(buf.size(), (size_t)allocation_length), 0); uint32_t size = 0; for (int lun = 0; lun < controller->GetMaxLuns(); lun++) { @@ -115,7 +124,7 @@ void PrimaryDevice::RequestSense() // Do not raise an exception here because the rest of the code must be executed controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN); - controller->SetStatus(0); + controller->SetStatus(status::GOOD); } vector buf = controller->GetDeviceForLun(lun)->HandleRequestSense(); @@ -195,7 +204,7 @@ vector PrimaryDevice::HandleRequestSense() const buf[12] = (byte)(GetStatusCode() >> 8); buf[13] = (byte)GetStatusCode(); - LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, controller->GetStatus(), + LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, (int)controller->GetStatus(), (int)buf[2], (int)buf[12]) return buf; diff --git a/src/raspberrypi/devices/primary_device.h b/src/raspberrypi/devices/primary_device.h index 2382b018..a0659f10 100644 --- a/src/raspberrypi/devices/primary_device.h +++ b/src/raspberrypi/devices/primary_device.h @@ -12,27 +12,33 @@ #pragma once #include "interfaces/scsi_primary_commands.h" -#include "controllers/scsi_controller.h" +#include "controllers/abstract_controller.h" #include "device.h" #include "dispatcher.h" #include class PrimaryDevice: public ScsiPrimaryCommands, public Device { - public: - explicit PrimaryDevice(const string&); + PrimaryDevice(const string&, int); ~PrimaryDevice() override = default; - PrimaryDevice(PrimaryDevice&) = delete; - PrimaryDevice& operator=(const PrimaryDevice&) = delete; virtual bool Dispatch(scsi_command); + int GetId() const override; + void SetController(AbstractController *); virtual bool WriteByteSequence(vector&, uint32_t); virtual int GetSendDelay() const { return BUS::SEND_NO_DELAY; } + // Override for device specific initializations, to be called after all device properties have been set + virtual bool Init(const unordered_map&) { return true; }; + + virtual void FlushCache() { + // Devices with a cache have to implement this method + } + protected: vector HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; diff --git a/src/raspberrypi/devices/scsi_command_util.h b/src/raspberrypi/devices/scsi_command_util.h index ea77a9a0..658f7d0a 100644 --- a/src/raspberrypi/devices/scsi_command_util.h +++ b/src/raspberrypi/devices/scsi_command_util.h @@ -14,7 +14,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; namespace scsi_command_util { diff --git a/src/raspberrypi/devices/scsi_daynaport.cpp b/src/raspberrypi/devices/scsi_daynaport.cpp index 40243cf6..20be31df 100644 --- a/src/raspberrypi/devices/scsi_daynaport.cpp +++ b/src/raspberrypi/devices/scsi_daynaport.cpp @@ -7,17 +7,17 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ Emulation of the DaynaPort SCSI Link Ethernet interface ] // -// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's +// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's // Tiny SCSI Emulator // - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT // - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator // -// Additional documentation and clarification is available at the +// Additional documentation and clarification is available at the // following link: // - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link // @@ -36,7 +36,7 @@ using namespace scsi_defs; using namespace scsi_command_util; // TODO Disk must not be the superclass -SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP") +SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady); dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6); @@ -139,7 +139,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) LOGTRACE("%s Read maximum length %d, (%04X)", __PRETTY_FUNCTION__, requested_length, requested_length) - // At host startup, it will send a READ(6) command with a length of 1. We should + // At host startup, it will send a READ(6) command with a length of 1. We should // respond by going into the status mode with a code of 0x02 if (requested_length == 1) { return 0; @@ -169,7 +169,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) LOGTRACE("%s Packet Sz %d (%08X) read: %d", __PRETTY_FUNCTION__, (unsigned int) rx_packet_size, (unsigned int) rx_packet_size, read_count) // This is a very basic filter to prevent unnecessary packets from - // being sent to the SCSI initiator. + // being sent to the SCSI initiator. send_message_to_host = false; // The following doesn't seem to work with unicast messages. Temporarily removing the filtering @@ -192,7 +192,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) /////// } send_message_to_host = true; - // TODO: We should check to see if this message is in the multicast + // TODO: We should check to see if this message is in the multicast // configuration from SCSI command 0x0D if (!send_message_to_host) { @@ -239,7 +239,7 @@ int SCSIDaynaPort::Read(const vector& cdb, vector& buf, uint64_t) // The CRC was already appended by the ctapdriver return size + DAYNAPORT_READ_HEADER_SZ; } - // If we got to this point, there are still messages in the queue, so + // If we got to this point, there are still messages in the queue, so // we should loop back and get the next one. } // end while @@ -300,7 +300,7 @@ bool SCSIDaynaPort::WriteBytes(const vector& cdb, const vector& buf, return true; } - + //--------------------------------------------------------------------------- // // RetrieveStats @@ -406,9 +406,9 @@ void SCSIDaynaPort::Write6() ctrl->length = GetInt16(ctrl->cmd, 3 + 8); } else { - LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, data_format) + LOGWARN("%s Unknown data format $%02X", __PRETTY_FUNCTION__, data_format) } - LOGTRACE("%s length: %04X (%d) format: %02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format) + LOGTRACE("%s length: $%04X (%d) format: $%02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format) if (ctrl->length <= 0) { throw scsi_error_exception(); diff --git a/src/raspberrypi/devices/scsi_daynaport.h b/src/raspberrypi/devices/scsi_daynaport.h index 4aaf8ab6..9f45d05d 100644 --- a/src/raspberrypi/devices/scsi_daynaport.h +++ b/src/raspberrypi/devices/scsi_daynaport.h @@ -7,12 +7,12 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ Emulation of the DaynaPort SCSI Link Ethernet interface ] // -// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's +// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's // Tiny SCSI Emulator // - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT // - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator @@ -45,10 +45,8 @@ class SCSIDaynaPort final : public Disk { public: - SCSIDaynaPort(); + explicit SCSIDaynaPort(int); ~SCSIDaynaPort() override = default; - SCSIDaynaPort(SCSIDaynaPort&) = delete; - SCSIDaynaPort& operator=(const SCSIDaynaPort&) = delete; bool Init(const unordered_map&) override; void Open(const Filepath& path) override; diff --git a/src/raspberrypi/devices/scsi_host_bridge.cpp b/src/raspberrypi/devices/scsi_host_bridge.cpp index c61abe19..f107def5 100644 --- a/src/raspberrypi/devices/scsi_host_bridge.cpp +++ b/src/raspberrypi/devices/scsi_host_bridge.cpp @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI Host Bridge for the Sharp X68000 ] @@ -27,7 +27,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -SCSIBR::SCSIBR() : Disk("SCBR") +SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun) { // Create host file system fs.Reset(); @@ -41,7 +41,7 @@ bool SCSIBR::Init(const unordered_map& params) { SetParams(params); -#ifdef __linux +#ifdef __linux__ // TAP Driver Generation m_bTapEnable = tap.Init(GetParams()); if (!m_bTapEnable){ diff --git a/src/raspberrypi/devices/scsi_host_bridge.h b/src/raspberrypi/devices/scsi_host_bridge.h index 937fad3b..53878b60 100644 --- a/src/raspberrypi/devices/scsi_host_bridge.h +++ b/src/raspberrypi/devices/scsi_host_bridge.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI Host Bridge for the Sharp X68000 ] @@ -24,7 +24,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; class SCSIBR final : public Disk { @@ -32,10 +32,8 @@ class SCSIBR final : public Disk public: - SCSIBR(); + explicit SCSIBR(int); ~SCSIBR() override = default; - SCSIBR(SCSIBR&) = delete; - SCSIBR& operator=(const SCSIBR&) = delete; bool Init(const unordered_map&) override; bool Dispatch(scsi_command) override; diff --git a/src/raspberrypi/devices/scsi_printer.cpp b/src/raspberrypi/devices/scsi_printer.cpp index 7a116c2a..a4fe1823 100644 --- a/src/raspberrypi/devices/scsi_printer.cpp +++ b/src/raspberrypi/devices/scsi_printer.cpp @@ -51,7 +51,7 @@ using namespace scsi_defs; using namespace ras_util; using namespace scsi_command_util; -SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP") +SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice("SCLP", lun) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady); dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit); @@ -149,8 +149,8 @@ void SCSIPrinter::Print() LOGTRACE("Receiving %d byte(s) to be printed", length) - if (length > controller->GetBufferSize()) { - LOGERROR("%s", string("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBufferSize()) + + if (length > controller->GetBuffer().size()) { + LOGERROR("%s", ("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBuffer().size()) + " bytes, " + to_string(length) + " bytes expected").c_str()) throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); diff --git a/src/raspberrypi/devices/scsi_printer.h b/src/raspberrypi/devices/scsi_printer.h index 1fd0f55b..d07e65e2 100644 --- a/src/raspberrypi/devices/scsi_printer.h +++ b/src/raspberrypi/devices/scsi_printer.h @@ -24,10 +24,8 @@ class SCSIPrinter final : public PrimaryDevice, public ScsiPrinterCommands //NOS public: - SCSIPrinter(); + explicit SCSIPrinter(int); ~SCSIPrinter() override; - SCSIPrinter(SCSIPrinter&) = delete; - SCSIPrinter& operator=(const SCSIPrinter&) = delete; bool Dispatch(scsi_command) override; diff --git a/src/raspberrypi/devices/scsicd.cpp b/src/raspberrypi/devices/scsicd.cpp index b076582c..7d6cc5f7 100644 --- a/src/raspberrypi/devices/scsicd.cpp +++ b/src/raspberrypi/devices/scsicd.cpp @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI CD-ROM ] @@ -24,7 +24,7 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(const unordered_set& sector_sizes) : Disk("SCCD") +SCSICD::SCSICD(int lun, const unordered_set& sector_sizes) : Disk("SCCD", lun) { SetSectorSizes(sector_sizes); @@ -92,10 +92,7 @@ void SCSICD::Open(const Filepath& path) super::Open(path); FileSupport::SetPath(path); - SetUpCache(path); - - // Set RAW flag - cache->SetRawMode(rawfile); + SetUpCache(path, 0, rawfile); // Attention if ready if (IsReady()) { @@ -117,8 +114,8 @@ void SCSICD::OpenIso(const Filepath& path) } // Get file size - off_t file_size = fio.GetFileSize(); - if (file_size < 0x800) { + off_t size = fio.GetFileSize(); + if (size < 0x800) { fio.Close(); throw io_exception("ISO CD-ROM file size must be at least 2048 bytes"); } @@ -157,16 +154,16 @@ void SCSICD::OpenIso(const Filepath& path) if (rawfile) { // Size must be a multiple of 2536 - if (file_size % 2536) { + if (size % 2536) { throw io_exception("Raw ISO CD-ROM file size must be a multiple of 2536 bytes but is " - + to_string(file_size) + " bytes"); + + to_string(size) + " bytes"); } // Set the number of blocks - SetBlockCount((DWORD)(file_size / 0x930)); + SetBlockCount((DWORD)(size / 0x930)); } else { // Set the number of blocks - SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount())); + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); } // Create only one data track @@ -296,9 +293,9 @@ int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) // Recreate the disk cache Filepath path; tracks[index]->GetPath(path); + // Re-assign disk cache (no need to save) - cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)GetBlockCount())); - cache->SetRawMode(rawfile); + ResizeCache(path, rawfile); // Reset data index dataindex = index; diff --git a/src/raspberrypi/devices/scsicd.h b/src/raspberrypi/devices/scsicd.h index 40d3c8e5..c3154f1e 100644 --- a/src/raspberrypi/devices/scsicd.h +++ b/src/raspberrypi/devices/scsicd.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI CD-ROM ] @@ -27,10 +27,8 @@ class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport { public: - explicit SCSICD(const unordered_set&); + SCSICD(int, const unordered_set&); ~SCSICD() override = default; - SCSICD(SCSICD&) = delete; - SCSICD& operator=(const SCSICD&) = delete; bool Dispatch(scsi_command) override; diff --git a/src/raspberrypi/devices/scsihd.cpp b/src/raspberrypi/devices/scsihd.cpp index 7b9b3758..2a1f088c 100644 --- a/src/raspberrypi/devices/scsihd.cpp +++ b/src/raspberrypi/devices/scsihd.cpp @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI hard disk ] @@ -22,8 +22,8 @@ using namespace scsi_command_util; -SCSIHD::SCSIHD(const unordered_set& sector_sizes, bool removable, scsi_defs::scsi_level level) - : Disk(removable ? "SCRM" : "SCHD") +SCSIHD::SCSIHD(int lun, const unordered_set& sector_sizes, bool removable, scsi_defs::scsi_level level) + : Disk(removable ? "SCRM" : "SCHD", lun) { scsi_level = level; @@ -41,7 +41,13 @@ void SCSIHD::FinalizeSetup(const Filepath &path, off_t size, off_t image_offset) if (!IsRemovable()) { uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); string unit; - if (capacity >= 1048576) { + // 10 GiB and more + if (capacity >= 1099511627776) { + capacity /= 1099511627776; + unit = "GiB"; + } + // 1 MiB and more + else if (capacity >= 1048576) { capacity /= 1048576; unit = "MiB"; } @@ -75,17 +81,17 @@ void SCSIHD::Open(const Filepath& path) } // Get file size - off_t file_size = fio.GetFileSize(); + off_t size = fio.GetFileSize(); fio.Close(); // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); - SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount())); + SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount())); // Effective size must be a multiple of the sector size - file_size = (file_size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); + size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); - FinalizeSetup(path, file_size); + FinalizeSetup(path, size); } vector SCSIHD::InquiryInternal() const diff --git a/src/raspberrypi/devices/scsihd.h b/src/raspberrypi/devices/scsihd.h index 7b19c3f7..2536a95a 100644 --- a/src/raspberrypi/devices/scsihd.h +++ b/src/raspberrypi/devices/scsihd.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI hard disk ] @@ -26,10 +26,8 @@ class SCSIHD : public Disk, public FileSupport public: - SCSIHD(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; - SCSIHD(SCSIHD&) = delete; - SCSIHD& operator=(const SCSIHD&) = delete; void FinalizeSetup(const Filepath&, off_t, off_t = 0); diff --git a/src/raspberrypi/devices/scsihd_nec.cpp b/src/raspberrypi/devices/scsihd_nec.cpp index 3ca9dbdf..d81205e1 100644 --- a/src/raspberrypi/devices/scsihd_nec.cpp +++ b/src/raspberrypi/devices/scsihd_nec.cpp @@ -54,18 +54,18 @@ void SCSIHD_NEC::Open(const Filepath& path) } // Get file size - off_t file_size = fio.GetFileSize(); + off_t size = fio.GetFileSize(); // NEC root sector array root_sector; - if (file_size >= (off_t)root_sector.size() && !fio.Read(root_sector.data(), root_sector.size())) { + if (size >= (off_t)root_sector.size() && !fio.Read(root_sector.data(), root_sector.size())) { fio.Close(); throw io_exception("Can't read NEC hard disk file root sector"); } fio.Close(); // Effective size must be a multiple of 512 - file_size = (file_size / 512) * 512; + size = (size / 512) * 512; int image_size = 0; int sector_size = 0; @@ -76,11 +76,11 @@ void SCSIHD_NEC::Open(const Filepath& path) if (const char *ext = path.GetFileExt(); !strcasecmp(ext, ".hdn")) { // Assuming sector size 512, number of sectors 25, number of heads 8 as default settings image_offset = 0; - image_size = (int)file_size; + image_size = (int)size; sector_size = 512; sectors = 25; heads = 8; - cylinders = (int)(file_size >> 9); + cylinders = (int)(size >> 9); cylinders >>= 3; cylinders /= 25; } @@ -113,23 +113,23 @@ void SCSIHD_NEC::Open(const Filepath& path) } // Image size consistency check - if (image_offset + image_size > file_size || image_size % sector_size != 0) { + if (image_offset + image_size > size || image_size % sector_size != 0) { throw io_exception("Image size consistency check failed"); } // Calculate sector size - for (file_size = 16; file_size > 0; --file_size) { - if ((1 << file_size) == sector_size) + for (size = 16; size > 0; --size) { + if ((1 << size) == sector_size) break; } - if (file_size <= 0 || file_size > 16) { + if (size <= 0 || size > 16) { throw io_exception("Invalid NEC disk size"); } - SetSectorSizeShiftCount((uint32_t)file_size); + SetSectorSizeShiftCount((uint32_t)size); SetBlockCount(image_size >> GetSectorSizeShiftCount()); - FinalizeSetup(path, file_size, image_offset); + FinalizeSetup(path, size, image_offset); } vector SCSIHD_NEC::InquiryInternal() const diff --git a/src/raspberrypi/devices/scsihd_nec.h b/src/raspberrypi/devices/scsihd_nec.h index a0b9dc88..16f1a529 100644 --- a/src/raspberrypi/devices/scsihd_nec.h +++ b/src/raspberrypi/devices/scsihd_nec.h @@ -7,7 +7,7 @@ // Copyright (C) 2014-2020 GIMONS // Copyright (C) akuker // -// Licensed under the BSD 3-Clause License. +// Licensed under the BSD 3-Clause License. // See LICENSE file in the project root folder. // // [ SCSI NEC "Genuine" Hard Disk] @@ -20,7 +20,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; //=========================================================================== // @@ -31,10 +31,8 @@ class SCSIHD_NEC : public SCSIHD { public: - SCSIHD_NEC() : SCSIHD(sector_sizes, false) {} + explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {} ~SCSIHD_NEC() override = default; - SCSIHD_NEC(SCSIHD_NEC&) = delete; - SCSIHD_NEC& operator=(const SCSIHD_NEC&) = delete; void Open(const Filepath&) override; diff --git a/src/raspberrypi/devices/scsimo.cpp b/src/raspberrypi/devices/scsimo.cpp index 756ba3bc..d22304c5 100644 --- a/src/raspberrypi/devices/scsimo.cpp +++ b/src/raspberrypi/devices/scsimo.cpp @@ -19,7 +19,7 @@ using namespace scsi_command_util; -SCSIMO::SCSIMO(const unordered_set& sector_sizes) : Disk("SCMO") +SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk("SCMO", lun) { SetSectorSizes(sector_sizes); @@ -45,14 +45,14 @@ void SCSIMO::Open(const Filepath& path) } // Get file size - off_t file_size = fio.GetFileSize(); + off_t size = fio.GetFileSize(); fio.Close(); // For some capacities there are hard-coded, well-defined sector sizes and block counts - if (!SetGeometryForCapacity(file_size)) { + if (!SetGeometryForCapacity(size)) { // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); - SetBlockCount(file_size >> GetSectorSizeShiftCount()); + SetBlockCount(size >> GetSectorSizeShiftCount()); } SetReadOnly(false); @@ -62,7 +62,7 @@ void SCSIMO::Open(const Filepath& path) Disk::Open(path); FileSupport::SetPath(path); - SetUpCache(path); + SetUpCache(path, 0); // Attention if ready if (IsReady()) { diff --git a/src/raspberrypi/devices/scsimo.h b/src/raspberrypi/devices/scsimo.h index 409422f2..a03a40e5 100644 --- a/src/raspberrypi/devices/scsimo.h +++ b/src/raspberrypi/devices/scsimo.h @@ -24,10 +24,8 @@ class SCSIMO : public Disk, public FileSupport { public: - explicit SCSIMO(const unordered_set&); + SCSIMO(int, const unordered_set&); ~SCSIMO() override = default; - SCSIMO(SCSIMO&) = delete; - SCSIMO& operator=(const SCSIMO&) = delete; void Open(const Filepath&) override; diff --git a/src/raspberrypi/fileio.h b/src/raspberrypi/fileio.h index 1946d431..cae25dec 100644 --- a/src/raspberrypi/fileio.h +++ b/src/raspberrypi/fileio.h @@ -25,8 +25,8 @@ public: Fileio() = default; virtual ~Fileio(); - Fileio(Fileio&) = delete; - Fileio& operator=(const Fileio&) = delete; + Fileio(Fileio&) = default; + Fileio& operator=(const Fileio&) = default; bool Open(const char *fname, OpenMode mode); bool Open(const Filepath& path, OpenMode mode); diff --git a/src/raspberrypi/filepath.h b/src/raspberrypi/filepath.h index cddddc26..e0716086 100644 --- a/src/raspberrypi/filepath.h +++ b/src/raspberrypi/filepath.h @@ -35,7 +35,7 @@ public: Filepath(); virtual ~Filepath() = default; - Filepath(Filepath&) = delete; + Filepath(Filepath&) = default; Filepath& operator=(const Filepath&); void Clear(); diff --git a/src/raspberrypi/hal/gpiobus.cpp b/src/raspberrypi/hal/gpiobus.cpp index 0bfd1cae..7337b808 100644 --- a/src/raspberrypi/hal/gpiobus.cpp +++ b/src/raspberrypi/hal/gpiobus.cpp @@ -19,13 +19,13 @@ #include "config.h" #include "log.h" #include -#ifdef __linux +#ifdef __linux__ #include #endif using namespace std; -#ifdef __linux +#ifdef __linux__ //--------------------------------------------------------------------------- // // imported from bcm_host.c @@ -1258,7 +1258,7 @@ bool GPIOBUS::WaitSignal(int pin, int ast) void GPIOBUS::DisableIRQ() { -#ifdef __linux +#ifdef __linux__ if (rpitype == 4) { // RPI4 is disabled by GICC giccpmr = gicc[GICC_PMR]; diff --git a/src/raspberrypi/hal/gpiobus.h b/src/raspberrypi/hal/gpiobus.h index 6005944c..9dfb1bbf 100644 --- a/src/raspberrypi/hal/gpiobus.h +++ b/src/raspberrypi/hal/gpiobus.h @@ -15,7 +15,7 @@ #include "scsi.h" #include -#ifdef __linux +#ifdef __linux__ #include #endif diff --git a/src/raspberrypi/localizer.cpp b/src/raspberrypi/localizer.cpp index 841258a8..b3446d50 100644 --- a/src/raspberrypi/localizer.cpp +++ b/src/raspberrypi/localizer.cpp @@ -195,13 +195,11 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri } } - assert(it != localized_messages.end()); - - auto messages = it->second; - if (messages.empty()) { - return "Missing localization for enum value " + to_string((int)key); + if (it == localized_messages.end()) { + return "Missing default localization for enum value " + to_string((int)key); } + auto messages = it->second; string message = messages[key]; message = regex_replace(message, regex("%1"), arg1); diff --git a/src/raspberrypi/localizer.h b/src/raspberrypi/localizer.h index 67a05c87..c44e2168 100644 --- a/src/raspberrypi/localizer.h +++ b/src/raspberrypi/localizer.h @@ -15,7 +15,7 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi +using namespace std; enum class LocalizationKey { ERROR_AUTHENTICATION, diff --git a/src/raspberrypi/socket_connector.cpp b/src/raspberrypi/protobuf_serializer.cpp similarity index 66% rename from src/raspberrypi/socket_connector.cpp rename to src/raspberrypi/protobuf_serializer.cpp index a95d4253..7e08eae5 100644 --- a/src/raspberrypi/socket_connector.cpp +++ b/src/raspberrypi/protobuf_serializer.cpp @@ -9,48 +9,21 @@ #include "rascsi_interface.pb.h" #include "rascsi_exceptions.h" -#include "socket_connector.h" +#include "protobuf_serializer.h" #include -#include #include using namespace std; using namespace rascsi_interface; -int SocketConnector::ReadCommand(PbCommand& command, int socket) const -{ - // Wait for connection - sockaddr client = {}; - socklen_t socklen = sizeof(client); - int fd = accept(socket, &client, &socklen); - if (fd < 0) { - throw io_exception("accept() failed"); - } - - // Read magic string - vector magic(6); - size_t bytes_read = ReadBytes(fd, magic); - if (!bytes_read) { - return -1; - } - if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { - throw io_exception("Invalid magic"); - } - - // Fetch the command - DeserializeMessage(fd, command); - - return fd; -} - //--------------------------------------------------------------------------- // -// Serialize/Deserialize protobuf message: Length followed by the actual data. -// Little endian is assumed. +// Serialize/Deserialize protobuf message: Length followed by the actual data. +// A little endian platform is assumed. // //--------------------------------------------------------------------------- -void SocketConnector::SerializeMessage(int fd, const google::protobuf::Message& message) const +void ProtobufSerializer::SerializeMessage(int fd, const google::protobuf::Message& message) const { string data; message.SerializeToString(&data); @@ -67,7 +40,7 @@ void SocketConnector::SerializeMessage(int fd, const google::protobuf::Message& } } -void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& message) const +void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const { // Read the header with the size of the protobuf data vector header_buf(4); @@ -91,7 +64,7 @@ void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& mess message.ParseFromString(data); } -size_t SocketConnector::ReadBytes(int fd, vector& buf) const +size_t ProtobufSerializer::ReadBytes(int fd, vector& buf) const { size_t offset = 0; while (offset < buf.size()) { diff --git a/src/raspberrypi/socket_connector.h b/src/raspberrypi/protobuf_serializer.h similarity index 57% rename from src/raspberrypi/socket_connector.h rename to src/raspberrypi/protobuf_serializer.h index ac3054c2..4582e4f7 100644 --- a/src/raspberrypi/socket_connector.h +++ b/src/raspberrypi/protobuf_serializer.h @@ -5,28 +5,22 @@ // // Copyright (C) 2022 Uwe Seimet // -// Helper for serializing/deserializing protobuf messages to/fromn sockets +// Helper for serializing/deserializing protobuf messages // //--------------------------------------------------------------------------- #pragma once #include "google/protobuf/message.h" -#include "rascsi_interface.pb.h" -#include "command_context.h" -#include "localizer.h" -#include +#include -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi - -class SocketConnector +class ProtobufSerializer { public: - SocketConnector() = default; - ~SocketConnector() = default; + ProtobufSerializer() = default; + ~ProtobufSerializer() = default; - int ReadCommand(PbCommand&, int) const; void SerializeMessage(int, const google::protobuf::Message&) const; void DeserializeMessage(int, google::protobuf::Message&) const; size_t ReadBytes(int, vector&) const; diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 117600b1..cb8dd3cd 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -12,6 +12,7 @@ #include "config.h" #include "controllers/controller_manager.h" +#include "controllers/scsi_controller.h" #include "devices/device_factory.h" #include "devices/device.h" #include "devices/disk.h" @@ -19,16 +20,17 @@ #include "hal/gpiobus.h" #include "hal/systimer.h" #include "rascsi_exceptions.h" -#include "socket_connector.h" +#include "protobuf_serializer.h" #include "command_util.h" #include "rascsi_version.h" +#include "rascsi_executor.h" #include "rascsi_response.h" -#include "rasutil.h" #include "rascsi_image.h" #include "rascsi_interface.pb.h" +#include "rascsi_service.h" +#include "rasutil.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" -#include #include #include #include @@ -58,40 +60,18 @@ static const char COMPONENT_SEPARATOR = ':'; // Variable declarations // //--------------------------------------------------------------------------- -static volatile bool running; // Running flag static volatile bool active; // Processing flag -shared_ptr bus; // GPIO Bus -int monsocket; // Monitor Socket -pthread_t monthread; // Monitor Thread -pthread_mutex_t ctrl_mutex; // Semaphore for the ctrl array +RascsiService service; +GPIOBUS bus; string current_log_level; // Some versions of spdlog do not support get_log_level() string access_token; -unordered_set reserved_ids; DeviceFactory device_factory; -ControllerManager controller_manager; +ControllerManager controller_manager(bus); RascsiImage rascsi_image; -RascsiResponse rascsi_response(&device_factory, &rascsi_image); -const SocketConnector socket_connector; +RascsiResponse rascsi_response(device_factory, controller_manager, ScsiController::LUN_MAX); +RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); +const ProtobufSerializer serializer; -void DetachAll(); -static void *MonThread(void *); - -//--------------------------------------------------------------------------- -// -// Signal Processing -// -//--------------------------------------------------------------------------- -void KillHandler(int) -{ - // Stop instruction - running = false; -} - -//--------------------------------------------------------------------------- -// -// Banner Output -// -//--------------------------------------------------------------------------- void Banner(int argc, char* argv[]) { FPRT(stdout,"SCSI Target Emulator RaSCSI Reloaded "); @@ -125,97 +105,32 @@ void Banner(int argc, char* argv[]) } } -//--------------------------------------------------------------------------- -// -// Initialization -// -//--------------------------------------------------------------------------- - -bool InitService(int port) -{ - if (int result = pthread_mutex_init(&ctrl_mutex, nullptr); result != EXIT_SUCCESS){ - LOGERROR("Unable to create a mutex. Error code: %d", result) - return false; - } - - // Create socket for monitor - sockaddr_in server = {}; - monsocket = socket(PF_INET, SOCK_STREAM, 0); - server.sin_family = PF_INET; - server.sin_port = htons(port); - server.sin_addr.s_addr = htonl(INADDR_ANY); - - // allow address reuse - if (int yes = 1; setsockopt(monsocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { - return false; - } - - signal(SIGPIPE, SIG_IGN); - - // Bind - if (bind(monsocket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { - FPRT(stderr, "Error: Port %d is in use, is rascsi already running?\n", port); - return false; - } - - // Create Monitor Thread - pthread_create(&monthread, nullptr, MonThread, nullptr); - - // Interrupt handler settings - if (signal(SIGINT, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGHUP, KillHandler) == SIG_ERR) { - return false; - } - if (signal(SIGTERM, KillHandler) == SIG_ERR) { - return false; - } - - running = false; - active = false; - - return true; -} - bool InitBus() { - // GPIOBUS creation - bus = make_shared(); - // GPIO Initialization - if (!bus->Init()) { + if (!bus.Init()) { return false; } // Bus Reset - bus->Reset(); + bus.Reset(); return true; } void Cleanup() { - DetachAll(); + executor.DetachAll(); // Clean up and discard the bus - if (bus != nullptr) { - bus->Cleanup(); - } - - // Close the monitor socket - if (monsocket >= 0) { - close(monsocket); - } - - pthread_mutex_destroy(&ctrl_mutex); + bus.Cleanup(); } void Reset() { controller_manager.ResetAllControllers(); - bus->Reset(); + bus.Reset(); } bool ReadAccessToken(const char *filename) @@ -255,65 +170,6 @@ bool ReadAccessToken(const char *filename) return true; } -string ValidateLunSetup(const PbCommand& command) -{ - // Mapping of available LUNs (bit vector) to devices - unordered_map luns; - - // Collect LUN bit vectors of new devices - for (const auto& device : command.devices()) { - luns[device.id()] |= 1 << device.unit(); - } - - // Collect LUN bit vectors of existing devices - for (const auto& device : device_factory.GetAllDevices()) { - luns[device->GetId()] |= 1 << device->GetLun(); - } - - // LUN 0 must exist for all devices - for (auto const& [id, lun]: luns) { - if (!(lun & 0x01)) { - return "LUN 0 is missing for device ID " + to_string(id); - } - } - - return ""; -} - -bool SetLogLevel(const string& log_level) -{ - if (log_level == "trace") { - set_level(level::trace); - } - else if (log_level == "debug") { - set_level(level::debug); - } - else if (log_level == "info") { - set_level(level::info); - } - else if (log_level == "warn") { - set_level(level::warn); - } - else if (log_level == "err") { - set_level(level::err); - } - else if (log_level == "critical") { - set_level(level::critical); - } - else if (log_level == "off") { - set_level(level::off); - } - else { - return false; - } - - current_log_level = log_level; - - LOGINFO("Set log level to '%s'", current_log_level.c_str()) - - return true; -} - void LogDevices(string_view devices) { stringstream ss(devices.data()); @@ -324,333 +180,6 @@ void LogDevices(string_view devices) } } -string SetReservedIds(string_view ids) -{ - list ids_to_reserve; - stringstream ss(ids.data()); - string id; - while (getline(ss, id, ',')) { - if (!id.empty()) { - ids_to_reserve.push_back(id); - } - } - - unordered_set reserved; - for (string id_to_reserve : ids_to_reserve) { - int id; - if (!GetAsInt(id_to_reserve, id) || id > 7) { - return "Invalid ID " + id_to_reserve; - } - - if (controller_manager.FindController(id) != nullptr) { - return "ID " + id_to_reserve + " is currently in use"; - } - - reserved.insert(id); - } - - reserved_ids = reserved; - - if (!reserved_ids.empty()) { - string s; - bool isFirst = true; - for (auto const& reserved_id : reserved_ids) { - if (!isFirst) { - s += ", "; - } - isFirst = false; - s += to_string(reserved_id); - } - - LOGINFO("Reserved ID(s) set to %s", s.c_str()) - } - else { - LOGINFO("Cleared reserved ID(s)") - } - - return ""; -} - -void DetachAll() -{ - controller_manager.DeleteAllControllers(); - device_factory.DeleteAllDevices(); - FileSupport::UnreserveAll(); - - LOGINFO("Detached all devices") -} - -bool Attach(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun) -{ - const int id = pb_device.id(); - const int unit = pb_device.unit(); - const PbDeviceType type = pb_device.type(); - - if (controller_manager.GetDeviceByIdAndLun(id, unit) != nullptr) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(unit)); - } - - if (unit >= ScsiController::LUN_MAX) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(unit), to_string(ScsiController::LUN_MAX)); - } - - string filename = GetParam(pb_device, "file"); - - PrimaryDevice *device = device_factory.CreateDevice(type, filename, id); - if (device == nullptr) { - if (type == UNDEFINED) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_TYPE, filename); - } - else { - return ReturnLocalizedError(context, LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, PbDeviceType_Name(type)); - } - } - - // If no filename was provided the medium is considered removed - auto file_support = dynamic_cast(device); - device->SetRemoved(file_support != nullptr ? filename.empty() : false); - - device->SetLun(unit); - - try { - if (!pb_device.vendor().empty()) { - device->SetVendor(pb_device.vendor()); - } - if (!pb_device.product().empty()) { - device->SetProduct(pb_device.product()); - } - if (!pb_device.revision().empty()) { - device->SetRevision(pb_device.revision()); - } - } - catch(const illegal_argument_exception& e) { //NOSONAR This exception is handled properly - return ReturnStatus(context, false, e.get_msg()); - } - - if (pb_device.block_size()) { - auto disk = dynamic_cast(device); - if (disk != nullptr && disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, pb_device.block_size())) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE, to_string(pb_device.block_size())); - } - } - else { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, PbDeviceType_Name(type)); - } - } - - // File check (type is HD, for removable media drives, CD and MO the medium (=file) may be inserted later - if (file_support != nullptr && !device->IsRemovable() && filename.empty()) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); - } - - Filepath filepath; - if (file_support != nullptr && !filename.empty()) { - filepath.SetPath(filename.c_str()); - string initial_filename = filepath.GetPath(); - - int id; - int unit; - if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); - } - - try { - try { - file_support->Open(filepath); - } - catch(const file_not_found_exception&) { - // If the file does not exist search for it in the default image folder - filepath.SetPath(string(rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); - - if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); - } - - file_support->Open(filepath); - } - } - catch(const io_exception& e) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_FILE_OPEN, initial_filename, e.get_msg()); - } - - file_support->ReserveFile(filepath, device->GetId(), device->GetLun()); - } - - // Only non read-only devices support protect/unprotect - // This operation must not be executed before Open() because Open() overrides some settings. - if (device->IsProtectable() && !device->IsReadOnly()) { - device->SetProtected(pb_device.protected_()); - } - - // Stop the dry run here, before permanently modifying something - if (dryRun) { - device_factory.DeleteDevice(*device); - - return true; - } - - unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; - if (!device->SupportsFile()) { - params.erase("file"); - } - if (!device->Init(params)) { - device_factory.DeleteDevice(*device); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(type), to_string(id), to_string(unit)); - } - - pthread_mutex_lock(&ctrl_mutex); - - if (!controller_manager.CreateScsiController(bus, device)) { - pthread_mutex_unlock(&ctrl_mutex); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_SCSI_CONTROLLER); - } - pthread_mutex_unlock(&ctrl_mutex); - - string msg = "Attached "; - if (device->IsReadOnly()) { - msg += "read-only "; - } - else if (device->IsProtectable() && device->IsProtected()) { - msg += "protected "; - } - msg += device->GetType() + " device, ID " + to_string(id) + ", unit " + to_string(unit); - LOGINFO("%s", msg.c_str()) - - return true; -} - -bool Detach(const CommandContext& context, PrimaryDevice *device, bool dryRun) -{ - if (!device->GetLun()) { - for (const Device *d : device_factory.GetAllDevices()) { - // LUN 0 can only be detached if there is no other LUN anymore - if (d->GetId() == device->GetId() && d->GetLun()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_LUN0); - } - } - } - - if (!dryRun) { - // Remember data that going to be deleted but are used for logging - int id = device->GetId(); - int lun = device->GetLun(); - string type = device->GetType(); - - if (auto file_support = dynamic_cast(device); file_support != nullptr) { - file_support->UnreserveFile(); - } - - // Delete the existing unit - pthread_mutex_lock(&ctrl_mutex); - if (!controller_manager.FindController(id)->DeleteDevice(device)) { - pthread_mutex_unlock(&ctrl_mutex); - - return ReturnLocalizedError(context, LocalizationKey::ERROR_DETACH); - } - device_factory.DeleteDevice(*device); - pthread_mutex_unlock(&ctrl_mutex); - - LOGINFO("Detached %s device with ID %d, unit %d", type.c_str(), id, lun) - } - - return true; -} - -bool Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, Device *device, bool dryRun) -{ - if (!device->IsRemoved()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_EJECT_REQUIRED); - } - - if (!pb_device.vendor().empty() || !pb_device.product().empty() || !pb_device.revision().empty()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_DEVICE_NAME_UPDATE); - } - - string filename = GetParam(pb_device, "file"); - if (filename.empty()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME); - } - - if (dryRun) { - return true; - } - - LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", - filename.c_str(), device->GetType().c_str(), pb_device.id(), pb_device.unit()) - - auto disk = dynamic_cast(device); - - if (pb_device.block_size()) { - if (disk != nullptr&& disk->IsSectorSizeConfigurable()) { - if (!disk->SetConfiguredSectorSize(device_factory, pb_device.block_size())) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE, to_string(pb_device.block_size())); - } - } - else { - return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, device->GetType()); - } - } - - int id; - int unit; - Filepath filepath; - filepath.SetPath(filename.c_str()); - string initial_filename = filepath.GetPath(); - - if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); - } - - auto file_support = dynamic_cast(device); - try { - try { - file_support->Open(filepath); - } - catch(const file_not_found_exception&) { - // If the file does not exist search for it in the default image folder - filepath.SetPath((rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); - - if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(unit)); - } - - file_support->Open(filepath); - } - } - catch(const io_exception& e) { //NOSONAR This exception is handled properly - return ReturnLocalizedError(context, LocalizationKey::ERROR_FILE_OPEN, initial_filename, e.get_msg()); - } - - file_support->ReserveFile(filepath, device->GetId(), device->GetLun()); - - // Only non read-only devices support protect/unprotect. - // This operation must not be executed before Open() because Open() overrides some settings. - if (device->IsProtectable() && !device->IsReadOnly()) { - device->SetProtected(pb_device.protected_()); - } - - if (disk != nullptr) { - disk->MediumChanged(); - } - - return true; -} - void TerminationHandler(int signum) { Cleanup(); @@ -658,251 +187,6 @@ void TerminationHandler(int signum) exit(signum); } -//--------------------------------------------------------------------------- -// -// Command Processing -// -//--------------------------------------------------------------------------- - -bool ProcessCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, const PbCommand& command, bool dryRun) -{ - const int id = pb_device.id(); - const int unit = pb_device.unit(); - const PbDeviceType type = pb_device.type(); - const PbOperation operation = command.operation(); - const map params = { command.params().begin(), command.params().end() }; - - ostringstream s; - s << (dryRun ? "Validating: " : "Executing: "); - s << "operation=" << PbOperation_Name(operation); - - if (!params.empty()) { - s << ", command params="; - bool isFirst = true; - for (const auto& [key, value]: params) { - if (!isFirst) { - s << ", "; - } - isFirst = false; - string v = key != "token" ? value : "???"; - s << "'" << key << "=" << v << "'"; - } - } - - s << ", device id=" << id << ", unit=" << unit << ", type=" << PbDeviceType_Name(type); - - if (pb_device.params_size()) { - s << ", device params="; - bool isFirst = true; - for (const auto& [key, value]: pb_device.params()) { - if (!isFirst) { - s << ":"; - } - isFirst = false; - s << "'" << key << "=" << value << "'"; - } - } - - 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()) - - // Check the Controller Number - if (id < 0) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_ID); - } - if (id >= ControllerManager::DEVICE_MAX) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); - } - - if (operation == ATTACH && reserved_ids.find(id) != reserved_ids.end()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_RESERVED_ID, to_string(id)); - } - - // Check the Unit Number - if (unit < 0 || unit >= ScsiController::LUN_MAX) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(unit), to_string(ScsiController::LUN_MAX - 1)); - } - - if (operation == ATTACH) { - return Attach(context, pb_device, dryRun); - } - - // Does the controller exist? - if (!dryRun && controller_manager.FindController(id) == nullptr) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id)); - } - - // Does the unit exist? - PrimaryDevice *device = controller_manager.GetDeviceByIdAndLun(id, unit); - if (device == nullptr) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(unit)); - } - - if (operation == DETACH) { - return Detach(context, device, dryRun); - } - - if ((operation == START || operation == STOP) && !device->IsStoppable()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, device->GetType()); - } - - if ((operation == INSERT || operation == EJECT) && !device->IsRemovable()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, device->GetType()); - } - - if ((operation == PROTECT || operation == UNPROTECT) && !device->IsProtectable()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, device->GetType()); - } - if ((operation == PROTECT || operation == UNPROTECT) && !device->IsReady()) { - return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_READY, device->GetType()); - } - - switch (operation) { - case START: - if (!dryRun) { - LOGINFO("Start requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit) - - if (!device->Start()) { - LOGWARN("Starting %s ID %d, unit %d failed", device->GetType().c_str(), id, unit) - } - } - break; - - case STOP: - if (!dryRun) { - LOGINFO("Stop requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit) - - // STOP is idempotent - device->Stop(); - } - break; - - case INSERT: - return Insert(context, pb_device, device, dryRun); - - case EJECT: - if (!dryRun) { - LOGINFO("Eject requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit) - - if (!device->Eject(true)) { - LOGWARN("Ejecting %s ID %d, unit %d failed", device->GetType().c_str(), id, unit) - } - } - break; - - case PROTECT: - if (!dryRun) { - LOGINFO("Write protection requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit) - - // PROTECT is idempotent - device->SetProtected(true); - } - break; - - case UNPROTECT: - if (!dryRun) { - LOGINFO("Write unprotection requested for %s ID %d, unit %d", device->GetType().c_str(), id, unit) - - // UNPROTECT is idempotent - device->SetProtected(false); - } - break; - - case ATTACH: - case DETACH: - // The non dry-run case has been handled before the switch - assert(dryRun); - break; - - case CHECK_AUTHENTICATION: - case NO_OPERATION: - // Do nothing, just log - LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) - break; - - default: - return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION); - } - - return true; -} - -bool ProcessCmd(const CommandContext& context, const PbCommand& command) -{ - switch (command.operation()) { - case DETACH_ALL: - DetachAll(); - return ReturnStatus(context); - - case RESERVE_IDS: { - const string ids = GetParam(command, "ids"); - string error = SetReservedIds(ids); - if (!error.empty()) { - return ReturnStatus(context, false, error); - } - - return ReturnStatus(context); - } - - case CREATE_IMAGE: - return rascsi_image.CreateImage(context, command); - - case DELETE_IMAGE: - return rascsi_image.DeleteImage(context, command); - - case RENAME_IMAGE: - return rascsi_image.RenameImage(context, command); - - case COPY_IMAGE: - return rascsi_image.CopyImage(context, command); - - case PROTECT_IMAGE: - case UNPROTECT_IMAGE: - return rascsi_image.SetImagePermissions(context, command); - - default: - // This is a device-specific command handled below - break; - } - - // Remember the list of reserved files, than run the dry run - const auto& reserved_files = FileSupport::GetReservedFiles(); - for (const auto& device : command.devices()) { - if (!ProcessCmd(context, device, command, true)) { - // Dry run failed, restore the file list - FileSupport::SetReservedFiles(reserved_files); - return false; - } - } - - // Restore the list of reserved files before proceeding - FileSupport::SetReservedFiles(reserved_files); - - if (string result = ValidateLunSetup(command); !result.empty()) { - return ReturnStatus(context, false, result); - } - - for (const auto& device : command.devices()) { - if (!ProcessCmd(context, device, command, false)) { - return false; - } - } - - // ATTACH and DETACH return the device list - if (context.fd != -1 && (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 command; - PbResult result; - rascsi_response.GetDevicesInfo(result, command); - socket_connector.SerializeMessage(context.fd, result); - return true; - } - - return ReturnStatus(context); -} - bool ProcessId(const string& id_spec, int& id, int& unit) { if (size_t separator_pos = id_spec.find(COMPONENT_SEPARATOR); separator_pos == string::npos) { @@ -922,61 +206,6 @@ bool ProcessId(const string& id_spec, int& id, int& unit) return true; } -void ShutDown(const CommandContext& context, const string& mode) { - if (mode.empty()) { - ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING); - return; - } - - PbResult result; - result.set_status(true); - - if (mode == "rascsi") { - LOGINFO("RaSCSI shutdown requested"); - - socket_connector.SerializeMessage(context.fd, result); - - TerminationHandler(0); - } - - // The root user has UID 0 - if (getuid()) { - ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_PERMISSION); - return; - } - - if (mode == "system") { - LOGINFO("System shutdown requested") - - socket_connector.SerializeMessage(context.fd, result); - - DetachAll(); - - if (system("init 0") == -1) { - LOGERROR("System shutdown failed: %s", strerror(errno)) - } - } - else if (mode == "reboot") { - LOGINFO("System reboot requested") - - socket_connector.SerializeMessage(context.fd, result); - - DetachAll(); - - if (system("init 6") == -1) { - LOGERROR("System reboot failed: %s", strerror(errno)) - } - } - else { - ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID); - } -} - -//--------------------------------------------------------------------------- -// -// Argument Parsing -// -//--------------------------------------------------------------------------- bool ParseArgument(int argc, char* argv[], int& port) { PbCommand command; @@ -1024,7 +253,7 @@ bool ParseArgument(int argc, char* argv[], int& port) continue; case 'F': { - if (string result = rascsi_image.SetDefaultImageFolder(optarg); !result.empty()) { + if (string result = rascsi_image.SetDefaultFolder(optarg); !result.empty()) { cerr << result << endl; return false; } @@ -1062,7 +291,7 @@ bool ParseArgument(int argc, char* argv[], int& port) continue; case 'r': { - string error = SetReservedIds(optarg); + string error = executor.SetReservedIds(optarg); if (!error.empty()) { cerr << error << endl; return false; @@ -1123,22 +352,22 @@ bool ParseArgument(int argc, char* argv[], int& port) name = ""; } - if (!log_level.empty() && !SetLogLevel(log_level)) { - LOGWARN("Invalid log level '%s'", log_level.c_str()) + if (!log_level.empty() && executor.SetLogLevel(log_level)) { + current_log_level = log_level; } // Attach all specified devices command.set_operation(ATTACH); Localizer localizer; - CommandContext context(socket_connector, localizer, -1, locale); - if (!ProcessCmd(context, command)) { + CommandContext context(serializer, localizer, -1, locale); + if (!executor.ProcessCmd(context, command)) { return false; } // Display and log the device list PbServerInfo server_info; - rascsi_response.GetDevices(server_info); + rascsi_response.GetDevices(server_info, rascsi_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); @@ -1147,29 +376,6 @@ bool ParseArgument(int argc, char* argv[], int& port) return true; } -//--------------------------------------------------------------------------- -// -// Pin the thread to a specific CPU -// -//--------------------------------------------------------------------------- -void FixCpu(int cpu) -{ -#ifdef __linux - // Get the number of CPUs - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); - int cpus = CPU_COUNT(&cpuset); - - // Set the thread affinity - if (cpu < cpus) { - CPU_ZERO(&cpuset); - CPU_SET(cpu, &cpuset); - sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); - } -#endif -} - static bool ExecuteCommand(PbCommand& command, CommandContext& context) { context.locale = GetParam(command, "locale"); @@ -1194,17 +400,19 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) switch(command.operation()) { case LOG_LEVEL: { string log_level = GetParam(command, "level"); - if (bool status = SetLogLevel(log_level); !status) { + if (bool status = executor.SetLogLevel(log_level); !status) { ReturnLocalizedError(context, LocalizationKey::ERROR_LOG_LEVEL, log_level); } else { + current_log_level = log_level; + ReturnStatus(context); } break; } case DEFAULT_FOLDER: { - if (string status = rascsi_image.SetDefaultImageFolder(GetParam(command, "folder")); !status.empty()) { + if (string status = rascsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { ReturnStatus(context, false, status); } else { @@ -1214,42 +422,43 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) } case DEVICES_INFO: { - rascsi_response.GetDevicesInfo(result, command); - socket_connector.SerializeMessage(context.fd, result); + rascsi_response.GetDevicesInfo(result, command, rascsi_image.GetDefaultFolder()); + serializer.SerializeMessage(context.fd, result); break; } case DEVICE_TYPES_INFO: { - result.set_allocated_device_types_info(rascsi_response.GetDeviceTypesInfo(result)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_device_types_info(rascsi_response.GetDeviceTypesInfo(result).release()); + serializer.SerializeMessage(context.fd, result); break; } case SERVER_INFO: { result.set_allocated_server_info(rascsi_response.GetServerInfo( - result, reserved_ids, current_log_level, GetParam(command, "folder_pattern"), - GetParam(command, "file_pattern"), rascsi_image.GetDepth())); - socket_connector.SerializeMessage(context.fd, result); + result, executor.GetReservedIds(), current_log_level, rascsi_image.GetDefaultFolder(), + GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), + rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.fd, result); break; } case VERSION_INFO: { - result.set_allocated_version_info(rascsi_response.GetVersionInfo(result)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_version_info(rascsi_response.GetVersionInfo(result).release()); + serializer.SerializeMessage(context.fd, result); break; } case LOG_LEVEL_INFO: { - result.set_allocated_log_level_info(rascsi_response.GetLogLevelInfo(result, current_log_level)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_log_level_info(rascsi_response.GetLogLevelInfo(result, current_log_level).release()); + serializer.SerializeMessage(context.fd, result); break; } case DEFAULT_IMAGE_FILES_INFO: { result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result, - GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), - rascsi_image.GetDepth())); - socket_connector.SerializeMessage(context.fd, result); + rascsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), + GetParam(command, "file_pattern"), rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.fd, result); break; } @@ -1259,11 +468,11 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) } else { auto image_file = make_unique(); - bool status = rascsi_response.GetImageFile(image_file.get(), filename); + bool status = rascsi_response.GetImageFile(*image_file.get(), rascsi_image.GetDefaultFolder(), filename); if (status) { result.set_status(true); result.set_allocated_image_file_info(image_file.get()); - socket_connector.SerializeMessage(context.fd, result); + serializer.SerializeMessage(context.fd, result); } else { ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_FILE_INFO); @@ -1273,32 +482,35 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) } case NETWORK_INTERFACES_INFO: { - result.set_allocated_network_interfaces_info(rascsi_response.GetNetworkInterfacesInfo(result)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_network_interfaces_info(rascsi_response.GetNetworkInterfacesInfo(result).release()); + serializer.SerializeMessage(context.fd, result); break; } case MAPPING_INFO: { - result.set_allocated_mapping_info(rascsi_response.GetMappingInfo(result)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_mapping_info(rascsi_response.GetMappingInfo(result).release()); + serializer.SerializeMessage(context.fd, result); break; } case OPERATION_INFO: { result.set_allocated_operation_info(rascsi_response.GetOperationInfo(result, - rascsi_image.GetDepth())); - socket_connector.SerializeMessage(context.fd, result); + rascsi_image.GetDepth()).release()); + serializer.SerializeMessage(context.fd, result); break; } case RESERVED_IDS_INFO: { - result.set_allocated_reserved_ids_info(rascsi_response.GetReservedIds(result, reserved_ids)); - socket_connector.SerializeMessage(context.fd, result); + result.set_allocated_reserved_ids_info(rascsi_response.GetReservedIds(result, + executor.GetReservedIds()).release()); + serializer.SerializeMessage(context.fd, result); break; } case SHUT_DOWN: { - ShutDown(context, GetParam(command, "mode")); + if (executor.ShutDown(context, GetParam(command, "mode"))) { + TerminationHandler(0); + } break; } @@ -1308,7 +520,7 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) usleep(500 * 1000); } - ProcessCmd(context, command); + executor.ProcessCmd(context, command); break; } } @@ -1316,70 +528,10 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context) return true; } -//--------------------------------------------------------------------------- -// -// Monitor Thread -// -//--------------------------------------------------------------------------- -static void *MonThread(void *) //NOSONAR The pointer cannot be const void * because of the thread API -{ - // Scheduler Settings - sched_param schedparam; - schedparam.sched_priority = 0; - sched_setscheduler(0, SCHED_IDLE, &schedparam); - - // Set the affinity to a specific processor core - FixCpu(2); - - // Wait for the execution to start - while (!running) { - usleep(1); - } - - // Set up the monitor socket to receive commands - listen(monsocket, 1); - - Localizer localizer; - - while (true) { - CommandContext context(socket_connector, localizer, -1, ""); - - try { - PbCommand command; - context.fd = socket_connector.ReadCommand(command, monsocket); - if (context.fd == -1) { - continue; - } - - if (!ExecuteCommand(command, context)) { - continue; - } - } - catch(const io_exception& e) { - LOGWARN("%s", e.get_msg().c_str()) - - // Fall through - } - - if (context.fd >= 0) { - close(context.fd); - } - } - - return nullptr; -} - -//--------------------------------------------------------------------------- -// -// Main processing -// -//--------------------------------------------------------------------------- int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; - BUS::phase_t phase; - // added setvbuf to override stdout buffering, so logs are written immediately and not when the process exits. setvbuf(stdout, nullptr, _IONBF, 0); @@ -1395,7 +547,7 @@ int main(int argc, char* argv[]) } } - SetLogLevel("info"); + executor.SetLogLevel("info"); // Create a thread-safe stdout logger to process the log messages auto logger = stdout_color_mt("rascsi stdout logger"); @@ -1405,7 +557,7 @@ int main(int argc, char* argv[]) } int port = DEFAULT_PORT; - if (!InitService(port)) { + if (!service.Init(&ExecuteCommand, port)) { return EPERM; } @@ -1438,16 +590,13 @@ int main(int argc, char* argv[]) #endif // Start execution - running = true; + service.SetRunning(true); // Main Loop - while (running) { - // Work initialization - phase = BUS::phase_t::busfree; - + while (service.IsRunning()) { #ifdef USE_SEL_EVENT_ENABLE // SEL signal polling - if (!bus->PollSelectEvent()) { + if (!bus.PollSelectEvent()) { // Stop on interrupt if (errno == EINTR) { break; @@ -1456,11 +605,10 @@ int main(int argc, char* argv[]) } // Get the bus - bus->Acquire(); + bus.Acquire(); #else - bus->Acquire(); - if (!bus->GetSEL()) { - // TODO Why sleep for 0 microseconds? + bus.Acquire(); + if (!bus.GetSEL()) { usleep(0); continue; } @@ -1468,27 +616,27 @@ int main(int argc, char* argv[]) // 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()) { + if (bus.GetBSY()) { uint32_t now = SysTimer::GetTimerLow(); while ((SysTimer::GetTimerLow() - now) < 3 * 1000 * 1000) { - bus->Acquire(); - if (!bus->GetBSY()) { + bus.Acquire(); + if (!bus.GetBSY()) { break; } } } // Stop because the bus is busy or another device responded - if (bus->GetBSY() || !bus->GetSEL()) { + if (bus.GetBSY() || !bus.GetSEL()) { continue; } int initiator_id = -1; // The initiator and target ID - BYTE id_data = bus->GetDAT(); + BYTE id_data = bus.GetDAT(); - pthread_mutex_lock(&ctrl_mutex); + BUS::phase_t phase = BUS::phase_t::busfree; // Identify the responsible controller shared_ptr controller = controller_manager.IdentifyController(id_data); @@ -1502,21 +650,20 @@ int main(int argc, char* argv[]) // Return to bus monitoring if the selection phase has not started if (phase != BUS::phase_t::selection) { - pthread_mutex_unlock(&ctrl_mutex); continue; } // Start target device active = true; -#ifndef USE_SEL_EVENT_ENABLE +#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 (running) { + while (service.IsRunning()) { // Target drive phase = controller->Process(initiator_id); @@ -1525,10 +672,8 @@ int main(int argc, char* argv[]) break; } } - pthread_mutex_unlock(&ctrl_mutex); - -#ifndef USE_SEL_EVENT_ENABLE +#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); diff --git a/src/raspberrypi/rascsi_exceptions.h b/src/raspberrypi/rascsi_exceptions.h index 75a39b88..a28c4bf1 100644 --- a/src/raspberrypi/rascsi_exceptions.h +++ b/src/raspberrypi/rascsi_exceptions.h @@ -13,41 +13,31 @@ #include #include -using namespace std; //NOSONAR Not relevant for rascsi - -class illegal_argument_exception final : public exception { -private: +class io_exception : public std::exception +{ string msg; public: - explicit illegal_argument_exception(const string& msg) : msg(msg) {} - ~illegal_argument_exception() override = default; - const string& get_msg() const { return msg; } -}; - -class io_exception : public exception { -private: - string msg; - -public: explicit io_exception(const string& msg) : msg(msg) {} ~io_exception() override = default; const string& get_msg() const { return msg; } }; -class file_not_found_exception : public io_exception { +class file_not_found_exception : public io_exception +{ using io_exception::io_exception; }; -class scsi_error_exception final : public exception { -private: +class scsi_error_exception final : public std::exception +{ scsi_defs::sense_key sense_key; scsi_defs::asc asc; scsi_defs::status status; public: + scsi_error_exception(scsi_defs::sense_key sense_key = scsi_defs::sense_key::ABORTED_COMMAND, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) diff --git a/src/raspberrypi/rascsi_executor.cpp b/src/raspberrypi/rascsi_executor.cpp new file mode 100644 index 00000000..10d77202 --- /dev/null +++ b/src/raspberrypi/rascsi_executor.cpp @@ -0,0 +1,762 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2021-2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "log.h" +#include "controllers/controller_manager.h" +#include "controllers/scsi_controller.h" +#include "devices/device_factory.h" +#include "devices/primary_device.h" +#include "devices/disk.h" +#include "devices/file_support.h" +#include "rascsi_service.h" +#include "rascsi_response.h" +#include "rascsi_image.h" +#include "rascsi_exceptions.h" +#include "localizer.h" +#include "command_util.h" +#include "rasutil.h" +#include "spdlog/spdlog.h" +#include "rascsi_executor.h" +#include + +using namespace spdlog; +using namespace command_util; +using namespace ras_util; + +bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, + const PbCommand& command, bool dryRun) +{ + PrintCommand(command, pb_device, dryRun); + + const int id = pb_device.id(); + const int lun = pb_device.unit(); + + if (!ValidateIdAndLun(context, id, lun)) { + return false; + } + + const PbOperation operation = command.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); + + if (!ValidationOperationAgainstDevice(context, device, operation)) { + return false; + } + + switch (operation) { + case START: + return Start(device, dryRun); + + case STOP: + return Stop(device, dryRun); + + case ATTACH: + return Attach(context, pb_device, dryRun); + + case DETACH: + return Detach(context, device, dryRun); + + case INSERT: + return Insert(context, pb_device, device, dryRun); + + case EJECT: + return Eject(device, dryRun); + + case PROTECT: + return Protect(device, dryRun); + + case UNPROTECT: + return Unprotect(device, dryRun); + break; + + case CHECK_AUTHENTICATION: + case NO_OPERATION: + // Do nothing, just log + LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) + break; + + default: + return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION); + } + + return true; +} + +bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command) +{ + switch (command.operation()) { + case DETACH_ALL: + DetachAll(); + return ReturnStatus(context); + + case RESERVE_IDS: { + const string ids = GetParam(command, "ids"); + if (string error = SetReservedIds(ids); !error.empty()) { + return ReturnStatus(context, false, error); + } + + return ReturnStatus(context); + } + + case CREATE_IMAGE: + return rascsi_image.CreateImage(context, command); + + case DELETE_IMAGE: + return rascsi_image.DeleteImage(context, command); + + case RENAME_IMAGE: + return rascsi_image.RenameImage(context, command); + + case COPY_IMAGE: + return rascsi_image.CopyImage(context, command); + + case PROTECT_IMAGE: + case UNPROTECT_IMAGE: + return rascsi_image.SetImagePermissions(context, command); + + default: + // This is a device-specific command handled below + break; + } + + // Remember the list of reserved files, than run the dry run + const auto& reserved_files = FileSupport::GetReservedFiles(); + for (const auto& device : command.devices()) { + if (!ProcessCmd(context, device, command, true)) { + // Dry run failed, restore the file list + FileSupport::SetReservedFiles(reserved_files); + return false; + } + } + + // Restore the list of reserved files before proceeding + FileSupport::SetReservedFiles(reserved_files); + + if (string result = ValidateLunSetup(command); !result.empty()) { + return ReturnStatus(context, false, result); + } + + for (const auto& device : command.devices()) { + if (!ProcessCmd(context, device, command, false)) { + return false; + } + } + + // ATTACH and DETACH return the device list + if (context.fd != -1 && (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; + rascsi_response.GetDevicesInfo(result, cmd, rascsi_image.GetDefaultFolder()); + serializer.SerializeMessage(context.fd, result); + return true; + } + + return ReturnStatus(context); +} + +bool RascsiExecutor::SetLogLevel(const string& log_level) const +{ + if (log_level == "trace") { + set_level(level::trace); + } + else if (log_level == "debug") { + set_level(level::debug); + } + else if (log_level == "info") { + set_level(level::info); + } + else if (log_level == "warn") { + set_level(level::warn); + } + else if (log_level == "err") { + set_level(level::err); + } + else if (log_level == "critical") { + set_level(level::critical); + } + else if (log_level == "off") { + set_level(level::off); + } + else { + LOGWARN("Invalid log level '%s'", log_level.c_str()) + + return false; + } + + LOGINFO("Set log level to '%s'", log_level.c_str()) + + return true; +} + +bool RascsiExecutor::Start(shared_ptr device, bool dryRun) const +{ + if (!dryRun) { + LOGINFO("Start requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + + if (!device->Start()) { + LOGWARN("Starting %s ID %d, unit %d failed", device->GetType().c_str(), device->GetId(), device->GetLun()) + } + } + + return true; +} + +bool RascsiExecutor::Stop(shared_ptr device, bool dryRun) const +{ + if (!dryRun) { + LOGINFO("Stop requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + + // STOP is idempotent + device->Stop(); + } + + return true; +} + +bool RascsiExecutor::Eject(shared_ptr device, bool dryRun) const +{ + if (!dryRun) { + LOGINFO("Eject requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + + if (!device->Eject(true)) { + LOGWARN("Ejecting %s ID %d, unit %d failed", device->GetType().c_str(), device->GetId(), device->GetLun()) + } + } + + return true; +} + +bool RascsiExecutor::Protect(shared_ptr device, bool dryRun) const +{ + if (!dryRun) { + LOGINFO("Write protection requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), + device->GetLun()) + + // PROTECT is idempotent + device->SetProtected(true); + } + + return true; +} + +bool RascsiExecutor::Unprotect(shared_ptr device, bool dryRun) const +{ + if (!dryRun) { + LOGINFO("Write unprotection requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), + device->GetLun()) + + // UNPROTECT is idempotent + device->SetProtected(false); + } + + return true; +} + +bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun) +{ + const int id = pb_device.id(); + const int lun = pb_device.unit(); + const PbDeviceType type = pb_device.type(); + + if (lun >= ScsiController::LUN_MAX) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX)); + } + + if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun)); + } + + if (reserved_ids.find(id) != reserved_ids.end()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_RESERVED_ID, to_string(id)); + } + + string filename = GetParam(pb_device, "file"); + + auto device = CreateDevice(context, type, lun, filename); + if (device == nullptr) { + return false; + } + + // If no filename was provided the medium is considered removed + auto file_support = dynamic_cast(device.get()); + device->SetRemoved(file_support != nullptr ? filename.empty() : false); + + if (!SetProductData(context, pb_device, device)) { + return false; + } + + if (!SetSectorSize(context, PbDeviceType_Name(type), device, pb_device.block_size())) { + return false; + } + + string full_path; + if (file_support != nullptr) { + // File check (type is HD, for removable media drives, CD and MO the medium (=file) may be inserted later + if (!device->IsRemovable() && filename.empty()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); + } + + if (!ValidateImageFile(context, device, filename, full_path)) { + return false; + } + } + + // Only non read-only devices support protect/unprotect + // This operation must not be executed before Open() because Open() overrides some settings. + if (device->IsProtectable() && !device->IsReadOnly()) { + device->SetProtected(pb_device.protected_()); + } + + // Stop the dry run here, before permanently modifying something + if (dryRun) { + return true; + } + + unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; + if (!device->SupportsFile()) { + params.erase("file"); + } + + if (!device->Init(params)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(type), + to_string(id), to_string(lun)); + } + + if (!controller_manager.AttachToScsiController(id, device)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_SCSI_CONTROLLER); + } + + Filepath filepath; + filepath.SetPath(full_path.c_str()); + file_support->ReserveFile(filepath, id, lun); + + string msg = "Attached "; + if (device->IsReadOnly()) { + msg += "read-only "; + } + else if (device->IsProtectable() && device->IsProtected()) { + msg += "protected "; + } + msg += device->GetType() + " device, ID " + to_string(id) + ", unit " + to_string(lun); + LOGINFO("%s", msg.c_str()) + + return true; +} + +bool RascsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, + shared_ptr device, bool dryRun) const +{ + if (!device->IsRemoved()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_EJECT_REQUIRED); + } + + if (!pb_device.vendor().empty() || !pb_device.product().empty() || !pb_device.revision().empty()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_DEVICE_NAME_UPDATE); + } + + string filename = GetParam(pb_device, "file"); + if (filename.empty()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME); + } + + if (dryRun) { + return true; + } + + LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", + filename.c_str(), device->GetType().c_str(), pb_device.id(), pb_device.unit()) + + if (!SetSectorSize(context, device->GetType(), device, pb_device.block_size())) { + return false; + } + + string full_path; + if (!ValidateImageFile(context, device, filename, full_path)) { + return false; + } + + Filepath filepath; + filepath.SetPath(full_path.c_str()); + dynamic_pointer_cast(device)->ReserveFile(filepath, device->GetId(), device->GetLun()); + + // Only non read-only devices support protect/unprotect. + // This operation must not be executed before Open() because Open() overrides some settings. + if (device->IsProtectable() && !device->IsReadOnly()) { + device->SetProtected(pb_device.protected_()); + } + + if (auto disk = dynamic_pointer_cast(device); disk != nullptr) { + disk->MediumChanged(); + } + + return true; +} + +bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr device, bool dryRun) const +{ + // LUN 0 can only be detached if there is no other LUN anymore + if (!device->GetLun() && controller_manager.FindController(device->GetId())->GetLunCount() > 1) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_LUN0); + } + + if (!dryRun) { + // Prepare log string before the device data are lost due to deletion + string s = "Detached " + device->GetType() + " device with ID " + to_string(device->GetId()) + + ", unit " + to_string(device->GetLun()); + + if (auto file_support = dynamic_pointer_cast(device); file_support != nullptr) { + file_support->UnreserveFile(); + } + + auto controller = controller_manager.FindController(device->GetId()); + if (controller == nullptr || !controller->DeleteDevice(device)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_DETACH); + } + + // If no LUN is left also delete the controller + if (!controller->GetLunCount()) { + controller_manager.DeleteController(controller); + } + + LOGINFO("%s", s.c_str()) + } + + return true; +} + +void RascsiExecutor::DetachAll() +{ + controller_manager.DeleteAllControllers(); + FileSupport::UnreserveAll(); + + LOGINFO("Detached all devices") +} + +bool RascsiExecutor::ShutDown(const CommandContext& context, const string& mode) { + if (mode.empty()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING); + } + + PbResult result; + result.set_status(true); + + if (mode == "rascsi") { + LOGINFO("RaSCSI shutdown requested") + + serializer.SerializeMessage(context.fd, result); + + return true; + } + + if (mode != "system" && mode != "reboot") { + return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, mode); + } + + // The root user has UID 0 + if (getuid()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_PERMISSION); + } + + if (mode == "system") { + LOGINFO("System shutdown requested") + + serializer.SerializeMessage(context.fd, result); + + DetachAll(); + + if (system("init 0") == -1) { + LOGERROR("System shutdown failed: %s", strerror(errno)) + } + } + else if (mode == "reboot") { + LOGINFO("System reboot requested") + + serializer.SerializeMessage(context.fd, result); + + DetachAll(); + + if (system("init 6") == -1) { + LOGERROR("System reboot failed: %s", strerror(errno)) + } + } + else { + assert(false); + } + + return false; +} + +string RascsiExecutor::SetReservedIds(string_view ids) +{ + list ids_to_reserve; + stringstream ss(ids.data()); + string id; + while (getline(ss, id, ',')) { + if (!id.empty()) { + ids_to_reserve.push_back(id); + } + } + + unordered_set reserved; + for (string id_to_reserve : ids_to_reserve) { + int res_id; + if (!GetAsInt(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; + + if (!reserved_ids.empty()) { + string s; + bool isFirst = true; + for (auto const& reserved_id : reserved_ids) { + if (!isFirst) { + s += ", "; + } + isFirst = false; + s += to_string(reserved_id); + } + + LOGINFO("Reserved ID(s) set to %s", s.c_str()) + } + else { + LOGINFO("Cleared reserved ID(s)") + } + + return ""; +} + +bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr device, + const string& filename, string& full_path) const +{ + auto file_support = dynamic_pointer_cast(device); + if (file_support == nullptr || filename.empty()) { + return true; + } + + int id; + int lun; + Filepath filepath; + filepath.SetPath(filename.c_str()); + + if (FileSupport::GetIdsForReservedFile(filepath, id, lun)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id), to_string(lun)); + } + + string initial_filename = filepath.GetPath(); + + try { + if (!file_support->FileExists(filepath)) { + // If the file does not exist search for it in the default image folder + filepath.SetPath((rascsi_image.GetDefaultFolder() + "/" + filename).c_str()); + + if (FileSupport::GetIdsForReservedFile(filepath, id, lun)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id), to_string(lun)); + } + + file_support->Open(filepath); + } + } + catch(const io_exception& e) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_FILE_OPEN, initial_filename, e.get_msg()); + } + + full_path = filepath.GetPath(); + + return true; +} + +void RascsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const +{ + const map> params = { command.params().begin(), command.params().end() }; + + ostringstream s; + s << (dryRun ? "Validating" : "Executing"); + s << ": operation=" << PbOperation_Name(command.operation()); + + if (!params.empty()) { + s << ", command params="; + bool isFirst = true; + for (const auto& [key, value]: params) { + if (!isFirst) { + s << ", "; + } + isFirst = false; + string v = key != "token" ? value : "???"; + s << "'" << key << "=" << v << "'"; + } + } + + s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type=" + << PbDeviceType_Name(pb_device.type()); + + if (pb_device.params_size()) { + s << ", device params="; + bool isFirst = true; + for (const auto& [key, value]: pb_device.params()) { + if (!isFirst) { + s << ":"; + } + isFirst = false; + s << "'" << key << "=" << value << "'"; + } + } + + 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()) +} + +string RascsiExecutor::ValidateLunSetup(const PbCommand& command) const +{ + // Mapping of available LUNs (bit vector) to devices + unordered_map luns; + + // Collect LUN bit vectors of new devices + for (const auto& device : command.devices()) { + luns[device.id()] |= 1 << device.unit(); + } + + // Collect LUN bit vectors of existing devices + for (const auto& device : controller_manager.GetAllDevices()) { + luns[device->GetId()] |= 1 << device->GetLun(); + } + + // LUN 0 must exist for all devices + for (auto const& [id, lun]: luns) { + if (!(lun & 0x01)) { + return "LUN 0 is missing for device ID " + to_string(id); + } + } + + return ""; +} + +bool RascsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const +{ + if (controller_manager.FindController(id) == nullptr) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id)); + } + + if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun)); + } + + return true; +} + +shared_ptr RascsiExecutor::CreateDevice(const CommandContext& context, const PbDeviceType type, + int lun, const string& filename) const +{ + auto device = device_factory.CreateDevice(controller_manager, type, lun, filename); + if (device == nullptr) { + if (type == UNDEFINED) { + ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_TYPE, filename); + } + else { + ReturnLocalizedError(context, LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, PbDeviceType_Name(type)); + } + } + + return device; +} + +bool RascsiExecutor::SetSectorSize(const CommandContext& context, const string& type, + shared_ptr device, int block_size) const +{ + if (block_size) { + auto disk = dynamic_pointer_cast(device); + if (disk != nullptr && disk->IsSectorSizeConfigurable()) { + if (!disk->SetConfiguredSectorSize(device_factory, block_size)) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size)); + } + } + else { + return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, type); + } + } + + return true; +} + +bool RascsiExecutor::ValidationOperationAgainstDevice(const CommandContext& context, + const shared_ptr device, const PbOperation& operation) +{ + const string& type = device->GetType(); + + if ((operation == START || operation == STOP) && !device->IsStoppable()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, type); + } + + if ((operation == INSERT || operation == EJECT) && !device->IsRemovable()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, type); + } + + if ((operation == PROTECT || operation == UNPROTECT) && !device->IsProtectable()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, type); + } + + if ((operation == PROTECT || operation == UNPROTECT) && !device->IsReady()) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_READY, type); + } + + return true; +} + +bool RascsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun) +{ + // Validate the device ID and LUN + if (id < 0) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_ID); + } + if (id >= ControllerManager::DEVICE_MAX) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); + } + if (lun < 0 || lun >= ScsiController::LUN_MAX) { + return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1)); + } + + return true; +} + +bool RascsiExecutor::SetProductData(const CommandContext& context, const PbDeviceDefinition& pb_device, + shared_ptr device) +{ + try { + if (!pb_device.vendor().empty()) { + device->SetVendor(pb_device.vendor()); + } + if (!pb_device.product().empty()) { + device->SetProduct(pb_device.product()); + } + if (!pb_device.revision().empty()) { + device->SetRevision(pb_device.revision()); + } + } + catch(const invalid_argument& e) { + return ReturnStatus(context, false, e.what()); + } + + return true; +} diff --git a/src/raspberrypi/rascsi_executor.h b/src/raspberrypi/rascsi_executor.h new file mode 100644 index 00000000..02b40a6a --- /dev/null +++ b/src/raspberrypi/rascsi_executor.h @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2021-2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "protobuf_serializer.h" + +class RascsiResponse; +class RascsiImage; +class DeviceFactory; +class ControllerManager; +class PrimaryDevice; +class CommandContext; + +class RascsiExecutor +{ + RascsiResponse& rascsi_response; + + RascsiImage& rascsi_image; + + DeviceFactory& device_factory; + + ControllerManager& controller_manager; + + ProtobufSerializer serializer; + + unordered_set reserved_ids; + +public: + + RascsiExecutor(RascsiResponse& rascsi_response, RascsiImage& rascsi_image, DeviceFactory& device_factory, + ControllerManager& controller_manager) + : rascsi_response(rascsi_response), rascsi_image(rascsi_image), device_factory(device_factory), + controller_manager(controller_manager) {} + ~RascsiExecutor() = default; + + unordered_set GetReservedIds() const { return reserved_ids; } + + bool ProcessCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); + bool ProcessCmd(const CommandContext&, const PbCommand&); + bool SetLogLevel(const string&) const; + bool Start(shared_ptr, bool) const; + bool Stop(shared_ptr, bool) const; + bool Eject(shared_ptr, bool) const; + bool Protect(shared_ptr, bool) const; + bool Unprotect(shared_ptr, bool) const; + bool Attach(const CommandContext&, const PbDeviceDefinition&, bool); + bool Insert(const CommandContext&, const PbDeviceDefinition&, shared_ptr, bool) const; + bool Detach(const CommandContext&, shared_ptr, bool) const; + void DetachAll(); + bool ShutDown(const CommandContext&, const string&); + string SetReservedIds(string_view); + bool ValidateImageFile(const CommandContext&, shared_ptr, const string&, string&) const; + void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; + string ValidateLunSetup(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&, const string& type, shared_ptr, int) const; + + static bool ValidationOperationAgainstDevice(const CommandContext&, const shared_ptr, + const PbOperation&); + static bool ValidateIdAndLun(const CommandContext&, int, int); + static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, shared_ptr); + +}; diff --git a/src/raspberrypi/rascsi_image.cpp b/src/raspberrypi/rascsi_image.cpp index a6e3a82d..2029c952 100644 --- a/src/raspberrypi/rascsi_image.cpp +++ b/src/raspberrypi/rascsi_image.cpp @@ -18,7 +18,7 @@ #include #include #include -#ifdef __linux +#ifdef __linux__ #include #endif @@ -30,7 +30,7 @@ using namespace command_util; RascsiImage::RascsiImage() { // ~/images is the default folder for device image files, for the root user it is /home/pi/images - default_image_folder = GetHomeDir() + "/images"; + default_folder = GetHomeDir() + "/images"; } bool RascsiImage::CheckDepth(string_view filename) const @@ -57,7 +57,7 @@ bool RascsiImage::CreateImageFolder(const CommandContext& context, const string& return true; } -string RascsiImage::SetDefaultImageFolder(const string& f) +string RascsiImage::SetDefaultFolder(const string& f) { if (f.empty()) { return "Can't set default image folder: Missing folder name"; @@ -81,9 +81,9 @@ string RascsiImage::SetDefaultImageFolder(const string& f) return "Folder '" + f + "' does not exist or is not accessible"; } - default_image_folder = folder; + default_folder = folder; - LOGINFO("Default image folder set to '%s'", default_image_folder.c_str()) + LOGINFO("Default image folder set to '%s'", default_folder.c_str()) return ""; } @@ -113,7 +113,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); } - string full_filename = default_image_folder + "/" + filename; + string full_filename = GetFullName(filename); if (!IsValidDstFilename(full_filename)) { return ReturnStatus(context, false, "Can't create image file: '" + full_filename + "': File already exists"); } @@ -127,10 +127,10 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co try { len = stoull(size); } - catch(const invalid_argument&) { //NOSONAR This exception is handled properly + catch(const invalid_argument&) { return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size); } - catch(const out_of_range&) { //NOSONAR This exception is handled properly + catch(const out_of_range&) { return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size); } if (len < 512 || (len & 0x1ff)) { @@ -151,7 +151,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': " + string(strerror(errno))); } -#ifndef __linux +#ifndef __linux__ close(image_fd); unlink(full_filename.c_str()); @@ -186,7 +186,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); } - string full_filename = default_image_folder + "/" + filename; + string full_filename = GetFullName(filename); int id; int unit; @@ -205,7 +205,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co size_t last_slash = filename.rfind('/'); while (last_slash != string::npos) { string folder = filename.substr(0, last_slash); - string full_folder = default_image_folder + "/" + folder; + string full_folder = GetFullName(folder); if (error_code error; !filesystem::is_empty(full_folder, error) || error) { break; @@ -234,7 +234,7 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); } - from = default_image_folder + "/" + from; + from = GetFullName(from); if (!IsValidSrcFilename(from)) { return ReturnStatus(context, false, "Can't rename/move image file: '" + from + "': Invalid name or type"); } @@ -248,7 +248,7 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); } - to = default_image_folder + "/" + to; + to = GetFullName(to); if (!IsValidDstFilename(to)) { return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': File already exists"); } @@ -277,7 +277,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str()); } - from = default_image_folder + "/" + from; + from = GetFullName(from); if (!IsValidSrcFilename(from)) { return ReturnStatus(context, false, "Can't copy image file: '" + from + "': Invalid name or type"); } @@ -291,7 +291,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str()); } - to = default_image_folder + "/" + to; + to = GetFullName(to); if (!IsValidDstFilename(to)) { return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists"); } @@ -333,7 +333,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm return ReturnStatus(context, false, "Can't open destination image file '" + to + "': " + string(strerror(errno))); } -#ifndef __linux +#ifndef __linux__ close(fd_dst); close(fd_src); @@ -370,7 +370,7 @@ bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCom return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str()); } - filename = default_image_folder + "/" + filename; + filename = GetFullName(filename); if (!IsValidSrcFilename(filename)) { return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type"); } diff --git a/src/raspberrypi/rascsi_image.h b/src/raspberrypi/rascsi_image.h index e2d9c7ab..04c195d3 100644 --- a/src/raspberrypi/rascsi_image.h +++ b/src/raspberrypi/rascsi_image.h @@ -13,7 +13,7 @@ #include "command_context.h" #include -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace rascsi_interface; class RascsiImage { @@ -21,15 +21,13 @@ public: RascsiImage(); ~RascsiImage() = default; - RascsiImage(RascsiImage&) = delete; - RascsiImage& operator=(const RascsiImage&) = delete; void SetDepth(int d) { depth = d; } int GetDepth() const { return depth; } bool CheckDepth(string_view) const; bool CreateImageFolder(const CommandContext&, const string&) const; - string GetDefaultImageFolder() const { return default_image_folder; } - string SetDefaultImageFolder(const string&); + string GetDefaultFolder() const { return default_folder; } + string SetDefaultFolder(const string&); bool IsValidSrcFilename(const string&) const; bool IsValidDstFilename(const string&) const; bool CreateImage(const CommandContext&, const PbCommand&) const; @@ -37,12 +35,13 @@ public: bool RenameImage(const CommandContext&, const PbCommand&) const; bool CopyImage(const CommandContext&, const PbCommand&) const; bool SetImagePermissions(const CommandContext&, const PbCommand&) const; + string GetFullName(const string& filename) const { return default_folder + "/" + filename; } private: string GetHomeDir() const; - string default_image_folder; + string default_folder; int depth = 1; }; diff --git a/src/raspberrypi/rascsi_response.cpp b/src/raspberrypi/rascsi_response.cpp index 491996c2..ead233ff 100644 --- a/src/raspberrypi/rascsi_response.cpp +++ b/src/raspberrypi/rascsi_response.cpp @@ -7,59 +7,57 @@ // //--------------------------------------------------------------------------- -#include +#include "controllers/controller_manager.h" #include "devices/file_support.h" #include "devices/disk.h" #include "devices/device_factory.h" #include "command_util.h" #include "rascsi_version.h" #include "rascsi_interface.pb.h" -#include "rascsi_image.h" #include "rascsi_response.h" using namespace rascsi_interface; using namespace command_util; -PbDeviceProperties *RascsiResponse::GetDeviceProperties(const Device *device) +unique_ptr RascsiResponse::GetDeviceProperties(const Device& device) const { - auto properties = make_unique().release(); + auto properties = make_unique(); - properties->set_luns(ScsiController::LUN_MAX); - 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(dynamic_cast(device) != nullptr); - properties->set_supports_params(device->SupportsParams()); + properties->set_luns(max_luns); + 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(dynamic_cast(&device) != nullptr); + properties->set_supports_params(device.SupportsParams()); PbDeviceType t = UNDEFINED; - PbDeviceType_Parse(device->GetType(), &t); + PbDeviceType_Parse(device.GetType(), &t); - if (device->SupportsParams()) { - for (const auto& [key, value] : device_factory->GetDefaultParams(t)) { + if (device.SupportsParams()) { + for (const auto& [key, value] : device_factory.GetDefaultParams(t)) { auto& map = *properties->mutable_default_params(); map[key] = value; } } - for (const auto& block_size : device_factory->GetSectorSizes(t)) { + for (const auto& block_size : device_factory.GetSectorSizes(t)) { properties->add_block_sizes(block_size); } return properties; } -void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) +void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const { - PbDeviceTypeProperties *type_properties = device_types_info.add_properties(); + auto type_properties = device_types_info.add_properties(); type_properties->set_type(type); - const PrimaryDevice *device = device_factory->CreateDevice(type, "", -1); - type_properties->set_allocated_properties(GetDeviceProperties(device)); - device_factory->DeleteDevice(*device); //NOSONAR The allocated memory is managed by protobuf -} + auto device = device_factory.CreateDevice(controller_manager, type, 0, ""); + type_properties->set_allocated_properties(GetDeviceProperties(*device).release()); +} //NOSONAR The allocated memory is managed by protobuf -void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) +void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const { // Start with 2 instead of 1. 1 was the removed SASI drive type. int ordinal = 2; @@ -71,60 +69,60 @@ void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_ } } -void RascsiResponse::GetDevice(const Device *device, PbDevice *pb_device) +void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const string& default_folder) const { - pb_device->set_id(device->GetId()); - pb_device->set_unit(device->GetLun()); - pb_device->set_vendor(device->GetVendor()); - pb_device->set_product(device->GetProduct()); - pb_device->set_revision(device->GetRevision()); + pb_device.set_id(device.GetId()); + pb_device.set_unit(device.GetLun()); + pb_device.set_vendor(device.GetVendor()); + pb_device.set_product(device.GetProduct()); + pb_device.set_revision(device.GetRevision()); PbDeviceType type = UNDEFINED; - PbDeviceType_Parse(device->GetType(), &type); - pb_device->set_type(type); + PbDeviceType_Parse(device.GetType(), &type); + pb_device.set_type(type); - pb_device->set_allocated_properties(GetDeviceProperties(device)); + pb_device.set_allocated_properties(GetDeviceProperties(device).release()); auto status = make_unique().release(); - pb_device->set_allocated_status(status); - status->set_protected_(device->IsProtected()); - status->set_stopped(device->IsStopped()); - status->set_removed(device->IsRemoved()); - status->set_locked(device->IsLocked()); + pb_device.set_allocated_status(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 - for (const auto& [key, value] : device->GetParams()) { - AddParam(*pb_device, key, value); + if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf + for (const auto& [key, value] : device.GetParams()) { + AddParam(pb_device, key, value); } } - if (const auto disk = dynamic_cast(device); disk) { - pb_device->set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes()); - pb_device->set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount()); + if (const auto disk = dynamic_cast(&device); disk) { + pb_device.set_block_size(device.IsRemoved()? 0 : disk->GetSectorSizeInBytes()); + pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount()); } - const auto file_support = dynamic_cast(device); + const auto file_support = dynamic_cast(&device); if (file_support) { Filepath filepath; file_support->GetPath(filepath); auto image_file = make_unique().release(); - GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath()); - pb_device->set_allocated_file(image_file); + GetImageFile(*image_file, default_folder, device.IsRemovable() && !device.IsReady() ? "" : filepath.GetPath()); + pb_device.set_allocated_file(image_file); } } //NOSONAR The allocated memory is managed by protobuf -bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filename) const +bool RascsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const { if (!filename.empty()) { - image_file->set_name(filename); - image_file->set_type(device_factory->GetTypeForFile(filename)); + image_file.set_name(filename); + image_file.set_type(device_factory.GetTypeForFile(filename)); - string f = filename[0] == '/' ? filename : rascsi_image->GetDefaultImageFolder() + "/" + filename; + string f = filename[0] == '/' ? filename : default_folder + "/" + filename; - image_file->set_read_only(access(f.c_str(), W_OK)); + image_file.set_read_only(access(f.c_str(), W_OK)); if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { - image_file->set_size(st.st_size); + image_file.set_size(st.st_size); return true; } } @@ -132,8 +130,9 @@ bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filenam return false; } -void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, string_view default_image_folder, - const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) { +void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder, + const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const +{ if (scan_depth-- < 0) { return; } @@ -151,35 +150,29 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, stri const dirent *dir; while ((dir = readdir(d))) { - bool is_supported_type = dir->d_type == DT_REG || dir->d_type == DT_DIR || dir->d_type == DT_LNK || dir->d_type == DT_BLK; - if (is_supported_type && dir->d_name[0] != '.') { - string name_lower = dir->d_name; - if (!file_pattern.empty()) { - transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); + string filename = GetNextImageFile(dir, folder); + if (filename.empty()) { + continue; + } + + string name_lower = dir->d_name; + if (!file_pattern.empty()) { + transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); + } + + 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); } - string filename = folder + "/" + dir->d_name; + continue; + } - if (struct stat st; dir->d_type == DT_REG && !stat(filename.c_str(), &st)) { - if (!st.st_size) { - LOGWARN("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, folder.c_str()) - continue; - } - } else if (dir->d_type == DT_LNK && stat(filename.c_str(), &st)) { - LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) - continue; - } else if (dir->d_type == DT_DIR) { - if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) { - GetAvailableImages(image_files_info, default_image_folder, filename, folder_pattern, - file_pattern, scan_depth); - } - continue; - } - - if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { - if (auto image_file = make_unique(); GetImageFile(image_file.get(), filename)) { - GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1)); - } + 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)); } } } @@ -187,16 +180,15 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, stri closedir(d); } -PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const string& folder_pattern, - const string& file_pattern, int scan_depth) +unique_ptr RascsiResponse::GetAvailableImages(PbResult& result, const string& default_folder, + const string& folder_pattern, const string& file_pattern, int scan_depth) const { - auto image_files_info = make_unique().release(); + auto image_files_info = make_unique(); - string default_image_folder = rascsi_image->GetDefaultImageFolder(); - image_files_info->set_default_image_folder(default_image_folder); + image_files_info->set_default_image_folder(default_folder); image_files_info->set_depth(scan_depth); - GetAvailableImages(*image_files_info, default_image_folder, default_image_folder, folder_pattern, + GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern, file_pattern, scan_depth); result.set_status(true); @@ -204,19 +196,19 @@ PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const str return image_files_info; } -void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& folder_pattern, - const string& file_pattern, int scan_depth) +void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder, + const string& folder_pattern, const string& file_pattern, int scan_depth) const { - PbImageFilesInfo *image_files_info = GetAvailableImages(result, folder_pattern, file_pattern, scan_depth); - image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder()); - server_info.set_allocated_image_files_info(image_files_info); + 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()); result.set_status(true); //NOSONAR The allocated memory is managed by protobuf } -PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unordered_set& ids) +unique_ptr RascsiResponse::GetReservedIds(PbResult& result, const unordered_set& ids) const { - auto reserved_ids_info = make_unique().release(); + auto reserved_ids_info = make_unique(); for (int id : ids) { reserved_ids_info->add_ids(id); } @@ -226,48 +218,52 @@ PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unorde return reserved_ids_info; } -void RascsiResponse::GetDevices(PbServerInfo& server_info) +void RascsiResponse::GetDevices(PbServerInfo& server_info, const string& default_folder) const { - for (const Device *device : device_factory->GetAllDevices()) { + for (const auto& device : controller_manager.GetAllDevices()) { PbDevice *pb_device = server_info.mutable_devices_info()->add_devices(); - GetDevice(device, pb_device); + GetDevice(*device, *pb_device, default_folder); } } -void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command) +void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command, const string& default_folder) const { set id_sets; + + const auto& devices = controller_manager.GetAllDevices(); + + // If no device list was provided in the command get information on all devices if (!command.devices_size()) { - for (const Device *device : device_factory->GetAllDevices()) { + for (const auto& device : devices) { id_sets.insert(make_pair(device->GetId(), device->GetLun())); } } + // Otherwise get information on the devices provided in the command else { - for (const auto& device : command.devices()) { - if (device_factory->GetDeviceByIdAndLun(device.id(), device.unit())) { - id_sets.insert(make_pair(device.id(), device.unit())); - } - else { - result.set_status(false); - result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit())); - return; + id_sets = MatchDevices(result, command); + if (id_sets.empty()) { + return; + } + } + + auto devices_info = make_unique(); + + for (const auto& [id, lun] : id_sets) { + for (const auto& d : devices) { + if (d->GetId() == id && d->GetLun() == lun) { + GetDevice(*d, *devices_info->add_devices(), default_folder); + break; } } } - auto devices_info = make_unique().release(); - result.set_allocated_devices_info(devices_info); - - for (const auto& [id, lun] : id_sets) { - GetDevice(device_factory->GetDeviceByIdAndLun(id, lun), devices_info->add_devices()); - } - + result.set_allocated_devices_info(devices_info.release()); result.set_status(true); } -PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result) +unique_ptr RascsiResponse::GetDeviceTypesInfo(PbResult& result) const { - auto device_types_info = make_unique().release(); + auto device_types_info = make_unique(); GetAllDeviceTypeProperties(*device_types_info); @@ -276,29 +272,30 @@ PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result) return device_types_info; } -PbServerInfo *RascsiResponse::GetServerInfo(PbResult& result, const unordered_set& reserved_ids, - const string& current_log_level, const string& folder_pattern, const string& file_pattern, int scan_depth) +unique_ptr RascsiResponse::GetServerInfo(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 { - auto server_info = make_unique().release(); + auto server_info = make_unique(); - server_info->set_allocated_version_info(GetVersionInfo(result)); - server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level)); //NOSONAR The allocated memory is managed by protobuf + 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, folder_pattern, file_pattern, scan_depth); - server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result)); - server_info->set_allocated_mapping_info(GetMappingInfo(result)); //NOSONAR The allocated memory is managed by protobuf - GetDevices(*server_info); //NOSONAR The allocated memory is managed by protobuf - server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids)); - server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth)); //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(*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; } -PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result) +unique_ptr RascsiResponse::GetVersionInfo(PbResult& result) const { - auto version_info = make_unique().release(); + auto version_info = make_unique(); version_info->set_major_version(rascsi_major_version); version_info->set_minor_version(rascsi_minor_version); @@ -309,9 +306,9 @@ PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result) return version_info; } -PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) +unique_ptr RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const { - auto log_level_info = make_unique().release(); + auto log_level_info = make_unique(); for (const auto& log_level : log_levels) { log_level_info->add_log_levels(log_level); @@ -324,11 +321,11 @@ PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& return log_level_info; } -PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) +unique_ptr RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) const { - auto network_interfaces_info = make_unique().release(); + auto network_interfaces_info = make_unique(); - for (const auto& network_interface : device_factory->GetNetworkInterfaces()) { + for (const auto& network_interface : device_factory.GetNetworkInterfaces()) { network_interfaces_info->add_name(network_interface); } @@ -337,11 +334,11 @@ PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& resu return network_interfaces_info; } -PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result) +unique_ptr RascsiResponse::GetMappingInfo(PbResult& result) const { - auto mapping_info = make_unique().release(); + auto mapping_info = make_unique(); - for (const auto& [name, type] : device_factory->GetExtensionMapping()) { + for (const auto& [name, type] : device_factory.GetExtensionMapping()) { (*mapping_info->mutable_mapping())[name] = type; } @@ -350,131 +347,147 @@ PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result) return mapping_info; } -PbOperationInfo *RascsiResponse::GetOperationInfo(PbResult& result, int depth) +unique_ptr RascsiResponse::GetOperationInfo(PbResult& result, 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"); - AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds"); + 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(); + AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds").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); + 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 rascsi server information"); if (depth) { - AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names"); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); + operation.release(); - CreateOperation(*operation_info, VERSION_INFO, "Get rascsi server version"); + CreateOperation(*operation_info, VERSION_INFO, "Get rascsi server version").release(); - CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices"); + CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release(); - CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type"); + CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release(); 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"); + AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release(); } - AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names"); + AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release(); + operation.release(); operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true); + AddOperationParameter(*operation, "file", "Image file name", "", true).release(); + operation.release(); - CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information"); + CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release(); - CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces"); + CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release(); - CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types"); + CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release(); - CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs"); + CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release(); operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder"); - AddOperationParameter(*operation, "folder", "Default image file folder name", "", true); + AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release(); + operation.release(); operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level"); - AddOperationParameter(*operation, "level", "New log level", "", true); + AddOperationParameter(*operation, "level", "New log level", "", true).release(); + operation.release(); operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs"); - AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true); + AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release(); + operation.release(); operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot"); - PbOperationParameter *parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true); + auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release(); parameter->add_permitted_values("rascsi"); // System shutdown/reboot requires root permissions if (!getuid()) { parameter->add_permitted_values("system"); parameter->add_permitted_values("reboot"); } + 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); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false"); + 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, DELETE_IMAGE, "Delete image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true); + AddOperationParameter(*operation, "file", "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); + AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); + AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); + 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); - parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false"); + 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, PROTECT_IMAGE, "Write-protect image file"); - AddOperationParameter(*operation, "file", "Image file name", "", true); + 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); + AddOperationParameter(*operation, "file", "Image file name", "", 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); + AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release(); + operation.release(); - CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data"); + CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release(); result.set_status(true); - return operation_info.release(); + return operation_info; } -PbOperationMetaData *RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, +unique_ptr RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, const string& description) const { - auto meta_data = make_shared(); + auto meta_data = make_unique(); 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.get(); - return &(*operation_info.mutable_operations())[ordinal]; + (*operation_info.mutable_operations())[ordinal] = *meta_data.release(); + return unique_ptr(&(*operation_info.mutable_operations())[ordinal]); } -PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name, - const string& description, const string& default_value, bool is_mandatory) +unique_ptr RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, + const string& name, const string& description, const string& default_value, bool is_mandatory) const { auto parameter = unique_ptr(meta_data.add_parameters()); parameter->set_name(name); @@ -482,5 +495,59 @@ PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData& parameter->set_default_value(default_value); parameter->set_is_mandatory(is_mandatory); - return parameter.release(); + return parameter; +} + +set RascsiResponse::MatchDevices(PbResult& result, const PbCommand& command) const +{ + set id_sets; + + for (const auto& device : command.devices()) { + bool has_device = false; + for (const auto& d : controller_manager.GetAllDevices()) { + if (d->GetId() == device.id() && d->GetLun() == device.unit()) { + id_sets.insert(make_pair(device.id(), device.unit())); + has_device = true; + break; + } + } + + if (!has_device) { + id_sets.clear(); + + result.set_status(false); + result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit())); + + break; + } + } + + return id_sets; +} + +string RascsiResponse::GetNextImageFile(const dirent *dir, const string& folder) +{ + // Ignore unknown folder types and folder names starting with '.' + if ((dir->d_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK) + || dir->d_name[0] == '.') { + return ""; + } + + string filename = folder + "/" + dir->d_name; + + struct stat st; + + bool file_exists = !stat(filename.c_str(), &st); + + if (dir->d_type == DT_REG && file_exists && !st.st_size) { + LOGWARN("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, folder.c_str()) + return ""; + } + + if (dir->d_type == DT_LNK && !file_exists) { + LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) + return ""; + } + + return filename; } diff --git a/src/raspberrypi/rascsi_response.h b/src/raspberrypi/rascsi_response.h index 537a9c7c..b8cf56c2 100644 --- a/src/raspberrypi/rascsi_response.h +++ b/src/raspberrypi/rascsi_response.h @@ -9,54 +9,60 @@ #pragma once -#include "devices/device_factory.h" #include "rascsi_interface.pb.h" +#include #include #include -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace std; +using namespace rascsi_interface; class DeviceFactory; -class RascsiImage; +class ControllerManager; class Device; class RascsiResponse { public: - RascsiResponse(DeviceFactory *device_factory, const RascsiImage *rascsi_image) - : device_factory(device_factory), rascsi_image(rascsi_image) {} + RascsiResponse(DeviceFactory& device_factory, const ControllerManager& controller_manager, int max_luns) + : device_factory(device_factory), controller_manager(controller_manager), max_luns(max_luns) {} ~RascsiResponse() = default; - RascsiResponse(RascsiResponse&) = delete; - RascsiResponse& operator=(const RascsiResponse&) = delete; - bool GetImageFile(PbImageFile *, const string&) const; - PbImageFilesInfo *GetAvailableImages(PbResult&, const string&, const string&, int); - PbReservedIdsInfo *GetReservedIds(PbResult&, const unordered_set&); - void GetDevices(PbServerInfo&); - void GetDevicesInfo(PbResult&, const PbCommand&); - PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&); - PbVersionInfo *GetVersionInfo(PbResult&); - PbServerInfo *GetServerInfo(PbResult&, const unordered_set&, const string&, const string&, const string&, int); - PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&); - PbMappingInfo *GetMappingInfo(PbResult&); - PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&); - PbOperationInfo *GetOperationInfo(PbResult&, int); + 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 GetDevices(PbServerInfo&, const string&) const; + void GetDevicesInfo(PbResult&, const PbCommand&, const string&) const; + unique_ptr GetDeviceTypesInfo(PbResult&) const; + unique_ptr GetVersionInfo(PbResult&) const; + unique_ptr GetServerInfo(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; private: - DeviceFactory *device_factory; - const RascsiImage *rascsi_image; + DeviceFactory& device_factory; + + const ControllerManager& controller_manager; + + int max_luns; const list log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" }; - PbDeviceProperties *GetDeviceProperties(const Device *); - void GetDevice(const Device *, PbDevice *); - void GetAllDeviceTypeProperties(PbDeviceTypesInfo&); - void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType); - void GetAvailableImages(PbImageFilesInfo&, string_view, const string&, const string&, const string&, int); - void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, int); - PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; - PbOperationParameter *AddOperationParameter(PbOperationMetaData&, const string&, const string&, - const string& = "", bool = false); + unique_ptr GetDeviceProperties(const Device&) 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; + set MatchDevices(PbResult&, const PbCommand&) const; + + static string GetNextImageFile(const dirent *, const string&); }; diff --git a/src/raspberrypi/rascsi_service.cpp b/src/raspberrypi/rascsi_service.cpp new file mode 100644 index 00000000..4ccc41b4 --- /dev/null +++ b/src/raspberrypi/rascsi_service.cpp @@ -0,0 +1,138 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "log.h" +#include "rascsi_exceptions.h" +#include "command_context.h" +#include "protobuf_serializer.h" +#include "localizer.h" +#include "rasutil.h" +#include "rascsi_service.h" +#include +#include + +using namespace rascsi_interface; + +volatile bool RascsiService::running = false; + +RascsiService::~RascsiService() +{ + if (service_socket != -1) { + close(service_socket); + } +} + +bool RascsiService::Init(bool (e)(PbCommand&, CommandContext&), int 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; + } + + server.sin_family = PF_INET; + server.sin_port = htons(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; + } + + signal(SIGPIPE, SIG_IGN); + + if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { + cerr << "Error: Port " << port << " is in use, is rascsi already running?" << endl; + return false; + } + + execute = e; + + monthread = thread(&RascsiService::Execute, this); + monthread.detach(); + + // Interrupt handler settings + return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR + && signal(SIGTERM, KillHandler) != SIG_ERR; +} + +void RascsiService::Execute() +{ +#ifdef __linux__ + // Scheduler Settings + sched_param schedparam; + schedparam.sched_priority = 0; + sched_setscheduler(0, SCHED_IDLE, &schedparam); +#endif + + // Set the affinity to a specific processor core + ras_util::FixCpu(2); + + // Wait for the execution to start + while (!running) { + usleep(1); + } + + // Set up the monitor socket to receive commands + listen(service_socket, 1); + + ProtobufSerializer serializer; + Localizer localizer; + while (true) { + CommandContext context(serializer, localizer, -1, ""); + + try { + PbCommand command; + context.fd = ReadCommand(serializer, command); + if (context.fd == -1) { + continue; + } + + execute(command, context); + } + catch(const io_exception& e) { + LOGWARN("%s", e.get_msg().c_str()) + + // Fall through + } + + if (context.fd != -1) { + close(context.fd); + } + } +} + +int RascsiService::ReadCommand(ProtobufSerializer& serializer, PbCommand& command) +{ + // Wait for connection + sockaddr client = {}; + socklen_t socklen = sizeof(client); + int fd = accept(service_socket, &client, &socklen); + if (fd < 0) { + throw io_exception("accept() failed"); + } + + // Read magic string + vector magic(6); + size_t bytes_read = serializer.ReadBytes(fd, magic); + if (!bytes_read) { + return -1; + } + if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) { + throw io_exception("Invalid magic"); + } + + // Fetch the command + serializer.DeserializeMessage(fd, command); + + return fd; +} + diff --git a/src/raspberrypi/rascsi_service.h b/src/raspberrypi/rascsi_service.h new file mode 100644 index 00000000..f5c7640d --- /dev/null +++ b/src/raspberrypi/rascsi_service.h @@ -0,0 +1,43 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi_interface.pb.h" +#include + +class CommandContext; +class ProtobufSerializer; + +class RascsiService +{ + bool (*execute)(rascsi_interface::PbCommand&, CommandContext&) = nullptr; + + int service_socket = -1; + + thread monthread; + + static volatile bool running; + +public: + + RascsiService() = default; + ~RascsiService(); + + bool Init(bool (ExecuteCommand)(rascsi_interface::PbCommand&, CommandContext&), int); + + bool IsRunning() const { return running; } + void SetRunning(bool b) const { running = b; } + + void Execute(); + + int ReadCommand(ProtobufSerializer&, rascsi_interface::PbCommand&); + + static void KillHandler(int) { running = false; } +}; diff --git a/src/raspberrypi/rascsi_version.cpp b/src/raspberrypi/rascsi_version.cpp index 0827d3bf..4c43aa37 100644 --- a/src/raspberrypi/rascsi_version.cpp +++ b/src/raspberrypi/rascsi_version.cpp @@ -14,7 +14,7 @@ // The following should be updated for each release const int rascsi_major_version = 22; // Last two digits of year -const int rascsi_minor_version = 9; // Month +const int rascsi_minor_version = 10; // Month const int rascsi_patch_version = -1; // Patch number - increment for each update using namespace std; diff --git a/src/raspberrypi/rasctl_commands.cpp b/src/raspberrypi/rasctl_commands.cpp index 4c2350f9..e6289d51 100644 --- a/src/raspberrypi/rasctl_commands.cpp +++ b/src/raspberrypi/rasctl_commands.cpp @@ -8,7 +8,7 @@ //--------------------------------------------------------------------------- #include "rascsi_exceptions.h" -#include "socket_connector.h" +#include "protobuf_serializer.h" #include "command_util.h" #include "rasutil.h" #include "rasctl_commands.h" @@ -151,7 +151,7 @@ void RasctlCommands::SendCommand() throw io_exception("Can't write magic"); } - socket_connector.SerializeMessage(fd, command); + serializer.SerializeMessage(fd, command); } catch(const io_exception& e) { cerr << "Error: " << e.get_msg() << endl; @@ -165,7 +165,7 @@ void RasctlCommands::SendCommand() // Receive result try { - socket_connector.DeserializeMessage(fd, result); + serializer.DeserializeMessage(fd, result); if (!result.status()) { throw io_exception(result.msg()); @@ -305,7 +305,7 @@ void RasctlCommands::CommandServerInfo() 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(); }); + sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); cout << "Attached devices:" << endl; diff --git a/src/raspberrypi/rasctl_commands.h b/src/raspberrypi/rasctl_commands.h index c7b69bde..e70c6f2b 100644 --- a/src/raspberrypi/rasctl_commands.h +++ b/src/raspberrypi/rasctl_commands.h @@ -9,12 +9,12 @@ #pragma once -#include "socket_connector.h" +#include "protobuf_serializer.h" #include "rascsi_interface.pb.h" #include "rasctl_display.h" #include -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace rascsi_interface; class RasctlCommands { @@ -22,8 +22,6 @@ public: RasctlCommands(const PbCommand&, const string&, int, const string&, const string&); ~RasctlCommands() = default; - RasctlCommands(RasctlCommands&) = delete; - RasctlCommands& operator=(const RasctlCommands&) = delete; void Execute(const string&, const string&, const string&, const string&, const string&); @@ -51,7 +49,7 @@ private: void CommandOperationInfo(); void SendCommand(); - SocketConnector socket_connector; + ProtobufSerializer serializer; PbCommand command; string hostname; int port; diff --git a/src/raspberrypi/rasctl_display.h b/src/raspberrypi/rasctl_display.h index 983c382b..2ad32df9 100644 --- a/src/raspberrypi/rasctl_display.h +++ b/src/raspberrypi/rasctl_display.h @@ -11,7 +11,7 @@ #include "rascsi_interface.pb.h" -using namespace rascsi_interface; //NOSONAR Not relevant for rascsi +using namespace rascsi_interface; class RasctlDisplay { @@ -19,8 +19,6 @@ class RasctlDisplay RasctlDisplay() = default; ~RasctlDisplay() = default; - RasctlDisplay(RasctlDisplay&) = delete; - RasctlDisplay& operator=(const RasctlDisplay&) = delete; void DisplayDevices(const PbDevicesInfo&) const; void DisplayDeviceInfo(const PbDevice&) const; diff --git a/src/raspberrypi/rasutil.cpp b/src/raspberrypi/rasutil.cpp index 6237cb6d..20605b8c 100644 --- a/src/raspberrypi/rasutil.cpp +++ b/src/raspberrypi/rasutil.cpp @@ -22,7 +22,8 @@ bool ras_util::GetAsInt(const string& value, int& result) } try { - result = (int)stoul(value); + auto v = stoul(value); + result = (int)v; } catch(const invalid_argument&) { return false; @@ -46,7 +47,7 @@ string ras_util::ListDevices(const list& pb_devices) << "+----+-----+------+-------------------------------------" << endl; list devices = pb_devices; - devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() && a.unit() < b.unit(); }); + devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); }); for (const auto& device : devices) { string filename; @@ -82,3 +83,22 @@ string ras_util::ListDevices(const list& pb_devices) return s.str(); } + +// Pin the thread to a specific CPU +void ras_util::FixCpu(int cpu) +{ +#ifdef __linux__ + // Get the number of CPUs + cpu_set_t cpuset; + CPU_ZERO(&cpuset); + sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); + int cpus = CPU_COUNT(&cpuset); + + // Set the thread affinity + if (cpu < cpus) { + CPU_ZERO(&cpuset); + CPU_SET(cpu, &cpuset); + sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); + } +#endif +} diff --git a/src/raspberrypi/rasutil.h b/src/raspberrypi/rasutil.h index 6cb55150..e71c10dd 100644 --- a/src/raspberrypi/rasutil.h +++ b/src/raspberrypi/rasutil.h @@ -19,4 +19,6 @@ namespace ras_util { bool GetAsInt(const std::string&, int&); std::string ListDevices(const std::list&); + + void FixCpu(int); } diff --git a/src/raspberrypi/scsi.cpp b/src/raspberrypi/scsi.cpp index 847043fb..32ae9f1c 100644 --- a/src/raspberrypi/scsi.cpp +++ b/src/raspberrypi/scsi.cpp @@ -9,7 +9,7 @@ // //--------------------------------------------------------------------------- -#include "scsi.h" +#include "bus.h" using namespace std; @@ -31,7 +31,7 @@ BUS::phase_t BUS::GetPhase() } // Get target phase from bus signal line - DWORD mci = GetMSG() ? 0x04 : 0x00; + int mci = GetMSG() ? 0x04 : 0x00; mci |= GetCD() ? 0x02 : 0x00; mci |= GetIO() ? 0x01 : 0x00; return GetPhase(mci); @@ -42,13 +42,9 @@ BUS::phase_t BUS::GetPhase() // Determine Phase String phase enum // //--------------------------------------------------------------------------- -const char* BUS::GetPhaseStrRaw(phase_t current_phase){ - if(current_phase <= phase_t::reserved) { - return phase_str_table[(int)current_phase]; - } - else { - return "INVALID"; - } +const char* BUS::GetPhaseStrRaw(phase_t current_phase) { + const auto& it = phase_str_mapping.find(current_phase); + return it != phase_str_mapping.end() ? it->second : "INVALID"; } //--------------------------------------------------------------------------- @@ -70,24 +66,21 @@ const array BUS::phase_table = { phase_t::msgin // | 1 | 1 | 1 | }; - //--------------------------------------------------------------------------- // -// Phase Table -// This MUST be kept in sync with the phase_t enum type! +// Phase string to phase mapping // //--------------------------------------------------------------------------- -const array BUS::phase_str_table = { - "busfree", - "arbitration", - "selection", - "reselection", - "command", - "execute", - "datain", - "dataout", - "status", - "msgin", - "msgout", - "reserved" +const unordered_map BUS::phase_str_mapping = { + { phase_t::busfree, "busfree" }, + { phase_t::arbitration, "arbitration" }, + { phase_t::selection, "selection" }, + { phase_t::reselection, "reselection" }, + { phase_t::command, "command" }, + { phase_t::datain, "datain" }, + { phase_t::dataout, "dataout" }, + { phase_t::status, "status" }, + { phase_t::msgin, "msgin" }, + { phase_t::msgout, "msgout" }, + { phase_t::reserved, "reserved" } }; diff --git a/src/raspberrypi/scsi.h b/src/raspberrypi/scsi.h index 9591216d..b57e4cef 100644 --- a/src/raspberrypi/scsi.h +++ b/src/raspberrypi/scsi.h @@ -5,118 +5,12 @@ // Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp) // Copyright (C) 2014-2020 GIMONS // -// [ SCSI Common Functionality ] -// //--------------------------------------------------------------------------- #pragma once -#include "os.h" -#include - -using namespace std; //NOSONAR Not relevant for rascsi - -//=========================================================================== -// -// SCSI Bus -// -//=========================================================================== -class BUS -{ -public: - // Operation modes definition - enum class mode_e { - TARGET = 0, - INITIATOR = 1, - MONITOR = 2, - }; - - // Phase definitions - enum class phase_t : int { - busfree, - arbitration, - selection, - reselection, - command, - execute, // Execute phase is an extension of the command phase - datain, - dataout, - status, - msgin, - msgout, - reserved // Unused - }; - - BUS() = default; - virtual ~BUS() = default; - - // Basic Functions - virtual bool Init(mode_e mode) = 0; - virtual void Reset() = 0; - virtual void Cleanup() = 0; - phase_t GetPhase(); - - static phase_t GetPhase(DWORD mci) - { - return phase_table[mci]; - } - - // Get the string phase name, based upon the raw data - static const char* GetPhaseStrRaw(phase_t current_phase); - - // Extract as specific pin field from a raw data capture - static inline DWORD GetPinRaw(DWORD raw_data, DWORD pin_num) - { - return ((raw_data >> pin_num) & 1); - } - - virtual bool GetBSY() const = 0; - virtual void SetBSY(bool ast) = 0; - - virtual bool GetSEL() const = 0; - virtual void SetSEL(bool ast) = 0; - - virtual bool GetATN() const = 0; - virtual void SetATN(bool ast) = 0; - - virtual bool GetACK() const = 0; - virtual void SetACK(bool ast) = 0; - - virtual bool GetRST() const = 0; - virtual void SetRST(bool ast) = 0; - - virtual bool GetMSG() const = 0; - virtual void SetMSG(bool ast) = 0; - - virtual bool GetCD() const = 0; - virtual void SetCD(bool ast) = 0; - - virtual bool GetIO() = 0; - virtual void SetIO(bool ast) = 0; - - virtual bool GetREQ() const = 0; - virtual void SetREQ(bool ast) = 0; - - virtual BYTE GetDAT() = 0; - virtual void SetDAT(BYTE dat) = 0; - virtual bool GetDP() const = 0; // Get parity signal - - virtual uint32_t Acquire() = 0; - virtual int CommandHandShake(BYTE *buf) = 0; - virtual int ReceiveHandShake(BYTE *buf, int count) = 0; - virtual int SendHandShake(BYTE *buf, int count, int delay_after_bytes) = 0; - - virtual bool GetSignal(int pin) const = 0; - // Get SCSI input signal value - virtual void SetSignal(int pin, bool ast) = 0; - // Set SCSI output signal value - static const int SEND_NO_DELAY = -1; - // Passed into SendHandShake when we don't want to delay -private: - static const array phase_table; - - static const array phase_str_table; -}; +// TODO Remove this include as soon as gpiobus.cpp/h is open for editing (adding the include there) again +#include "bus.h" namespace scsi_defs { enum class scsi_level : int { diff --git a/src/raspberrypi/scsimon.cpp b/src/raspberrypi/scsimon.cpp index 9db75e14..f28d63b4 100644 --- a/src/raspberrypi/scsimon.cpp +++ b/src/raspberrypi/scsimon.cpp @@ -33,7 +33,7 @@ static const int _MAX_FNAME = 256; // //--------------------------------------------------------------------------- static volatile bool running; // Running flag -unique_ptr bus; // GPIO Bus +GPIOBUS bus; // GPIO Bus DWORD buff_size = 1000000; data_capture *data_buffer; @@ -186,15 +186,14 @@ bool Init() } // GPIO Initialization - bus = make_unique(); - if (!bus->Init()) + if (!bus.Init()) { LOGERROR("Unable to intiailize the GPIO bus. Exiting....") return false; } // Bus Reset - bus->Reset(); + bus.Reset(); // Other running = false; @@ -204,8 +203,7 @@ bool Init() void Cleanup() { - if (!import_data) - { + if (!import_data) { LOGINFO("Stopping data collection....") } LOGINFO(" ") @@ -216,17 +214,14 @@ void Cleanup() LOGINFO("Generating %s...", html_file_name) scsimon_generate_html(html_file_name, data_buffer, data_idx); - if (bus) - { - // Cleanup the Bus - bus->Cleanup(); - } + // Cleanup the Bus + bus.Cleanup(); } void Reset() { // Reset the bus - bus->Reset(); + bus.Reset(); } //--------------------------------------------------------------------------- @@ -234,7 +229,7 @@ void Reset() // Pin the thread to a specific CPU (Only applies to Linux) // //--------------------------------------------------------------------------- -#ifdef __linux +#ifdef __linux__ void FixCpu(int cpu) { // Get the number of CPUs @@ -326,7 +321,7 @@ int main(int argc, char *argv[]) // Reset Reset(); -#ifdef __linux +#ifdef __linux__ // Set the affinity to a specific processor core FixCpu(3); @@ -338,7 +333,7 @@ int main(int argc, char *argv[]) // Start execution running = true; - bus->SetACT(false); + bus.SetACT(false); (void)gettimeofday(&start_time, nullptr); @@ -348,7 +343,7 @@ int main(int argc, char *argv[]) while (running) { // Work initialization - this_sample = (bus->Acquire() & ALL_SCSI_PINS); + this_sample = (bus.Acquire() & ALL_SCSI_PINS); loop_count++; if (loop_count > LLONG_MAX - 1) { diff --git a/src/raspberrypi/test/abstract_controller_test.cpp b/src/raspberrypi/test/abstract_controller_test.cpp index 1fca4ad8..85ca262d 100644 --- a/src/raspberrypi/test/abstract_controller_test.cpp +++ b/src/raspberrypi/test/abstract_controller_test.cpp @@ -7,54 +7,35 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" +#include "bus.h" +#include "rascsi_exceptions.h" #include "controllers/abstract_controller.h" +using namespace scsi_defs; + TEST(AbstractControllerTest, Reset) { MockAbstractController controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); controller.SetPhase(BUS::phase_t::status); EXPECT_EQ(BUS::phase_t::status, controller.GetPhase()); + EXPECT_CALL(*device, Reset()).Times(1); controller.Reset(); EXPECT_TRUE(controller.IsBusFree()); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_EQ(0, controller.GetLength()); } TEST(AbstractControllerTest, SetGetStatus) { MockAbstractController controller(0); - controller.SetStatus(0x1234); - EXPECT_EQ(0x1234, controller.GetStatus()); -} - -TEST(AbstractControllerTest, SetPhase) -{ - MockAbstractController controller(0); - - controller.SetPhase(BUS::phase_t::selection); - EXPECT_TRUE(controller.IsSelection()); - - controller.SetPhase(BUS::phase_t::busfree); - EXPECT_TRUE(controller.IsBusFree()); - - controller.SetPhase(BUS::phase_t::command); - EXPECT_TRUE(controller.IsCommand()); - - controller.SetPhase(BUS::phase_t::status); - EXPECT_TRUE(controller.IsStatus()); - - controller.SetPhase(BUS::phase_t::datain); - EXPECT_TRUE(controller.IsDataIn()); - - controller.SetPhase(BUS::phase_t::dataout); - EXPECT_TRUE(controller.IsDataOut()); - - controller.SetPhase(BUS::phase_t::msgin); - EXPECT_TRUE(controller.IsMsgIn()); - - controller.SetPhase(BUS::phase_t::msgout); - EXPECT_TRUE(controller.IsMsgOut()); + controller.SetStatus(status::BUSY); + EXPECT_EQ(status::BUSY, controller.GetStatus()); } TEST(AbstractControllerTest, ProcessPhase) @@ -92,6 +73,12 @@ TEST(AbstractControllerTest, ProcessPhase) controller.SetPhase(BUS::phase_t::msgout); EXPECT_CALL(controller, MsgOut()).Times(1); controller.ProcessPhase(); + + controller.SetPhase(BUS::phase_t::reselection); + EXPECT_THROW(controller.ProcessPhase(), scsi_error_exception); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_THROW(controller.ProcessPhase(), scsi_error_exception); } TEST(AbstractControllerTest, DeviceLunLifeCycle) @@ -100,24 +87,22 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) const int LUN = 4; MockAbstractController controller(ID); - MockPrimaryDevice device1; - MockPrimaryDevice device2; - - EXPECT_FALSE(controller.HasLuns()); - - device1.SetLun(LUN); - device2.SetLun(32); + auto device1 = make_shared(LUN); + auto device2 = make_shared(32); + auto device3 = make_shared(-1); + EXPECT_EQ(0, controller.GetLunCount()); EXPECT_EQ(ID, controller.GetTargetId()); - EXPECT_TRUE(controller.AddDevice(&device1)); - EXPECT_FALSE(controller.AddDevice(&device2)); - EXPECT_TRUE(controller.HasLuns()); + EXPECT_TRUE(controller.AddDevice(device1)); + EXPECT_FALSE(controller.AddDevice(device2)); + EXPECT_FALSE(controller.AddDevice(device3)); + EXPECT_TRUE(controller.GetLunCount() > 0); EXPECT_TRUE(controller.HasDeviceForLun(LUN)); EXPECT_FALSE(controller.HasDeviceForLun(0)); - EXPECT_EQ(&device1, controller.GetDeviceForLun(LUN)); + EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN)); EXPECT_EQ(nullptr, controller.GetDeviceForLun(0)); - EXPECT_TRUE(controller.DeleteDevice(&device1)); - EXPECT_FALSE(controller.HasLuns()); + EXPECT_TRUE(controller.DeleteDevice(device1)); + EXPECT_EQ(0, controller.GetLunCount()); } TEST(AbstractControllerTest, ExtractInitiatorId) @@ -136,7 +121,7 @@ TEST(AbstractControllerTest, GetOpcode) { MockAbstractController controller(0); - vector& cmd = controller.InitCmd(2); + vector& cmd = controller.InitCmd(1); cmd[0] = 0x12; EXPECT_EQ(0x12, (int)controller.GetOpcode()); @@ -154,10 +139,23 @@ TEST(AbstractControllerTest, GetLun) EXPECT_EQ(LUN, controller.GetLun()); } -TEST(AbstractControllerTest, Ctrl) +TEST(AbstractControllerTest, Length) { MockAbstractController controller(0); EXPECT_FALSE(controller.HasValidLength()); + + controller.UpdateOffsetAndLength(); + EXPECT_EQ(0, controller.GetLength()); } +TEST(AbstractControllerTest, Offset) +{ + MockAbstractController controller(0); + + controller.ResetOffset(); + EXPECT_EQ(0, controller.GetOffset()); + + controller.UpdateOffsetAndLength(); + EXPECT_EQ(0, controller.GetOffset()); +} diff --git a/src/raspberrypi/test/command_util_test.cpp b/src/raspberrypi/test/command_util_test.cpp index fd42f018..cfb4c5d6 100644 --- a/src/raspberrypi/test/command_util_test.cpp +++ b/src/raspberrypi/test/command_util_test.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "rascsi_interface.pb.h" #include "command_util.h" @@ -59,3 +59,10 @@ TEST(CommandUtil, ParseParameters) TestSpecialDevice("printer"); TestSpecialDevice("services"); } + +TEST(CommandUtil, ReturnLocalizedError) +{ + MockCommandContext context; + + EXPECT_FALSE(ReturnLocalizedError(context, LocalizationKey::ERROR_LOG_LEVEL)); +} diff --git a/src/raspberrypi/test/controller_manager_test.cpp b/src/raspberrypi/test/controller_manager_test.cpp index ba0a5dfc..0297a5db 100644 --- a/src/raspberrypi/test/controller_manager_test.cpp +++ b/src/raspberrypi/test/controller_manager_test.cpp @@ -7,31 +7,56 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "devices/device_factory.h" #include "controllers/controller_manager.h" -TEST(ControllerManagerTest, ControllerManager) +TEST(ControllerManagerTest, LifeCycle) { const int ID = 4; - const int LUN = 6; + const int LUN1 = 2; + const int LUN2 = 3; + + MockBus bus; + ControllerManager controller_manager(bus); DeviceFactory device_factory; - ControllerManager controller_manager; - auto device = device_factory.CreateDevice(UNDEFINED, "services", ID); - device->SetId(ID); - device->SetLun(LUN); - - controller_manager.CreateScsiController(nullptr, device); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); + controller_manager.AttachToScsiController(ID, device); + auto controller = controller_manager.FindController(ID); + 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_NE(nullptr, controller_manager.FindController(ID)); EXPECT_EQ(nullptr, controller_manager.FindController(0)); - EXPECT_EQ(device, controller_manager.GetDeviceByIdAndLun(ID, LUN)); + EXPECT_NE(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1)); EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(0, 0)); - controller_manager.DeleteAllControllers(); - device_factory.DeleteAllDevices(); + device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services"); + controller_manager.AttachToScsiController(ID, device); + controller = controller_manager.FindController(ID); + controller_manager.DeleteController(controller); EXPECT_EQ(nullptr, controller_manager.FindController(ID)); - EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN)); + + controller_manager.DeleteAllControllers(); + EXPECT_EQ(nullptr, controller_manager.FindController(ID)); + EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1)); +} + +TEST(ControllerManagerTest, ResetAllControllers) +{ + const int ID = 2; + + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + controller_manager.AttachToScsiController(ID, device); + auto controller = controller_manager.FindController(ID); + EXPECT_NE(nullptr, controller); + + controller->SetStatus(status::BUSY); + controller_manager.ResetAllControllers(); + EXPECT_EQ(status::GOOD, controller->GetStatus()); } diff --git a/src/raspberrypi/test/device_factory_test.cpp b/src/raspberrypi/test/device_factory_test.cpp index 7a775461..b45b2d5c 100644 --- a/src/raspberrypi/test/device_factory_test.cpp +++ b/src/raspberrypi/test/device_factory_test.cpp @@ -7,9 +7,10 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "rascsi_exceptions.h" #include "rascsi_version.h" +#include "controllers/controller_manager.h" #include "devices/device.h" #include "devices/device_factory.h" #include @@ -37,27 +38,6 @@ TEST(DeviceFactoryTest, GetTypeForFile) EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED); } -TEST(DeviceFactoryTest, LifeCycle) -{ - DeviceFactory device_factory; - - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "services", -1); - EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHS", device->GetType()); - - list devices = device_factory.GetAllDevices(); - EXPECT_EQ(1, devices.size()); - EXPECT_EQ(device, devices.front()); - - EXPECT_EQ(device, device_factory.GetDeviceByIdAndLun(-1, 0)); - EXPECT_EQ(nullptr, device_factory.GetDeviceByIdAndLun(-1, 1)); - - device_factory.DeleteDevice(*device); - devices = device_factory.GetAllDevices(); - EXPECT_EQ(0, devices.size()); - EXPECT_EQ(nullptr, device_factory.GetDeviceByIdAndLun(-1, 0)); -} - TEST(DeviceFactoryTest, GetSectorSizes) { DeviceFactory device_factory; @@ -150,23 +130,27 @@ TEST(DeviceFactoryTest, GetDefaultParams) TEST(DeviceFactoryTest, UnknownDeviceType) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device1 = device_factory.CreateDevice(UNDEFINED, "test", -1); + auto device1 = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test"); EXPECT_EQ(nullptr, device1); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" - PrimaryDevice *device2 = device_factory.CreateDevice(SAHD, "test", -1); + auto device2 = device_factory.CreateDevice(controller_manager, SAHD, 0, "test"); #pragma GCC diagnostic pop EXPECT_EQ(nullptr, device2); } TEST(DeviceFactoryTest, SCHD_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.hda", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hda"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCHD", device->GetType()); EXPECT_TRUE(device->SupportsFile()); @@ -186,83 +170,64 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults) EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), device->GetRevision()); - device_factory.DeleteDevice(*device); - - device = device_factory.CreateDevice(UNDEFINED, "test.hds", -1); + device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hds"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCHD", device->GetType()); - device_factory.DeleteDevice(*device); - device = device_factory.CreateDevice(UNDEFINED, "test.hdi", -1); + device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hdi"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCHD", device->GetType()); - device_factory.DeleteDevice(*device); - device = device_factory.CreateDevice(UNDEFINED, "test.nhd", -1); + device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.nhd"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCHD", device->GetType()); - device_factory.DeleteDevice(*device); +} + +void TestRemovableDrive(const string& type, const string& filename, const string& product) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, filename); + EXPECT_NE(nullptr, device); + EXPECT_EQ(type, device->GetType()); + EXPECT_TRUE(device->SupportsFile()); + EXPECT_FALSE(device->SupportsParams()); + EXPECT_TRUE(device->IsProtectable()); + EXPECT_FALSE(device->IsProtected()); + EXPECT_FALSE(device->IsReadOnly()); + EXPECT_TRUE(device->IsRemovable()); + EXPECT_FALSE(device->IsRemoved()); + EXPECT_TRUE(device->IsLockable()); + EXPECT_FALSE(device->IsLocked()); + EXPECT_TRUE(device->IsStoppable()); + EXPECT_FALSE(device->IsStopped()); + + EXPECT_EQ("RaSCSI", device->GetVendor()); + EXPECT_EQ(product, device->GetProduct()); + EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), + device->GetRevision()); + } TEST(DeviceFactoryTest, SCRM_Device_Defaults) { - DeviceFactory device_factory; - - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.hdr", -1); - EXPECT_NE(nullptr, device); - EXPECT_EQ("SCRM", device->GetType()); - EXPECT_TRUE(device->SupportsFile()); - EXPECT_FALSE(device->SupportsParams()); - EXPECT_TRUE(device->IsProtectable()); - EXPECT_FALSE(device->IsProtected()); - EXPECT_FALSE(device->IsReadOnly()); - EXPECT_TRUE(device->IsRemovable()); - EXPECT_FALSE(device->IsRemoved()); - EXPECT_TRUE(device->IsLockable()); - EXPECT_FALSE(device->IsLocked()); - EXPECT_TRUE(device->IsStoppable()); - EXPECT_FALSE(device->IsStopped()); - - EXPECT_EQ("RaSCSI", device->GetVendor()); - EXPECT_EQ("SCSI HD (REM.)", device->GetProduct()); - EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), - device->GetRevision()); - - device_factory.DeleteDevice(*device); + TestRemovableDrive("SCRM", "test.hdr", "SCSI HD (REM.)"); } TEST(DeviceFactoryTest, SCMO_Device_Defaults) { - DeviceFactory device_factory; - - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.mos", -1); - EXPECT_NE(nullptr, device); - EXPECT_EQ("SCMO", device->GetType()); - EXPECT_TRUE(device->SupportsFile()); - EXPECT_FALSE(device->SupportsParams()); - EXPECT_TRUE(device->IsProtectable()); - EXPECT_FALSE(device->IsProtected()); - EXPECT_FALSE(device->IsReadOnly()); - EXPECT_TRUE(device->IsRemovable()); - EXPECT_FALSE(device->IsRemoved()); - EXPECT_TRUE(device->IsLockable()); - EXPECT_FALSE(device->IsLocked()); - EXPECT_TRUE(device->IsStoppable()); - EXPECT_FALSE(device->IsStopped()); - - EXPECT_EQ("RaSCSI", device->GetVendor()); - EXPECT_EQ("SCSI MO", device->GetProduct()); - EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), - device->GetRevision()); - - device_factory.DeleteDevice(*device); + TestRemovableDrive("SCMO", "test.mos", "SCSI MO"); } TEST(DeviceFactoryTest, SCCD_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.iso", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.iso"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCCD", device->GetType()); EXPECT_TRUE(device->SupportsFile()); @@ -281,15 +246,15 @@ TEST(DeviceFactoryTest, SCCD_Device_Defaults) EXPECT_EQ("SCSI CD-ROM", device->GetProduct()); EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), device->GetRevision()); - - device_factory.DeleteDevice(*device); } TEST(DeviceFactoryTest, SCBR_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "bridge", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "bridge"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCBR", device->GetType()); EXPECT_FALSE(device->SupportsFile()); @@ -308,15 +273,15 @@ TEST(DeviceFactoryTest, SCBR_Device_Defaults) EXPECT_EQ("RASCSI BRIDGE", device->GetProduct()); EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), device->GetRevision()); - - device_factory.DeleteDevice(*device); } TEST(DeviceFactoryTest, SCDP_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "daynaport", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "daynaport"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCDP", device->GetType()); EXPECT_FALSE(device->SupportsFile()); @@ -334,15 +299,15 @@ TEST(DeviceFactoryTest, SCDP_Device_Defaults) EXPECT_EQ("Dayna", device->GetVendor()); EXPECT_EQ("SCSI/Link", device->GetProduct()); EXPECT_EQ("1.4a", device->GetRevision()); - - device_factory.DeleteDevice(*device); } TEST(DeviceFactoryTest, SCHS_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "services", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCHS", device->GetType()); EXPECT_FALSE(device->SupportsFile()); @@ -361,15 +326,15 @@ TEST(DeviceFactoryTest, SCHS_Device_Defaults) EXPECT_EQ("Host Services", device->GetProduct()); EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), device->GetRevision()); - - device_factory.DeleteDevice(*device); } TEST(DeviceFactoryTest, SCLP_Device_Defaults) { + MockBus bus; DeviceFactory device_factory; + ControllerManager controller_manager(bus); - PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "printer", -1); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "printer"); EXPECT_NE(nullptr, device); EXPECT_EQ("SCLP", device->GetType()); EXPECT_FALSE(device->SupportsFile()); @@ -388,6 +353,4 @@ TEST(DeviceFactoryTest, SCLP_Device_Defaults) EXPECT_EQ("SCSI PRINTER", device->GetProduct()); EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), device->GetRevision()); - - device_factory.DeleteDevice(*device); } diff --git a/src/raspberrypi/test/device_test.cpp b/src/raspberrypi/test/device_test.cpp index 18356b9c..ab0d7c20 100644 --- a/src/raspberrypi/test/device_test.cpp +++ b/src/raspberrypi/test/device_test.cpp @@ -7,31 +7,15 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "rascsi_exceptions.h" #include "devices/device.h" -class TestDevice final : public Device -{ - FRIEND_TEST(DeviceTest, Params); - FRIEND_TEST(DeviceTest, StatusCode); - FRIEND_TEST(DeviceTest, Reset); - FRIEND_TEST(DeviceTest, Start); - FRIEND_TEST(DeviceTest, Stop); - FRIEND_TEST(DeviceTest, Eject); - -public: - - TestDevice() : Device("test") {} - ~TestDevice() override = default; -}; - TEST(DeviceTest, Properties) { - const int ID = 4; const int LUN = 5; - TestDevice device; + MockDevice device(LUN); EXPECT_FALSE(device.IsProtectable()); device.SetProtectable(true); @@ -69,31 +53,50 @@ TEST(DeviceTest, Properties) device.SetLocked(true); EXPECT_TRUE(device.IsLocked()); - device.SetId(ID); - EXPECT_EQ(ID, device.GetId()); + EXPECT_FALSE(device.SupportsParams()); + EXPECT_TRUE(device.SupportsFile()); + device.SupportsParams(true); + EXPECT_TRUE(device.SupportsParams()); + EXPECT_FALSE(device.SupportsFile()); - device.SetLun(LUN); EXPECT_EQ(LUN, device.GetLun()); } -TEST(DeviceTest, ProductData) +TEST(DeviceTest, Vendor) { - TestDevice device; + MockDevice device(0); - EXPECT_THROW(device.SetVendor(""), illegal_argument_exception); - EXPECT_THROW(device.SetVendor("123456789"), illegal_argument_exception); + EXPECT_THROW(device.SetVendor(""), invalid_argument); + EXPECT_THROW(device.SetVendor("123456789"), invalid_argument); device.SetVendor("12345678"); EXPECT_EQ("12345678", device.GetVendor()); +} - EXPECT_THROW(device.SetProduct(""), illegal_argument_exception); - EXPECT_THROW(device.SetProduct("12345678901234567"), illegal_argument_exception); +TEST(DeviceTest, Product) +{ + MockDevice device(0); + + EXPECT_THROW(device.SetProduct(""), invalid_argument); + EXPECT_THROW(device.SetProduct("12345678901234567"), invalid_argument); device.SetProduct("1234567890123456"); EXPECT_EQ("1234567890123456", device.GetProduct()); + device.SetProduct("xyz", false); + EXPECT_EQ("1234567890123456", device.GetProduct()) << "Changing vital product data is not SCSI complient"; +} - EXPECT_THROW(device.SetRevision(""), illegal_argument_exception); - EXPECT_THROW(device.SetRevision("12345"), illegal_argument_exception); +TEST(DeviceTest, Revision) +{ + MockDevice device(0); + + EXPECT_THROW(device.SetRevision(""), invalid_argument); + EXPECT_THROW(device.SetRevision("12345"), invalid_argument); device.SetRevision("1234"); EXPECT_EQ("1234", device.GetRevision()); +} + +TEST(DeviceTest, GetPaddedName) +{ + MockDevice device(0); device.SetVendor("V"); device.SetProduct("P"); @@ -104,7 +107,7 @@ TEST(DeviceTest, ProductData) TEST(DeviceTest, Params) { - TestDevice device; + MockDevice device(0); unordered_map params; params["key"] = "value"; @@ -124,23 +127,15 @@ TEST(DeviceTest, Params) TEST(DeviceTest, StatusCode) { - TestDevice device; + MockDevice device(0); device.SetStatusCode(123); EXPECT_EQ(123, device.GetStatusCode()); } -TEST(DeviceTest, Init) -{ - TestDevice device; - unordered_map params; - - EXPECT_TRUE(device.Init(params)); -} - TEST(DeviceTest, Reset) { - TestDevice device; + MockDevice device(0); device.SetLocked(true); device.SetAttn(true); @@ -153,7 +148,7 @@ TEST(DeviceTest, Reset) TEST(DeviceTest, Start) { - TestDevice device; + MockDevice device(0); device.SetStopped(true); device.SetReady(false); @@ -166,7 +161,7 @@ TEST(DeviceTest, Start) TEST(DeviceTest, Stop) { - TestDevice device; + MockDevice device(0); device.SetReady(true); device.SetAttn(true); @@ -179,7 +174,7 @@ TEST(DeviceTest, Stop) TEST(DeviceTest, Eject) { - TestDevice device; + MockDevice device(0); device.SetReady(false); device.SetRemovable(false); diff --git a/src/raspberrypi/test/disk_test.cpp b/src/raspberrypi/test/disk_test.cpp index fe2b1699..89429295 100644 --- a/src/raspberrypi/test/disk_test.cpp +++ b/src/raspberrypi/test/disk_test.cpp @@ -7,151 +7,169 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" +#include "scsi.h" +#include "devices/disk.h" #include "rascsi_exceptions.h" +using namespace scsi_defs; + +TEST(DiskTest, Dispatch) +{ + MockAbstractController controller(0); + const unordered_set sector_sizes; + auto disk = make_shared(); + + controller.AddDevice(disk); + + controller.InitCmd(6); + + disk->MediumChanged(); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); +} + TEST(DiskTest, Rezero) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdRezero), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRezero), scsi_error_exception) << "REZERO must fail because drive is not ready"; - disk.SetReady(true); + disk->SetReady(true); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRezero)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, FormatUnit) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(6); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdFormat), scsi_error_exception); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdFormat), scsi_error_exception); - disk.SetReady(true); + disk->SetReady(true); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdFormat)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdFormat)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[1] = 0x10; cmd[4] = 1; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdFormat), scsi_error_exception); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdFormat), scsi_error_exception); } TEST(DiskTest, ReassignBlocks) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReassign), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReassign), scsi_error_exception) << "REASSIGN must fail because drive is not ready"; - disk.SetReady(true); + disk->SetReady(true); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReassign)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReassign)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, Seek) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(10); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSeek6), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_error_exception) << "SEEK(6) must fail for a medium with 0 blocks"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSeek10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_error_exception) << "SEEK(10) must fail for a medium with 0 blocks"; - disk.SetBlockCount(1); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSeek6), scsi_error_exception) + disk->SetBlockCount(1); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_error_exception) << "SEEK(6) must fail because drive is not ready"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSeek10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_error_exception) << "SEEK(10) must fail because drive is not ready"; - disk.SetReady(true); + disk->SetReady(true); // Block count for SEEK(6) cmd[4] = 1; EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSeek6)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[4] = 0; // Block count for SEEK(10) cmd[5] = 1; EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSeek10)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, ReadCapacity) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(16); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) << "Neithed READ CAPACITY(16) nor READ LONG(16)"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity10), scsi_error_exception) << "READ CAPACITY(10) must fail because drive is not ready"; // READ CAPACITY(16), not READ LONG(16) cmd[1] = 0x10; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) << "READ CAPACITY(16) must fail because drive is not ready"; cmd[1] = 0x00; - disk.SetReady(true); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity10), scsi_error_exception) + disk->SetReady(true); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity10), scsi_error_exception) << "READ CAPACITY(10) must fail because the medium has no capacity"; // READ CAPACITY(16), not READ LONG(16) cmd[1] = 0x10; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) << "READ CAPACITY(16) must fail because the medium has no capacity"; cmd[1] = 0x00; - disk.SetBlockCount(0x12345678); + disk->SetBlockCount(0x12345678); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadCapacity10)); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10)); EXPECT_EQ(0x12, controller.GetBuffer()[0]); EXPECT_EQ(0x34, controller.GetBuffer()[1]); EXPECT_EQ(0x56, controller.GetBuffer()[2]); EXPECT_EQ(0x77, controller.GetBuffer()[3]); - disk.SetBlockCount(0x1234567887654321); + disk->SetBlockCount(0x1234567887654321); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadCapacity10)); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10)); EXPECT_EQ(0xff, controller.GetBuffer()[0]); EXPECT_EQ(0xff, controller.GetBuffer()[1]); EXPECT_EQ(0xff, controller.GetBuffer()[2]); EXPECT_EQ(0xff, controller.GetBuffer()[3]); - disk.SetSectorSizeInBytes(1024); + disk->SetSectorSizeInBytes(1024); // READ CAPACITY(16), not READ LONG(16) cmd[1] = 0x10; EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); EXPECT_EQ(0x12, controller.GetBuffer()[0]); EXPECT_EQ(0x34, controller.GetBuffer()[1]); EXPECT_EQ(0x56, controller.GetBuffer()[2]); @@ -168,172 +186,172 @@ TEST(DiskTest, ReadCapacity) TEST(DiskTest, ReadWriteLong) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(16); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadLong10)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdWriteLong10)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[2] = 1; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadLong10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_error_exception) << "READ LONG(10) must fail because the capacity is exceeded"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdWriteLong10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_error_exception) << "WRITE LONG(10) must fail because the capacity is exceeded"; // READ LONG(16), not READ CAPACITY(16) cmd[1] = 0x11; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) << "READ LONG(16) must fail because the capacity is exceeded"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdWriteLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_error_exception) << "WRITE LONG(16) must fail because the capacity is exceeded"; cmd[2] = 0; cmd[7] = 1; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadLong10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_error_exception) << "READ LONG(10) must fail because it currently only supports 0 bytes transfer length"; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdWriteLong10), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_error_exception) << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; cmd[7] = 0; // READ LONG(16), not READ CAPACITY(16) cmd[1] = 0x11; EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[1] = 0x00; EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdWriteLong16)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[13] = 1; // READ LONG(16), not READ CAPACITY(16) cmd[1] = 0x11; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_error_exception) << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; cmd[1] = 0x00; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdWriteLong16), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_error_exception) << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length"; } TEST(DiskTest, ReserveRelease) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); controller.InitCmd(6); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReserve6)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRelease6)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRelease6)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, SendDiagnostic) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(6); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSendDiag)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSendDiag)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[1] = 0x10; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) << "SEND DIAGNOSTIC must fail because PF bit is not supported"; cmd[1] = 0; cmd[3] = 1; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; cmd[3] = 0; cmd[4] = 1; - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_error_exception) << "SEND DIAGNOSTIC must fail because parameter list is not supported"; } TEST(DiskTest, PreventAllowMediumRemoval) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); vector& cmd = controller.InitCmd(6); - EXPECT_THROW(disk.Dispatch(scsi_command::eCmdRemoval), scsi_error_exception) + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRemoval), scsi_error_exception) << "REMOVAL must fail because drive is not ready"; - disk.SetReady(true); + disk->SetReady(true); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRemoval)); - EXPECT_EQ(0, controller.GetStatus()); - EXPECT_FALSE(disk.IsLocked()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_FALSE(disk->IsLocked()); cmd[4] = 1; EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRemoval)); - EXPECT_EQ(0, controller.GetStatus()); - EXPECT_TRUE(disk.IsLocked()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_TRUE(disk->IsLocked()); } TEST(DiskTest, SynchronizeCache) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); controller.InitCmd(10); - EXPECT_CALL(disk, FlushCache()).Times(1); + EXPECT_CALL(*disk, FlushCache()).Times(1); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSynchronizeCache10)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); controller.InitCmd(16); - EXPECT_CALL(disk, FlushCache()).Times(1); + EXPECT_CALL(*disk, FlushCache()).Times(1); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSynchronizeCache16)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, ReadDefectData) { - MockScsiController controller(nullptr, 0); - MockSCSIHD_NEC disk; + MockAbstractController controller(0); + auto disk = make_shared(); - controller.AddDevice(&disk); + controller.AddDevice(disk); controller.InitCmd(10); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadDefectData10)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, SectorSize) { - MockSCSIHD_NEC disk; + MockDisk disk; - unordered_set sizes = { 1, 2, 3 }; + unordered_set sizes = { 512, 1024 }; disk.SetSectorSizes(sizes); EXPECT_TRUE(disk.IsSectorSizeConfigurable()); @@ -375,7 +393,8 @@ TEST(DiskTest, SectorSize) TEST(DiskTest, ConfiguredSectorSize) { DeviceFactory device_factory; - MockSCSIHD_NEC disk; + const unordered_set sector_sizes; + MockSCSIHD disk(0, sector_sizes, false); EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); EXPECT_EQ(512, disk.GetConfiguredSectorSize()); @@ -386,7 +405,7 @@ TEST(DiskTest, ConfiguredSectorSize) TEST(DiskTest, BlockCount) { - MockSCSIHD_NEC disk; + MockDisk disk; disk.SetBlockCount(0x1234567887654321); EXPECT_EQ(0x1234567887654321, disk.GetBlockCount()); diff --git a/src/raspberrypi/test/file_support_test.cpp b/src/raspberrypi/test/file_support_test.cpp index 968e7d21..08ca9396 100644 --- a/src/raspberrypi/test/file_support_test.cpp +++ b/src/raspberrypi/test/file_support_test.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "devices/file_support.h" class TestFileSupport : public FileSupport diff --git a/src/raspberrypi/test/testing.h b/src/raspberrypi/test/mocks.h similarity index 54% rename from src/raspberrypi/test/testing.h rename to src/raspberrypi/test/mocks.h index 5ac012e1..ec7d316e 100644 --- a/src/raspberrypi/test/testing.h +++ b/src/raspberrypi/test/mocks.h @@ -11,25 +11,102 @@ #include -#include "controllers/controller_manager.h" -#include "devices/device_factory.h" +#include "controllers/scsi_controller.h" #include "devices/primary_device.h" #include "devices/scsihd.h" #include "devices/scsihd_nec.h" #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/host_services.h" +#include "protobuf_serializer.h" +#include "command_context.h" +#include "localizer.h" + +class MockBus final : public BUS //NOSONAR Having many fields/methods cannot be avoided +{ +public: + + MOCK_METHOD(bool, Init, (mode_e), (override)); + MOCK_METHOD(void, Reset, (), (override)); + MOCK_METHOD(void, Cleanup, (), (override)); + MOCK_METHOD(bool, GetBSY, (), (const override)); + MOCK_METHOD(void, SetBSY, (bool), (override)); + MOCK_METHOD(bool, GetSEL, (), (const override)); + MOCK_METHOD(void, SetSEL, (bool), (override)); + MOCK_METHOD(bool, GetATN, (), (const override)); + MOCK_METHOD(void, SetATN, (bool), (override)); + MOCK_METHOD(bool, GetACK, (), (const override)); + MOCK_METHOD(void, SetACK, (bool), (override)); + MOCK_METHOD(bool, GetRST, (), (const override)); + MOCK_METHOD(void, SetRST, (bool), (override)); + MOCK_METHOD(bool, GetMSG, (), (const override)); + MOCK_METHOD(void, SetMSG, (bool), (override)); + MOCK_METHOD(bool, GetCD, (), (const override)); + MOCK_METHOD(void, SetCD, (bool), (override)); + MOCK_METHOD(bool, GetIO, (), (override)); + MOCK_METHOD(void, SetIO, (bool), (override)); + MOCK_METHOD(bool, GetREQ, (), (const override)); + MOCK_METHOD(void, SetREQ, (bool), (override)); + MOCK_METHOD(BYTE, GetDAT, (), (override)); + MOCK_METHOD(void, SetDAT, (BYTE), (override)); + MOCK_METHOD(bool, GetDP, (), (const override)); + MOCK_METHOD(uint32_t, Acquire, (), (override)); + MOCK_METHOD(int, CommandHandShake, (BYTE *), (override)); + MOCK_METHOD(int, ReceiveHandShake, (BYTE *, int), (override)); + MOCK_METHOD(int, SendHandShake, (BYTE *, int, int), (override)); + MOCK_METHOD(bool, GetSignal, (int), (const override)); + MOCK_METHOD(void, SetSignal, (int, bool), (override)); + + MockBus() = default; + ~MockBus() override = default; +}; + +class MockPhaseHandler : public PhaseHandler +{ + FRIEND_TEST(PhaseHandlerTest, Phases); + +public: + + MOCK_METHOD(BUS::phase_t, Process, (int), (override)); + MOCK_METHOD(void, Status, (), ()); + MOCK_METHOD(void, DataIn, (), ()); + MOCK_METHOD(void, DataOut, (), ()); + MOCK_METHOD(void, BusFree, (), ()); + MOCK_METHOD(void, Selection, (), ()); + MOCK_METHOD(void, Command, (), ()); + MOCK_METHOD(void, MsgIn, (), ()); + MOCK_METHOD(void, MsgOut, (), ()); + + using PhaseHandler::PhaseHandler; +}; class MockAbstractController final : public AbstractController //NOSONAR Having many fields/methods cannot be avoided { FRIEND_TEST(AbstractControllerTest, Reset); - FRIEND_TEST(AbstractControllerTest, SetPhase); FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, GetOpcode); FRIEND_TEST(AbstractControllerTest, GetLun); - FRIEND_TEST(AbstractControllerTest, Ctrl); + FRIEND_TEST(AbstractControllerTest, Length); + FRIEND_TEST(AbstractControllerTest, Offset); + FRIEND_TEST(PrimaryDeviceTest, Inquiry); + FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); + FRIEND_TEST(PrimaryDeviceTest, RequestSense); + FRIEND_TEST(PrimaryDeviceTest, ReportLuns); + FRIEND_TEST(PrimaryDeviceTest, UnknownCommand); + FRIEND_TEST(DiskTest, Dispatch); + FRIEND_TEST(DiskTest, Rezero); + FRIEND_TEST(DiskTest, FormatUnit); + FRIEND_TEST(DiskTest, ReassignBlocks); + FRIEND_TEST(DiskTest, Seek); + FRIEND_TEST(DiskTest, ReadCapacity); + FRIEND_TEST(DiskTest, ReadWriteLong); + FRIEND_TEST(DiskTest, ReserveRelease); + FRIEND_TEST(DiskTest, SendDiagnostic); + FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); + FRIEND_TEST(DiskTest, SynchronizeCache); + FRIEND_TEST(DiskTest, ReadDefectData); public: @@ -48,36 +125,48 @@ public: MOCK_METHOD(void, SetByteTransfer, (bool), (override)); MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override)); - explicit MockAbstractController(int target_id) : AbstractController(nullptr, target_id, 32) {} + explicit MockAbstractController(int target_id) : AbstractController(bus, target_id, 32) { + AllocateBuffer(512); + } ~MockAbstractController() override = default; + + MockBus bus; }; class MockScsiController final : public ScsiController { - FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); - FRIEND_TEST(PrimaryDeviceTest, Inquiry); + FRIEND_TEST(ScsiControllerTest, RequestSense); FRIEND_TEST(PrimaryDeviceTest, RequestSense); - FRIEND_TEST(PrimaryDeviceTest, ReportLuns); - FRIEND_TEST(PrimaryDeviceTest, UnknownCommand); - FRIEND_TEST(DiskTest, Rezero); - FRIEND_TEST(DiskTest, FormatUnit); - FRIEND_TEST(DiskTest, ReassignBlocks); - FRIEND_TEST(DiskTest, Seek); - FRIEND_TEST(DiskTest, ReadCapacity); - FRIEND_TEST(DiskTest, ReadWriteLong); - FRIEND_TEST(DiskTest, ReserveRelease); - FRIEND_TEST(DiskTest, SendDiagnostic); - FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); - FRIEND_TEST(DiskTest, SynchronizeCache); - FRIEND_TEST(DiskTest, ReadDefectData); public: + MOCK_METHOD(void, Reset, (), ()); MOCK_METHOD(void, Status, (), ()); MOCK_METHOD(void, DataIn, (), ()); MOCK_METHOD(void, DataOut, (), ()); + MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override)); - using ScsiController::ScsiController; + explicit MockScsiController(int target_id) : ScsiController(bus, target_id) {} + ~MockScsiController() override = default; + + MockBus bus; +}; + +class MockDevice final : public Device +{ + FRIEND_TEST(DeviceTest, Params); + FRIEND_TEST(DeviceTest, StatusCode); + FRIEND_TEST(DeviceTest, Reset); + FRIEND_TEST(DeviceTest, Start); + FRIEND_TEST(DeviceTest, Stop); + FRIEND_TEST(DeviceTest, Eject); + +public: + + MOCK_METHOD(int, GetId, (), (const)); + + explicit MockDevice(int lun) : Device("test", lun) {} + ~MockDevice() override = default; }; class MockPrimaryDevice final : public PrimaryDevice @@ -86,12 +175,15 @@ class MockPrimaryDevice final : public PrimaryDevice FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); FRIEND_TEST(PrimaryDeviceTest, RequestSense); FRIEND_TEST(PrimaryDeviceTest, Inquiry); + FRIEND_TEST(ScsiControllerTest, RequestSense); + FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice); public: + MOCK_METHOD(void, Reset, (), ()); MOCK_METHOD(vector, InquiryInternal, (), (const)); - MockPrimaryDevice() : PrimaryDevice("test") {} + explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {} ~MockPrimaryDevice() override = default; }; @@ -99,12 +191,12 @@ class MockModePageDevice final : public ModePageDevice { FRIEND_TEST(ModePagesTest, ModePageDevice_AddModePages); - MockModePageDevice() : ModePageDevice("test") {} + explicit MockModePageDevice(int lun) : ModePageDevice("test", lun) {} ~MockModePageDevice() override = default; MOCK_METHOD(vector, InquiryInternal, (), (const)); - MOCK_METHOD(int, ModeSense6, (const vector&, vector&, int), (const override)); - MOCK_METHOD(int, ModeSense10, (const vector&, vector&, int), (const override)); + MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); void SetUpModePages(map>& pages, int page, bool) const override { // Return dummy data for other pages than page 0 @@ -115,17 +207,8 @@ class MockModePageDevice final : public ModePageDevice } }; -class MockSCSIHD final : public SCSIHD +class MockDisk final : public Disk { - FRIEND_TEST(ModePagesTest, SCSIHD_SetUpModePages); - - explicit MockSCSIHD(const unordered_set& sector_sizes) : SCSIHD(sector_sizes, false) {} - ~MockSCSIHD() override = default; -}; - -class MockSCSIHD_NEC final : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests -{ - FRIEND_TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages); FRIEND_TEST(DiskTest, Rezero); FRIEND_TEST(DiskTest, FormatUnit); FRIEND_TEST(DiskTest, ReassignBlocks); @@ -138,9 +221,30 @@ class MockSCSIHD_NEC final : public SCSIHD_NEC //NOSONAR Ignore inheritance hier FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); FRIEND_TEST(DiskTest, SectorSize); - FRIEND_TEST(DiskTest, ConfiguredSectorSize); FRIEND_TEST(DiskTest, BlockCount); +public: + + MOCK_METHOD(vector, InquiryInternal, (), (const)); + MOCK_METHOD(void, FlushCache, (), (override)); + + MockDisk() : Disk("test", 0) {} + ~MockDisk() override = default; +}; + +class MockSCSIHD final : public SCSIHD +{ + FRIEND_TEST(DiskTest, ConfiguredSectorSize); + FRIEND_TEST(ModePagesTest, SCSIHD_SetUpModePages); + FRIEND_TEST(RascsiExecutorTest, SetSectorSize); + + using SCSIHD::SCSIHD; +}; + +class MockSCSIHD_NEC final : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests +{ + FRIEND_TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages); + MOCK_METHOD(void, FlushCache, (), (override)); using SCSIHD_NEC::SCSIHD_NEC; @@ -166,3 +270,14 @@ class MockHostServices final : public HostServices using HostServices::HostServices; }; + +class MockCommandContext : public CommandContext +{ + ProtobufSerializer s; + Localizer l; + +public: + + MockCommandContext() : CommandContext(s, l, STDOUT_FILENO, "") {} + ~MockCommandContext() = default; +}; diff --git a/src/raspberrypi/test/mode_pages_test.cpp b/src/raspberrypi/test/mode_pages_test.cpp index d853fc2c..652516a6 100644 --- a/src/raspberrypi/test/mode_pages_test.cpp +++ b/src/raspberrypi/test/mode_pages_test.cpp @@ -7,9 +7,10 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "spdlog/spdlog.h" #include "rascsi_exceptions.h" +#include "controllers/controller_manager.h" #include "devices/scsi_command_util.h" #include "devices/scsihd.h" #include "devices/scsihd_nec.h" @@ -24,11 +25,12 @@ TEST(ModePagesTest, ModePageDevice_AddModePages) vector cdb(6); vector buf(512); - MockModePageDevice device; + MockModePageDevice device(0); cdb[2] = 0x3f; - EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length was not limited"; - EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length was not limited"; + EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, -1)) << "Negative maximum length must be rejected"; + EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length 0 must be rejected"; + EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length 1 must be rejected"; cdb[2] = 0x00; EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_error_exception) << "Data for non-existing mode page 0 were returned"; @@ -38,7 +40,7 @@ TEST(ModePagesTest, SCSIHD_SetUpModePages) { map> mode_pages; const unordered_set sector_sizes; - MockSCSIHD device(sector_sizes); + MockSCSIHD device(0, sector_sizes, false); device.SetUpModePages(mode_pages, 0x3f, false); EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; @@ -52,7 +54,7 @@ TEST(ModePagesTest, SCSIHD_SetUpModePages) TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages) { map> mode_pages; - MockSCSIHD_NEC device; + MockSCSIHD_NEC device(0); device.SetUpModePages(mode_pages, 0x3f, false); EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; @@ -67,7 +69,7 @@ TEST(ModePagesTest, SCSICD_SetUpModePages) { map> mode_pages; const unordered_set sector_sizes; - MockSCSICD device(sector_sizes); + MockSCSICD device(0, sector_sizes); device.SetUpModePages(mode_pages, 0x3f, false); EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages"; @@ -84,7 +86,7 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages) { map> mode_pages; const unordered_set sector_sizes; - MockSCSIMO device(sector_sizes); + MockSCSIMO device(0, sector_sizes); device.SetUpModePages(mode_pages, 0x3f, false); EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages"; @@ -98,11 +100,12 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages) TEST(ModePagesTest, HostServices_SetUpModePages) { + MockBus bus; + ControllerManager controller_manager(bus); + MockHostServices device(0, controller_manager); map> mode_pages; - DeviceFactory device_factory; - MockHostServices device(device_factory); - device.SetUpModePages(mode_pages, 0x3f, false); + device.SetUpModePages(mode_pages, 0x3f, false); EXPECT_EQ(1, mode_pages.size()) << "Unexpected number of mode pages"; EXPECT_EQ(10, mode_pages[32].size()); } diff --git a/src/raspberrypi/test/phase_handler_test.cpp b/src/raspberrypi/test/phase_handler_test.cpp new file mode 100644 index 00000000..93d1613f --- /dev/null +++ b/src/raspberrypi/test/phase_handler_test.cpp @@ -0,0 +1,40 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "controllers/phase_handler.h" + +TEST(PhaseHandlerTest, Phases) +{ + MockPhaseHandler handler; + + handler.SetPhase(BUS::phase_t::selection); + EXPECT_TRUE(handler.IsSelection()); + + handler.SetPhase(BUS::phase_t::busfree); + EXPECT_TRUE(handler.IsBusFree()); + + handler.SetPhase(BUS::phase_t::command); + EXPECT_TRUE(handler.IsCommand()); + + handler.SetPhase(BUS::phase_t::status); + EXPECT_TRUE(handler.IsStatus()); + + handler.SetPhase(BUS::phase_t::datain); + EXPECT_TRUE(handler.IsDataIn()); + + handler.SetPhase(BUS::phase_t::dataout); + EXPECT_TRUE(handler.IsDataOut()); + + handler.SetPhase(BUS::phase_t::msgin); + EXPECT_TRUE(handler.IsMsgIn()); + + handler.SetPhase(BUS::phase_t::msgout); + EXPECT_TRUE(handler.IsMsgOut()); +} diff --git a/src/raspberrypi/test/primary_device_test.cpp b/src/raspberrypi/test/primary_device_test.cpp index 2cde1cc0..15ef9729 100644 --- a/src/raspberrypi/test/primary_device_test.cpp +++ b/src/raspberrypi/test/primary_device_test.cpp @@ -7,101 +7,117 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" +#include "scsi.h" #include "rascsi_exceptions.h" #include "devices/primary_device.h" #include "devices/device_factory.h" +using namespace scsi_defs; + +TEST(PrimaryDeviceTest, GetId) +{ + const int ID = 5; + + MockAbstractController controller(ID); + auto device = make_shared(0); + + 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) { - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device; + MockAbstractController controller(0); + auto device = make_shared(0); - controller.AddDevice(&device); + controller.AddDevice(device); EXPECT_CALL(controller, Status()).Times(1); - device.EnterStatusPhase(); + device->EnterStatusPhase(); EXPECT_CALL(controller, DataIn()).Times(1); - device.EnterDataInPhase(); + device->EnterDataInPhase(); EXPECT_CALL(controller, DataOut()).Times(1); - device.EnterDataOutPhase(); + device->EnterDataOutPhase(); } TEST(PrimaryDeviceTest, TestUnitReady) { - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device; + MockAbstractController controller(0); + auto device = make_shared(0); - controller.AddDevice(&device); + controller.AddDevice(device); - device.SetReset(true); - device.SetAttn(true); - device.SetReady(false); + device->SetReset(true); + device->SetAttn(true); + device->SetReady(false); EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); - device.SetReset(false); + device->SetReset(false); EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); - device.SetReset(true); - device.SetAttn(false); + device->SetReset(true); + device->SetAttn(false); EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); - device.SetReset(false); - device.SetAttn(true); + device->SetReset(false); + device->SetAttn(true); EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); - device.SetAttn(false); + device->SetAttn(false); EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception); - device.SetReady(true); + device->SetReady(true); EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdTestUnitReady)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device; + MockAbstractController controller(0); + auto device = make_shared(0); - device.SetController(&controller); + device->SetController(&controller); vector& cmd = controller.InitCmd(6); // ALLOCATION LENGTH cmd[4] = 255; - ON_CALL(device, InquiryInternal()).WillByDefault([&device]() { - return device.HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); + ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { + return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); }); - EXPECT_CALL(device, InquiryInternal()).Times(1); + EXPECT_CALL(*device, InquiryInternal()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry)); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); EXPECT_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported"; - EXPECT_TRUE(controller.AddDevice(&device)); - EXPECT_FALSE(controller.AddDevice(&device)) << "Duplicate LUN was not rejected"; - EXPECT_CALL(device, InquiryInternal()).Times(1); + EXPECT_TRUE(controller.AddDevice(device)); + EXPECT_FALSE(controller.AddDevice(device)) << "Duplicate LUN was not rejected"; + EXPECT_CALL(*device, InquiryInternal()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry)); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); 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(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([&device]() { + return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); }); - EXPECT_CALL(device, InquiryInternal()).Times(1); + EXPECT_CALL(*device, InquiryInternal()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry)); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); 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"; @@ -110,41 +126,41 @@ TEST(PrimaryDeviceTest, Inquiry) cmd[1] = 0x01; EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "EVPD bit is not supported"; + EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "EVPD bit is not supported"; cmd[2] = 0x01; EXPECT_CALL(controller, DataIn()).Times(0); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "PAGE CODE field is not supported"; + EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "PAGE CODE field is not supported"; cmd[1] = 0x00; cmd[2] = 0x00; // ALLOCATION LENGTH cmd[4] = 1; - EXPECT_CALL(device, InquiryInternal()).Times(1); + EXPECT_CALL(*device, InquiryInternal()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry)); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size"; EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling"; } TEST(PrimaryDeviceTest, RequestSense) { - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device; + MockAbstractController controller(0); + auto device = make_shared(0); - controller.AddDevice(&device); + controller.AddDevice(device); vector& cmd = controller.InitCmd(6); // ALLOCATION LENGTH cmd[4] = 255; - device.SetReady(false); - EXPECT_THROW(device.Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception); + device->SetReady(false); + EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception); - device.SetReady(true); + device->SetReady(true); EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device.Dispatch(scsi_command::eCmdRequestSense)); - EXPECT_EQ(0, controller.GetStatus()); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(PrimaryDeviceTest, ReportLuns) @@ -152,15 +168,13 @@ TEST(PrimaryDeviceTest, ReportLuns) const int LUN1 = 1; const int LUN2 = 4; - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device1; - MockPrimaryDevice device2; + MockAbstractController controller(0); + auto device1 = make_shared(LUN1); + auto device2 = make_shared(LUN2); - device1.SetLun(LUN1); - controller.AddDevice(&device1); + controller.AddDevice(device1); EXPECT_TRUE(controller.HasDeviceForLun(LUN1)); - device2.SetLun(LUN2); - controller.AddDevice(&device2); + controller.AddDevice(device2); EXPECT_TRUE(controller.HasDeviceForLun(LUN2)); vector& cmd = controller.InitCmd(10); @@ -168,7 +182,7 @@ TEST(PrimaryDeviceTest, ReportLuns) cmd[9] = 255; EXPECT_CALL(controller, DataIn()).Times(1); - EXPECT_TRUE(device1.Dispatch(scsi_command::eCmdReportLuns)); + EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns)); const vector& buffer = controller.GetBuffer(); EXPECT_EQ(0x00, buffer[0]) << "Wrong data length"; EXPECT_EQ(0x00, buffer[1]) << "Wrong data length"; @@ -192,23 +206,47 @@ TEST(PrimaryDeviceTest, ReportLuns) EXPECT_EQ(LUN2, buffer[23]) << "Wrong LUN2 number"; cmd[2] = 0x01; - EXPECT_THROW(device1.Dispatch(scsi_command::eCmdReportLuns), scsi_error_exception) << "Only SELECT REPORT mode 0 is supported"; + EXPECT_THROW(device1->Dispatch(scsi_command::eCmdReportLuns), scsi_error_exception) << "Only SELECT REPORT mode 0 is supported"; } TEST(PrimaryDeviceTest, UnknownCommand) { - MockScsiController controller(nullptr, 0); - MockPrimaryDevice device; + MockAbstractController controller(0); + auto device = make_shared(0); - controller.AddDevice(&device); + controller.AddDevice(device); controller.InitCmd(1); - EXPECT_FALSE(device.Dispatch((scsi_command)0xFF)); + EXPECT_FALSE(device->Dispatch((scsi_command)0xFF)); +} + +TEST(PrimaryDeviceTest, WriteByteSequence) +{ + vector data; + MockPrimaryDevice device(0); + + EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device must not support writing byte sequences"; } TEST(PrimaryDeviceTest, GetSendDelay) { - MockPrimaryDevice device; + MockPrimaryDevice device(0); EXPECT_EQ(-1, device.GetSendDelay()); } + +TEST(PrimaryDeviceTest, Init) +{ + unordered_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/src/raspberrypi/test/protobuf_serializer_test.cpp b/src/raspberrypi/test/protobuf_serializer_test.cpp new file mode 100644 index 00000000..8a7d5c16 --- /dev/null +++ b/src/raspberrypi/test/protobuf_serializer_test.cpp @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "rascsi_exceptions.h" +#include "rascsi_interface.pb.h" +#include "protobuf_serializer.h" + +using namespace rascsi_interface; + +TEST(ProtobufSerializerTest, SerializeMessage) +{ + PbResult message; + ProtobufSerializer serializer; + + serializer.SerializeMessage(STDOUT_FILENO, message); + EXPECT_THROW(serializer.SerializeMessage(-1, message), io_exception); +} diff --git a/src/raspberrypi/test/rascsi_exceptions_test.cpp b/src/raspberrypi/test/rascsi_exceptions_test.cpp new file mode 100644 index 00000000..d15d2ba2 --- /dev/null +++ b/src/raspberrypi/test/rascsi_exceptions_test.cpp @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "rascsi_exceptions.h" + +using namespace scsi_defs; + +TEST(RascsiExceptionsTest, IoException) +{ + try { + throw io_exception("msg"); + } + catch(const io_exception& e) { + EXPECT_EQ("msg", e.get_msg()); + } +} + +TEST(RascsiExceptionsTest, FileNotFoundException) +{ + try { + throw file_not_found_exception("msg"); + } + catch(const file_not_found_exception& e) { + EXPECT_EQ("msg", e.get_msg()); + } +} + +TEST(RascsiExceptionsTest, ScsiErrorException) +{ + try { + throw scsi_error_exception(); + } + catch(const scsi_error_exception& e) { + EXPECT_EQ(sense_key::ABORTED_COMMAND, e.get_sense_key()); + EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); + EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); + } + + try { + throw scsi_error_exception(sense_key::UNIT_ATTENTION); + } + catch(const scsi_error_exception& e) { + EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); + EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); + EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); + } + + try { + throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE); + } + catch(const scsi_error_exception& e) { + EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); + EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); + EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); + } + + try { + throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE, status::BUSY); + } + catch(const scsi_error_exception& e) { + EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); + EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); + EXPECT_EQ(status::BUSY, e.get_status()); + } +} diff --git a/src/raspberrypi/test/rascsi_executor_test.cpp b/src/raspberrypi/test/rascsi_executor_test.cpp new file mode 100644 index 00000000..9608e122 --- /dev/null +++ b/src/raspberrypi/test/rascsi_executor_test.cpp @@ -0,0 +1,491 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "controllers/controller_manager.h" +#include "devices/device_factory.h" +#include "command_util.h" +#include "rascsi_response.h" +#include "rascsi_image.h" +#include "rascsi_executor.h" + +using namespace rascsi_interface; +using namespace command_util; + +TEST(RascsiExecutorTest, ProcessCmd) +{ + const int ID = 3; + const int LUN = 0; + + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + PbDeviceDefinition definition; + PbCommand command; + MockCommandContext context; + + definition.set_id(8); + definition.set_unit(32); + EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid ID must fail"; + + definition.set_id(ID); + EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid LUN must fail"; + + definition.set_unit(LUN); + EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Unknown operation must fail"; + + command.set_operation(ATTACH); + EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Operation for unknown device must fail"; +} + +TEST(RascsiExecutorTest, SetLogLevel) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, 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("critical")); + EXPECT_TRUE(executor.SetLogLevel("off")); + EXPECT_FALSE(executor.SetLogLevel("xyz")); +} + +TEST(RascsiExecutorTest, Attach) +{ + const int ID = 3; + const int LUN = 0; + + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + PbDeviceDefinition definition; + MockCommandContext context; + + definition.set_unit(32); + EXPECT_FALSE(executor.Attach(context, definition, false)); + + auto device = device_factory.CreateDevice(controller_manager, SCHD, LUN, ""); + definition.set_id(ID); + definition.set_unit(LUN); + + executor.SetReservedIds(to_string(ID)); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Reserved ID not rejected"; + + executor.SetReservedIds(""); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Unknown device type not rejected"; + + definition.set_type(PbDeviceType::SCHS); + EXPECT_TRUE(executor.Attach(context, definition, false)); + controller_manager.DeleteAllControllers(); + + definition.set_type(PbDeviceType::SCHD); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; + + definition.set_block_size(1); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid sector size not rejeced"; + + definition.set_block_size(1024); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without image file not rejected"; + + AddParam(definition, "file", "/non_existing_file"); + EXPECT_FALSE(executor.Attach(context, definition, false)); + + AddParam(definition, "file", "/dev/zero"); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Empty image file not rejected"; + + // Further testing is not possible without a filesystem +} + +TEST(RascsiExecutorTest, Insert) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + PbDeviceDefinition definition; + MockCommandContext context; + + auto device = device_factory.CreateDevice(controller_manager, SCRM, 0, "test"); + + device->SetRemoved(false); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Medium is not removed"; + + device->SetRemoved(true); + definition.set_vendor("v"); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; + definition.set_vendor(""); + + definition.set_product("p"); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; + definition.set_product(""); + + definition.set_revision("r"); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; + definition.set_revision(""); + + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Filename is missing"; + + AddParam(definition, "file", "filename"); + EXPECT_TRUE(executor.Insert(context, definition, device, true)) << "Dry-run must not fail"; + EXPECT_FALSE(executor.Insert(context, definition, device, false)); + + definition.set_block_size(1); + EXPECT_FALSE(executor.Insert(context, definition, device, false)); + + definition.set_block_size(0); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation has to fail"; + + AddParam(definition, "file", "/non_existing_file"); + EXPECT_FALSE(executor.Insert(context, definition, device, false)); + + AddParam(definition, "file", "/dev/zero"); + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "File has 0 bytes"; + + // Further testing is not possible without a filesystem +} + +TEST(RascsiExecutorTest, Detach) +{ + const int ID = 3; + const int LUN1 = 0; + const int LUN2 = 1; + + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + auto device1 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); + controller_manager.AttachToScsiController(ID, device1); + auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services"); + controller_manager.AttachToScsiController(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()); +} + +TEST(RascsiExecutorTest, DetachAll) +{ + const int ID = 4; + + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + controller_manager.AttachToScsiController(ID, device); + EXPECT_NE(nullptr, controller_manager.FindController(ID)); + EXPECT_FALSE(controller_manager.GetAllDevices().empty()); + + executor.DetachAll(); + EXPECT_EQ(nullptr, controller_manager.FindController(ID)); + EXPECT_TRUE(controller_manager.GetAllDevices().empty()); +} + +TEST(RascsiExecutorTest, ShutDown) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + EXPECT_FALSE(executor.ShutDown(context, "")); + EXPECT_FALSE(executor.ShutDown(context, "xyz")); + EXPECT_TRUE(executor.ShutDown(context, "rascsi")); + EXPECT_FALSE(executor.ShutDown(context, "system")); + EXPECT_FALSE(executor.ShutDown(context, "reboot")); +} + +TEST(RascsiExecutorTest, SetReservedIds) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + + string error = executor.SetReservedIds("xyz"); + EXPECT_FALSE(error.empty()); + EXPECT_TRUE(executor.GetReservedIds().empty()); + + error = executor.SetReservedIds("8"); + EXPECT_FALSE(error.empty()); + EXPECT_TRUE(executor.GetReservedIds().empty()); + + error = executor.SetReservedIds("-1"); + EXPECT_FALSE(error.empty()); + EXPECT_TRUE(executor.GetReservedIds().empty()); + + error = executor.SetReservedIds(""); + EXPECT_TRUE(error.empty()); + EXPECT_TRUE(executor.GetReservedIds().empty()); + + error = executor.SetReservedIds("7,1,2,3,5"); + 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)); + + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + controller_manager.AttachToScsiController(5, device); + error = executor.SetReservedIds("5"); + EXPECT_FALSE(error.empty()); +} + +TEST(RascsiExecutorTest, ValidateImageFile) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + string full_path; + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path)); + EXPECT_TRUE(full_path.empty()); + + device = device_factory.CreateDevice(controller_manager, SCHD, 0, "test"); + EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path)); + EXPECT_TRUE(full_path.empty()); + + EXPECT_FALSE(executor.ValidateImageFile(context, device, "/non_existing_file", full_path)); + EXPECT_TRUE(full_path.empty()); +} + +TEST(RascsiExecutorTest, ValidateLunSetup) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + PbCommand command; + + auto device1 = command.add_devices(); + device1->set_unit(0); + string error = executor.ValidateLunSetup(command); + EXPECT_TRUE(error.empty()); + + device1->set_unit(1); + error = executor.ValidateLunSetup(command); + EXPECT_FALSE(error.empty()); + + auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + controller_manager.AttachToScsiController(0, device2); + error = executor.ValidateLunSetup(command); + EXPECT_TRUE(error.empty()); +} + +TEST(RascsiExecutorTest, VerifyExistingIdAndLun) +{ + const int ID = 1; + const int LUN1 = 2; + const int LUN2 = 3; + + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); + auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); + controller_manager.AttachToScsiController(ID, device); + EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); + EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); +} + +TEST(RascsiExecutorTest, CreateDevice) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + EXPECT_EQ(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "")); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + EXPECT_EQ(nullptr, executor.CreateDevice(context, SAHD, 0, "")); +#pragma GCC diagnostic pop + EXPECT_NE(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "services")); + EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); +} + +TEST(RascsiExecutorTest, SetSectorSize) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + unordered_set sizes; + auto disk = make_shared(0, sizes, false); + EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 512)); + + sizes.insert(512); + disk = make_shared(0, sizes, false); + EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 0)); + EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 1)); + EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 512)); +} + +TEST(RascsiExecutorTest, ValidationOperationAgainstDevice) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + auto device = make_shared(0); + + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, ATTACH)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, DETACH)); + + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, START)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, STOP)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + + device->SetStoppable(true); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + + device->SetRemovable(true); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + + device->SetProtectable(true); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); + EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + + device->SetReady(true); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); + EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); +} + +TEST(RascsiExecutorTest, ValidateIdAndLun) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + + EXPECT_FALSE(executor.ValidateIdAndLun(context, -1, 0)); + EXPECT_FALSE(executor.ValidateIdAndLun(context, 8, 0)); + EXPECT_FALSE(executor.ValidateIdAndLun(context, 7, -1)); + EXPECT_FALSE(executor.ValidateIdAndLun(context, 7, 32)); + EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 0)); + EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); +} + +TEST(RascsiExecutorTest, SetProductData) +{ + MockBus bus; + DeviceFactory device_factory; + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + MockCommandContext context; + PbDeviceDefinition definition; + + auto device = make_shared(0); + + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + + definition.set_vendor("123456789"); + EXPECT_FALSE(executor.SetProductData(context, definition, device)); + definition.set_vendor("1"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + definition.set_vendor("12345678"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + + definition.set_product("12345678901234567"); + EXPECT_FALSE(executor.SetProductData(context, definition, device)); + definition.set_product("1"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + definition.set_product("1234567890123456"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + + definition.set_revision("12345"); + EXPECT_FALSE(executor.SetProductData(context, definition, device)); + definition.set_revision("1"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); + definition.set_revision("1234"); + EXPECT_TRUE(executor.SetProductData(context, definition, device)); +} diff --git a/src/raspberrypi/test/rascsi_response_test.cpp b/src/raspberrypi/test/rascsi_response_test.cpp new file mode 100644 index 00000000..17cdc05f --- /dev/null +++ b/src/raspberrypi/test/rascsi_response_test.cpp @@ -0,0 +1,92 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "controllers/controller_manager.h" +#include "devices/device_factory.h" +#include "rascsi_interface.pb.h" +#include "rascsi_response.h" + +using namespace rascsi_interface; + +TEST(RascsiResponseTest, Operation_Count) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + PbResult pb_operation_info_result; + + const auto operation_info = rascsi_response.GetOperationInfo(pb_operation_info_result, 0); + EXPECT_EQ(PbOperation_ARRAYSIZE - 1, operation_info->operations_size()); +} + +void TestNonDiskDevice(const string& name, int default_param_count) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + + auto d = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, name); + controller_manager.AttachToScsiController(0, d); + + PbServerInfo server_info; + rascsi_response.GetDevices(server_info, "image_folder"); + + EXPECT_EQ(1, server_info.devices_info().devices().size()); + const auto& device = server_info.devices_info().devices()[0]; + EXPECT_FALSE(device.properties().read_only()); + EXPECT_FALSE(device.properties().protectable()); + EXPECT_FALSE(device.properties().stoppable()); + EXPECT_FALSE(device.properties().removable()); + EXPECT_FALSE(device.properties().lockable()); + EXPECT_EQ(0, device.params().size()); + EXPECT_EQ(32, device.properties().luns()); + EXPECT_EQ(0, device.block_size()); + EXPECT_EQ(0, device.block_count()); + EXPECT_EQ(default_param_count, device.properties().default_params().size()); + EXPECT_FALSE(device.properties().supports_file()); + if (default_param_count) { + EXPECT_TRUE(device.properties().supports_params()); + } + else { + EXPECT_FALSE(device.properties().supports_params()); + } +} + +TEST(RascsiResponseTest, GetDevice_Printer) +{ + TestNonDiskDevice("printer", 2); +} + +TEST(RascsiResponseTest, GetDevice_HostServices) +{ + TestNonDiskDevice("services", 0); +} + +TEST(RascsiResponseTest, GetReservedIds) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + unordered_set ids; + PbResult result; + + const auto& reserved_ids_info1 = rascsi_response.GetReservedIds(result, ids); + EXPECT_TRUE(result.status()); + EXPECT_TRUE(reserved_ids_info1->ids().empty()); + + ids.insert(3); + const auto& reserved_ids_info2 = rascsi_response.GetReservedIds(result, ids); + EXPECT_TRUE(result.status()); + EXPECT_EQ(1, reserved_ids_info2->ids().size()); + EXPECT_EQ(3, reserved_ids_info2->ids()[0]); +} diff --git a/src/raspberrypi/test/rasutil_test.cpp b/src/raspberrypi/test/rasutil_test.cpp new file mode 100644 index 00000000..66190d65 --- /dev/null +++ b/src/raspberrypi/test/rasutil_test.cpp @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "rasutil.h" + +using namespace ras_util; + +TEST(RasUtilTest, GetAsInt) +{ + int result; + + EXPECT_FALSE(GetAsInt("", result)); + EXPECT_FALSE(GetAsInt("xyz", result)); + EXPECT_FALSE(GetAsInt("-1", result)); + EXPECT_FALSE(GetAsInt("1234567898765432112345678987654321", result)) << "Value is out of range"; + EXPECT_TRUE(GetAsInt("0", result)); + EXPECT_EQ(0, result); + EXPECT_TRUE(GetAsInt("1234", result)); + EXPECT_EQ(1234, result); +} diff --git a/src/raspberrypi/test/response_test.cpp b/src/raspberrypi/test/response_test.cpp deleted file mode 100644 index 9ccbb0d6..00000000 --- a/src/raspberrypi/test/response_test.cpp +++ /dev/null @@ -1,27 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi -// -// Copyright (C) 2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "testing.h" -#include "devices/device_factory.h" -#include "rascsi_interface.pb.h" -#include "rascsi_response.h" -#include "rascsi_image.h" - -using namespace rascsi_interface; - -TEST(ResponseTest, Operation_Count) -{ - DeviceFactory device_factory; - RascsiImage rascsi_image; - RascsiResponse rascsi_response(&device_factory, &rascsi_image); - PbResult pb_operation_info_result; - - const auto operation_info = unique_ptr(rascsi_response.GetOperationInfo(pb_operation_info_result, 0)); - EXPECT_EQ(PbOperation_ARRAYSIZE - 1, operation_info->operations_size()); -} diff --git a/src/raspberrypi/test/scsi_command_util_test.cpp b/src/raspberrypi/test/scsi_command_util_test.cpp index a3507a31..9f545609 100644 --- a/src/raspberrypi/test/scsi_command_util_test.cpp +++ b/src/raspberrypi/test/scsi_command_util_test.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" #include "devices/scsi_command_util.h" using namespace scsi_command_util; diff --git a/src/raspberrypi/test/scsi_controller_test.cpp b/src/raspberrypi/test/scsi_controller_test.cpp index dbdb19ae..c915c063 100644 --- a/src/raspberrypi/test/scsi_controller_test.cpp +++ b/src/raspberrypi/test/scsi_controller_test.cpp @@ -7,12 +7,35 @@ // //--------------------------------------------------------------------------- -#include "testing.h" +#include "mocks.h" +#include "scsi.h" #include "controllers/scsi_controller.h" +using namespace scsi_defs; + TEST(ScsiControllerTest, GetMaxLuns) { - MockScsiController controller(nullptr, 0); + MockScsiController controller(0); EXPECT_EQ(32, controller.GetMaxLuns()); } + +TEST(ScsiControllerTest, RequestSense) +{ + MockScsiController controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + vector& cmd = controller.InitCmd(6); + // ALLOCATION LENGTH + cmd[4] = 255; + // Non-existing LUN + cmd[1] = 0x20; + + device->SetReady(true); + EXPECT_CALL(controller, Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN, status::CHECK_CONDITION)).Times(1); + EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense)); + EXPECT_EQ(status::GOOD, controller.GetStatus()) << "Illegal CHECK CONDITION for non-exsting LUN"; +} diff --git a/src/raspberrypi/test/scsihd_test.cpp b/src/raspberrypi/test/scsihd_test.cpp new file mode 100644 index 00000000..9b37ca37 --- /dev/null +++ b/src/raspberrypi/test/scsihd_test.cpp @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "rascsi_exceptions.h" +#include "devices/scsihd.h" + +TEST(ScsiHdTest, FinalizeSetup) +{ + MockSCSIHD_NEC disk(0); + Filepath filepath; + + EXPECT_THROW(disk.FinalizeSetup(filepath, 2LL * 1024 * 1024 * 1024 * 1024 + 1, 0), io_exception); +} diff --git a/src/raspberrypi/test/test_setup.cpp b/src/raspberrypi/test/test_setup.cpp index eee2c44d..da528f76 100644 --- a/src/raspberrypi/test/test_setup.cpp +++ b/src/raspberrypi/test/test_setup.cpp @@ -11,14 +11,14 @@ #include "spdlog/spdlog.h" -class Environment : public ::testing::Environment +class Environment final : public ::testing::Environment { spdlog::level::level_enum log_level; public: explicit Environment(spdlog::level::level_enum level) : log_level(level) {} - ~Environment() final = default; + ~Environment() override = default; void SetUp() override { spdlog::set_level(log_level); } };