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 }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="tests/*" --define sonar.python.version=3
cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="**/test/**" --define sonar.cpd.exclusions="**test/**" --define sonar.python.version=3

View File

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

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

View File

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

View File

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

View File

@ -20,6 +20,6 @@
#define USE_SEL_EVENT_ENABLE // Check SEL signal by event
// This avoids an indefinite loop with warnings if there is no RaSCSI hardware
// and thus helps with running rasctl and unit test on x86 hardware.
#if defined(__x86_64__) || defined(__X86__) || !defined(__linux)
#if defined(__x86_64__) || defined(__X86__) || !defined(__linux__)
#undef USE_SEL_EVENT_ENABLE
#endif

View File

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

View File

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

View File

@ -15,17 +15,22 @@
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) {
controller = make_shared<ScsiController>(bus, device->GetId());
controllers[device->GetId()] = controller;
controller = make_shared<ScsiController>(bus, id);
controllers[id] = controller;
}
return controller->AddDevice(device);
}
void ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
{
controllers.erase(controller->GetTargetId());
}
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{
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;
}
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()
{
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) {
return controller->GetDeviceForLun(lun);

View File

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

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

View File

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

View File

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

View File

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

View File

@ -92,39 +92,14 @@ namespace Human68k {
};
/// Seek types
enum seek_t {
enum class seek_t {
SK_BEGIN = 0, ///< From the beginning of a file
SK_CURRENT = 1, ///< From the current location
SK_END = 2, ///< From the end of the file
};
/// Media byte
enum media_t {
MEDIA_2DD_10 = 0xE0, ///< 2DD/10 sector
MEDIA_1D_9 = 0xE5, ///< 1D/9 sector
MEDIA_2D_9 = 0xE6, ///< 2D/9 sector
MEDIA_1D_8 = 0xE7, ///< 1D/8 sector
MEDIA_2D_8 = 0xE8, ///< 2D/8 sector
MEDIA_2HT = 0xEA, ///< 2HT
MEDIA_2HS = 0xEB, ///< 2HS
MEDIA_2HDE = 0xEC, ///< 2DDE
MEDIA_1DD_9 = 0xEE, ///< 1DD/9 sector
MEDIA_1DD_8 = 0xEF, ///< 1DD/8 sector
MEDIA_MANUAL = 0xF1, ///< Remote drive (manual eject)
MEDIA_REMOVABLE = 0xF2, ///< Remote drive (removable)
MEDIA_REMOTE = 0xF3, ///< Remote drive
MEDIA_DAT = 0xF4, ///< SCSI-DAT
MEDIA_CDROM = 0xF5, ///< SCSI-CDROM
MEDIA_MO = 0xF6, ///< SCSI-MO
MEDIA_SCSI_HD = 0xF7, ///< SCSI-HD
MEDIA_SASI_HD = 0xF8, ///< SASI-HD
MEDIA_RAMDISK = 0xF9, ///< RAM disk
MEDIA_2HQ = 0xFA, ///< 2HQ
MEDIA_2DD_8 = 0xFB, ///< 2DD/8 sector
MEDIA_2DD_9 = 0xFC, ///< 2DD/9 sector
MEDIA_2HC = 0xFD, ///< 2HC
MEDIA_2HD = 0xFE, ///< 2HD
};
// Media byte
const static int MEDIA_REMOTE = 0xF3; ///< Remote drive
struct namests_t {
BYTE wildcard; ///< Wildcard character length
@ -338,11 +313,7 @@ Normal is 0. Becomes 1 if attempting to mount in read-only mode.
Reserving the other values for future use.
Insurance against hard-to-detect devices such as homemade USB storage.
*/
enum {
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
};
static const DWORD FSFLAG_WRITE_PROTECT = 0x00000001; ///< Bit0: Force write protect
//===========================================================================
//
@ -357,8 +328,8 @@ class CRing {
public:
CRing() { Init(); }
~CRing() { Remove(); }
CRing(CRing&) = delete;
CRing& operator=(const CRing&) = delete;
CRing(CRing&) = default;
CRing& operator=(const CRing&) = default;
void Init() { next = prev = this; }
@ -442,8 +413,6 @@ class CHostFilename {
public:
CHostFilename() = default;
~CHostFilename() = default;
CHostFilename(CHostFilename&) = delete;
CHostFilename& operator=(const CHostFilename&) = delete;
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
@ -532,8 +501,8 @@ public:
CHostPath() = default;
~CHostPath();
CHostPath(CHostPath&) = delete;
CHostPath& operator=(const CHostPath&) = delete;
CHostPath(CHostPath&) = default;
CHostPath& operator=(const CHostPath&) = default;
void Clean(); ///< Initialialize for reuse
@ -596,8 +565,6 @@ class CHostFiles {
public:
CHostFiles() = default;
~CHostFiles() = default;
CHostFiles(CHostFiles&) = delete;
CHostFiles& operator=(const CHostFiles&) = delete;
void Init();
@ -675,8 +642,8 @@ class CHostFcb {
public:
CHostFcb() = default;
~CHostFcb() { Close(); }
CHostFcb(CHostFcb&) = delete;
CHostFcb& operator=(const CHostFcb&) = delete;
CHostFcb(CHostFcb&) = default;
CHostFcb& operator=(const CHostFcb&) = default;
void Init();
@ -694,7 +661,7 @@ public:
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
bool Truncate() const; ///< Truncate file
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
DWORD Seek(DWORD nOffset, Human68k::seek_t nHumanSeek); ///< Seek file
bool TimeStamp(DWORD nHumanTime) const; ///< Set file time stamp
void Close(); ///< Close file
@ -747,8 +714,8 @@ class CHostDrv
public:
CHostDrv() = default;
~CHostDrv();
CHostDrv(CHostDrv&) = delete;
CHostDrv& operator=(const CHostDrv&) = delete;
CHostDrv(CHostDrv&) = default;
CHostDrv& operator=(const CHostDrv&) = default;
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
@ -811,8 +778,8 @@ public:
CHostEntry() = default;
~CHostEntry();
CHostEntry(CHostEntry&) = delete;
CHostEntry& operator=(const CHostEntry&) = delete;
CHostEntry(CHostEntry&) = default;
CHostEntry& operator=(const CHostEntry&) = default;
void Init() const; ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)

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) {
#ifndef __linux
#ifndef __linux__
return false;
#else
ifreq ifr;
@ -83,7 +83,7 @@ CTapDriver::~CTapDriver()
}
static bool ip_link(int fd, const char* ifname, bool up) {
#ifndef __linux
#ifndef __linux__
return false;
#else
ifreq ifr;
@ -126,7 +126,7 @@ static bool is_interface_up(string_view interface) {
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
{
#ifndef __linux
#ifndef __linux__
return false;
#else
unordered_map<string, string> params = const_params;

View File

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

View File

@ -7,17 +7,16 @@
//
//---------------------------------------------------------------------------
#include <cassert>
#include "rascsi_version.h"
#include "log.h"
#include "rascsi_exceptions.h"
#include "device.h"
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace std;
Device::Device(const string& t) : type(t)
Device::Device(const string& type, int lun) : type(type), lun(lun)
{
assert(type.length() == 4);
@ -43,7 +42,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v)
{
if (v.empty() || v.length() > 8) {
throw illegal_argument_exception("Vendor '" + v + "' must be between 1 and 8 characters");
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
}
vendor = v;
@ -52,10 +51,10 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force)
{
if (p.empty() || p.length() > 16) {
throw illegal_argument_exception("Product '" + p + "' must be between 1 and 16 characters");
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
}
// Changing the device name is not SCSI compliant
// Changing vital product data is not SCSI compliant
if (!product.empty() && !force) {
return;
}
@ -66,7 +65,7 @@ void Device::SetProduct(const string& p, bool force)
void Device::SetRevision(const string& r)
{
if (r.empty() || r.length() > 4) {
throw illegal_argument_exception("Revision '" + r + "' must be between 1 and 4 characters");
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
}
revision = r;
@ -74,13 +73,10 @@ void Device::SetRevision(const string& r)
string Device::GetPaddedName() const
{
string name = vendor;
name.append(8 - vendor.length(), ' ');
name += product;
name.append(16 - product.length(), ' ');
name += revision;
name.append(4 - revision.length(), ' ');
ostringstream os;
os << left << setfill(' ') << setw(8) << vendor << setw(16) << product << setw(4) << revision;
string name = os.str();
assert(name.length() == 28);
return name;
@ -107,15 +103,6 @@ void Device::SetParams(const unordered_map<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()
{
if (!ready) {

View File

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

View File

@ -25,8 +25,6 @@
using namespace std;
using namespace rascsi_interface;
multimap<int, unique_ptr<PrimaryDevice>> DeviceFactory::devices;
DeviceFactory::DeviceFactory()
{
sector_sizes[SCHD] = { 512, 1024, 2048, 4096 };
@ -60,45 +58,6 @@ DeviceFactory::DeviceFactory()
extension_mapping["iso"] = SCCD;
}
void DeviceFactory::DeleteDevice(const PrimaryDevice& device) const
{
auto [begin, end] = devices.equal_range(device.GetId());
for (auto& it = begin; it != end; ++it) {
if (it->second->GetLun() == device.GetLun()) {
devices.erase(it);
break;
}
}
}
void DeviceFactory::DeleteAllDevices() const
{
devices.clear();
}
const PrimaryDevice *DeviceFactory::GetDeviceByIdAndLun(int i, int lun) const
{
for (const auto& [id, device] : devices) {
if (device->GetId() == i && device->GetLun() == lun) {
return device.get();
}
}
return nullptr;
}
list<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 ext;
@ -132,7 +91,8 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
}
// ID -1 is used by rascsi to create a temporary device
PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename, int id)
shared_ptr<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 (type == UNDEFINED) {
@ -142,13 +102,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
}
}
unique_ptr<PrimaryDevice> device;
shared_ptr<PrimaryDevice> device;
switch (type) {
case SCHD: {
if (string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_unique<SCSIHD_NEC>();
device = make_shared<SCSIHD_NEC>(lun);
} 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
if (ext == "hda") {
@ -162,7 +123,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
}
case SCRM:
device = make_unique<SCSIHD>(sector_sizes[SCRM], true);
device = make_shared<SCSIHD>(lun, sector_sizes[SCRM], true);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -171,7 +132,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCMO:
device = make_unique<SCSIMO>(sector_sizes[SCMO]);
device = make_shared<SCSIMO>(lun, sector_sizes[SCMO]);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -180,7 +141,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCCD:
device = make_unique<SCSICD>(sector_sizes[SCCD]);
device = make_shared<SCSICD>(lun, sector_sizes[SCCD]);
device->SetReadOnly(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -189,14 +150,15 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCBR:
device = make_unique<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->SupportsParams(true);
device->SetDefaultParams(default_params[SCBR]);
break;
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
device->SetVendor("Dayna");
device->SetProduct("SCSI/Link");
@ -206,14 +168,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCHS:
device = make_unique<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
device->SetVendor("RaSCSI");
device->SetProduct("Host Services");
break;
case SCLP:
device = make_unique<SCSIPrinter>();
device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCLP]);
@ -223,17 +185,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
}
if (device != nullptr) {
PrimaryDevice *d = device.release();
d->SetId(id);
devices.emplace(id, d);
return d;
}
return nullptr;
return device;
}
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
@ -260,7 +212,7 @@ list<string> DeviceFactory::GetNetworkInterfaces() const
{
list<string> network_interfaces;
#ifdef __linux
#ifdef __linux__
ifaddrs *addrs;
getifaddrs(&addrs);
ifaddrs *tmp = addrs;

View File

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

View File

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

View File

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

View File

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

View File

@ -21,10 +21,8 @@
DiskTrack::~DiskTrack()
{
// Release memory, but do not save automatically
if (dt.buffer) {
free(dt.buffer);
}
}
void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
{

View File

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

View File

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

View File

@ -7,6 +7,7 @@
//
//---------------------------------------------------------------------------
#include "rascsi_exceptions.h"
#include "file_support.h"
using namespace std;
@ -39,3 +40,16 @@ void FileSupport::UnreserveAll()
{
reserved_files.clear();
}
bool FileSupport::FileExists(const Filepath& filepath)
{
try {
// Disk::Open closes the file in case it exists
Open(filepath);
}
catch(const file_not_found_exception&) {
return false;
}
return true;
}

View File

@ -15,7 +15,7 @@
#include <string>
#include "filepath.h"
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
using id_set = pair<int, int>;
@ -30,8 +30,6 @@ public:
FileSupport() = default;
virtual ~FileSupport() = default;
FileSupport(FileSupport&) = delete;
FileSupport& operator=(const FileSupport&) = delete;
void GetPath(Filepath& path) const { path = diskpath; }
void SetPath(const Filepath& path) { diskpath = path; }
@ -39,6 +37,7 @@ public:
void ReserveFile(const Filepath&, int, int) const;
void UnreserveFile() const;
static void UnreserveAll();
bool FileExists(const Filepath&);
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
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
//
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "rascsi_exceptions.h"
#include "device_factory.h"
#include "scsi_command_util.h"
#include "dispatcher.h"
#include "host_services.h"
@ -30,7 +31,8 @@
using namespace scsi_defs;
using namespace scsi_command_util;
HostServices::HostServices(const DeviceFactory& factory) : ModePageDevice("SCHS"), device_factory(factory)
HostServices::HostServices(int lun, const ControllerManager& manager)
: ModePageDevice("SCHS", lun), controller_manager(manager)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady);
dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit);
@ -60,40 +62,35 @@ void HostServices::StartStopUnit()
if (!start) {
// Flush any caches
for (PrimaryDevice *device : device_factory.GetAllDevices()) {
for (const auto& device : controller_manager.GetAllDevices()) {
device->FlushCache();
}
if (load) {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_PI);
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI);
}
else {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_RASCSI);
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI);
}
EnterStatusPhase();
return;
}
else if (load) {
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI);
}
else {
if (load) {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::RESTART_PI);
EnterStatusPhase();
return;
}
}
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
EnterStatusPhase();
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
auto length = (int)min((size_t)max_length, (size_t)cdb[4]);
auto length = (int)min(buf.size(), (size_t)cdb[4]);
fill_n(buf.begin(), length, 0);
// Basic Information
@ -114,14 +111,14 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_
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
if (!(cdb[1] & 0x08)) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
auto length = (int)min((size_t)max_length, (size_t)GetInt16(cdb, 7));
auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7));
fill_n(buf.begin(), length, 0);
// Basic Information

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
PrimaryDevice::PrimaryDevice(const string& id) : Device(id)
PrimaryDevice::PrimaryDevice(const string& type, int lun) : Device(type, lun)
{
// Mandatory SCSI primary commands
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady);
@ -33,6 +33,15 @@ bool PrimaryDevice::Dispatch(scsi_command cmd)
return dispatcher.Dispatch(this, cmd);
}
int PrimaryDevice::GetId() const
{
if (controller == nullptr) {
LOGERROR("Device is missing its controller")
}
return controller != nullptr ? controller->GetTargetId() : -1;
}
void PrimaryDevice::SetController(AbstractController *c)
{
controller = c;
@ -65,7 +74,7 @@ void PrimaryDevice::Inquiry()
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId())
// Signal that the requested LUN does not exist
controller->GetBuffer()[0] |= 0x7f;
controller->GetBuffer().data()[0] = 0x7f;
}
EnterDataInPhase();
@ -81,7 +90,7 @@ void PrimaryDevice::ReportLuns()
uint32_t allocation_length = GetInt32(ctrl->cmd, 6);
vector<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;
for (int lun = 0; lun < controller->GetMaxLuns(); lun++) {
@ -115,7 +124,7 @@ void PrimaryDevice::RequestSense()
// Do not raise an exception here because the rest of the code must be executed
controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
controller->SetStatus(0);
controller->SetStatus(status::GOOD);
}
vector<byte> buf = controller->GetDeviceForLun(lun)->HandleRequestSense();
@ -195,7 +204,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
buf[12] = (byte)(GetStatusCode() >> 8);
buf[13] = (byte)GetStatusCode();
LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, controller->GetStatus(),
LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, (int)controller->GetStatus(),
(int)buf[2], (int)buf[12])
return buf;

