More unit tests, replacement of raw pointers and C arrays, separation of concerns (#878)

* Added unit tests

* Fixed SonarCloud issues

* Updated error handling

* Updated deletion of controllers

* Image folder handling cleanup

* Fixed clang warning

* Removed duplicate code

* Reduced code complexity

* Updated array handling

* Initialize device with ID and LUN

* Use smart pointers

* Updated memory management

* Logging updates

* Extracted methods

* Split scsi.h
This commit is contained in:
Uwe Seimet 2022-10-04 17:23:42 +02:00 committed by GitHub
parent 0e4d42f04c
commit efbfb54d26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 3501 additions and 2430 deletions

View File

@ -56,5 +56,5 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: | 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

View File

@ -92,16 +92,18 @@ SRC_RASCSI_CORE = \
rascsi_version.cpp \ rascsi_version.cpp \
rascsi_image.cpp \ rascsi_image.cpp \
rascsi_response.cpp \ rascsi_response.cpp \
rascsi_executor.cpp \
rasutil.cpp \ rasutil.cpp \
command_util.cpp \ command_util.cpp \
socket_connector.cpp \ protobuf_serializer.cpp \
localizer.cpp localizer.cpp
SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp')
SRC_RASCSI_CORE += $(SRC_PROTOBUF) SRC_RASCSI_CORE += $(SRC_PROTOBUF)
SRC_RASCSI = rascsi.cpp SRC_RASCSI = rascsi.cpp \
rascsi_service.cpp
SRC_SCSIMON = \ SRC_SCSIMON = \
scsimon.cpp \ scsimon.cpp \
@ -117,7 +119,7 @@ SRC_RASCTL = \
rascsi_version.cpp \ rascsi_version.cpp \
rasutil.cpp \ rasutil.cpp \
command_util.cpp \ command_util.cpp \
socket_connector.cpp \ protobuf_serializer.cpp \
localizer.cpp localizer.cpp
SRC_RASCTL += $(SRC_PROTOBUF) SRC_RASCTL += $(SRC_PROTOBUF)

113
src/raspberrypi/bus.h Normal file
View File

@ -0,0 +1,113 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
//
//---------------------------------------------------------------------------
#pragma once
#include "os.h"
#include "scsi.h"
#include <array>
#include <unordered_map>
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_t, 8> phase_table;
static const unordered_map<phase_t, const char *> phase_str_mapping;
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator RaSCSI Reloaded // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021-2022 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -11,16 +11,18 @@
#include <string> #include <string>
class SocketConnector; class ProtobufSerializer;
class Localizer; class Localizer;
struct CommandContext class CommandContext
{ {
CommandContext(const SocketConnector& c, const Localizer& l, int f, const std::string& s) public:
: connector(c), localizer(l), fd(f), locale(s) {}
CommandContext(const ProtobufSerializer& c, const Localizer& l, int f, const std::string& s)
: serializer(c), localizer(l), fd(f), locale(s) {}
~CommandContext() = default; ~CommandContext() = default;
const SocketConnector& connector; const ProtobufSerializer& serializer;
const Localizer& localizer; const Localizer& localizer;
int fd; int fd;
std::string locale; std::string locale;

View File

@ -10,7 +10,7 @@
#include "log.h" #include "log.h"
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include "localizer.h" #include "localizer.h"
#include "socket_connector.h" #include "protobuf_serializer.h"
#include "command_util.h" #include "command_util.h"
#include <sstream> #include <sstream>
@ -129,7 +129,7 @@ bool command_util::ReturnStatus(const CommandContext& context, bool status, cons
result.set_status(status); result.set_status(status);
result.set_error_code(error_code); result.set_error_code(error_code);
result.set_msg(msg); result.set_msg(msg);
context.connector.SerializeMessage(context.fd, result); context.serializer.SerializeMessage(context.fd, result);
} }
return status; return status;

View File

@ -17,7 +17,7 @@
#include "localizer.h" #include "localizer.h"
#include <string> #include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace rascsi_interface;
namespace command_util namespace command_util
{ {

View File

@ -20,6 +20,6 @@
#define USE_SEL_EVENT_ENABLE // Check SEL signal by event #define USE_SEL_EVENT_ENABLE // Check SEL signal by event
// This avoids an indefinite loop with warnings if there is no RaSCSI hardware // 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. // 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 #undef USE_SEL_EVENT_ENABLE
#endif #endif

View File

@ -7,8 +7,9 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "abstract_controller.h" #include "rascsi_exceptions.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "abstract_controller.h"
void AbstractController::AllocateBuffer(size_t size) void AbstractController::AllocateBuffer(size_t size)
{ {
@ -17,7 +18,18 @@ void AbstractController::AllocateBuffer(size_t size)
} }
} }
PrimaryDevice *AbstractController::GetDeviceForLun(int lun) const { unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, lun] : luns) {
devices.insert(lun);
}
return devices;
}
shared_ptr<PrimaryDevice> AbstractController::GetDeviceForLun(int lun) const {
const auto& it = luns.find(lun); const auto& it = luns.find(lun);
return it == luns.end() ? nullptr : it->second; return it == luns.end() ? nullptr : it->second;
} }
@ -26,7 +38,7 @@ void AbstractController::Reset()
{ {
SetPhase(BUS::phase_t::busfree); SetPhase(BUS::phase_t::busfree);
ctrl.status = 0x00; ctrl.status = status::GOOD;
ctrl.message = 0x00; ctrl.message = 0x00;
ctrl.blocks = 0; ctrl.blocks = 0;
ctrl.next = 0; ctrl.next = 0;
@ -75,18 +87,15 @@ void AbstractController::ProcessPhase()
break; break;
default: default:
assert(false); LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase()))
throw scsi_error_exception();
break; break;
} }
} }
bool AbstractController::AddDevice(PrimaryDevice *device) bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
{ {
if (device->GetLun() >= GetMaxLuns()) { if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
return false;
}
if (HasDeviceForLun(device->GetLun())) {
return false; return false;
} }
@ -96,7 +105,7 @@ bool AbstractController::AddDevice(PrimaryDevice *device)
return true; return true;
} }
bool AbstractController::DeleteDevice(const PrimaryDevice *device) bool AbstractController::DeleteDevice(const shared_ptr<PrimaryDevice> device)
{ {
return luns.erase(device->GetLun()) == 1; return luns.erase(device->GetLun()) == 1;
} }

View File

@ -11,24 +11,24 @@
#pragma once #pragma once
#include "scsi.h" #include "bus.h"
#include "phase_handler.h"
#include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <memory> #include <memory>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class PrimaryDevice; class PrimaryDevice;
class AbstractController class AbstractController : public PhaseHandler
{ {
friend class PrimaryDevice; friend class PrimaryDevice;
friend class ScsiController; friend class ScsiController;
BUS::phase_t phase = BUS::phase_t::busfree; // Logical units of this controller mapped to their LUN numbers
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
// Logical units of this device controller mapped to their LUN numbers
unordered_map<int, PrimaryDevice *> luns;
public: public:
@ -41,7 +41,7 @@ public:
using ctrl_t = struct _ctrl_t { using ctrl_t = struct _ctrl_t {
vector<int> cmd; // Command data, dynamically allocated per received command vector<int> cmd; // Command data, dynamically allocated per received command
uint32_t status; // Status data scsi_defs::status status; // Status data
int message; // Message data int message; // Message data
// Transfer // Transfer
@ -52,21 +52,8 @@ public:
uint32_t length; // Transfer remaining length uint32_t length; // Transfer remaining length
}; };
AbstractController(shared_ptr<BUS> bus, int target_id, int luns) : target_id(target_id), bus(bus), max_luns(luns) {} AbstractController(BUS& bus, int target_id, int max_luns) : target_id(target_id), bus(bus), max_luns(max_luns) {}
virtual ~AbstractController() = default; ~AbstractController() override = 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;
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, 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; scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0;
@ -81,19 +68,19 @@ public:
int GetTargetId() const { return target_id; } int GetTargetId() const { return target_id; }
int GetMaxLuns() const { return max_luns; } int GetMaxLuns() const { return max_luns; }
bool HasLuns() const { return !luns.empty(); } int GetLunCount() const { return (int)luns.size(); }
PrimaryDevice *GetDeviceForLun(int) const; unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
bool AddDevice(PrimaryDevice *); shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
bool DeleteDevice(const PrimaryDevice *); bool AddDevice(shared_ptr<PrimaryDevice>);
bool DeleteDevice(const shared_ptr<PrimaryDevice>);
bool HasDeviceForLun(int) const; bool HasDeviceForLun(int) const;
int ExtractInitiatorId(int id_data) const; int ExtractInitiatorId(int) const;
void AllocateBuffer(size_t); void AllocateBuffer(size_t);
vector<BYTE>& GetBuffer() { return ctrl.buffer; } vector<BYTE>& GetBuffer() { return ctrl.buffer; }
size_t GetBufferSize() const { return ctrl.buffer.size(); } scsi_defs::status GetStatus() const { return ctrl.status; }
uint32_t GetStatus() const { return ctrl.status; } void SetStatus(scsi_defs::status s) { ctrl.status = s; }
void SetStatus(uint32_t s) { ctrl.status = s; }
uint32_t GetLength() const { return ctrl.length; } uint32_t GetLength() const { return ctrl.length; }
protected: protected:
@ -106,25 +93,15 @@ protected:
vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; } vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; }
bool HasValidLength() const { return ctrl.length != 0; } bool HasValidLength() const { return ctrl.length != 0; }
int GetOffset() const { return ctrl.offset; }
void ResetOffset() { ctrl.offset = 0; } void ResetOffset() { ctrl.offset = 0; }
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 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: private:
int target_id; int target_id;
shared_ptr<BUS> bus; BUS& bus;
int max_luns; int max_luns;

View File

