Issues 1179 and 1182 (#1232)

* Update logging

* Remove duplicate code

* Update unit tests

* Clean up includes

* Merge ProtobufSerializer into protobuf_util namespace

* Precompile regex

* Add const

* Add Split() convenience method, update log level/ID parsing

* Move log.h to legacy folder

* Elimininate gotos

* Fixes for gcc 13

* Update compiler flags

* Update default folder handling

* Use references instead of pointers

* Move code for better encapsulation

* Move code

* Remove unused method argument

* Move device logger

* Remove redundant to_string

* Rename for consistency

* Update handling of protobuf pointers

* Simplify protobuf usage

* Memory handling update

* Add hasher
This commit is contained in:
Uwe Seimet 2023-10-15 08:38:15 +02:00 committed by GitHub
parent c1f6f3ffea
commit 41bdcd4aed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
161 changed files with 4767 additions and 5150 deletions

View File

@ -7,8 +7,6 @@
CROSS_COMPILE =
CXX = $(CROSS_COMPILE)g++
AR = $(CROSS_COMPILE)ar
RANLIB = $(CROSS_COMPILE)ranlib
## DEBUG=1 : A Debug build includes the debugger symbols
## and disables compiler optimization. Typically,
@ -16,30 +14,21 @@ RANLIB = $(CROSS_COMPILE)ranlib
DEBUG ?= 0
ifeq ($(DEBUG), 1)
# Debug compiler flags
CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG
BUILD_TYPE = Debug
CXXFLAGS += -Og -g -Wall -Wextra -DDEBUG
else
# Release compiler flags
CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG
BUILD_TYPE = Release
endif
ifeq ("$(shell uname -s)","Linux")
# -Wno-psabi might not work on non-Linux platforms
CXXFLAGS += -Wno-psabi
endif
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10)
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP
## EXTRA_FLAGS : Can be used to pass special purpose flags
CXXFLAGS += $(EXTRA_FLAGS)
ifeq "$(GCCVERSION10)" "1"
CXXFLAGS += -DFMT_HEADER_ONLY
endif
## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type
## that you are using. The typical options are
@ -86,12 +75,12 @@ SRC_PROTOC = piscsi_interface.proto
SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp
SRC_PROTOBUF = \
shared/protobuf_util.cpp \
shared/protobuf_serializer.cpp
shared/protobuf_util.cpp
SRC_SHARED = \
shared/piscsi_version.cpp \
shared/piscsi_util.cpp
shared/piscsi_util.cpp \
shared/network_util.cpp
SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp')
SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp')
@ -167,7 +156,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC)
protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC)
mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@
$(OBJ_GENERATED) : $(SRC_GENERATED)
$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR)
$(CXX) $(CXXFLAGS) -c $< -o $@
## Build Targets:
@ -180,10 +169,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED)
## Note that you have to run 'make clean' before switching
## between coverage and non-coverage builds.
.DEFAULT_GOAL := all
.PHONY: all ALL docs test coverage lcov
all: $(BIN_ALL) docs
.PHONY: all docs test coverage lcov
test: $(BINDIR)/$(PISCSI_TEST)
all: $(SRC_GENERATED) $(BIN_ALL) docs
test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST)
$(BINDIR)/$(PISCSI_TEST)
coverage: CXXFLAGS += --coverage
@ -198,10 +188,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/
$(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED)
$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs
$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf
$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf
$(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR)
@ -213,8 +203,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR)
$(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP)
$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest
# Phony rules for building individual utilities
.PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP)

View File

@ -3,16 +3,22 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "devices/primary_device.h"
#include "abstract_controller.h"
#include <ranges>
using namespace scsi_defs;
AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns)
{
device_logger.SetIdAndLun(target_id, -1);
}
void AbstractController::AllocateCmd(size_t size)
{
if (size > ctrl.cmd.size()) {
@ -40,9 +46,8 @@ unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, lun] : luns) {
devices.insert(lun);
}
// "luns | views:values" is not supported by the bullseye compiler
ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } );
return devices;
}
@ -56,100 +61,66 @@ void AbstractController::Reset()
{
SetPhase(phase_t::busfree);
ctrl.status = status::GOOD;
ctrl.message = 0x00;
ctrl.blocks = 0;
ctrl.next = 0;
ctrl.offset = 0;
ctrl.length = 0;
ctrl = {};
SetByteTransfer(false);
// Reset all LUNs
for (const auto& [lun, device] : luns) {
for (const auto& [_, device] : luns) {
device->Reset();
}
GetBus().Reset();
}
void AbstractController::ProcessPhase()
void AbstractController::ProcessOnController(int id_data)
{
switch (GetPhase()) {
case phase_t::busfree:
BusFree();
break;
device_logger.SetIdAndLun(GetTargetId(), -1);
case phase_t::selection:
Selection();
break;
const int initiator_id = ExtractInitiatorId(id_data);
if (initiator_id != UNKNOWN_INITIATOR_ID) {
LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id));
}
else {
LogTrace("++++ Starting processing for unknown initiator ID");
}
case phase_t::dataout:
DataOut();
break;
case phase_t::datain:
DataIn();
break;
case phase_t::command:
Command();
break;
case phase_t::status:
Status();
break;
case phase_t::msgout:
MsgOut();
break;
case phase_t::msgin:
MsgIn();
break;
default:
throw scsi_exception(sense_key::ABORTED_COMMAND);
break;
while (Process(initiator_id)) {
// Handle bus phases until the bus is free for the next command
}
}
bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
{
if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
const int lun = device->GetLun();
if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) {
return false;
}
luns[device->GetLun()] = device;
device->SetController(shared_from_this());
luns[lun] = device;
device->SetController(this);
return true;
}
bool AbstractController::RemoveDevice(shared_ptr<PrimaryDevice> device)
bool AbstractController::RemoveDevice(PrimaryDevice& device)
{
device->SetController(nullptr);
device.CleanUp();
return luns.erase(device->GetLun()) == 1;
return luns.erase(device.GetLun()) == 1;
}
bool AbstractController::HasDeviceForLun(int lun) const
{
return luns.find(lun) != luns.end();
return luns.contains(lun);
}
int AbstractController::ExtractInitiatorId(int id_data) const
{
int initiator_id = UNKNOWN_INITIATOR_ID;
if (int tmp = id_data - (1 << target_id); tmp) {
initiator_id = 0;
for (int j = 0; j < 8; j++) {
tmp >>= 1;
if (tmp) {
initiator_id++;
}
else {
break;
}
}
if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) {
return static_cast<int>(log2(id_data_without_target & -id_data_without_target));
}
return initiator_id;
return UNKNOWN_INITIATOR_ID;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Base class for device controllers
//
@ -14,17 +14,19 @@
#include "shared/scsi.h"
#include "hal/bus.h"
#include "phase_handler.h"
#include "controller_manager.h"
#include "devices/device_logger.h"
#include <unordered_set>
#include <unordered_map>
#include <span>
#include <vector>
#include <memory>
#include <functional>
using namespace std;
class PrimaryDevice;
class AbstractController : public PhaseHandler, public enable_shared_from_this<AbstractController>
class AbstractController : public PhaseHandler
{
public:
@ -37,19 +39,19 @@ public:
RESTART_PI
};
AbstractController(shared_ptr<ControllerManager> controller_manager, int target_id, int max_luns)
: controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {}
AbstractController(BUS&, int, int);
~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;
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;
virtual void Reset();
virtual int GetInitiatorId() const = 0;
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
virtual int GetEffectiveLun() const = 0;
virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0;
void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; }
piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; }
int GetTargetId() const { return target_id; }
int GetMaxLuns() const { return max_luns; }
@ -58,52 +60,59 @@ public:
unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
bool AddDevice(shared_ptr<PrimaryDevice>);
bool RemoveDevice(shared_ptr<PrimaryDevice>);
bool RemoveDevice(PrimaryDevice&);
bool HasDeviceForLun(int) const;
int ExtractInitiatorId(int) const;
void ProcessOnController(int);
// TODO These should probably be extracted into a new TransferHandler class
void AllocateBuffer(size_t);
vector<uint8_t>& GetBuffer() { return ctrl.buffer; }
scsi_defs::status GetStatus() const { return ctrl.status; }
auto& GetBuffer() { return ctrl.buffer; }
auto GetStatus() const { return ctrl.status; }
void SetStatus(scsi_defs::status s) { ctrl.status = s; }
uint32_t GetLength() const { return ctrl.length; }
auto GetLength() const { return ctrl.length; }
void SetLength(uint32_t l) { ctrl.length = l; }
uint32_t GetBlocks() const { return ctrl.blocks; }
bool HasBlocks() const { return ctrl.blocks; }
void SetBlocks(uint32_t b) { ctrl.blocks = b; }
void DecrementBlocks() { --ctrl.blocks; }
uint64_t GetNext() const { return ctrl.next; }
auto GetNext() const { return ctrl.next; }
void SetNext(uint64_t n) { ctrl.next = n; }
void IncrementNext() { ++ctrl.next; }
int GetMessage() const { return ctrl.message; }
void SetMessage(int m) { ctrl.message = m; }
vector<int>& GetCmd() { return ctrl.cmd; }
int GetCmd(int index) const { return ctrl.cmd[index]; }
auto GetCmd() const { return ctrl.cmd; }
int GetCmdByte(int index) const { return ctrl.cmd[index]; }
bool IsByteTransfer() const { return is_byte_transfer; }
void SetByteTransfer(bool);
uint32_t GetBytesToTransfer() const { return bytes_to_transfer; }
auto GetBytesToTransfer() const { return bytes_to_transfer; }
void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; }
protected:
shared_ptr<ControllerManager> GetControllerManager() const { return controller_manager.lock(); }
inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); }
BUS& GetBus() const { return bus; }
scsi_defs::scsi_command GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
auto GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
void ProcessPhase();
void AllocateCmd(size_t);
void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; }
// TODO These should probably be extracted into a new TransferHandler class
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; }
void LogTrace(const string& s) const { device_logger.Trace(s); }
void LogDebug(const string& s) const { device_logger.Debug(s); }
void LogInfo(const string& s) const { device_logger.Info(s); }
void LogWarn(const string& s) const { device_logger.Warn(s); }
void LogError(const string& s) const { device_logger.Error(s); }
private:
int ExtractInitiatorId(int) const;
using ctrl_t = struct _ctrl_t {
// Command data, dynamically resized if required
vector<int> cmd = vector<int>(16);
@ -121,7 +130,9 @@ private:
ctrl_t ctrl = {};
weak_ptr<ControllerManager> controller_manager;
BUS& bus;
DeviceLogger device_logger;
// Logical units of this controller mapped to their LUN numbers
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
@ -132,4 +143,6 @@ private:
bool is_byte_transfer = false;
uint32_t bytes_to_transfer = 0;
piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE;
};

View File

@ -3,29 +3,34 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "scsi_controller.h"
#include "controller_manager.h"
using namespace std;
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
shared_ptr<ScsiController> ControllerManager::CreateScsiController(BUS& bus, int id) const
{
auto controller = FindController(id);
if (controller != nullptr) {
return make_shared<ScsiController>(bus, id);
}
bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr<PrimaryDevice> device)
{
if (auto controller = FindController(id); controller != nullptr) {
if (controller->HasDeviceForLun(device->GetLun())) {
return false;
}
return controller->AddDevice(device);
}
// If there is no LUN yet the first LUN must be LUN 0
if (device->GetLun() == 0) {
controller = make_shared<ScsiController>(shared_from_this(), id);
if (controller->AddDevice(device)) {
// If this is LUN 0 create a new controller
if (!device->GetLun()) {
if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) {
controllers[id] = controller;
return true;
@ -35,20 +40,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice>
return false;
}
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
bool ControllerManager::DeleteController(const AbstractController& controller)
{
return controllers.erase(controller->GetTargetId()) == 1;
}
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{
for (const auto& [id, controller] : controllers) {
if (data & (1 << controller->GetTargetId())) {
return controller;
}
for (const auto& device : controller.GetDevices()) {
device->CleanUp();
}
return nullptr;
return controllers.erase(controller.GetTargetId()) == 1;
}
void ControllerManager::DeleteAllControllers()
{
unordered_set<shared_ptr<AbstractController>> values;
ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } );
for (const auto& controller : values) {
DeleteController(*controller);
}
assert(controllers.empty());
}
AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const
{
if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } );
it != controllers.end()) {
(*it).second->ProcessOnController(id_data);
return (*it).second->GetShutdownMode();
}
return AbstractController::piscsi_shutdown_mode::NONE;
}
shared_ptr<AbstractController> ControllerManager::FindController(int target_id) const
@ -57,11 +79,15 @@ shared_ptr<AbstractController> ControllerManager::FindController(int target_id)
return it == controllers.end() ? nullptr : it->second;
}
bool ControllerManager::HasController(int target_id) const {
return controllers.contains(target_id);
}
unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, controller] : controllers) {
for (const auto& [_, controller] : controllers) {
const auto& d = controller->GetDevices();
devices.insert(d.begin(), d.end());
}
@ -69,12 +95,12 @@ unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() cons
return devices;
}
void ControllerManager::DeleteAllControllers()
bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const
{
controllers.clear();
return GetDeviceForIdAndLun(id, lun) != nullptr;
}
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceForIdAndLun(int id, int lun) const
{
if (const auto& controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Keeps track of and manages the controllers
//
@ -11,36 +11,41 @@
#pragma once
#include "hal/bus.h"
#include "controllers/abstract_controller.h"
#include <unordered_map>
#include <unordered_set>
#include <memory>
#include "hal/bus.h"
using namespace std;
class AbstractController;
class ScsiController;
class PrimaryDevice;
class ControllerManager : public enable_shared_from_this<ControllerManager>
class ControllerManager
{
BUS& bus;
unordered_map<int, shared_ptr<AbstractController>> controllers;
public:
explicit ControllerManager(BUS& bus) : bus(bus) {}
ControllerManager() = default;
~ControllerManager() = default;
// Maximum number of controller devices
static const int DEVICE_MAX = 8;
inline BUS& GetBus() const { return bus; }
bool AttachToScsiController(int, shared_ptr<PrimaryDevice>);
bool DeleteController(shared_ptr<AbstractController>);
shared_ptr<AbstractController> IdentifyController(int) const;
shared_ptr<AbstractController> FindController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
bool AttachToController(BUS&, int, shared_ptr<PrimaryDevice>);
bool DeleteController(const AbstractController&);
void DeleteAllControllers();
shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
AbstractController::piscsi_shutdown_mode ProcessOnController(int) const;
shared_ptr<AbstractController> FindController(int) const;
bool HasController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
bool HasDeviceForIdAndLun(int, int) const;
shared_ptr<PrimaryDevice> GetDeviceForIdAndLun(int, int) const;
static int GetScsiIdMax() { return 8; }
static int GetScsiLunMax() { return 32; }
private:
shared_ptr<ScsiController> CreateScsiController(BUS&, int) const;
// Controllers mapped to their device IDs
unordered_map<int, shared_ptr<AbstractController>> controllers;
};

View File

@ -3,15 +3,18 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "shared/scsi.h"
#include "shared/piscsi_exceptions.h"
#include <unordered_map>
#include <functional>
using namespace scsi_defs;
using namespace scsi_defs;
class PhaseHandler
{
@ -31,7 +34,7 @@ public:
virtual void MsgIn() = 0;
virtual void MsgOut() = 0;
virtual phase_t Process(int) = 0;
virtual bool Process(int) = 0;
protected:
@ -45,4 +48,27 @@ protected:
bool IsDataOut() const { return phase == phase_t::dataout; }
bool IsMsgIn() const { return phase == phase_t::msgin; }
bool IsMsgOut() const { return phase == phase_t::msgout; }
void ProcessPhase() const
{
try {
phase_executors.at(phase)();
}
catch(const out_of_range&) {
throw scsi_exception(sense_key::aborted_command);
}
}
private:
const unordered_map<phase_t, function<void()>> phase_executors = {
{ phase_t::busfree, [this] () { BusFree(); } },
{ phase_t::selection, [this] () { Selection(); } },
{ phase_t::dataout, [this] () { DataOut(); } },
{ phase_t::datain, [this] () { DataIn(); } },
{ phase_t::command, [this] () { Command(); } },
{ phase_t::status, [this] () { Status(); } },
{ phase_t::msgout, [this] () { MsgOut(); } },
{ phase_t::msgin, [this] () { MsgIn(); } },
};
};

View File

@ -6,7 +6,7 @@
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
@ -16,7 +16,9 @@
#include "shared/piscsi_exceptions.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "devices/interfaces/byte_writer.h"
#include "controllers/controller_manager.h"
#include "devices/scsi_host_bridge.h"
#include "devices/scsi_daynaport.h"
#include "devices/mode_page_device.h"
#include "devices/disk.h"
#include "scsi_controller.h"
@ -28,11 +30,8 @@
using namespace scsi_defs;
ScsiController::ScsiController(shared_ptr<ControllerManager> controller_manager, int target_id)
: AbstractController(controller_manager, target_id, LUN_MAX)
ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax())
{
logger.SetIdAndLun(target_id, -1);
// The initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger.
AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN));
@ -44,26 +43,21 @@ void ScsiController::Reset()
execstart = 0;
identified_lun = -1;
initiator_id = UNKNOWN_INITIATOR_ID;
scsi.atnmsg = false;
scsi.msc = 0;
scsi.msb = {};
SetByteTransfer(false);
scsi = {};
}
phase_t ScsiController::Process(int id)
bool ScsiController::Process(int id)
{
GetBus().Acquire();
if (GetBus().GetRST()) {
logger.Warn("RESET signal received!");
LogWarn("RESET signal received!");
Reset();
GetBus().Reset();
return GetPhase();
return false;
}
initiator_id = id;
@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id)
ProcessPhase();
}
catch(const scsi_exception&) {
// Any exception should have been handled during the phase processing
logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
Reset();
GetBus().Reset();
BusFree();
}
return GetPhase();
return !IsBusFree();
}
void ScsiController::BusFree()
{
if (!IsBusFree()) {
logger.Trace("Bus free phase");
LogTrace("Bus Free phase");
SetPhase(phase_t::busfree);
GetBus().SetREQ(false);
@ -98,7 +89,7 @@ void ScsiController::BusFree()
GetBus().SetBSY(false);
// Initialize status and message
SetStatus(status::GOOD);
SetStatus(status::good);
SetMessage(0x00);
// Initialize ATN message reception status
@ -108,39 +99,6 @@ void ScsiController::BusFree()
SetByteTransfer(false);
if (shutdown_mode != piscsi_shutdown_mode::NONE) {
// Prepare the shutdown by flushing all caches
for (const auto& device : GetControllerManager()->GetAllDevices()) {
device->FlushCache();
}
}
// When the bus is free PiSCSI or the Pi may be shut down.
// This code has to be executed in the bus free phase and thus has to be located in the controller.
switch(shutdown_mode) {
case piscsi_shutdown_mode::STOP_PISCSI:
logger.Info("PiSCSI shutdown requested");
exit(EXIT_SUCCESS);
break;
case piscsi_shutdown_mode::STOP_PI:
logger.Info("Raspberry Pi shutdown requested");
if (system("init 0") == -1) {
logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno)));
}
break;
case piscsi_shutdown_mode::RESTART_PI:
logger.Info("Raspberry Pi restart requested");
if (system("init 6") == -1) {
logger.Error("Raspberry Pi restart failed: " + string(strerror(errno)));
}
break;
default:
break;
}
return;
}
@ -153,18 +111,7 @@ void ScsiController::BusFree()
void ScsiController::Selection()
{
if (!IsSelection()) {
// A different device controller was selected
if (int id = 1 << GetTargetId(); (static_cast<int>(GetBus().GetDAT()) & id) == 0) {
return;
}
// Abort if there is no LUN for this controller
if (!GetLunCount()) {
return;
}
logger.Trace("Selection phase");
LogTrace("Selection phase");
SetPhase(phase_t::selection);
// Raise BSY and respond
@ -174,6 +121,8 @@ void ScsiController::Selection()
// Selection completed
if (!GetBus().GetSEL() && GetBus().GetBSY()) {
LogTrace("Selection completed");
// Message out phase if ATN=1, otherwise command phase
if (GetBus().GetATN()) {
MsgOut();
@ -186,8 +135,7 @@ void ScsiController::Selection()
void ScsiController::Command()
{
if (!IsCommand()) {
logger.Trace("Command phase");
LogTrace("Command phase");
SetPhase(phase_t::command);
GetBus().SetMSG(false);
@ -198,9 +146,9 @@ void ScsiController::Command()
if (actual_count == 0) {
stringstream s;
s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
logger.Trace(s.str());
LogTrace(s.str());
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
Error(sense_key::illegal_request, asc::invalid_command_operation_code);
return;
}
@ -210,16 +158,16 @@ void ScsiController::Command()
if (actual_count != command_byte_count) {
stringstream s;
s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
+ to_string(actual_count) + " byte(s)");
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
// Command data transfer
AllocateCmd(command_byte_count);
for (int i = 0; i < command_byte_count; i++) {
GetCmd()[i] = GetBuffer()[i];
SetCmdByte(i, GetBuffer()[i]);
}
SetLength(0);
@ -234,9 +182,9 @@ void ScsiController::Execute()
s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $"
<< setfill('0') << hex;
for (int i = 0; i < BUS::GetCommandByteCount(static_cast<uint8_t>(GetOpcode())); i++) {
s << setw(2) << GetCmd(i);
s << setw(2) << GetCmdByte(i);
}
logger.Debug(s.str());
LogDebug(s.str());
// Initialization for data transfer
ResetOffset();
@ -245,15 +193,15 @@ 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(status::GOOD);
SetStatus(status::good);
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) {
logger.Trace("Invalid LUN " + to_string(lun));
LogTrace("Invalid LUN " + to_string(lun));
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
Error(sense_key::illegal_request, asc::invalid_lun);
return;
}
@ -265,7 +213,7 @@ void ScsiController::Execute()
// SCSI-2 4.4.3 Incorrect logical unit handling
if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) {
logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
GetBuffer().data()[0] = 0x7f;
@ -279,7 +227,7 @@ void ScsiController::Execute()
device->SetStatusCode(0);
}
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) {
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) {
try {
device->Dispatch(GetOpcode());
}
@ -288,7 +236,7 @@ void ScsiController::Execute()
}
}
else {
Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict);
}
}
@ -304,9 +252,8 @@ void ScsiController::Status()
}
stringstream s;
s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
logger.Trace(s.str());
s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
LogTrace(s.str());
SetPhase(phase_t::status);
// Signal line operated by the target
@ -329,8 +276,7 @@ void ScsiController::Status()
void ScsiController::MsgIn()
{
if (!IsMsgIn()) {
logger.Trace("Message In phase");
LogTrace("Message In phase");
SetPhase(phase_t::msgin);
GetBus().SetMSG(true);
@ -347,8 +293,6 @@ void ScsiController::MsgIn()
void ScsiController::MsgOut()
{
if (!IsMsgOut()) {
logger.Trace("Message Out phase");
// process the IDENTIFY message
if (IsSelection()) {
scsi.atnmsg = true;
@ -356,6 +300,7 @@ void ScsiController::MsgOut()
scsi.msb = {};
}
LogTrace("Message Out phase");
SetPhase(phase_t::msgout);
GetBus().SetMSG(true);
@ -387,8 +332,7 @@ void ScsiController::DataIn()
return;
}
logger.Trace("Entering Data In phase");
LogTrace("Data In phase");
SetPhase(phase_t::datain);
GetBus().SetMSG(false);
@ -417,11 +361,9 @@ void ScsiController::DataOut()
return;
}
logger.Trace("Data Out phase");
LogTrace("Data Out phase");
SetPhase(phase_t::dataout);
// Signal line operated by the target
GetBus().SetMSG(false);
GetBus().SetCD(false);
GetBus().SetIO(false);
@ -441,7 +383,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
// Reset check
if (GetBus().GetRST()) {
Reset();
GetBus().Reset();
return;
}
@ -453,9 +394,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
}
int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) {
if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) {
if (!HasDeviceForLun(0)) {
logger.Error("No LUN 0");
LogError("No LUN 0");
SetStatus(status);
SetMessage(0x00);
@ -468,11 +409,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
lun = 0;
}
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) {
stringstream s;
s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast<int>(sense_key)
<< ", ASC $" << static_cast<int>(asc);
logger.Debug(s.str());
LogDebug(s.str());
// Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode((static_cast<int>(sense_key) << 16) | (static_cast<int>(asc) << 8));
@ -481,7 +422,7 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
SetStatus(status);
SetMessage(0x00);
logger.Trace("Error (to status phase)");
LogTrace("Error (to status phase)");
Status();
}
@ -492,7 +433,7 @@ void ScsiController::Send()
assert(GetBus().GetIO());
if (HasValidLength()) {
logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
// The delay should be taken from the respective LUN, but as there are no Daynaport drivers for
// LUNs other than 0 this work-around works.
@ -500,7 +441,7 @@ void ScsiController::Send()
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != static_cast<int>(GetLength())) {
// If you cannot send all, move to status phase
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
@ -510,33 +451,30 @@ void ScsiController::Send()
}
DecrementBlocks();
bool result = true;
// Processing after data collection (read/data-in only)
if (IsDataIn() && GetBlocks() != 0) {
if (IsDataIn() && HasBlocks()) {
// set next buffer (set offset, length)
result = XferIn(GetBuffer());
logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks()));
if (!XferIn(GetBuffer())) {
// If result FALSE, move to status phase
Error(sense_key::aborted_command);
return;
}
LogTrace("Processing after data collection");
}
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
}
// Continue sending if block !=0
if (GetBlocks() != 0){
logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks()));
// Continue sending if blocks != 0
if (HasBlocks()) {
LogTrace("Continuing to send");
assert(HasValidLength());
assert(GetOffset() == 0);
return;
}
// Move to next phase
logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
// Message in phase
case phase_t::msgin:
// Completed sending response to extended message of IDENTIFY message
if (scsi.atnmsg) {
@ -551,13 +489,11 @@ void ScsiController::Send()
}
break;
// Data-in Phase
case phase_t::datain:
// status phase
Status();
break;
// status phase
case phase_t::status:
// Message in phase
SetLength(1);
@ -578,13 +514,13 @@ void ScsiController::Receive()
assert(!GetBus().GetIO());
if (HasValidLength()) {
logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
// If not able to receive all, move to status phase
if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) {
logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
+ to_string(len));
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
}
@ -603,10 +539,10 @@ void ScsiController::Receive()
bool result = true;
// Processing after receiving data (by phase)
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
case phase_t::dataout:
if (GetBlocks() == 0) {
if (!HasBlocks()) {
// End with this buffer
result = XferOut(false);
} else {
@ -633,12 +569,12 @@ void ScsiController::Receive()
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
// Continue to receive if block != 0
if (GetBlocks() != 0) {
// Continue to receive if blocks != 0
if (HasBlocks()) {
assert(HasValidLength());
assert(GetOffset() == 0);
return;
@ -692,7 +628,7 @@ void ScsiController::ReceiveBytes()
bool result = true;
// Processing after receiving data (by phase)
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
switch (GetPhase()) {
case phase_t::dataout:
result = XferOut(false);
@ -716,7 +652,7 @@ void ScsiController::ReceiveBytes()
// If result FALSE, move to status phase
if (!result) {
Error(sense_key::ABORTED_COMMAND);
Error(sense_key::aborted_command);
return;
}
@ -752,7 +688,7 @@ bool ScsiController::XferOut(bool cont)
SetByteTransfer(false);
auto device = GetDeviceForLun(GetEffectiveLun());
return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false;
return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false;
}
void ScsiController::DataOutNonBlockOriented()
@ -760,7 +696,6 @@ void ScsiController::DataOutNonBlockOriented()
assert(IsDataOut());
switch (GetOpcode()) {
// TODO Check why these cases are needed
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
@ -777,7 +712,7 @@ void ScsiController::DataOutNonBlockOriented()
device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset());
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
}
}
break;
@ -790,7 +725,7 @@ void ScsiController::DataOutNonBlockOriented()
stringstream s;
s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex
<< static_cast<int>(GetOpcode());
logger.Warn(s.str());
LogWarn(s.str());
break;
}
}
@ -807,9 +742,9 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
stringstream s;
s << "Command: $" << setfill('0') << setw(2) << hex << static_cast<int>(GetOpcode());
logger.Trace(s.str());
LogTrace(s.str());
int lun = GetEffectiveLun();
const int lun = GetEffectiveLun();
if (!HasDeviceForLun(lun)) {
return false;
}
@ -821,7 +756,7 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
case scsi_command::eCmdRead16:
// Read from disk
try {
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext()));
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(buf, GetNext()));
}
catch(const scsi_exception&) {
// If there is an error, go to the status phase
@ -878,13 +813,23 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
// TODO Verify has to verify, not to write
// TODO Verify has to verify, not to write, see https://github.com/PiSCSI/piscsi/issues/807
case scsi_command::eCmdVerify10:
case scsi_command::eCmdVerify16:
{
// Special case for SCBR and SCDP
if (auto byte_writer = dynamic_pointer_cast<ByteWriter>(device); byte_writer) {
if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) {
// TODO Get rid of this special case for SCBR
if (auto bridge = dynamic_pointer_cast<SCSIBR>(device); bridge) {
if (!bridge->ReadWrite(GetCmd(), GetBuffer())) {
return false;
}
ResetOffset();
break;
}
// TODO Get rid of this special case for SCDP
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(device); daynaport) {
if (!daynaport->Write(GetCmd(), GetBuffer())) {
return false;
}
@ -898,7 +843,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
}
try {
disk->Write(GetCmd(), GetBuffer(), GetNext() - 1);
disk->Write(GetBuffer(), GetNext() - 1);
}
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc());
@ -918,14 +863,14 @@ bool ScsiController::XferOutBlockOriented(bool cont)
}
case scsi_command::eCmdSetMcastAddr:
logger.Trace("Done with DaynaPort Set Multicast Address");
LogTrace("Done with DaynaPort Set Multicast Address");
break;
default:
stringstream s;
s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex
<< static_cast<int>(GetOpcode()) << ")";
logger.Warn(s.str());
LogWarn(s.str());
break;
}
@ -934,15 +879,15 @@ bool ScsiController::XferOutBlockOriented(bool cont)
void ScsiController::ProcessCommand()
{
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
stringstream s;
s << "CDB=$" << setfill('0') << setw(2) << hex;
for (uint32_t i = 0; i < len; i++) {
GetCmd()[i] = GetBuffer()[i];
s << GetCmd(i);
SetCmdByte(i, GetBuffer()[i]);
s << GetCmdByte(i);
}
logger.Trace(s.str());
LogTrace(s.str());
Execute();
}
@ -954,13 +899,13 @@ void ScsiController::ParseMessage()
const uint8_t message_type = scsi.msb[i];
if (message_type == 0x06) {
logger.Trace("Received ABORT message");
LogTrace("Received ABORT message");
BusFree();
return;
}
if (message_type == 0x0C) {
logger.Trace("Received BUS DEVICE RESET message");
LogTrace("Received BUS DEVICE RESET message");
scsi.syncoffset = 0;
if (auto device = GetDeviceForLun(identified_lun); device != nullptr) {
device->DiscardReservation();
@ -971,11 +916,11 @@ void ScsiController::ParseMessage()
if (message_type >= 0x80) {
identified_lun = static_cast<int>(message_type) & 0x1F;
logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun));
LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun));
}
if (message_type == 0x01) {
logger.Trace("Received EXTENDED MESSAGE");
LogTrace("Received EXTENDED MESSAGE");
// Check only when synchronous transfer is possible
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
@ -1046,4 +991,4 @@ void ScsiController::Sleep()
SysTimer::SleepUsec(MIN_EXEC_TIME - time);
}
execstart = 0;
}
}