View File

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

View File

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

View File

@ -36,7 +36,7 @@ using namespace scsi_defs;
using namespace scsi_command_util;
// TODO Disk must not be the superclass
SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP")
SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
@ -406,9 +406,9 @@ void SCSIDaynaPort::Write6()
ctrl->length = GetInt16(ctrl->cmd, 3 + 8);
}
else {
LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, data_format)
LOGWARN("%s Unknown data format $%02X", __PRETTY_FUNCTION__, data_format)
}
LOGTRACE("%s length: %04X (%d) format: %02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
LOGTRACE("%s length: $%04X (%d) format: $%02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
if (ctrl->length <= 0) {
throw scsi_error_exception();

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ using namespace scsi_defs;
using namespace ras_util;
using namespace scsi_command_util;
SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP")
SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice("SCLP", lun)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady);
dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit);
@ -149,8 +149,8 @@ void SCSIPrinter::Print()
LOGTRACE("Receiving %d byte(s) to be printed", length)
if (length > controller->GetBufferSize()) {
LOGERROR("%s", string("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBufferSize()) +
if (length > controller->GetBuffer().size()) {
LOGERROR("%s", ("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected").c_str())
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,48 +9,21 @@
#include "rascsi_interface.pb.h"
#include "rascsi_exceptions.h"
#include "socket_connector.h"
#include "protobuf_serializer.h"
#include <unistd.h>
#include <netinet/in.h>
#include <sstream>
using namespace std;
using namespace rascsi_interface;
int SocketConnector::ReadCommand(PbCommand& command, int socket) const
{
// Wait for connection
sockaddr client = {};
socklen_t socklen = sizeof(client);
int fd = accept(socket, &client, &socklen);
if (fd < 0) {
throw io_exception("accept() failed");
}
// Read magic string
vector<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.
// 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;
message.SerializeToString(&data);
@ -67,7 +40,7 @@ void SocketConnector::SerializeMessage(int fd, const google::protobuf::Message&
}
}
void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& message) const
void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const
{
// Read the header with the size of the protobuf data
vector<byte> header_buf(4);
@ -91,7 +64,7 @@ void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& mess
message.ParseFromString(data);
}
size_t SocketConnector::ReadBytes(int fd, vector<byte>& buf) const
size_t ProtobufSerializer::ReadBytes(int fd, vector<byte>& buf) const
{
size_t offset = 0;
while (offset < buf.size()) {

View File

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

File diff suppressed because it is too large Load Diff

View File

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

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 <array>
#include <filesystem>
#ifdef __linux
#ifdef __linux__
#include <sys/sendfile.h>
#endif
@ -30,7 +30,7 @@ using namespace command_util;
RascsiImage::RascsiImage()
{
// ~/images is the default folder for device image files, for the root user it is /home/pi/images
default_image_folder = GetHomeDir() + "/images";
default_folder = GetHomeDir() + "/images";
}
bool RascsiImage::CheckDepth(string_view filename) const
@ -57,7 +57,7 @@ bool RascsiImage::CreateImageFolder(const CommandContext& context, const string&
return true;
}
string RascsiImage::SetDefaultImageFolder(const string& f)
string RascsiImage::SetDefaultFolder(const string& f)
{
if (f.empty()) {
return "Can't set default image folder: Missing folder name";
@ -81,9 +81,9 @@ string RascsiImage::SetDefaultImageFolder(const string& f)
return "Folder '" + f + "' does not exist or is not accessible";
}
default_image_folder = folder;
default_folder = folder;
LOGINFO("Default image folder set to '%s'", default_image_folder.c_str())
LOGINFO("Default image folder set to '%s'", default_folder.c_str())
return "";
}
@ -113,7 +113,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
string full_filename = default_image_folder + "/" + filename;
string full_filename = GetFullName(filename);
if (!IsValidDstFilename(full_filename)) {
return ReturnStatus(context, false, "Can't create image file: '" + full_filename + "': File already exists");
}
@ -127,10 +127,10 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
try {
len = stoull(size);
}
catch(const invalid_argument&) { //NOSONAR This exception is handled properly
catch(const invalid_argument&) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
}
catch(const out_of_range&) { //NOSONAR This exception is handled properly
catch(const out_of_range&) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
}
if (len < 512 || (len & 0x1ff)) {
@ -151,7 +151,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': " + string(strerror(errno)));
}
#ifndef __linux
#ifndef __linux__
close(image_fd);
unlink(full_filename.c_str());
@ -186,7 +186,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
string full_filename = default_image_folder + "/" + filename;
string full_filename = GetFullName(filename);
int id;
int unit;
@ -205,7 +205,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
size_t last_slash = filename.rfind('/');
while (last_slash != string::npos) {
string folder = filename.substr(0, last_slash);
string full_folder = default_image_folder + "/" + folder;
string full_folder = GetFullName(folder);
if (error_code error; !filesystem::is_empty(full_folder, error) || error) {
break;
@ -234,7 +234,7 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = default_image_folder + "/" + from;
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't rename/move image file: '" + from + "': Invalid name or type");
}
@ -248,7 +248,7 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = default_image_folder + "/" + to;
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': File already exists");
}
@ -277,7 +277,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = default_image_folder + "/" + from;
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't copy image file: '" + from + "': Invalid name or type");
}
@ -291,7 +291,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = default_image_folder + "/" + to;
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists");
}
@ -333,7 +333,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
return ReturnStatus(context, false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
}
#ifndef __linux
#ifndef __linux__
close(fd_dst);
close(fd_src);
@ -370,7 +370,7 @@ bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCom
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
filename = default_image_folder + "/" + filename;
filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) {
return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type");
}

View File

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

View File

@ -7,59 +7,57 @@
//
//---------------------------------------------------------------------------
#include <dirent.h>
#include "controllers/controller_manager.h"
#include "devices/file_support.h"
#include "devices/disk.h"
#include "devices/device_factory.h"
#include "command_util.h"
#include "rascsi_version.h"
#include "rascsi_interface.pb.h"
#include "rascsi_image.h"
#include "rascsi_response.h"
using namespace rascsi_interface;
using namespace command_util;
PbDeviceProperties *RascsiResponse::GetDeviceProperties(const Device *device)
unique_ptr<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_read_only(device->IsReadOnly());
properties->set_protectable(device->IsProtectable());
properties->set_stoppable(device->IsStoppable());
properties->set_removable(device->IsRemovable());
properties->set_lockable(device->IsLockable());
properties->set_supports_file(dynamic_cast<const FileSupport *>(device) != nullptr);
properties->set_supports_params(device->SupportsParams());
properties->set_luns(max_luns);
properties->set_read_only(device.IsReadOnly());
properties->set_protectable(device.IsProtectable());
properties->set_stoppable(device.IsStoppable());
properties->set_removable(device.IsRemovable());
properties->set_lockable(device.IsLockable());
properties->set_supports_file(dynamic_cast<const FileSupport *>(&device) != nullptr);
properties->set_supports_params(device.SupportsParams());
PbDeviceType t = UNDEFINED;
PbDeviceType_Parse(device->GetType(), &t);
PbDeviceType_Parse(device.GetType(), &t);
if (device->SupportsParams()) {
for (const auto& [key, value] : device_factory->GetDefaultParams(t)) {
if (device.SupportsParams()) {
for (const auto& [key, value] : device_factory.GetDefaultParams(t)) {
auto& map = *properties->mutable_default_params();
map[key] = value;
}
}
for (const auto& block_size : device_factory->GetSectorSizes(t)) {
for (const auto& block_size : device_factory.GetSectorSizes(t)) {
properties->add_block_sizes(block_size);
}
return properties;
}
void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type)
void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const
{
PbDeviceTypeProperties *type_properties = device_types_info.add_properties();
auto type_properties = device_types_info.add_properties();
type_properties->set_type(type);
const PrimaryDevice *device = device_factory->CreateDevice(type, "", -1);
type_properties->set_allocated_properties(GetDeviceProperties(device));
device_factory->DeleteDevice(*device); //NOSONAR The allocated memory is managed by protobuf
}
auto device = device_factory.CreateDevice(controller_manager, type, 0, "");
type_properties->set_allocated_properties(GetDeviceProperties(*device).release());
} //NOSONAR The allocated memory is managed by protobuf
void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info)
void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const
{
// Start with 2 instead of 1. 1 was the removed SASI drive type.
int ordinal = 2;
@ -71,60 +69,60 @@ void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_
}
}
void RascsiResponse::GetDevice(const Device *device, PbDevice *pb_device)
void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const string& default_folder) const
{
pb_device->set_id(device->GetId());
pb_device->set_unit(device->GetLun());
pb_device->set_vendor(device->GetVendor());
pb_device->set_product(device->GetProduct());
pb_device->set_revision(device->GetRevision());
pb_device.set_id(device.GetId());
pb_device.set_unit(device.GetLun());
pb_device.set_vendor(device.GetVendor());
pb_device.set_product(device.GetProduct());
pb_device.set_revision(device.GetRevision());
PbDeviceType type = UNDEFINED;
PbDeviceType_Parse(device->GetType(), &type);
pb_device->set_type(type);
PbDeviceType_Parse(device.GetType(), &type);
pb_device.set_type(type);
pb_device->set_allocated_properties(GetDeviceProperties(device));
pb_device.set_allocated_properties(GetDeviceProperties(device).release());
auto status = make_unique<PbDeviceStatus>().release();
pb_device->set_allocated_status(status);
status->set_protected_(device->IsProtected());
status->set_stopped(device->IsStopped());
status->set_removed(device->IsRemoved());
status->set_locked(device->IsLocked());
pb_device.set_allocated_status(status);
status->set_protected_(device.IsProtected());
status->set_stopped(device.IsStopped());
status->set_removed(device.IsRemoved());
status->set_locked(device.IsLocked());
if (device->SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf
for (const auto& [key, value] : device->GetParams()) {
AddParam(*pb_device, key, value);
if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf
for (const auto& [key, value] : device.GetParams()) {
AddParam(pb_device, key, value);
}
}
if (const auto disk = dynamic_cast<const Disk*>(device); disk) {
pb_device->set_block_size(device->IsRemoved()? 0 : disk->GetSectorSizeInBytes());
pb_device->set_block_count(device->IsRemoved() ? 0: disk->GetBlockCount());
if (const auto disk = dynamic_cast<const Disk*>(&device); disk) {
pb_device.set_block_size(device.IsRemoved()? 0 : disk->GetSectorSizeInBytes());
pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount());
}
const auto file_support = dynamic_cast<const FileSupport *>(device);
const auto file_support = dynamic_cast<const FileSupport *>(&device);
if (file_support) {
Filepath filepath;
file_support->GetPath(filepath);
auto image_file = make_unique<PbImageFile>().release();
GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath());
pb_device->set_allocated_file(image_file);
GetImageFile(*image_file, default_folder, device.IsRemovable() && !device.IsReady() ? "" : filepath.GetPath());
pb_device.set_allocated_file(image_file);
}
} //NOSONAR The allocated memory is managed by protobuf
bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filename) const
bool RascsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const
{
if (!filename.empty()) {
image_file->set_name(filename);
image_file->set_type(device_factory->GetTypeForFile(filename));
image_file.set_name(filename);
image_file.set_type(device_factory.GetTypeForFile(filename));
string f = filename[0] == '/' ? filename : rascsi_image->GetDefaultImageFolder() + "/" + filename;
string f = filename[0] == '/' ? filename : default_folder + "/" + filename;
image_file->set_read_only(access(f.c_str(), W_OK));
image_file.set_read_only(access(f.c_str(), W_OK));
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) {
image_file->set_size(st.st_size);
image_file.set_size(st.st_size);
return true;
}
}
@ -132,8 +130,9 @@ bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filenam
return false;
}
void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, string_view default_image_folder,
const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) {
void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder,
const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
if (scan_depth-- < 0) {
return;
}
@ -151,35 +150,29 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, stri
const dirent *dir;
while ((dir = readdir(d))) {
bool is_supported_type = dir->d_type == DT_REG || dir->d_type == DT_DIR || dir->d_type == DT_LNK || dir->d_type == DT_BLK;
if (is_supported_type && dir->d_name[0] != '.') {
string filename = GetNextImageFile(dir, folder);
if (filename.empty()) {
continue;
}
string name_lower = dir->d_name;
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
string filename = folder + "/" + dir->d_name;
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 (dir->d_type == DT_DIR) {
if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
GetAvailableImages(image_files_info, default_image_folder, filename, folder_pattern,
GetAvailableImages(image_files_info, default_folder, filename, folder_pattern,
file_pattern, scan_depth);
}
continue;
}
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(image_file.get(), filename)) {
GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1));
}
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(*image_file.get(), default_folder, filename)) {
GetImageFile(*image_files_info.add_image_files(), default_folder,
filename.substr(default_folder.length() + 1));
}
}
}
@ -187,16 +180,15 @@ void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, stri
closedir(d);
}
PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const string& folder_pattern,
const string& file_pattern, int scan_depth)
unique_ptr<PbImageFilesInfo> RascsiResponse::GetAvailableImages(PbResult& result, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
auto image_files_info = make_unique<PbImageFilesInfo>().release();
auto image_files_info = make_unique<PbImageFilesInfo>();
string default_image_folder = rascsi_image->GetDefaultImageFolder();
image_files_info->set_default_image_folder(default_image_folder);
image_files_info->set_default_image_folder(default_folder);
image_files_info->set_depth(scan_depth);
GetAvailableImages(*image_files_info, default_image_folder, default_image_folder, folder_pattern,
GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern,
file_pattern, scan_depth);
result.set_status(true);
@ -204,19 +196,19 @@ PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const str
return image_files_info;
}
void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& folder_pattern,
const string& file_pattern, int scan_depth)
void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
PbImageFilesInfo *image_files_info = GetAvailableImages(result, folder_pattern, file_pattern, scan_depth);
image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder());
server_info.set_allocated_image_files_info(image_files_info);
auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth);
image_files_info->set_default_image_folder(default_folder);
server_info.set_allocated_image_files_info(image_files_info.release());
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf
}
PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unordered_set<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) {
reserved_ids_info->add_ids(id);
}
@ -226,48 +218,52 @@ PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const unorde
return reserved_ids_info;
}
void RascsiResponse::GetDevices(PbServerInfo& server_info)
void RascsiResponse::GetDevices(PbServerInfo& server_info, const string& default_folder) const
{
for (const Device *device : device_factory->GetAllDevices()) {
for (const auto& device : controller_manager.GetAllDevices()) {
PbDevice *pb_device = server_info.mutable_devices_info()->add_devices();
GetDevice(device, pb_device);
GetDevice(*device, *pb_device, default_folder);
}
}
void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command)
void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command, const string& default_folder) const
{
set<id_set> id_sets;
const auto& devices = controller_manager.GetAllDevices();
// If no device list was provided in the command get information on all devices
if (!command.devices_size()) {
for (const Device *device : device_factory->GetAllDevices()) {
for (const auto& device : devices) {
id_sets.insert(make_pair(device->GetId(), device->GetLun()));
}
}
// Otherwise get information on the devices provided in the command
else {
for (const auto& device : command.devices()) {
if (device_factory->GetDeviceByIdAndLun(device.id(), device.unit())) {
id_sets.insert(make_pair(device.id(), device.unit()));
}
else {
result.set_status(false);
result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit()));
id_sets = MatchDevices(result, command);
if (id_sets.empty()) {
return;
}
}
}
auto devices_info = make_unique<PbDevicesInfo>().release();
result.set_allocated_devices_info(devices_info);
auto devices_info = make_unique<PbDevicesInfo>();
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);
}
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);
@ -276,29 +272,30 @@ PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result)
return device_types_info;
}
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)
unique_ptr<PbServerInfo> RascsiResponse::GetServerInfo(PbResult& result, const unordered_set<int>& reserved_ids,
const string& current_log_level, const string& default_folder, const string& folder_pattern,
const string& file_pattern, int scan_depth) const
{
auto server_info = make_unique<PbServerInfo>().release();
auto server_info = make_unique<PbServerInfo>();
server_info->set_allocated_version_info(GetVersionInfo(result));
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level)); //NOSONAR The allocated memory is managed by protobuf
server_info->set_allocated_version_info(GetVersionInfo(result).release());
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf
GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf
GetAvailableImages(result, *server_info, folder_pattern, file_pattern, scan_depth);
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result));
server_info->set_allocated_mapping_info(GetMappingInfo(result)); //NOSONAR The allocated memory is managed by protobuf
GetDevices(*server_info); //NOSONAR The allocated memory is managed by protobuf
server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids));
server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth)); //NOSONAR The allocated memory is managed by protobuf
GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth);
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release());
server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf
GetDevices(*server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf
server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release());
server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf
result.set_status(true);
return server_info;
}
PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result)
unique_ptr<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_minor_version(rascsi_minor_version);
@ -309,9 +306,9 @@ PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result)
return version_info;
}
PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level)
unique_ptr<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) {
log_level_info->add_log_levels(log_level);
@ -324,11 +321,11 @@ PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string&
return log_level_info;
}
PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& result)
unique_ptr<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);
}
@ -337,11 +334,11 @@ PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& resu
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;
}
@ -350,131 +347,147 @@ PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result)
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 = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required");
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device");
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list");
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge");
AddOperationParameter(*operation, "cmd", "Print command for the printer device");
AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds");
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release();
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release();
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release();
AddOperationParameter(*operation, "cmd", "Print command for the printer device").release();
AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds").release();
operation.release();
CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required");
CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release();
CreateOperation(*operation_info, DETACH_ALL, "Detach all devices");
CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release();
CreateOperation(*operation_info, START, "Start device, device-specific parameters are required");
CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release();
CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required");
CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release();
operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required");
CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release();
CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required");
CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release();
CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required");
CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release();
operation = CreateOperation(*operation_info, SERVER_INFO, "Get rascsi server information");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
CreateOperation(*operation_info, VERSION_INFO, "Get rascsi server version");
CreateOperation(*operation_info, VERSION_INFO, "Get rascsi server version").release();
CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices");
CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release();
CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type");
CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release();
operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information");
CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release();
CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces");
CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release();
CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types");
CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release();
CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release();
operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true);
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true);
AddOperationParameter(*operation, "level", "New log level", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true);
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot");
PbOperationParameter *parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true);
auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release();
parameter->add_permitted_values("rascsi");
// System shutdown/reboot requires root permissions
if (!getuid()) {
parameter->add_permitted_values("system");
parameter->add_permitted_values("reboot");
}
operation.release();
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true);
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true);
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release();
operation.release();
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data");
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release();
result.set_status(true);
return operation_info.release();
return operation_info;
}
PbOperationMetaData *RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
unique_ptr<PbOperationMetaData> RascsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
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_description(description);
int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index();
(*operation_info.mutable_operations())[ordinal] = *meta_data.get();
return &(*operation_info.mutable_operations())[ordinal];
(*operation_info.mutable_operations())[ordinal] = *meta_data.release();
return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]);
}
PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name,
const string& description, const string& default_value, bool is_mandatory)
unique_ptr<PbOperationParameter> RascsiResponse::AddOperationParameter(PbOperationMetaData& meta_data,
const string& name, const string& description, const string& default_value, bool is_mandatory) const
{
auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters());
parameter->set_name(name);
@ -482,5 +495,59 @@ PbOperationParameter *RascsiResponse::AddOperationParameter(PbOperationMetaData&
parameter->set_default_value(default_value);
parameter->set_is_mandatory(is_mandatory);
return parameter.release();
return parameter;
}
set<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
#include "devices/device_factory.h"
#include "rascsi_interface.pb.h"
#include <dirent.h>
#include <list>
#include <string>
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi
using namespace std;
using namespace rascsi_interface;
class DeviceFactory;
class RascsiImage;
class ControllerManager;
class Device;
class RascsiResponse
{
public:
RascsiResponse(DeviceFactory *device_factory, const RascsiImage *rascsi_image)
: device_factory(device_factory), rascsi_image(rascsi_image) {}
RascsiResponse(DeviceFactory& device_factory, const ControllerManager& controller_manager, int max_luns)
: device_factory(device_factory), controller_manager(controller_manager), max_luns(max_luns) {}
~RascsiResponse() = default;
RascsiResponse(RascsiResponse&) = delete;
RascsiResponse& operator=(const RascsiResponse&) = delete;
bool GetImageFile(PbImageFile *, const string&) const;
PbImageFilesInfo *GetAvailableImages(PbResult&, const string&, const string&, int);
PbReservedIdsInfo *GetReservedIds(PbResult&, const unordered_set<int>&);
void GetDevices(PbServerInfo&);
void GetDevicesInfo(PbResult&, const PbCommand&);
PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&);
PbVersionInfo *GetVersionInfo(PbResult&);
PbServerInfo *GetServerInfo(PbResult&, const unordered_set<int>&, const string&, const string&, const string&, int);
PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&);
PbMappingInfo *GetMappingInfo(PbResult&);
PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&);
PbOperationInfo *GetOperationInfo(PbResult&, int);
bool GetImageFile(PbImageFile&, const string&, const string&) const;
unique_ptr<PbImageFilesInfo> GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const;
unique_ptr<PbReservedIdsInfo> GetReservedIds(PbResult&, const unordered_set<int>&) const;
void GetDevices(PbServerInfo&, const string&) const;
void GetDevicesInfo(PbResult&, const PbCommand&, const string&) const;
unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const;
unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const;
unique_ptr<PbServerInfo> GetServerInfo(PbResult&, const unordered_set<int>&, const string&, const string&, const string&,
const string&, int) const;
unique_ptr<PbNetworkInterfacesInfo> GetNetworkInterfacesInfo(PbResult&) const;
unique_ptr<PbMappingInfo> GetMappingInfo(PbResult&) const;
unique_ptr<PbLogLevelInfo> GetLogLevelInfo(PbResult&, const string&) const;
unique_ptr<PbOperationInfo> GetOperationInfo(PbResult&, int) const;
private:
DeviceFactory *device_factory;
const RascsiImage *rascsi_image;
DeviceFactory& device_factory;
const ControllerManager& controller_manager;
int max_luns;
const list<string> log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" };
PbDeviceProperties *GetDeviceProperties(const Device *);
void GetDevice(const Device *, PbDevice *);
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&);
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType);
void GetAvailableImages(PbImageFilesInfo&, string_view, const string&, const string&, const string&, int);
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, int);
PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
PbOperationParameter *AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false);
unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
void GetDevice(const Device&, PbDevice&, const string&) const;
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const;
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const;
unique_ptr<PbOperationMetaData> CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
unique_ptr<PbOperationParameter> AddOperationParameter(PbOperationMetaData&, const string&, const string&,
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
const int rascsi_major_version = 22; // Last two digits of year
const int rascsi_minor_version = 9; // Month
const int rascsi_minor_version = 10; // Month
const int rascsi_patch_version = -1; // Patch number - increment for each update
using namespace std;

View File

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

View File

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

View File

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

View File

@ -22,7 +22,8 @@ bool ras_util::GetAsInt(const string& value, int& result)
}
try {
result = (int)stoul(value);
auto v = stoul(value);
result = (int)v;
}
catch(const invalid_argument&) {
return false;
@ -46,7 +47,7 @@ string ras_util::ListDevices(const list<PbDevice>& pb_devices)
<< "+----+-----+------+-------------------------------------" << endl;
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) {
string filename;
@ -82,3 +83,22 @@ string ras_util::ListDevices(const list<PbDevice>& pb_devices)
return s.str();
}
// Pin the thread to a specific CPU
void ras_util::FixCpu(int cpu)
{
#ifdef __linux__
// Get the number of CPUs
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
sched_getaffinity(0, sizeof(cpu_set_t), &cpuset);
int cpus = CPU_COUNT(&cpuset);
// Set the thread affinity
if (cpu < cpus) {
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
}
#endif
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,31 +7,56 @@
//
//---------------------------------------------------------------------------
#include "testing.h"
#include "mocks.h"
#include "devices/device_factory.h"
#include "controllers/controller_manager.h"
TEST(ControllerManagerTest, ControllerManager)
TEST(ControllerManagerTest, LifeCycle)
{
const int ID = 4;
const int LUN = 6;
const int LUN1 = 2;
const int LUN2 = 3;
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
ControllerManager controller_manager;
auto device = device_factory.CreateDevice(UNDEFINED, "services", ID);
device->SetId(ID);
device->SetLun(LUN);
controller_manager.CreateScsiController(nullptr, device);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services");
controller_manager.AttachToScsiController(ID, device);
auto controller = controller_manager.FindController(ID);
EXPECT_NE(nullptr, controller);
EXPECT_EQ(1, controller->GetLunCount());
EXPECT_NE(nullptr, controller_manager.IdentifyController(1 << ID));
EXPECT_EQ(nullptr, controller_manager.IdentifyController(0));
EXPECT_NE(nullptr, controller_manager.FindController(ID));
EXPECT_EQ(nullptr, controller_manager.FindController(0));
EXPECT_EQ(device, controller_manager.GetDeviceByIdAndLun(ID, LUN));
EXPECT_NE(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1));
EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(0, 0));
controller_manager.DeleteAllControllers();
device_factory.DeleteAllDevices();
device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services");
controller_manager.AttachToScsiController(ID, device);
controller = controller_manager.FindController(ID);
controller_manager.DeleteController(controller);
EXPECT_EQ(nullptr, controller_manager.FindController(ID));
EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN));
controller_manager.DeleteAllControllers();
EXPECT_EQ(nullptr, controller_manager.FindController(ID));
EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1));
}
TEST(ControllerManagerTest, ResetAllControllers)
{
const int ID = 2;
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
controller_manager.AttachToScsiController(ID, device);
auto controller = controller_manager.FindController(ID);
EXPECT_NE(nullptr, controller);
controller->SetStatus(status::BUSY);
controller_manager.ResetAllControllers();
EXPECT_EQ(status::GOOD, controller->GetStatus());
}