@ -15,17 +15,22 @@
using namespace std; using namespace std;
bool ControllerManager::CreateScsiController(shared_ptr<BUS> bus, PrimaryDevice *device) bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
{ {
shared_ptr<AbstractController> controller = FindController(device->GetId()); auto controller = FindController(id);
if (controller == nullptr) { if (controller == nullptr) {
controller = make_shared<ScsiController>(bus, device->GetId()); controller = make_shared<ScsiController>(bus, id);
controllers[device->GetId()] = controller; controllers[id] = controller;
} }
return controller->AddDevice(device); return controller->AddDevice(device);
} }
void ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
{
controllers.erase(controller->GetTargetId());
}
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{ {
for (const auto& [id, controller] : controllers) { for (const auto& [id, controller] : controllers) {
@ -43,6 +48,18 @@ shared_ptr<AbstractController> ControllerManager::FindController(int target_id)
return it == controllers.end() ? nullptr : it->second; return it == controllers.end() ? nullptr : it->second;
} }
unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, controller] : controllers) {
auto d = controller->GetDevices();
devices.insert(d.begin(), d.end());
}
return devices;
}
void ControllerManager::DeleteAllControllers() void ControllerManager::DeleteAllControllers()
{ {
controllers.clear(); controllers.clear();
@ -55,7 +72,7 @@ void ControllerManager::ResetAllControllers() const
} }
} }
PrimaryDevice *ControllerManager::GetDeviceByIdAndLun(int id, int lun) const shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
{ {
if (const auto controller = FindController(id); controller != nullptr) { if (const auto controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun); return controller->GetDeviceForLun(lun);

View File

@ -12,9 +12,10 @@
#pragma once #pragma once
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <memory> #include <memory>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class BUS; class BUS;
class AbstractController; class AbstractController;
@ -22,23 +23,24 @@ class PrimaryDevice;
class ControllerManager class ControllerManager
{ {
BUS& bus;
unordered_map<int, shared_ptr<AbstractController>> controllers;
public: public:
ControllerManager() = default; explicit ControllerManager(BUS& bus) : bus(bus) {}
~ControllerManager() = default; ~ControllerManager() = default;
ControllerManager(ControllerManager&) = delete;
ControllerManager& operator=(const ControllerManager&) = delete;
// Maximum number of controller devices // Maximum number of controller devices
static const int DEVICE_MAX = 8; static const int DEVICE_MAX = 8;
bool CreateScsiController(shared_ptr<BUS>, PrimaryDevice *); bool AttachToScsiController(int, shared_ptr<PrimaryDevice>);
void DeleteController(shared_ptr<AbstractController>);
shared_ptr<AbstractController> IdentifyController(int) const; shared_ptr<AbstractController> IdentifyController(int) const;
shared_ptr<AbstractController> FindController(int) const; shared_ptr<AbstractController> FindController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
void DeleteAllControllers(); void DeleteAllControllers();
void ResetAllControllers() const; void ResetAllControllers() const;
PrimaryDevice *GetDeviceByIdAndLun(int, int) const; shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
unordered_map<int, shared_ptr<AbstractController>> controllers;
}; };

View File

@ -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; }
};

View File

@ -21,13 +21,13 @@
#include "scsi_controller.h" #include "scsi_controller.h"
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#ifdef __linux #ifdef __linux__
#include <linux/if_tun.h> #include <linux/if_tun.h>
#endif #endif
using namespace scsi_defs; using namespace scsi_defs;
ScsiController::ScsiController(shared_ptr<BUS> 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 initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger. // the size of an Ethernet message, whichever is larger.
@ -52,17 +52,17 @@ void ScsiController::Reset()
BUS::phase_t ScsiController::Process(int id) BUS::phase_t ScsiController::Process(int id)
{ {
// Get bus information // Get bus information
bus->Acquire(); bus.Acquire();
// Check to see if the reset signal was asserted // Check to see if the reset signal was asserted
if (bus->GetRST()) { if (bus.GetRST()) {
LOGWARN("RESET signal received!") LOGWARN("RESET signal received!")
// Reset the controller // Reset the controller
Reset(); Reset();
// Reset the bus // Reset the bus
bus->Reset(); bus.Reset();
return GetPhase(); 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__) LOGERROR("%s Unhandled SCSI error, resetting controller and bus and entering bus free phase", __PRETTY_FUNCTION__)
Reset(); Reset();
bus->Reset(); bus.Reset();
BusFree(); BusFree();
} }
@ -101,14 +101,14 @@ void ScsiController::BusFree()
SetPhase(BUS::phase_t::busfree); SetPhase(BUS::phase_t::busfree);
bus->SetREQ(false); bus.SetREQ(false);
bus->SetMSG(false); bus.SetMSG(false);
bus->SetCD(false); bus.SetCD(false);
bus->SetIO(false); bus.SetIO(false);
bus->SetBSY(false); bus.SetBSY(false);
// Initialize status and message // Initialize status and message
SetStatus(0); SetStatus(status::GOOD);
ctrl.message = 0x00; ctrl.message = 0x00;
// Initialize ATN message reception status // Initialize ATN message reception status
@ -120,21 +120,21 @@ void ScsiController::BusFree()
bytes_to_transfer = 0; bytes_to_transfer = 0;
// When the bus is free RaSCSI or the Pi may be shut down. // 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) { switch(shutdown_mode) {
case AbstractController::rascsi_shutdown_mode::STOP_RASCSI: case rascsi_shutdown_mode::STOP_RASCSI:
LOGINFO("RaSCSI shutdown requested") LOGINFO("RaSCSI shutdown requested")
exit(0); exit(0);
break; break;
case AbstractController::rascsi_shutdown_mode::STOP_PI: case rascsi_shutdown_mode::STOP_PI:
LOGINFO("Raspberry Pi shutdown requested") LOGINFO("Raspberry Pi shutdown requested")
if (system("init 0") == -1) { if (system("init 0") == -1) {
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno)) LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno))
} }
break; break;
case AbstractController::rascsi_shutdown_mode::RESTART_PI: case rascsi_shutdown_mode::RESTART_PI:
LOGINFO("Raspberry Pi restart requested") LOGINFO("Raspberry Pi restart requested")
if (system("init 6") == -1) { if (system("init 6") == -1) {
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno)) LOGERROR("Raspberry Pi restart failed: %s", strerror(errno))
@ -149,7 +149,7 @@ void ScsiController::BusFree()
} }
// Move to selection phase // Move to selection phase
if (bus->GetSEL() && !bus->GetBSY()) { if (bus.GetSEL() && !bus.GetBSY()) {
Selection(); Selection();
} }
} }
@ -158,12 +158,12 @@ void ScsiController::Selection()
{ {
if (!IsSelection()) { if (!IsSelection()) {
// A different device controller was selected // 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; return;
} }
// Abort if there is no LUN for this controller // Abort if there is no LUN for this controller
if (!HasLuns()) { if (!GetLunCount()) {
return; return;
} }
@ -172,14 +172,14 @@ void ScsiController::Selection()
SetPhase(BUS::phase_t::selection); SetPhase(BUS::phase_t::selection);
// Raise BSY and respond // Raise BSY and respond
bus->SetBSY(true); bus.SetBSY(true);
return; return;
} }
// Selection completed // Selection completed
if (!bus->GetSEL() && bus->GetBSY()) { if (!bus.GetSEL() && bus.GetBSY()) {
// Message out phase if ATN=1, otherwise command phase // Message out phase if ATN=1, otherwise command phase
if (bus->GetATN()) { if (bus.GetATN()) {
MsgOut(); MsgOut();
} else { } else {
Command(); Command();
@ -194,11 +194,11 @@ void ScsiController::Command()
SetPhase(BUS::phase_t::command); SetPhase(BUS::phase_t::command);
bus->SetMSG(false); bus.SetMSG(false);
bus->SetCD(true); bus.SetCD(true);
bus->SetIO(false); 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]); int command_byte_count = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
// If not able to receive all, move to the status phase // 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()) LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode())
SetPhase(BUS::phase_t::execute);
// Initialization for data transfer // Initialization for data transfer
ResetOffset(); ResetOffset();
ctrl.blocks = 1; 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 // Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) { if (GetOpcode() != scsi_command::eCmdRequestSense) {
SetStatus(0); SetStatus(status::GOOD);
} }
int lun = GetEffectiveLun(); int lun = GetEffectiveLun();
@ -258,7 +256,7 @@ 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 // Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) { if (GetOpcode() != scsi_command::eCmdRequestSense) {
@ -298,14 +296,14 @@ void ScsiController::Status()
SysTimer::SleepUsec(5); 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); SetPhase(BUS::phase_t::status);
// Signal line operated by the target // Signal line operated by the target
bus->SetMSG(false); bus.SetMSG(false);
bus->SetCD(true); bus.SetCD(true);
bus->SetIO(true); bus.SetIO(true);
// Data transfer is 1 byte x 1 block // Data transfer is 1 byte x 1 block
ResetOffset(); ResetOffset();
@ -326,9 +324,9 @@ void ScsiController::MsgIn()
SetPhase(BUS::phase_t::msgin); SetPhase(BUS::phase_t::msgin);
bus->SetMSG(true); bus.SetMSG(true);
bus->SetCD(true); bus.SetCD(true);
bus->SetIO(true); bus.SetIO(true);
// length, blocks are already set // length, blocks are already set
assert(HasValidLength()); assert(HasValidLength());
@ -357,9 +355,9 @@ void ScsiController::MsgOut()
SetPhase(BUS::phase_t::msgout); SetPhase(BUS::phase_t::msgout);
bus->SetMSG(true); bus.SetMSG(true);
bus->SetCD(true); bus.SetCD(true);
bus->SetIO(false); bus.SetIO(false);
// Data transfer is 1 byte x 1 block // Data transfer is 1 byte x 1 block
ResetOffset(); ResetOffset();
@ -390,9 +388,9 @@ void ScsiController::DataIn()
SetPhase(BUS::phase_t::datain); SetPhase(BUS::phase_t::datain);
bus->SetMSG(false); bus.SetMSG(false);
bus->SetCD(false); bus.SetCD(false);
bus->SetIO(true); bus.SetIO(true);
// length, blocks are already set // length, blocks are already set
assert(ctrl.blocks > 0); assert(ctrl.blocks > 0);
@ -423,9 +421,9 @@ void ScsiController::DataOut()
SetPhase(BUS::phase_t::dataout); SetPhase(BUS::phase_t::dataout);
// Signal line operated by the target // Signal line operated by the target
bus->SetMSG(false); bus.SetMSG(false);
bus->SetCD(false); bus.SetCD(false);
bus->SetIO(false); bus.SetIO(false);
ResetOffset(); ResetOffset();
return; return;
@ -437,12 +435,12 @@ void ScsiController::DataOut()
void ScsiController::Error(sense_key sense_key, asc asc, status status) void ScsiController::Error(sense_key sense_key, asc asc, status status)
{ {
// Get bus information // Get bus information
bus->Acquire(); bus.Acquire();
// Reset check // Reset check
if (bus->GetRST()) { if (bus.GetRST()) {
Reset(); Reset();
bus->Reset(); bus.Reset();
return; 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) { if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
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 // Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8)); GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8));
} }
SetStatus((uint32_t)status); SetStatus(status);
ctrl.message = 0x00; ctrl.message = 0x00;
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__) 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() void ScsiController::Send()
{ {
assert(!bus->GetREQ()); assert(!bus.GetREQ());
assert(bus->GetIO()); assert(bus.GetIO());
if (HasValidLength()) { 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()) + 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 // 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. // 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); HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != (int)ctrl.length) { len != (int)ctrl.length) {
// If you cannot send all, move to status phase // If you cannot send all, move to status phase
@ -571,15 +572,15 @@ void ScsiController::Receive()
LOGTRACE("%s",__PRETTY_FUNCTION__) LOGTRACE("%s",__PRETTY_FUNCTION__)
// REQ is low // REQ is low
assert(!bus->GetREQ()); assert(!bus.GetREQ());
assert(!bus->GetIO()); assert(!bus.GetIO());
// Length != 0 if received // Length != 0 if received
if (HasValidLength()) { if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length) LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase // 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) { len != (int)ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len) LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len)
Error(sense_key::ABORTED_COMMAND); Error(sense_key::ABORTED_COMMAND);
@ -675,14 +676,14 @@ bool ScsiController::XferMsg(int msg)
void ScsiController::ReceiveBytes() void ScsiController::ReceiveBytes()
{ {
assert(!bus->GetREQ()); assert(!bus.GetREQ());
assert(!bus->GetIO()); assert(!bus.GetIO());
if (HasValidLength()) { if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length) LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase // 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) { len != ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d", LOGERROR("%s Not able to receive %d bytes of data, only received %d",
__PRETTY_FUNCTION__, ctrl.length, len) __PRETTY_FUNCTION__, ctrl.length, len)
@ -773,7 +774,7 @@ void ScsiController::FlushUnit()
{ {
assert(IsDataOut()); assert(IsDataOut());
auto disk = dynamic_cast<Disk *>(GetDeviceForLun(GetEffectiveLun())); auto disk = dynamic_pointer_cast<Disk>(GetDeviceForLun(GetEffectiveLun()));
if (disk == nullptr) { if (disk == nullptr) {
return; return;
} }
@ -795,7 +796,7 @@ void ScsiController::FlushUnit()
// Without it we would not need this method at all. // 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? // ModeSelect is already handled in XferOutBlockOriented(). Why would it have to be handled once more?
try { try {
disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset); disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset());
} }
catch(const scsi_error_exception& e) { catch(const scsi_error_exception& e) {
LOGWARN("Error occured while processing Mode Select command %02X\n", (int)GetOpcode()) LOGWARN("Error occured while processing Mode Select command %02X\n", (int)GetOpcode())
@ -838,7 +839,7 @@ bool ScsiController::XferIn(vector<BYTE>& buf)
case scsi_command::eCmdRead16: case scsi_command::eCmdRead16:
// Read from disk // Read from disk
try { try {
ctrl.length = (static_cast<Disk *>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next); ctrl.length = (dynamic_pointer_cast<Disk>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next);
} }
catch(const scsi_error_exception&) { catch(const scsi_error_exception&) {
// If there is an error, go to the status phase // If there is an error, go to the status phase
@ -868,7 +869,7 @@ bool ScsiController::XferIn(vector<BYTE>& buf)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
bool ScsiController::XferOutBlockOriented(bool cont) bool ScsiController::XferOutBlockOriented(bool cont)
{ {
auto disk = dynamic_cast<Disk *>(GetDeviceForLun(GetEffectiveLun())); auto disk = dynamic_pointer_cast<Disk>(GetDeviceForLun(GetEffectiveLun()));
if (disk == nullptr) { if (disk == nullptr) {
return false; return false;
} }
@ -878,7 +879,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdModeSelect6: case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10: case scsi_command::eCmdModeSelect10:
try { try {
disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset); disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset());
} }
catch(const scsi_error_exception& e) { catch(const scsi_error_exception& e) {
Error(e.get_sense_key(), e.get_asc(), e.get_status()); 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 // Special case Write function for brige
// TODO This class must not know about SCSIBR // TODO This class must not know about SCSIBR
if (auto bridge = dynamic_cast<SCSIBR *>(disk); bridge) { if (auto bridge = dynamic_pointer_cast<SCSIBR>(disk); bridge) {
if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) { if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) {
// Write failed // Write failed
return false; return false;
@ -907,7 +908,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
// Special case Write function for DaynaPort // Special case Write function for DaynaPort
// TODO This class must not know about DaynaPort // TODO This class must not know about DaynaPort
if (auto daynaport = dynamic_cast<SCSIDaynaPort *>(disk); daynaport) { if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(disk); daynaport) {
daynaport->WriteBytes(ctrl.cmd, GetBuffer(), 0); daynaport->WriteBytes(ctrl.cmd, GetBuffer(), 0);
ResetOffset(); ResetOffset();
@ -961,9 +962,10 @@ void ScsiController::ProcessCommand()
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]); uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
stringstream s; stringstream s;
s << setfill('0') << setw(2) << hex;
for (uint32_t i = 0; i < len; i++) { for (uint32_t i = 0; i < len; i++) {
ctrl.cmd[i] = GetBuffer()[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()) LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str())
@ -1036,7 +1038,7 @@ void ScsiController::ParseMessage()
void ScsiController::ProcessMessage() void ScsiController::ProcessMessage()
{ {
// Continue message out phase as long as ATN keeps asserting // Continue message out phase as long as ATN keeps asserting
if (bus->GetATN()) { if (bus.GetATN()) {
// Data transfer is 1 byte x 1 block // Data transfer is 1 byte x 1 block
ResetOffset(); ResetOffset();
ctrl.length = 1; ctrl.length = 1;

View File

@ -54,10 +54,8 @@ public:
// Maximum number of logical units // Maximum number of logical units
static const int LUN_MAX = 32; static const int LUN_MAX = 32;
ScsiController(shared_ptr<BUS>, int); ScsiController(BUS&, int);
~ScsiController() override = default; ~ScsiController() override = default;
ScsiController(ScsiController&) = delete;
ScsiController& operator=(const ScsiController&) = delete;
void Reset() override; void Reset() override;

View File

@ -22,8 +22,6 @@ public:
CDTrack() = default; CDTrack() = default;
~CDTrack() = default; ~CDTrack() = default;
CDTrack(CDTrack&) = delete;
CDTrack& operator=(const CDTrack&) = delete;
void Init(int track, DWORD first, DWORD last); void Init(int track, DWORD first, DWORD last);

View File

@ -2381,18 +2381,16 @@ bool CHostFcb::Truncate() const
/// Return -1 if error is thrown. /// 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); assert(m_pFile);
int nSeek; int nSeek;
switch (nHumanSeek) { switch (nHumanSeek) {
case Human68k::SK_BEGIN: case Human68k::seek_t::SK_BEGIN:
nSeek = SEEK_SET; nSeek = SEEK_SET;
break; break;
case Human68k::SK_CURRENT: case Human68k::seek_t::SK_CURRENT:
nSeek = SEEK_CUR; nSeek = SEEK_CUR;
break; break;
// case SK_END: // case SK_END:
@ -3356,13 +3354,13 @@ int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset)
return FS_NOTOPENED; return FS_NOTOPENED;
// Parameter check // Parameter check
if (nSeek > Human68k::SK_END) { if (nSeek > (DWORD)Human68k::seek_t::SK_END) {
m_cFcb.Free(pHostFcb); m_cFcb.Free(pHostFcb);
return FS_INVALIDPRM; return FS_INVALIDPRM;
} }
// File seek // File seek
DWORD nResult = pHostFcb->Seek(nOffset, nSeek); DWORD nResult = pHostFcb->Seek(nOffset, (Human68k::seek_t)nSeek);
if (nResult == (DWORD)-1) { if (nResult == (DWORD)-1) {
m_cFcb.Free(pHostFcb); m_cFcb.Free(pHostFcb);
return FS_CANTSEEK; return FS_CANTSEEK;

View File

@ -92,39 +92,14 @@ namespace Human68k {
}; };
/// Seek types /// Seek types
enum seek_t { enum class seek_t {
SK_BEGIN = 0, ///< From the beginning of a file SK_BEGIN = 0, ///< From the beginning of a file
SK_CURRENT = 1, ///< From the current location SK_CURRENT = 1, ///< From the current location
SK_END = 2, ///< From the end of the file SK_END = 2, ///< From the end of the file
}; };
/// Media byte // Media byte
enum media_t { const static int MEDIA_REMOTE = 0xF3; ///< Remote drive
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
};
struct namests_t { struct namests_t {
BYTE wildcard; ///< Wildcard character length BYTE wildcard; ///< Wildcard character length
@ -338,11 +313,7 @@ Normal is 0. Becomes 1 if attempting to mount in read-only mode.
Reserving the other values for future use. Reserving the other values for future use.
Insurance against hard-to-detect devices such as homemade USB storage. Insurance against hard-to-detect devices such as homemade USB storage.
*/ */
enum { static const DWORD FSFLAG_WRITE_PROTECT = 0x00000001; ///< Bit0: Force write protect
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
};
//=========================================================================== //===========================================================================
// //
@ -357,8 +328,8 @@ class CRing {
public: public:
CRing() { Init(); } CRing() { Init(); }
~CRing() { Remove(); } ~CRing() { Remove(); }
CRing(CRing&) = delete; CRing(CRing&) = default;
CRing& operator=(const CRing&) = delete; CRing& operator=(const CRing&) = default;
void Init() { next = prev = this; } void Init() { next = prev = this; }
@ -442,8 +413,6 @@ class CHostFilename {
public: public:
CHostFilename() = default; CHostFilename() = default;
~CHostFilename() = default; ~CHostFilename() = default;
CHostFilename(CHostFilename&) = delete;
CHostFilename& operator=(const CHostFilename&) = delete;
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
@ -532,8 +501,8 @@ public:
CHostPath() = default; CHostPath() = default;
~CHostPath(); ~CHostPath();
CHostPath(CHostPath&) = delete; CHostPath(CHostPath&) = default;
CHostPath& operator=(const CHostPath&) = delete; CHostPath& operator=(const CHostPath&) = default;
void Clean(); ///< Initialialize for reuse void Clean(); ///< Initialialize for reuse
@ -596,8 +565,6 @@ class CHostFiles {
public: public:
CHostFiles() = default; CHostFiles() = default;
~CHostFiles() = default; ~CHostFiles() = default;
CHostFiles(CHostFiles&) = delete;
CHostFiles& operator=(const CHostFiles&) = delete;
void Init(); void Init();
@ -675,8 +642,8 @@ class CHostFcb {
public: public:
CHostFcb() = default; CHostFcb() = default;
~CHostFcb() { Close(); } ~CHostFcb() { Close(); }
CHostFcb(CHostFcb&) = delete; CHostFcb(CHostFcb&) = default;
CHostFcb& operator=(const CHostFcb&) = delete; CHostFcb& operator=(const CHostFcb&) = default;
void Init(); void Init();
@ -694,7 +661,7 @@ public:
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
bool Truncate() const; ///< Truncate 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 bool TimeStamp(DWORD nHumanTime) const; ///< Set file time stamp
void Close(); ///< Close file void Close(); ///< Close file
@ -747,8 +714,8 @@ class CHostDrv
public: public:
CHostDrv() = default; CHostDrv() = default;
~CHostDrv(); ~CHostDrv();
CHostDrv(CHostDrv&) = delete; CHostDrv(CHostDrv&) = default;
CHostDrv& operator=(const CHostDrv&) = delete; CHostDrv& operator=(const CHostDrv&) = default;
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load) void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
@ -811,8 +778,8 @@ public:
CHostEntry() = default; CHostEntry() = default;
~CHostEntry(); ~CHostEntry();
CHostEntry(CHostEntry&) = delete; CHostEntry(CHostEntry&) = default;
CHostEntry& operator=(const CHostEntry&) = delete; CHostEntry& operator=(const CHostEntry&) = default;
void Init() const; ///< Initialization (when the driver is installed) void Init() const; ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting) void Clean(); ///< Release (when starting up or resetting)

View File

@ -37,7 +37,7 @@ using namespace ras_util;
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) { static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
#ifndef __linux #ifndef __linux__
return false; return false;
#else #else
ifreq ifr; ifreq ifr;
@ -83,7 +83,7 @@ CTapDriver::~CTapDriver()
} }
static bool ip_link(int fd, const char* ifname, bool up) { static bool ip_link(int fd, const char* ifname, bool up) {
#ifndef __linux #ifndef __linux__
return false; return false;
#else #else
ifreq ifr; ifreq ifr;
@ -126,7 +126,7 @@ static bool is_interface_up(string_view interface) {
bool CTapDriver::Init(const unordered_map<string, string>& const_params) bool CTapDriver::Init(const unordered_map<string, string>& const_params)
{ {
#ifndef __linux #ifndef __linux__
return false; return false;
#else #else
unordered_map<string, string> params = const_params; unordered_map<string, string> params = const_params;

View File

@ -19,7 +19,7 @@
#include <string> #include <string>
#include <array> #include <array>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class CTapDriver class CTapDriver
{ {
@ -30,8 +30,8 @@ class CTapDriver
CTapDriver() = default; CTapDriver() = default;
~CTapDriver(); ~CTapDriver();
CTapDriver(CTapDriver&) = delete; CTapDriver(CTapDriver&) = default;
CTapDriver& operator=(const CTapDriver&) = delete; CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&); bool Init(const unordered_map<string, string>&);

View File

@ -7,17 +7,16 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <cassert>
#include "rascsi_version.h" #include "rascsi_version.h"
#include "log.h" #include "log.h"
#include "rascsi_exceptions.h"
#include "device.h" #include "device.h"
#include <cassert>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
using namespace std; 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); assert(type.length() == 4);
@ -43,7 +42,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v) void Device::SetVendor(const string& v)
{ {
if (v.empty() || v.length() > 8) { 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; vendor = v;
@ -52,10 +51,10 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force) void Device::SetProduct(const string& p, bool force)
{ {
if (p.empty() || p.length() > 16) { 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) { if (!product.empty() && !force) {
return; return;
} }
@ -66,7 +65,7 @@ void Device::SetProduct(const string& p, bool force)
void Device::SetRevision(const string& r) void Device::SetRevision(const string& r)
{ {
if (r.empty() || r.length() > 4) { 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; revision = r;
@ -74,13 +73,10 @@ void Device::SetRevision(const string& r)
string Device::GetPaddedName() const string Device::GetPaddedName() const
{ {
string name = vendor; ostringstream os;
name.append(8 - vendor.length(), ' '); os << left << setfill(' ') << setw(8) << vendor << setw(16) << product << setw(4) << revision;
name += product;
name.append(16 - product.length(), ' ');
name += revision;
name.append(4 - revision.length(), ' ');
string name = os.str();
assert(name.length() == 28); assert(name.length() == 28);
return name; return name;
@ -107,15 +103,6 @@ void Device::SetParams(const unordered_map<string, string>& 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() bool Device::Start()
{ {
if (!ready) { if (!ready) {

View File

@ -9,16 +9,13 @@
#pragma once #pragma once
#include "scsi.h"
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
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"; const string DEFAULT_VENDOR = "RaSCSI";
string type; string type;
@ -45,15 +42,11 @@ class Device
bool lockable = false; bool lockable = false;
bool locked = false; bool locked = false;
// The block size is configurable
bool block_size_configurable = false;
// Device can be created with parameters // Device can be created with parameters
bool supports_params = false; bool supports_params = false;
// Device ID and LUN // Immutable LUN
int32_t id = 0; int lun;
int32_t lun = 0;
// Device identifier (for INQUIRY) // Device identifier (for INQUIRY)
string vendor = DEFAULT_VENDOR; string vendor = DEFAULT_VENDOR;
@ -86,21 +79,16 @@ protected:
string GetParam(const string&) const; string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&); void SetParams(const unordered_map<string, string>&);
explicit Device(const string&); Device(const string&, int);
public: public:
virtual ~Device() = default; 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<string, string>&) { return true; };
const string& GetType() const { return type; } const string& GetType() const { return type; }
bool IsReady() const { return ready; } bool IsReady() const { return ready; }
void Reset(); virtual void Reset();
bool IsProtectable() const { return protectable; } bool IsProtectable() const { return protectable; }
void SetProtectable(bool b) { protectable = b; } void SetProtectable(bool b) { protectable = b; }
@ -123,10 +111,8 @@ public:
bool IsLocked() const { return locked; } bool IsLocked() const { return locked; }
void SetLocked(bool b) { locked = b; } void SetLocked(bool b) { locked = b; }
int32_t GetId() const { return id; } virtual int GetId() const = 0;
void SetId(int32_t i) { id = i; } int GetLun() const { return lun; }
int32_t GetLun() const { return lun; }
void SetLun(int32_t l) { lun = l; }
string GetVendor() const { return vendor; } string GetVendor() const { return vendor; }
void SetVendor(const string&); void SetVendor(const string&);
@ -142,12 +128,9 @@ public:
unordered_map<string, string> GetParams() const { return params; } unordered_map<string, string> GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; } void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
void SetStatusCode(int); void SetStatusCode(int s) { status_code = s; }
bool Start(); bool Start();
void Stop(); void Stop();
virtual bool Eject(bool); virtual bool Eject(bool);
virtual void FlushCache() {
// Devices with a cache have to implement this method
}
}; };

View File

@ -25,8 +25,6 @@
using namespace std; using namespace std;
using namespace rascsi_interface; using namespace rascsi_interface;
multimap<int, unique_ptr<PrimaryDevice>> DeviceFactory::devices;
DeviceFactory::DeviceFactory() DeviceFactory::DeviceFactory()
{ {
sector_sizes[SCHD] = { 512, 1024, 2048, 4096 }; sector_sizes[SCHD] = { 512, 1024, 2048, 4096 };
@ -60,45 +58,6 @@ DeviceFactory::DeviceFactory()
extension_mapping["iso"] = SCCD; 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<PrimaryDevice *> DeviceFactory::GetAllDevices() const
{
list<PrimaryDevice *> result;
for (const auto& [id, device] : devices) {
result.push_back(device.get());
}
return result;
}
string DeviceFactory::GetExtension(const string& filename) const string DeviceFactory::GetExtension(const string& filename) const
{ {
string ext; string ext;
@ -132,7 +91,8 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
} }
// ID -1 is used by rascsi to create a temporary device // ID -1 is used by rascsi to create a temporary device
PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename, int id) shared_ptr<PrimaryDevice> 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 no type was specified try to derive the device type from the filename
if (type == UNDEFINED) { if (type == UNDEFINED) {
@ -142,13 +102,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
} }
} }
unique_ptr<PrimaryDevice> device; shared_ptr<PrimaryDevice> device;
switch (type) { switch (type) {
case SCHD: { case SCHD: {
if (string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { if (string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_unique<SCSIHD_NEC>(); device = make_shared<SCSIHD_NEC>(lun);
} else { } else {
device = make_unique<SCSIHD>(sector_sizes[SCHD], false, ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); device = make_shared<SCSIHD>(lun, sector_sizes[SCHD], false,
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
// Some Apple tools require a particular drive identification // Some Apple tools require a particular drive identification
if (ext == "hda") { if (ext == "hda") {
@ -162,7 +123,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
} }
case SCRM: case SCRM:
device = make_unique<SCSIHD>(sector_sizes[SCRM], true); device = make_shared<SCSIHD>(lun, sector_sizes[SCRM], true);
device->SetProtectable(true); device->SetProtectable(true);
device->SetStoppable(true); device->SetStoppable(true);
device->SetRemovable(true); device->SetRemovable(true);
@ -171,7 +132,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break; break;
case SCMO: case SCMO:
device = make_unique<SCSIMO>(sector_sizes[SCMO]); device = make_shared<SCSIMO>(lun, sector_sizes[SCMO]);
device->SetProtectable(true); device->SetProtectable(true);
device->SetStoppable(true); device->SetStoppable(true);
device->SetRemovable(true); device->SetRemovable(true);
@ -180,7 +141,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break; break;
case SCCD: case SCCD:
device = make_unique<SCSICD>(sector_sizes[SCCD]); device = make_shared<SCSICD>(lun, sector_sizes[SCCD]);
device->SetReadOnly(true); device->SetReadOnly(true);
device->SetStoppable(true); device->SetStoppable(true);
device->SetRemovable(true); device->SetRemovable(true);
@ -189,14 +150,15 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break; break;
case SCBR: case SCBR:
device = make_unique<SCSIBR>(); device = make_shared<SCSIBR>(lun);
// Since this is an emulation for a specific driver the product name has to be set accordingly
device->SetProduct("RASCSI BRIDGE"); device->SetProduct("RASCSI BRIDGE");
device->SupportsParams(true); device->SupportsParams(true);
device->SetDefaultParams(default_params[SCBR]); device->SetDefaultParams(default_params[SCBR]);
break; break;
case SCDP: case SCDP:
device = make_unique<SCSIDaynaPort>(); device = make_shared<SCSIDaynaPort>(lun);
// Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly
device->SetVendor("Dayna"); device->SetVendor("Dayna");
device->SetProduct("SCSI/Link"); device->SetProduct("SCSI/Link");
@ -206,14 +168,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break; break;
case SCHS: case SCHS:
device = make_unique<HostServices>(*this); device = make_shared<HostServices>(lun, controller_manager);
// Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly // Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly
device->SetVendor("RaSCSI"); device->SetVendor("RaSCSI");
device->SetProduct("Host Services"); device->SetProduct("Host Services");
break; break;
case SCLP: case SCLP:
device = make_unique<SCSIPrinter>(); device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER"); device->SetProduct("SCSI PRINTER");
device->SupportsParams(true); device->SupportsParams(true);
device->SetDefaultParams(default_params[SCLP]); device->SetDefaultParams(default_params[SCLP]);
@ -223,17 +185,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break; break;
} }
if (device != nullptr) { return device;
PrimaryDevice *d = device.release();
d->SetId(id);
devices.emplace(id, d);
return d;
}
return nullptr;
} }
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
@ -260,7 +212,7 @@ list<string> DeviceFactory::GetNetworkInterfaces() const
{ {
list<string> network_interfaces; list<string> network_interfaces;
#ifdef __linux #ifdef __linux__
ifaddrs *addrs; ifaddrs *addrs;
getifaddrs(&addrs); getifaddrs(&addrs);
ifaddrs *tmp = addrs; ifaddrs *tmp = addrs;

View File

@ -18,9 +18,10 @@
#include <string> #include <string>
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace rascsi_interface;
class ControllerManager;
class PrimaryDevice; class PrimaryDevice;
class DeviceFactory class DeviceFactory
@ -29,14 +30,8 @@ public:
DeviceFactory(); DeviceFactory();
~DeviceFactory() = default; ~DeviceFactory() = default;
DeviceFactory(DeviceFactory&) = delete;
DeviceFactory& operator=(const DeviceFactory&) = delete;
PrimaryDevice *CreateDevice(PbDeviceType, const string&, int); shared_ptr<PrimaryDevice> CreateDevice(const ControllerManager&, PbDeviceType, int, const string&);
void DeleteDevice(const PrimaryDevice&) const;
void DeleteAllDevices() const;
const PrimaryDevice *GetDeviceByIdAndLun(int, int) const;
list<PrimaryDevice *> GetAllDevices() const;
PbDeviceType GetTypeForFile(const string&) const; PbDeviceType GetTypeForFile(const string&) const;
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const; const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
const unordered_set<uint32_t>& GetSectorSizes(const string&) const; const unordered_set<uint32_t>& GetSectorSizes(const string&) const;
@ -54,8 +49,6 @@ private:
string GetExtension(const string&) const; string GetExtension(const string&) const;
static std::multimap<int, unique_ptr<PrimaryDevice>> devices;
unordered_set<uint32_t> empty_set; unordered_set<uint32_t> empty_set;
unordered_map<string, string> empty_map; unordered_map<string, string> empty_map;
}; };

View File

@ -25,7 +25,7 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; 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::eCmdRezero, "Rezero", &Disk::Rezero);
dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit); dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit);
@ -88,7 +88,9 @@ bool Disk::Dispatch(scsi_command cmd)
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
void Disk::Open(const Filepath& path) void Disk::Open(const Filepath& path)
{ {
assert(blocks > 0); if (blocks == 0) {
throw io_exception("Disk has 0 blocks");
}
SetReady(true); SetReady(true);
@ -108,10 +110,17 @@ void Disk::Open(const Filepath& path)
SetLocked(false); 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); assert(cache == nullptr);
cache = make_unique<DiskCache>(path, size_shift_count, (uint32_t)blocks, image_offset); cache = make_unique<DiskCache>(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() void Disk::FlushCache()
@ -266,10 +275,10 @@ void Disk::StartStopUnit()
bool load = ctrl->cmd[4] & 0x02; bool load = ctrl->cmd[4] & 0x02;
if (load) { if (load) {
LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium") LOGTRACE(start ? "Loading medium" : "Ejecting medium")
} }
else { else {
LOGTRACE("%s", start ? "Starting unit" : "Stopping unit") LOGTRACE(start ? "Starting unit" : "Stopping unit")
SetStopped(!start); SetStopped(!start);
} }
@ -315,7 +324,7 @@ void Disk::PreventAllowMediumRemoval()
bool lock = ctrl->cmd[4] & 0x01; bool lock = ctrl->cmd[4] & 0x01;
LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium") LOGTRACE(lock ? "Locking medium" : "Unlocking medium")
SetLocked(lock); SetLocked(lock);
@ -346,7 +355,7 @@ void Disk::MediumChanged()
is_medium_changed = true; is_medium_changed = true;
} }
else { 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; return status;
} }
int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{ {
// Get length, clear buffer // 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); fill_n(buf.begin(), length, 0);
// DEVICE SPECIFIC PARAMETER // DEVICE SPECIFIC PARAMETER
@ -378,7 +387,7 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
} }
// Basic information // Basic information
int info_size = 4; int size = 4;
// Add block descriptor if DBD is 0 // Add block descriptor if DBD is 0
if ((cdb[1] & 0x08) == 0) { if ((cdb[1] & 0x08) == 0) {
@ -388,33 +397,33 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
// Only if ready // Only if ready
if (IsReady()) { if (IsReady()) {
// Short LBA mode parameter block descriptor (number of blocks and block length) // 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()); SetInt32(buf, 8, GetSectorSizeInBytes());
} }
info_size = 12; size = 12;
} }
info_size += super::AddModePages(cdb, buf, info_size, length - info_size); size += super::AddModePages(cdb, buf, size, length - size);
if (info_size > 255) { if (size > 255) {
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);
} }
// Do not return more than ALLOCATION LENGTH bytes // Do not return more than ALLOCATION LENGTH bytes
if (info_size > length) { if (size > length) {
info_size = length; size = length;
} }
// Final setting of mode data 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<int>& cdb, vector<BYTE>& buf, int max_length) const int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf) const
{ {
// Get length, clear buffer // 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); fill_n(buf.begin(), length, 0);
// DEVICE SPECIFIC PARAMETER // DEVICE SPECIFIC PARAMETER
@ -423,11 +432,11 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
} }
// Basic Information // Basic Information
int info_size = 8; int size = 8;
// Add block descriptor if DBD is 0, only if ready // Add block descriptor if DBD is 0, only if ready
if ((cdb[1] & 0x08) == 0 && IsReady()) { if ((cdb[1] & 0x08) == 0 && IsReady()) {
uint64_t disk_blocks = GetBlockCount(); uint64_t disk_blocks = blocks;
uint32_t disk_size = GetSectorSizeInBytes(); uint32_t disk_size = GetSectorSizeInBytes();
// Check LLBAA for short or long block descriptor // Check LLBAA for short or long block descriptor
@ -439,7 +448,7 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
SetInt32(buf, 8, (uint32_t)disk_blocks); SetInt32(buf, 8, (uint32_t)disk_blocks);
SetInt32(buf, 12, disk_size); SetInt32(buf, 12, disk_size);
info_size = 16; size = 16;
} }
else { else {
// Mode parameter header, LONGLBA // Mode parameter header, LONGLBA
@ -452,24 +461,24 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
SetInt64(buf, 8, disk_blocks); SetInt64(buf, 8, disk_blocks);
SetInt32(buf, 20, disk_size); SetInt32(buf, 20, disk_size);
info_size = 24; size = 24;
} }
} }
info_size += super::AddModePages(cdb, buf, info_size, length - info_size); size += super::AddModePages(cdb, buf, size, length - size);
if (info_size > 65535) { if (size > 65535) {
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);
} }
// Do not return more than ALLOCATION LENGTH bytes // Do not return more than ALLOCATION LENGTH bytes
if (info_size > length) { if (size > length) {
info_size = length; size = length;
} }
// Final setting of mode data 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<int, vector<byte>>& pages, int page, bool changeable) const void Disk::SetUpModePages(map<int, vector<byte>>& 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 block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2);
uint64_t capacity = GetBlockCount(); uint64_t capacity = blocks;
if (block > capacity) { if (block > capacity) {
LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " 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) LOGTRACE("%s READ/WRITE/VERIFY/SEEK command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count)
// Check capacity // 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 " LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
+ to_string(start) + ", block count " + to_string(count)).c_str()) + to_string(start) + ", block count " + to_string(count)).c_str())
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); 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; break;
default: default:
assert(false); throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes");
break; break;
} }
} }

View File

@ -31,6 +31,8 @@ class Disk : public ModePageDevice, public ScsiBlockCommands
Dispatcher<Disk> dispatcher; Dispatcher<Disk> dispatcher;
unique_ptr<DiskCache> cache;
// The supported configurable sector sizes, empty if not configurable // The supported configurable sector sizes, empty if not configurable
unordered_set<uint32_t> sector_sizes; unordered_set<uint32_t> sector_sizes;
uint32_t configured_sector_size = 0; uint32_t configured_sector_size = 0;
@ -45,10 +47,8 @@ class Disk : public ModePageDevice, public ScsiBlockCommands
public: public:
explicit Disk(const string&); Disk(const string&, int);
~Disk() override; ~Disk() override;
Disk(Disk&) = delete;
Disk& operator=(const Disk&) = delete;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;
@ -105,13 +105,14 @@ private:
void ValidateBlockAddress(access_mode) const; void ValidateBlockAddress(access_mode) const;
bool CheckAndGetStartAndCount(uint64_t&, uint32_t&, access_mode) const; bool CheckAndGetStartAndCount(uint64_t&, uint32_t&, access_mode) const;
int ModeSense6(const vector<int>&, vector<BYTE>&, int) const override; int ModeSense6(const vector<int>&, vector<BYTE>&) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&, int) const override; int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
protected: protected:
virtual void Open(const Filepath&); 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, vector<byte>>&, int, bool) const override; void SetUpModePages(map<int, vector<byte>>&, int, bool) const override;
virtual void AddErrorPage(map<int, vector<byte>>&, bool) const; virtual void AddErrorPage(map<int, vector<byte>>&, bool) const;
@ -126,6 +127,4 @@ protected:
void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; }
uint32_t GetConfiguredSectorSize() const; uint32_t GetConfiguredSectorSize() const;
void SetBlockCount(uint64_t b) { blocks = b; } void SetBlockCount(uint64_t b) { blocks = b; }
unique_ptr<DiskCache> cache;
}; };

View File

@ -19,7 +19,7 @@
#include <array> #include <array>
#include <memory> #include <memory>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class DiskCache class DiskCache
{ {
@ -36,8 +36,6 @@ public:
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0); DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
~DiskCache() = default; ~DiskCache() = default;
DiskCache(DiskCache&) = delete;
DiskCache& operator=(const DiskCache&) = delete;
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting

View File

@ -21,9 +21,7 @@
DiskTrack::~DiskTrack() DiskTrack::~DiskTrack()
{ {
// Release memory, but do not save automatically // 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) void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)

View File

@ -18,7 +18,7 @@
#include "filepath.h" #include "filepath.h"
#include <vector> #include <vector>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class DiskTrack class DiskTrack
{ {

View File

@ -14,8 +14,8 @@
#include "log.h" #include "log.h"
#include <unordered_map> #include <unordered_map>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
using namespace scsi_defs; //NOSONAR Not relevant for rascsi using namespace scsi_defs;
template<class T> template<class T>
class Dispatcher class Dispatcher
@ -24,8 +24,6 @@ public:
Dispatcher() = default; Dispatcher() = default;
~Dispatcher() = default; ~Dispatcher() = default;
Dispatcher(Dispatcher&) = delete;
Dispatcher& operator=(const Dispatcher&) = delete;
using operation = void (T::*)(); using operation = void (T::*)();
using command_t = struct _command_t { using command_t = struct _command_t {

View File

@ -7,6 +7,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "rascsi_exceptions.h"
#include "file_support.h" #include "file_support.h"
using namespace std; using namespace std;
@ -39,3 +40,16 @@ void FileSupport::UnreserveAll()
{ {
reserved_files.clear(); 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;
}

View File

@ -15,7 +15,7 @@
#include <string> #include <string>
#include "filepath.h" #include "filepath.h"
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
using id_set = pair<int, int>; using id_set = pair<int, int>;
@ -30,8 +30,6 @@ public:
FileSupport() = default; FileSupport() = default;
virtual ~FileSupport() = default; virtual ~FileSupport() = default;
FileSupport(FileSupport&) = delete;
FileSupport& operator=(const FileSupport&) = delete;
void GetPath(Filepath& path) const { path = diskpath; } void GetPath(Filepath& path) const { path = diskpath; }
void SetPath(const Filepath& path) { diskpath = path; } void SetPath(const Filepath& path) { diskpath = path; }
@ -39,6 +37,7 @@ public:
void ReserveFile(const Filepath&, int, int) const; void ReserveFile(const Filepath&, int, int) const;
void UnreserveFile() const; void UnreserveFile() const;
static void UnreserveAll(); static void UnreserveAll();
bool FileExists(const Filepath&);
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; } static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& files_in_use) static void SetReservedFiles(const unordered_map<string, id_set>& files_in_use)

View File

@ -20,8 +20,9 @@
// c) start && load (LOAD): Reboot the Raspberry Pi // c) start && load (LOAD): Reboot the Raspberry Pi
// //
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "device_factory.h"
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include "dispatcher.h" #include "dispatcher.h"
#include "host_services.h" #include "host_services.h"
@ -30,7 +31,8 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; 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::eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady);
dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit); dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit);
@ -60,40 +62,35 @@ void HostServices::StartStopUnit()
if (!start) { if (!start) {
// Flush any caches // Flush any caches
for (PrimaryDevice *device : device_factory.GetAllDevices()) { for (const auto& device : controller_manager.GetAllDevices()) {
device->FlushCache(); device->FlushCache();
} }
if (load) { if (load) {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_PI); controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI);
} }
else { else {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_RASCSI); controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI);
} }
}
EnterStatusPhase(); else if (load) {
return; controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI);
} }
else { 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<int>& cdb, vector<BYTE>& buf, int max_length) const int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{ {
// Block descriptors cannot be returned // Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) { if (!(cdb[1] & 0x08)) {
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);
} }
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); fill_n(buf.begin(), length, 0);
// Basic Information // Basic Information
@ -114,14 +111,14 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_
return size; return size;
} }
int HostServices::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const int HostServices::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf) const
{ {
// Block descriptors cannot be returned // Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) { if (!(cdb[1] & 0x08)) {
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);
} }
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); fill_n(buf.begin(), length, 0);
// Basic Information // Basic Information

View File

@ -15,17 +15,15 @@
#include <vector> #include <vector>
#include <map> #include <map>
class DeviceFactory; class ControllerManager;
class HostServices: public ModePageDevice class HostServices: public ModePageDevice
{ {
public: public:
explicit HostServices(const DeviceFactory&); HostServices(int, const ControllerManager&);
~HostServices() override = default; ~HostServices() override = default;
HostServices(HostServices&) = delete;
HostServices& operator=(const HostServices&) = delete;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;
@ -58,10 +56,10 @@ private:
Dispatcher<HostServices> dispatcher; Dispatcher<HostServices> dispatcher;
int ModeSense6(const vector<int>&, vector<BYTE>&, int) const override; const ControllerManager& controller_manager;
int ModeSense10(const vector<int>&, vector<BYTE>&, int) const override;
int ModeSense6(const vector<int>&, vector<BYTE>&) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const; void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const;
const DeviceFactory& device_factory;
}; };

View File

@ -20,7 +20,7 @@ using namespace std;
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; 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::eCmdModeSense6, "ModeSense6", &ModePageDevice::ModeSense6);
dispatcher.Add(scsi_command::eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10); dispatcher.Add(scsi_command::eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10);
@ -63,14 +63,14 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
for (auto const& [index, data] : pages) { for (auto const& [index, data] : pages) {
// The specification mandates that page 0 must be returned after all others // The specification mandates that page 0 must be returned after all others
if (index) { if (index) {
size_t offset = result.size(); size_t off = result.size();
// Page data // Page data
result.insert(result.end(), data.begin(), data.end()); result.insert(result.end(), data.begin(), data.end());
// Page code, PS bit may already have been set // Page code, PS bit may already have been set
result[offset] |= (byte)index; result[off] |= (byte)index;
// Page payload size // Page payload size
result[offset + 1] = (byte)(data.size() - 2); result[off + 1] = (byte)(data.size() - 2);
} }
else { else {
page0 = data; page0 = data;
@ -79,10 +79,12 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
// Page 0 must be last // Page 0 must be last
if (!page0.empty()) { if (!page0.empty()) {
size_t off = result.size();
// Page data // Page data
result.insert(result.end(), page0.begin(), page0.end()); result.insert(result.end(), page0.begin(), page0.end());
// Page payload size // 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 // Do not return more than the requested number of bytes
@ -94,14 +96,14 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
void ModePageDevice::ModeSense6() void ModePageDevice::ModeSense6()
{ {
ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize()); ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer());
EnterDataInPhase(); EnterDataInPhase();
} }
void ModePageDevice::ModeSense10() void ModePageDevice::ModeSense10()
{ {
ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize()); ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer());
EnterDataInPhase(); EnterDataInPhase();
} }
@ -145,7 +147,7 @@ int ModePageDevice::ModeSelectCheck6() const
int ModePageDevice::ModeSelectCheck10() const int ModePageDevice::ModeSelectCheck10() const
{ {
// Receive the data specified by the parameter length // 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); return ModeSelectCheck((int)length);
} }

View File

@ -18,10 +18,8 @@ class ModePageDevice: public PrimaryDevice
{ {
public: public:
explicit ModePageDevice(const string&); ModePageDevice(const string&, int);
~ModePageDevice()override = default; ~ModePageDevice()override = default;
ModePageDevice(ModePageDevice&) = delete;
ModePageDevice& operator=(const ModePageDevice&) = delete;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;
@ -38,8 +36,8 @@ private:
Dispatcher<ModePageDevice> dispatcher; Dispatcher<ModePageDevice> dispatcher;
virtual int ModeSense6(const vector<int>&, vector<BYTE>&, int) const = 0; virtual int ModeSense6(const vector<int>&, vector<BYTE>&) const = 0;
virtual int ModeSense10(const vector<int>&, vector<BYTE>&, int) const = 0; virtual int ModeSense10(const vector<int>&, vector<BYTE>&) const = 0;
void ModeSense6(); void ModeSense6();
void ModeSense10(); void ModeSense10();

View File

@ -17,7 +17,7 @@ using namespace std;
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; 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 // Mandatory SCSI primary commands
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady);
@ -33,6 +33,15 @@ bool PrimaryDevice::Dispatch(scsi_command cmd)
return dispatcher.Dispatch(this, 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) void PrimaryDevice::SetController(AbstractController *c)
{ {
controller = c; controller = c;
@ -65,7 +74,7 @@ void PrimaryDevice::Inquiry()
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId()) LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId())
// Signal that the requested LUN does not exist // Signal that the requested LUN does not exist
controller->GetBuffer()[0] |= 0x7f; controller->GetBuffer().data()[0] = 0x7f;
} }
EnterDataInPhase(); EnterDataInPhase();
@ -81,7 +90,7 @@ void PrimaryDevice::ReportLuns()
uint32_t allocation_length = GetInt32(ctrl->cmd, 6); uint32_t allocation_length = GetInt32(ctrl->cmd, 6);
vector<BYTE>& buf = controller->GetBuffer(); vector<BYTE>& 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; uint32_t size = 0;
for (int lun = 0; lun < controller->GetMaxLuns(); lun++) { 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 // 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->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
controller->SetStatus(0); controller->SetStatus(status::GOOD);
} }
vector<byte> buf = controller->GetDeviceForLun(lun)->HandleRequestSense(); vector<byte> buf = controller->GetDeviceForLun(lun)->HandleRequestSense();
@ -195,7 +204,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
buf[12] = (byte)(GetStatusCode() >> 8); buf[12] = (byte)(GetStatusCode() >> 8);
buf[13] = (byte)GetStatusCode(); 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]) (int)buf[2], (int)buf[12])
return buf; return buf;

View File

@ -12,27 +12,33 @@
#pragma once #pragma once
#include "interfaces/scsi_primary_commands.h" #include "interfaces/scsi_primary_commands.h"
#include "controllers/scsi_controller.h" #include "controllers/abstract_controller.h"
#include "device.h" #include "device.h"
#include "dispatcher.h" #include "dispatcher.h"
#include <string> #include <string>
class PrimaryDevice: public ScsiPrimaryCommands, public Device class PrimaryDevice: public ScsiPrimaryCommands, public Device
{ {
public: public:
explicit PrimaryDevice(const string&); PrimaryDevice(const string&, int);
~PrimaryDevice() override = default; ~PrimaryDevice() override = default;
PrimaryDevice(PrimaryDevice&) = delete;
PrimaryDevice& operator=(const PrimaryDevice&) = delete;
virtual bool Dispatch(scsi_command); virtual bool Dispatch(scsi_command);
int GetId() const override;
void SetController(AbstractController *); void SetController(AbstractController *);
virtual bool WriteByteSequence(vector<BYTE>&, uint32_t); virtual bool WriteByteSequence(vector<BYTE>&, uint32_t);
virtual int GetSendDelay() const { return BUS::SEND_NO_DELAY; } 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<string, string>&) { return true; };
virtual void FlushCache() {
// Devices with a cache have to implement this method
}
protected: protected:
vector<byte> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; vector<byte> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;

View File

@ -14,7 +14,7 @@
#include <vector> #include <vector>
#include <map> #include <map>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
namespace scsi_command_util namespace scsi_command_util
{ {

View File

@ -36,7 +36,7 @@ using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
// TODO Disk must not be the superclass // 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::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6); dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
@ -406,9 +406,9 @@ void SCSIDaynaPort::Write6()
ctrl->length = GetInt16(ctrl->cmd, 3 + 8); ctrl->length = GetInt16(ctrl->cmd, 3 + 8);
} }
else { 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) { if (ctrl->length <= 0) {
throw scsi_error_exception(); throw scsi_error_exception();

View File

@ -45,10 +45,8 @@ class SCSIDaynaPort final : public Disk
{ {
public: public:
SCSIDaynaPort(); explicit SCSIDaynaPort(int);
~SCSIDaynaPort() override = default; ~SCSIDaynaPort() override = default;
SCSIDaynaPort(SCSIDaynaPort&) = delete;
SCSIDaynaPort& operator=(const SCSIDaynaPort&) = delete;
bool Init(const unordered_map<string, string>&) override; bool Init(const unordered_map<string, string>&) override;
void Open(const Filepath& path) override; void Open(const Filepath& path) override;

View File

@ -27,7 +27,7 @@ using namespace std;
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
SCSIBR::SCSIBR() : Disk("SCBR") SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun)
{ {
// Create host file system // Create host file system
fs.Reset(); fs.Reset();
@ -41,7 +41,7 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
{ {
SetParams(params); SetParams(params);
#ifdef __linux #ifdef __linux__
// TAP Driver Generation // TAP Driver Generation
m_bTapEnable = tap.Init(GetParams()); m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){ if (!m_bTapEnable){

View File

@ -24,7 +24,7 @@
#include <string> #include <string>
#include <array> #include <array>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
class SCSIBR final : public Disk class SCSIBR final : public Disk
{ {
@ -32,10 +32,8 @@ class SCSIBR final : public Disk
public: public:
SCSIBR(); explicit SCSIBR(int);
~SCSIBR() override = default; ~SCSIBR() override = default;
SCSIBR(SCSIBR&) = delete;
SCSIBR& operator=(const SCSIBR&) = delete;
bool Init(const unordered_map<string, string>&) override; bool Init(const unordered_map<string, string>&) override;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;

View File

@ -51,7 +51,7 @@ using namespace scsi_defs;
using namespace ras_util; using namespace ras_util;
using namespace scsi_command_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::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady);
dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit); dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit);
@ -149,8 +149,8 @@ void SCSIPrinter::Print()
LOGTRACE("Receiving %d byte(s) to be printed", length) LOGTRACE("Receiving %d byte(s) to be printed", length)
if (length > controller->GetBufferSize()) { if (length > controller->GetBuffer().size()) {
LOGERROR("%s", string("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBufferSize()) + LOGERROR("%s", ("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected").c_str()) " bytes, " + to_string(length) + " bytes expected").c_str())
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);

View File

@ -24,10 +24,8 @@ class SCSIPrinter final : public PrimaryDevice, public ScsiPrinterCommands //NOS
public: public:
SCSIPrinter(); explicit SCSIPrinter(int);
~SCSIPrinter() override; ~SCSIPrinter() override;
SCSIPrinter(SCSIPrinter&) = delete;
SCSIPrinter& operator=(const SCSIPrinter&) = delete;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;

View File

@ -24,7 +24,7 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
SCSICD::SCSICD(const unordered_set<uint32_t>& sector_sizes) : Disk("SCCD") SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCCD", lun)
{ {
SetSectorSizes(sector_sizes); SetSectorSizes(sector_sizes);
@ -92,10 +92,7 @@ void SCSICD::Open(const Filepath& path)
super::Open(path); super::Open(path);
FileSupport::SetPath(path); FileSupport::SetPath(path);
SetUpCache(path); SetUpCache(path, 0, rawfile);
// Set RAW flag
cache->SetRawMode(rawfile);
// Attention if ready // Attention if ready
if (IsReady()) { if (IsReady()) {
@ -117,8 +114,8 @@ void SCSICD::OpenIso(const Filepath& path)
} }
// Get file size // Get file size
off_t file_size = fio.GetFileSize(); off_t size = fio.GetFileSize();
if (file_size < 0x800) { if (size < 0x800) {
fio.Close(); fio.Close();
throw io_exception("ISO CD-ROM file size must be at least 2048 bytes"); 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) { if (rawfile) {
// Size must be a multiple of 2536 // 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 " 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 // Set the number of blocks
SetBlockCount((DWORD)(file_size / 0x930)); SetBlockCount((DWORD)(size / 0x930));
} else { } else {
// Set the number of blocks // Set the number of blocks
SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount())); SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount()));
} }
// Create only one data track // Create only one data track
@ -296,9 +293,9 @@ int SCSICD::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t block)
// Recreate the disk cache // Recreate the disk cache
Filepath path; Filepath path;
tracks[index]->GetPath(path); tracks[index]->GetPath(path);
// Re-assign disk cache (no need to save) // Re-assign disk cache (no need to save)
cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)GetBlockCount())); ResizeCache(path, rawfile);
cache->SetRawMode(rawfile);
// Reset data index // Reset data index
dataindex = index; dataindex = index;

View File

@ -27,10 +27,8 @@ class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport
{ {
public: public:
explicit SCSICD(const unordered_set<uint32_t>&); SCSICD(int, const unordered_set<uint32_t>&);
~SCSICD() override = default; ~SCSICD() override = default;
SCSICD(SCSICD&) = delete;
SCSICD& operator=(const SCSICD&) = delete;
bool Dispatch(scsi_command) override; bool Dispatch(scsi_command) override;

View File

@ -22,8 +22,8 @@
using namespace scsi_command_util; using namespace scsi_command_util;
SCSIHD::SCSIHD(const unordered_set<uint32_t>& sector_sizes, bool removable, scsi_defs::scsi_level level) SCSIHD::SCSIHD(int lun, const unordered_set<uint32_t>& sector_sizes, bool removable, scsi_defs::scsi_level level)
: Disk(removable ? "SCRM" : "SCHD") : Disk(removable ? "SCRM" : "SCHD", lun)
{ {
scsi_level = level; scsi_level = level;
@ -41,7 +41,13 @@ void SCSIHD::FinalizeSetup(const Filepath &path, off_t size, off_t image_offset)
if (!IsRemovable()) { if (!IsRemovable()) {
uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes();
string unit; 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; capacity /= 1048576;
unit = "MiB"; unit = "MiB";
} }
@ -75,17 +81,17 @@ void SCSIHD::Open(const Filepath& path)
} }
// Get file size // Get file size
off_t file_size = fio.GetFileSize(); off_t size = fio.GetFileSize();
fio.Close(); fio.Close();
// Sector size (default 512 bytes) and number of blocks // Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount())); SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount()));
// Effective size must be a multiple of the sector size // 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<byte> SCSIHD::InquiryInternal() const vector<byte> SCSIHD::InquiryInternal() const

View File

@ -26,10 +26,8 @@ class SCSIHD : public Disk, public FileSupport
public: public:
SCSIHD(const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2); SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
~SCSIHD() override = default; ~SCSIHD() override = default;
SCSIHD(SCSIHD&) = delete;
SCSIHD& operator=(const SCSIHD&) = delete;
void FinalizeSetup(const Filepath&, off_t, off_t = 0); void FinalizeSetup(const Filepath&, off_t, off_t = 0);

View File

@ -54,18 +54,18 @@ void SCSIHD_NEC::Open(const Filepath& path)
} }
// Get file size // Get file size
off_t file_size = fio.GetFileSize(); off_t size = fio.GetFileSize();
// NEC root sector // NEC root sector
array<BYTE, 512> root_sector; array<BYTE, 512> 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(); fio.Close();
throw io_exception("Can't read NEC hard disk file root sector"); throw io_exception("Can't read NEC hard disk file root sector");
} }
fio.Close(); fio.Close();
// Effective size must be a multiple of 512 // Effective size must be a multiple of 512
file_size = (file_size / 512) * 512; size = (size / 512) * 512;
int image_size = 0; int image_size = 0;
int sector_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")) { if (const char *ext = path.GetFileExt(); !strcasecmp(ext, ".hdn")) {
// Assuming sector size 512, number of sectors 25, number of heads 8 as default settings // Assuming sector size 512, number of sectors 25, number of heads 8 as default settings
image_offset = 0; image_offset = 0;
image_size = (int)file_size; image_size = (int)size;
sector_size = 512; sector_size = 512;
sectors = 25; sectors = 25;
heads = 8; heads = 8;
cylinders = (int)(file_size >> 9); cylinders = (int)(size >> 9);
cylinders >>= 3; cylinders >>= 3;
cylinders /= 25; cylinders /= 25;
} }
@ -113,23 +113,23 @@ void SCSIHD_NEC::Open(const Filepath& path)
} }
// Image size consistency check // 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"); throw io_exception("Image size consistency check failed");
} }
// Calculate sector size // Calculate sector size
for (file_size = 16; file_size > 0; --file_size) { for (size = 16; size > 0; --size) {
if ((1 << file_size) == sector_size) if ((1 << size) == sector_size)
break; break;
} }
if (file_size <= 0 || file_size > 16) { if (size <= 0 || size > 16) {
throw io_exception("Invalid NEC disk size"); throw io_exception("Invalid NEC disk size");
} }
SetSectorSizeShiftCount((uint32_t)file_size); SetSectorSizeShiftCount((uint32_t)size);
SetBlockCount(image_size >> GetSectorSizeShiftCount()); SetBlockCount(image_size >> GetSectorSizeShiftCount());
FinalizeSetup(path, file_size, image_offset); FinalizeSetup(path, size, image_offset);
} }
vector<byte> SCSIHD_NEC::InquiryInternal() const vector<byte> SCSIHD_NEC::InquiryInternal() const

