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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -21,13 +21,13 @@
#include "scsi_controller.h"
#include <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,13 +256,13 @@ void ScsiController::Execute()
}
}
PrimaryDevice *device = GetDeviceForLun(lun);
auto device = GetDeviceForLun(lun);
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
device->SetStatusCode(0);
}
try {
if (!device->Dispatch(GetOpcode())) {
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode())
@ -298,14 +296,14 @@ void ScsiController::Status()
SysTimer::SleepUsec(5);
}
LOGTRACE("%s Status Phase $%02X",__PRETTY_FUNCTION__, GetStatus())
LOGTRACE("%s Status Phase, status is $%02X",__PRETTY_FUNCTION__, (int)GetStatus())
SetPhase(BUS::phase_t::status);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(true);
bus->SetIO(true);
bus.SetMSG(false);
bus.SetCD(true);
bus.SetIO(true);
// Data transfer is 1 byte x 1 block
ResetOffset();
@ -326,9 +324,9 @@ void ScsiController::MsgIn()
SetPhase(BUS::phase_t::msgin);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(true);
bus.SetMSG(true);
bus.SetCD(true);
bus.SetIO(true);
// length, blocks are already set
assert(HasValidLength());
@ -357,9 +355,9 @@ void ScsiController::MsgOut()
SetPhase(BUS::phase_t::msgout);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(false);
bus.SetMSG(true);
bus.SetCD(true);
bus.SetIO(false);
// Data transfer is 1 byte x 1 block
ResetOffset();
@ -390,9 +388,9 @@ void ScsiController::DataIn()
SetPhase(BUS::phase_t::datain);
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(true);
bus.SetMSG(false);
bus.SetCD(false);
bus.SetIO(true);
// length, blocks are already set
assert(ctrl.blocks > 0);
@ -423,9 +421,9 @@ void ScsiController::DataOut()
SetPhase(BUS::phase_t::dataout);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(false);
bus.SetMSG(false);
bus.SetCD(false);
bus.SetIO(false);
ResetOffset();
return;
@ -437,12 +435,12 @@ void ScsiController::DataOut()
void ScsiController::Error(sense_key sense_key, asc asc, status status)
{
// Get bus information
bus->Acquire();
bus.Acquire();
// Reset check
if (bus->GetRST()) {
if (bus.GetRST()) {
Reset();
bus->Reset();
bus.Reset();
return;
}
@ -461,11 +459,14 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
}
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
// Set Sense Key and ASC for a subsequent REQUEST SENSE
LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X",
(int)sense_key << 16, (int)asc << 8, (int)asc & 0xff)
// Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8));
}
SetStatus((uint32_t)status);
SetStatus(status);
ctrl.message = 0x00;
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__)
@ -475,16 +476,16 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
void ScsiController::Send()
{
assert(!bus->GetREQ());
assert(bus->GetIO());
assert(!bus.GetREQ());
assert(bus.GetIO());
if (HasValidLength()) {
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(GetOffset()) + ", length "
+ to_string(ctrl.length)).c_str())
// TODO The delay has to be taken from ctrl.unit[lun], but as there are currently no Daynaport drivers for
// LUNs other than 0 this work-around works.
if (int len = bus->SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length,
if (int len = bus.SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length,
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != (int)ctrl.length) {
// If you cannot send all, move to status phase
@ -571,15 +572,15 @@ void ScsiController::Receive()
LOGTRACE("%s",__PRETTY_FUNCTION__)
// REQ is low
assert(!bus->GetREQ());
assert(!bus->GetIO());
assert(!bus.GetREQ());
assert(!bus.GetIO());
// Length != 0 if received
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (int len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length);
if (int len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != (int)ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len)
Error(sense_key::ABORTED_COMMAND);
@ -675,14 +676,14 @@ bool ScsiController::XferMsg(int msg)
void ScsiController::ReceiveBytes()
{
assert(!bus->GetREQ());
assert(!bus->GetIO());
assert(!bus.GetREQ());
assert(!bus.GetIO());
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (uint32_t len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length);
if (uint32_t len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",
__PRETTY_FUNCTION__, ctrl.length, len)
@ -773,7 +774,7 @@ void ScsiController::FlushUnit()
{
assert(IsDataOut());
auto disk = dynamic_cast<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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -54,10 +54,8 @@ public:
// Maximum number of logical units
static const int LUN_MAX = 32;
ScsiController(shared_ptr<BUS>, int);
ScsiController(BUS&, int);
~ScsiController() override = default;
ScsiController(ScsiController&) = delete;
ScsiController& operator=(const ScsiController&) = delete;
void Reset() override;

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -22,8 +22,6 @@ public:
CDTrack() = default;
~CDTrack() = default;
CDTrack(CDTrack&) = delete;
CDTrack& operator=(const CDTrack&) = delete;
void Init(int track, DWORD first, DWORD last);

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
@ -220,7 +195,7 @@ namespace Human68k {
/// Command line parameter struct
/**
The driver itself is included in the beginning of the argument,
The driver itself is included in the beginning of the argument,
so setting to a length longer than HUMAN68K_PATH_MAX
*/
struct argument_t {
@ -257,9 +232,9 @@ static const int XM6_HOST_PSEUDO_CLUSTER_MAX = 10;
/// Number of caches for directory entries
/**
Human68k carries out a large number of checks of directory entries when doing an operation
inside a subdirectory. This specifies the number of caches used to speed up this operation.
Cache is allocated per drive. The more you add the faster it gets, but use too many
Human68k carries out a large number of checks of directory entries when doing an operation
inside a subdirectory. This specifies the number of caches used to speed up this operation.
Cache is allocated per drive. The more you add the faster it gets, but use too many
and the host OS gets under a heavy load, so be careful.
Default is 16.
@ -268,10 +243,10 @@ static const int XM6_HOST_DIRENTRY_CACHE_MAX = 16;
/// Max number of entries that can be stored per directory
/**
When a large number of files are stored in a directory, a larger amount of data than
contemporanous applications can handle will be returned. This may lead to errors such as
When a large number of files are stored in a directory, a larger amount of data than
contemporanous applications can handle will be returned. This may lead to errors such as
partial data being recognized, performance dropping significantly, or OOM crashes.
To guard against this, an upper limit is defined here. In the case of a particular
To guard against this, an upper limit is defined here. In the case of a particular
file manager, the upper limit is 2560 files. This is one good example to use as reference.
Default is around 60000 entries. (Upper limit of the FAT root directory)
@ -280,17 +255,17 @@ static const int XM6_HOST_DIRENTRY_FILE_MAX = 65535;
/// Max number of patterns for file name deduplication
/**
The file names on the Human68k side are automatically created based on the file system on
the host side. However, Human68k have stricter file name length restrictions than the host has.
Because of this, there is a risk that file name duplication will occur. When this happens,
WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve
the duplication. Theoretically, there are over 60 million (36^5) unique file names that
can be generated by this method. However, in reality any more than a few hundred
deduplications will take excessive processing time. So here an upper limit to deduplication
is set in order to maintain system performance. If a system is operated with common sense,
you should only need a few dozen deduplication patterns, so this value can be kept low
to further improve performance. In the case deduplication is not carried out, multiple files
with the same name will be created. When trying to access such files,
The file names on the Human68k side are automatically created based on the file system on
the host side. However, Human68k have stricter file name length restrictions than the host has.
Because of this, there is a risk that file name duplication will occur. When this happens,
WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve
the duplication. Theoretically, there are over 60 million (36^5) unique file names that
can be generated by this method. However, in reality any more than a few hundred
deduplications will take excessive processing time. So here an upper limit to deduplication
is set in order to maintain system performance. If a system is operated with common sense,
you should only need a few dozen deduplication patterns, so this value can be kept low
to further improve performance. In the case deduplication is not carried out, multiple files
with the same name will be created. When trying to access such files,
only the first entry will ever be accessed.
Default is 36 patterns.
@ -338,11 +313,7 @@ Normal is 0. Becomes 1 if attempting to mount in read-only mode.
Reserving the other values for future use.
Insurance against hard-to-detect devices such as homemade USB storage.
*/
enum {
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
};
static const DWORD FSFLAG_WRITE_PROTECT = 0x00000001; ///< Bit0: Force write protect
//===========================================================================
//
@ -357,8 +328,8 @@ class CRing {
public:
CRing() { Init(); }
~CRing() { Remove(); }
CRing(CRing&) = delete;
CRing& operator=(const CRing&) = delete;
CRing(CRing&) = default;
CRing& operator=(const CRing&) = default;
void Init() { next = prev = this; }
@ -442,8 +413,6 @@ class CHostFilename {
public:
CHostFilename() = default;
~CHostFilename() = default;
CHostFilename(CHostFilename&) = delete;
CHostFilename& operator=(const CHostFilename&) = delete;
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
@ -468,7 +437,7 @@ public:
{ m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry
void SetEntryCluster(WORD nHumanCluster)
{ m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry
const Human68k::dirent_t* GetEntry() const
const Human68k::dirent_t* GetEntry() const
{ return &m_dirHuman; } ///< Get Human68k directory entry
int CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes
bool isSameEntry(const Human68k::dirent_t* pdirHuman) const
@ -505,7 +474,7 @@ get updated for new directories created as a result of file operations, which
triggers updates to directory entires.
However, on the host file system, new directories do typically get an updated time stamp.
The unfortunate outcome is that when copying a directory for instance, the time stamp
The unfortunate outcome is that when copying a directory for instance, the time stamp
will get overwritten even if the application did not intend for the time stamp to get updated.
Here follows an implementation of a directory cache FAT time stamp emulation feature.
@ -532,8 +501,8 @@ public:
CHostPath() = default;
~CHostPath();
CHostPath(CHostPath&) = delete;
CHostPath& operator=(const CHostPath&) = delete;
CHostPath(CHostPath&) = default;
CHostPath& operator=(const CHostPath&) = default;
void Clean(); ///< Initialialize for reuse
@ -575,9 +544,9 @@ private:
//
/// File search processing
///
/// It's pretty much impossible to process Human68k file names as Unicode internally.
/// It's pretty much impossible to process Human68k file names as Unicode internally.
/// So, we carry out binary conversion for processing. We leave it up to the
/// directory entry cache to handle the conversion, which allows WINDRV to read
/// directory entry cache to handle the conversion, which allows WINDRV to read
/// everything as Shift-JIS. Additionally, it allows Human68k names to be
/// fully independent of base path assignments.
///
@ -596,8 +565,6 @@ class CHostFiles {
public:
CHostFiles() = default;
~CHostFiles() = default;
CHostFiles(CHostFiles&) = delete;
CHostFiles& operator=(const CHostFiles&) = delete;
void Init();
@ -675,8 +642,8 @@ class CHostFcb {
public:
CHostFcb() = default;
~CHostFcb() { Close(); }
CHostFcb(CHostFcb&) = delete;
CHostFcb& operator=(const CHostFcb&) = delete;
CHostFcb(CHostFcb&) = default;
CHostFcb& operator=(const CHostFcb&) = default;
void Init();
@ -694,7 +661,7 @@ public:
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
bool Truncate() const; ///< Truncate file
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
DWORD Seek(DWORD nOffset, Human68k::seek_t nHumanSeek); ///< Seek file
bool TimeStamp(DWORD nHumanTime) const; ///< Set file time stamp
void Close(); ///< Close file
@ -747,8 +714,8 @@ class CHostDrv
public:
CHostDrv() = default;
~CHostDrv();
CHostDrv(CHostDrv&) = delete;
CHostDrv& operator=(const CHostDrv&) = delete;
CHostDrv(CHostDrv&) = default;
CHostDrv& operator=(const CHostDrv&) = default;
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
@ -811,8 +778,8 @@ public:
CHostEntry() = default;
~CHostEntry();
CHostEntry(CHostEntry&) = delete;
CHostEntry& operator=(const CHostEntry&) = delete;
CHostEntry(CHostEntry&) = default;
CHostEntry& operator=(const CHostEntry&) = default;
void Init() const; ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
@ -854,13 +821,13 @@ private:
/** @note
Current state of affairs:
While it violates the design philosophy of XM6, we should find a way for
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
While it violates the design philosophy of XM6, we should find a way for
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
This way, we get the following benefits.
Benefit no. 1
Makes it possible to manage a large number of command handler methods in one place.
There is a high chance the command handlers will change drastically because of
Makes it possible to manage a large number of command handler methods in one place.
There is a high chance the command handlers will change drastically because of
host system architectural changes, so we will save a huge amount of maintenance work
in the long run.
@ -870,7 +837,7 @@ It is not feasible to implement code in XM6 for simultaneous use of file system
Therefore file system object polymorphism is a waste of CPU cycles.
I made the change as an experiment. Performance did improve.
The improvement was obvious from looking at the source the compiler spit out
The improvement was obvious from looking at the source the compiler spit out
after changing the FILESYS_FAST_STRUCTURE value in windrv.h.
You may understand now why I decided to rant here.

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;
@ -158,7 +158,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
LOGTRACE("Opened tap device %d", m_hTAP)
// IFF_NO_PI for no extra packet information
ifreq ifr = {};
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;

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

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);
}
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
EnterStatusPhase();
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Block descriptors cannot be returned
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

@ -7,17 +7,17 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
//
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// Tiny SCSI Emulator
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
//
// Additional documentation and clarification is available at the
// Additional documentation and clarification is available at the
// following link:
// - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link
//
@ -36,7 +36,7 @@ using namespace scsi_defs;
using namespace scsi_command_util;
// TODO Disk must not be the superclass
SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP")
SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
@ -139,7 +139,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
LOGTRACE("%s Read maximum length %d, (%04X)", __PRETTY_FUNCTION__, requested_length, requested_length)
// At host startup, it will send a READ(6) command with a length of 1. We should
// At host startup, it will send a READ(6) command with a length of 1. We should
// respond by going into the status mode with a code of 0x02
if (requested_length == 1) {
return 0;
@ -169,7 +169,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
LOGTRACE("%s Packet Sz %d (%08X) read: %d", __PRETTY_FUNCTION__, (unsigned int) rx_packet_size, (unsigned int) rx_packet_size, read_count)
// This is a very basic filter to prevent unnecessary packets from
// being sent to the SCSI initiator.
// being sent to the SCSI initiator.
send_message_to_host = false;
// The following doesn't seem to work with unicast messages. Temporarily removing the filtering
@ -192,7 +192,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
/////// }
send_message_to_host = true;
// TODO: We should check to see if this message is in the multicast
// TODO: We should check to see if this message is in the multicast
// configuration from SCSI command 0x0D
if (!send_message_to_host) {
@ -239,7 +239,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
// The CRC was already appended by the ctapdriver
return size + DAYNAPORT_READ_HEADER_SZ;
}
// If we got to this point, there are still messages in the queue, so
// If we got to this point, there are still messages in the queue, so
// we should loop back and get the next one.
} // end while
@ -300,7 +300,7 @@ bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, const vector<BYTE>& buf,
return true;
}
//---------------------------------------------------------------------------
//
// RetrieveStats
@ -406,9 +406,9 @@ void SCSIDaynaPort::Write6()
ctrl->length = GetInt16(ctrl->cmd, 3 + 8);
}
else {
LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, data_format)
LOGWARN("%s Unknown data format $%02X", __PRETTY_FUNCTION__, data_format)
}
LOGTRACE("%s length: %04X (%d) format: %02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
LOGTRACE("%s length: $%04X (%d) format: $%02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
if (ctrl->length <= 0) {
throw scsi_error_exception();

View File

@ -7,12 +7,12 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
//
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// Tiny SCSI Emulator
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
@ -45,10 +45,8 @@ class SCSIDaynaPort final : public Disk
{
public:
SCSIDaynaPort();
explicit SCSIDaynaPort(int);
~SCSIDaynaPort() override = default;
SCSIDaynaPort(SCSIDaynaPort&) = delete;
SCSIDaynaPort& operator=(const SCSIDaynaPort&) = delete;
bool Init(const unordered_map<string, string>&) override;
void Open(const Filepath& path) override;

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI Host Bridge for the Sharp X68000 ]
@ -27,7 +27,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
SCSIBR::SCSIBR() : Disk("SCBR")
SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun)
{
// Create host file system
fs.Reset();
@ -41,7 +41,7 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
{
SetParams(params);
#ifdef __linux
#ifdef __linux__
// TAP Driver Generation
m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI Host Bridge for the Sharp X68000 ]
@ -24,7 +24,7 @@
#include <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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
@ -24,7 +24,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
SCSICD::SCSICD(const unordered_set<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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
@ -27,10 +27,8 @@ class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport
{
public:
explicit SCSICD(const unordered_set<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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
@ -22,8 +22,8 @@
using namespace scsi_command_util;
SCSIHD::SCSIHD(const unordered_set<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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
@ -26,10 +26,8 @@ class SCSIHD : public Disk, public FileSupport
public:
SCSIHD(const unordered_set<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

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI NEC "Genuine" Hard Disk]
@ -20,7 +20,7 @@
#include <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.
// Serialize/Deserialize protobuf message: Length followed by the actual data.
// A little endian platform is assumed.
//
//---------------------------------------------------------------------------
void SocketConnector::SerializeMessage(int fd, const google::protobuf::Message& message) const
void ProtobufSerializer::SerializeMessage(int fd, const google::protobuf::Message& message) const
{
string data;
message.SerializeToString(&data);
@ -67,7 +40,7 @@ void SocketConnector::SerializeMessage(int fd, const google::protobuf::Message&
}
}
void SocketConnector::DeserializeMessage(int fd, google::protobuf::Message& message) const
void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& message) const
{
// Read the header with the size of the protobuf data
vector<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 name_lower = dir->d_name;
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
string filename = GetNextImageFile(dir, folder);
if (filename.empty()) {
continue;
}
string name_lower = dir->d_name;
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
if (dir->d_type == DT_DIR) {
if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
GetAvailableImages(image_files_info, default_folder, filename, folder_pattern,
file_pattern, scan_depth);
}
string filename = folder + "/" + dir->d_name;
continue;
}
if (struct stat st; dir->d_type == DT_REG && !stat(filename.c_str(), &st)) {
if (!st.st_size) {
LOGWARN("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, folder.c_str())
continue;
}
} else if (dir->d_type == DT_LNK && stat(filename.c_str(), &st)) {
LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str())
continue;
} else if (dir->d_type == DT_DIR) {
if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
GetAvailableImages(image_files_info, default_image_folder, filename, folder_pattern,
file_pattern, scan_depth);
}
continue;
}
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(image_file.get(), filename)) {
GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1));
}
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
if (auto image_file = make_unique<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()));
return;
id_sets = MatchDevices(result, command);
if (id_sets.empty()) {
return;
}
}
auto devices_info = make_unique<PbDevicesInfo>();
for (const auto& [id, lun] : id_sets) {
for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) {
GetDevice(*d, *devices_info->add_devices(), default_folder);
break;
}
}
}
auto devices_info = make_unique<PbDevicesInfo>().release();
result.set_allocated_devices_info(devices_info);
for (const auto& [id, lun] : id_sets) {
GetDevice(device_factory->GetDeviceByIdAndLun(id, lun), devices_info->add_devices());
}
result.set_allocated_devices_info(devices_info.release());
result.set_status(true);
}
PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result)
unique_ptr<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);
@ -42,13 +42,9 @@ BUS::phase_t BUS::GetPhase()
// Determine Phase String phase enum
//
//---------------------------------------------------------------------------
const char* BUS::GetPhaseStrRaw(phase_t current_phase){
if(current_phase <= phase_t::reserved) {
return phase_str_table[(int)current_phase];
}
else {
return "INVALID";
}
const char* BUS::GetPhaseStrRaw(phase_t current_phase) {
const auto& it = phase_str_mapping.find(current_phase);
return it != phase_str_mapping.end() ? it->second : "INVALID";
}
//---------------------------------------------------------------------------
@ -70,24 +66,21 @@ const array<BUS::phase_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();
}
// Cleanup the Bus
bus.Cleanup();
}
void Reset()
{
// Reset the bus
bus->Reset();
bus.Reset();
}
//---------------------------------------------------------------------------
@ -234,7 +229,7 @@ void Reset()
// Pin the thread to a specific CPU (Only applies to Linux)
//
//---------------------------------------------------------------------------
#ifdef __linux
#ifdef __linux__
void FixCpu(int cpu)
{
// Get the number of CPUs
@ -326,7 +321,7 @@ int main(int argc, char *argv[])
// Reset
Reset();
#ifdef __linux
#ifdef __linux__
// Set the affinity to a specific processor core
FixCpu(3);
@ -338,7 +333,7 @@ int main(int argc, char *argv[])
// Start execution
running = true;
bus->SetACT(false);
bus.SetACT(false);
(void)gettimeofday(&start_time, nullptr);
@ -348,7 +343,7 @@ int main(int argc, char *argv[])
while (running)
{
// Work initialization
this_sample = (bus->Acquire() & ALL_SCSI_PINS);
this_sample = (bus.Acquire() & ALL_SCSI_PINS);
loop_count++;
if (loop_count > LLONG_MAX - 1)
{

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