View File

@ -15,8 +15,6 @@
#pragma once
#include "shared/scsi.h"
#include "controller_manager.h"
#include "devices/device_logger.h"
#include "abstract_controller.h"
#include <array>
@ -52,20 +50,17 @@ class ScsiController : public AbstractController
public:
// Maximum number of logical units
static inline const int LUN_MAX = 32;
explicit ScsiController(shared_ptr<ControllerManager>, int);
ScsiController(BUS&, int);
~ScsiController() override = default;
void Reset() override;
phase_t Process(int) override;
bool Process(int) override;
int GetEffectiveLun() const override;
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information,
scsi_defs::status status = scsi_defs::status::check_condition) override;
int GetInitiatorId() const override { return initiator_id; }
@ -79,16 +74,8 @@ public:
void DataIn() override;
void DataOut() override;
// TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC,
// e.g. by using ifdef __arm__. Currently the unit tests require this method to be public.
virtual void Execute();
void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; }
private:
DeviceLogger logger;
// Execution start time
uint32_t execstart = 0;
@ -109,6 +96,9 @@ private:
void DataOutNonBlockOriented();
void Receive();
// TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__.
virtual void Execute();
void ProcessCommand();
void ParseMessage();
void ProcessMessage();
@ -116,7 +106,5 @@ private:
void Sleep();
scsi_t scsi = {};
AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE;
};

View File

@ -14,6 +14,7 @@
#pragma once
#include <cstdint>
#include <string>
using namespace std;

View File

@ -17,8 +17,8 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "cfilesystem.h"
#include <sys/stat.h>
#include <dirent.h>
#include <iconv.h>
#include <utime.h>
@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const
// Acquire sector data
if (!m_cEntry.GetCapacityCache(nUnit, &cap)) {
// Carry out an extra media check here because it may be skipped when doing a manual eject
if (!m_cEntry.isEnable(nUnit))
goto none;
// Media check
if (m_cEntry.isMediaOffline(nUnit))
goto none;
// Get drive status
m_cEntry.GetCapacity(nUnit, &cap);
if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) {
cap.clusters = 4; // This is totally fine, right?
cap.sectors = 64;
cap.bytes = 512;
}
else {
// Get drive status
m_cEntry.GetCapacity(nUnit, &cap);
}
}
} else {
none:
cap.clusters = 4; // This is totally fine, right?
cap.sectors = 64;
cap.bytes = 512;

View File

@ -13,6 +13,15 @@
#pragma once
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <time.h>
#include <unistd.h>
using TCHAR = char;
static const int FILEPATH_MAX = 260;

View File

@ -9,126 +9,71 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_exceptions.h"
#include "shared/network_util.h"
#include <unistd.h>
#include <poll.h>
#include <arpa/inet.h>
#include "ctapdriver.h"
#include <spdlog/spdlog.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sstream>
#ifdef __linux__
#include <sys/epoll.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/sockios.h>
#endif
using namespace std;
using namespace piscsi_util;
using namespace network_util;
//---------------------------------------------------------------------------
//
// Initialization
//
//---------------------------------------------------------------------------
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
const string CTapDriver::BRIDGE_NAME = "piscsi_bridge";
static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) {
#ifndef __linux__
return false;
return "if_nametoindex: Linux is required";
#else
ifreq ifr;
ifr.ifr_ifindex = if_nametoindex(ifname);
ifr.ifr_ifindex = if_nametoindex(ifname.c_str());
if (ifr.ifr_ifindex == 0) {
LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno))
return false;
return "Can't if_nametoindex " + ifname;
}
strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1);
strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) {
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno))
return false;
return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF");
}
return true;
return "";
#endif
}
CTapDriver::~CTapDriver()
{
if (m_hTAP != -1) {
if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
LOGERROR("Can't open bridge socket: %s", strerror(errno))
} else {
LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME)
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here
LOGWARN("Warning: Removing piscsi0 from the bridge failed.")
LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge")
}
close(br_socket_fd);
}
// Release TAP defice
close(m_hTAP);
}
if (m_pcap_dumper != nullptr) {
pcap_dump_close(m_pcap_dumper);
}
if (m_pcap != nullptr) {
pcap_close(m_pcap);
}
}
static bool ip_link(int fd, const char* ifname, bool up) {
string ip_link(int fd, const char* ifname, bool up) {
#ifndef __linux__
return false;
return "Can't ip_link: Linux is required";
#else
ifreq ifr;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator
int err = ioctl(fd, SIOCGIFFLAGS, &ifr);
if (err) {
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno))
return false;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
return "Can't ioctl SIOCGIFFLAGS";
}
ifr.ifr_flags &= ~IFF_UP;
if (up) {
ifr.ifr_flags |= IFF_UP;
}
err = ioctl(fd, SIOCSIFFLAGS, &ifr);
if (err) {
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno))
return false;
if (ioctl(fd, SIOCSIFFLAGS, &ifr)) {
return "Can't ioctl SIOCSIFFLAGS";
}
return true;
return "";
#endif
}
static bool is_interface_up(string_view interface) {
string file = "/sys/class/net/";
file += interface;
file += "/carrier";
bool status = true;
FILE *fp = fopen(file.c_str(), "r");
if (!fp || fgetc(fp) != '1') {
status = false;
}
if (fp) {
fclose(fp);
}
return status;
}
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
bool CTapDriver::Init(const param_map& const_params)
{
#ifndef __linux__
return false;
#else
unordered_map<string, string> params = const_params;
param_map params = const_params;
stringstream s(params["interface"]);
string interface;
while (getline(s, interface, ',')) {
@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
inet = params["inet"];
LOGTRACE("Opening tap device")
spdlog::trace("Opening tap device");
// TAP device initilization
if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) {
LOGERROR("Can't open tun: %s", strerror(errno))
LogErrno("Can't open tun");
return false;
}
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;
string dev = "piscsi0";
strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1);
strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
LOGTRACE("Going to open %s", ifr.ifr_name)
spdlog::trace("Going to open " + string(ifr.ifr_name));
int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
if (ret < 0) {
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno))
LogErrno("Can't ioctl TUNSETIFF");
close(m_hTAP);
return false;
}
LOGTRACE("Return code from ioctl was %d", ret)
spdlog::trace("Return code from ioctl was " + to_string(ret));
const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (ip_fd < 0) {
LOGERROR("Can't open ip socket: %s", strerror(errno))
LogErrno("Can't open ip socket");
close(m_hTAP);
return false;
@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (br_socket_fd < 0) {
LOGERROR("Can't open bridge socket: %s", strerror(errno))
LogErrno("Can't open bridge socket");
close(m_hTAP);
close(ip_fd);
return false;
}
auto cleanUp = [&] (const string& error) {
LogErrno(error);
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
};
// Check if the bridge has already been created
string sys_file = "/sys/class/net/";
sys_file += BRIDGE_NAME;
if (access(sys_file.c_str(), F_OK)) {
LOGINFO("%s is not yet available", BRIDGE_NAME)
// TODO Find an alternative to accessing a file, there is most likely a system call/ioctl
if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) {
spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME);
LOGTRACE("Checking which interface is available for creating the bridge")
string bridge_interface;
for (const string& iface : interfaces) {
if (is_interface_up(iface)) {
LOGTRACE("%s", string("Interface " + iface + " is up").c_str())
bridge_interface = iface;
break;
}
else {
LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str())
}
const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } );
if (it == interfaces.end()) {
return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME);
}
if (bridge_interface.empty()) {
LOGERROR("No interface is up, not creating bridge")
return false;
}
const string bridge_interface = *it;
LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str())
spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface);
if (bridge_interface == "eth0") {
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str())
if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) {
return cleanUp(error);
}
}
else {
string address = inet;
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) {
address = inet.substr(0, separatorPos);
int m;
if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) {
LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str())
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
// long long is required for compatibility with 32 bit platforms
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
}
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
ifreq ifr_a;
ifr_a.ifr_addr.sa_family = AF_INET;
strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ);
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
ifreq ifr_n;
ifr_n.ifr_addr.sa_family = AF_INET;
strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ);
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME)
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
}
else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) {
return cleanUp(error);
}
LOGTRACE("ip link set dev %s up", BRIDGE_NAME)
spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up");
if (!ip_link(ip_fd, BRIDGE_NAME, true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) {
return cleanUp(error);
}
}
else
{
LOGINFO("%s is already available", BRIDGE_NAME)
else {
spdlog::info(BRIDGE_NAME + " is already available");
}
LOGTRACE("ip link set piscsi0 up")
spdlog::trace(">ip link set piscsi0 up");
if (!ip_link(ip_fd, "piscsi0", true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) {
return cleanUp(error);
}
LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME)
spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0");
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) {
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) {
return cleanUp(error);
}
// Get MAC address
LOGTRACE("Getting the MAC address")
spdlog::trace("Getting the MAC address");
ifr.ifr_addr.sa_family = AF_INET;
if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) {
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno))
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
return cleanUp("Can't ioctl SIOCGIFHWADDR");
}
LOGTRACE("Got the MAC")
// Save MAC address
memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size());
@ -353,50 +188,124 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
close(ip_fd);
close(br_socket_fd);
LOGINFO("Tap device %s created", ifr.ifr_name)
spdlog::info("Tap device " + string(ifr.ifr_name) + " created");
return true;
#endif
}
void CTapDriver::OpenDump(const string& path) {
if (m_pcap == nullptr) {
m_pcap = pcap_open_dead(DLT_EN10MB, 65535);
}
if (m_pcap_dumper != nullptr) {
pcap_dump_close(m_pcap_dumper);
}
m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str());
if (m_pcap_dumper == nullptr) {
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap))
throw io_exception("Can't open pcap file");
}
void CTapDriver::CleanUp() const
{
if (m_hTAP != -1) {
if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) {
LogErrno("Can't open bridge socket");
} else {
spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0");
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) {
spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error);
spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge");
}
close(br_socket_fd);
}
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str())
// Release TAP device
close(m_hTAP);
}
}
bool CTapDriver::Enable() const
pair<string, string> CTapDriver::ExtractAddressAndMask(const string& s)
{
string address = s;
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
if (const auto& components = Split(s, '/', 2); components.size() == 2) {
address = components[0];
int m;
if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) {
spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'");
return { "", "" };
}
// long long is required for compatibility with 32 bit platforms
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
}
return { address, netmask };
}
string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface)
{
#ifdef __linux__
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
return "Can't ioctl SIOCBRADDBR";
}
spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface);
if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) {
return error;
}
#endif
return "";
}
string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s)
{
#ifdef __linux__
const auto [address, netmask] = ExtractAddressAndMask(s);
if (address.empty() || netmask.empty()) {
return "Error extracting inet address and netmask";
}
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
return "Can't ioctl SIOCBRADDBR";
}
ifreq ifr_a;
ifr_a.ifr_addr.sa_family = AF_INET;
strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
return "Can't convert '" + address + "' into a network address";
}
ifreq ifr_n;
ifr_n.ifr_addr.sa_family = AF_INET;
strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
return "Can't convert '" + netmask + "' into a netmask";
}
spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME);
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK";
}
#endif
return "";
}
string CTapDriver::IpLink(bool enable) const
{
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__)
const bool result = ip_link(fd, "piscsi0", true);
spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down"));
const string result = ip_link(fd, "piscsi0", enable);
close(fd);
return result;
}
bool CTapDriver::Disable() const
void CTapDriver::Flush() const
{
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__)
const bool result = ip_link(fd, "piscsi0", false);
close(fd);
return result;
}
void CTapDriver::Flush()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
while (PendingPackets()) {
while (HasPendingPackets()) {
array<uint8_t, ETH_FRAME_LEN> m_garbage_buffer;
(void)Receive(m_garbage_buffer.data());
}
@ -409,12 +318,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const
memcpy(mac, m_MacAddr.data(), m_MacAddr.size());
}
//---------------------------------------------------------------------------
//
// Receive
//
//---------------------------------------------------------------------------
bool CTapDriver::PendingPackets() const
bool CTapDriver::HasPendingPackets() const
{
assert(m_hTAP != -1);
@ -424,20 +328,16 @@ bool CTapDriver::PendingPackets() const
fds.events = POLLIN | POLLERR;
fds.revents = 0;
poll(&fds, 1, 0);
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents)
if (!(fds.revents & POLLIN)) {
return false;
} else {
return true;
}
spdlog::trace(to_string(fds.revents) + " revents");
return !(fds.revents & POLLIN);
}
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
uint32_t CTapDriver::Crc32(span<const uint8_t> data) {
uint32_t crc = 0xffffffff;
for (int i = 0; i < length; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
for (const auto d: data) {
crc ^= d;
for (int i = 0; i < 8; i++) {
const uint32_t mask = -(static_cast<int>(crc) & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
@ -445,19 +345,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
return ~crc;
}
int CTapDriver::Receive(uint8_t *buf)
int CTapDriver::Receive(uint8_t *buf) const
{
assert(m_hTAP != -1);
// Check if there is data that can be received
if (!PendingPackets()) {
if (!HasPendingPackets()) {
return 0;
}
// Receive
auto dwReceived = static_cast<uint32_t>(read(m_hTAP, buf, ETH_FRAME_LEN));
if (dwReceived == static_cast<uint32_t>(-1)) {
LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__)
spdlog::warn("Error occured while receiving a packet");
return 0;
}
@ -466,49 +366,28 @@ int CTapDriver::Receive(uint8_t *buf)
// We need to add the Frame Check Status (FCS) CRC back onto the end of the packet.
// The Linux network subsystem removes it, since most software apps shouldn't ever
// need it.
const int crc = Crc32(buf, dwReceived);
const int crc = Crc32(span(buf, dwReceived));
buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF);
buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF);
buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF);
buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF);
LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3])
spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) +
" " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3]));
// Add FCS size to the received message size
dwReceived += 4;
}
if (m_pcap_dumper != nullptr) {
pcap_pkthdr h = {
.ts = {},
.caplen = dwReceived,
.len = dwReceived
};
gettimeofday(&h.ts, nullptr);
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1])
}
// Return the number of bytes
return dwReceived;
}
int CTapDriver::Send(const uint8_t *buf, int len)
int CTapDriver::Send(const uint8_t *buf, int len) const
{
assert(m_hTAP != -1);
if (m_pcap_dumper != nullptr) {
pcap_pkthdr h = {
.ts = {},
.caplen = (bpf_u_int32)len,
.len = (bpf_u_int32)len,
};
gettimeofday(&h.ts, nullptr);
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1])
}
// Start sending
return static_cast<int>(write(m_hTAP, buf, len));
}

View File

@ -11,46 +11,55 @@
#pragma once
#include <pcap/pcap.h>
#include <net/ethernet.h>
#include "devices/device.h"
#include <unordered_map>
#include <vector>
#include <string>
#include <array>
#include <span>
#ifndef ETH_FRAME_LEN
static const int ETH_FRAME_LEN = 1514;
#endif
#ifndef ETH_FCS_LEN
static const int ETH_FCS_LEN = 4;
#endif
using namespace std;
class CTapDriver
{
static constexpr const char *BRIDGE_NAME = "piscsi_bridge";
static const string BRIDGE_NAME;
public:
CTapDriver() = default;
~CTapDriver();
~CTapDriver() = default;
CTapDriver(CTapDriver&) = default;
CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&);
void OpenDump(const string& path); // Capture packets
void GetMacAddr(uint8_t *mac) const;
int Receive(uint8_t *buf);
int Send(const uint8_t *buf, int len);
bool PendingPackets() const; // Check if there are IP packets available
bool Enable() const; // Enable the piscsi0 interface
bool Disable() const; // Disable the piscsi0 interface
void Flush(); // Purge all of the packets that are waiting to be processed
bool Init(const param_map&);
void CleanUp() const;
static uint32_t Crc32(const uint8_t *, int);
void GetMacAddr(uint8_t *) const;
int Receive(uint8_t *) const;
int Send(const uint8_t *, int) const;
bool HasPendingPackets() const; // Check if there are IP packets available
string IpLink(bool) const; // Enable/Disable the piscsi0 interface
void Flush() const; // Purge all of the packets that are waiting to be processed
static uint32_t Crc32(span<const uint8_t>);
private:
static string SetUpEth0(int, const string&);
static string SetUpNonEth0(int, int, const string&);
static pair<string, string> ExtractAddressAndMask(const string&);
array<byte, 6> m_MacAddr; // MAC Address
int m_hTAP = -1; // File handle
pcap_t *m_pcap = nullptr;
pcap_dumper_t *m_pcap_dumper = nullptr;
// Prioritized comma-separated list of interfaces to create the bridge for
vector<string> interfaces;

View File

@ -3,22 +3,24 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_version.h"
#include "device.h"
#include <spdlog/spdlog.h>
#include <cassert>
#include <sstream>
#include <iomanip>
#include <stdexcept>
using namespace std;
Device::Device(PbDeviceType type, int lun) : type(type), lun(lun)
{
ostringstream os;
os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version;
os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version;
revision = os.str();
}
@ -39,7 +41,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v)
{
if (v.empty() || v.length() > 8) {
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters");
}
vendor = v;
@ -48,7 +50,7 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force)
{
if (p.empty() || p.length() > 16) {
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters");
}
// Changing vital product data is not SCSI compliant
@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force)
void Device::SetRevision(const string& r)
{
if (r.empty() || r.length() > 4) {
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters");
}
revision = r;
@ -85,7 +87,7 @@ string Device::GetParam(const string& key) const
return it == params.end() ? "" : it->second;
}
void Device::SetParams(const unordered_map<string, string>& set_params)
void Device::SetParams(const param_map& set_params)
{
params = default_params;
@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map<string, string>& set_params)
for (const auto& [key, value] : set_params) {
// It is assumed that there are default parameters for all supported parameters
if (params.find(key) != params.end()) {
if (params.contains(key)) {
params[key] = value;
}
else {
LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str())
spdlog::warn("Ignored unknown parameter '" + key + "'");
}
}
}

View File

@ -3,20 +3,26 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "shared/log.h"
#include "generated/piscsi_interface.pb.h"
#include "shared/piscsi_util.h"
#include <unordered_map>
#include <string>
using namespace std;
using namespace piscsi_interface;
// A combination of device ID and LUN
using id_set = pair<int, int>;
// The map used for storing/passing device parameters
using param_map = unordered_map<string, string, piscsi_util::StringHash, equal_to<>>;
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
{
const string DEFAULT_VENDOR = "PiSCSI";
@ -60,10 +66,10 @@ class Device //NOSONAR The number of fields and methods is justified, the comple
string revision;
// The parameters the device was created with
unordered_map<string, string> params;
param_map params;
// The default parameters
unordered_map<string, string> default_params;
param_map default_params;
// Sense Key and ASC
// MSB Reserved (0x00)
@ -90,14 +96,15 @@ protected:
int GetStatusCode() const { return status_code; }
string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&);
void SetParams(const param_map&);
public:
virtual ~Device() = default;
PbDeviceType GetType() const { return type; }
const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); }
string GetTypeString() const { return PbDeviceType_Name(type); }
string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); }
bool IsReady() const { return ready; }
virtual void Reset();
@ -131,8 +138,8 @@ public:
bool SupportsFile() const { return supports_file; }
void SupportsParams(bool b) { supports_params = b; }
void SupportsFile(bool b) { supports_file = b; }
unordered_map<string, string> GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
auto GetParams() const { return params; }
void SetDefaultParams(const param_map& p) { default_params = p; }
void SetStatusCode(int s) { status_code = s; }

View File

@ -3,11 +3,11 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_util.h"
#include "shared/network_util.h"
#include "scsihd.h"
#include "scsihd_nec.h"
#include "scsimo.h"
@ -17,15 +17,10 @@
#include "scsi_daynaport.h"
#include "host_services.h"
#include "device_factory.h"
#include <ifaddrs.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <net/if.h>
#include <unistd.h>
using namespace std;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace network_util;
DeviceFactory::DeviceFactory()
{
@ -34,19 +29,11 @@ DeviceFactory::DeviceFactory()
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
sector_sizes[SCCD] = { 512, 2048};
string network_interfaces;
for (const auto& network_interface : GetNetworkInterfaces()) {
if (network_interface.rfind("dummy", 0) == string::npos) {
if (!network_interfaces.empty()) {
network_interfaces += ",";
}
network_interfaces += network_interface;
}
}
const string interfaces = Join(GetNetworkInterfaces(), ",");
default_params[SCBR]["interface"] = network_interfaces;
default_params[SCBR]["interface"] = interfaces;
default_params[SCBR]["inet"] = DEFAULT_IP;
default_params[SCDP]["interface"] = network_interfaces;
default_params[SCDP]["interface"] = interfaces;
default_params[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f";
@ -96,8 +83,8 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun);
} else {
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCHD)->second, false,
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, false,
ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
// Some Apple tools require a particular drive identification
if (ext == "hda") {
@ -109,18 +96,18 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
}
case SCRM:
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCRM)->second, true);
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, true);
device->SetProduct("SCSI HD (REM.)");
break;
case SCMO:
device = make_shared<SCSIMO>(lun, sector_sizes.find(SCMO)->second);
device = make_shared<SCSIMO>(lun, sector_sizes.find(type)->second);
device->SetProduct("SCSI MO");
break;
case SCCD:
device = make_shared<SCSICD>(lun, sector_sizes.find(SCCD)->second,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
device = make_shared<SCSICD>(lun, sector_sizes.find(type)->second,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
device->SetProduct("SCSI CD-ROM");
break;
@ -128,7 +115,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
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->SetDefaultParams(default_params.find(SCBR)->second);
device->SetDefaultParams(default_params.find(type)->second);
break;
case SCDP:
@ -137,7 +124,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
device->SetVendor("Dayna");
device->SetProduct("SCSI/Link");
device->SetRevision("1.4a");
device->SetDefaultParams(default_params.find(SCDP)->second);
device->SetDefaultParams(default_params.find(type)->second);
break;
case SCHS:
@ -150,7 +137,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
case SCLP:
device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER");
device->SetDefaultParams(default_params.find(SCLP)->second);
device->SetDefaultParams(default_params.find(type)->second);
break;
default:
@ -163,44 +150,11 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
{
const auto& it = sector_sizes.find(type);
return it != sector_sizes.end() ? it->second : empty_set;
return it != sector_sizes.end() ? it->second : EMPTY_SET;
}
const unordered_map<string, string>& DeviceFactory::GetDefaultParams(PbDeviceType type) const
const param_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const
{
const auto& it = default_params.find(type);
return it != default_params.end() ? it->second : empty_map;
}
vector<string> DeviceFactory::GetNetworkInterfaces() const
{
vector<string> network_interfaces;
#ifdef __linux__
ifaddrs *addrs;
getifaddrs(&addrs);
ifaddrs *tmp = addrs;
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET &&
strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) {
const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
ifreq ifr = {};
strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here
// Only list interfaces that are up
if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) {
network_interfaces.emplace_back(tmp->ifa_name);
}
close(fd);
}
tmp = tmp->ifa_next;
}
freeifaddrs(addrs);
#endif
return network_interfaces;
return it != default_params.end() ? it->second : EMPTY_PARAM_MAP;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
// The DeviceFactory creates devices based on their type and the image file extension
//
@ -11,8 +11,9 @@
#pragma once
#include "shared/piscsi_util.h"
#include "devices/device.h"
#include <string>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include "generated/piscsi_interface.pb.h"
@ -34,20 +35,19 @@ public:
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, int, const string&) const;
PbDeviceType GetTypeForFile(const string&) const;
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) const;
vector<string> GetNetworkInterfaces() const;
const unordered_map<string, PbDeviceType>& GetExtensionMapping() const { return extension_mapping; }
const param_map& GetDefaultParams(PbDeviceType type) const;
const auto& GetExtensionMapping() const { return extension_mapping; }
private:
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
unordered_map<PbDeviceType, unordered_map<string, string>> default_params;
unordered_map<PbDeviceType, param_map> default_params;
unordered_map<string, PbDeviceType> extension_mapping;
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> extension_mapping;
unordered_map<string, PbDeviceType> device_mapping;
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> device_mapping;
unordered_set<uint32_t> empty_set;
unordered_map<string, string> empty_map;
inline static const unordered_set<uint32_t> EMPTY_SET;
inline static const param_map EMPTY_PARAM_MAP;
};

View File