View File

@ -20,7 +20,7 @@
#include <unordered_set> #include <unordered_set>
#include <map> #include <map>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
//=========================================================================== //===========================================================================
// //
@ -31,10 +31,8 @@ class SCSIHD_NEC : public SCSIHD
{ {
public: public:
SCSIHD_NEC() : SCSIHD(sector_sizes, false) {} explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {}
~SCSIHD_NEC() override = default; ~SCSIHD_NEC() override = default;
SCSIHD_NEC(SCSIHD_NEC&) = delete;
SCSIHD_NEC& operator=(const SCSIHD_NEC&) = delete;
void Open(const Filepath&) override; void Open(const Filepath&) override;

View File

@ -19,7 +19,7 @@
using namespace scsi_command_util; using namespace scsi_command_util;
SCSIMO::SCSIMO(const unordered_set<uint32_t>& sector_sizes) : Disk("SCMO") SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCMO", lun)
{ {
SetSectorSizes(sector_sizes); SetSectorSizes(sector_sizes);
@ -45,14 +45,14 @@ void SCSIMO::Open(const Filepath& path)
} }
// Get file size // Get file size
off_t file_size = fio.GetFileSize(); off_t size = fio.GetFileSize();
fio.Close(); fio.Close();
// For some capacities there are hard-coded, well-defined sector sizes and block counts // 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 // Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount(file_size >> GetSectorSizeShiftCount()); SetBlockCount(size >> GetSectorSizeShiftCount());
} }
SetReadOnly(false); SetReadOnly(false);
@ -62,7 +62,7 @@ void SCSIMO::Open(const Filepath& path)
Disk::Open(path); Disk::Open(path);
FileSupport::SetPath(path); FileSupport::SetPath(path);
SetUpCache(path); SetUpCache(path, 0);
// Attention if ready // Attention if ready
if (IsReady()) { if (IsReady()) {

View File

@ -24,10 +24,8 @@ class SCSIMO : public Disk, public FileSupport
{ {
public: public:
explicit SCSIMO(const unordered_set<uint32_t>&); SCSIMO(int, const unordered_set<uint32_t>&);
~SCSIMO() override = default; ~SCSIMO() override = default;
SCSIMO(SCSIMO&) = delete;
SCSIMO& operator=(const SCSIMO&) = delete;
void Open(const Filepath&) override; void Open(const Filepath&) override;

View File

@ -25,8 +25,8 @@ public:
Fileio() = default; Fileio() = default;
virtual ~Fileio(); virtual ~Fileio();
Fileio(Fileio&) = delete; Fileio(Fileio&) = default;
Fileio& operator=(const Fileio&) = delete; Fileio& operator=(const Fileio&) = default;
bool Open(const char *fname, OpenMode mode); bool Open(const char *fname, OpenMode mode);
bool Open(const Filepath& path, OpenMode mode); bool Open(const Filepath& path, OpenMode mode);

View File

@ -35,7 +35,7 @@ public:
Filepath(); Filepath();
virtual ~Filepath() = default; virtual ~Filepath() = default;
Filepath(Filepath&) = delete; Filepath(Filepath&) = default;
Filepath& operator=(const Filepath&); Filepath& operator=(const Filepath&);
void Clear(); void Clear();

View File

@ -19,13 +19,13 @@
#include "config.h" #include "config.h"
#include "log.h" #include "log.h"
#include <array> #include <array>
#ifdef __linux #ifdef __linux__
#include <sys/epoll.h> #include <sys/epoll.h>
#endif #endif
using namespace std; using namespace std;
#ifdef __linux #ifdef __linux__
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// imported from bcm_host.c // imported from bcm_host.c
@ -1258,7 +1258,7 @@ bool GPIOBUS::WaitSignal(int pin, int ast)
void GPIOBUS::DisableIRQ() void GPIOBUS::DisableIRQ()
{ {
#ifdef __linux #ifdef __linux__
if (rpitype == 4) { if (rpitype == 4) {
// RPI4 is disabled by GICC // RPI4 is disabled by GICC
giccpmr = gicc[GICC_PMR]; giccpmr = gicc[GICC_PMR];

View File

@ -15,7 +15,7 @@
#include "scsi.h" #include "scsi.h"
#include <array> #include <array>
#ifdef __linux #ifdef __linux__
#include <linux/gpio.h> #include <linux/gpio.h>
#endif #endif

View File

@ -195,13 +195,11 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri
} }
} }
assert(it != localized_messages.end()); if (it == localized_messages.end()) {
return "Missing default localization for enum value " + to_string((int)key);
auto messages = it->second;
if (messages.empty()) {
return "Missing localization for enum value " + to_string((int)key);
} }
auto messages = it->second;
string message = messages[key]; string message = messages[key];
message = regex_replace(message, regex("%1"), arg1); message = regex_replace(message, regex("%1"), arg1);

View File

@ -15,7 +15,7 @@
#include <unordered_set> #include <unordered_set>
#include <unordered_map> #include <unordered_map>
using namespace std; //NOSONAR Not relevant for rascsi using namespace std;
enum class LocalizationKey { enum class LocalizationKey {
ERROR_AUTHENTICATION, ERROR_AUTHENTICATION,

View File

@ -9,48 +9,21 @@
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "socket_connector.h" #include "protobuf_serializer.h"
#include <unistd.h> #include <unistd.h>
#include <netinet/in.h>
#include <sstream> #include <sstream>
using namespace std; using namespace std;
using namespace rascsi_interface; 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<byte> 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. // Serialize/Deserialize protobuf message: Length followed by the actual data.
// Little endian is assumed. // 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; string data;
message.SerializeToString(&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 // Read the header with the size of the protobuf data
vector<byte> header_buf(4); vector<byte> header_buf(4);
@ -91,7 +64,7 @@ void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& mess
message.ParseFromString(data); message.ParseFromString(data);
} }
size_t SocketConnector::ReadBytes(int fd, vector<byte>& buf) const size_t ProtobufSerializer::ReadBytes(int fd, vector<byte>& buf) const
{ {
size_t offset = 0; size_t offset = 0;
while (offset < buf.size()) { while (offset < buf.size()) {

View File

@ -5,28 +5,22 @@
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet
// //
// Helper for serializing/deserializing protobuf messages to/fromn sockets // Helper for serializing/deserializing protobuf messages
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "google/protobuf/message.h" #include "google/protobuf/message.h"
#include "rascsi_interface.pb.h" #include <vector>
#include "command_context.h"
#include "localizer.h"
#include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi class ProtobufSerializer
class SocketConnector
{ {
public: public:
SocketConnector() = default; ProtobufSerializer() = default;
~SocketConnector() = default; ~ProtobufSerializer() = default;
int ReadCommand(PbCommand&, int) const;
void SerializeMessage(int, const google::protobuf::Message&) const; void SerializeMessage(int, const google::protobuf::Message&) const;
void DeserializeMessage(int, google::protobuf::Message&) const; void DeserializeMessage(int, google::protobuf::Message&) const;
size_t ReadBytes(int, vector<byte>&) const; size_t ReadBytes(int, vector<byte>&) const;

File diff suppressed because it is too large Load Diff

View File

@ -13,41 +13,31 @@
#include <exception> #include <exception>
#include <string> #include <string>
using namespace std; //NOSONAR Not relevant for rascsi class io_exception : public std::exception
{
class illegal_argument_exception final : public exception {
private:
string msg; string msg;
public: 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) {} explicit io_exception(const string& msg) : msg(msg) {}
~io_exception() override = default; ~io_exception() override = default;
const string& get_msg() const { return msg; } 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; using io_exception::io_exception;
}; };
class scsi_error_exception final : public exception { class scsi_error_exception final : public std::exception
private: {
scsi_defs::sense_key sense_key; scsi_defs::sense_key sense_key;
scsi_defs::asc asc; scsi_defs::asc asc;
scsi_defs::status status; scsi_defs::status status;
public: public:
scsi_error_exception(scsi_defs::sense_key sense_key = scsi_defs::sense_key::ABORTED_COMMAND, 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::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) scsi_defs::status status = scsi_defs::status::CHECK_CONDITION)

View File

@ -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 <sstream>
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<PrimaryDevice> 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<PrimaryDevice> 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<PrimaryDevice> 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<PrimaryDevice> 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<PrimaryDevice> 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<FileSupport *>(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<string, string> 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<PrimaryDevice> 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<FileSupport>(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<Disk>(device); disk != nullptr) {
disk->MediumChanged();
}
return true;
}
bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDevice> 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<FileSupport>(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<string> ids_to_reserve;
stringstream ss(ids.data());
string id;
while (getline(ss, id, ',')) {
if (!id.empty()) {
ids_to_reserve.push_back(id);
}
}
unordered_set<int> 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<PrimaryDevice> device,
const string& filename, string& full_path) const
{
auto file_support = dynamic_pointer_cast<FileSupport>(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<string, string, less<>> 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<uint32_t, uint32_t> 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<PrimaryDevice> 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<PrimaryDevice> device, int block_size) const
{
if (block_size) {
auto disk = dynamic_pointer_cast<Disk>(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<PrimaryDevice> 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<PrimaryDevice> 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;
}

View File

@ -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<int> 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<int> 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<PrimaryDevice>, bool) const;
bool Stop(shared_ptr<PrimaryDevice>, bool) const;
bool Eject(shared_ptr<PrimaryDevice>, bool) const;
bool Protect(shared_ptr<PrimaryDevice>, bool) const;
bool Unprotect(shared_ptr<PrimaryDevice>, bool) const;
bool Attach(const CommandContext&, const PbDeviceDefinition&, bool);
bool Insert(const CommandContext&, const PbDeviceDefinition&, shared_ptr<PrimaryDevice>, bool) const;
bool Detach(const CommandContext&, shared_ptr<PrimaryDevice>, bool) const;
void DetachAll();
bool ShutDown(const CommandContext&, const string&);
string SetReservedIds(string_view);
bool ValidateImageFile(const CommandContext&, shared_ptr<PrimaryDevice>, 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<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const;
bool SetSectorSize(const CommandContext&, const string& type, shared_ptr<PrimaryDevice>, int) const;
static bool ValidationOperationAgainstDevice(const CommandContext&, const shared_ptr<PrimaryDevice>,
const PbOperation&);
static bool ValidateIdAndLun(const CommandContext&, int, int);
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, shared_ptr<PrimaryDevice>);
};

View File

@ -18,7 +18,7 @@
#include <string> #include <string>
#include <array> #include <array>
#include <filesystem> #include <filesystem>
#ifdef __linux #ifdef __linux__
#include <sys/sendfile.h> #include <sys/sendfile.h>
#endif #endif
@ -30,7 +30,7 @@ using namespace command_util;
RascsiImage::RascsiImage() RascsiImage::RascsiImage()
{ {
// ~/images is the default folder for device image files, for the root user it is /home/pi/images // ~/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 bool RascsiImage::CheckDepth(string_view filename) const
@ -57,7 +57,7 @@ bool RascsiImage::CreateImageFolder(const CommandContext& context, const string&
return true; return true;
} }
string RascsiImage::SetDefaultImageFolder(const string& f) string RascsiImage::SetDefaultFolder(const string& f)
{ {
if (f.empty()) { if (f.empty()) {
return "Can't set default image folder: Missing folder name"; 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"; 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 ""; 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()); 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)) { if (!IsValidDstFilename(full_filename)) {
return ReturnStatus(context, false, "Can't create image file: '" + full_filename + "': File already exists"); 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 { try {
len = stoull(size); 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); 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); return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
} }
if (len < 512 || (len & 0x1ff)) { 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))); return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': " + string(strerror(errno)));
} }
#ifndef __linux #ifndef __linux__
close(image_fd); close(image_fd);
unlink(full_filename.c_str()); 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()); 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 id;
int unit; int unit;
@ -205,7 +205,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
size_t last_slash = filename.rfind('/'); size_t last_slash = filename.rfind('/');
while (last_slash != string::npos) { while (last_slash != string::npos) {
string folder = filename.substr(0, last_slash); 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) { if (error_code error; !filesystem::is_empty(full_folder, error) || error) {
break; 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()); return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
} }
from = default_image_folder + "/" + from; from = GetFullName(from);
if (!IsValidSrcFilename(from)) { if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't rename/move image file: '" + from + "': Invalid name or type"); 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()); return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
} }
to = default_image_folder + "/" + to; to = GetFullName(to);
if (!IsValidDstFilename(to)) { if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': File already exists"); 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()); return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
} }
from = default_image_folder + "/" + from; from = GetFullName(from);
if (!IsValidSrcFilename(from)) { if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't copy image file: '" + from + "': Invalid name or type"); 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()); return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
} }
to = default_image_folder + "/" + to; to = GetFullName(to);
if (!IsValidDstFilename(to)) { if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists"); 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))); return ReturnStatus(context, false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
} }
#ifndef __linux #ifndef __linux__
close(fd_dst); close(fd_dst);
close(fd_src); 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()); return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
} }
filename = default_image_folder + "/" + filename; filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) { if (!IsValidSrcFilename(filename)) {
return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type"); return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type");
} }

View File

@ -13,7 +13,7 @@
#include "command_context.h" #include "command_context.h"
#include <string> #include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace rascsi_interface;
class RascsiImage class RascsiImage
{ {
@ -21,15 +21,13 @@ public:
RascsiImage(); RascsiImage();
~RascsiImage() = default; ~RascsiImage() = default;
RascsiImage(RascsiImage&) = delete;
RascsiImage& operator=(const RascsiImage&) = delete;
void SetDepth(int d) { depth = d; } void SetDepth(int d) { depth = d; }
int GetDepth() const { return depth; } int GetDepth() const { return depth; }
bool CheckDepth(string_view) const; bool CheckDepth(string_view) const;
bool CreateImageFolder(const CommandContext&, const string&) const; bool CreateImageFolder(const CommandContext&, const string&) const;
string GetDefaultImageFolder() const { return default_image_folder; } string GetDefaultFolder() const { return default_folder; }
string SetDefaultImageFolder(const string&); string SetDefaultFolder(const string&);
bool IsValidSrcFilename(const string&) const; bool IsValidSrcFilename(const string&) const;
bool IsValidDstFilename(const string&) const; bool IsValidDstFilename(const string&) const;
bool CreateImage(const CommandContext&, const PbCommand&) const; bool CreateImage(const CommandContext&, const PbCommand&) const;
@ -37,12 +35,13 @@ public:
bool RenameImage(const CommandContext&, const PbCommand&) const; bool RenameImage(const CommandContext&, const PbCommand&) const;
bool CopyImage(const CommandContext&, const PbCommand&) const; bool CopyImage(const CommandContext&, const PbCommand&) const;
bool SetImagePermissions(const CommandContext&, const PbCommand&) const; bool SetImagePermissions(const CommandContext&, const PbCommand&) const;
string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
private: private:
string GetHomeDir() const; string GetHomeDir() const;
string default_image_folder; string default_folder;
int depth = 1; int depth = 1;
}; };

View File

@ -7,59 +7,57 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include <dirent.h> #include "controllers/controller_manager.h"
#include "devices/file_support.h" #include "devices/file_support.h"
#include "devices/disk.h" #include "devices/disk.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
#include "command_util.h" #include "command_util.h"
#include "rascsi_version.h" #include "rascsi_version.h"
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include "rascsi_image.h"
#include "rascsi_response.h" #include "rascsi_response.h"
using namespace rascsi_interface; using namespace rascsi_interface;
using namespace command_util; using namespace command_util;
PbDeviceProperties *RascsiResponse::GetDeviceProperties(const Device *device) unique_ptr<PbDeviceProperties> RascsiResponse::GetDeviceProperties(const Device& device) const
{ {
auto properties = make_unique<PbDeviceProperties>().release(); auto properties = make_unique<PbDeviceProperties>();
properties->set_luns(ScsiController::LUN_MAX); properties->set_luns(max_luns);
properties->set_read_only(device->IsReadOnly()); properties->set_read_only(device.IsReadOnly());
properties->set_protectable(device->IsProtectable()); properties->set_protectable(device.IsProtectable());
properties->set_stoppable(device->IsStoppable()); properties->set_stoppable(device.IsStoppable());
properties->set_removable(device->IsRemovable()); properties->set_removable(device.IsRemovable());
properties->set_lockable(device->IsLockable()); properties->set_lockable(device.IsLockable());
properties->set_supports_file(dynamic_cast<const FileSupport *>(device) != nullptr); properties->set_supports_file(dynamic_cast<const FileSupport *>(&device) != nullptr);
properties->set_supports_params(device->SupportsParams()); properties->set_supports_params(device.SupportsParams());
PbDeviceType t = UNDEFINED; PbDeviceType t = UNDEFINED;
PbDeviceType_Parse(device->GetType(), &t); PbDeviceType_Parse(device.GetType(), &t);
if (device->SupportsParams()) { if (device.SupportsParams()) {
for (const auto& [key, value] : device_factory->GetDefaultParams(t)) { for (const auto& [key, value] : device_factory.GetDefaultParams(t)) {
auto& map = *properties->mutable_default_params(); auto& map = *properties->mutable_default_params();
map[key] = value; 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); properties->add_block_sizes(block_size);
} }
return properties; 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); type_properties->set_type(type);
const PrimaryDevice *device = device_factory->CreateDevice(type, "", -1); auto device = device_factory.CreateDevice(controller_manager, type, 0, "");
type_properties->set_allocated_properties(GetDeviceProperties(device)); type_properties->set_allocated_properties(GetDeviceProperties(*device).release());
device_factory->DeleteDevice(*device); //NOSONAR The allocated memory is managed by protobuf } //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. // Start with 2 instead of 1. 1 was the removed SASI drive type.
int ordinal = 2; 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_id(device.GetId());
pb_device->set_unit(device->GetLun()); pb_device.set_unit(device.GetLun());
pb_device->set_vendor(device->GetVendor()); pb_device.set_vendor(device.GetVendor());
pb_device->set_product(device->GetProduct()); pb_device.set_product(device.GetProduct());
pb_device->set_revision(device->GetRevision()); pb_device.set_revision(device.GetRevision());
PbDeviceType type = UNDEFINED; PbDeviceType type = UNDEFINED;
PbDeviceType_Parse(device->GetType(), &type); PbDeviceType_Parse(device.GetType(), &type);
pb_device->set_type(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<PbDeviceStatus>().release(); auto status = make_unique<PbDeviceStatus>().release();
pb_device->set_allocated_status(status); pb_device.set_allocated_status(status);
status->set_protected_(device->IsProtected()); status->set_protected_(device.IsProtected());
status->set_stopped(device->IsStopped()); status->set_stopped(device.IsStopped());
status->set_removed(device->IsRemoved()); status->set_removed(device.IsRemoved());
status->set_locked(device->IsLocked()); status->set_locked(device.IsLocked());
if (device->SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf
for (const auto& [key, value] : device->GetParams()) { for (const auto& [key, value] : device.GetParams()) {
AddParam(*pb_device, key, value); AddParam(pb_device, key, value);
} }
} }
if (const auto disk = dynamic_cast<const Disk*>(device); disk) { if (const auto disk = dynamic_cast<const Disk*>(&device); disk) {
pb_device->set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes()); pb_device.set_block_size(device.IsRemoved()? 0 : disk->GetSectorSizeInBytes());
pb_device->set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount()); pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount());
} }
const auto file_support = dynamic_cast<const FileSupport *>(device); const auto file_support = dynamic_cast<const FileSupport *>(&device);
if (file_support) { if (file_support) {
Filepath filepath; Filepath filepath;
file_support->GetPath(filepath); file_support->GetPath(filepath);
auto image_file = make_unique<PbImageFile>().release(); auto image_file = make_unique<PbImageFile>().release();
GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath()); GetImageFile(*image_file, default_folder, device.IsRemovable() && !device.IsReady() ? "" : filepath.GetPath());
pb_device->set_allocated_file(image_file); pb_device.set_allocated_file(image_file);
} }
} //NOSONAR The allocated memory is managed by protobuf } //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()) { if (!filename.empty()) {
image_file->set_name(filename); image_file.set_name(filename);
image_file->set_type(device_factory->GetTypeForFile(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)) { 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; return true;
} }
} }
@ -132,8 +130,9 @@ bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filenam
return false; return false;
} }
void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, string_view default_image_folder, 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 string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
if (scan_depth-- < 0) { if (scan_depth-- < 0) {
return; return;
} }
@ -151,35 +150,29 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, stri
const dirent *dir; const dirent *dir;
while ((dir = readdir(d))) { 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; string filename = GetNextImageFile(dir, folder);
if (is_supported_type && dir->d_name[0] != '.') { if (filename.empty()) {
continue;
}
string name_lower = dir->d_name; string name_lower = dir->d_name;
if (!file_pattern.empty()) { if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower); transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
} }
string filename = folder + "/" + dir->d_name; if (dir->d_type == DT_DIR) {
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) { if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
GetAvailableImages(image_files_info, default_image_folder, filename, folder_pattern, GetAvailableImages(image_files_info, default_folder, filename, folder_pattern,
file_pattern, scan_depth); file_pattern, scan_depth);
} }
continue; continue;
} }
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(image_file.get(), filename)) { if (auto image_file = make_unique<PbImageFile>(); GetImageFile(*image_file.get(), default_folder, filename)) {
GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1)); 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); closedir(d);
} }
PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const string& folder_pattern, unique_ptr<PbImageFilesInfo> RascsiResponse::GetAvailableImages(PbResult& result, const string& default_folder,
const string& file_pattern, int scan_depth) const string& folder_pattern, const string& file_pattern, int scan_depth) const
{ {
auto image_files_info = make_unique<PbImageFilesInfo>().release(); auto image_files_info = make_unique<PbImageFilesInfo>();
string default_image_folder = rascsi_image->GetDefaultImageFolder(); image_files_info->set_default_image_folder(default_folder);
image_files_info->set_default_image_folder(default_image_folder);
image_files_info->set_depth(scan_depth); 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); file_pattern, scan_depth);
result.set_status(true); result.set_status(true);
@ -204,19 +196,19 @@ PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const str
return image_files_info; return image_files_info;
} }
void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& folder_pattern, void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder,
const string& file_pattern, int scan_depth) const string& folder_pattern, const string& file_pattern, int scan_depth) const
{ {
PbImageFilesInfo *image_files_info = GetAvailableImages(result, folder_pattern, file_pattern, scan_depth); auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth);
image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder()); image_files_info->set_default_image_folder(default_folder);
server_info.set_allocated_image_files_info(image_files_info); server_info.set_allocated_image_files_info(image_files_info.release());
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf result.set_status(true); //NOSONAR The allocated memory is managed by protobuf
} }
PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unordered_set<int>& ids) unique_ptr<PbReservedIdsInfo> RascsiResponse::GetReservedIds(PbResult& result, const unordered_set<int>& ids) const
{ {
auto reserved_ids_info = make_unique<PbReservedIdsInfo>().release(); auto reserved_ids_info = make_unique<PbReservedIdsInfo>();
for (int id : ids) { for (int id : ids) {
reserved_ids_info->add_ids(id); reserved_ids_info->add_ids(id);
} }
@ -226,48 +218,52 @@ PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unorde
return reserved_ids_info; 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(); 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_set> id_sets; set<id_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()) { 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())); id_sets.insert(make_pair(device->GetId(), device->GetLun()));
} }
} }
// Otherwise get information on the devices provided in the command
else { else {
for (const auto& device : command.devices()) { id_sets = MatchDevices(result, command);
if (device_factory->GetDeviceByIdAndLun(device.id(), device.unit())) { if (id_sets.empty()) {
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; return;
} }
} }
}
auto devices_info = make_unique<PbDevicesInfo>().release(); auto devices_info = make_unique<PbDevicesInfo>();
result.set_allocated_devices_info(devices_info);
for (const auto& [id, lun] : id_sets) { for (const auto& [id, lun] : id_sets) {
GetDevice(device_factory->GetDeviceByIdAndLun(id, lun), devices_info->add_devices()); for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) {
GetDevice(*d, *devices_info->add_devices(), default_folder);
break;
}
}
} }
result.set_allocated_devices_info(devices_info.release());
result.set_status(true); result.set_status(true);
} }
PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result) unique_ptr<PbDeviceTypesInfo> RascsiResponse::GetDeviceTypesInfo(PbResult& result) const
{ {
auto device_types_info = make_unique<PbDeviceTypesInfo>().release(); auto device_types_info = make_unique<PbDeviceTypesInfo>();
GetAllDeviceTypeProperties(*device_types_info); GetAllDeviceTypeProperties(*device_types_info);
@ -276,29 +272,30 @@ PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result)
return device_types_info; return device_types_info;
} }
PbServerInfo *RascsiResponse::GetServerInfo(PbResult& result, const unordered_set<int>& reserved_ids, unique_ptr<PbServerInfo> RascsiResponse::GetServerInfo(PbResult& result, const unordered_set<int>& reserved_ids,
const string& current_log_level, const string& folder_pattern, const string& file_pattern, int scan_depth) 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<PbServerInfo>().release(); auto server_info = make_unique<PbServerInfo>();
server_info->set_allocated_version_info(GetVersionInfo(result)); server_info->set_allocated_version_info(GetVersionInfo(result).release());
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level)); //NOSONAR The allocated memory is managed by protobuf 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 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); GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth);
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result)); server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release());
server_info->set_allocated_mapping_info(GetMappingInfo(result)); //NOSONAR The allocated memory is managed by protobuf server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf
GetDevices(*server_info); //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)); server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release());
server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth)); //NOSONAR The allocated memory is managed by protobuf server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf
result.set_status(true); result.set_status(true);
return server_info; return server_info;
} }
PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result) unique_ptr<PbVersionInfo> RascsiResponse::GetVersionInfo(PbResult& result) const
{ {
auto version_info = make_unique<PbVersionInfo>().release(); auto version_info = make_unique<PbVersionInfo>();
version_info->set_major_version(rascsi_major_version); version_info->set_major_version(rascsi_major_version);
version_info->set_minor_version(rascsi_minor_version); version_info->set_minor_version(rascsi_minor_version);
@ -309,9 +306,9 @@ PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result)
return version_info; return version_info;
} }
PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) unique_ptr<PbLogLevelInfo> RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const
{ {
auto log_level_info = make_unique<PbLogLevelInfo>().release(); auto log_level_info = make_unique<PbLogLevelInfo>();
for (const auto& log_level : log_levels) { for (const auto& log_level : log_levels) {
log_level_info->add_log_levels(log_level); log_level_info->add_log_levels(log_level);
@ -324,11 +321,11 @@ PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string&
return log_level_info; return log_level_info;
} }
PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) unique_ptr<PbNetworkInterfacesInfo> RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) const
{ {
auto network_interfaces_info = make_unique<PbNetworkInterfacesInfo>().release(); auto network_interfaces_info = make_unique<PbNetworkInterfacesInfo>();
for (const auto& network_interface : device_factory->GetNetworkInterfaces()) { for (const auto& network_interface : device_factory.GetNetworkInterfaces()) {
network_interfaces_info->add_name(network_interface); network_interfaces_info->add_name(network_interface);
} }
@ -337,11 +334,11 @@ PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& resu
return network_interfaces_info; return network_interfaces_info;
} }
PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result) unique_ptr<PbMappingInfo> RascsiResponse::GetMappingInfo(PbResult& result) const
{ {
auto mapping_info = make_unique<PbMappingInfo>().release(); auto mapping_info = make_unique<PbMappingInfo>();
for (const auto& [name, type] : device_factory->GetExtensionMapping()) { for (const auto& [name, type] : device_factory.GetExtensionMapping()) {
(*mapping_info->mutable_mapping())[name] = type; (*mapping_info->mutable_mapping())[name] = type;
} }
@ -350,131 +347,147 @@ PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result)
return mapping_info; return mapping_info;
} }
PbOperationInfo *RascsiResponse::GetOperationInfo(PbResult& result, int depth) unique_ptr<PbOperationInfo> RascsiResponse::GetOperationInfo(PbResult& result, int depth) const
{ {
auto operation_info = make_unique<PbOperationInfo>(); auto operation_info = make_unique<PbOperationInfo>();
auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required"); 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, "name", "Image file name in case of a mass storage device").release();
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list"); AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release();
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge"); AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release();
AddOperationParameter(*operation, "cmd", "Print command for the printer device"); AddOperationParameter(*operation, "cmd", "Print command for the printer device").release();
AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds"); 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"); 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"); operation = CreateOperation(*operation_info, SERVER_INFO, "Get rascsi server information");
if (depth) { 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"); operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
if (depth) { 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"); 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"); 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"); 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"); 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"); 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"); parameter->add_permitted_values("rascsi");
// System shutdown/reboot requires root permissions // System shutdown/reboot requires root permissions
if (!getuid()) { if (!getuid()) {
parameter->add_permitted_values("system"); parameter->add_permitted_values("system");
parameter->add_permitted_values("reboot"); parameter->add_permitted_values("reboot");
} }
operation.release();
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file"); operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true); AddOperationParameter(*operation, "file", "Image file name", "", true).release();
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true); AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false"); parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true"); parameter->add_permitted_values("true");
parameter->add_permitted_values("false"); parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file"); 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"); operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true); AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true); AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file"); operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true); AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true); AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false"); parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true"); parameter->add_permitted_values("true");
parameter->add_permitted_values("false"); parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file"); 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"); 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"); 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); result.set_status(true);
return operation_info.release(); return operation_info;
} }
PbOperationMetaData *RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation, unique_ptr<PbOperationMetaData> RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
const string& description) const const string& description) const
{ {
auto meta_data = make_shared<PbOperationMetaData>(); auto meta_data = make_unique<PbOperationMetaData>();
meta_data->set_server_side_name(PbOperation_Name(operation)); meta_data->set_server_side_name(PbOperation_Name(operation));
meta_data->set_description(description); meta_data->set_description(description);
int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index();
(*operation_info.mutable_operations())[ordinal] = *meta_data.get(); (*operation_info.mutable_operations())[ordinal] = *meta_data.release();
return &(*operation_info.mutable_operations())[ordinal]; return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]);
} }
PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name, unique_ptr<PbOperationParameter> RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data,
const string& description, const string& default_value, bool is_mandatory) const string& name, const string& description, const string& default_value, bool is_mandatory) const
{ {
auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters()); auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters());
parameter->set_name(name); parameter->set_name(name);
@ -482,5 +495,59 @@ PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData&
parameter->set_default_value(default_value); parameter->set_default_value(default_value);
parameter->set_is_mandatory(is_mandatory); parameter->set_is_mandatory(is_mandatory);
return parameter.release(); return parameter;
}
set<id_set> RascsiResponse::MatchDevices(PbResult& result, const PbCommand& command) const
{
set<id_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;
} }