View File

@ -7,9 +7,10 @@
//
//---------------------------------------------------------------------------
#include "testing.h"
#include "mocks.h"
#include "rascsi_exceptions.h"
#include "rascsi_version.h"
#include "controllers/controller_manager.h"
#include "devices/device.h"
#include "devices/device_factory.h"
#include <unordered_map>
@ -37,27 +38,6 @@ TEST(DeviceFactoryTest, GetTypeForFile)
EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED);
}
TEST(DeviceFactoryTest, LifeCycle)
{
DeviceFactory device_factory;
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "services", -1);
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHS", device->GetType());
list<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)
{
DeviceFactory device_factory;
@ -150,23 +130,27 @@ TEST(DeviceFactoryTest, GetDefaultParams)
TEST(DeviceFactoryTest, UnknownDeviceType)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device1 = device_factory.CreateDevice(UNDEFINED, "test", -1);
auto device1 = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test");
EXPECT_EQ(nullptr, device1);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
PrimaryDevice *device2 = device_factory.CreateDevice(SAHD, "test", -1);
auto device2 = device_factory.CreateDevice(controller_manager, SAHD, 0, "test");
#pragma GCC diagnostic pop
EXPECT_EQ(nullptr, device2);
}
TEST(DeviceFactoryTest, SCHD_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.hda", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hda");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
EXPECT_TRUE(device->SupportsFile());
@ -186,83 +170,64 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults)
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
device = device_factory.CreateDevice(UNDEFINED, "test.hds", -1);
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hds");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
device_factory.DeleteDevice(*device);
device = device_factory.CreateDevice(UNDEFINED, "test.hdi", -1);
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hdi");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
device_factory.DeleteDevice(*device);
device = device_factory.CreateDevice(UNDEFINED, "test.nhd", -1);
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.nhd");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
device_factory.DeleteDevice(*device);
}
void TestRemovableDrive(const string& type, const string& filename, const string& product)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, filename);
EXPECT_NE(nullptr, device);
EXPECT_EQ(type, device->GetType());
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ(product, device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
}
TEST(DeviceFactoryTest, SCRM_Device_Defaults)
{
DeviceFactory device_factory;
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.hdr", -1);
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCRM", device->GetType());
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI HD (REM.)", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
TestRemovableDrive("SCRM", "test.hdr", "SCSI HD (REM.)");
}
TEST(DeviceFactoryTest, SCMO_Device_Defaults)
{
DeviceFactory device_factory;
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.mos", -1);
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCMO", device->GetType());
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI MO", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
TestRemovableDrive("SCMO", "test.mos", "SCSI MO");
}
TEST(DeviceFactoryTest, SCCD_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "test.iso", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.iso");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCCD", device->GetType());
EXPECT_TRUE(device->SupportsFile());
@ -281,15 +246,15 @@ TEST(DeviceFactoryTest, SCCD_Device_Defaults)
EXPECT_EQ("SCSI CD-ROM", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
}
TEST(DeviceFactoryTest, SCBR_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "bridge", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "bridge");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCBR", device->GetType());
EXPECT_FALSE(device->SupportsFile());
@ -308,15 +273,15 @@ TEST(DeviceFactoryTest, SCBR_Device_Defaults)
EXPECT_EQ("RASCSI BRIDGE", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
}
TEST(DeviceFactoryTest, SCDP_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "daynaport", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "daynaport");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCDP", device->GetType());
EXPECT_FALSE(device->SupportsFile());
@ -334,15 +299,15 @@ TEST(DeviceFactoryTest, SCDP_Device_Defaults)
EXPECT_EQ("Dayna", device->GetVendor());
EXPECT_EQ("SCSI/Link", device->GetProduct());
EXPECT_EQ("1.4a", device->GetRevision());
device_factory.DeleteDevice(*device);
}
TEST(DeviceFactoryTest, SCHS_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "services", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHS", device->GetType());
EXPECT_FALSE(device->SupportsFile());
@ -361,15 +326,15 @@ TEST(DeviceFactoryTest, SCHS_Device_Defaults)
EXPECT_EQ("Host Services", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
}
TEST(DeviceFactoryTest, SCLP_Device_Defaults)
{
MockBus bus;
DeviceFactory device_factory;
ControllerManager controller_manager(bus);
PrimaryDevice *device = device_factory.CreateDevice(UNDEFINED, "printer", -1);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "printer");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCLP", device->GetType());
EXPECT_FALSE(device->SupportsFile());
@ -388,6 +353,4 @@ TEST(DeviceFactoryTest, SCLP_Device_Defaults)
EXPECT_EQ("SCSI PRINTER", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
device_factory.DeleteDevice(*device);
}

View File

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

View File

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

View File

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

View File

@ -11,25 +11,102 @@
#include <gmock/gmock.h>
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "controllers/scsi_controller.h"
#include "devices/primary_device.h"
#include "devices/scsihd.h"
#include "devices/scsihd_nec.h"
#include "devices/scsicd.h"
#include "devices/scsimo.h"
#include "devices/host_services.h"
#include "protobuf_serializer.h"
#include "command_context.h"
#include "localizer.h"
class MockBus final : public BUS //NOSONAR Having many fields/methods cannot be avoided
{
public:
MOCK_METHOD(bool, Init, (mode_e), (override));
MOCK_METHOD(void, Reset, (), (override));
MOCK_METHOD(void, Cleanup, (), (override));
MOCK_METHOD(bool, GetBSY, (), (const override));
MOCK_METHOD(void, SetBSY, (bool), (override));
MOCK_METHOD(bool, GetSEL, (), (const override));
MOCK_METHOD(void, SetSEL, (bool), (override));
MOCK_METHOD(bool, GetATN, (), (const override));
MOCK_METHOD(void, SetATN, (bool), (override));
MOCK_METHOD(bool, GetACK, (), (const override));
MOCK_METHOD(void, SetACK, (bool), (override));
MOCK_METHOD(bool, GetRST, (), (const override));
MOCK_METHOD(void, SetRST, (bool), (override));
MOCK_METHOD(bool, GetMSG, (), (const override));
MOCK_METHOD(void, SetMSG, (bool), (override));
MOCK_METHOD(bool, GetCD, (), (const override));
MOCK_METHOD(void, SetCD, (bool), (override));
MOCK_METHOD(bool, GetIO, (), (override));
MOCK_METHOD(void, SetIO, (bool), (override));
MOCK_METHOD(bool, GetREQ, (), (const override));
MOCK_METHOD(void, SetREQ, (bool), (override));
MOCK_METHOD(BYTE, GetDAT, (), (override));
MOCK_METHOD(void, SetDAT, (BYTE), (override));
MOCK_METHOD(bool, GetDP, (), (const override));
MOCK_METHOD(uint32_t, Acquire, (), (override));
MOCK_METHOD(int, CommandHandShake, (BYTE *), (override));
MOCK_METHOD(int, ReceiveHandShake, (BYTE *, int), (override));
MOCK_METHOD(int, SendHandShake, (BYTE *, int, int), (override));
MOCK_METHOD(bool, GetSignal, (int), (const override));
MOCK_METHOD(void, SetSignal, (int, bool), (override));
MockBus() = default;
~MockBus() override = default;
};
class MockPhaseHandler : public PhaseHandler
{
FRIEND_TEST(PhaseHandlerTest, Phases);
public:
MOCK_METHOD(BUS::phase_t, Process, (int), (override));
MOCK_METHOD(void, Status, (), ());
MOCK_METHOD(void, DataIn, (), ());
MOCK_METHOD(void, DataOut, (), ());
MOCK_METHOD(void, BusFree, (), ());
MOCK_METHOD(void, Selection, (), ());
MOCK_METHOD(void, Command, (), ());
MOCK_METHOD(void, MsgIn, (), ());
MOCK_METHOD(void, MsgOut, (), ());
using PhaseHandler::PhaseHandler;
};
class MockAbstractController final : public AbstractController //NOSONAR Having many fields/methods cannot be avoided
{
FRIEND_TEST(AbstractControllerTest, Reset);
FRIEND_TEST(AbstractControllerTest, SetPhase);
FRIEND_TEST(AbstractControllerTest, ProcessPhase);
FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle);
FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId);
FRIEND_TEST(AbstractControllerTest, GetOpcode);
FRIEND_TEST(AbstractControllerTest, GetLun);
FRIEND_TEST(AbstractControllerTest, Ctrl);
FRIEND_TEST(AbstractControllerTest, Length);
FRIEND_TEST(AbstractControllerTest, Offset);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, ReportLuns);
FRIEND_TEST(PrimaryDeviceTest, UnknownCommand);
FRIEND_TEST(DiskTest, Dispatch);
FRIEND_TEST(DiskTest, Rezero);
FRIEND_TEST(DiskTest, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks);
FRIEND_TEST(DiskTest, Seek);
FRIEND_TEST(DiskTest, ReadCapacity);
FRIEND_TEST(DiskTest, ReadWriteLong);
FRIEND_TEST(DiskTest, ReserveRelease);
FRIEND_TEST(DiskTest, SendDiagnostic);
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
FRIEND_TEST(DiskTest, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData);
public:
@ -48,36 +125,48 @@ public:
MOCK_METHOD(void, SetByteTransfer, (bool), (override));
MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override));
explicit MockAbstractController(int target_id) : AbstractController(nullptr, target_id, 32) {}
explicit MockAbstractController(int target_id) : AbstractController(bus, target_id, 32) {
AllocateBuffer(512);
}
~MockAbstractController() override = default;
MockBus bus;
};
class MockScsiController final : public ScsiController
{
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, ReportLuns);
FRIEND_TEST(PrimaryDeviceTest, UnknownCommand);
FRIEND_TEST(DiskTest, Rezero);
FRIEND_TEST(DiskTest, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks);
FRIEND_TEST(DiskTest, Seek);
FRIEND_TEST(DiskTest, ReadCapacity);
FRIEND_TEST(DiskTest, ReadWriteLong);
FRIEND_TEST(DiskTest, ReserveRelease);
FRIEND_TEST(DiskTest, SendDiagnostic);
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
FRIEND_TEST(DiskTest, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData);
public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(void, Status, (), ());
MOCK_METHOD(void, DataIn, (), ());
MOCK_METHOD(void, DataOut, (), ());
MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override));
using ScsiController::ScsiController;
explicit MockScsiController(int target_id) : ScsiController(bus, target_id) {}
~MockScsiController() override = default;
MockBus bus;
};
class MockDevice final : public Device
{
FRIEND_TEST(DeviceTest, Params);
FRIEND_TEST(DeviceTest, StatusCode);
FRIEND_TEST(DeviceTest, Reset);
FRIEND_TEST(DeviceTest, Start);
FRIEND_TEST(DeviceTest, Stop);
FRIEND_TEST(DeviceTest, Eject);
public:
MOCK_METHOD(int, GetId, (), (const));
explicit MockDevice(int lun) : Device("test", lun) {}
~MockDevice() override = default;
};
class MockPrimaryDevice final : public PrimaryDevice
@ -86,12 +175,15 @@ class MockPrimaryDevice final : public PrimaryDevice
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice);
public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MockPrimaryDevice() : PrimaryDevice("test") {}
explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {}
~MockPrimaryDevice() override = default;
};
@ -99,12 +191,12 @@ class MockModePageDevice final : public ModePageDevice
{
FRIEND_TEST(ModePagesTest, ModePageDevice_AddModePages);
MockModePageDevice() : ModePageDevice("test") {}
explicit MockModePageDevice(int lun) : ModePageDevice("test", lun) {}
~MockModePageDevice() override = default;
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&, int), (const override));
MOCK_METHOD(int, ModeSense10, (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>&), (const override));
void SetUpModePages(map<int, vector<byte>>& pages, int page, bool) const override {
// Return dummy data for other pages than page 0
@ -115,17 +207,8 @@ class MockModePageDevice final : public ModePageDevice
}
};
class MockSCSIHD final : public SCSIHD
class MockDisk final : public Disk
{
FRIEND_TEST(ModePagesTest, SCSIHD_SetUpModePages);
explicit MockSCSIHD(const unordered_set<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, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks);
@ -138,9 +221,30 @@ class MockSCSIHD_NEC final : public SCSIHD_NEC //NOSONAR Ignore inheritance hier
FRIEND_TEST(DiskTest, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData);
FRIEND_TEST(DiskTest, SectorSize);
FRIEND_TEST(DiskTest, ConfiguredSectorSize);
FRIEND_TEST(DiskTest, BlockCount);
public:
MOCK_METHOD(vector<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));
using SCSIHD_NEC::SCSIHD_NEC;
@ -166,3 +270,14 @@ class MockHostServices final : public HostServices
using HostServices::HostServices;
};
class MockCommandContext : public CommandContext
{
ProtobufSerializer s;
Localizer l;
public:
MockCommandContext() : CommandContext(s, l, STDOUT_FILENO, "") {}
~MockCommandContext() = default;
};

