mirror of
https://github.com/akuker/RASCSI.git
synced 2025-04-02 10:30:03 +00:00
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:
parent
0e4d42f04c
commit
efbfb54d26
2
.github/workflows/run_tests.yml
vendored
2
.github/workflows/run_tests.yml
vendored
@ -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
|
||||
|
||||
|
@ -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
113
src/raspberrypi/bus.h
Normal file
@ -0,0 +1,113 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "os.h"
|
||||
#include "scsi.h"
|
||||
#include <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;
|
||||
};
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
46
src/raspberrypi/controllers/phase_handler.h
Normal file
46
src/raspberrypi/controllers/phase_handler.h
Normal 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; }
|
||||
};
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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>&);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -18,7 +18,7 @@
|
||||
#include "filepath.h"
|
||||
#include <vector>
|
||||
|
||||
using namespace std; //NOSONAR Not relevant for rascsi
|
||||
using namespace std;
|
||||
|
||||
class DiskTrack
|
||||
{
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -14,7 +14,7 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
using namespace std; //NOSONAR Not relevant for rascsi
|
||||
using namespace std;
|
||||
|
||||
namespace scsi_command_util
|
||||
{
|
||||
|
@ -7,17 +7,17 @@
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
|
||||
//
|
||||
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||
// Tiny SCSI Emulator
|
||||
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
|
||||
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
|
||||
//
|
||||
// Additional documentation and clarification is available at the
|
||||
// Additional documentation and clarification is available at the
|
||||
// following link:
|
||||
// - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link
|
||||
//
|
||||
@ -36,7 +36,7 @@ using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
// TODO Disk must not be the superclass
|
||||
SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP")
|
||||
SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun)
|
||||
{
|
||||
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
|
||||
dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
|
||||
@ -139,7 +139,7 @@ int SCSIDaynaPort::Read(const vector<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();
|
||||
|
@ -7,12 +7,12 @@
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
|
||||
//
|
||||
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
|
||||
// Tiny SCSI Emulator
|
||||
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
|
||||
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
|
||||
@ -45,10 +45,8 @@ class SCSIDaynaPort final : public Disk
|
||||
{
|
||||
public:
|
||||
|
||||
SCSIDaynaPort();
|
||||
explicit SCSIDaynaPort(int);
|
||||
~SCSIDaynaPort() override = default;
|
||||
SCSIDaynaPort(SCSIDaynaPort&) = delete;
|
||||
SCSIDaynaPort& operator=(const SCSIDaynaPort&) = delete;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
void Open(const Filepath& path) override;
|
||||
|
@ -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){
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -35,7 +35,7 @@ public:
|
||||
|
||||
Filepath();
|
||||
virtual ~Filepath() = default;
|
||||
Filepath(Filepath&) = delete;
|
||||
Filepath(Filepath&) = default;
|
||||
Filepath& operator=(const Filepath&);
|
||||
|
||||
void Clear();
|
||||
|
@ -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];
|
||||
|
@ -15,7 +15,7 @@
|
||||
#include "scsi.h"
|
||||
#include <array>
|
||||
|
||||
#ifdef __linux
|
||||
#ifdef __linux__
|
||||
#include <linux/gpio.h>
|
||||
#endif
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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()) {
|
@ -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
@ -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)
|
||||
|
762
src/raspberrypi/rascsi_executor.cpp
Normal file
762
src/raspberrypi/rascsi_executor.cpp
Normal 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;
|
||||
}
|
71
src/raspberrypi/rascsi_executor.h
Normal file
71
src/raspberrypi/rascsi_executor.h
Normal 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>);
|
||||
|
||||
};
|
@ -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");
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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&);
|
||||
};
|
||||
|
138
src/raspberrypi/rascsi_service.cpp
Normal file
138
src/raspberrypi/rascsi_service.cpp
Normal 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;
|
||||
}
|
||||
|
43
src/raspberrypi/rascsi_service.h
Normal file
43
src/raspberrypi/rascsi_service.h
Normal 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; }
|
||||
};
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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" }
|
||||
};
|
||||
|
@ -5,118 +5,12 @@
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
//
|
||||
// [ SCSI Common Functionality ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "os.h"
|
||||
#include <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 {
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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());
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "testing.h"
|
||||
#include "mocks.h"
|
||||
#include "devices/file_support.h"
|
||||
|
||||
class TestFileSupport : public FileSupport
|
||||
|
@ -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;
|
||||
};
|
@ -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());
|
||||
}
|
||||
|
40
src/raspberrypi/test/phase_handler_test.cpp
Normal file
40
src/raspberrypi/test/phase_handler_test.cpp
Normal 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());
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
24
src/raspberrypi/test/protobuf_serializer_test.cpp
Normal file
24
src/raspberrypi/test/protobuf_serializer_test.cpp
Normal 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);
|
||||
}
|
72
src/raspberrypi/test/rascsi_exceptions_test.cpp
Normal file
72
src/raspberrypi/test/rascsi_exceptions_test.cpp
Normal 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());
|
||||
}
|
||||
}
|
491
src/raspberrypi/test/rascsi_executor_test.cpp
Normal file
491
src/raspberrypi/test/rascsi_executor_test.cpp
Normal 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));
|
||||
}
|
92
src/raspberrypi/test/rascsi_response_test.cpp
Normal file
92
src/raspberrypi/test/rascsi_response_test.cpp
Normal 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]);
|
||||
}
|
27
src/raspberrypi/test/rasutil_test.cpp
Normal file
27
src/raspberrypi/test/rasutil_test.cpp
Normal 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);
|
||||
}
|
@ -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());
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "testing.h"
|
||||
#include "mocks.h"
|
||||
#include "devices/scsi_command_util.h"
|
||||
|
||||
using namespace scsi_command_util;
|
||||
|
@ -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";
|
||||
}
|
||||
|
20
src/raspberrypi/test/scsihd_test.cpp
Normal file
20
src/raspberrypi/test/scsihd_test.cpp
Normal 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);
|
||||
}
|
@ -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); }
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user