View File

@ -9,54 +9,60 @@
#pragma once #pragma once
#include "devices/device_factory.h"
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include <dirent.h>
#include <list> #include <list>
#include <string> #include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace std;
using namespace rascsi_interface;
class DeviceFactory; class DeviceFactory;
class RascsiImage; class ControllerManager;
class Device; class Device;
class RascsiResponse class RascsiResponse
{ {
public: public:
RascsiResponse(DeviceFactory *device_factory, const RascsiImage *rascsi_image) RascsiResponse(DeviceFactory& device_factory, const ControllerManager& controller_manager, int max_luns)
: device_factory(device_factory), rascsi_image(rascsi_image) {} : device_factory(device_factory), controller_manager(controller_manager), max_luns(max_luns) {}
~RascsiResponse() = default; ~RascsiResponse() = default;
RascsiResponse(RascsiResponse&) = delete;
RascsiResponse& operator=(const RascsiResponse&) = delete;
bool GetImageFile(PbImageFile *, const string&) const; bool GetImageFile(PbImageFile&, const string&, const string&) const;
PbImageFilesInfo *GetAvailableImages(PbResult&, const string&, const string&, int); unique_ptr<PbImageFilesInfo> GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const;
PbReservedIdsInfo *GetReservedIds(PbResult&, const unordered_set<int>&); unique_ptr<PbReservedIdsInfo> GetReservedIds(PbResult&, const unordered_set<int>&) const;
void GetDevices(PbServerInfo&); void GetDevices(PbServerInfo&, const string&) const;
void GetDevicesInfo(PbResult&, const PbCommand&); void GetDevicesInfo(PbResult&, const PbCommand&, const string&) const;
PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&); unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const;
PbVersionInfo *GetVersionInfo(PbResult&); unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const;
PbServerInfo *GetServerInfo(PbResult&, const unordered_set<int>&, const string&, const string&, const string&, int); unique_ptr<PbServerInfo> GetServerInfo(PbResult&, const unordered_set<int>&, const string&, const string&, const string&,
PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&); const string&, int) const;
PbMappingInfo *GetMappingInfo(PbResult&); unique_ptr<PbNetworkInterfacesInfo> GetNetworkInterfacesInfo(PbResult&) const;
PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&); unique_ptr<PbMappingInfo> GetMappingInfo(PbResult&) const;
PbOperationInfo *GetOperationInfo(PbResult&, int); unique_ptr<PbLogLevelInfo> GetLogLevelInfo(PbResult&, const string&) const;
unique_ptr<PbOperationInfo> GetOperationInfo(PbResult&, int) const;
private: private:
DeviceFactory *device_factory; DeviceFactory& device_factory;
const RascsiImage *rascsi_image;
const ControllerManager& controller_manager;
int max_luns;
const list<string> log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" }; const list<string> log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" };
PbDeviceProperties *GetDeviceProperties(const Device *); unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
void GetDevice(const Device *, PbDevice *); void GetDevice(const Device&, PbDevice&, const string&) const;
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&); void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const;
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType); void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
void GetAvailableImages(PbImageFilesInfo&, string_view, const string&, const string&, const string&, int); void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, int); void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const;
PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; unique_ptr<PbOperationMetaData> CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
PbOperationParameter *AddOperationParameter(PbOperationMetaData&, const string&, const string&, unique_ptr<PbOperationParameter> AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false); const string& = "", bool = false) const;
set<id_set> MatchDevices(PbResult&, const PbCommand&) const;
static string GetNextImageFile(const dirent *, const string&);
}; };