@ -3,63 +3,52 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "device_logger.h"
using namespace std;
using namespace spdlog;
void DeviceLogger::Trace(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGTRACE("%s", m.c_str())
}
Log(level::trace, message);
}
void DeviceLogger::Debug(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGDEBUG("%s", m.c_str())
}
Log(level::debug, message);
}
void DeviceLogger::Info(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGINFO("%s", m.c_str())
}
Log(level::info, message);
}
void DeviceLogger::Warn(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGWARN("%s", m.c_str())
}
Log(level::warn, message);
}
void DeviceLogger::Error(const string& message) const
{
if (const string m = GetLogMessage(message); !m.empty()) {
LOGERROR("%s", m.c_str())
}
Log(level::err, message);
}
string DeviceLogger::GetLogMessage(const string& message) const
void DeviceLogger::Log(level::level_enum level, const string& message) const
{
if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))
{
if (!message.empty() &&
(log_device_id == -1 ||
(log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) {
if (lun == -1) {
return "(ID " + to_string(id) + ") - " + message;
log(level, "(ID " + to_string(id) + ") - " + message);
}
else {
return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message;
log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message);
}
}
return "";
}
void DeviceLogger::SetIdAndLun(int i, int l)

View File

@ -3,12 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "spdlog/spdlog.h"
#include <string>
using namespace std;
@ -27,14 +28,13 @@ public:
void Warn(const string&) const;
void Error(const string&) const;
string GetLogMessage(const string&) const;
void SetIdAndLun(int, int);
static void SetLogIdAndLun(int, int);
private:
void Log(spdlog::level::level_enum, const string&) const;
int id = -1;
int lun = -1;

View File

@ -23,15 +23,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
Disk::~Disk()
{
// Save disk cache, only if ready
if (IsReady() && cache != nullptr) {
cache->Save();
}
}
bool Disk::Init(const unordered_map<string, string>& params)
bool Disk::Init(const param_map& params)
{
StorageDevice::Init(params);
@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map<string, string>& params)
return true;
}
void Disk::CleanUp()
{
FlushCache();
StorageDevice::CleanUp();
}
void Disk::Dispatch(scsi_command cmd)
{
// Media changes must be reported on the next access, i.e. not only for TEST UNIT READY
@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd)
SetMediumChanged(false);
GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change);
}
else {
PrimaryDevice::Dispatch(cmd);
@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw)
void Disk::FlushCache()
{
if (cache != nullptr) {
if (cache != nullptr && IsReady()) {
cache->Save();
}
}
@ -103,8 +102,8 @@ void Disk::FormatUnit()
CheckReady();
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -115,9 +114,9 @@ void Disk::Read(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) {
GetController()->SetBlocks(blocks);
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
LogTrace("Length is " + to_string(GetController()->GetLength()));
// Set next block
GetController()->SetNext(start + 1);
@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 7) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 12) != 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const
void Disk::Write(access_mode mode) const
{
if (IsProtected()) {
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
throw scsi_exception(sense_key::data_protect, asc::write_protected);
}
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) {
// if BytChk=0
if ((GetController()->GetCmd(1) & 0x02) == 0) {
if ((GetController()->GetCmdByte(1) & 0x02) == 0) {
Seek();
return;
}
// Test reading
GetController()->SetBlocks(blocks);
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
// Set next block
GetController()->SetNext(start + 1);
@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode)
void Disk::StartStopUnit()
{
const bool start = GetController()->GetCmd(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02;
const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmdByte(4) & 0x02;
if (load) {
GetLogger().Trace(start ? "Loading medium" : "Ejecting medium");
LogTrace(start ? "Loading medium" : "Ejecting medium");
}
else {
GetLogger().Trace(start ? "Starting unit" : "Stopping unit");
LogTrace(start ? "Starting unit" : "Stopping unit");
SetStopped(!start);
}
@ -217,12 +216,12 @@ void Disk::StartStopUnit()
if (load) {
if (IsLocked()) {
// Cannot be ejected because it is locked
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
}
// Eject
if (!Eject(false)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
}
}
else {
@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval()
{
CheckReady();
const bool lock = GetController()->GetCmd(4) & 0x01;
const bool lock = GetController()->GetCmdByte(4) & 0x01;
GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium");
LogTrace(lock ? "Locking medium" : "Unlocking medium");
SetLocked(lock);
@ -279,7 +278,7 @@ bool Disk::Eject(bool force)
return status;
}
int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
int Disk::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
{
// Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
@ -315,7 +314,7 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
return size;
}
int Disk::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
int Disk::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
{
// Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
@ -497,27 +496,27 @@ void Disk::AddCachePage(map<int, vector<byte>>& pages, bool changeable) const
pages[8] = buf;
}
int Disk::Read(const vector<int>&, vector<uint8_t>& buf, uint64_t block)
int Disk::Read(span<uint8_t> buf, uint64_t block)
{
assert(block < GetBlockCount());
CheckReady();
if (!cache->ReadSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
throw scsi_exception(sense_key::medium_error, asc::read_fault);
}
return GetSectorSizeInBytes();
}
void Disk::Write(const vector<int>&, const vector<uint8_t>& buf, uint64_t block)
void Disk::Write(span<const uint8_t> buf, uint64_t block)
{
assert(block < GetBlockCount());
CheckReady();
if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) {
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT);
throw scsi_exception(sense_key::medium_error, asc::write_fault);
}
}
@ -553,7 +552,7 @@ void Disk::ReadCapacity10()
CheckReady();
if (GetBlockCount() == 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
}
vector<uint8_t>& buf = GetController()->GetBuffer();
@ -580,7 +579,7 @@ void Disk::ReadCapacity16()
CheckReady();
if (GetBlockCount() == 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
}
vector<uint8_t>& buf = GetController()->GetBuffer();
@ -604,7 +603,7 @@ void Disk::ReadCapacity16()
void Disk::ReadCapacity16_ReadLong16()
{
// The service action determines the actual command
switch (GetController()->GetCmd(1) & 0x1f) {
switch (GetController()->GetCmdByte(1) & 0x1f) {
case 0x10:
ReadCapacity16();
break;
@ -614,7 +613,7 @@ void Disk::ReadCapacity16_ReadLong16()
break;
default:
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
break;
}
}
@ -624,9 +623,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const
const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2);
if (block > GetBlockCount()) {
GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
+ to_string(block));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
}
@ -638,7 +637,7 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
if (mode == RW6 || mode == SEEK6) {
start = GetInt24(GetController()->GetCmd(), 1);
count = GetController()->GetCmd(4);
count = GetController()->GetCmdByte(4);
if (!count) {
count= 0x100;
}
@ -659,13 +658,13 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
stringstream s;
s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start;
GetLogger().Trace(s.str() + ", blocks: " + to_string(count));
LogTrace(s.str() + ", blocks: " + to_string(count));
// Check capacity
if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) {
GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
+ to_string(start) + ", block count " + to_string(count));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
// Do not process 0 blocks
@ -689,10 +688,8 @@ uint32_t Disk::GetSectorSizeInBytes() const
void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes)
{
DeviceFactory device_factory;
if (const auto& sizes = device_factory.GetSectorSizes(GetType());
!sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) {
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) {
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
}
size_shift_count = CalculateShiftCount(size_in_bytes);
@ -706,12 +703,11 @@ uint32_t Disk::GetConfiguredSectorSize() const
bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size)
{
if (unordered_set<uint32_t> sizes = device_factory.GetSectorSizes(GetType());
sizes.find(configured_size) == sizes.end()) {
if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) {
return false;
}
configured_sector_size = configured_size;
configured_sector_size = configured_size;
return true;
}

View File

@ -22,6 +22,7 @@
#include "interfaces/scsi_block_commands.h"
#include "storage_device.h"
#include <string>
#include <span>
#include <unordered_set>
#include <unordered_map>
#include <tuple>
@ -43,18 +44,18 @@ class Disk : public StorageDevice, private ScsiBlockCommands
public:
Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {}
~Disk() override;
using StorageDevice::StorageDevice;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
void Dispatch(scsi_command) override;
bool Eject(bool) override;
virtual void Write(const vector<int>&, const vector<uint8_t>&, uint64_t);
virtual void Write(span<const uint8_t>, uint64_t);
virtual int Read(const vector<int>&, vector<uint8_t>& , uint64_t);
virtual int Read(span<uint8_t> , uint64_t);
uint32_t GetSectorSizeInBytes() const;
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); }
@ -92,8 +93,8 @@ private:
void ValidateBlockAddress(access_mode) const;
tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
static inline const unordered_map<uint32_t, uint32_t> shift_counts =
{ { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } };

View File

@ -30,7 +30,7 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff
bool DiskCache::Save() const
{
// Save valid tracks
return none_of(cache.begin(), cache.end(), [this](const cache_t& c)
return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c)
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path); });
}
@ -46,7 +46,7 @@ shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
return Assign(track);
}
bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
bool DiskCache::ReadSector(span<uint8_t> buf, uint32_t block)
{
shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) {
@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
return disktrk->ReadSector(buf, block & 0xff);
}
bool DiskCache::WriteSector(const vector<uint8_t>& buf, uint32_t block)
bool DiskCache::WriteSector(span<const uint8_t> buf, uint32_t block)
{
shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) {

View File

@ -15,6 +15,7 @@
#pragma once
#include <span>
#include <array>
#include <memory>
#include <string>
@ -41,8 +42,8 @@ public:
// Access
bool Save() const; // Save and release all
bool ReadSector(vector<uint8_t>&, uint32_t); // Sector Read
bool WriteSector(const vector<uint8_t>&, uint32_t); // Sector Write
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read
bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write
private:

View File

@ -14,8 +14,10 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "disk_track.h"
#include <spdlog/spdlog.h>
#include <cassert>
#include <cstdlib>
#include <fstream>
DiskTrack::~DiskTrack()
@ -75,7 +77,7 @@ bool DiskTrack::Load(const string& path)
if (dt.buffer == nullptr) {
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
LOGWARN("posix_memalign failed")
spdlog::warn("posix_memalign failed");
}
dt.length = length;
}
@ -88,14 +90,14 @@ bool DiskTrack::Load(const string& path)
if (dt.length != static_cast<uint32_t>(length)) {
free(dt.buffer);
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
LOGWARN("posix_memalign failed")
spdlog::warn("posix_memalign failed");
}
dt.length = length;
}
// Resize and clear changemap
dt.changemap.resize(dt.sectors);
fill(dt.changemap.begin(), dt.changemap.end(), false);
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
ifstream in(path, ios::binary);
if (in.fail()) {
@ -209,13 +211,13 @@ bool DiskTrack::Save(const string& path)
}
// Drop the change flag and exit
fill(dt.changemap.begin(), dt.changemap.end(), false);
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
dt.changed = false;
return true;
}
bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
bool DiskTrack::ReadSector(span<uint8_t> buf, int sec) const
{
assert(sec >= 0 && sec < 0x100);
@ -238,7 +240,7 @@ bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
return true;
}
bool DiskTrack::WriteSector(const vector<uint8_t>& buf, int sec)
bool DiskTrack::WriteSector(span<const uint8_t> buf, int sec)
{
assert((sec >= 0) && (sec < 0x100));
assert(!dt.raw);

View File

@ -16,6 +16,8 @@
#pragma once
#include <cstdlib>
#include <cstdint>
#include <span>
#include <vector>
#include <string>
@ -52,8 +54,8 @@ private:
bool Save(const string& path);
// Read / Write
bool ReadSector(vector<uint8_t>&, int) const; // Sector Read
bool WriteSector(const vector<uint8_t>& buf, int); // Sector Write
bool ReadSector(span<uint8_t>, int) const; // Sector Read
bool WriteSector(span<const uint8_t> buf, int); // Sector Write
int GetTrack() const { return dt.track; } // Get track
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Host Services with realtime clock and shutdown support
//
@ -25,11 +25,13 @@
#include "scsi_command_util.h"
#include "host_services.h"
#include <algorithm>
#include <chrono>
using namespace std::chrono;
using namespace scsi_defs;
using namespace scsi_command_util;
bool HostServices::Init(const unordered_map<string, string>& params)
bool HostServices::Init(const param_map& params)
{
ModePageDevice::Init(params);
@ -49,13 +51,13 @@ void HostServices::TestUnitReady()
vector<uint8_t> HostServices::InquiryInternal() const
{
return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
return HandleInquiry(device_type::processor, scsi_level::spc_3, false);
}
void HostServices::StartStopUnit() const
{
const bool start = GetController()->GetCmd(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02;
const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmdByte(4) & 0x02;
if (!start) {
if (load) {
@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const
GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI);
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
int HostServices::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
return size;
}
int HostServices::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
int HostServices::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map<int, vector<byte>>& pages, bool chan
pages[32] = vector<byte>(10);
if (!changeable) {
time_t t = time(nullptr);
const auto now = system_clock::now();
const time_t t = system_clock::to_time_t(now);
tm localtime;
localtime_r(&t, &localtime);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Host Services with realtime clock and shutdown support
//
@ -12,6 +12,7 @@
#pragma once
#include "mode_page_device.h"
#include <span>
#include <vector>
#include <map>
@ -23,7 +24,7 @@ public:
explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {}
~HostServices() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
vector<uint8_t> InquiryInternal() const override;
void TestUnitReady() override;
@ -48,8 +49,8 @@ private:
};
void StartStopUnit() const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const;
};

View File

@ -1,27 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Abstraction for the DaynaPort and the host bridge, which both have methods for writing byte sequences
//
//---------------------------------------------------------------------------
#pragma once
#include <vector>
using namespace std;
class ByteWriter
{
public:
ByteWriter() = default;
virtual ~ByteWriter() = default;
virtual bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) = 0;
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// A basic device with mode page support, to be used for subclassing
//
@ -20,7 +20,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
bool ModePageDevice::Init(const unordered_map<string, string>& params)
bool ModePageDevice::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map<string, string>& params)
return true;
}
int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
int ModePageDevice::AddModePages(cdb_t cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
{
const int max_length = length - offset;
if (max_length < 0) {
@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
stringstream s;
s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page;
GetLogger().Trace(s.str());
LogTrace(s.str());
// Mode page data mapped to the respective page numbers, C++ maps are ordered by key
map<int, vector<byte>> pages;
@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
if (pages.empty()) {
s << "Unsupported mode page $" << page;
GetLogger().Trace(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
LogTrace(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Holds all mode page data
@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
}
if (static_cast<int>(result.size()) > max_size) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const auto size = static_cast<int>(min(static_cast<size_t>(max_length), result.size()));
@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const
EnterDataInPhase();
}
void ModePageDevice::ModeSelect(scsi_command, const vector<int>&, const vector<uint8_t>&, int) const
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int) const
{
// There is no default implementation of MDOE SELECT
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
// There is no default implementation of MODE SELECT
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
}
void ModePageDevice::ModeSelect6() const
{
SaveParametersCheck(GetController()->GetCmd(4));
SaveParametersCheck(GetController()->GetCmdByte(4));
}
void ModePageDevice::ModeSelect10() const
@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const
void ModePageDevice::SaveParametersCheck(int length) const
{
if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
GetController()->SetLength(length);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -11,25 +11,25 @@
#include "primary_device.h"
#include <string>
#include <span>
#include <vector>
#include <map>
// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages
class ModePageDevice : public PrimaryDevice
{
public:
using PrimaryDevice::PrimaryDevice;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
virtual void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const;
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const;
protected:
bool SupportsSaveParameters() const { return supports_save_parameters; }
void SupportsSaveParameters(bool b) { supports_save_parameters = b; }
int AddModePages(const vector<int>&, vector<uint8_t>&, int, int, int) const;
int AddModePages(cdb_t, vector<uint8_t>&, int, int, int) const;
virtual void SetUpModePages(map<int, vector<byte>>&, int, bool) const = 0;
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const {
// Nothing to add by default
@ -39,8 +39,8 @@ private:
bool supports_save_parameters = false;
virtual int ModeSense6(const vector<int>&, vector<uint8_t>&) const = 0;
virtual int ModeSense10(const vector<int>&, vector<uint8_t>&) const = 0;
virtual int ModeSense6(cdb_t, vector<uint8_t>&) const = 0;
virtual int ModeSense10(cdb_t, vector<uint8_t>&) const = 0;
void ModeSense6() const;
void ModeSense10() const;

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -17,7 +17,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
bool PrimaryDevice::Init(const unordered_map<string, string>& params)
bool PrimaryDevice::Init(const param_map& params)
{
// Mandatory SCSI primary commands
AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); });
@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map<string, string>& params)
return true;
}
void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute)
void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute)
{
commands[opcode] = execute;
commands[cmd] = execute;
}
void PrimaryDevice::Dispatch(scsi_command cmd)
@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd)
s << "$" << setfill('0') << setw(2) << hex << static_cast<int>(cmd);
if (const auto& it = commands.find(cmd); it != commands.end()) {
GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) +
" (" + s.str() + ")");
LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")");
it->second();
}
else {
GetLogger().Trace("Received unsupported command: " + s.str());
LogTrace("Received unsupported command: " + s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
}
}
@ -67,18 +66,14 @@ void PrimaryDevice::Reset()
int PrimaryDevice::GetId() const
{
if (GetController() == nullptr) {
GetLogger().Error("Device is missing its controller");
}
return GetController() != nullptr ? GetController()->GetTargetId() : -1;
}
void PrimaryDevice::SetController(shared_ptr<AbstractController> c)
void PrimaryDevice::SetController(AbstractController *c)
{
controller = c;
logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun());
device_logger.SetIdAndLun(GetId(), GetLun());
}
void PrimaryDevice::TestUnitReady()
@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady()
void PrimaryDevice::Inquiry()
{
// EVPD and page code check
if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
vector<uint8_t> buf = InquiryInternal();
const vector<uint8_t> buf = InquiryInternal();
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetInt16(GetController()->GetCmd(), 3)));
@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry()
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
// Report if the device does not support the requested LUN
if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
GetLogger().Trace("LUN is not available");
if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
LogTrace("LUN is not available");
// Signal that the requested LUN does not exist
GetController()->GetBuffer().data()[0] = 0x7f;
@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry()
void PrimaryDevice::ReportLuns()
{
// Only SELECT REPORT mode 0 is supported
if (GetController()->GetCmd(2)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(2)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6);
@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense()
lun = 0;
// Do not raise an exception here because the rest of the code must be executed
GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
GetController()->Error(sense_key::illegal_request, asc::invalid_lun);
GetController()->SetStatus(status::GOOD);
GetController()->SetStatus(status::good);
}
vector<byte> buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense();
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmd(4)));
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmdByte(4)));
memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length);
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense()
void PrimaryDevice::SendDiagnostic()
{
// Do not support PF bit
if (GetController()->GetCmd(1) & 0x10) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(1) & 0x10) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Do not support parameter list
if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) {
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterStatusPhase();
@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady()
// Not ready if reset
if (IsReset()) {
SetReset(false);
GetLogger().Trace("Device in reset");
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET);
LogTrace("Device in reset");
throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset);
}
// Not ready if it needs attention
if (IsAttn()) {
SetAttn(false);
GetLogger().Trace("Device in needs attention");
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
LogTrace("Device in needs attention");
throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change);
}
// Return status if not ready
if (!IsReady()) {
GetLogger().Trace("Device not ready");
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
LogTrace("Device not ready");
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
}
GetLogger().Trace("Device is ready");
LogTrace("Device is ready");
}
vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const
@ -223,8 +218,8 @@ vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level,
buf[0] = static_cast<uint8_t>(type);
buf[1] = is_removable ? 0x80 : 0x00;
buf[2] = static_cast<uint8_t>(level);
buf[3] = level >= scsi_level::SCSI_2 ?
static_cast<uint8_t>(scsi_level::SCSI_2) : static_cast<uint8_t>(scsi_level::SCSI_1_CCS);
buf[3] = level >= scsi_level::scsi_2 ?
static_cast<uint8_t>(scsi_level::scsi_2) : static_cast<uint8_t>(scsi_level::scsi_1_ccs);
buf[4] = 0x1F;
// Padded vendor, product, revision
@ -237,7 +232,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
{
// Return not ready only if there are no errors
if (!GetStatusCode() && !IsReady()) {
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
}
// Set 18 bytes including extended sense data
@ -257,14 +252,14 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
<< "Status $" << static_cast<int>(GetController()->GetStatus())
<< ", Sense Key $" << static_cast<int>(buf[2])
<< ", ASC $" << static_cast<int>(buf[12]);
GetLogger().Trace(s.str());
LogTrace(s.str());
return buf;
}
bool PrimaryDevice::WriteByteSequence(vector<uint8_t>&, uint32_t)
bool PrimaryDevice::WriteByteSequence(span<const uint8_t>)
{
GetLogger().Error("Writing bytes is not supported by this device");
LogError("Writing bytes is not supported by this device");
return false;
}
@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit()
reserving_initiator = GetController()->GetInitiatorId();
if (reserving_initiator != -1) {
GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator));
LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator));
}
else {
GetLogger().Trace("Reserved device for unknown initiator");
LogTrace("Reserved device for unknown initiator");
}
EnterStatusPhase();
@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit()
void PrimaryDevice::ReleaseUnit()
{
if (reserving_initiator != -1) {
GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator));
LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator));
}
else {
GetLogger().Trace("Released device reserved by unknown initiator");
LogTrace("Released device reserved by unknown initiator");
}
DiscardReservation();
@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr
}
if (initiator_id != -1) {
GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
}
else {
GetLogger().Trace("Unknown initiator tries to access reserved device");
LogTrace("Unknown initiator tries to access reserved device");
}
return false;

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// A device implementing mandatory SCSI primary commands, to be used for subclassing
//
@ -18,6 +18,7 @@
#include "device_logger.h"
#include <string>
#include <unordered_map>
#include <span>
#include <functional>
using namespace std;
@ -25,6 +26,8 @@ using namespace scsi_defs;
class PrimaryDevice: private ScsiPrimaryCommands, public Device
{
friend class AbstractController;
using operation = function<void()>;
public:
@ -32,15 +35,16 @@ public:
PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {}
~PrimaryDevice() override = default;
virtual bool Init(const unordered_map<string, string>&);
virtual bool Init(const param_map&);
virtual void CleanUp() {
// Override if cleanup work is required for a derived device
};
virtual void Dispatch(scsi_command);
int GetId() const override;
void SetController(shared_ptr<AbstractController>);
virtual bool WriteByteSequence(vector<uint8_t>&, uint32_t);
virtual bool WriteByteSequence(span<const uint8_t>);
int GetSendDelay() const { return send_delay; }
@ -57,8 +61,6 @@ protected:
void AddCommand(scsi_command, const operation&);
const DeviceLogger& GetLogger() const { return logger; }
vector<uint8_t> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;
virtual vector<uint8_t> InquiryInternal() const = 0;
void CheckReady();
@ -69,16 +71,24 @@ protected:
void ReserveUnit() override;
void ReleaseUnit() override;
void EnterStatusPhase() const { controller.lock()->Status(); }
void EnterDataInPhase() const { controller.lock()->DataIn(); }
void EnterDataOutPhase() const { controller.lock()->DataOut(); }
void EnterStatusPhase() const { controller->Status(); }
void EnterDataInPhase() const { controller->DataIn(); }
void EnterDataOutPhase() const { controller->DataOut(); }
inline shared_ptr<AbstractController> GetController() const { return controller.lock(); }
auto GetController() const { return controller; }
void LogTrace(const string& s) const { device_logger.Trace(s); }
void LogDebug(const string& s) const { device_logger.Debug(s); }
void LogInfo(const string& s) const { device_logger.Info(s); }
void LogWarn(const string& s) const { device_logger.Warn(s); }
void LogError(const string& s) const { device_logger.Error(s); }
private:
static const int NOT_RESERVED = -2;
void SetController(AbstractController *);
void TestUnitReady() override;
void RequestSense() override;
void ReportLuns() override;
@ -86,9 +96,11 @@ private:
vector<byte> HandleRequestSense() const;
DeviceLogger logger;
// TODO Try to remove this field and use controller->Log*() methods instead
DeviceLogger device_logger;
weak_ptr<AbstractController> controller;
// Owned by the controller manager
AbstractController *controller = nullptr;
unordered_map<scsi_command, operation> commands;

View File

@ -3,31 +3,31 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "device_logger.h"
#include "scsi_command_util.h"
#include <spdlog/spdlog.h>
#include <cstring>
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace scsi_defs;
void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector<int>& cdb,
const vector<uint8_t>& buf, int length, int sector_size)
string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length, int sector_size)
{
assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10);
assert(length >= 0);
string result;
// PF
if (!(cdb[1] & 0x10)) {
// Vendor-specific parameters (SCSI-1) are not supported.
// Do not report an error in order to support Apple's HD SC Setup.
return;
return result;
}
// Skip block descriptors
@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
// Parse the pages
while (length > 0) {
// Format device page
if (int page = buf[offset]; page == 0x03) {
if (const int page = buf[offset]; page == 0x03) {
if (length < 14) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
// With this page the sector size for a subsequent FORMAT can be selected, but only very few
@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
if (GetInt16(buf, offset + 12) != sector_size) {
// With piscsi it is not possible to permanently (by formatting) change the sector size,
// because the size is an externally configurable setting only
logger.Warn("In order to change the sector size use the -b option when launching piscsi");
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
spdlog::warn("In order to change the sector size use the -b option when launching piscsi");
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
has_valid_page_code = true;
@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
else {
stringstream s;
s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page;
logger.Warn(s.str());
result = s.str();
}
// Advance to the next page
@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
}
if (!has_valid_page_code) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
}
return result;
}
void scsi_command_util::EnrichFormatPage(map<int, vector<byte>>& pages, bool changeable, int sector_size)
@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map<int, vector<byte>>& pages, bo
// No changeable area
if (!changeable) {
const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA));
}
}
int scsi_command_util::GetInt16(const vector<uint8_t>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt16(const vector<int>& buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (buf[offset] << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt24(const vector<int>& buf, int offset)
int scsi_command_util::GetInt24(span <const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 2);
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
}
uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
uint32_t scsi_command_util::GetInt32(span <const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
(static_cast<uint32_t>(buf[offset + 2]) << 8) | static_cast<uint32_t>(buf[offset + 3]);
}
uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
uint64_t scsi_command_util::GetInt64(span<const int> buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 7);
@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
(static_cast<uint64_t>(buf[offset + 6]) << 8) | static_cast<uint64_t>(buf[offset + 7]);
}
void scsi_command_util::SetInt16(vector<byte>& buf, int offset, int value)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<byte>(value >> 8);
buf[offset + 1] = static_cast<byte>(value);
}
void scsi_command_util::SetInt32(vector<byte>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<byte>(value >> 24);
buf[offset + 1] = static_cast<byte>(value >> 16);
buf[offset + 2] = static_cast<byte>(value >> 8);
buf[offset + 3] = static_cast<byte>(value);
}
void scsi_command_util::SetInt16(vector<uint8_t>& buf, int offset, int value)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<uint8_t>(value >> 8);
buf[offset + 1] = static_cast<uint8_t>(value);
}
void scsi_command_util::SetInt32(vector<uint8_t>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<uint8_t>(value >> 24);
buf[offset + 1] = static_cast<uint8_t>(value >> 16);
buf[offset + 2] = static_cast<uint8_t>(value >> 8);
buf[offset + 3] = static_cast<uint8_t>(value);
}
void scsi_command_util::SetInt64(vector<uint8_t>& buf, int offset, uint64_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 7);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Shared code for SCSI command implementations
//
@ -12,27 +12,49 @@
#pragma once
#include "shared/scsi.h"
#include <cstdint>
#include <cassert>
#include <span>
#include <vector>
#include <map>
using namespace std;
class DeviceLogger;
namespace scsi_command_util
{
void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int, int);
string ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int, int);
void EnrichFormatPage(map<int, vector<byte>>&, bool, int);
void AddAppleVendorModePage(map<int, vector<byte>>&, bool);
int GetInt16(const vector<uint8_t>&, int);
int GetInt16(const vector<int>&, int);
int GetInt24(const vector<int>&, int);
uint32_t GetInt32(const vector<int>&, int);
uint64_t GetInt64(const vector<int>&, int);
void SetInt16(vector<byte>&, int, int);
void SetInt32(vector<byte>&, int, uint32_t);
void SetInt16(vector<uint8_t>&, int, int);
void SetInt32(vector<uint8_t>&, int, uint32_t);
int GetInt16(const auto buf, int offset)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
};
template<typename T>
void SetInt16(vector<T>& buf, int offset, int value)
{
assert(buf.size() > static_cast<size_t>(offset) + 1);
buf[offset] = static_cast<T>(value >> 8);
buf[offset + 1] = static_cast<T>(value);
}
template<typename T>
void SetInt32(vector<T>& buf, int offset, uint32_t value)
{
assert(buf.size() > static_cast<size_t>(offset) + 3);
buf[offset] = static_cast<T>(value >> 24);
buf[offset + 1] = static_cast<T>(value >> 16);
buf[offset + 2] = static_cast<T>(value >> 8);
buf[offset + 3] = static_cast<T>(value);
}
int GetInt24(span<const int>, int);
uint32_t GetInt32(span <const int>, int);
uint64_t GetInt64(span<const int>, int);
void SetInt64(vector<uint8_t>&, int, uint64_t);
}

View File