View File

@ -7,9 +7,10 @@
//
//---------------------------------------------------------------------------
#include "testing.h"
#include "mocks.h"
#include "spdlog/spdlog.h"
#include "rascsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "devices/scsi_command_util.h"
#include "devices/scsihd.h"
#include "devices/scsihd_nec.h"
@ -24,11 +25,12 @@ TEST(ModePagesTest, ModePageDevice_AddModePages)
vector<int> cdb(6);
vector<BYTE> buf(512);
MockModePageDevice device;
MockModePageDevice device(0);
cdb[2] = 0x3f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length was not limited";
EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length was not limited";
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, -1)) << "Negative maximum length must be rejected";
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length 0 must be rejected";
EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length 1 must be rejected";
cdb[2] = 0x00;
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_error_exception) << "Data for non-existing mode page 0 were returned";
@ -38,7 +40,7 @@ TEST(ModePagesTest, SCSIHD_SetUpModePages)
{
map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD device(sector_sizes);
MockSCSIHD device(0, sector_sizes, false);
device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
@ -52,7 +54,7 @@ TEST(ModePagesTest, SCSIHD_SetUpModePages)
TEST(ModePagesTest, SCSIHD_NEC_SetUpModePages)
{
map<int, vector<byte>> mode_pages;
MockSCSIHD_NEC device;
MockSCSIHD_NEC device(0);
device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
@ -67,7 +69,7 @@ TEST(ModePagesTest, SCSICD_SetUpModePages)
{
map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSICD device(sector_sizes);
MockSCSICD device(0, sector_sizes);
device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages";
@ -84,7 +86,7 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages)
{
map<int, vector<byte>> mode_pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIMO device(sector_sizes);
MockSCSIMO device(0, sector_sizes);
device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages";
@ -98,11 +100,12 @@ TEST(ModePagesTest, SCSIMO_SetUpModePages)
TEST(ModePagesTest, HostServices_SetUpModePages)
{
MockBus bus;
ControllerManager controller_manager(bus);
MockHostServices device(0, controller_manager);
map<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(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 "devices/primary_device.h"
#include "devices/device_factory.h"
using namespace scsi_defs;
TEST(PrimaryDeviceTest, GetId)
{
const int ID = 5;
MockAbstractController controller(ID);
auto device = make_shared<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)
{
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device;
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device);
controller.AddDevice(device);
EXPECT_CALL(controller, Status()).Times(1);
device.EnterStatusPhase();
device->EnterStatusPhase();
EXPECT_CALL(controller, DataIn()).Times(1);
device.EnterDataInPhase();
device->EnterDataInPhase();
EXPECT_CALL(controller, DataOut()).Times(1);
device.EnterDataOutPhase();
device->EnterDataOutPhase();
}
TEST(PrimaryDeviceTest, TestUnitReady)
{
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device;
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device);
controller.AddDevice(device);
device.SetReset(true);
device.SetAttn(true);
device.SetReady(false);
device->SetReset(true);
device->SetAttn(true);
device->SetReady(false);
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
device.SetReset(false);
device->SetReset(false);
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
device.SetReset(true);
device.SetAttn(false);
device->SetReset(true);
device->SetAttn(false);
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
device.SetReset(false);
device.SetAttn(true);
device->SetReset(false);
device->SetAttn(true);
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
device.SetAttn(false);
device->SetAttn(false);
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_error_exception);
device.SetReady(true);
device->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdTestUnitReady));
EXPECT_EQ(0, controller.GetStatus());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(PrimaryDeviceTest, Inquiry)
{
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device;
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
device.SetController(&controller);
device->SetController(&controller);
vector<int>& cmd = controller.InitCmd(6);
// ALLOCATION LENGTH
cmd[4] = 255;
ON_CALL(device, InquiryInternal()).WillByDefault([&device]() {
return device.HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
});
EXPECT_CALL(device, InquiryInternal()).Times(1);
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported";
EXPECT_TRUE(controller.AddDevice(&device));
EXPECT_FALSE(controller.AddDevice(&device)) << "Duplicate LUN was not rejected";
EXPECT_CALL(device, InquiryInternal()).Times(1);
EXPECT_TRUE(controller.AddDevice(device));
EXPECT_FALSE(controller.AddDevice(device)) << "Duplicate LUN was not rejected";
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(device_type::PROCESSOR, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x00, controller.GetBuffer()[1]) << "Device was not reported as non-removable";
EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1f, controller.GetBuffer()[4]) << "Wrong additional data size";
ON_CALL(device, InquiryInternal()).WillByDefault([&device]() {
return device.HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true);
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true);
});
EXPECT_CALL(device, InquiryInternal()).Times(1);
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x80, controller.GetBuffer()[1]) << "Device was not reported as removable";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
@ -110,41 +126,41 @@ TEST(PrimaryDeviceTest, Inquiry)
cmd[1] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "EVPD bit is not supported";
EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "EVPD bit is not supported";
cmd[2] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "PAGE CODE field is not supported";
EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_error_exception) << "PAGE CODE field is not supported";
cmd[1] = 0x00;
cmd[2] = 0x00;
// ALLOCATION LENGTH
cmd[4] = 1;
EXPECT_CALL(device, InquiryInternal()).Times(1);
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdInquiry));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling";
}
TEST(PrimaryDeviceTest, RequestSense)
{
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device;
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device);
controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(6);
// ALLOCATION LENGTH
cmd[4] = 255;
device.SetReady(false);
EXPECT_THROW(device.Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception);
device->SetReady(false);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_error_exception);
device.SetReady(true);
device->SetReady(true);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device.Dispatch(scsi_command::eCmdRequestSense));
EXPECT_EQ(0, controller.GetStatus());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(PrimaryDeviceTest, ReportLuns)
@ -152,15 +168,13 @@ TEST(PrimaryDeviceTest, ReportLuns)
const int LUN1 = 1;
const int LUN2 = 4;
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device1;
MockPrimaryDevice device2;
MockAbstractController controller(0);
auto device1 = make_shared<MockPrimaryDevice>(LUN1);
auto device2 = make_shared<MockPrimaryDevice>(LUN2);
device1.SetLun(LUN1);
controller.AddDevice(&device1);
controller.AddDevice(device1);
EXPECT_TRUE(controller.HasDeviceForLun(LUN1));
device2.SetLun(LUN2);
controller.AddDevice(&device2);
controller.AddDevice(device2);
EXPECT_TRUE(controller.HasDeviceForLun(LUN2));
vector<int>& cmd = controller.InitCmd(10);
@ -168,7 +182,7 @@ TEST(PrimaryDeviceTest, ReportLuns)
cmd[9] = 255;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_TRUE(device1.Dispatch(scsi_command::eCmdReportLuns));
EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns));
const vector<BYTE>& buffer = controller.GetBuffer();
EXPECT_EQ(0x00, buffer[0]) << "Wrong data length";
EXPECT_EQ(0x00, buffer[1]) << "Wrong data length";
@ -192,23 +206,47 @@ TEST(PrimaryDeviceTest, ReportLuns)
EXPECT_EQ(LUN2, buffer[23]) << "Wrong LUN2 number";
cmd[2] = 0x01;
EXPECT_THROW(device1.Dispatch(scsi_command::eCmdReportLuns), scsi_error_exception) << "Only SELECT REPORT mode 0 is supported";
EXPECT_THROW(device1->Dispatch(scsi_command::eCmdReportLuns), scsi_error_exception) << "Only SELECT REPORT mode 0 is supported";
}
TEST(PrimaryDeviceTest, UnknownCommand)
{
MockScsiController controller(nullptr, 0);
MockPrimaryDevice device;
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(&device);
controller.AddDevice(device);
controller.InitCmd(1);
EXPECT_FALSE(device.Dispatch((scsi_command)0xFF));
EXPECT_FALSE(device->Dispatch((scsi_command)0xFF));
}
TEST(PrimaryDeviceTest, WriteByteSequence)
{
vector<BYTE> data;
MockPrimaryDevice device(0);
EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device must not support writing byte sequences";
}
TEST(PrimaryDeviceTest, GetSendDelay)
{
MockPrimaryDevice device;
MockPrimaryDevice device(0);
EXPECT_EQ(-1, device.GetSendDelay());
}
TEST(PrimaryDeviceTest, Init)
{
unordered_map<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"
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"
using namespace scsi_defs;
TEST(ScsiControllerTest, GetMaxLuns)
{
MockScsiController controller(nullptr, 0);
MockScsiController controller(0);
EXPECT_EQ(32, controller.GetMaxLuns());
}
TEST(ScsiControllerTest, RequestSense)
{
MockScsiController controller(0);
auto device = make_shared<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"
class Environment : public ::testing::Environment
class Environment final : public ::testing::Environment
{
spdlog::level::level_enum log_level;
public:
explicit Environment(spdlog::level::level_enum level) : log_level(level) {}
~Environment() final = default;
~Environment() override = default;
void SetUp() override { spdlog::set_level(log_level); }
};