View File

@ -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 <netinet/in.h>
#include <csignal>
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<byte> 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;
}

View File

@ -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 <thread>
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; }
};

View File

@ -14,7 +14,7 @@
// The following should be updated for each release // The following should be updated for each release
const int rascsi_major_version = 22; // Last two digits of year 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 const int rascsi_patch_version = -1; // Patch number - increment for each update
using namespace std; using namespace std;

View File

@ -8,7 +8,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "socket_connector.h" #include "protobuf_serializer.h"
#include "command_util.h" #include "command_util.h"
#include "rasutil.h" #include "rasutil.h"
#include "rasctl_commands.h" #include "rasctl_commands.h"
@ -151,7 +151,7 @@ void RasctlCommands::SendCommand()
throw io_exception("Can't write magic"); throw io_exception("Can't write magic");
} }
socket_connector.SerializeMessage(fd, command); serializer.SerializeMessage(fd, command);
} }
catch(const io_exception& e) { catch(const io_exception& e) {
cerr << "Error: " << e.get_msg() << endl; cerr << "Error: " << e.get_msg() << endl;
@ -165,7 +165,7 @@ void RasctlCommands::SendCommand()
// Receive result // Receive result
try { try {
socket_connector.DeserializeMessage(fd, result); serializer.DeserializeMessage(fd, result);
if (!result.status()) { if (!result.status()) {
throw io_exception(result.msg()); throw io_exception(result.msg());
@ -305,7 +305,7 @@ void RasctlCommands::CommandServerInfo()
if (server_info.devices_info().devices_size()) { if (server_info.devices_info().devices_size()) {
list<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; list<PbDevice> 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; cout << "Attached devices:" << endl;

View File

@ -9,12 +9,12 @@
#pragma once #pragma once
#include "socket_connector.h" #include "protobuf_serializer.h"
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include "rasctl_display.h" #include "rasctl_display.h"
#include <string> #include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace rascsi_interface;
class RasctlCommands class RasctlCommands
{ {
@ -22,8 +22,6 @@ public:
RasctlCommands(const PbCommand&, const string&, int, const string&, const string&); RasctlCommands(const PbCommand&, const string&, int, const string&, const string&);
~RasctlCommands() = default; ~RasctlCommands() = default;
RasctlCommands(RasctlCommands&) = delete;
RasctlCommands& operator=(const RasctlCommands&) = delete;
void Execute(const string&, const string&, const string&, const string&, const string&); void Execute(const string&, const string&, const string&, const string&, const string&);
@ -51,7 +49,7 @@ private:
void CommandOperationInfo(); void CommandOperationInfo();
void SendCommand(); void SendCommand();
SocketConnector socket_connector; ProtobufSerializer serializer;
PbCommand command; PbCommand command;
string hostname; string hostname;
int port; int port;

View File

@ -11,7 +11,7 @@
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi using namespace rascsi_interface;
class RasctlDisplay class RasctlDisplay
{ {
@ -19,8 +19,6 @@ class RasctlDisplay
RasctlDisplay() = default; RasctlDisplay() = default;
~RasctlDisplay() = default; ~RasctlDisplay() = default;
RasctlDisplay(RasctlDisplay&) = delete;
RasctlDisplay& operator=(const RasctlDisplay&) = delete;
void DisplayDevices(const PbDevicesInfo&) const; void DisplayDevices(const PbDevicesInfo&) const;
void DisplayDeviceInfo(const PbDevice&) const; void DisplayDeviceInfo(const PbDevice&) const;

View File

@ -22,7 +22,8 @@ bool ras_util::GetAsInt(const string& value, int& result)
} }
try { try {
result = (int)stoul(value); auto v = stoul(value);
result = (int)v;
} }
catch(const invalid_argument&) { catch(const invalid_argument&) {
return false; return false;
@ -46,7 +47,7 @@ string ras_util::ListDevices(const list<PbDevice>& pb_devices)
<< "+----+-----+------+-------------------------------------" << endl; << "+----+-----+------+-------------------------------------" << endl;
list<PbDevice> devices = pb_devices; list<PbDevice> 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) { for (const auto& device : devices) {
string filename; string filename;
@ -82,3 +83,22 @@ string ras_util::ListDevices(const list<PbDevice>& pb_devices)
return s.str(); 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
}

View File

@ -19,4 +19,6 @@ namespace ras_util
{ {
bool GetAsInt(const std::string&, int&); bool GetAsInt(const std::string&, int&);
std::string ListDevices(const std::list<rascsi_interface::PbDevice>&); std::string ListDevices(const std::list<rascsi_interface::PbDevice>&);
void FixCpu(int);
} }

View File

@ -9,7 +9,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "scsi.h" #include "bus.h"
using namespace std; using namespace std;
@ -31,7 +31,7 @@ BUS::phase_t BUS::GetPhase()
} }
// Get target phase from bus signal line // Get target phase from bus signal line
DWORD mci = GetMSG() ? 0x04 : 0x00; int mci = GetMSG() ? 0x04 : 0x00;
mci |= GetCD() ? 0x02 : 0x00; mci |= GetCD() ? 0x02 : 0x00;
mci |= GetIO() ? 0x01 : 0x00; mci |= GetIO() ? 0x01 : 0x00;
return GetPhase(mci); return GetPhase(mci);
@ -42,13 +42,9 @@ BUS::phase_t BUS::GetPhase()
// Determine Phase String phase enum // Determine Phase String phase enum
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
const char* BUS::GetPhaseStrRaw(phase_t current_phase){ const char* BUS::GetPhaseStrRaw(phase_t current_phase) {
if(current_phase <= phase_t::reserved) { const auto& it = phase_str_mapping.find(current_phase);
return phase_str_table[(int)current_phase]; return it != phase_str_mapping.end() ? it->second : "INVALID";
}
else {
return "INVALID";
}
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -70,24 +66,21 @@ const array<BUS::phase_t, 8> BUS::phase_table = {
phase_t::msgin // | 1 | 1 | 1 | phase_t::msgin // | 1 | 1 | 1 |
}; };
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// Phase Table // Phase string to phase mapping
// This MUST be kept in sync with the phase_t enum type!
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
const array<const char*, 12> BUS::phase_str_table = { const unordered_map<BUS::phase_t, const char*> BUS::phase_str_mapping = {
"busfree", { phase_t::busfree, "busfree" },
"arbitration", { phase_t::arbitration, "arbitration" },
"selection", { phase_t::selection, "selection" },
"reselection", { phase_t::reselection, "reselection" },
"command", { phase_t::command, "command" },
"execute", { phase_t::datain, "datain" },
"datain", { phase_t::dataout, "dataout" },
"dataout", { phase_t::status, "status" },
"status", { phase_t::msgin, "msgin" },
"msgin", { phase_t::msgout, "msgout" },
"msgout", { phase_t::reserved, "reserved" }
"reserved"
}; };

View File

@ -5,118 +5,12 @@
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// //
// [ SCSI Common Functionality ]
//
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "os.h" // TODO Remove this include as soon as gpiobus.cpp/h is open for editing (adding the include there) again
#include <array> #include "bus.h"
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_t, 8> phase_table;
static const array<const char *, 12> phase_str_table;
};
namespace scsi_defs { namespace scsi_defs {
enum class scsi_level : int { enum class scsi_level : int {

View File

@ -33,7 +33,7 @@ static const int _MAX_FNAME = 256;
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
static volatile bool running; // Running flag static volatile bool running; // Running flag
unique_ptr<GPIOBUS> bus; // GPIO Bus GPIOBUS bus; // GPIO Bus
DWORD buff_size = 1000000; DWORD buff_size = 1000000;
data_capture *data_buffer; data_capture *data_buffer;
@ -186,15 +186,14 @@ bool Init()
} }
// GPIO Initialization // GPIO Initialization
bus = make_unique<GPIOBUS>(); if (!bus.Init())
if (!bus->Init())
{ {
LOGERROR("Unable to intiailize the GPIO bus. Exiting....") LOGERROR("Unable to intiailize the GPIO bus. Exiting....")
return false; return false;
} }
// Bus Reset // Bus Reset
bus->Reset(); bus.Reset();
// Other // Other
running = false; running = false;
@ -204,8 +203,7 @@ bool Init()
void Cleanup() void Cleanup()
{ {
if (!import_data) if (!import_data) {
{
LOGINFO("Stopping data collection....") LOGINFO("Stopping data collection....")
} }
LOGINFO(" ") LOGINFO(" ")
@ -216,17 +214,14 @@ void Cleanup()
LOGINFO("Generating %s...", html_file_name) LOGINFO("Generating %s...", html_file_name)
scsimon_generate_html(html_file_name, data_buffer, data_idx); scsimon_generate_html(html_file_name, data_buffer, data_idx);
if (bus)
{
// Cleanup the Bus // Cleanup the Bus
bus->Cleanup(); bus.Cleanup();
}
} }
void Reset() void Reset()
{ {
// Reset the bus // Reset the bus
bus->Reset(); bus.Reset();
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -234,7 +229,7 @@ void Reset()
// Pin the thread to a specific CPU (Only applies to Linux) // Pin the thread to a specific CPU (Only applies to Linux)
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#ifdef __linux #ifdef __linux__
void FixCpu(int cpu) void FixCpu(int cpu)
{ {
// Get the number of CPUs // Get the number of CPUs
@ -326,7 +321,7 @@ int main(int argc, char *argv[])
// Reset // Reset
Reset(); Reset();
#ifdef __linux #ifdef __linux__
// Set the affinity to a specific processor core // Set the affinity to a specific processor core
FixCpu(3); FixCpu(3);
@ -338,7 +333,7 @@ int main(int argc, char *argv[])
// Start execution // Start execution
running = true; running = true;
bus->SetACT(false); bus.SetACT(false);
(void)gettimeofday(&start_time, nullptr); (void)gettimeofday(&start_time, nullptr);
@ -348,7 +343,7 @@ int main(int argc, char *argv[])
while (running) while (running)
{ {
// Work initialization // Work initialization
this_sample = (bus->Acquire() & ALL_SCSI_PINS); this_sample = (bus.Acquire() & ALL_SCSI_PINS);
loop_count++; loop_count++;
if (loop_count > LLONG_MAX - 1) if (loop_count > LLONG_MAX - 1)
{ {

View File

@ -7,54 +7,35 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "bus.h"
#include "rascsi_exceptions.h"
#include "controllers/abstract_controller.h" #include "controllers/abstract_controller.h"
using namespace scsi_defs;
TEST(AbstractControllerTest, Reset) TEST(AbstractControllerTest, Reset)
{ {
MockAbstractController controller(0); MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
controller.SetPhase(BUS::phase_t::status); controller.SetPhase(BUS::phase_t::status);
EXPECT_EQ(BUS::phase_t::status, controller.GetPhase()); EXPECT_EQ(BUS::phase_t::status, controller.GetPhase());
EXPECT_CALL(*device, Reset()).Times(1);
controller.Reset(); controller.Reset();
EXPECT_TRUE(controller.IsBusFree()); EXPECT_TRUE(controller.IsBusFree());
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_EQ(0, controller.GetLength());
} }
TEST(AbstractControllerTest, SetGetStatus) TEST(AbstractControllerTest, SetGetStatus)
{ {
MockAbstractController controller(0); MockAbstractController controller(0);
controller.SetStatus(0x1234); controller.SetStatus(status::BUSY);
EXPECT_EQ(0x1234, controller.GetStatus()); EXPECT_EQ(status::BUSY, 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());
} }
TEST(AbstractControllerTest, ProcessPhase) TEST(AbstractControllerTest, ProcessPhase)
@ -92,6 +73,12 @@ TEST(AbstractControllerTest, ProcessPhase)
controller.SetPhase(BUS::phase_t::msgout); controller.SetPhase(BUS::phase_t::msgout);
EXPECT_CALL(controller, MsgOut()).Times(1); EXPECT_CALL(controller, MsgOut()).Times(1);
controller.ProcessPhase(); 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) TEST(AbstractControllerTest, DeviceLunLifeCycle)
@ -100,24 +87,22 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle)
const int LUN = 4; const int LUN = 4;
MockAbstractController controller(ID); MockAbstractController controller(ID);
MockPrimaryDevice device1; auto device1 = make_shared<MockPrimaryDevice>(LUN);
MockPrimaryDevice device2; auto device2 = make_shared<MockPrimaryDevice>(32);
auto device3 = make_shared<MockPrimaryDevice>(-1);
EXPECT_FALSE(controller.HasLuns());
device1.SetLun(LUN);
device2.SetLun(32);
EXPECT_EQ(0, controller.GetLunCount());
EXPECT_EQ(ID, controller.GetTargetId()); EXPECT_EQ(ID, controller.GetTargetId());
EXPECT_TRUE(controller.AddDevice(&device1)); EXPECT_TRUE(controller.AddDevice(device1));
EXPECT_FALSE(controller.AddDevice(&device2)); EXPECT_FALSE(controller.AddDevice(device2));
EXPECT_TRUE(controller.HasLuns()); EXPECT_FALSE(controller.AddDevice(device3));
EXPECT_TRUE(controller.GetLunCount() > 0);
EXPECT_TRUE(controller.HasDeviceForLun(LUN)); EXPECT_TRUE(controller.HasDeviceForLun(LUN));
EXPECT_FALSE(controller.HasDeviceForLun(0)); EXPECT_FALSE(controller.HasDeviceForLun(0));
EXPECT_EQ(&device1, controller.GetDeviceForLun(LUN)); EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN));
EXPECT_EQ(nullptr, controller.GetDeviceForLun(0)); EXPECT_EQ(nullptr, controller.GetDeviceForLun(0));
EXPECT_TRUE(controller.DeleteDevice(&device1)); EXPECT_TRUE(controller.DeleteDevice(device1));
EXPECT_FALSE(controller.HasLuns()); EXPECT_EQ(0, controller.GetLunCount());
} }
TEST(AbstractControllerTest, ExtractInitiatorId) TEST(AbstractControllerTest, ExtractInitiatorId)
@ -136,7 +121,7 @@ TEST(AbstractControllerTest, GetOpcode)
{ {
MockAbstractController controller(0); MockAbstractController controller(0);
vector<int>& cmd = controller.InitCmd(2); vector<int>& cmd = controller.InitCmd(1);
cmd[0] = 0x12; cmd[0] = 0x12;
EXPECT_EQ(0x12, (int)controller.GetOpcode()); EXPECT_EQ(0x12, (int)controller.GetOpcode());
@ -154,10 +139,23 @@ TEST(AbstractControllerTest, GetLun)
EXPECT_EQ(LUN, controller.GetLun()); EXPECT_EQ(LUN, controller.GetLun());
} }
TEST(AbstractControllerTest, Ctrl) TEST(AbstractControllerTest, Length)
{ {
MockAbstractController controller(0); MockAbstractController controller(0);
EXPECT_FALSE(controller.HasValidLength()); 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());
}

View File

@ -7,7 +7,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "rascsi_interface.pb.h" #include "rascsi_interface.pb.h"
#include "command_util.h" #include "command_util.h"
@ -59,3 +59,10 @@ TEST(CommandUtil, ParseParameters)
TestSpecialDevice("printer"); TestSpecialDevice("printer");
TestSpecialDevice("services"); TestSpecialDevice("services");
} }
TEST(CommandUtil, ReturnLocalizedError)
{
MockCommandContext context;
EXPECT_FALSE(ReturnLocalizedError(context, LocalizationKey::ERROR_LOG_LEVEL));
}

View File

@ -7,31 +7,56 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
#include "controllers/controller_manager.h" #include "controllers/controller_manager.h"
TEST(ControllerManagerTest, ControllerManager) TEST(ControllerManagerTest, LifeCycle)
{ {
const int ID = 4; 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; DeviceFactory device_factory;
ControllerManager controller_manager;
auto device = device_factory.CreateDevice(UNDEFINED, "services", ID); auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services");
device->SetId(ID); controller_manager.AttachToScsiController(ID, device);
device->SetLun(LUN); auto controller = controller_manager.FindController(ID);
EXPECT_NE(nullptr, controller);
controller_manager.CreateScsiController(nullptr, device); EXPECT_EQ(1, controller->GetLunCount());
EXPECT_NE(nullptr, controller_manager.IdentifyController(1 << ID)); EXPECT_NE(nullptr, controller_manager.IdentifyController(1 << ID));
EXPECT_EQ(nullptr, controller_manager.IdentifyController(0)); EXPECT_EQ(nullptr, controller_manager.IdentifyController(0));
EXPECT_NE(nullptr, controller_manager.FindController(ID));
EXPECT_EQ(nullptr, controller_manager.FindController(0)); 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)); EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(0, 0));
controller_manager.DeleteAllControllers(); device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services");
device_factory.DeleteAllDevices(); 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.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());
} }

View File

@ -7,9 +7,10 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "rascsi_version.h" #include "rascsi_version.h"
#include "controllers/controller_manager.h"
#include "devices/device.h" #include "devices/device.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
#include <unordered_map> #include <unordered_map>
@ -37,27 +38,6 @@ TEST(DeviceFactoryTest, GetTypeForFile)
EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED); 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<PrimaryDevice *> 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) TEST(DeviceFactoryTest, GetSectorSizes)
{ {
DeviceFactory device_factory; DeviceFactory device_factory;
@ -150,23 +130,27 @@ TEST(DeviceFactoryTest, GetDefaultParams)
TEST(DeviceFactoryTest, UnknownDeviceType) TEST(DeviceFactoryTest, UnknownDeviceType)
{ {
MockBus bus;
DeviceFactory device_factory; 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); EXPECT_EQ(nullptr, device1);
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #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 #pragma GCC diagnostic pop
EXPECT_EQ(nullptr, device2); EXPECT_EQ(nullptr, device2);
} }
TEST(DeviceFactoryTest, SCHD_Device_Defaults) TEST(DeviceFactoryTest, SCHD_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType()); EXPECT_EQ("SCHD", device->GetType());
EXPECT_TRUE(device->SupportsFile()); 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), EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision()); device->GetRevision());
device_factory.DeleteDevice(*device); device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hds");
device = device_factory.CreateDevice(UNDEFINED, "test.hds", -1);
EXPECT_NE(nullptr, device); EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType()); 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_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType()); 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_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType()); 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) TEST(DeviceFactoryTest, SCRM_Device_Defaults)
{ {
DeviceFactory device_factory; TestRemovableDrive("SCRM", "test.hdr", "SCSI HD (REM.)");
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);
} }
TEST(DeviceFactoryTest, SCMO_Device_Defaults) TEST(DeviceFactoryTest, SCMO_Device_Defaults)
{ {
DeviceFactory device_factory; TestRemovableDrive("SCMO", "test.mos", "SCSI MO");
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);
} }
TEST(DeviceFactoryTest, SCCD_Device_Defaults) TEST(DeviceFactoryTest, SCCD_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCCD", device->GetType()); EXPECT_EQ("SCCD", device->GetType());
EXPECT_TRUE(device->SupportsFile()); EXPECT_TRUE(device->SupportsFile());
@ -281,15 +246,15 @@ TEST(DeviceFactoryTest, SCCD_Device_Defaults)
EXPECT_EQ("SCSI CD-ROM", device->GetProduct()); 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), EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision()); device->GetRevision());
device_factory.DeleteDevice(*device);
} }
TEST(DeviceFactoryTest, SCBR_Device_Defaults) TEST(DeviceFactoryTest, SCBR_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCBR", device->GetType()); EXPECT_EQ("SCBR", device->GetType());
EXPECT_FALSE(device->SupportsFile()); EXPECT_FALSE(device->SupportsFile());
@ -308,15 +273,15 @@ TEST(DeviceFactoryTest, SCBR_Device_Defaults)
EXPECT_EQ("RASCSI BRIDGE", device->GetProduct()); EXPECT_EQ("RASCSI BRIDGE", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision()); device->GetRevision());
device_factory.DeleteDevice(*device);
} }
TEST(DeviceFactoryTest, SCDP_Device_Defaults) TEST(DeviceFactoryTest, SCDP_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCDP", device->GetType()); EXPECT_EQ("SCDP", device->GetType());
EXPECT_FALSE(device->SupportsFile()); EXPECT_FALSE(device->SupportsFile());
@ -334,15 +299,15 @@ TEST(DeviceFactoryTest, SCDP_Device_Defaults)
EXPECT_EQ("Dayna", device->GetVendor()); EXPECT_EQ("Dayna", device->GetVendor());
EXPECT_EQ("SCSI/Link", device->GetProduct()); EXPECT_EQ("SCSI/Link", device->GetProduct());
EXPECT_EQ("1.4a", device->GetRevision()); EXPECT_EQ("1.4a", device->GetRevision());
device_factory.DeleteDevice(*device);
} }
TEST(DeviceFactoryTest, SCHS_Device_Defaults) TEST(DeviceFactoryTest, SCHS_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCHS", device->GetType()); EXPECT_EQ("SCHS", device->GetType());
EXPECT_FALSE(device->SupportsFile()); EXPECT_FALSE(device->SupportsFile());
@ -361,15 +326,15 @@ TEST(DeviceFactoryTest, SCHS_Device_Defaults)
EXPECT_EQ("Host Services", device->GetProduct()); EXPECT_EQ("Host Services", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision()); device->GetRevision());
device_factory.DeleteDevice(*device);
} }
TEST(DeviceFactoryTest, SCLP_Device_Defaults) TEST(DeviceFactoryTest, SCLP_Device_Defaults)
{ {
MockBus bus;
DeviceFactory device_factory; 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_NE(nullptr, device);
EXPECT_EQ("SCLP", device->GetType()); EXPECT_EQ("SCLP", device->GetType());
EXPECT_FALSE(device->SupportsFile()); EXPECT_FALSE(device->SupportsFile());
@ -388,6 +353,4 @@ TEST(DeviceFactoryTest, SCLP_Device_Defaults)
EXPECT_EQ("SCSI PRINTER", device->GetProduct()); EXPECT_EQ("SCSI PRINTER", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2), EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision()); device->GetRevision());
device_factory.DeleteDevice(*device);
} }