@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun)
SupportsParams(true);
}
bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
bool SCSIDaynaPort::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -55,15 +55,13 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
SetSendDelay(DAYNAPORT_READ_HEADER_SZ);
m_bTapEnable = m_tap.Init(GetParams());
if(!m_bTapEnable){
GetLogger().Error("Unable to open the TAP interface");
if (!m_bTapEnable) {
// Not terminating on regular Linux PCs is helpful for testing
#if !defined(__x86_64__) && !defined(__X86__)
return false;
return false;
#endif
} else {
GetLogger().Trace("Tap interface created");
LogTrace("Tap interface created");
}
Reset();
@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
return true;
}
void SCSIDaynaPort::CleanUp()
{
m_tap.CleanUp();
}
vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
{
vector<uint8_t> buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false);
vector<uint8_t> buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false);
// The Daynaport driver for the Mac expects 37 bytes: Increase additional length and
// add a vendor-specific byte in order to satisfy this driver.
@ -116,14 +119,14 @@ vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
// - The SCSI/Link apparently has about 6KB buffer space for packets.
//
//---------------------------------------------------------------------------
int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t) const
{
int rx_packet_size = 0;
const auto response = (scsi_resp_read_t*)buf.data();
const int requested_length = cdb[4];
GetLogger().Trace("Read maximum length: " + to_string(requested_length));
LogTrace("Read maximum length: " + to_string(requested_length));
// At startup the host may send a READ(6) command with a sector count of 1 to read the root sector.
// We should respond by going into the status mode with a code of 0x02.
@ -146,13 +149,13 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
// If we didn't receive anything, return size of 0
if (rx_packet_size <= 0) {
GetLogger().Trace("No packet received");
LogTrace("No packet received");
response->length = 0;
response->flags = read_data_flags_t::e_no_more_data;
return DAYNAPORT_READ_HEADER_SZ;
}
GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
// This is a very basic filter to prevent unnecessary packets from
// being sent to the SCSI initiator.
@ -187,11 +190,11 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
for (int i = 0 ; i < 6; i++) {
s << " $" << static_cast<int>(response->data[i]);
}
GetLogger().Debug(s.str());
LogDebug(s.str());
// If there are pending packets to be processed, we'll tell the host that the read
// length was 0.
if (!m_tap.PendingPackets()) {
if (!m_tap.HasPendingPackets()) {
response->length = 0;
response->flags = read_data_flags_t::e_no_more_data;
return DAYNAPORT_READ_HEADER_SZ;
@ -216,7 +219,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
size = 64;
}
SetInt16(buf, 0, size);
SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00);
SetInt32(buf, 2, m_tap.HasPendingPackets() ? 0x10 : 0x00);
// Return the packet size + 2 for the length + 4 for the flag field
// The CRC was already appended by the ctapdriver
@ -249,25 +252,23 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
// XX XX ... is the actual packet
//
//---------------------------------------------------------------------------
bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
bool SCSIDaynaPort::Write(cdb_t cdb, span<const uint8_t> buf) const
{
const int data_format = cdb[5];
int data_length = GetInt16(cdb, 3);
if (data_format == 0x00) {
if (const int data_format = cdb[5]; data_format == 0x00) {
const int data_length = GetInt16(cdb, 3);
m_tap.Send(buf.data(), data_length);
GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
}
else if (data_format == 0x80) {
// The data length is specified in the first 2 bytes of the payload
data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
const int data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
m_tap.Send(&(buf.data()[4]), data_length);
GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
}
else {
stringstream s;
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
GetLogger().Warn(s.str());
LogWarn(s.str());
}
GetController()->SetBlocks(0);
@ -291,7 +292,7 @@ bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uin
// - long #3: frames lost
//
//---------------------------------------------------------------------------
int SCSIDaynaPort::RetrieveStats(const vector<int>& cdb, vector<uint8_t>& buf) const
int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector<uint8_t>& buf) const
{
memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats));
@ -304,7 +305,7 @@ void SCSIDaynaPort::TestUnitReady()
EnterStatusPhase();
}
void SCSIDaynaPort::Read6()
void SCSIDaynaPort::Read6() const
{
// Get record number and block number
const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff;
@ -312,17 +313,17 @@ void SCSIDaynaPort::Read6()
// If any commands have a bogus control value, they were probably not
// generated by the DaynaPort driver so ignore them
if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) {
GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5)));
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) {
LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5)));
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
stringstream s;
s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record;
GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks()));
LogTrace(s.str());
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record));
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
LogTrace("Length is " + to_string(GetController()->GetLength()));
// Set next block
GetController()->SetNext(record + 1);
@ -335,7 +336,7 @@ void SCSIDaynaPort::Write6() const
// Ensure a sufficient buffer size (because it is not transfer for each block)
GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE);
const int data_format = GetController()->GetCmd(5);
const int data_format = GetController()->GetCmdByte(5);
if (data_format == 0x00) {
GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3));
@ -346,15 +347,15 @@ void SCSIDaynaPort::Write6() const
else {
stringstream s;
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
GetLogger().Warn(s.str());
LogWarn(s.str());
}
stringstream s;
s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format;
GetLogger().Trace(s.str());
LogTrace(s.str());
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Set next block
@ -406,7 +407,7 @@ void SCSIDaynaPort::SetInterfaceMode() const
// Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address"
GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer()));
switch(GetController()->GetCmd(5)){
switch(GetController()->GetCmdByte(5)){
case CMD_SCSILINK_SETMODE:
// Not implemented, do nothing
EnterStatusPhase();
@ -419,21 +420,21 @@ void SCSIDaynaPort::SetInterfaceMode() const
default:
stringstream s;
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5);
GetLogger().Warn(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5);
LogWarn(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
break;
}
}
void SCSIDaynaPort::SetMcastAddr() const
{
GetController()->SetLength(GetController()->GetCmd(4));
GetController()->SetLength(GetController()->GetCmdByte(4));
if (GetController()->GetLength() == 0) {
stringstream s;
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2);
GetLogger().Warn(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2);
LogWarn(s.str());
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
EnterDataOutPhase();
@ -451,27 +452,27 @@ void SCSIDaynaPort::SetMcastAddr() const
// seconds
//
//---------------------------------------------------------------------------
void SCSIDaynaPort::EnableInterface()
void SCSIDaynaPort::EnableInterface() const
{
if (GetController()->GetCmd(5) & 0x80) {
if (!m_tap.Enable()) {
GetLogger().Warn("Unable to enable the DaynaPort Interface");
if (GetController()->GetCmdByte(5) & 0x80) {
if (const string error = m_tap.IpLink(true); !error.empty()) {
LogWarn("Unable to enable the DaynaPort Interface: " + error);
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
m_tap.Flush();
GetLogger().Info("The DaynaPort interface has been ENABLED");
LogInfo("The DaynaPort interface has been ENABLED");
}
else {
if (!m_tap.Disable()) {
GetLogger().Warn("Unable to disable the DaynaPort Interface");
if (const string error = m_tap.IpLink(false); !error.empty()) {
LogWarn("Unable to disable the DaynaPort Interface: " + error);
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
GetLogger().Info("The DaynaPort interface has been DISABLED");
LogInfo("The DaynaPort interface has been DISABLED");
}
EnterStatusPhase();

View File

@ -29,10 +29,11 @@
#pragma once
#include "interfaces/byte_writer.h"
#include "primary_device.h"
#include "ctapdriver.h"
#include <net/ethernet.h>
#include <string>
#include <span>
#include <unordered_map>
#include <array>
@ -41,29 +42,30 @@
// DaynaPort SCSI Link
//
//===========================================================================
class SCSIDaynaPort : public PrimaryDevice, public ByteWriter
class SCSIDaynaPort : public PrimaryDevice
{
public:
explicit SCSIDaynaPort(int);
~SCSIDaynaPort() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
// Commands
vector<uint8_t> InquiryInternal() const override;
int Read(const vector<int>&, vector<uint8_t>&, uint64_t);
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
int Read(cdb_t, vector<uint8_t>&, uint64_t) const;
bool Write(cdb_t, span<const uint8_t>) const;
int RetrieveStats(const vector<int>&, vector<uint8_t>&) const;
int RetrieveStats(cdb_t, vector<uint8_t>&) const;
void TestUnitReady() override;
void Read6();
void Read6() const;
void Write6() const;
void RetrieveStatistics() const;
void SetInterfaceMode() const;
void SetMcastAddr() const;
void EnableInterface();
void EnableInterface() const;
static const int DAYNAPORT_BUFFER_SIZE = 0x1000000;

View File

@ -20,7 +20,6 @@
#include "scsi_command_util.h"
#include "scsi_host_bridge.h"
#include <arpa/inet.h>
#include <array>
using namespace std;
using namespace scsi_defs;
@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun)
SupportsParams(true);
}
bool SCSIBR::Init(const unordered_map<string, string>& params)
bool SCSIBR::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -43,10 +42,8 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); });
#ifdef __linux__
// TAP Driver Generation
m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){
GetLogger().Error("Unable to open the TAP interface");
return false;
}
#endif
@ -70,9 +67,14 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
#endif
}
void SCSIBR::CleanUp()
{
tap.CleanUp();
}
vector<uint8_t> SCSIBR::InquiryInternal() const
{
vector<uint8_t> buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false);
vector<uint8_t> buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false);
// The bridge returns more additional bytes than the other devices
buf.resize(0x1F + 8 + 5);
@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady()
EnterStatusPhase();
}
int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
int SCSIBR::GetMessage10(cdb_t cdb, vector<uint8_t>& buf)
{
// Type
const int type = cdb[2];
@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
return 0;
}
bool SCSIBR::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
bool SCSIBR::ReadWrite(cdb_t cdb, vector<uint8_t>& buf)
{
// Type
const int type = cdb[2];
@ -252,7 +254,7 @@ void SCSIBR::GetMessage10()
GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer()));
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Set next block
@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const
{
GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6));
if (GetController()->GetLength() <= 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Ensure a sufficient buffer size (because it is not a transfer for each block)
@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector<uint8_t>& mac) const
return static_cast<int>(mac_addr.size());
}
void SCSIBR::SetMacAddr(const vector<uint8_t>& mac)
void SCSIBR::SetMacAddr(span<const uint8_t> mac)
{
memcpy(mac_addr.data(), mac.data(), mac_addr.size());
}
@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector<uint8_t>& buf, int index)
packet_enable = false;
}
void SCSIBR::SendPacket(const vector<uint8_t>& buf, int len)
void SCSIBR::SendPacket(span<const uint8_t> buf, int len) const
{
tap.Send(buf.data(), len);
}
@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp);
Human68k::capacity_t cap;
Human68k::capacity_t cap = {};
fsresult = fs.GetCapacity(nUnit, &cap);
cap.freearea = htons(cap.freearea);
@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp);
Human68k::dpb_t dpb;
Human68k::dpb_t dpb = {};
fsresult = fs.GetDPB(nUnit, &dpb);
dpb.sector_size = htons(dpb.sector_size);

View File

@ -17,16 +17,16 @@
//---------------------------------------------------------------------------
#pragma once
#include "interfaces/byte_writer.h"
#include "primary_device.h"
#include "ctapdriver.h"
#include "cfilesystem.h"
#include <string>
#include <span>
#include <array>
using namespace std;
class SCSIBR : public PrimaryDevice, public ByteWriter
class SCSIBR : public PrimaryDevice
{
static constexpr const array<uint8_t, 6> bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
@ -35,12 +35,13 @@ public:
explicit SCSIBR(int);
~SCSIBR() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
// Commands
vector<uint8_t> InquiryInternal() const override;
int GetMessage10(const vector<int>&, vector<uint8_t>&);
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
int GetMessage10(cdb_t, vector<uint8_t>&);
bool ReadWrite(cdb_t, vector<uint8_t>&);
void TestUnitReady() override;
void GetMessage10();
void SendMessage10() const;
@ -48,16 +49,16 @@ public:
private:
int GetMacAddr(vector<uint8_t>&) const; // Get MAC address
void SetMacAddr(const vector<uint8_t>&); // Set MAC address
void SetMacAddr(span<const uint8_t>); // Set MAC address
void ReceivePacket(); // Receive a packet
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
void SendPacket(const vector<uint8_t>&, int); // Send a packet
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
void SendPacket(span<const uint8_t>, int) const; // Send a packet
CTapDriver tap; // TAP driver
bool m_bTapEnable = false; // TAP valid flag
array<uint8_t, 6> mac_addr = {}; // MAC Address
array<uint8_t, 6> mac_addr = {}; // MAC Address
int packet_len = 0; // Receive packet size
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
bool packet_enable = false; // Received packet valid
int ReadFsResult(vector<uint8_t>&) const; // Read filesystem (result code)

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
@ -32,7 +32,6 @@
#include "shared/piscsi_exceptions.h"
#include "scsi_command_util.h"
#include "scsi_printer.h"
#include <sys/stat.h>
#include <filesystem>
using namespace std;
@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun)
SupportsParams(true);
}
bool SCSIPrinter::Init(const unordered_map<string, string>& params)
bool SCSIPrinter::Init(const param_map& params)
{
PrimaryDevice::Init(params);
@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); });
if (GetParam("cmd").find("%f") == string::npos) {
GetLogger().Trace("Missing filename specifier %f");
LogTrace("Missing filename specifier %f");
return false;
}
@ -82,20 +81,20 @@ void SCSIPrinter::TestUnitReady()
vector<uint8_t> SCSIPrinter::InquiryInternal() const
{
return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false);
return HandleInquiry(device_type::printer, scsi_level::scsi_2, false);
}
void SCSIPrinter::Print()
{
const uint32_t length = GetInt24(GetController()->GetCmd(), 2);
GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed");
LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed");
if (length > GetController()->GetBuffer().size()) {
GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected");
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
GetController()->SetLength(length);
@ -107,9 +106,9 @@ void SCSIPrinter::Print()
void SCSIPrinter::SynchronizeBuffer()
{
if (!out.is_open()) {
GetLogger().Warn("Nothing to print");
LogWarn("Nothing to print");
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
string cmd = GetParam("cmd");
@ -118,25 +117,24 @@ void SCSIPrinter::SynchronizeBuffer()
cmd.replace(file_position, 2, filename);
error_code error;
LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
GetLogger().Debug("Executing '" + cmd + "'");
LogDebug("Executing print command '" + cmd + "'");
if (system(cmd.c_str())) {
GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured");
LogError("Printing file '" + filename + "' failed, the printing system might not be configured");
Cleanup();
CleanUp();
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
Cleanup();
CleanUp();
EnterStatusPhase();
}
bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
{
if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end());
@ -145,7 +143,7 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
// There is no C++ API that generates a file with a unique name
const int fd = mkstemp(f.data());
if (fd == -1) {
GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
return false;
}
close(fd);
@ -154,21 +152,23 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
out.open(filename, ios::binary);
if (out.fail()) {
throw scsi_exception(sense_key::ABORTED_COMMAND);
throw scsi_exception(sense_key::aborted_command);
}
GetLogger().Trace("Created printer output file '" + filename + "'");
LogTrace("Created printer output file '" + filename + "'");
}
GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'");
LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'");
out.write((const char*)buf.data(), length);
out.write((const char *)buf.data(), buf.size());
return !out.fail();
}
void SCSIPrinter::Cleanup()
void SCSIPrinter::CleanUp()
{
PrimaryDevice::CleanUp();
if (out.is_open()) {
out.close();

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
//
@ -15,6 +15,7 @@
#include <fstream>
#include <string>
#include <unordered_map>
#include <span>
using namespace std;
@ -29,11 +30,12 @@ public:
explicit SCSIPrinter(int);
~SCSIPrinter() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void CleanUp() override;
vector<uint8_t> InquiryInternal() const override;
bool WriteByteSequence(vector<uint8_t>&, uint32_t) override;
bool WriteByteSequence(span<const uint8_t>) override;
private:
@ -44,8 +46,6 @@ private:
void Print() override;
void SynchronizeBuffer();
void Cleanup();
string file_template;
string filename;

View File

@ -21,7 +21,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
: Disk(SCCD, lun), scsi_level(level)
{
SetSectorSizes(sector_sizes);
@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::
SetLockable(true);
}
bool SCSICD::Init(const unordered_map<string, string>& params)
bool SCSICD::Init(const param_map& params)
{
Disk::Init(params);
@ -116,7 +116,7 @@ void SCSICD::OpenIso()
if (rawfile) {
if (size % 2536) {
GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
+ to_string(size) + " bytes");
}
@ -165,7 +165,7 @@ void SCSICD::ReadToc()
vector<uint8_t> SCSICD::InquiryInternal() const
{
return HandleInquiry(device_type::CD_ROM, scsi_level, true);
return HandleInquiry(device_type::cd_rom, scsi_level, true);
}
void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map<int, vector<byte>>& pages, int page, bool changea
}
}
int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
int SCSICD::Read(span<uint8_t> buf, uint64_t block)
{
CheckReady();
const int index = SearchTrack(static_cast<int>(block));
if (index < 0) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
}
assert(tracks[index]);
@ -241,15 +241,15 @@ int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
}
assert(dataindex >= 0);
return Disk::Read(cdb, buf, block);
return Disk::Read(buf, block);
}
int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
int SCSICD::ReadTocInternal(cdb_t cdb, vector<uint8_t>& buf)
{
CheckReady();
// If ready, there is at least one track
assert(tracks.size() > 0);
assert(!tracks.empty());
assert(tracks[0]);
// Get allocation length, clear buffer
@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
const int last = tracks[tracks.size() - 1]->GetTrackNo();
// Except for AA
if (cdb[6] > last && cdb[6] != 0xaa) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Check start index
@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
// AA if not found or internal error
if (!tracks[index]) {
if (cdb[6] != 0xaa) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
}
// Returns the final LBA+1 because it is AA

View File

@ -17,20 +17,23 @@
#include "cd_track.h"
#include "disk.h"
#include "interfaces/scsi_mmc_commands.h"
#include <span>
#include <vector>
#include <map>
class SCSICD : public Disk, private ScsiMmcCommands
{
public:
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::SCSI_2);
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::scsi_2);
~SCSICD() override = default;
bool Init(const unordered_map<string, string>&) override;
bool Init(const param_map&) override;
void Open() override;
vector<uint8_t> InquiryInternal() const override;
int Read(const vector<int>&, vector<uint8_t>&, uint64_t) override;
int Read(span<uint8_t>, uint64_t) override;
protected:
@ -39,7 +42,7 @@ protected:
private:
int ReadTocInternal(const vector<int>&, vector<uint8_t>&);
int ReadTocInternal(cdb_t, vector<uint8_t>&);
void AddCDROMPage(map<int, vector<byte>>&, bool) const;
void AddCDDAPage(map<int, vector<byte>>&, bool) const;

View File

@ -5,7 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -14,8 +14,8 @@
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "scsihd.h"
#include "scsi_command_util.h"
#include "scsihd.h"
using namespace scsi_command_util;
@ -70,7 +70,7 @@ void SCSIHD::Open()
{
assert(!IsReady());
off_t size = GetFileSize();
const off_t size = GetFileSize();
// Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
@ -81,12 +81,15 @@ void SCSIHD::Open()
vector<uint8_t> SCSIHD::InquiryInternal() const
{
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable());
return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable());
}
void SCSIHD::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
{
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {
LogWarn(result);
}
}
void SCSIHD::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const

View File

@ -5,7 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -18,6 +18,7 @@
#include "shared/scsi.h"
#include "disk.h"
#include <string>
#include <span>
#include <vector>
#include <map>
@ -27,7 +28,7 @@ class SCSIHD : public Disk
public:
SCSIHD(int, 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;
void FinalizeSetup(off_t);
@ -36,7 +37,7 @@ public:
// Commands
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;

View File

@ -1,14 +1,15 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2021-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -47,7 +48,7 @@ void SCSIHD_NEC::Open()
FinalizeSetup(image_offset);
}
pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
pair<int, int> SCSIHD_NEC::SetParameters(span<const char> data, int size)
{
array<uint8_t, 512> root_sector = {};
memcpy(root_sector.data(), data.data(), root_sector.size());
@ -107,12 +108,12 @@ pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)");
}
return make_pair(image_size, sector_size);
return { image_size, sector_size };
}
vector<uint8_t> SCSIHD_NEC::InquiryInternal() const
{
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false);
return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false);
}
void SCSIHD_NEC::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const

View File

@ -47,7 +47,7 @@ protected:
private:
pair<int, int> SetParameters(const array<char, 512>&, int);
pair<int, int> SetParameters(span<const char>, int);
static int GetInt16LittleEndian(const uint8_t *);
static int GetInt32LittleEndian(const uint8_t *);

View File

@ -1,14 +1,15 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk(SCMO
SetSectorSizes(sector_sizes);
// 128 MB, 512 bytes per sector, 248826 sectors
geometries[512 * 248826] = make_pair(512, 248826);
geometries[512 * 248826] = { 512, 248826 };
// 230 MB, 512 bytes per block, 446325 sectors
geometries[512 * 446325] = make_pair(512, 446325);
geometries[512 * 446325] = { 512, 446325 };
// 540 MB, 512 bytes per sector, 1041500 sectors
geometries[512 * 1041500] = make_pair(512, 1041500);
geometries[512 * 1041500] = { 512, 1041500 };
// 640 MB, 20248 bytes per sector, 310352 sectors
geometries[2048 * 310352] = make_pair(2048, 310352);
geometries[2048 * 310352] = { 2048, 310352 };
SetProtectable(true);
SetRemovable(true);
@ -61,7 +62,7 @@ void SCSIMO::Open()
vector<uint8_t> SCSIMO::InquiryInternal() const
{
return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true);
return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true);
}
void SCSIMO::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map<int, vector<byte>>& pages, bool) const
// Do not report update blocks
}
void SCSIMO::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
{
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
!result.empty()) {
LogWarn(result);
}
}
//

View File

@ -5,6 +5,7 @@
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
@ -15,6 +16,7 @@
#pragma once
#include "disk.h"
#include <span>
#include <vector>
#include <map>
@ -30,7 +32,7 @@ public:
void Open() override;
vector<uint8_t> InquiryInternal() const override;
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
protected:

View File

@ -3,15 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h"
#include "storage_device.h"
#include <sys/stat.h>
#include <unistd.h>
#include <filesystem>
using namespace std;
using namespace filesystem;
@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type,
SetStoppable(true);
}
void StorageDevice::CleanUp()
{
UnreserveFile();
ModePageDevice::CleanUp();
}
void StorageDevice::SetFilename(string_view f)
{
filename = filesystem::path(f);
// Permanently write-protected
SetReadOnly(IsReadOnlyFile());
SetProtectable(!IsReadOnlyFile());
if (IsReadOnlyFile()) {
SetProtected(false);
}
}
void StorageDevice::ValidateFile()
{
if (blocks == 0) {
throw io_exception(string(GetTypeString()) + " device has 0 blocks");
}
if (!exists(path(filename))) {
throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist");
if (!exists(filename)) {
throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist");
}
if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) {
throw io_exception("Drive capacity cannot exceed 2 TiB");
throw io_exception("Image files > 2 TiB are not supported");
}
// TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp)
if (access(filename.c_str(), W_OK)) {
if (IsReadOnlyFile()) {
// Permanently write-protected
SetReadOnly(true);
SetProtectable(false);
@ -50,28 +69,28 @@ void StorageDevice::ValidateFile()
SetReady(true);
}
void StorageDevice::ReserveFile(const string& file, int id, int lun) const
void StorageDevice::ReserveFile() const
{
assert(!file.empty());
assert(reserved_files.find(file) == reserved_files.end());
assert(!filename.empty());
assert(!reserved_files.contains(filename.string()));
reserved_files[file] = make_pair(id, lun);
reserved_files[filename.string()] = { GetId(), GetLun() };
}
void StorageDevice::UnreserveFile()
{
reserved_files.erase(filename);
reserved_files.erase(filename.string());
filename = "";
filename.clear();
}
id_set StorageDevice::GetIdsForReservedFile(const string& file)
{
if (const auto& it = reserved_files.find(file); it != reserved_files.end()) {
return make_pair(it->second.first, it->second.second);
return { it->second.first, it->second.second };
}
return make_pair(-1, -1);
return { -1, -1 };
}
void StorageDevice::UnreserveAll()
@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll()
reserved_files.clear();
}
bool StorageDevice::FileExists(const string& file)
bool StorageDevice::FileExists(string_view file)
{
return exists(file);
return exists(path(file));
}
bool StorageDevice::IsReadOnlyFile() const
@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const
off_t StorageDevice::GetFileSize() const
{
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
if (struct stat st; !stat(filename.c_str(), &st)) {
return st.st_size;
try {
return file_size(filename);
}
catch (const filesystem_error& e) {
throw io_exception("Can't get size of '" + filename.string() + "': " + e.what());
}
throw io_exception("Can't get size of '" + filename + "'");
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
// The base class for all mass storage devices with image file support
//
@ -11,14 +11,14 @@
#pragma once
#include "shared/piscsi_util.h"
#include "mode_page_device.h"
#include <unordered_map>
#include <string>
#include <filesystem>
using namespace std;
using id_set = pair<int, int>;
class StorageDevice : public ModePageDevice
{
public:
@ -26,24 +26,27 @@ public:
StorageDevice(PbDeviceType, int);
~StorageDevice() override = default;
void CleanUp() override;
virtual void Open() = 0;
string GetFilename() const { return filename; }
void SetFilename(string_view f) { filename = f; }
string GetFilename() const { return filename.string(); }
void SetFilename(string_view);
uint64_t GetBlockCount() const { return blocks; }
void ReserveFile(const string&, int, int) const;
void ReserveFile() const;
void UnreserveFile();
// TODO Remove this method, it is only used by the unit tests
static void UnreserveAll();
static bool FileExists(const string&);
bool IsReadOnlyFile() const;
static bool FileExists(string_view);
void SetMediumChanged(bool b) { medium_changed = b; }
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& r) { reserved_files = r; }
static auto GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>>& r)
{ reserved_files = r; }
static id_set GetIdsForReservedFile(const string&);
protected:
@ -58,13 +61,14 @@ protected:
private:
// Total number of blocks
bool IsReadOnlyFile() const;
uint64_t blocks = 0;
string filename;
filesystem::path filename;
bool medium_changed = false;
// The list of image files in use and the IDs and LUNs using these files
static inline unordered_map<string, id_set> reserved_files;
static inline unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>> reserved_files;
};

View File

@ -12,6 +12,7 @@
#pragma once
#include "shared/scsi.h"
#include <cstdint>
#include <string>
using namespace scsi_defs;

View File

@ -13,7 +13,7 @@
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include <spdlog/spdlog.h>
#include <array>
#include <sys/ioctl.h>
#include <sys/mman.h>
@ -280,8 +280,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
if (actmode == mode_e::TARGET) {
for (i = 0; i < count; i++) {
if (i == delay_after_bytes) {
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
(int)delay_after_bytes)
spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " +
to_string(delay_after_bytes) + " bytes");
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
}
@ -325,8 +325,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
for (i = 0; i < count; i++) {
if (i == delay_after_bytes) {
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
(int)delay_after_bytes)
spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " +
to_string(delay_after_bytes) + " bytes");
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
}
@ -392,7 +392,7 @@ bool GPIOBUS::PollSelectEvent()
return false;
#else
GPIO_FUNCTION_TRACE
LOGTRACE("%s", __PRETTY_FUNCTION__)
spdlog::trace(__PRETTY_FUNCTION__);
errno = 0;
int prev_mode = -1;
if (SBC_Version::IsBananaPi()) {
@ -401,12 +401,12 @@ bool GPIOBUS::PollSelectEvent()
}
if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) {
LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__)
spdlog::warn("epoll_wait failed");
return false;
}
if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) {
LOGWARN("%s read failed", __PRETTY_FUNCTION__)
spdlog::warn("read failed");
return false;
}
@ -455,4 +455,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast)
// We timed out waiting for the signal
return false;
}
}

View File

@ -214,7 +214,7 @@ class GPIOBUS : public BUS
// SEL signal event request
struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it
// epoll file descriptor
int epfd; // NOSONAR: This protected so derived classes can access it
int epfd = 0; // NOSONAR: This is protected so derived classes can access it
#endif
};

View File

@ -34,7 +34,9 @@
#include <memory>
#include <sstream>
#include <string.h>
#ifdef __linux__
#include <sys/epoll.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
@ -43,7 +45,7 @@
#include "hal/pi_defs/bpi-gpio.h"
#include "hal/sunxi_utils.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "hal/log.h"
#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0])))
@ -62,7 +64,7 @@ bool GPIOBUS_BananaM2p::Init(mode_e mode)
int gpio_bank = SunXI::GPIO_BANK(gpio_num);
if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) {
if (std::ranges::find(gpio_banks, gpio_bank) != gpio_banks.end()) {
LOGTRACE("Duplicate bank: %d", gpio_bank)
} else {
@ -723,9 +725,9 @@ void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast)
}
if (sunxi_gpio_state == SunXI::HIGH)
pio->DAT &= ~(1 << num);
pio->DAT = pio->DAT & ~(1 << num);
else
pio->DAT |= (1 << num);
pio->DAT = pio->DAT | (1 << num);
}
void GPIOBUS_BananaM2p::DisableIRQ()
@ -978,9 +980,9 @@ void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value)
}
if (value == 0)
pio->DAT &= ~(1 << num);
pio->DAT = pio->DAT & ~(1 << num);
else
pio->DAT |= (1 << num);
pio->DAT = pio->DAT | (1 << num);
}
void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array<uint32_t, 12> &mask, array<uint32_t, 12> &value)
@ -1043,11 +1045,11 @@ void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud)
else if (pud == SunXI::PUD_UP)
*(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP;
else // pud == PUD_OFF
*(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;
*(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3;
SunXI::short_wait();
*(gpio_map + clk_offset) = 1 << shift;
SunXI::short_wait();
*(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;
*(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3;
*(gpio_map + clk_offset) = 0;
}

View File

@ -16,7 +16,6 @@
#include "hal/pi_defs/bpi-gpio.h"
#include "hal/sbc_version.h"
#include "hal/sunxi_utils.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>

View File

@ -15,7 +15,7 @@
#include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus_virtual.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
#include <spdlog/spdlog.h>
using namespace std;
@ -29,13 +29,13 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
// Also make Init() private.
SBC_Version::Init();
if (SBC_Version::IsBananaPi()) {
LOGTRACE("Creating GPIOBUS_BananaM2p")
spdlog::trace("Creating GPIOBUS_BananaM2p");
return_ptr = make_unique<GPIOBUS_BananaM2p>();
} else if (SBC_Version::IsRaspberryPi()) {
LOGTRACE("Creating GPIOBUS_Raspberry")
spdlog::trace("Creating GPIOBUS_Raspberry");
return_ptr = make_unique<GPIOBUS_Raspberry>();
} else {
LOGINFO("Creating Virtual GPIOBUS")
spdlog::trace("Creating Virtual GPIOBUS");
return_ptr = make_unique<GPIOBUS_Virtual>();
}
if (!return_ptr->Init(mode)) {
@ -43,7 +43,7 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
}
return_ptr->Reset();
} catch (const invalid_argument&) {
LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?")
spdlog::error("Exception while trying to initialize GPIO bus. Are you running as root?");
return_ptr = nullptr;
}

View File

@ -18,10 +18,12 @@
#include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "hal/log.h"
#include <map>
#include <string.h>
#include <cstring>
#ifdef __linux__
#include <sys/epoll.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>
@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
// Open /dev/mem
int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1) {
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
return false;
}
// Map peripheral region memory
void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
if (map == MAP_FAILED) {
LOGERROR("Error: Unable to map memory: %s", strerror(errno))
spdlog::error("Error: Unable to map memory: "+ string(strerror(errno)));
close(fd);
return false;
}

View File

@ -13,7 +13,6 @@
#include "hal/data_sample_raspberry.h"
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>

View File

@ -13,12 +13,14 @@
#include "hal/gpiobus_virtual.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "hal/log.h"
#include <cstddef>
#include <map>
#include <memory>
#include <string.h>
#ifdef __linux__
#include <sys/epoll.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/time.h>

View File

@ -13,7 +13,6 @@
#include "hal/data_sample_raspberry.h"
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "shared/scsi.h"
#include <map>
@ -171,4 +170,4 @@ class GPIOBUS_Virtual final : public GPIOBUS
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
int fd_shm, fd_log;
#endif
};
};