View File

@ -7,31 +7,15 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "devices/device.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) TEST(DeviceTest, Properties)
{ {
const int ID = 4;
const int LUN = 5; const int LUN = 5;
TestDevice device; MockDevice device(LUN);
EXPECT_FALSE(device.IsProtectable()); EXPECT_FALSE(device.IsProtectable());
device.SetProtectable(true); device.SetProtectable(true);
@ -69,31 +53,50 @@ TEST(DeviceTest, Properties)
device.SetLocked(true); device.SetLocked(true);
EXPECT_TRUE(device.IsLocked()); EXPECT_TRUE(device.IsLocked());
device.SetId(ID); EXPECT_FALSE(device.SupportsParams());
EXPECT_EQ(ID, device.GetId()); EXPECT_TRUE(device.SupportsFile());
device.SupportsParams(true);
EXPECT_TRUE(device.SupportsParams());
EXPECT_FALSE(device.SupportsFile());
device.SetLun(LUN);
EXPECT_EQ(LUN, device.GetLun()); 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(""), invalid_argument);
EXPECT_THROW(device.SetVendor("123456789"), illegal_argument_exception); EXPECT_THROW(device.SetVendor("123456789"), invalid_argument);
device.SetVendor("12345678"); device.SetVendor("12345678");
EXPECT_EQ("12345678", device.GetVendor()); EXPECT_EQ("12345678", device.GetVendor());
}
EXPECT_THROW(device.SetProduct(""), illegal_argument_exception); TEST(DeviceTest, Product)
EXPECT_THROW(device.SetProduct("12345678901234567"), illegal_argument_exception); {
MockDevice device(0);
EXPECT_THROW(device.SetProduct(""), invalid_argument);
EXPECT_THROW(device.SetProduct("12345678901234567"), invalid_argument);
device.SetProduct("1234567890123456"); device.SetProduct("1234567890123456");
EXPECT_EQ("1234567890123456", device.GetProduct()); 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); TEST(DeviceTest, Revision)
EXPECT_THROW(device.SetRevision("12345"), illegal_argument_exception); {
MockDevice device(0);
EXPECT_THROW(device.SetRevision(""), invalid_argument);
EXPECT_THROW(device.SetRevision("12345"), invalid_argument);
device.SetRevision("1234"); device.SetRevision("1234");
EXPECT_EQ("1234", device.GetRevision()); EXPECT_EQ("1234", device.GetRevision());
}
TEST(DeviceTest, GetPaddedName)
{
MockDevice device(0);
device.SetVendor("V"); device.SetVendor("V");
device.SetProduct("P"); device.SetProduct("P");
@ -104,7 +107,7 @@ TEST(DeviceTest, ProductData)
TEST(DeviceTest, Params) TEST(DeviceTest, Params)
{ {
TestDevice device; MockDevice device(0);
unordered_map<string, string> params; unordered_map<string, string> params;
params["key"] = "value"; params["key"] = "value";
@ -124,23 +127,15 @@ TEST(DeviceTest, Params)
TEST(DeviceTest, StatusCode) TEST(DeviceTest, StatusCode)
{ {
TestDevice device; MockDevice device(0);
device.SetStatusCode(123); device.SetStatusCode(123);
EXPECT_EQ(123, device.GetStatusCode()); EXPECT_EQ(123, device.GetStatusCode());
} }
TEST(DeviceTest, Init)
{
TestDevice device;
unordered_map<string, string> params;
EXPECT_TRUE(device.Init(params));
}
TEST(DeviceTest, Reset) TEST(DeviceTest, Reset)
{ {
TestDevice device; MockDevice device(0);
device.SetLocked(true); device.SetLocked(true);
device.SetAttn(true); device.SetAttn(true);
@ -153,7 +148,7 @@ TEST(DeviceTest, Reset)
TEST(DeviceTest, Start) TEST(DeviceTest, Start)
{ {
TestDevice device; MockDevice device(0);
device.SetStopped(true); device.SetStopped(true);
device.SetReady(false); device.SetReady(false);
@ -166,7 +161,7 @@ TEST(DeviceTest, Start)
TEST(DeviceTest, Stop) TEST(DeviceTest, Stop)
{ {
TestDevice device; MockDevice device(0);
device.SetReady(true); device.SetReady(true);
device.SetAttn(true); device.SetAttn(true);
@ -179,7 +174,7 @@ TEST(DeviceTest, Stop)
TEST(DeviceTest, Eject) TEST(DeviceTest, Eject)
{ {
TestDevice device; MockDevice device(0);
device.SetReady(false); device.SetReady(false);
device.SetRemovable(false); device.SetRemovable(false);

View File

@ -7,151 +7,169 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "scsi.h"
#include "devices/disk.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
using namespace scsi_defs;
TEST(DiskTest, Dispatch)
{
MockAbstractController controller(0);
const unordered_set<uint32_t> sector_sizes;
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
controller.InitCmd(6);
disk->MediumChanged();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
}
TEST(DiskTest, Rezero) TEST(DiskTest, Rezero)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
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"; << "REZERO must fail because drive is not ready";
disk.SetReady(true); disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRezero)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, FormatUnit) TEST(DiskTest, FormatUnit)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6); vector<int>& 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_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdFormat)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdFormat));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x10; cmd[1] = 0x10;
cmd[4] = 1; 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) TEST(DiskTest, ReassignBlocks)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
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"; << "REASSIGN must fail because drive is not ready";
disk.SetReady(true); disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReassign)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReassign));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, Seek) TEST(DiskTest, Seek)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(10); vector<int>& 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"; << "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"; << "SEEK(10) must fail for a medium with 0 blocks";
disk.SetBlockCount(1); disk->SetBlockCount(1);
EXPECT_THROW(disk.Dispatch(scsi_command::eCmdSeek6), scsi_error_exception) EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_error_exception)
<< "SEEK(6) must fail because drive is not ready"; << "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"; << "SEEK(10) must fail because drive is not ready";
disk.SetReady(true); disk->SetReady(true);
// Block count for SEEK(6) // Block count for SEEK(6)
cmd[4] = 1; cmd[4] = 1;
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSeek6)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[4] = 0; cmd[4] = 0;
// Block count for SEEK(10) // Block count for SEEK(10)
cmd[5] = 1; cmd[5] = 1;
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSeek10)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, ReadCapacity) TEST(DiskTest, ReadCapacity)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(16); vector<int>& 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)"; << "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(10) must fail because drive is not ready";
// READ CAPACITY(16), not READ LONG(16) // READ CAPACITY(16), not READ LONG(16)
cmd[1] = 0x10; 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"; << "READ CAPACITY(16) must fail because drive is not ready";
cmd[1] = 0x00; cmd[1] = 0x00;
disk.SetReady(true); disk->SetReady(true);
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 the medium has no capacity"; << "READ CAPACITY(10) must fail because the medium has no capacity";
// READ CAPACITY(16), not READ LONG(16) // READ CAPACITY(16), not READ LONG(16)
cmd[1] = 0x10; 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"; << "READ CAPACITY(16) must fail because the medium has no capacity";
cmd[1] = 0x00; cmd[1] = 0x00;
disk.SetBlockCount(0x12345678); disk->SetBlockCount(0x12345678);
EXPECT_CALL(controller, DataIn()).Times(1); 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(0x12, controller.GetBuffer()[0]);
EXPECT_EQ(0x34, controller.GetBuffer()[1]); EXPECT_EQ(0x34, controller.GetBuffer()[1]);
EXPECT_EQ(0x56, controller.GetBuffer()[2]); EXPECT_EQ(0x56, controller.GetBuffer()[2]);
EXPECT_EQ(0x77, controller.GetBuffer()[3]); EXPECT_EQ(0x77, controller.GetBuffer()[3]);
disk.SetBlockCount(0x1234567887654321); disk->SetBlockCount(0x1234567887654321);
EXPECT_CALL(controller, DataIn()).Times(1); 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()[0]);
EXPECT_EQ(0xff, controller.GetBuffer()[1]); EXPECT_EQ(0xff, controller.GetBuffer()[1]);
EXPECT_EQ(0xff, controller.GetBuffer()[2]); EXPECT_EQ(0xff, controller.GetBuffer()[2]);
EXPECT_EQ(0xff, controller.GetBuffer()[3]); EXPECT_EQ(0xff, controller.GetBuffer()[3]);
disk.SetSectorSizeInBytes(1024); disk->SetSectorSizeInBytes(1024);
// READ CAPACITY(16), not READ LONG(16) // READ CAPACITY(16), not READ LONG(16)
cmd[1] = 0x10; cmd[1] = 0x10;
EXPECT_CALL(controller, DataIn()).Times(1); 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(0x12, controller.GetBuffer()[0]);
EXPECT_EQ(0x34, controller.GetBuffer()[1]); EXPECT_EQ(0x34, controller.GetBuffer()[1]);
EXPECT_EQ(0x56, controller.GetBuffer()[2]); EXPECT_EQ(0x56, controller.GetBuffer()[2]);
@ -168,172 +186,172 @@ TEST(DiskTest, ReadCapacity)
TEST(DiskTest, ReadWriteLong) TEST(DiskTest, ReadWriteLong)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(16); vector<int>& cmd = controller.InitCmd(16);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadLong10)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdWriteLong10)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[2] = 1; 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"; << "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"; << "WRITE LONG(10) must fail because the capacity is exceeded";
// READ LONG(16), not READ CAPACITY(16) // READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11; 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"; << "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"; << "WRITE LONG(16) must fail because the capacity is exceeded";
cmd[2] = 0; cmd[2] = 0;
cmd[7] = 1; 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"; << "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"; << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length";
cmd[7] = 0; cmd[7] = 0;
// READ LONG(16), not READ CAPACITY(16) // READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11; cmd[1] = 0x11;
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x00; cmd[1] = 0x00;
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdWriteLong16)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong16));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[13] = 1; cmd[13] = 1;
// READ LONG(16), not READ CAPACITY(16) // READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11; 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"; << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length";
cmd[1] = 0x00; 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"; << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length";
} }
TEST(DiskTest, ReserveRelease) TEST(DiskTest, ReserveRelease)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
controller.InitCmd(6); controller.InitCmd(6);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReserve6)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReserve6));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRelease6)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRelease6));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, SendDiagnostic) TEST(DiskTest, SendDiagnostic)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6); vector<int>& cmd = controller.InitCmd(6);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSendDiag)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSendDiag));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x10; 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"; << "SEND DIAGNOSTIC must fail because PF bit is not supported";
cmd[1] = 0; cmd[1] = 0;
cmd[3] = 1; 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"; << "SEND DIAGNOSTIC must fail because parameter list is not supported";
cmd[3] = 0; cmd[3] = 0;
cmd[4] = 1; 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"; << "SEND DIAGNOSTIC must fail because parameter list is not supported";
} }
TEST(DiskTest, PreventAllowMediumRemoval) TEST(DiskTest, PreventAllowMediumRemoval)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6); vector<int>& 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"; << "REMOVAL must fail because drive is not ready";
disk.SetReady(true); disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRemoval)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_FALSE(disk.IsLocked()); EXPECT_FALSE(disk->IsLocked());
cmd[4] = 1; cmd[4] = 1;
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdRemoval)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_TRUE(disk.IsLocked()); EXPECT_TRUE(disk->IsLocked());
} }
TEST(DiskTest, SynchronizeCache) TEST(DiskTest, SynchronizeCache)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
controller.InitCmd(10); controller.InitCmd(10);
EXPECT_CALL(disk, FlushCache()).Times(1); EXPECT_CALL(*disk, FlushCache()).Times(1);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSynchronizeCache10)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache10));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
controller.InitCmd(16); controller.InitCmd(16);
EXPECT_CALL(disk, FlushCache()).Times(1); EXPECT_CALL(*disk, FlushCache()).Times(1);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdSynchronizeCache16)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, ReadDefectData) TEST(DiskTest, ReadDefectData)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockSCSIHD_NEC disk; auto disk = make_shared<MockDisk>();
controller.AddDevice(&disk); controller.AddDevice(disk);
controller.InitCmd(10); controller.InitCmd(10);
EXPECT_CALL(controller, DataIn()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(disk.Dispatch(scsi_command::eCmdReadDefectData10)); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(DiskTest, SectorSize) TEST(DiskTest, SectorSize)
{ {
MockSCSIHD_NEC disk; MockDisk disk;
unordered_set<uint32_t> sizes = { 1, 2, 3 }; unordered_set<uint32_t> sizes = { 512, 1024 };
disk.SetSectorSizes(sizes); disk.SetSectorSizes(sizes);
EXPECT_TRUE(disk.IsSectorSizeConfigurable()); EXPECT_TRUE(disk.IsSectorSizeConfigurable());
@ -375,7 +393,8 @@ TEST(DiskTest, SectorSize)
TEST(DiskTest, ConfiguredSectorSize) TEST(DiskTest, ConfiguredSectorSize)
{ {
DeviceFactory device_factory; DeviceFactory device_factory;
MockSCSIHD_NEC disk; const unordered_set<uint32_t> sector_sizes;
MockSCSIHD disk(0, sector_sizes, false);
EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512)); EXPECT_TRUE(disk.SetConfiguredSectorSize(device_factory, 512));
EXPECT_EQ(512, disk.GetConfiguredSectorSize()); EXPECT_EQ(512, disk.GetConfiguredSectorSize());
@ -386,7 +405,7 @@ TEST(DiskTest, ConfiguredSectorSize)
TEST(DiskTest, BlockCount) TEST(DiskTest, BlockCount)
{ {
MockSCSIHD_NEC disk; MockDisk disk;
disk.SetBlockCount(0x1234567887654321); disk.SetBlockCount(0x1234567887654321);
EXPECT_EQ(0x1234567887654321, disk.GetBlockCount()); EXPECT_EQ(0x1234567887654321, disk.GetBlockCount());

View File

@ -7,7 +7,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "devices/file_support.h" #include "devices/file_support.h"
class TestFileSupport : public FileSupport class TestFileSupport : public FileSupport

View File

@ -11,25 +11,102 @@
#include <gmock/gmock.h> #include <gmock/gmock.h>
#include "controllers/controller_manager.h" #include "controllers/scsi_controller.h"
#include "devices/device_factory.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "devices/scsihd.h" #include "devices/scsihd.h"
#include "devices/scsihd_nec.h" #include "devices/scsihd_nec.h"
#include "devices/scsicd.h" #include "devices/scsicd.h"
#include "devices/scsimo.h" #include "devices/scsimo.h"
#include "devices/host_services.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 class MockAbstractController final : public AbstractController //NOSONAR Having many fields/methods cannot be avoided
{ {
FRIEND_TEST(AbstractControllerTest, Reset); FRIEND_TEST(AbstractControllerTest, Reset);
FRIEND_TEST(AbstractControllerTest, SetPhase);
FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, ProcessPhase);
FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle);
FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId);
FRIEND_TEST(AbstractControllerTest, GetOpcode); FRIEND_TEST(AbstractControllerTest, GetOpcode);
FRIEND_TEST(AbstractControllerTest, GetLun); 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: public:
@ -48,36 +125,48 @@ public:
MOCK_METHOD(void, SetByteTransfer, (bool), (override)); MOCK_METHOD(void, SetByteTransfer, (bool), (override));
MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (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; ~MockAbstractController() override = default;
MockBus bus;
}; };
class MockScsiController final : public ScsiController class MockScsiController final : public ScsiController
{ {
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(PrimaryDeviceTest, 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: public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(void, Status, (), ()); MOCK_METHOD(void, Status, (), ());
MOCK_METHOD(void, DataIn, (), ()); MOCK_METHOD(void, DataIn, (), ());
MOCK_METHOD(void, DataOut, (), ()); 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 class MockPrimaryDevice final : public PrimaryDevice
@ -86,12 +175,15 @@ class MockPrimaryDevice final : public PrimaryDevice
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, RequestSense); FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, Inquiry); FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice);
public: public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const)); MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MockPrimaryDevice() : PrimaryDevice("test") {} explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {}
~MockPrimaryDevice() override = default; ~MockPrimaryDevice() override = default;
}; };
@ -99,12 +191,12 @@ class MockModePageDevice final : public ModePageDevice
{ {
FRIEND_TEST(ModePagesTest, ModePageDevice_AddModePages); FRIEND_TEST(ModePagesTest, ModePageDevice_AddModePages);
MockModePageDevice() : ModePageDevice("test") {} explicit MockModePageDevice(int lun) : ModePageDevice("test", lun) {}
~MockModePageDevice() override = default; ~MockModePageDevice() override = default;
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const)); MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&, int), (const override)); MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&), (const override));
MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&, int), (const override)); MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&), (const override));
void SetUpModePages(map<int, vector<byte>>& pages, int page, bool) const override { void SetUpModePages(map<int, vector<byte>>& pages, int page, bool) const override {
// Return dummy data for other pages than page 0 // 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<uint32_t>& 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, Rezero);
FRIEND_TEST(DiskTest, FormatUnit); FRIEND_TEST(DiskTest, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks); 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, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData); FRIEND_TEST(DiskTest, ReadDefectData);
FRIEND_TEST(DiskTest, SectorSize); FRIEND_TEST(DiskTest, SectorSize);
FRIEND_TEST(DiskTest, ConfiguredSectorSize);
FRIEND_TEST(DiskTest, BlockCount); FRIEND_TEST(DiskTest, BlockCount);
public:
MOCK_METHOD(vector<byte>, 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)); MOCK_METHOD(void, FlushCache, (), (override));
using SCSIHD_NEC::SCSIHD_NEC; using SCSIHD_NEC::SCSIHD_NEC;
@ -166,3 +270,14 @@ class MockHostServices final : public HostServices
using HostServices::HostServices; using HostServices::HostServices;
}; };
class MockCommandContext : public CommandContext
{
ProtobufSerializer s;
Localizer l;
public:
MockCommandContext() : CommandContext(s, l, STDOUT_FILENO, "") {}
~MockCommandContext() = default;
};