View File

@ -9,9 +9,11 @@
//
//---------------------------------------------------------------------------
// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead.
#pragma once
#include "spdlog/spdlog.h"
#include <spdlog/spdlog.h>
static const int LOGBUF_SIZE = 512;

View File

@ -10,7 +10,7 @@
//---------------------------------------------------------------------------
#include "sbc_version.h"
#include "shared/log.h"
#include "log.h"
#include <fstream>
#include <iostream>
#include <sstream>

View File

@ -11,6 +11,7 @@
#pragma once
#include <cstdint>
#include <map>
#include <string>

View File

@ -40,12 +40,6 @@
using namespace std;
static const string BLACK = "\033[30m"; /* Black */
static const string RED = "\033[31m"; /* Red */
static const string GREEN = "\033[32m"; /* Green */
static const string YELLOW = "\033[33m"; /* Yellow */
static const string BLUE = "\033[34m"; /* Blue */
static const string MAGENTA = "\033[35m"; /* Magenta */
static const string CYAN = "\033[36m"; /* Cyan */
static const string WHITE = "\033[37m"; /* White */
@ -62,4 +56,4 @@ void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs)
printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]);
printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB,
WHITE.c_str());
}
}

View File

@ -14,13 +14,12 @@
#include "hal/systimer.h"
#include "hal/systimer_allwinner.h"
#include "hal/systimer_raspberry.h"
#include <spdlog/spdlog.h>
#include <sys/mman.h>
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
bool SysTimer::initialized = false;
bool SysTimer::is_allwinnner = false;
bool SysTimer::is_raspberry = false;
@ -29,7 +28,7 @@ std::unique_ptr<PlatformSpecificTimer> SysTimer::systimer_ptr;
void SysTimer::Init()
{
LOGTRACE("%s", __PRETTY_FUNCTION__)
spdlog::trace(__PRETTY_FUNCTION__);
if (!initialized) {
if (SBC_Version::IsRaspberryPi()) {

View File

@ -14,7 +14,7 @@
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "hal/log.h"
const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem";
@ -71,14 +71,14 @@ void SysTimer_AllWinner::enable_hs_timer()
hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF)
hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX;
// Select prescale value of 1, continuouse mode
// Select prescale value of 1, continuous mode
hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1;
// Set reload bit
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD;
hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_RELOAD;
// Enable HSTimer
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN;
hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_EN;
}
// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to

View File

@ -12,6 +12,7 @@
//---------------------------------------------------------------------------
#include "hal/systimer_raspberry.h"
#include <spdlog/spdlog.h>
#include <memory>
#include <sys/ioctl.h>
#include <sys/mman.h>
@ -19,8 +20,6 @@
#include "hal/gpiobus.h"
#include "hal/sbc_version.h"
#include "shared/log.h"
// System timer address
volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr;
// ARM timer address
@ -40,14 +39,14 @@ void SysTimer_Raspberry::Init()
// Open /dev/mem
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd == -1) {
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
return;
}
// Map peripheral region memory
void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr);
if (map == MAP_FAILED) {
LOGERROR("Error: Unable to map memory")
spdlog::error("Error: Unable to map memory");
close(mem_fd);
return;
}

View File

@ -10,11 +10,10 @@
//---------------------------------------------------------------------------
#include "monitor/sm_core.h"
#include "hal/data_sample.h"
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "monitor/sm_reports.h"
#include "shared/log.h"
#include "hal/log.h"
#include "shared/piscsi_version.h"
#include "shared/piscsi_util.h"
#include <climits>
@ -77,28 +76,28 @@ void ScsiMon::ParseArguments(const vector<char *> &args)
void ScsiMon::PrintHelpText(const vector<char *> &args) const
{
LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0])
LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data")
LOGINFO(" If -i option is not specified, scsimon will read the gpio pins")
LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size)
LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)")
LOGINFO(" will be appended to this file name")
spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]");
spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data");
spdlog::info(" If -i option is not specified, scsimon will read the gpio pins");
spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size));
spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)");
spdlog::info(" will be appended to this file name");
}
void ScsiMon::Banner() const
{
if (import_data) {
LOGINFO("Reading input file: %s", input_file_name.c_str())
spdlog::info("Reading input file: " + input_file_name);
} else {
LOGINFO("Reading live data from the GPIO pins")
LOGINFO(" Connection type : %s", CONNECT_DESC.c_str())
spdlog::info("Reading live data from the GPIO pins");
spdlog::info(" Connection type: " + CONNECT_DESC);
}
LOGINFO(" Data buffer size: %u", buff_size)
LOGINFO(" ")
LOGINFO("Generating output files:")
LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name.c_str())
LOGINFO(" %s - JSON file with raw data", json_file_name.c_str())
LOGINFO(" %s - HTML file with summary of commands", html_file_name.c_str())
spdlog::info(" Data buffer size: " + to_string(buff_size));
spdlog::info(" ");
spdlog::info("Generating output files:");
spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave");
spdlog::info(" " + json_file_name + " - JSON file with raw data");
spdlog::info(" " + html_file_name + " - HTML file with summary of commands");
}
bool ScsiMon::Init()
@ -116,7 +115,7 @@ bool ScsiMon::Init()
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
if (bus == nullptr) {
LOGERROR("Unable to intiailize the GPIO bus. Exiting....")
spdlog::error("Unable to intiailize the GPIO bus. Exiting....");
return false;
}
@ -128,14 +127,14 @@ bool ScsiMon::Init()
void ScsiMon::Cleanup() const
{
if (!import_data) {
LOGINFO("Stopping data collection....")
spdlog::info("Stopping data collection ...");
}
LOGINFO(" ")
LOGINFO("Generating %s...", vcd_file_name.c_str())
spdlog::info(" ");
spdlog::info("Generating " + vcd_file_name + "...");
scsimon_generate_value_change_dump(vcd_file_name, data_buffer);
LOGINFO("Generating %s...", json_file_name.c_str())
spdlog::info("Generating " + json_file_name + "...");
scsimon_generate_json(json_file_name, data_buffer);
LOGINFO("Generating %s...", html_file_name.c_str())
spdlog::info("Generating " + html_file_name + "...");
scsimon_generate_html(html_file_name, data_buffer);
bus->Cleanup();
@ -179,15 +178,15 @@ int ScsiMon::run(const vector<char *> &args)
if (import_data) {
data_idx = scsimon_read_json(input_file_name, data_buffer);
if (data_idx > 0) {
LOGDEBUG("Read %d samples from %s", data_idx, input_file_name.c_str())
spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'");
Cleanup();
}
exit(0);
}
LOGINFO(" ")
LOGINFO("Now collecting data.... Press CTRL-C to stop.")
LOGINFO(" ")
spdlog::info(" ");
spdlog::info("Now collecting data.... Press CTRL-C to stop.");
spdlog::info(" ");
// Initialize
int ret = 0;
@ -219,12 +218,12 @@ int ScsiMon::run(const vector<char *> &args)
while (running) {
loop_count++;
if (loop_count > LLONG_MAX - 1) {
LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.")
spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating.");
running = false;
}
if (data_idx >= (buff_size - 2)) {
LOGINFO("Internal data buffer is full. SCSIMON is terminating.")
spdlog::info("Internal data buffer is full. SCSIMON is terminating.");
running = false;
}
@ -249,15 +248,13 @@ int ScsiMon::run(const vector<char *> &args)
timersub(&stop_time, &start_time, &time_diff);
elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec);
LOGINFO("%s", ("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) +
" seconds")
.c_str())
LOGINFO("%s", ("Collected " + to_string(data_idx) + " changes").c_str())
spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) +
" seconds");
spdlog::info("Collected " + to_string(data_idx) + " changes");
ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count;
LOGINFO("%s", ("Read the SCSI bus " + to_string(loop_count) + " times with an average of " +
to_string(ns_per_loop) + " ns for each read")
.c_str())
spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " +
to_string(ns_per_loop) + " ns for each read");
Cleanup();

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -13,6 +13,7 @@
#include "hal/data_sample.h"
#include <memory>
#include <vector>
#include <atomic>
using namespace std;
@ -37,7 +38,7 @@ class ScsiMon
static void KillHandler(int);
static inline volatile bool running;
static inline atomic<bool> running;
shared_ptr<BUS> bus;

View File

@ -9,7 +9,7 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "hal/log.h"
#include "shared/piscsi_version.h"
#include "sm_reports.h"
#include <iostream>
@ -173,7 +173,7 @@ static void print_html_data(ofstream& html_fp, const vector<shared_ptr<DataSampl
void scsimon_generate_html(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
{
LOGINFO("Creating HTML report file (%s)", filename.c_str())
spdlog::info("Creating HTML report file (" + filename + ")");
ofstream html_ofstream;

View File

@ -10,7 +10,7 @@
//---------------------------------------------------------------------------
#include "hal/data_sample_raspberry.h"
#include "shared/log.h"
#include "hal/log.h"
#include "sm_reports.h"
#include "string.h"
#include <fstream>
@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector<shared_ptr<DataSa
sample_count++;
if (sample_count == UINT32_MAX) {
LOGWARN("Maximum number of samples read. Some data may not be included.")
spdlog::warn("Maximum number of samples read. Some data may not be included.");
break;
}
}
@ -69,7 +69,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector<shared_ptr<DataSa
//---------------------------------------------------------------------------
void scsimon_generate_json(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
{
LOGTRACE("Creating JSON file (%s)", filename.c_str())
spdlog::trace("Creating JSON file (" + filename + ")");
ofstream json_ofstream;
json_ofstream.open(filename.c_str(), ios::out);

View File

@ -11,7 +11,7 @@
#include "hal/data_sample.h"
#include "hal/gpiobus.h"
#include "shared/log.h"
#include "hal/log.h"
#include "sm_core.h"
#include "sm_reports.h"
#include <fstream>
@ -77,7 +77,7 @@ static void vcd_output_if_changed_byte(ofstream &fp, uint8_t data, int pin, char
void scsimon_generate_value_change_dump(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
{
LOGTRACE("Creating Value Change Dump file (%s)", filename.c_str())
spdlog::trace("Creating Value Change Dump file (" + filename + ")");
ofstream vcd_ofstream;
vcd_ofstream.open(filename.c_str(), ios::out);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -13,7 +13,7 @@ using namespace std;
int main(int argc, char *argv[])
{
const vector<char *> args(argv, argv + argc);
vector<char *> args(argv, argv + argc);
return Piscsi().run(args);
}

View File

@ -3,24 +3,50 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "generated/piscsi_interface.pb.h"
#include "shared/piscsi_exceptions.h"
#include "shared/protobuf_util.h"
#include "command_context.h"
#include <spdlog/spdlog.h>
#include <iostream>
using namespace std;
using namespace piscsi_interface;
using namespace protobuf_util;
void CommandContext::Cleanup()
bool CommandContext::ReadCommand()
{
if (fd != -1) {
close(fd);
fd = -1;
// Read magic string
array<byte, 6> magic;
if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) {
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
throw io_exception("Invalid magic");
}
// Fetch the command
DeserializeMessage(fd, command);
return true;
}
return false;
}
void CommandContext::WriteResult(const PbResult& result) const
{
// The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool
if (fd != -1) {
SerializeMessage(fd, result);
}
}
void CommandContext::WriteSuccessResult(PbResult& result) const
{
result.set_status(true);
WriteResult(result);
}
bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2,
@ -33,7 +59,7 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error
const string& arg2, const string& arg3) const
{
// For the logfile always use English
LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3));
return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false);
}
@ -42,17 +68,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
{
// Do not log twice if logging has already been done in the localized error handling above
if (log && !status && !msg.empty()) {
LOGERROR("%s", msg.c_str())
spdlog::error(msg);
}
if (fd == -1) {
if (!msg.empty()) {
if (status) {
cerr << "Error: " << msg << endl;
}
else {
cout << msg << endl;
}
cerr << "Error: " << msg << endl;
}
}
else {
@ -60,8 +81,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
serializer.SerializeMessage(fd, result);
WriteResult(result);
}
return status;
}
bool CommandContext::ReturnSuccessStatus() const
{
return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true);
}
bool CommandContext::ReturnErrorStatus(const string& msg) const
{
return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true);
}

View File

@ -3,15 +3,14 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "generated/piscsi_interface.pb.h"
#include "localizer.h"
#include "shared/protobuf_serializer.h"
#include "generated/piscsi_interface.pb.h"
#include <string>
using namespace std;
@ -19,27 +18,36 @@ using namespace piscsi_interface;
class CommandContext
{
const ProtobufSerializer serializer;
const Localizer localizer;
string locale;
int fd;
public:
CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {}
CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {}
explicit CommandContext(int f) : fd(f) {}
~CommandContext() = default;
void Cleanup();
const ProtobufSerializer& GetSerializer() const { return serializer; }
int GetFd() const { return fd; }
void SetFd(int f) { fd = f; }
bool IsValid() const { return fd != -1; }
string GetDefaultFolder() const { return default_folder; }
void SetDefaultFolder(string_view f) { default_folder = f; }
bool ReadCommand();
void WriteResult(const PbResult&) const;
void WriteSuccessResult(PbResult&) const;
const PbCommand& GetCommand() const { return command; }
bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const;
bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const;
bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const;
bool ReturnSuccessStatus() const;
bool ReturnErrorStatus(const string&) const;
private:
bool ReturnStatus(bool, const string&, PbErrorCode, bool) const;
const Localizer localizer;
PbCommand command;
string default_folder;
string locale;
int fd = -1;
};

View File

@ -3,15 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "localizer.h"
#include <cassert>
#include <string>
#include <algorithm>
#include <regex>
using namespace std;
@ -31,12 +29,12 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida");
Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作");
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido");
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1");
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido");
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID");
@ -51,26 +49,26 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息");
Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved");
Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert");
Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat");
@ -84,21 +82,21 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant");
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente");
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'");
@ -199,11 +197,11 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN");
Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。");
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed");
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen");
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3");
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló");
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败");
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed");
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen");
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 ");
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló");
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar");
@ -235,17 +233,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value
// Safeguards against empty messages, duplicate entries and unsupported locales
assert(locale.size());
assert(value.size());
assert(supported_languages.find(locale) != supported_languages.end());
assert(supported_languages.contains(locale));
assert(localized_messages[locale][key].empty());
localized_messages[locale][key] = value;
}
string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2,
const string &arg3) const
{
string locale_lower = locale;
transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower);
string locale_lower;
ranges::transform(locale, back_inserter(locale_lower), ::tolower);
auto it = localized_messages.find(locale_lower);
if (it == localized_messages.end()) {
@ -268,9 +265,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri
}
string message = m->second;
message = regex_replace(message, regex("%1"), arg1);
message = regex_replace(message, regex("%2"), arg2);
message = regex_replace(message, regex("%3"), arg3);
message = regex_replace(message, regex1, arg1);
message = regex_replace(message, regex2, arg2);
message = regex_replace(message, regex3, arg3);
return message;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
// Message localization support. Currently only for messages with up to 3 string parameters.
//
@ -11,9 +11,11 @@
#pragma once
#include "shared/piscsi_util.h"
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <regex>
using namespace std;
@ -64,8 +66,12 @@ public:
private:
void Add(LocalizationKey, const string&, string_view);
unordered_map<string, unordered_map<LocalizationKey, string>> localized_messages;
unordered_map<string, unordered_map<LocalizationKey, string>, piscsi_util::StringHash, equal_to<>> localized_messages;
// Supported locales, always lower case
unordered_set<string> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
unordered_set<string, piscsi_util::StringHash, equal_to<>> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
const regex regex1 = regex("%1");
const regex regex2 = regex("%2");
const regex regex3 = regex("%3");
};

View File

@ -1,57 +1,52 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
// Copyright (C) 2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/config.h"
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_serializer.h"
#include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "devices/device_logger.h"
#include "devices/device_factory.h"
#include "devices/storage_device.h"
#include "hal/gpiobus_factory.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "piscsi/piscsi_executor.h"
#include "piscsi/piscsi_core.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include <spdlog/spdlog.h>
#include <netinet/in.h>
#include <csignal>
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <list>
#include <filesystem>
#include <vector>
using namespace std;
using namespace filesystem;
using namespace spdlog;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace protobuf_util;
using namespace scsi_defs;
void Piscsi::Banner(const vector<char *>& args) const
void Piscsi::Banner(span<char *> args) const
{
cout << piscsi_util::Banner("(Backend Service)");
cout << "Connection type: " << CONNECT_DESC << '\n' << flush;
if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){
cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n";
cout << " ID is SCSI device ID (0-7).\n";
cout << " LUN is the optional logical unit (0-31).\n";
cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n";
cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n";
cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n";
cout << " Image type is detected based on file extension if no explicit type is specified.\n";
cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n";
@ -69,59 +64,57 @@ void Piscsi::Banner(const vector<char *>& args) const
}
}
bool Piscsi::InitBus() const
bool Piscsi::InitBus()
{
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
if (bus == nullptr) {
return false;
}
auto b = bus;
controller_manager = make_shared<ControllerManager>(*b);
auto c = controller_manager;
executor = make_shared<PiscsiExecutor>(piscsi_image, *c);
executor = make_unique<PiscsiExecutor>(piscsi_image, *bus, controller_manager);
return true;
}
void Piscsi::Cleanup()
void Piscsi::CleanUp()
{
executor->DetachAll();
if (service.IsRunning()) {
service.Stop();
}
service.Cleanup();
executor->DetachAll();
bus->Cleanup();
}
void Piscsi::ReadAccessToken(const string& filename) const
void Piscsi::ReadAccessToken(const path& filename)
{
struct stat st;
if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) {
throw parser_exception("Can't access token file '" + filename + "'");
if (error_code error; !is_regular_file(filename, error)) {
throw parser_exception("Access token file '" + filename.string() + "' must be a regular file");
}
if (st.st_uid || st.st_gid) {
throw parser_exception("Access token file '" + filename + "' must be owned by root");
if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) {
throw parser_exception("Access token file '" + filename.string() + "' must be owned by root");
}
if (const auto perms = filesystem::status(filename).permissions();
(perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none ||
(perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) {
throw parser_exception("Access token file '" + filename + "' must be readable by root only");
throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only");
}
ifstream token_file(filename);
if (token_file.fail()) {
throw parser_exception("Can't open access token file '" + filename + "'");
throw parser_exception("Can't open access token file '" + filename.string() + "'");
}
getline(token_file, access_token);
if (token_file.fail()) {
throw parser_exception("Can't read access token file '" + filename + "'");
throw parser_exception("Can't read access token file '" + filename.string() + "'");
}
if (access_token.empty()) {
throw parser_exception("Access token file '" + filename + "' must not be empty");
throw parser_exception("Access token file '" + filename.string() + "' must not be empty");
}
}
@ -131,59 +124,74 @@ void Piscsi::LogDevices(string_view devices) const
string line;
while (getline(ss, line, '\n')) {
LOGINFO("%s", line.c_str())
spdlog::info(line);
}
}
PbDeviceType Piscsi::ParseDeviceType(const string& value) const
{
string t = value;
PbDeviceType type;
transform(t.begin(), t.end(), t.begin(), ::toupper);
if (!PbDeviceType_Parse(t, &type)) {
throw parser_exception("Illegal device type '" + value + "'");
}
return type;
}
void Piscsi::TerminationHandler(int)
{
Cleanup();
instance->CleanUp();
// Process will terminate automatically
}
Piscsi::optargs_type Piscsi::ParseArguments(const vector<char *>& args, int& port) const
string Piscsi::ParseArguments(span<char *> args, PbCommand& command, int& port, string& reserved_ids)
{
optargs_type optargs;
string log_level = "info";
PbDeviceType type = UNDEFINED;
int block_size = 0;
string name;
string id_and_lun;
string locale = GetLocale();
// Avoid duplicate messages while parsing
set_level(level::off);
opterr = 1;
int opt;
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) {
switch (opt) {
// The following options can not be processed until AFTER
// the 'bus' object is created and configured
// The two options below are kind of a compound option with two letters
case 'i':
case 'I':
case 'b':
continue;
case 'd':
case 'D':
case 'R':
case 'n':
case 'r':
case 't':
case 'F':
case 'z':
{
const string optarg_str = optarg == nullptr ? "" : optarg;
optargs.emplace_back(opt, optarg_str);
id_and_lun = optarg;
continue;
case 'b':
if (!GetAsUnsignedInt(optarg, block_size)) {
throw parser_exception("Invalid block size " + string(optarg));
}
continue;
case 'z':
locale = optarg;
continue;
case 'F':
if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) {
throw parser_exception(error);
}
continue;
}
case 'L':
current_log_level = optarg;
log_level = optarg;
continue;
case 'R':
int depth;
if (!GetAsUnsignedInt(optarg, depth)) {
throw parser_exception("Invalid image file scan depth " + string(optarg));
}
piscsi_image.SetDepth(depth);
continue;
case 'n':
name = optarg;
continue;
case 'p':
@ -196,92 +204,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector<char *>& args, int& por
ReadAccessToken(optarg);
continue;
case 'v':
cout << piscsi_get_version_string() << endl;
exit(0);
case 1:
{
// Encountered filename
const string optarg_str = (optarg == nullptr) ? "" : string(optarg);
optargs.emplace_back(opt, optarg_str);
continue;
}
default:
throw parser_exception("Parser error");
}
if (optopt) {
throw parser_exception("Parser error");
}
}
return optargs;
}
void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
{
PbCommand command;
PbDeviceType type = UNDEFINED;
int block_size = 0;
string name;
string log_level;
string id_and_lun;
const char *locale = setlocale(LC_MESSAGES, "");
if (locale == nullptr || !strcmp(locale, "C")) {
locale = "en";
}
opterr = 1;
for (const auto& [option, value] : optargs) {
switch (option) {
case 'i':
case 'I':
continue;
case 'd':
case 'D':
id_and_lun = value;
continue;
case 'b':
if (!GetAsUnsignedInt(value, block_size)) {
throw parser_exception("Invalid block size " + value);
}
continue;
case 'z':
locale = value.c_str();
continue;
case 'F':
if (const string error = piscsi_image.SetDefaultFolder(value); !error.empty()) {
throw parser_exception(error);
}
continue;
case 'R':
int depth;
if (!GetAsUnsignedInt(value, depth)) {
throw parser_exception("Invalid image file scan depth " + value);
}
piscsi_image.SetDepth(depth);
continue;
case 'n':
name = value;
continue;
case 'r':
if (const string error = executor->SetReservedIds(value); !error.empty()) {
throw parser_exception(error);
}
reserved_ids = optarg;
continue;
case 't':
type = ParseDeviceType(value);
type = ParseDeviceType(optarg);
continue;
case 1:
@ -292,10 +220,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
throw parser_exception("Parser error");
}
PbDeviceDefinition *device = command.add_devices();
if (optopt) {
throw parser_exception("Parser error");
}
// Set up the device data
auto device = command.add_devices();
if (!id_and_lun.empty()) {
if (const string error = SetIdAndLun(*device, id_and_lun, ScsiController::LUN_MAX); !error.empty()) {
if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) {
throw parser_exception(error);
}
}
@ -303,7 +237,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
device->set_type(type);
device->set_block_size(block_size);
ParseParameters(*device, value);
ParseParameters(*device, optarg);
SetProductData(*device, name);
@ -313,182 +247,231 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
id_and_lun = "";
}
// Attach all specified devices
command.set_operation(ATTACH);
if (CommandContext context(locale); !executor->ProcessCmd(context, command)) {
throw parser_exception("Can't execute " + PbOperation_Name(command.operation()));
if (!SetLogLevel(log_level)) {
throw parser_exception("Invalid log level '" + log_level + "'");
}
// Display and log the device list
PbServerInfo server_info;
piscsi_response.GetDevices(controller_manager->GetAllDevices(), server_info, piscsi_image.GetDefaultFolder());
const list<PbDevice>& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
const string device_list = ListDevices(devices);
LogDevices(device_list);
cout << device_list << flush;
return locale;
}
bool Piscsi::ExecuteCommand(const CommandContext& context, const PbCommand& command)
PbDeviceType Piscsi::ParseDeviceType(const string& value)
{
string t;
ranges::transform(value, back_inserter(t), ::toupper);
if (PbDeviceType type; PbDeviceType_Parse(t, &type)) {
return type;
}
throw parser_exception("Illegal device type '" + value + "'");
}
bool Piscsi::SetLogLevel(const string& log_level) const
{
int id = -1;
int lun = -1;
string level = log_level;
if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) {
level = components[0];
if (components.size() > 1) {
if (const string error = ProcessId(components[1], id, lun); !error.empty()) {
spdlog::warn("Error setting log level: " + error);
return false;
}
}
}
const level::level_enum l = level::from_str(level);
// Compensate for spdlog using 'off' for unknown levels
if (to_string_view(l) != level) {
spdlog::warn("Invalid log level '" + level + "'");
return false;
}
set_level(l);
DeviceLogger::SetLogIdAndLun(id, lun);
if (id != -1) {
if (lun == -1) {
spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'");
}
else {
spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'");
}
}
else {
spdlog::info("Set log level to '" + level + "'");
}
return true;
}
bool Piscsi::ExecuteCommand(CommandContext& context)
{
context.SetDefaultFolder(piscsi_image.GetDefaultFolder());
const PbCommand& command = context.GetCommand();
if (!access_token.empty() && access_token != GetParam(command, "token")) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
}
if (!PbOperation_IsValid(command.operation())) {
LOGERROR("Received unknown command with operation opcode %d", command.operation())
spdlog::error("Received unknown command with operation opcode " + to_string(command.operation()));
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION);
}
LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str())
spdlog::trace("Received " + PbOperation_Name(command.operation()) + " command");
PbResult result;
ProtobufSerializer serializer;
switch(command.operation()) {
case LOG_LEVEL: {
const string log_level = GetParam(command, "level");
if (const bool status = executor->SetLogLevel(log_level); !status) {
case LOG_LEVEL:
if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) {
context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level);
}
else {
current_log_level = log_level;
context.ReturnStatus();
context.ReturnSuccessStatus();
}
break;
}
case DEFAULT_FOLDER: {
if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) {
context.ReturnStatus(false, status);
case DEFAULT_FOLDER:
if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) {
context.ReturnErrorStatus(error);
}
else {
context.ReturnStatus();
context.ReturnSuccessStatus();
}
break;
}
case DEVICES_INFO: {
piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command,
piscsi_image.GetDefaultFolder());
serializer.SerializeMessage(context.GetFd(), result);
case DEVICES_INFO:
response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder());
context.WriteResult(result);
break;
}
case DEVICE_TYPES_INFO: {
result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release());
serializer.SerializeMessage(context.GetFd(), result);
case DEVICE_TYPES_INFO:
response.GetDeviceTypesInfo(*result.mutable_device_types_info());
context.WriteSuccessResult(result);
break;
}
case SERVER_INFO: {
result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(),
result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"),
piscsi_image.GetDepth()).release());
serializer.SerializeMessage(context.GetFd(), result);
case SERVER_INFO:
response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(),
executor->GetReservedIds(), piscsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
context.WriteSuccessResult(result);
break;
}
case VERSION_INFO: {
result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release());
serializer.SerializeMessage(context.GetFd(), result);
case VERSION_INFO:
response.GetVersionInfo(*result.mutable_version_info());
context.WriteSuccessResult(result);
break;
}
case LOG_LEVEL_INFO: {
result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release());
serializer.SerializeMessage(context.GetFd(), result);
case LOG_LEVEL_INFO:
response.GetLogLevelInfo(*result.mutable_log_level_info());
context.WriteSuccessResult(result);
break;
}
case DEFAULT_IMAGE_FILES_INFO: {
result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result,
piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"),
GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release());
serializer.SerializeMessage(context.GetFd(), result);
case DEFAULT_IMAGE_FILES_INFO:
response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
context.WriteSuccessResult(result);
break;
}
case IMAGE_FILE_INFO: {
case IMAGE_FILE_INFO:
if (string filename = GetParam(command, "file"); filename.empty()) {
context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME);
}
else {
auto image_file = make_unique<PbImageFile>();
const bool status = piscsi_response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), filename);
const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(),
filename);
if (status) {
result.set_status(true);
result.set_allocated_image_file_info(image_file.get());
serializer.SerializeMessage(context.GetFd(), result);
result.set_status(true);
context.WriteResult(result);
}
else {
context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO);
}
}
break;
}
case NETWORK_INTERFACES_INFO: {
result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release());
serializer.SerializeMessage(context.GetFd(), result);
case NETWORK_INTERFACES_INFO:
response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info());
context.WriteSuccessResult(result);
break;
}
case MAPPING_INFO: {
result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release());
serializer.SerializeMessage(context.GetFd(), result);
case MAPPING_INFO:
response.GetMappingInfo(*result.mutable_mapping_info());
context.WriteSuccessResult(result);
break;
}
case OPERATION_INFO: {
result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result,
piscsi_image.GetDepth()).release());
serializer.SerializeMessage(context.GetFd(), result);
case OPERATION_INFO:
response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth());
context.WriteSuccessResult(result);
break;
}
case RESERVED_IDS_INFO: {
result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result,
executor->GetReservedIds()).release());
serializer.SerializeMessage(context.GetFd(), result);
case RESERVED_IDS_INFO:
response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds());
context.WriteSuccessResult(result);
break;
}
case SHUT_DOWN: {
case SHUT_DOWN:
if (executor->ShutDown(context, GetParam(command, "mode"))) {
TerminationHandler(0);
}
break;
}
default: {
// Wait until we become idle
case NO_OPERATION:
context.ReturnSuccessStatus();
break;
// TODO The image operations below can most likely directly be executed without calling the executor,
// because they do not require the target to be idle
case CREATE_IMAGE:
case DELETE_IMAGE:
case RENAME_IMAGE:
case COPY_IMAGE:
case PROTECT_IMAGE:
case UNPROTECT_IMAGE:
case RESERVE_IDS:
return executor->ProcessCmd(context);
// The remaining commands can only be executed when the target is idle
// TODO What happens when the target becomes active while the command is still being executed?
// A field 'mutex locker' can probably avoid SCSI commands and ProcessCmd() being executed at the same time
default:
// TODO Find a better way to wait
const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000};
while (active) {
while (target_is_active) {
nanosleep(&ts, nullptr);
}
executor->ProcessCmd(context, command);
break;
}
return executor->ProcessCmd(context);
}
return true;
}
int Piscsi::run(const vector<char *>& args)
int Piscsi::run(span<char *> args)
{
GOOGLE_PROTOBUF_VERIFY_VERSION;
Banner(args);
// The -v option shall result in no other action except displaying the version
if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) {
cout << piscsi_get_version_string() << '\n';
return EXIT_SUCCESS;
}
PbCommand command;
string locale;
string reserved_ids;
int port = DEFAULT_PORT;
optargs_type optargs;
try {
optargs = ParseArguments(args, port);
locale = ParseArguments(args, command, port, reserved_ids);
}
catch(const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
@ -496,35 +479,51 @@ int Piscsi::run(const vector<char *>& args)
return EXIT_FAILURE;
}
// current_log_level may have been updated by ParseArguments()
executor->SetLogLevel(current_log_level);
// Create a thread-safe stdout logger to process the log messages
const auto logger = stdout_color_mt("piscsi stdout logger");
if (!InitBus()) {
cerr << "Error: Can't initialize bus" << endl;
return EXIT_FAILURE;
}
// We need to wait to create the devices until after the bus/controller/etc objects have been created
// TODO Try to resolve dependencies so that this work-around can be removed
try {
CreateInitialDevices(optargs);
}
catch(const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port);
!error.empty()) {
cerr << "Error: " << error << endl;
Cleanup();
CleanUp();
return EXIT_FAILURE;
}
if (!service.Init(&ExecuteCommand, port)) {
if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) {
cerr << "Error: " << error << endl;
CleanUp();
return EXIT_FAILURE;
}
if (command.devices_size()) {
// Attach all specified devices
command.set_operation(ATTACH);
if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) {
cerr << "Error: Can't attach devices" << endl;
CleanUp();
return EXIT_FAILURE;
}
}
// Display and log the device list
PbServerInfo server_info;
response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder());
const vector<PbDevice>& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
const string device_list = ListDevices(devices);
LogDevices(device_list);
cout << device_list << flush;
instance = this;
// Signal handler to detach all devices on a KILL or TERM signal
struct sigaction termination_handler;
termination_handler.sa_handler = TerminationHandler;
@ -532,22 +531,30 @@ int Piscsi::run(const vector<char *>& args)
termination_handler.sa_flags = 0;
sigaction(SIGINT, &termination_handler, nullptr);
sigaction(SIGTERM, &termination_handler, nullptr);
signal(SIGPIPE, SIG_IGN);
// Set the affinity to a specific processor core
FixCpu(3);
sched_param schparam;
service.Start();
Process();
return EXIT_SUCCESS;
}
void Piscsi::Process()
{
#ifdef USE_SEL_EVENT_ENABLE
// Scheduling policy setting (highest priority)
// TODO Check whether this results in any performance gain
sched_param schparam;
schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
sched_setscheduler(0, SCHED_FIFO, &schparam);
#else
cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl;
#endif
// Start execution
service.SetRunning(true);
// Main Loop
while (service.IsRunning()) {
#ifdef USE_SEL_EVENT_ENABLE
@ -571,81 +578,55 @@ int Piscsi::run(const vector<char *>& args)
}
#endif
// Wait until BSY is released as there is a possibility for the
// initiator to assert it while setting the ID (for up to 3 seconds)
WaitForNotBusy();
// Only process the SCSI command if the bus is not busy and no other device responded
if (IsNotBusy() && bus->GetSEL()) {
target_is_active = true;
// Stop because the bus is busy or another device responded
if (bus->GetBSY() || !bus->GetSEL()) {
continue;
}
int initiator_id = AbstractController::UNKNOWN_INITIATOR_ID;
// The initiator and target ID
const uint8_t id_data = bus->GetDAT();
phase_t phase = phase_t::busfree;
// Identify the responsible controller
auto controller = controller_manager->IdentifyController(id_data);
if (controller != nullptr) {
device_logger.SetIdAndLun(controller->GetTargetId(), -1);
initiator_id = controller->ExtractInitiatorId(id_data);
if (initiator_id != AbstractController::UNKNOWN_INITIATOR_ID) {
device_logger.Trace("++++ Starting processing for initiator ID " + to_string(initiator_id));
}
else {
device_logger.Trace("++++ Starting processing for unknown initiator ID");
// Process command on the responsible controller based on the current initiator and target ID
if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT());
shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) {
// When the bus is free PiSCSI or the Pi may be shut down.
ShutDown(shutdown_mode);
}
if (controller->Process(initiator_id) == phase_t::selection) {
phase = phase_t::selection;
}
target_is_active = false;
}
// Return to bus monitoring if the selection phase has not started
if (phase != phase_t::selection) {
continue;
}
// Start target device
active = true;
#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__)
// Scheduling policy setting (highest priority)
schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
sched_setscheduler(0, SCHED_FIFO, &schparam);
#endif
// Loop until the bus is free
while (service.IsRunning()) {
// Target drive
phase = controller->Process(initiator_id);
// End when the bus is free
if (phase == phase_t::busfree) {
break;
}
}
#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__)
// Set the scheduling priority back to normal
schparam.sched_priority = 0;
sched_setscheduler(0, SCHED_OTHER, &schparam);
#endif
// End the target travel
active = false;
}
return EXIT_SUCCESS;
}
void Piscsi::WaitForNotBusy() const
void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode)
{
CleanUp();
switch(shutdown_mode) {
case AbstractController::piscsi_shutdown_mode::STOP_PISCSI:
spdlog::info("PiSCSI shutdown requested");
break;
case AbstractController::piscsi_shutdown_mode::STOP_PI:
spdlog::info("Raspberry Pi shutdown requested");
if (system("init 0") == -1) {
spdlog::error("Raspberry Pi shutdown failed");
}
break;
case AbstractController::piscsi_shutdown_mode::RESTART_PI:
spdlog::info("Raspberry Pi restart requested");
if (system("init 6") == -1) {
spdlog::error("Raspberry Pi restart failed");
}
break;
case AbstractController::piscsi_shutdown_mode::NONE:
assert(false);
break;
}
}
bool Piscsi::IsNotBusy() const
{
// Wait until BSY is released as there is a possibility for the
// initiator to assert it while setting the ID (for up to 3 seconds)
if (bus->GetBSY()) {
const uint32_t now = SysTimer::GetTimerLow();
@ -654,8 +635,12 @@ void Piscsi::WaitForNotBusy() const
bus->Acquire();
if (!bus->GetBSY()) {
break;
return true;
}
}
return false;
}
return true;
}

View File

@ -3,31 +3,31 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "devices/device_logger.h"
#include "controllers/controller_manager.h"
#include "controllers/abstract_controller.h"
#include "piscsi/command_context.h"
#include "piscsi/piscsi_service.h"
#include "piscsi/piscsi_image.h"
#include "piscsi/piscsi_response.h"
#include "piscsi/piscsi_executor.h"
#include "generated/piscsi_interface.pb.h"
#include <vector>
#include "spdlog/sinks/stdout_color_sinks.h"
#include <span>
#include <string>
#include <atomic>
using namespace std;
class BUS;
class ControllerManager;
class PiscsiExecutor;
class Piscsi
{
using optargs_type = vector<pair<int, string>>;
static const int DEFAULT_PORT = 6868;
public:
@ -35,46 +35,47 @@ public:
Piscsi() = default;
~Piscsi() = default;
int run(const vector<char *>&);
int run(span<char *>);
private:
void Banner(const vector<char *>&) const;
bool InitBus() const;
static void Cleanup();
void ReadAccessToken(const string&) const;
void Banner(span<char *>) const;
bool InitBus();
void CleanUp();
void ReadAccessToken(const path&);
void LogDevices(string_view) const;
PbDeviceType ParseDeviceType(const string&) const;
static void TerminationHandler(int);
optargs_type ParseArguments(const vector<char *>&, int&) const;
void CreateInitialDevices(const optargs_type&) const;
void WaitForNotBusy() const;
string ParseArguments(span<char *>, PbCommand&, int&, string&);
void Process();
bool IsNotBusy() const;
// TODO Should not be static and should be moved to PiscsiService
static bool ExecuteCommand(const CommandContext&, const PbCommand&);
void ShutDown(AbstractController::piscsi_shutdown_mode);
DeviceLogger device_logger;
bool ExecuteCommand(CommandContext&);
// A static instance is needed because of the signal handler
static inline shared_ptr<BUS> bus;
bool SetLogLevel(const string&) const;
// TODO These fields should not be static
const shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("piscsi stdout logger");
static inline PiscsiService service;
static inline PiscsiImage piscsi_image;
const static inline PiscsiResponse piscsi_response;
static inline shared_ptr<ControllerManager> controller_manager;
static inline shared_ptr<PiscsiExecutor> executor;
static PbDeviceType ParseDeviceType(const string&);
// Processing flag
static inline volatile bool active;
atomic_bool target_is_active;
// Some versions of spdlog do not support get_log_level(), so we have to remember the level
static inline string current_log_level = "info";
string access_token;
static inline string access_token;
PiscsiImage piscsi_image;
PiscsiResponse response;
PiscsiService service;
unique_ptr<PiscsiExecutor> executor;
ControllerManager controller_manager;
unique_ptr<BUS> bus;
// Required for the termination handler
static inline Piscsi *instance;
};

View File

@ -3,35 +3,31 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "devices/device_logger.h"
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "devices/disk.h"
#include "piscsi_service.h"
#include "piscsi_image.h"
#include "localizer.h"
#include "command_context.h"
#include "piscsi_executor.h"
#include <spdlog/spdlog.h>
#include <sstream>
using namespace spdlog;
using namespace protobuf_util;
using namespace piscsi_util;
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device,
const PbCommand& command, bool dryRun)
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun)
{
PrintCommand(command, pb_device, dryRun);
spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device));
const int id = pb_device.id();
const int lun = pb_device.unit();
@ -40,14 +36,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return false;
}
const PbOperation operation = command.operation();
const PbOperation operation = context.GetCommand().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);
auto device = controller_manager.GetDeviceForIdAndLun(id, lun);
if (!ValidateOperationAgainstDevice(context, *device, operation)) {
return false;
@ -64,7 +60,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return Attach(context, pb_device, dryRun);
case DETACH:
return Detach(context, device, dryRun);
return Detach(context, *device, dryRun);
case INSERT:
return Insert(context, pb_device, device, dryRun);
@ -82,7 +78,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
case CHECK_AUTHENTICATION:
case NO_OPERATION:
// Do nothing, just log
LOGTRACE("Received %s command", PbOperation_Name(operation).c_str())
spdlog::trace("Received " + PbOperation_Name(operation) + " command");
break;
default:
@ -92,136 +88,83 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return true;
}
bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command)
bool PiscsiExecutor::ProcessCmd(const CommandContext& context)
{
const PbCommand& command = context.GetCommand();
switch (command.operation()) {
case DETACH_ALL:
DetachAll();
return context.ReturnStatus();
return context.ReturnSuccessStatus();
case RESERVE_IDS: {
const string ids = GetParam(command, "ids");
if (const string error = SetReservedIds(ids); !error.empty()) {
return context.ReturnStatus(false, error);
return context.ReturnErrorStatus(error);
}
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
case CREATE_IMAGE:
return piscsi_image.CreateImage(context, command);
return piscsi_image.CreateImage(context);
case DELETE_IMAGE:
return piscsi_image.DeleteImage(context, command);
return piscsi_image.DeleteImage(context);
case RENAME_IMAGE:
return piscsi_image.RenameImage(context, command);
return piscsi_image.RenameImage(context);
case COPY_IMAGE:
return piscsi_image.CopyImage(context, command);
return piscsi_image.CopyImage(context);
case PROTECT_IMAGE:
case UNPROTECT_IMAGE:
return piscsi_image.SetImagePermissions(context, command);
return piscsi_image.SetImagePermissions(context);
default:
// This is a device-specific command handled below
break;
}
// Remember the list of reserved files, than run the dry run
// Remember the list of reserved files during the dry run
const auto& reserved_files = StorageDevice::GetReservedFiles();
for (const auto& device : command.devices()) {
if (!ProcessDeviceCmd(context, device, command, true)) {
// Dry run failed, restore the file list
StorageDevice::SetReservedFiles(reserved_files);
return false;
}
}
// Restore the list of reserved files before proceeding
const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device)
{ return ProcessDeviceCmd(context, device, true); }) != command.devices().end();
StorageDevice::SetReservedFiles(reserved_files);
if (const string result = ValidateLunSetup(command); !result.empty()) {
return context.ReturnStatus(false, result);
}
for (const auto& device : command.devices()) {
if (!ProcessDeviceCmd(context, device, command, false)) {
return false;
}
}
// ATTACH and DETACH return the device list
if (context.IsValid() && (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;
piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd,
piscsi_image.GetDefaultFolder());
serializer.SerializeMessage(context.GetFd(), result);
return true;
}
return context.ReturnStatus();
}
bool PiscsiExecutor::SetLogLevel(const string& log_level) const
{
int id = -1;
int lun = -1;
string level = log_level;
if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
level = log_level.substr(0, separator_pos);
const string l = log_level.substr(separator_pos + 1);
separator_pos = l.find(COMPONENT_SEPARATOR);
if (separator_pos != string::npos) {
const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun);
if (!error.empty()) {
LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str())
return false;
}
}
else if (!GetAsUnsignedInt(l, id)) {
LOGWARN("Invalid device ID specifier '%s'", l.c_str())
return false;
}
}
if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) {
set_level(it->second);
}
else {
LOGWARN("Invalid log level '%s'", log_level.c_str())
if (reserved) {
return false;
}
DeviceLogger::SetLogIdAndLun(id, lun);
if (id != -1) {
if (lun == -1) {
LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str())
}
else {
LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str())
}
}
else {
LOGINFO("Set log level to '%s'", level.c_str())
if (const string error = EnsureLun0(command); !error.empty()) {
return context.ReturnErrorStatus(error);
}
return true;
if (ranges::find_if_not(command.devices(), [&] (const auto& device)
{ return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) {
return false;
}
// ATTACH and DETACH return the device list
if (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;
piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, context.GetDefaultFolder());
context.WriteResult(result);
return true;
}
return context.ReturnSuccessStatus();
}
bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Start requested for " + device.GetIdentifier());
if (!device.Start()) {
LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::warn("Starting " + device.GetIdentifier() + " failed");
}
}
@ -231,7 +174,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Stop requested for " + device.GetIdentifier());
device.Stop();
}
@ -242,10 +185,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::info("Eject requested for " + device.GetIdentifier());
if (!device.Eject(true)) {
LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
spdlog::warn("Ejecting " + device.GetIdentifier() + " failed");
}
}
@ -255,8 +198,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
device.GetLun())
spdlog::info("Write protection requested for " + device.GetIdentifier());
device.SetProtected(true);
}
@ -267,8 +209,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const
{
if (!dryRun) {
LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
device.GetLun())
spdlog::info("Write unprotection requested for " + device.GetIdentifier());
device.SetProtected(false);
}
@ -282,15 +223,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
const int lun = pb_device.unit();
const PbDeviceType type = pb_device.type();
if (lun >= ScsiController::LUN_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX));
if (lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
to_string(ControllerManager::GetScsiLunMax()));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) {
if (controller_manager.HasDeviceForIdAndLun(id, lun)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun));
}
if (reserved_ids.find(id) != reserved_ids.end()) {
if (reserved_ids.contains(id)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id));
}
@ -302,8 +244,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
}
// If no filename was provided the medium is considered not inserted
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
device->SetRemoved(storage_device != nullptr ? filename.empty() : false);
device->SetRemoved(device->SupportsFile() ? filename.empty() : false);
if (!SetProductData(context, pb_device, *device)) {
return false;
@ -313,14 +254,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return false;
}
string full_path;
const auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (device->SupportsFile()) {
// Only with removable media drives, CD and MO the medium (=file) may be inserted later
if (!device->IsRemovable() && filename.empty()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type));
}
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
if (!ValidateImageFile(context, *storage_device, filename)) {
return false;
}
}
@ -336,23 +277,22 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return true;
}
unordered_map<string, string> params = { pb_device.params().begin(), pb_device.params().end() };
param_map params = { pb_device.params().begin(), pb_device.params().end() };
if (!device->SupportsFile()) {
// Clients like scsictl might have sent both "file" and "interfaces"
params.erase("file");
}
if (!device->Init(params)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()),
to_string(id), to_string(lun));
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier());
}
if (!controller_manager.AttachToController(bus, id, device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
}
if (storage_device != nullptr) {
storage_device->ReserveFile(full_path, id, lun);
}
if (!controller_manager.AttachToScsiController(id, device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
storage_device->ReserveFile();
}
string msg = "Attached ";
@ -362,8 +302,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
else if (device->IsProtectable() && device->IsProtected()) {
msg += "protected ";
}
msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun);
LOGINFO("%s", msg.c_str())
msg += device->GetIdentifier();
spdlog::info(msg);
return true;
}
@ -371,12 +311,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
const shared_ptr<PrimaryDevice>& device, bool dryRun) const
{
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (storage_device == nullptr) {
if (!device->SupportsFile()) {
return false;
}
if (!storage_device->IsRemoved()) {
if (!device->IsRemoved()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED);
}
@ -394,56 +333,52 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
return true;
}
LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "",
filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit())
spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename +
"' requested into " + device->GetIdentifier());
// TODO It may be better to add PrimaryDevice::Insert for all device-specific insert operations
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (!SetSectorSize(context, storage_device, pb_device.block_size())) {
return false;
}
string full_path;
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
if (!ValidateImageFile(context, *storage_device, filename)) {
return false;
}
storage_device->SetProtected(pb_device.protected_());
storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun());
storage_device->ReserveFile();
storage_device->SetMediumChanged(true);
return true;
}
bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<PrimaryDevice>& device, bool dryRun) const
bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun)
{
auto controller = controller_manager.FindController(device->GetId());
auto controller = controller_manager.FindController(device.GetId());
if (controller == nullptr) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
// LUN 0 can only be detached if there is no other LUN anymore
if (!device->GetLun() && controller->GetLunCount() > 1) {
if (!device.GetLun() && controller->GetLunCount() > 1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0);
}
if (!dryRun) {
// Remember the ID before it gets invalid when removing the device
const int id = device->GetId();
// Remember the device identifier for the log message before the device data become invalid on removal
const string identifier = device.GetIdentifier();
if (!controller->RemoveDevice(device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
// If no LUN is left also delete the controller
if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) {
if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
if (auto storage_device = dynamic_pointer_cast<StorageDevice>(device); storage_device != nullptr) {
storage_device->UnreserveFile();
}
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id)
+ ", unit " + to_string(device->GetLun())).c_str())
spdlog::info("Detached " + identifier);
}
return true;
@ -452,9 +387,8 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<Prim
void PiscsiExecutor::DetachAll()
{
controller_manager.DeleteAllControllers();
StorageDevice::UnreserveAll();
LOGINFO("Detached all devices")
spdlog::info("Detached all devices");
}
bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode) {
@ -467,9 +401,9 @@ bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode)
// The PiSCSI shutdown mode is "rascsi" instead of "piscsi" for backwards compatibility
if (mode == "rascsi") {
LOGINFO("PiSCSI shutdown requested")
spdlog::info("PiSCSI shutdown requested");
serializer.SerializeMessage(context.GetFd(), result);
context.WriteResult(result);
return true;
}
@ -484,145 +418,116 @@ bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode)
}
if (mode == "system") {
LOGINFO("System shutdown requested")
serializer.SerializeMessage(context.GetFd(), result);
spdlog::info("System shutdown requested");
DetachAll();
context.WriteResult(result);
if (system("init 0") == -1) {
LOGERROR("System shutdown failed: %s", strerror(errno))
spdlog::error("System shutdown failed");
}
}
else if (mode == "reboot") {
LOGINFO("System reboot requested")
serializer.SerializeMessage(context.GetFd(), result);
spdlog::info("System reboot requested");
DetachAll();
context.WriteResult(result);
if (system("init 6") == -1) {
LOGERROR("System reboot failed: %s", strerror(errno))
spdlog::error("System reboot failed");
}
}
else {
assert(false);
}
return false;
}
string PiscsiExecutor::SetReservedIds(string_view ids)
{
list<string> ids_to_reserve;
set<int> ids_to_reserve;
stringstream ss(ids.data());
string id;
while (getline(ss, id, ',')) {
if (!id.empty()) {
ids_to_reserve.push_back(id);
}
}
set<int> reserved;
for (const string& id_to_reserve : ids_to_reserve) {
int res_id;
if (!GetAsUnsignedInt(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.begin(), reserved.end() };
if (!reserved_ids.empty()) {
string s;
bool isFirst = true;
for (const auto& reserved_id : reserved) {
if (!isFirst) {
s += ", ";
}
isFirst = false;
s += to_string(reserved_id);
if (!GetAsUnsignedInt(id, res_id) || res_id > 7) {
return "Invalid ID " + id;
}
LOGINFO("Reserved ID(s) set to %s", s.c_str())
if (controller_manager.HasController(res_id)) {
return "ID " + id + " is currently in use";
}
ids_to_reserve.insert(res_id);
}
reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() };
if (!ids_to_reserve.empty()) {
spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve));
}
else {
LOGINFO("Cleared reserved ID(s)")
spdlog::info("Cleared reserved ID(s)");
}
return "";
}
bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device,
const string& filename, string& full_path) const
const string& filename) const
{
if (filename.empty()) {
return true;
}
if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id1), to_string(lun1));
if (!CheckForReservedFile(context, filename)) {
return false;
}
string effective_filename = filename;
storage_device.SetFilename(filename);
if (!StorageDevice::FileExists(filename)) {
// If the file does not exist search for it in the default image folder
effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename;
const string effective_filename = context.GetDefaultFolder() + "/" + filename;
if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id2), to_string(lun2));
if (!CheckForReservedFile(context, effective_filename)) {
return false;
}
if (!StorageDevice::FileExists(effective_filename)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
}
}
storage_device.SetFilename(effective_filename);
if (storage_device.IsReadOnlyFile()) {
// Permanently write-protected
storage_device.SetReadOnly(true);
storage_device.SetProtectable(false);
}
else {
storage_device.SetReadOnly(false);
storage_device.SetProtectable(true);
storage_device.SetFilename(effective_filename);
}
try {
storage_device.Open();
}
catch(const io_exception&) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename());
}
full_path = effective_filename;
return true;
}
void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const
bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename)
{
if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id) + ":" + to_string(lun));
}
return true;
}
string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) 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());
s << "operation=" << PbOperation_Name(command.operation());
if (!params.empty()) {
s << ", command params=";
bool isFirst = true;
for (const auto& [key, value]: params) {
for (const auto& [key, value] : params) {
if (!isFirst) {
s << ", ";
}
@ -632,8 +537,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
}
}
s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type="
<< PbDeviceType_Name(pb_device.type());
s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type());
if (pb_device.params_size()) {
s << ", device params=";
@ -649,13 +553,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
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())
return s.str();
}
string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
string PiscsiExecutor::EnsureLun0(const PbCommand& command) const
{
// Mapping of available LUNs (bit vector) to devices
unordered_map<uint32_t, uint32_t> luns;
unordered_map<int32_t, int32_t> luns;
// Collect LUN bit vectors of new devices
for (const auto& device : command.devices()) {
@ -667,23 +572,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
luns[device->GetId()] |= 1 << device->GetLun();
}
// LUN 0 must exist for all devices
for (const auto& [id, lun]: luns) {
if (!(lun & 0x01)) {
return "LUN 0 is missing for device ID " + to_string(id);
}
}
return "";
const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } );
return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first);
}
bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const
{
if (controller_manager.FindController(id) == nullptr) {
if (!controller_manager.HasController(id)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) {
if (!controller_manager.HasDeviceForIdAndLun(id, lun)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun));
}
@ -706,13 +605,13 @@ shared_ptr<PrimaryDevice> PiscsiExecutor::CreateDevice(const CommandContext& con
return device;
}
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int block_size) const
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int size) const
{
if (block_size) {
auto disk = dynamic_pointer_cast<Disk>(device);
if (size) {
const auto disk = dynamic_pointer_cast<Disk>(device);
if (disk != nullptr && disk->IsSectorSizeConfigurable()) {
if (!disk->SetConfiguredSectorSize(device_factory, block_size)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size));
if (!disk->SetConfiguredSectorSize(device_factory, size)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size));
}
}
else {
@ -725,22 +624,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<Pri
}
bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const PrimaryDevice& device,
const PbOperation& operation)
PbOperation operation)
{
if ((operation == START || operation == STOP) && !device.IsStoppable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == INSERT || operation == EJECT) && !device.IsRemovable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsProtectable()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, PbOperation_Name(operation),
device.GetTypeString());
}
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsReady()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, device.GetTypeString());
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, PbOperation_Name(operation),
device.GetTypeString());
}
return true;
@ -748,15 +651,16 @@ bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& contex
bool PiscsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun)
{
// Validate the device ID and LUN
if (id < 0) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID);
}
if (id >= ControllerManager::DEVICE_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1));
if (id >= ControllerManager::GetScsiIdMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id),
to_string(ControllerManager::GetScsiIdMax() - 1));
}
if (lun < 0 || lun >= ScsiController::LUN_MAX) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1));
if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
to_string(ControllerManager::GetScsiLunMax() - 1));
}
return true;
@ -777,7 +681,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic
}
}
catch(const invalid_argument& e) {
return context.ReturnStatus(false, e.what());
return context.ReturnErrorStatus(e.what());
}
return true;

View File

@ -3,22 +3,21 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "spdlog/spdlog.h"
#include "shared/protobuf_serializer.h"
#include "hal/bus.h"
#include "controllers/controller_manager.h"
#include "piscsi/piscsi_response.h"
#include <unordered_set>
#include <unordered_map>
class PiscsiImage;
class DeviceFactory;
class ControllerManager;
class PrimaryDevice;
class StorageDevice;
class CommandContext;
using namespace spdlog;
@ -27,15 +26,14 @@ class PiscsiExecutor
{
public:
PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager)
: piscsi_image(piscsi_image), controller_manager(controller_manager) {}
PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager)
: piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {}
~PiscsiExecutor() = default;
unordered_set<int> GetReservedIds() const { return reserved_ids; }
auto GetReservedIds() const { return reserved_ids; }
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool);
bool ProcessCmd(const CommandContext&, const PbCommand&);
bool SetLogLevel(const string&) const;
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool);
bool ProcessCmd(const CommandContext&);
bool Start(PrimaryDevice&, bool) const;
bool Stop(PrimaryDevice&, bool) const;
bool Eject(PrimaryDevice&, bool) const;
@ -43,41 +41,34 @@ public:
bool Unprotect(PrimaryDevice&, bool) const;
bool Attach(const CommandContext&, const PbDeviceDefinition&, bool);
bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr<PrimaryDevice>&, bool) const;
bool Detach(const CommandContext&, const shared_ptr<PrimaryDevice>&, bool) const;
bool Detach(const CommandContext&, PrimaryDevice&, bool);
void DetachAll();
bool ShutDown(const CommandContext&, const string&);
string SetReservedIds(string_view);
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const;
void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const;
string ValidateLunSetup(const PbCommand&) const;
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const;
string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const;
string EnsureLun0(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&, shared_ptr<PrimaryDevice>, int) const;
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&);
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation);
static bool ValidateIdAndLun(const CommandContext&, int, int);
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&);
private:
static bool CheckForReservedFile(const CommandContext&, const string&);
const PiscsiResponse piscsi_response;
PiscsiImage& piscsi_image;
BUS& bus;
ControllerManager& controller_manager;
const DeviceFactory device_factory;
const ProtobufSerializer serializer;
unordered_set<int> reserved_ids;
static inline const unordered_map<string, level::level_enum> log_level_mapping = {
{ "trace", level::trace },
{ "debug", level::debug },
{ "info", level::info },
{ "warn", level::warn },
{ "err", level::err },
{ "off", level::off }
};
};