View File

@ -7,9 +7,10 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "devices/scsi_command_util.h" #include "devices/scsi_command_util.h"
#include "devices/scsihd.h" #include "devices/scsihd.h"
#include "devices/scsihd_nec.h" #include "devices/scsihd_nec.h"
@ -24,11 +25,12 @@ TEST(ModePagesTest, ModePageDevice_AddModePages)
vector<int> cdb(6); vector<int> cdb(6);
vector<BYTE> buf(512); vector<BYTE> buf(512);
MockModePageDevice device; MockModePageDevice device(0);
cdb[2] = 0x3f; cdb[2] = 0x3f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length was not limited"; EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, -1)) << "Negative maximum length must be rejected";
EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length was not limited"; 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; cdb[2] = 0x00;
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_error_exception) << "Data for non-existing mode page 0 were returned"; 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<int, vector<byte>> mode_pages; map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes; const unordered_set<uint32_t> sector_sizes;
MockSCSIHD device(sector_sizes); MockSCSIHD device(0, sector_sizes, false);
device.SetUpModePages(mode_pages, 0x3f, false); device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
@ -52,7 +54,7 @@ TEST(ModePagesTest, SCSIHD_SetUpModePages)
TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages) TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages)
{ {
map<int, vector<byte>> mode_pages; map<int, vector<byte>> mode_pages;
MockSCSIHD_NEC device; MockSCSIHD_NEC device(0);
device.SetUpModePages(mode_pages, 0x3f, false); device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
@ -67,7 +69,7 @@ TEST(ModePagesTest, SCSICD_SetUpModePages)
{ {
map<int, vector<byte>> mode_pages; map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes; const unordered_set<uint32_t> sector_sizes;
MockSCSICD device(sector_sizes); MockSCSICD device(0, sector_sizes);
device.SetUpModePages(mode_pages, 0x3f, false); device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages"; EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages";
@ -84,7 +86,7 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages)
{ {
map<int, vector<byte>> mode_pages; map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes; const unordered_set<uint32_t> sector_sizes;
MockSCSIMO device(sector_sizes); MockSCSIMO device(0, sector_sizes);
device.SetUpModePages(mode_pages, 0x3f, false); device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages"; EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages";
@ -98,11 +100,12 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages)
TEST(ModePagesTest, HostServices_SetUpModePages) TEST(ModePagesTest, HostServices_SetUpModePages)
{ {
MockBus bus;
ControllerManager controller_manager(bus);
MockHostServices device(0, controller_manager);
map<int, vector<byte>> mode_pages; map<int, vector<byte>> 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(1, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(10, mode_pages[32].size()); EXPECT_EQ(10, mode_pages[32].size());
} }

View File

@ -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());
}

View File

@ -7,101 +7,117 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "scsi.h"
#include "rascsi_exceptions.h" #include "rascsi_exceptions.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
using namespace scsi_defs;
TEST(PrimaryDeviceTest, GetId)
{
const int ID = 5;
MockAbstractController controller(ID);
auto device = make_shared<MockPrimaryDevice>(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) TEST(PrimaryDeviceTest, PhaseChange)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device; auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device); controller.AddDevice(device);
EXPECT_CALL(controller, Status()).Times(1); EXPECT_CALL(controller, Status()).Times(1);
device.EnterStatusPhase(); device->EnterStatusPhase();
EXPECT_CALL(controller, DataIn()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1);
device.EnterDataInPhase(); device->EnterDataInPhase();
EXPECT_CALL(controller, DataOut()).Times(1); EXPECT_CALL(controller, DataOut()).Times(1);
device.EnterDataOutPhase(); device->EnterDataOutPhase();
} }
TEST(PrimaryDeviceTest, TestUnitReady) TEST(PrimaryDeviceTest, TestUnitReady)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device; auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device); controller.AddDevice(device);
device.SetReset(true); device->SetReset(true);
device.SetAttn(true); device->SetAttn(true);
device.SetReady(false); device->SetReady(false);
EXPECT_CALL(controller, DataIn()).Times(0); 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_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->SetReset(true);
device.SetAttn(false); device->SetAttn(false);
EXPECT_CALL(controller, DataIn()).Times(0); 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);
device.SetAttn(true); device->SetAttn(true);
EXPECT_CALL(controller, DataIn()).Times(0); 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_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_CALL(controller, Status()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdTestUnitReady)); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(PrimaryDeviceTest, Inquiry) TEST(PrimaryDeviceTest, Inquiry)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device; auto device = make_shared<MockPrimaryDevice>(0);
device.SetController(&controller); device->SetController(&controller);
vector<int>& cmd = controller.InitCmd(6); vector<int>& cmd = controller.InitCmd(6);
// ALLOCATION LENGTH // ALLOCATION LENGTH
cmd[4] = 255; cmd[4] = 255;
ON_CALL(device, InquiryInternal()).WillByDefault([&device]() { ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device.HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); 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_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_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported";
EXPECT_TRUE(controller.AddDevice(&device)); EXPECT_TRUE(controller.AddDevice(device));
EXPECT_FALSE(controller.AddDevice(&device)) << "Duplicate LUN was not rejected"; EXPECT_FALSE(controller.AddDevice(device)) << "Duplicate LUN was not rejected";
EXPECT_CALL(device, InquiryInternal()).Times(1); EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).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(device_type::PROCESSOR, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x00, controller.GetBuffer()[1]) << "Device was not reported as non-removable"; 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::SPC_3, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level"; EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1f, controller.GetBuffer()[4]) << "Wrong additional data size"; EXPECT_EQ(0x1f, controller.GetBuffer()[4]) << "Wrong additional data size";
ON_CALL(device, InquiryInternal()).WillByDefault([&device]() { ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device.HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); 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_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(device_type::DIRECT_ACCESS, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x80, controller.GetBuffer()[1]) << "Device was not reported as removable"; EXPECT_EQ(0x80, controller.GetBuffer()[1]) << "Device was not reported as removable";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level"; EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
@ -110,41 +126,41 @@ TEST(PrimaryDeviceTest, Inquiry)
cmd[1] = 0x01; cmd[1] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0); 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; cmd[2] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0); 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[1] = 0x00;
cmd[2] = 0x00; cmd[2] = 0x00;
// ALLOCATION LENGTH // ALLOCATION LENGTH
cmd[4] = 1; cmd[4] = 1;
EXPECT_CALL(device, InquiryInternal()).Times(1); EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).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(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling"; EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling";
} }
TEST(PrimaryDeviceTest, RequestSense) TEST(PrimaryDeviceTest, RequestSense)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device; auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device); controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(6); vector<int>& cmd = controller.InitCmd(6);
// ALLOCATION LENGTH // ALLOCATION LENGTH
cmd[4] = 255; cmd[4] = 255;
device.SetReady(false); device->SetReady(false);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception); EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception);
device.SetReady(true); device->SetReady(true);
EXPECT_CALL(controller, DataIn()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdRequestSense)); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
EXPECT_EQ(0, controller.GetStatus()); EXPECT_EQ(status::GOOD, controller.GetStatus());
} }
TEST(PrimaryDeviceTest, ReportLuns) TEST(PrimaryDeviceTest, ReportLuns)
@ -152,15 +168,13 @@ TEST(PrimaryDeviceTest, ReportLuns)
const int LUN1 = 1; const int LUN1 = 1;
const int LUN2 = 4; const int LUN2 = 4;
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device1; auto device1 = make_shared<MockPrimaryDevice>(LUN1);
MockPrimaryDevice device2; auto device2 = make_shared<MockPrimaryDevice>(LUN2);
device1.SetLun(LUN1); controller.AddDevice(device1);
controller.AddDevice(&device1);
EXPECT_TRUE(controller.HasDeviceForLun(LUN1)); EXPECT_TRUE(controller.HasDeviceForLun(LUN1));
device2.SetLun(LUN2); controller.AddDevice(device2);
controller.AddDevice(&device2);
EXPECT_TRUE(controller.HasDeviceForLun(LUN2)); EXPECT_TRUE(controller.HasDeviceForLun(LUN2));
vector<int>& cmd = controller.InitCmd(10); vector<int>& cmd = controller.InitCmd(10);
@ -168,7 +182,7 @@ TEST(PrimaryDeviceTest, ReportLuns)
cmd[9] = 255; cmd[9] = 255;
EXPECT_CALL(controller, DataIn()).Times(1); EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device1.Dispatch(scsi_command::eCmdReportLuns)); EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns));
const vector<BYTE>& buffer = controller.GetBuffer(); const vector<BYTE>& buffer = controller.GetBuffer();
EXPECT_EQ(0x00, buffer[0]) << "Wrong data length"; EXPECT_EQ(0x00, buffer[0]) << "Wrong data length";
EXPECT_EQ(0x00, buffer[1]) << "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"; EXPECT_EQ(LUN2, buffer[23]) << "Wrong LUN2 number";
cmd[2] = 0x01; 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) TEST(PrimaryDeviceTest, UnknownCommand)
{ {
MockScsiController controller(nullptr, 0); MockAbstractController controller(0);
MockPrimaryDevice device; auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device); controller.AddDevice(device);
controller.InitCmd(1); controller.InitCmd(1);
EXPECT_FALSE(device.Dispatch((scsi_command)0xFF)); EXPECT_FALSE(device->Dispatch((scsi_command)0xFF));
}
TEST(PrimaryDeviceTest, WriteByteSequence)
{
vector<BYTE> data;
MockPrimaryDevice device(0);
EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device must not support writing byte sequences";
} }
TEST(PrimaryDeviceTest, GetSendDelay) TEST(PrimaryDeviceTest, GetSendDelay)
{ {
MockPrimaryDevice device; MockPrimaryDevice device(0);
EXPECT_EQ(-1, device.GetSendDelay()); EXPECT_EQ(-1, device.GetSendDelay());
} }
TEST(PrimaryDeviceTest, Init)
{
unordered_map<string, string> 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();
}

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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<int> 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<uint32_t> sizes;
auto disk = make_shared<MockSCSIHD>(0, sizes, false);
EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 512));
sizes.insert(512);
disk = make_shared<MockSCSIHD>(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<MockPrimaryDevice>(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<MockPrimaryDevice>(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));
}