View File

@ -3,21 +3,18 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/protobuf_util.h"
#include "devices/disk.h"
#include "command_context.h"
#include "piscsi_image.h"
#include "shared/protobuf_util.h"
#include <spdlog/spdlog.h>
#include <unistd.h>
#include <pwd.h>
#include <fstream>
#include <string>
#include <array>
#include <filesystem>
using namespace std;
using namespace filesystem;
@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage()
bool PiscsiImage::CheckDepth(string_view filename) const
{
return count(filename.begin(), filename.end(), '/') <= depth;
return ranges::count(filename, '/') <= depth;
}
bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const
bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const
{
if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) {
const auto folder = path(filename.substr(0, filename_start));
if (const auto folder = path(filename).parent_path(); !folder.string().empty()) {
// Checking for existence first prevents an error if the top-level folder is a softlink
if (error_code error; exists(folder, error)) {
return true;
@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string&
return ChangeOwner(context, folder, false);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what());
return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what());
}
}
return true;
}
string PiscsiImage::SetDefaultFolder(const string& f)
string PiscsiImage::SetDefaultFolder(string_view f)
{
if (f.empty()) {
return "Can't set default image folder: Missing folder name";
}
string folder = f;
// If a relative path is specified, the path is assumed to be relative to the user's home directory
if (folder[0] != '/') {
folder = GetHomeDir() + "/" + folder;
path folder(f);
if (folder.is_relative()) {
folder = path(GetHomeDir() + "/" + folder.string());
}
else {
if (folder.find("/home/") != 0) {
return "Default image folder must be located in '/home/'";
}
if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) {
return "Default image folder must be located in '" + home_root.string() + "'";
}
// Resolve a potential symlink
auto p = path(folder);
if (error_code error; is_symlink(p, error)) {
p = read_symlink(p);
if (error_code error; is_symlink(folder, error)) {
folder = read_symlink(folder);
}
if (error_code error; !is_directory(p, error)) {
return "'" + string(p) + "' is not a valid folder";
if (error_code error; !is_directory(folder)) {
return string("'") + folder.string() + "' is not a valid image folder";
}
default_folder = string(p);
default_folder = folder.string();
LOGINFO("Default image folder set to '%s'", default_folder.c_str())
spdlog::info("Default image folder set to '" + default_folder + "'");
return "";
}
bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::CreateImage(const CommandContext& context) const
{
const string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Can't create image file: Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
const string full_filename = GetFullName(filename);
if (!IsValidDstFilename(full_filename)) {
return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists");
return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists");
}
const string size = GetParam(command, "size");
const string size = GetParam(context.GetCommand(), "size");
if (size.empty()) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size");
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size");
}
off_t len;
@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
len = stoull(size);
}
catch(const invalid_argument&) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
}
catch(const out_of_range&) {
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
}
if (len < 512 || (len & 0x1ff)) {
return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)");
return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)");
}
if (!CreateImageFolder(context, full_filename)) {
return false;
}
const bool read_only = GetParam(command, "read_only") == "true";
const bool read_only = GetParam(context.GetCommand(), "read_only") == "true";
error_code error;
path file(full_filename);
@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
catch(const filesystem_error& e) {
remove(file, error);
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what());
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what());
}
LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes").c_str())
spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::DeleteImage(const CommandContext& context) const
{
const string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
}
const auto full_filename = path(GetFullName(filename));
if (!exists(full_filename)) {
return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist");
return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist");
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
if (!IsReservedFile(context, full_filename, "delete")) {
return false;
}
if (error_code error; !remove(full_filename, error)) {
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'");
return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'");
}
// Delete empty subfolders
@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
}
if (error_code error; !remove(full_folder)) {
return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'");
return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'");
}
last_slash = folder.rfind('/');
}
LOGINFO("Deleted image file '%s'", full_filename.c_str())
spdlog::info("Deleted image file '" + full_filename.string() + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::RenameImage(const CommandContext& context) const
{
string from;
string to;
if (!ValidateParams(context, command, "rename/move", from, to)) {
return false;
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
}
if (!CreateImageFolder(context, to)) {
if (!ValidateParams(context, "rename/move", from, to)) {
return false;
}
@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co
rename(path(from), path(to));
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what());
return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what());
}
LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::CopyImage(const CommandContext& context) const
{
string from;
string to;
if (!ValidateParams(context, command, "copy", from, to)) {
return false;
}
if (access(from.c_str(), R_OK)) {
return context.ReturnStatus(false, "Can't read source image file '" + from + "'");
}
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
if (id != -1 || lun != -1) {
return context.ReturnStatus(false, "Can't copy image file '" + from +
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
}
if (!CreateImageFolder(context, to)) {
if (!ValidateParams(context, "copy", from, to)) {
return false;
}
@ -268,119 +233,134 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
copy_symlink(f, t);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what());
return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what());
}
LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
try {
copy_file(f, t);
permissions(t, GetParam(command, "read_only") == "true" ?
permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what());
return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what());
}
LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str())
spdlog::info("Copied image file '" + from + "' to '" + to + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const
bool PiscsiImage::SetImagePermissions(const CommandContext& context) const
{
string filename = GetParam(command, "file");
const string filename = GetParam(context.GetCommand(), "file");
if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename");
return context.ReturnErrorStatus("Missing image filename");
}
if (!CheckDepth(filename)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
}
filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) {
return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type");
const string full_filename = GetFullName(filename);
if (!IsValidSrcFilename(full_filename)) {
return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type");
}
const bool protect = command.operation() == PROTECT_IMAGE;
const bool protect = context.GetCommand().operation() == PROTECT_IMAGE;
try {
permissions(path(filename), protect ?
permissions(path(full_filename), protect ?
perms::owner_read | perms::group_read | perms::others_read :
perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write);
}
catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
filename + "': " + e.what());
return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
full_filename + "': " + e.what());
}
if (protect) {
LOGINFO("Protected image file '%s'", filename.c_str())
}
else {
LOGINFO("Unprotected image file '%s'", filename.c_str())
}
spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'");
return context.ReturnStatus();
return context.ReturnSuccessStatus();
}
bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation,
string& from, string& to) const
bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op)
{
from = GetParam(command, "from");
if (from.empty()) {
return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename");
}
if (!CheckDepth(from)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type");
}
to = GetParam(command, "to");
if (to.empty()) {
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists");
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file);
if (id != -1) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + file +
"', it is currently being used by device " + to_string(id) + ":" + to_string(lun));
}
return true;
}
bool PiscsiImage::IsValidSrcFilename(const string& filename)
bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const
{
from = GetParam(context.GetCommand(), "from");
if (from.empty()) {
return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename");
}
if (!CheckDepth(from)) {
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'");
}
to = GetParam(context.GetCommand(), "to");
if (to.empty()) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'");
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type");
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists");
}
if (!IsReservedFile(context, from, op)) {
return false;
}
if (!CreateImageFolder(context, to)) {
return false;
}
return true;
}
bool PiscsiImage::IsValidSrcFilename(string_view filename)
{
// Source file must exist and must be a regular file or a symlink
path file(filename);
return is_regular_file(file) || is_symlink(file);
error_code error;
return is_regular_file(file, error) || is_symlink(file, error);
}
bool PiscsiImage::IsValidDstFilename(const string& filename)
bool PiscsiImage::IsValidDstFilename(string_view filename)
{
// Destination file must not yet exist
try {
return !exists(path(filename));
}
catch(const filesystem_error&) {
return true;
return false;
}
}
@ -394,7 +374,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam
error_code error;
remove(filename, error);
return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e));
return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e));
}
permissions(filename, read_only ?
@ -437,5 +417,5 @@ pair<int, int> PiscsiImage::GetUidAndGid()
gid = pwd.pw_gid;
}
return make_pair(uid, gid);
return { uid, gid };
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -28,22 +28,23 @@ public:
void SetDepth(int d) { depth = d; }
int GetDepth() const { return depth; }
string GetDefaultFolder() const { return default_folder; }
string SetDefaultFolder(const string&);
bool CreateImage(const CommandContext&, const PbCommand&) const;
bool DeleteImage(const CommandContext&, const PbCommand&) const;
bool RenameImage(const CommandContext&, const PbCommand&) const;
bool CopyImage(const CommandContext&, const PbCommand&) const;
bool SetImagePermissions(const CommandContext&, const PbCommand&) const;
string SetDefaultFolder(string_view);
bool CreateImage(const CommandContext&) const;
bool DeleteImage(const CommandContext&) const;
bool RenameImage(const CommandContext&) const;
bool CopyImage(const CommandContext&) const;
bool SetImagePermissions(const CommandContext&) const;
private:
bool CheckDepth(string_view) const;
string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
bool CreateImageFolder(const CommandContext&, const string&) const;
bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const;
bool CreateImageFolder(const CommandContext&, string_view) const;
static bool IsReservedFile(const CommandContext&, const string&, const string&);
bool ValidateParams(const CommandContext&, const string&, string&, string&) const;
static bool IsValidSrcFilename(const string&);
static bool IsValidDstFilename(const string&);
static bool IsValidSrcFilename(string_view);
static bool IsValidDstFilename(string_view);
static bool ChangeOwner(const CommandContext&, const path&, bool);
static string GetHomeDir();
static pair<int, int> GetUidAndGid();

View File

@ -3,50 +3,48 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "controllers/controller_manager.h"
#include "shared/protobuf_util.h"
#include "shared/network_util.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_version.h"
#include "devices/disk.h"
#include "devices/device_factory.h"
#include "generated/piscsi_interface.pb.h"
#include "piscsi_response.h"
#include <spdlog/spdlog.h>
#include <filesystem>
using namespace std;
using namespace filesystem;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace network_util;
using namespace protobuf_util;
unique_ptr<PbDeviceProperties> PiscsiResponse::GetDeviceProperties(const Device& device) const
void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const
{
auto properties = make_unique<PbDeviceProperties>();
// Currently there is only a SCSI controller, i.e. there can always be 32 LUNs
properties->set_luns(32);
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(device.SupportsFile());
properties->set_supports_params(device.SupportsParams());
properties.set_luns(ControllerManager::GetScsiLunMax());
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(device.SupportsFile());
properties.set_supports_params(device.SupportsParams());
if (device.SupportsParams()) {
for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) {
auto& map = *properties->mutable_default_params();
auto& map = *properties.mutable_default_params();
map[key] = value;
}
}
for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) {
properties->add_block_sizes(block_size);
properties.add_block_sizes(block_size);
}
return properties;
}
void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const
@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf
auto type_properties = device_types_info.add_properties();
type_properties->set_type(type);
const auto device = device_factory.CreateDevice(type, 0, "");
type_properties->set_allocated_properties(GetDeviceProperties(*device).release());
} //NOSONAR The allocated memory is managed by protobuf
GetDeviceProperties(*device, *type_properties->mutable_properties());
}
void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const
void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const
{
// Start with 2 instead of 1. 1 was the removed SASI drive type.
int ordinal = 2;
@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
pb_device.set_revision(device.GetRevision());
pb_device.set_type(device.GetType());
pb_device.set_allocated_properties(GetDeviceProperties(device).release());
GetDeviceProperties(device, *pb_device.mutable_properties());
auto status = make_unique<PbDeviceStatus>().release(); //NOSONAR The allocated memory is managed by protobuf
pb_device.set_allocated_status(status);
auto status = pb_device.mutable_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
if (device.SupportsParams()) {
for (const auto& [key, value] : device.GetParams()) {
SetParam(pb_device, key, value);
}
@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
const auto storage_device = dynamic_cast<const StorageDevice *>(&device);
if (storage_device != nullptr) {
auto image_file = make_unique<PbImageFile>().release();
GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : "");
pb_device.set_allocated_file(image_file);
GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : "");
}
} //NOSONAR The allocated memory is managed by protobuf
}
bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const
{
@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
image_file.set_name(filename);
image_file.set_type(device_factory.GetTypeForFile(filename));
const string f = filename[0] == '/' ? filename : default_folder + "/" + filename;
const path p(filename[0] == '/' ? filename : default_folder + "/" + filename);
image_file.set_read_only(access(f.c_str(), W_OK));
image_file.set_read_only(access(p.c_str(), W_OK));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) {
image_file.set_size(st.st_size);
error_code error;
if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) {
image_file.set_size(file_size(p));
return true;
}
}
@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
}
void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder,
const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
if (scan_depth-- < 0) {
const path default_path(default_folder);
if (!is_directory(default_path)) {
return;
}
string folder_pattern_lower = folder_pattern;
transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower);
string folder_pattern_lower;
ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower);
string file_pattern_lower = file_pattern;
transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower);
string file_pattern_lower;
ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower);
DIR *d = opendir(folder.c_str());
if (d == nullptr) {
return;
}
// C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
const dirent *dir;
while ((dir = readdir(d))) {
string filename = GetNextImageFile(dir, folder);
if (filename.empty()) {
for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink);
it != recursive_directory_iterator(); it++) {
if (it.depth() > scan_depth) {
it.disable_recursion_pending();
continue;
}
string name_lower = dir->d_name;
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
const string parent = it->path().parent_path().string();
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);
}
const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : "";
if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) {
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(), default_folder, filename)) {
GetImageFile(*image_files_info.add_image_files(), default_folder,
filename.substr(default_folder.length() + 1));
}
if (!ValidateImageFile(it->path())) {
continue;
}
const string filename = folder.empty() ?
it->path().filename().string() : folder + "/" + it->path().filename().string();
if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) {
GetImageFile(*image_files_info.add_image_files(), default_folder, filename);
}
}
closedir(d);
}
unique_ptr<PbImageFilesInfo> PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder,
void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
auto image_files_info = make_unique<PbImageFilesInfo>();
image_files_info.set_default_image_folder(default_folder);
image_files_info.set_depth(scan_depth);
image_files_info->set_default_image_folder(default_folder);
image_files_info->set_depth(scan_depth);
GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern,
file_pattern, scan_depth);
result.set_status(true);
return image_files_info;
GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth);
}
void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder,
void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder,
const string& folder_pattern, const string& file_pattern, int scan_depth) const
{
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());
server_info.mutable_image_files_info()->set_default_image_folder(default_folder);
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf
GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth);
}
unique_ptr<PbReservedIdsInfo> PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set<int>& ids) const
void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set<int>& ids) const
{
auto reserved_ids_info = make_unique<PbReservedIdsInfo>();
for (const int id : ids) {
reserved_ids_info->add_ids(id);
reserved_ids_info.add_ids(id);
}
result.set_status(true);
return reserved_ids_info;
}
void PiscsiResponse::GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbServerInfo& server_info,
@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
// If no device list was provided in the command get information on all devices
if (!command.devices_size()) {
for (const auto& device : devices) {
id_sets.insert(make_pair(device->GetId(), device->GetLun()));
id_sets.insert({ device->GetId(), device->GetLun() });
}
}
// Otherwise get information on the devices provided in the command
@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
}
}
auto devices_info = make_unique<PbDevicesInfo>();
auto devices_info = result.mutable_devices_info();
for (const auto& [id, lun] : id_sets) {
for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) {
@ -254,244 +224,179 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
}
}
result.set_allocated_devices_info(devices_info.release());
result.set_status(true);
}
unique_ptr<PbDeviceTypesInfo> PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set<shared_ptr<PrimaryDevice>>& devices,
const unordered_set<int>& reserved_ids, const string& default_folder, const string& folder_pattern,
const string& file_pattern, int scan_depth) const
{
auto device_types_info = make_unique<PbDeviceTypesInfo>();
GetAllDeviceTypeProperties(*device_types_info);
result.set_status(true);
return device_types_info;
GetVersionInfo(*server_info.mutable_version_info());
GetLogLevelInfo(*server_info.mutable_log_level_info());
GetDeviceTypesInfo(*server_info.mutable_device_types_info());
GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth);
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
GetMappingInfo(*server_info.mutable_mapping_info());
GetDevices(devices, server_info, default_folder);
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth);
}
unique_ptr<PbServerInfo> PiscsiResponse::GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>& devices,
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
void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const
{
auto server_info = make_unique<PbServerInfo>();
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, 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(devices, *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;
version_info.set_major_version(piscsi_major_version);
version_info.set_minor_version(piscsi_minor_version);
version_info.set_patch_version(piscsi_patch_version);
}
unique_ptr<PbVersionInfo> PiscsiResponse::GetVersionInfo(PbResult& result) const
void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const
{
auto version_info = make_unique<PbVersionInfo>();
version_info->set_major_version(piscsi_major_version);
version_info->set_minor_version(piscsi_minor_version);
version_info->set_patch_version(piscsi_patch_version);
result.set_status(true);
return version_info;
}
unique_ptr<PbLogLevelInfo> PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const
{
auto log_level_info = make_unique<PbLogLevelInfo>();
for (const auto& log_level : log_levels) {
log_level_info->add_log_levels(log_level);
for (const auto& log_level : spdlog::level::level_string_views) {
log_level_info.add_log_levels(log_level.data());
}
log_level_info->set_current_log_level(current_log_level);
result.set_status(true);
return log_level_info;
log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data());
}
unique_ptr<PbNetworkInterfacesInfo> PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const
void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const
{
auto network_interfaces_info = make_unique<PbNetworkInterfacesInfo>();
for (const auto& network_interface : device_factory.GetNetworkInterfaces()) {
network_interfaces_info->add_name(network_interface);
for (const auto& network_interface : GetNetworkInterfaces()) {
network_interfaces_info.add_name(network_interface);
}
result.set_status(true);
return network_interfaces_info;
}
unique_ptr<PbMappingInfo> PiscsiResponse::GetMappingInfo(PbResult& result) const
void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const
{
auto mapping_info = make_unique<PbMappingInfo>();
for (const auto& [name, type] : device_factory.GetExtensionMapping()) {
(*mapping_info->mutable_mapping())[name] = type;
(*mapping_info.mutable_mapping())[name] = type;
}
result.set_status(true);
return mapping_info;
}
unique_ptr<PbOperationInfo> PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const
void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, 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");
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").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();
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);
operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required");
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 piscsi server information");
operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release();
CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version");
CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release();
CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices");
CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release();
CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type");
operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
if (depth) {
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
}
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
operation.release();
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release();
CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information");
CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release();
CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces");
CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release();
CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types");
CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release();
CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release();
operation.release();
operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true);
operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true).release();
operation.release();
operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true);
operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release();
operation.release();
operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true);
operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot");
auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release();
parameter->add_permitted_values("piscsi");
// System shutdown/reboot requires root permissions
if (!getuid()) {
parameter->add_permitted_values("system");
parameter->add_permitted_values("reboot");
operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot");
if (getuid()) {
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } );
}
else {
// System shutdown/reboot requires root permissions
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } );
}
operation.release();
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file");
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, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true);
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
AddOperationParameter(*operation, "to", "Destination 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);
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file");
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, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
operation.release();
operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true);
operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable");
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);
operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", 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);
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release();
result.set_status(true);
return operation_info;
CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data");
}
unique_ptr<PbOperationMetaData> PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
// This method returns a raw pointer because protobuf does not have support for smart pointers
PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
const string& description) const
{
auto meta_data = make_unique<PbOperationMetaData>();
meta_data->set_server_side_name(PbOperation_Name(operation));
meta_data->set_description(description);
PbOperationMetaData meta_data;
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.release();
return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]);
(*operation_info.mutable_operations())[ordinal] = meta_data;
return &(*operation_info.mutable_operations())[ordinal];
}
unique_ptr<PbOperationParameter> PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data,
const string& name, const string& description, const string& default_value, bool is_mandatory) const
void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name,
const string& description, const string& default_value, bool is_mandatory,
const vector<string>& permitted_values) const
{
auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters());
auto parameter = meta_data.add_parameters();
parameter->set_name(name);
parameter->set_description(description);
parameter->set_default_value(default_value);
parameter->set_is_mandatory(is_mandatory);
return parameter;
for (const auto& permitted_value : permitted_values) {
parameter->add_permitted_values(permitted_value);
}
}
set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbResult& result,
@ -503,7 +408,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
bool has_device = false;
for (const auto& d : devices) {
if (d->GetId() == device.id() && d->GetLun() == device.unit()) {
id_sets.insert(make_pair(device.id(), device.unit()));
id_sets.insert({ device.id(), device.unit() });
has_device = true;
break;
}
@ -513,7 +418,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
id_sets.clear();
result.set_status(false);
result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit()));
result.set_msg("No device for " + to_string(device.id()) + ":" + to_string(device.unit()));
break;
}
@ -522,30 +427,45 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
return id_sets;
}
string PiscsiResponse::GetNextImageFile(const dirent *dir, const string& folder)
bool PiscsiResponse::ValidateImageFile(const path& path)
{
// 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 "";
if (path.filename().string().starts_with(".")) {
return false;
}
const string filename = folder + "/" + dir->d_name;
filesystem::path p(path);
const bool file_exists = exists(path(filename));
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
struct stat st;
stat(filename.c_str(), &st);
if (dir->d_type == DT_REG && file_exists && !st.st_size) {
LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str())
return "";
// Follow symlink
if (is_symlink(p)) {
p = read_symlink(p);
if (!exists(p)) {
spdlog::warn("Image file symlink '" + path.string() + "' is broken");
return false;
}
}
if (dir->d_type == DT_LNK && !file_exists) {
LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str())
return "";
if (is_directory(p) || (is_other(p) && !is_block_file(p))) {
return false;
}
return filename;
if (!is_block_file(p) && file_size(p) < 256) {
spdlog::warn("Image file '" + p.string() + "' is invalid");
return false;
}
return true;
}
bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower)
{
if (!pattern_lower.empty()) {
string name_lower;
ranges::transform(input, back_inserter(name_lower), ::tolower);
if (name_lower.find(pattern_lower) == string::npos) {
return false;
}
}
return true;
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -12,52 +12,51 @@
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "generated/piscsi_interface.pb.h"
#include <dirent.h>
#include <array>
#include <string>
#include <span>
using namespace std;
using namespace filesystem;
using namespace piscsi_interface;
class PiscsiResponse
{
using id_set = pair<int, int>;
public:
PiscsiResponse() = default;
~PiscsiResponse() = default;
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 GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetReservedIds(PbReservedIdsInfo&, const unordered_set<int>&) const;
void GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbServerInfo&, const string&) const;
void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const;
unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const;
unique_ptr<PbServerInfo> GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, 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;
void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
void GetVersionInfo(PbVersionInfo&) const;
void GetServerInfo(PbServerInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&, const unordered_set<int>&,
const string&, const string&, const string&, int) const;
void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
void GetMappingInfo(PbMappingInfo&) const;
void GetLogLevelInfo(PbLogLevelInfo&) const;
void GetOperationInfo(PbOperationInfo&, int) const;
private:
DeviceFactory device_factory;
inline static const vector<string> EMPTY_VECTOR;
const inline static array<string, 6> log_levels = { "trace", "debug", "info", "warn", "err", "off" };
const DeviceFactory device_factory;
unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
void GetDeviceProperties(const Device&, PbDeviceProperties&) 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;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const;
PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
void AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false, const vector<string>& = EMPTY_VECTOR) const;
set<id_set> MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&) const;
static string GetNextImageFile(const dirent *, const string&);
static bool ValidateImageFile(const path&);
static bool FilterMatches(const string&, string_view);
};

View File