View File

@ -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<int> 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]);
}

View File

@ -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);
}

View File

@ -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<PbOperationInfo>(rascsi_response.GetOperationInfo(pb_operation_info_result, 0));
EXPECT_EQ(PbOperation_ARRAYSIZE - 1, operation_info->operations_size());
}

View File

@ -7,7 +7,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "devices/scsi_command_util.h" #include "devices/scsi_command_util.h"
using namespace scsi_command_util; using namespace scsi_command_util;

View File

@ -7,12 +7,35 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "testing.h" #include "mocks.h"
#include "scsi.h"
#include "controllers/scsi_controller.h" #include "controllers/scsi_controller.h"
using namespace scsi_defs;
TEST(ScsiControllerTest, GetMaxLuns) TEST(ScsiControllerTest, GetMaxLuns)
{ {
MockScsiController controller(nullptr, 0); MockScsiController controller(0);
EXPECT_EQ(32, controller.GetMaxLuns()); EXPECT_EQ(32, controller.GetMaxLuns());
} }
TEST(ScsiControllerTest, RequestSense)
{
MockScsiController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
vector<int>& 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";
}

View File

@ -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);
}

View File

@ -11,14 +11,14 @@
#include "spdlog/spdlog.h" #include "spdlog/spdlog.h"
class Environment : public ::testing::Environment class Environment final : public ::testing::Environment
{ {
spdlog::level::level_enum log_level; spdlog::level::level_enum log_level;
public: public:
explicit Environment(spdlog::level::level_enum level) : log_level(level) {} explicit Environment(spdlog::level::level_enum level) : log_level(level) {}
~Environment() final = default; ~Environment() override = default;
void SetUp() override { spdlog::set_level(log_level); } void SetUp() override { spdlog::set_level(log_level); }
}; };