@ -3,140 +3,114 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_serializer.h"
#include "shared/piscsi_exceptions.h"
#include "command_context.h"
#include "localizer.h"
#include "piscsi_service.h"
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <csignal>
#include <cassert>
using namespace piscsi_interface;
using namespace piscsi_util;
void PiscsiService::Cleanup() const
string PiscsiService::Init(const callback& cb, int port)
{
running = false;
assert(service_socket == -1);
if (service_socket != -1) {
close(service_socket);
}
}
bool PiscsiService::Init(const callback& cb, int port)
{
if (port <= 0 || port > 65535) {
return false;
return "Invalid port number " + to_string(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;
return "Unable to create service socket: " + string(strerror(errno));
}
if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
Stop();
return "Can't reuse address";
}
sockaddr_in server = {};
server.sin_family = PF_INET;
server.sin_port = htons((uint16_t)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;
server.sin_addr.s_addr = INADDR_ANY;
if (bind(service_socket, reinterpret_cast<const sockaddr *>(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler
Stop();
return "Port " + to_string(port) + " is in use, is piscsi already running?";
}
signal(SIGPIPE, SIG_IGN);
if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) {
cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl;
return false;
if (listen(service_socket, 2) == -1) {
Stop();
return "Can't listen to service socket: " + string(strerror(errno));
}
execute = cb;
monthread = thread(&PiscsiService::Execute, this);
monthread.detach();
return "";
}
// Interrupt handler settings
return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR
&& signal(SIGTERM, KillHandler) != SIG_ERR;
void PiscsiService::Start()
{
assert(service_socket != -1);
service_thread = jthread([this] () { Execute(); } );
}
void PiscsiService::Stop()
{
assert(service_socket != -1);
shutdown(service_socket, SHUT_RD);
close(service_socket);
service_socket = -1;
}
void PiscsiService::Execute() const
{
#ifdef __linux__
// Scheduler Settings
sched_param schedparam;
schedparam.sched_priority = 0;
// Run this thread with very low priority
sched_param schedparam = { .sched_priority = 0 };
sched_setscheduler(0, SCHED_IDLE, &schedparam);
#endif
// Set the affinity to a specific processor core
FixCpu(2);
// Wait for the execution to start
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000};
while (!running) {
nanosleep(&ts, nullptr);
}
// Set up the monitor socket to receive commands
listen(service_socket, 1);
while (true) {
CommandContext context;
try {
PbCommand command = ReadCommand(context);
if (context.IsValid()) {
execute(context, command);
}
// TODO Accept more than one command instead of closing the socket after a single command
while (service_socket != -1) {
const int fd = accept(service_socket, nullptr, nullptr);
if (fd != -1) {
ExecuteCommand(fd);
close(fd);
}
catch(const io_exception& e) {
LOGWARN("%s", e.what())
// Fall through
}
context.Cleanup();
}
}
PbCommand PiscsiService::ReadCommand(CommandContext& context) const
void PiscsiService::ExecuteCommand(int fd) const
{
// Wait for connection
sockaddr client = {};
socklen_t socklen = sizeof(client);
const int fd = accept(service_socket, &client, &socklen);
if (fd == -1) {
throw io_exception("accept() failed");
CommandContext context(fd);
try {
if (context.ReadCommand()) {
execute(context);
}
}
catch(const io_exception& e) {
spdlog::warn(e.what());
PbCommand command;
// Read magic string
vector<byte> magic(6);
const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic);
if (!bytes_read) {
return command;
// Try to return an error message (this may fail if the exception was caused when returning the actual result)
PbResult result;
result.set_msg(e.what());
try {
context.WriteResult(result);
}
catch(const io_exception&) { //NOSONAR Not handled on purpose
// Ignore
}
}
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
throw io_exception("Invalid magic");
}
// Fetch the command
context.GetSerializer().DeserializeMessage(fd, command);
context.SetFd(fd);
return command;
}

View File

@ -3,49 +3,42 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "generated/piscsi_interface.pb.h"
#include <functional>
#include <thread>
#include <string>
class CommandContext;
using namespace std;
using namespace piscsi_interface;
class PiscsiService
{
using callback = function<bool(const CommandContext&, piscsi_interface::PbCommand&)>;
callback execute;
int service_socket = -1;
thread monthread;
static inline volatile bool running = false;
using callback = function<bool(CommandContext&)>;
public:
PiscsiService() = default;
~PiscsiService() = default;
bool Init(const callback&, int);
void Cleanup() const;
bool IsRunning() const { return running; }
void SetRunning(bool b) const { running = b; }
string Init(const callback&, int);
void Start();
void Stop();
bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); }
private:
void Execute() const;
void ExecuteCommand(int) const;
PbCommand ReadCommand(CommandContext&) const;
callback execute;
static void KillHandler(int) { running = false; }
jthread service_thread;
int service_socket = -1;
};

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -23,7 +23,7 @@ package piscsi_interface;
// The available device types
enum PbDeviceType {
UNDEFINED = 0;
// Non-removable SASI drive, not supported anymore
// Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility
SAHD = 1 [deprecated = true];
// Non-removable SCSI drive
SCHD = 2;

View File

@ -3,26 +3,31 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/network_util.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h"
#include "scsictl_commands.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <netdb.h>
#include <iostream>
#include <list>
#include <vector>
using namespace std;
using namespace piscsi_interface;
using namespace network_util;
using namespace piscsi_util;
using namespace protobuf_util;
bool ScsictlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids,
const string& image_params, const string& filename)
bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, string_view reserved_ids,
string_view image_params, string_view filename)
{
switch(command.operation()) {
case LOG_LEVEL:
@ -79,6 +84,9 @@ bool ScsictlCommands::Execute(const string& log_level, const string& default_fol
case OPERATION_INFO:
return CommandOperationInfo();
case NO_OPERATION:
return false;
default:
return SendCommand();
}
@ -112,8 +120,8 @@ bool ScsictlCommands::SendCommand()
throw io_exception("Can't write magic");
}
serializer.SerializeMessage(fd, command);
serializer.DeserializeMessage(fd, result);
SerializeMessage(fd, command);
DeserializeMessage(fd, result);
close(fd);
@ -137,27 +145,23 @@ bool ScsictlCommands::CommandDevicesInfo()
return true;
}
bool ScsictlCommands::CommandLogLevel(const string& log_level)
bool ScsictlCommands::CommandLogLevel(string_view log_level)
{
SetParam(command, "level", log_level);
return SendCommand();
}
bool ScsictlCommands::CommandReserveIds(const string& reserved_ids)
bool ScsictlCommands::CommandReserveIds(string_view reserved_ids)
{
SetParam(command, "ids", reserved_ids);
return SendCommand();
}
bool ScsictlCommands::CommandCreateImage(const string& image_params)
bool ScsictlCommands::CommandCreateImage(string_view image_params)
{
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
SetParam(command, "file", string_view(image_params).substr(0, separator_pos));
SetParam(command, "size", string_view(image_params).substr(separator_pos + 1));
}
else {
if (!EvaluateParams(image_params, "file", "size")) {
cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl;
return false;
@ -168,20 +172,16 @@ bool ScsictlCommands::CommandCreateImage(const string& image_params)
return SendCommand();
}
bool ScsictlCommands::CommandDeleteImage(const string& filename)
bool ScsictlCommands::CommandDeleteImage(string_view filename)
{
SetParam(command, "file", filename);
return SendCommand();
}
bool ScsictlCommands::CommandRenameImage(const string& image_params)
bool ScsictlCommands::CommandRenameImage(string_view image_params)
{
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
SetParam(command, "from", string_view(image_params).substr(0, separator_pos));
SetParam(command, "to", string_view(image_params).substr(separator_pos + 1));
}
else {
if (!EvaluateParams(image_params, "from", "to")) {
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
return false;
@ -190,13 +190,9 @@ bool ScsictlCommands::CommandRenameImage(const string& image_params)
return SendCommand();
}
bool ScsictlCommands::CommandCopyImage(const string& image_params)
bool ScsictlCommands::CommandCopyImage(string_view image_params)
{
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
SetParam(command, "from", string_view(image_params).substr(0, separator_pos));
SetParam(command, "to", string_view(image_params).substr(separator_pos + 1));
}
else {
if (!EvaluateParams(image_params, "from", "to")) {
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
return false;
@ -205,7 +201,7 @@ bool ScsictlCommands::CommandCopyImage(const string& image_params)
return SendCommand();
}
bool ScsictlCommands::CommandDefaultImageFolder(const string& folder)
bool ScsictlCommands::CommandDefaultImageFolder(string_view folder)
{
SetParam(command, "folder", folder);
@ -259,8 +255,8 @@ bool ScsictlCommands::CommandServerInfo()
cout << scsictl_display.DisplayOperationInfo(server_info.operation_info());
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() || a.unit() < b.unit(); });
vector<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); });
cout << "Attached devices:\n";
@ -283,7 +279,7 @@ bool ScsictlCommands::CommandDefaultImageFilesInfo()
return true;
}
bool ScsictlCommands::CommandImageFileInfo(const string& filename)
bool ScsictlCommands::CommandImageFileInfo(string_view filename)
{
SetParam(command, "file", filename);
@ -339,15 +335,12 @@ bool ScsictlCommands::CommandOperationInfo()
return true;
}
bool ScsictlCommands::ResolveHostName(const string& host, sockaddr_in *addr)
bool ScsictlCommands::EvaluateParams(string_view image_params, const string& key1, const string& key2)
{
addrinfo hints = {};
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if (const auto& components = Split(string(image_params), COMPONENT_SEPARATOR, 2); components.size() == 2) {
SetParam(command, key1, components[0]);
SetParam(command, key2, components[1]);
if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) {
*addr = *(sockaddr_in *)(result->ai_addr);
freeaddrinfo(result);
return true;
}

View File

@ -3,13 +3,12 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "shared/protobuf_serializer.h"
#include "generated/piscsi_interface.pb.h"
#include "scsictl_display.h"
#include <string>
@ -26,40 +25,38 @@ public:
: command(command), hostname(hostname), port(port) {}
~ScsictlCommands() = default;
bool Execute(const string&, const string&, const string&, const string&, const string&);
bool Execute(string_view, string_view, string_view, string_view, string_view);
bool CommandDevicesInfo();
private:
bool CommandLogLevel(const string&);
bool CommandReserveIds(const string&);
bool CommandCreateImage(const string&);
bool CommandDeleteImage(const string&);
bool CommandRenameImage(const string&);
bool CommandCopyImage(const string&);
bool CommandDefaultImageFolder(const string&);
bool CommandLogLevel(string_view);
bool CommandReserveIds(string_view);
bool CommandCreateImage(string_view);
bool CommandDeleteImage(string_view);
bool CommandRenameImage(string_view);
bool CommandCopyImage(string_view);
bool CommandDefaultImageFolder(string_view);
bool CommandDeviceInfo();
bool CommandDeviceTypesInfo();
bool CommandVersionInfo();
bool CommandServerInfo();
bool CommandDefaultImageFilesInfo();
bool CommandImageFileInfo(const string&);
bool CommandImageFileInfo(string_view);
bool CommandNetworkInterfacesInfo();
bool CommandLogLevelInfo();
bool CommandReservedIdsInfo();
bool CommandMappingInfo();
bool CommandOperationInfo();
bool SendCommand();
bool EvaluateParams(string_view, const string&, const string&);
static bool ResolveHostName(const string&, sockaddr_in *);
ProtobufSerializer serializer;
PbCommand& command;
string hostname;
int port;
PbResult result;
ScsictlDisplay scsictl_display;
[[no_unique_address]] const ScsictlDisplay scsictl_display;
};

View File

@ -9,12 +9,12 @@
//
//---------------------------------------------------------------------------
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "shared/piscsi_util.h"
#include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h"
#include "generated/piscsi_interface.pb.h"
#include "scsictl/scsictl_parser.h"
#include "scsictl/scsictl_commands.h"
#include "scsictl/scsictl_core.h"
@ -30,27 +30,27 @@ using namespace protobuf_util;
void ScsiCtl::Banner(const vector<char *>& args) const
{
if (args.size() < 2) {
cout << piscsi_util::Banner("(Controller App)");
cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] ";
cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] ";
cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] ";
cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] ";
cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n";
cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n";
cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n";
cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n";
cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n";
cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n";
cout << " FILE|PARAM := image file path or device-specific parameter\n";
cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n";
cout << " HOST := piscsi host to connect to, default is 'localhost'\n";
cout << " PORT := piscsi port to connect to, default is 6868\n";
cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n";
cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n";
cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n";
cout << "Usage: " << args[0] << " -l\n";
cout << " Print device list.\n" << flush;
cout << piscsi_util::Banner("(Controller App)")
<< "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "
<< "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "
<< "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "
<< "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "
<< "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"
<< " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "},"
<< " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n"
<< " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"
<< " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"
<< " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"
<< " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"
<< " FILE|PARAM := image file path or device-specific parameter\n"
<< " IMAGE_FOLDER := default location for image files, default is '~/images'\n"
<< " HOST := piscsi host to connect to, default is 'localhost'\n"
<< " PORT := piscsi port to connect to, default is 6868\n"
<< " RESERVED_IDS := comma-separated list of IDs to reserve\n"
<< " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"
<< " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"
<< "Usage: " << args[0] << " -l\n"
<< " Print device list.\n" << flush;
exit(EXIT_SUCCESS);
}
@ -77,10 +77,7 @@ int ScsiCtl::run(const vector<char *>& args) const
string token;
bool list = false;
const char *locale = setlocale(LC_MESSAGES, "");
if (locale == nullptr || !strcmp(locale, "C")) {
locale = "en";
}
string locale = GetLocale();
opterr = 1;
int opt;
@ -88,7 +85,7 @@ int ScsiCtl::run(const vector<char *>& args) const
"e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
switch (opt) {
case 'i':
if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) {
if (const string error = SetIdAndLun(*device, optarg); !error.empty()) {
cerr << "Error: " << error << endl;
exit(EXIT_FAILURE);
}
@ -216,7 +213,7 @@ int ScsiCtl::run(const vector<char *>& args) const
break;
case 'v':
cout << "scsictl version: " << piscsi_get_version_string() << endl;
cout << "scsictl version: " << piscsi_get_version_string() << '\n';
exit(EXIT_SUCCESS);
break;
@ -263,7 +260,7 @@ int ScsiCtl::run(const vector<char *>& args) const
bool status;
try {
// Listing devices is a special case (rasctl backwards compatibility)
// Listing devices is a special case (legacy rasctl backwards compatibility)
if (list) {
command.clear_devices();
command.set_operation(DEVICES_INFO);

View File

@ -3,26 +3,29 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "shared/piscsi_util.h"
#include "shared/protobuf_util.h"
#include "generated/piscsi_interface.pb.h"
#include "scsictl_display.h"
#include <sstream>
#include <list>
#include <vector>
#include <set>
#include <map>
#include <iomanip>
using namespace std;
using namespace piscsi_interface;
using namespace piscsi_util;
using namespace protobuf_util;
string ScsictlDisplay::DisplayDevicesInfo(const PbDevicesInfo& devices_info) const
{
ostringstream s;
const list<PbDevice>& devices = { devices_info.devices().begin(), devices_info.devices().end() };
const vector<PbDevice> devices(devices_info.devices().begin(), devices_info.devices().end());
s << ListDevices(devices);
@ -50,51 +53,33 @@ string ScsictlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) const
s << " ";
bool hasProperty = false;
vector<string> properties;
if (pb_device.properties().read_only()) {
s << "read-only";
hasProperty = true;
properties.emplace_back("read-only");
}
if (pb_device.properties().protectable() && pb_device.status().protected_()) {
if (hasProperty) {
s << ", ";
}
s << "protected";
hasProperty = true;
properties.emplace_back("protected");
}
if (pb_device.properties().stoppable() && pb_device.status().stopped()) {
if (hasProperty) {
s << ", ";
}
s << "stopped";
hasProperty = true;
properties.emplace_back("stopped");
}
if (pb_device.properties().removable() && pb_device.status().removed()) {
if (hasProperty) {
s << ", ";
}
s << "removed";
hasProperty = true;
properties.emplace_back("removed");
}
if (pb_device.properties().lockable() && pb_device.status().locked()) {
if (hasProperty) {
s << ", ";
}
s << "locked";
properties.emplace_back("locked");
}
if (hasProperty) {
s << " ";
if (!properties.empty()) {
s << Join(properties) << " ";
}
DisplayParams(s, pb_device);
s << '\n';
s << DisplayParams(pb_device) << '\n';
return s.str();
}
@ -103,8 +88,8 @@ string ScsictlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) con
{
ostringstream s;
s << "piscsi server version: " << setw(2) << setfill('0') << version_info.major_version() << "."
<< setw(2) << setfill('0') << version_info.minor_version();
s << "piscsi server version: " << setfill('0') << setw(2) << version_info.major_version() << "."
<< setw(2) << version_info.minor_version();
if (version_info.patch_version() > 0) {
s << "." << version_info.patch_version();
@ -149,7 +134,7 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty
const PbDeviceProperties& properties = device_type_info.properties();
DisplayAttributes(s, properties);
s << DisplayAttributes(properties);
if (properties.supports_file()) {
s << "Image file support\n ";
@ -159,9 +144,9 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty
s << "Parameter support\n ";
}
DisplayDefaultParameters(s, properties);
s << DisplayDefaultParameters(properties);
DisplayBlockSizes(s, properties);
s << DisplayBlockSizes(properties);
}
s << '\n';
@ -174,20 +159,8 @@ string ScsictlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_
ostringstream s;
if (reserved_ids_info.ids_size()) {
s << "Reserved device IDs: ";
for (int i = 0; i < reserved_ids_info.ids_size(); i++) {
if(i) {
s << ", ";
}
s << reserved_ids_info.ids(i);
}
s << '\n';
}
else {
s << "No reserved device IDs\n";
const set<int32_t> sorted_ids(reserved_ids_info.ids().begin(), reserved_ids_info.ids().end());
s << "Reserved device IDs: " << Join(sorted_ids) << '\n';
}
return s.str();
@ -219,12 +192,9 @@ string ScsictlDisplay::DisplayImageFilesInfo(const PbImageFilesInfo& image_files
s << "Default image file folder: " << image_files_info.default_image_folder() << '\n';
s << "Supported folder depth: " << image_files_info.depth() << '\n';
if (image_files_info.image_files().empty()) {
s << " No image files available\n";
}
else {
list<PbImageFile> image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() };
image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); });
if (!image_files_info.image_files().empty()) {
vector<PbImageFile> image_files(image_files_info.image_files().begin(), image_files_info.image_files().end());
ranges::sort(image_files, [](const auto& a, const auto& b) { return a.name() < b.name(); });
s << "Available image files:\n";
for (const auto& image_file : image_files) {
@ -241,24 +211,8 @@ string ScsictlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& n
{
ostringstream s;
s << "Available (up) network interfaces:\n";
const list<string> sorted_interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() };
bool isFirst = true;
for (const auto& interface : sorted_interfaces) {
if (!isFirst) {
s << ", ";
}
else {
s << " ";
}
isFirst = false;
s << interface;
}
s << '\n';
const set<string, less<>> sorted_interfaces(network_interfaces_info.name().begin(), network_interfaces_info.name().end());
s << "Available (up) network interfaces: " << Join(sorted_interfaces) << '\n';
return s.str();
}
@ -269,9 +223,8 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con
s << "Supported image file extension to device type mappings:\n";
const map<string, PbDeviceType, less<>> sorted_mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() };
for (const auto& [extension, type] : sorted_mappings) {
for (const map<string, PbDeviceType, less<>> sorted_mappings(mapping_info.mapping().begin(), mapping_info.mapping().end());
const auto& [extension, type] : sorted_mappings) {
s << " " << extension << "->" << PbDeviceType_Name(type) << '\n';
}
@ -282,7 +235,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
{
ostringstream s;
const map<int, PbOperationMetaData, less<>> operations = { operation_info.operations().begin(), operation_info.operations().end() };
const map<int, PbOperationMetaData, less<>> operations(operation_info.operations().begin(), operation_info.operations().end());
// Copies result into a map sorted by operation name
auto unknown_operation = make_unique<PbOperationMetaData>();
@ -308,7 +261,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
}
s << '\n';
DisplayParameters(s, meta_data);
s << DisplayParameters(meta_data);
}
else {
s << " " << name << " (Unknown server-side operation)\n";
@ -318,96 +271,82 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
return s.str();
}
void ScsictlDisplay::DisplayParams(ostringstream& s, const PbDevice& pb_device) const
string ScsictlDisplay::DisplayParams(const PbDevice& pb_device) const
{
const map<string, string, less<>> sorted_params = { pb_device.params().begin(), pb_device.params().end() };
ostringstream s;
bool isFirst = true;
for (const auto& [key, value] : sorted_params) {
if (!isFirst) {
s << ":";
}
isFirst = false;
s << key << "=" << value;
}
}
void ScsictlDisplay::DisplayAttributes(ostringstream& s, const PbDeviceProperties& properties) const
{
if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.lockable()) {
s << "Properties: ";
bool has_property = false;
if (properties.read_only()) {
s << "read-only";
has_property = true;
}
if (properties.protectable()) {
s << (has_property ? ", " : "") << "protectable";
has_property = true;
}
if (properties.stoppable()) {
s << (has_property ? ", " : "") << "stoppable";
has_property = true;
}
if (properties.removable()) {
s << (has_property ? ", " : "") << "removable";
has_property = true;
}
if (properties.lockable()) {
s << (has_property ? ", " : "") << "lockable";
}
s << "\n ";
}
}
void ScsictlDisplay::DisplayDefaultParameters(ostringstream& s, const PbDeviceProperties& properties) const
{
if (properties.supports_params() && properties.default_params_size()) {
s << "Default parameters: ";
const map<string, string, less<>> sorted_params = { properties.default_params().begin(), properties.default_params().end() };
bool isFirst = true;
for (const auto& [key, value] : sorted_params) {
if (!isFirst) {
s << "\n ";
}
s << key << "=" << value;
isFirst = false;
}
set<string, less<>> params;
for (const auto& [key, value] : pb_device.params()) {
params.insert(key + "=" + value);
}
s << Join(params, ":");
return s.str();
}
void ScsictlDisplay::DisplayBlockSizes(ostringstream& s, const PbDeviceProperties& properties) const
string ScsictlDisplay::DisplayAttributes(const PbDeviceProperties& props) const
{
ostringstream s;
vector<string> properties;
if (props.read_only()) {
properties.emplace_back("read-only");
}
if (props.protectable()) {
properties.emplace_back("protectable");
}
if (props.stoppable()) {
properties.emplace_back("stoppable");
}
if (props.removable()) {
properties.emplace_back("removable");
}
if (props.lockable()) {
properties.emplace_back("lockable");
}
if (!properties.empty()) {
s << "Properties: " << Join(properties) << "\n ";
}
return s.str();
}
string ScsictlDisplay::DisplayDefaultParameters(const PbDeviceProperties& properties) const
{
ostringstream s;
if (!properties.default_params().empty()) {
set<string, less<>> params;
for (const auto& [key, value] : properties.default_params()) {
params.insert(key + "=" + value);
}
s << "Default parameters: " << Join(params, "\n ");
}
return s.str();
}
string ScsictlDisplay::DisplayBlockSizes(const PbDeviceProperties& properties) const
{
ostringstream s;
if (properties.block_sizes_size()) {
s << "Configurable block sizes in bytes: ";
const set<uint32_t> sorted_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() };
bool isFirst = true;
for (const auto& size : sorted_sizes) {
if (!isFirst) {
s << ", ";
}
s << size;
isFirst = false;
}
const set<uint32_t> sorted_sizes(properties.block_sizes().begin(), properties.block_sizes().end());
s << "Configurable block sizes in bytes: " << Join(sorted_sizes);
}
return s.str();
}
void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaData& meta_data) const
string ScsictlDisplay::DisplayParameters(const PbOperationMetaData& meta_data) const
{
list<PbOperationParameter> sorted_parameters = { meta_data.parameters().begin(), meta_data.parameters().end() };
sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); });
vector<PbOperationParameter> sorted_parameters(meta_data.parameters().begin(), meta_data.parameters().end());
ranges::sort(sorted_parameters, [](const auto& a, const auto& b) { return a.name() < b.name(); });
ostringstream s;
for (const auto& parameter : sorted_parameters) {
s << " " << parameter.name() << ": "
@ -418,30 +357,23 @@ void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaDa
}
s << '\n';
DisplayPermittedValues(s, parameter);
s << DisplayPermittedValues(parameter);
if (!parameter.default_value().empty()) {
s << " Default value: " << parameter.default_value() << '\n';
}
}
return s.str();
}
void ScsictlDisplay::DisplayPermittedValues(ostringstream& s, const PbOperationParameter& parameter) const
string ScsictlDisplay::DisplayPermittedValues(const PbOperationParameter& parameter) const
{
ostringstream s;
if (parameter.permitted_values_size()) {
s << " Permitted values: ";
bool isFirst = true;
for (const auto& permitted_value : parameter.permitted_values()) {
if (!isFirst) {
s << ", ";
}
isFirst = false;
s << permitted_value;
}
s << '\n';
const set<string, less<>> sorted_values(parameter.permitted_values().begin(), parameter.permitted_values().end());
s << " Permitted values: " << Join(parameter.permitted_values()) << '\n';
}
return s.str();
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
// Copyright (C) 2021-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -11,7 +11,6 @@
#include "generated/piscsi_interface.pb.h"
#include <string>
#include <sstream>
using namespace std;
using namespace piscsi_interface;
@ -37,10 +36,10 @@ public:
private:
void DisplayParams(ostringstream&, const PbDevice&) const;
void DisplayAttributes(ostringstream&, const PbDeviceProperties&) const;
void DisplayDefaultParameters(ostringstream&, const PbDeviceProperties&) const;
void DisplayBlockSizes(ostringstream&, const PbDeviceProperties&) const;
void DisplayParameters(ostringstream&, const PbOperationMetaData&) const;
void DisplayPermittedValues(ostringstream&, const PbOperationParameter&) const;
string DisplayParams(const PbDevice&) const;
string DisplayAttributes(const PbDeviceProperties&) const;
string DisplayDefaultParameters(const PbDeviceProperties&) const;
string DisplayBlockSizes(const PbDeviceProperties&) const;
string DisplayParameters(const PbOperationMetaData&) const;
string DisplayPermittedValues(const PbOperationParameter&) const;
};

View File

@ -3,13 +3,13 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "scsictl_parser.h"
PbOperation ScsictlParser::ParseOperation(const string& operation) const
PbOperation ScsictlParser::ParseOperation(string_view operation) const
{
const auto& it = operations.find(tolower(operation[0]));
return it != operations.end() ? it->second : NO_OPERATION;
@ -17,8 +17,8 @@ PbOperation ScsictlParser::ParseOperation(const string& operation) const
PbDeviceType ScsictlParser::ParseType(const string& type) const
{
string t = type;
transform(t.begin(), t.end(), t.begin(), ::toupper);
string t;
ranges::transform(type, back_inserter(t), ::toupper);
if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) {
return parsed_type;

View File

@ -22,12 +22,12 @@ public:
ScsictlParser() = default;
~ScsictlParser() = default;
PbOperation ParseOperation(const string&) const;
PbOperation ParseOperation(string_view) const;
PbDeviceType ParseType(const string&) const;
private:
unordered_map<int, PbOperation> operations = {
const unordered_map<int, PbOperation> operations = {
{ 'a', ATTACH },
{ 'd', DETACH },
{ 'e', EJECT },
@ -37,7 +37,7 @@ private:
{ 'u', UNPROTECT }
};
unordered_map<int, PbDeviceType> device_types = {
const unordered_map<int, PbDeviceType> device_types = {
{ 'b', SCBR },
{ 'c', SCCD },
{ 'd', SCDP },

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -13,7 +13,7 @@ using namespace std;
int main(int argc, char *argv[])
{
const vector<char *> args(argv, argv + argc);
vector<char *> args(argv, argv + argc);
return ScsiDump().run(args);
}

View File

@ -5,8 +5,8 @@
//
// Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022 akuker
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -17,10 +17,11 @@
#include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h"
#include "hal/systimer.h"
#include "shared/log.h"
#include "controllers/controller_manager.h"
#include "shared/piscsi_exceptions.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_version.h"
#include <spdlog/spdlog.h>
#include <filesystem>
#include <chrono>
#include <csignal>
#include <cstddef>
@ -28,10 +29,10 @@
#include <fstream>
#include <iostream>
#include <sstream>
#include <sys/stat.h>
#include <unistd.h>
using namespace std;
using namespace filesystem;
using namespace spdlog;
using namespace scsi_defs;
using namespace piscsi_util;
@ -50,17 +51,18 @@ void ScsiDump::KillHandler(int)
exit(EXIT_SUCCESS);
}
bool ScsiDump::Banner(const vector<char*>& args) const
bool ScsiDump::Banner(span<char *> args) const
{
cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n"
<< " ID is the target device ID (0-7).\n"
<< " LUN is the optional target device LUN (0-7). Default is 0.\n"
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
<< " Default is 0.\n"
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\n"
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE)
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE
<< " bytes. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n"
@ -86,7 +88,7 @@ bool ScsiDump::Init() const
return bus != nullptr;
}
void ScsiDump::ParseArguments(const vector<char*>& args)
void ScsiDump::ParseArguments(span<char *> args)
{
int opt;
@ -107,13 +109,13 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
case 's':
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB");
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
}
break;
case 't': {
const string error = ProcessId(optarg, 8, target_id, target_lun);
const string error = ProcessId(optarg, target_id, target_lun);
if (!error.empty()) {
throw parser_exception(error);
}
@ -149,7 +151,7 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
void ScsiDump::WaitPhase(phase_t phase) const
{
LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase))
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
// Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow();
@ -180,7 +182,7 @@ void ScsiDump::Selection() const
void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
{
LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second)
spdlog::debug("Executing " + command_mapping.find(cmd)->second.second);
Selection();
@ -324,7 +326,7 @@ pair<uint64_t, uint32_t> ScsiDump::ReadCapacity()
(static_cast<uint32_t>(buffer[sector_size_offset + 2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]);
return make_pair(capacity, sector_size);
return { capacity, sector_size };
}
void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length)
@ -387,7 +389,7 @@ void ScsiDump::WaitForBusy() const
}
}
int ScsiDump::run(const vector<char*>& args)
int ScsiDump::run(span<char *> args)
{
if (!Banner(args)) {
return EXIT_SUCCESS;
@ -436,12 +438,12 @@ int ScsiDump::DumpRestore()
if (restore) {
cout << "Starting restore\n" << flush;
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
off_t size;
if (struct stat st; !stat(filename.c_str(), &st)) {
size = st.st_size;
} else {
throw parser_exception("Can't determine file size");
try {
size = file_size(path(filename));
}
catch (const filesystem_error& e) {
throw parser_exception(string("Can't determine file size: ") + e.what());
}
cout << "Restore file size: " << size << " bytes\n";
@ -455,11 +457,11 @@ int ScsiDump::DumpRestore()
}
// Dump by buffer size
auto dsiz = static_cast<int>(buffer.size());
auto dsiz = static_cast<int>(buffer.size());
const int duni = dsiz / inq_info.sector_size;
auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
auto start_time = chrono::high_resolution_clock::now();
const auto start_time = chrono::high_resolution_clock::now();
int i;
for (i = 0; i < dnum; i++) {
@ -501,16 +503,16 @@ int ScsiDump::DumpRestore()
cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush;
}
auto stop_time = chrono::high_resolution_clock::now();
const auto stop_time = chrono::high_resolution_clock::now();
auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
cout << divider_str << "\n";
cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes ["
<< to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n";
cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n";
cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration)
<< " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024)
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
<< " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
<< " KiB per second)\n";
cout << divider_str << "\n";
@ -554,8 +556,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
cout << "Revision: " << str.data() << "\n" << flush;
inq_info.revision = string(str.data());
if (auto type = static_cast<device_type>(buffer[0]);
type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) {
if (const auto type = static_cast<device_type>(buffer[0]);
type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) {
throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY");
}
@ -564,8 +566,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
RequestSense();
const auto [capacity, sector_size] = ReadCapacity();
inq_info.capacity = capacity;
inq_info.sector_size = sector_size;
inq_info.capacity = capacity;
inq_info.sector_size = sector_size;
cout << "Sectors: " << capacity << "\n"
<< "Sector size: " << sector_size << " bytes\n"
@ -579,24 +581,23 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
{
string prop_filename = filename + ".properties";
string prop_str;
stringstream prop_stream(prop_str);
const string prop_filename = filename + ".properties";
stringstream prop_stream;
prop_stream << "{" << endl;
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl;
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl;
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl;
prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl;
prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl;
prop_stream << "}" << endl;
FILE* fp = fopen(prop_filename.c_str(), "w");
if (fp) {
fputs(prop_stream.str().c_str(), fp);
} else {
LOGWARN("Unable to open output file %s", prop_filename.c_str())
spdlog::warn("Unable to open output file '" + prop_filename + "'");
return;
}
fclose(fp);
}
}

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022-2023 Uwe Seimet
//
//---------------------------------------------------------------------------
@ -13,6 +13,7 @@
#include "shared/scsi.h"
#include <memory>
#include <string>
#include <span>
#include <vector>
using namespace std;
@ -26,7 +27,7 @@ class ScsiDump
ScsiDump() = default;
~ScsiDump() = default;
int run(const vector<char*>&);
int run(const span<char *>);
struct inquiry_info_struct {
string vendor;
@ -42,9 +43,9 @@ class ScsiDump
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info);
private:
bool Banner(const vector<char*>&) const;
bool Banner(span<char *>) const;
bool Init() const;
void ParseArguments(const vector<char*>&);
void ParseArguments(span<char *>);
int DumpRestore();
inquiry_info_t GetDeviceInfo();
void WaitPhase(phase_t) const;

View File

@ -12,7 +12,7 @@
//
//---------------------------------------------------------------------------
#include "shared/log.h"
#include "hal/log.h"
#include "shared/piscsi_version.h"
#include "shared/piscsi_util.h"
#include "spdlog/sinks/stdout_color_sinks.h"

View File

@ -30,11 +30,11 @@ void ScsiLoop_Cout::FinishTest(const string &test_name, int failures)
}
}
void ScsiLoop_Cout::PrintErrors(vector<string> &test_errors)
void ScsiLoop_Cout::PrintErrors(const vector<string> &test_errors)
{
if (test_errors.size() > 0) {
for (auto err_string : test_errors) {
if (!test_errors.empty()) {
for (auto& err_string : test_errors) {
cout << RED << err_string << endl;
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More