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

View File

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

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
// Base class for device controllers // Base class for device controllers
// //
@ -14,17 +14,19 @@
#include "shared/scsi.h" #include "shared/scsi.h"
#include "hal/bus.h" #include "hal/bus.h"
#include "phase_handler.h" #include "phase_handler.h"
#include "controller_manager.h" #include "devices/device_logger.h"
#include <unordered_set> #include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <span>
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <functional>
using namespace std; using namespace std;
class PrimaryDevice; class PrimaryDevice;
class AbstractController : public PhaseHandler, public enable_shared_from_this<AbstractController> class AbstractController : public PhaseHandler
{ {
public: public:
@ -37,19 +39,19 @@ public:
RESTART_PI RESTART_PI
}; };
AbstractController(shared_ptr<ControllerManager> controller_manager, int target_id, int max_luns) AbstractController(BUS&, int, int);
: controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {}
~AbstractController() override = default; ~AbstractController() override = default;
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information,
scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0; scsi_defs::status = scsi_defs::status::check_condition) = 0;
virtual void Reset(); virtual void Reset();
virtual int GetInitiatorId() const = 0; virtual int GetInitiatorId() const = 0;
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback // Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
virtual int GetEffectiveLun() const = 0; 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 GetTargetId() const { return target_id; }
int GetMaxLuns() const { return max_luns; } int GetMaxLuns() const { return max_luns; }
@ -58,52 +60,59 @@ public:
unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const; unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const; shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
bool AddDevice(shared_ptr<PrimaryDevice>); bool AddDevice(shared_ptr<PrimaryDevice>);
bool RemoveDevice(shared_ptr<PrimaryDevice>); bool RemoveDevice(PrimaryDevice&);
bool HasDeviceForLun(int) const; bool HasDeviceForLun(int) const;
int ExtractInitiatorId(int) const; void ProcessOnController(int);
// TODO These should probably be extracted into a new TransferHandler class // TODO These should probably be extracted into a new TransferHandler class
void AllocateBuffer(size_t); void AllocateBuffer(size_t);
vector<uint8_t>& GetBuffer() { return ctrl.buffer; } auto& GetBuffer() { return ctrl.buffer; }
scsi_defs::status GetStatus() const { return ctrl.status; } auto GetStatus() const { return ctrl.status; }
void SetStatus(scsi_defs::status s) { ctrl.status = s; } 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; } 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 SetBlocks(uint32_t b) { ctrl.blocks = b; }
void DecrementBlocks() { --ctrl.blocks; } 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 SetNext(uint64_t n) { ctrl.next = n; }
void IncrementNext() { ++ctrl.next; } void IncrementNext() { ++ctrl.next; }
int GetMessage() const { return ctrl.message; } int GetMessage() const { return ctrl.message; }
void SetMessage(int m) { ctrl.message = m; } void SetMessage(int m) { ctrl.message = m; }
vector<int>& GetCmd() { return ctrl.cmd; } auto GetCmd() const { return ctrl.cmd; }
int GetCmd(int index) const { return ctrl.cmd[index]; } int GetCmdByte(int index) const { return ctrl.cmd[index]; }
bool IsByteTransfer() const { return is_byte_transfer; } bool IsByteTransfer() const { return is_byte_transfer; }
void SetByteTransfer(bool); 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; } void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; }
protected: protected:
shared_ptr<ControllerManager> GetControllerManager() const { return controller_manager.lock(); } BUS& GetBus() const { return bus; }
inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); }
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; } int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
void ProcessPhase();
void AllocateCmd(size_t); 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 // TODO These should probably be extracted into a new TransferHandler class
bool HasValidLength() const { return ctrl.length != 0; } bool HasValidLength() const { return ctrl.length != 0; }
int GetOffset() const { return ctrl.offset; } int GetOffset() const { return ctrl.offset; }
void ResetOffset() { ctrl.offset = 0; } void ResetOffset() { ctrl.offset = 0; }
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; } void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; }
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: private:
int ExtractInitiatorId(int) const;
using ctrl_t = struct _ctrl_t { using ctrl_t = struct _ctrl_t {
// Command data, dynamically resized if required // Command data, dynamically resized if required
vector<int> cmd = vector<int>(16); vector<int> cmd = vector<int>(16);
@ -121,7 +130,9 @@ private:
ctrl_t ctrl = {}; ctrl_t ctrl = {};
weak_ptr<ControllerManager> controller_manager; BUS& bus;
DeviceLogger device_logger;
// Logical units of this controller mapped to their LUN numbers // Logical units of this controller mapped to their LUN numbers
unordered_map<int, shared_ptr<PrimaryDevice>> luns; unordered_map<int, shared_ptr<PrimaryDevice>> luns;
@ -132,4 +143,6 @@ private:
bool is_byte_transfer = false; bool is_byte_transfer = false;
uint32_t bytes_to_transfer = 0; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 "devices/primary_device.h"
#include "scsi_controller.h" #include "scsi_controller.h"
#include "controller_manager.h" #include "controller_manager.h"
using namespace std; 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); return make_shared<ScsiController>(bus, id);
if (controller != nullptr) { }
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); return controller->AddDevice(device);
} }
// If there is no LUN yet the first LUN must be LUN 0 // If this is LUN 0 create a new controller
if (device->GetLun() == 0) { if (!device->GetLun()) {
controller = make_shared<ScsiController>(shared_from_this(), id); if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) {
if (controller->AddDevice(device)) {
controllers[id] = controller; controllers[id] = controller;
return true; return true;
@ -35,20 +40,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice>
return false; return false;
} }
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller) bool ControllerManager::DeleteController(const AbstractController& controller)
{ {
return controllers.erase(controller->GetTargetId()) == 1; for (const auto& device : controller.GetDevices()) {
} device->CleanUp();
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{
for (const auto& [id, controller] : controllers) {
if (data & (1 << controller->GetTargetId())) {
return controller;
}
} }
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 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; 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>> ControllerManager::GetAllDevices() const
{ {
unordered_set<shared_ptr<PrimaryDevice>> devices; unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, controller] : controllers) { for (const auto& [_, controller] : controllers) {
const auto& d = controller->GetDevices(); const auto& d = controller->GetDevices();
devices.insert(d.begin(), d.end()); devices.insert(d.begin(), d.end());
} }
@ -69,12 +95,12 @@ unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() cons
return devices; 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) { if (const auto& controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun); return controller->GetDeviceForLun(lun);

View File

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

View File

@ -15,8 +15,6 @@
#pragma once #pragma once
#include "shared/scsi.h" #include "shared/scsi.h"
#include "controller_manager.h"
#include "devices/device_logger.h"
#include "abstract_controller.h" #include "abstract_controller.h"
#include <array> #include <array>
@ -52,20 +50,17 @@ class ScsiController : public AbstractController
public: public:
// Maximum number of logical units ScsiController(BUS&, int);
static inline const int LUN_MAX = 32;
explicit ScsiController(shared_ptr<ControllerManager>, int);
~ScsiController() override = default; ~ScsiController() override = default;
void Reset() override; void Reset() override;
phase_t Process(int) override; bool Process(int) override;
int GetEffectiveLun() const override; int GetEffectiveLun() const override;
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, 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; scsi_defs::status status = scsi_defs::status::check_condition) override;
int GetInitiatorId() const override { return initiator_id; } int GetInitiatorId() const override { return initiator_id; }
@ -79,16 +74,8 @@ public:
void DataIn() override; void DataIn() override;
void DataOut() 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: private:
DeviceLogger logger;
// Execution start time // Execution start time
uint32_t execstart = 0; uint32_t execstart = 0;
@ -109,6 +96,9 @@ private:
void DataOutNonBlockOriented(); void DataOutNonBlockOriented();
void Receive(); 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 ProcessCommand();
void ParseMessage(); void ParseMessage();
void ProcessMessage(); void ProcessMessage();
@ -116,7 +106,5 @@ private:
void Sleep(); void Sleep();
scsi_t scsi = {}; scsi_t scsi = {};
AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE;
}; };

View File

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

View File

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

View File

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

View File

@ -9,126 +9,71 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/log.h"
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "shared/piscsi_exceptions.h" #include "shared/network_util.h"
#include <unistd.h> #include <unistd.h>
#include <poll.h> #include <poll.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include "ctapdriver.h" #include "ctapdriver.h"
#include <spdlog/spdlog.h>
#include <net/if.h> #include <net/if.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sstream> #include <sstream>
#ifdef __linux__ #ifdef __linux__
#include <sys/epoll.h>
#include <linux/if.h>
#include <linux/if_tun.h> #include <linux/if_tun.h>
#include <linux/sockios.h> #include <linux/sockios.h>
#endif #endif
using namespace std; using namespace std;
using namespace piscsi_util; using namespace piscsi_util;
using namespace network_util;
//--------------------------------------------------------------------------- const string CTapDriver::BRIDGE_NAME = "piscsi_bridge";
//
// Initialization static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) {
//
//---------------------------------------------------------------------------
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
#ifndef __linux__ #ifndef __linux__
return false; return "if_nametoindex: Linux is required";
#else #else
ifreq ifr; ifreq ifr;
ifr.ifr_ifindex = if_nametoindex(ifname); ifr.ifr_ifindex = if_nametoindex(ifname.c_str());
if (ifr.ifr_ifindex == 0) { if (ifr.ifr_ifindex == 0) {
LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno)) return "Can't if_nametoindex " + ifname;
return false;
} }
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) { if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) {
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno)) return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF");
return false;
} }
return true; return "";
#endif #endif
} }
CTapDriver::~CTapDriver() string ip_link(int fd, const char* ifname, bool up) {
{
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) {
#ifndef __linux__ #ifndef __linux__
return false; return "Can't ip_link: Linux is required";
#else #else
ifreq ifr; ifreq ifr;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
int err = ioctl(fd, SIOCGIFFLAGS, &ifr); if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
if (err) { return "Can't ioctl SIOCGIFFLAGS";
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno))
return false;
} }
ifr.ifr_flags &= ~IFF_UP; ifr.ifr_flags &= ~IFF_UP;
if (up) { if (up) {
ifr.ifr_flags |= IFF_UP; ifr.ifr_flags |= IFF_UP;
} }
err = ioctl(fd, SIOCSIFFLAGS, &ifr); if (ioctl(fd, SIOCSIFFLAGS, &ifr)) {
if (err) { return "Can't ioctl SIOCSIFFLAGS";
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno))
return false;
} }
return true; return "";
#endif #endif
} }
static bool is_interface_up(string_view interface) { bool CTapDriver::Init(const param_map& const_params)
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)
{ {
#ifndef __linux__ #ifndef __linux__
return false; return false;
#else #else
unordered_map<string, string> params = const_params; param_map params = const_params;
stringstream s(params["interface"]); stringstream s(params["interface"]);
string interface; string interface;
while (getline(s, interface, ',')) { while (getline(s, interface, ',')) {
@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
} }
inet = params["inet"]; inet = params["inet"];
LOGTRACE("Opening tap device") spdlog::trace("Opening tap device");
// TAP device initilization // TAP device initilization
if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) { 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; return false;
} }
LOGTRACE("Opened tap device %d", m_hTAP)
// IFF_NO_PI for no extra packet information // IFF_NO_PI for no extra packet information
ifreq ifr = {}; ifreq ifr = {};
ifr.ifr_flags = IFF_TAP | IFF_NO_PI; ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
string dev = "piscsi0"; strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1);
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) { if (ret < 0) {
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno)) LogErrno("Can't ioctl TUNSETIFF");
close(m_hTAP); close(m_hTAP);
return false; 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); const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (ip_fd < 0) { if (ip_fd < 0) {
LOGERROR("Can't open ip socket: %s", strerror(errno)) LogErrno("Can't open ip socket");
close(m_hTAP); close(m_hTAP);
return false; 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); const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (br_socket_fd < 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(m_hTAP);
close(ip_fd); close(ip_fd);
return false; 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 // Check if the bridge has already been created
string sys_file = "/sys/class/net/"; // TODO Find an alternative to accessing a file, there is most likely a system call/ioctl
sys_file += BRIDGE_NAME; if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) {
if (access(sys_file.c_str(), F_OK)) { spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME);
LOGINFO("%s is not yet available", BRIDGE_NAME)
LOGTRACE("Checking which interface is available for creating the bridge") const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } );
if (it == interfaces.end()) {
string bridge_interface; return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME);
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())
}
} }
if (bridge_interface.empty()) { const string bridge_interface = *it;
LOGERROR("No interface is up, not creating bridge")
return false;
}
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") { if (bridge_interface == "eth0") {
LOGTRACE("brctl addbr %s", BRIDGE_NAME) if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) {
return cleanUp(error);
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;
} }
} }
else { else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) {
string address = inet; return cleanUp(error);
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;
}
} }
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)) { if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) {
close(m_hTAP); return cleanUp(error);
close(ip_fd);
close(br_socket_fd);
return false;
} }
} }
else else {
{ spdlog::info(BRIDGE_NAME + " is already available");
LOGINFO("%s is already available", BRIDGE_NAME)
} }
LOGTRACE("ip link set piscsi0 up") spdlog::trace(">ip link set piscsi0 up");
if (!ip_link(ip_fd, "piscsi0", true)) { if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) {
close(m_hTAP); return cleanUp(error);
close(ip_fd);
close(br_socket_fd);
return false;
} }
LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME) spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0");
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) { if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) {
close(m_hTAP); return cleanUp(error);
close(ip_fd);
close(br_socket_fd);
return false;
} }
// Get MAC address spdlog::trace("Getting the MAC address");
LOGTRACE("Getting the MAC address")
ifr.ifr_addr.sa_family = AF_INET; ifr.ifr_addr.sa_family = AF_INET;
if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) { if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) {
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno)) return cleanUp("Can't ioctl SIOCGIFHWADDR");
close(m_hTAP);
close(ip_fd);
close(br_socket_fd);
return false;
} }
LOGTRACE("Got the MAC")
// Save MAC address // Save MAC address
memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size()); 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(ip_fd);
close(br_socket_fd); close(br_socket_fd);
LOGINFO("Tap device %s created", ifr.ifr_name) spdlog::info("Tap device " + string(ifr.ifr_name) + " created");
return true; return true;
#endif #endif
} }
void CTapDriver::OpenDump(const string& path) { void CTapDriver::CleanUp() const
if (m_pcap == nullptr) { {
m_pcap = pcap_open_dead(DLT_EN10MB, 65535); if (m_hTAP != -1) {
} if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) {
if (m_pcap_dumper != nullptr) { LogErrno("Can't open bridge socket");
pcap_dump_close(m_pcap_dumper); } else {
} spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0");
m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str()); if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) {
if (m_pcap_dumper == nullptr) { spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error);
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap)) spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge");
throw io_exception("Can't open pcap file"); }
} 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); const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__) spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down"));
const bool result = ip_link(fd, "piscsi0", true); const string result = ip_link(fd, "piscsi0", enable);
close(fd); close(fd);
return result; return result;
} }
bool CTapDriver::Disable() const void CTapDriver::Flush() const
{ {
const int fd = socket(PF_INET, SOCK_DGRAM, 0); while (HasPendingPackets()) {
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()) {
array<uint8_t, ETH_FRAME_LEN> m_garbage_buffer; array<uint8_t, ETH_FRAME_LEN> m_garbage_buffer;
(void)Receive(m_garbage_buffer.data()); (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()); memcpy(mac, m_MacAddr.data(), m_MacAddr.size());
} }
//--------------------------------------------------------------------------- bool CTapDriver::HasPendingPackets() const
//
// Receive
//
//---------------------------------------------------------------------------
bool CTapDriver::PendingPackets() const
{ {
assert(m_hTAP != -1); assert(m_hTAP != -1);
@ -424,20 +328,16 @@ bool CTapDriver::PendingPackets() const
fds.events = POLLIN | POLLERR; fds.events = POLLIN | POLLERR;
fds.revents = 0; fds.revents = 0;
poll(&fds, 1, 0); poll(&fds, 1, 0);
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents) spdlog::trace(to_string(fds.revents) + " revents");
if (!(fds.revents & POLLIN)) { return !(fds.revents & POLLIN);
return false;
} else {
return true;
}
} }
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li // 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; uint32_t crc = 0xffffffff;
for (int i = 0; i < length; i++) { for (const auto d: data) {
crc ^= buf[i]; crc ^= d;
for (int j = 0; j < 8; j++) { for (int i = 0; i < 8; i++) {
const uint32_t mask = -(static_cast<int>(crc) & 1); const uint32_t mask = -(static_cast<int>(crc) & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask); crc = (crc >> 1) ^ (0xEDB88320 & mask);
} }
@ -445,19 +345,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
return ~crc; return ~crc;
} }
int CTapDriver::Receive(uint8_t *buf) int CTapDriver::Receive(uint8_t *buf) const
{ {
assert(m_hTAP != -1); assert(m_hTAP != -1);
// Check if there is data that can be received // Check if there is data that can be received
if (!PendingPackets()) { if (!HasPendingPackets()) {
return 0; return 0;
} }
// Receive // Receive
auto dwReceived = static_cast<uint32_t>(read(m_hTAP, buf, ETH_FRAME_LEN)); auto dwReceived = static_cast<uint32_t>(read(m_hTAP, buf, ETH_FRAME_LEN));
if (dwReceived == static_cast<uint32_t>(-1)) { 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; 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. // 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 // The Linux network subsystem removes it, since most software apps shouldn't ever
// need it. // 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 + 0] = (uint8_t)((crc >> 0) & 0xFF);
buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF); buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF);
buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF); buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF);
buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 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 // Add FCS size to the received message size
dwReceived += 4; 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 the number of bytes
return dwReceived; 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); 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 // Start sending
return static_cast<int>(write(m_hTAP, buf, len)); return static_cast<int>(write(m_hTAP, buf, len));
} }

View File

@ -11,46 +11,55 @@
#pragma once #pragma once
#include <pcap/pcap.h> #include "devices/device.h"
#include <net/ethernet.h>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <string> #include <string>
#include <array> #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; using namespace std;
class CTapDriver class CTapDriver
{ {
static constexpr const char *BRIDGE_NAME = "piscsi_bridge"; static const string BRIDGE_NAME;
public: public:
CTapDriver() = default; CTapDriver() = default;
~CTapDriver(); ~CTapDriver() = default;
CTapDriver(CTapDriver&) = default; CTapDriver(CTapDriver&) = default;
CTapDriver& operator=(const CTapDriver&) = default; CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&); bool Init(const param_map&);
void OpenDump(const string& path); // Capture packets void CleanUp() const;
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
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: 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 array<byte, 6> m_MacAddr; // MAC Address
int m_hTAP = -1; // File handle 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 // Prioritized comma-separated list of interfaces to create the bridge for
vector<string> interfaces; vector<string> interfaces;

View File

@ -3,22 +3,24 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "device.h" #include "device.h"
#include <spdlog/spdlog.h>
#include <cassert> #include <cassert>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
#include <stdexcept>
using namespace std; using namespace std;
Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) Device::Device(PbDeviceType type, int lun) : type(type), lun(lun)
{ {
ostringstream os; 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(); revision = os.str();
} }
@ -39,7 +41,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v) void Device::SetVendor(const string& v)
{ {
if (v.empty() || v.length() > 8) { if (v.empty() || v.length() > 8) {
throw 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; vendor = v;
@ -48,7 +50,7 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force) void Device::SetProduct(const string& p, bool force)
{ {
if (p.empty() || p.length() > 16) { if (p.empty() || p.length() > 16) {
throw 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 // 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) void Device::SetRevision(const string& r)
{ {
if (r.empty() || r.length() > 4) { 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; revision = r;
@ -85,7 +87,7 @@ string Device::GetParam(const string& key) const
return it == params.end() ? "" : it->second; 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; params = default_params;
@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map<string, string>& set_params)
for (const auto& [key, value] : set_params) { for (const auto& [key, value] : set_params) {
// It is assumed that there are default parameters for all supported parameters // 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; params[key] = value;
} }
else { 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "shared/log.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include "shared/piscsi_util.h"
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
using namespace std; using namespace std;
using namespace piscsi_interface; 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 class Device //NOSONAR The number of fields and methods is justified, the complexity is low
{ {
const string DEFAULT_VENDOR = "PiSCSI"; const string DEFAULT_VENDOR = "PiSCSI";
@ -60,10 +66,10 @@ class Device //NOSONAR The number of fields and methods is justified, the comple
string revision; string revision;
// The parameters the device was created with // The parameters the device was created with
unordered_map<string, string> params; param_map params;
// The default parameters // The default parameters
unordered_map<string, string> default_params; param_map default_params;
// Sense Key and ASC // Sense Key and ASC
// MSB Reserved (0x00) // MSB Reserved (0x00)
@ -90,14 +96,15 @@ protected:
int GetStatusCode() const { return status_code; } int GetStatusCode() const { return status_code; }
string GetParam(const string&) const; string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&); void SetParams(const param_map&);
public: public:
virtual ~Device() = default; virtual ~Device() = default;
PbDeviceType GetType() const { return type; } 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; } bool IsReady() const { return ready; }
virtual void Reset(); virtual void Reset();
@ -131,8 +138,8 @@ public:
bool SupportsFile() const { return supports_file; } bool SupportsFile() const { return supports_file; }
void SupportsParams(bool b) { supports_params = b; } void SupportsParams(bool b) { supports_params = b; }
void SupportsFile(bool b) { supports_file = b; } void SupportsFile(bool b) { supports_file = b; }
unordered_map<string, string> GetParams() const { return params; } auto GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; } void SetDefaultParams(const param_map& p) { default_params = p; }
void SetStatusCode(int s) { status_code = s; } void SetStatusCode(int s) { status_code = s; }

View File

@ -3,11 +3,11 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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.h"
#include "scsihd_nec.h" #include "scsihd_nec.h"
#include "scsimo.h" #include "scsimo.h"
@ -17,15 +17,10 @@
#include "scsi_daynaport.h" #include "scsi_daynaport.h"
#include "host_services.h" #include "host_services.h"
#include "device_factory.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 std;
using namespace piscsi_interface;
using namespace piscsi_util; using namespace piscsi_util;
using namespace network_util;
DeviceFactory::DeviceFactory() DeviceFactory::DeviceFactory()
{ {
@ -34,19 +29,11 @@ DeviceFactory::DeviceFactory()
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 }; sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
sector_sizes[SCCD] = { 512, 2048}; sector_sizes[SCCD] = { 512, 2048};
string network_interfaces; const string interfaces = Join(GetNetworkInterfaces(), ",");
for (const auto& network_interface : GetNetworkInterfaces()) {
if (network_interface.rfind("dummy", 0) == string::npos) {
if (!network_interfaces.empty()) {
network_interfaces += ",";
}
network_interfaces += network_interface;
}
}
default_params[SCBR]["interface"] = network_interfaces; default_params[SCBR]["interface"] = interfaces;
default_params[SCBR]["inet"] = DEFAULT_IP; 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[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f"; 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") { if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun); device = make_shared<SCSIHD_NEC>(lun);
} else { } else {
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCHD)->second, false, device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, false,
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
// Some Apple tools require a particular drive identification // Some Apple tools require a particular drive identification
if (ext == "hda") { if (ext == "hda") {
@ -109,18 +96,18 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
} }
case SCRM: 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.)"); device->SetProduct("SCSI HD (REM.)");
break; break;
case SCMO: 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"); device->SetProduct("SCSI MO");
break; break;
case SCCD: case SCCD:
device = make_shared<SCSICD>(lun, sector_sizes.find(SCCD)->second, device = make_shared<SCSICD>(lun, sector_sizes.find(type)->second,
GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2); GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
device->SetProduct("SCSI CD-ROM"); device->SetProduct("SCSI CD-ROM");
break; break;
@ -128,7 +115,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
device = make_shared<SCSIBR>(lun); device = make_shared<SCSIBR>(lun);
// Since this is an emulation for a specific driver the product name has to be set accordingly // Since this is an emulation for a specific driver the product name has to be set accordingly
device->SetProduct("RASCSI BRIDGE"); device->SetProduct("RASCSI BRIDGE");
device->SetDefaultParams(default_params.find(SCBR)->second); device->SetDefaultParams(default_params.find(type)->second);
break; break;
case SCDP: case SCDP:
@ -137,7 +124,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
device->SetVendor("Dayna"); device->SetVendor("Dayna");
device->SetProduct("SCSI/Link"); device->SetProduct("SCSI/Link");
device->SetRevision("1.4a"); device->SetRevision("1.4a");
device->SetDefaultParams(default_params.find(SCDP)->second); device->SetDefaultParams(default_params.find(type)->second);
break; break;
case SCHS: case SCHS:
@ -150,7 +137,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
case SCLP: case SCLP:
device = make_shared<SCSIPrinter>(lun); device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER"); device->SetProduct("SCSI PRINTER");
device->SetDefaultParams(default_params.find(SCLP)->second); device->SetDefaultParams(default_params.find(type)->second);
break; break;
default: 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 unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
{ {
const auto& it = sector_sizes.find(type); 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); const auto& it = default_params.find(type);
return it != default_params.end() ? it->second : empty_map; return it != default_params.end() ? it->second : EMPTY_PARAM_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;
} }

View File

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

View File

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

View File

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

View File

@ -23,15 +23,7 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
Disk::~Disk() bool Disk::Init(const param_map& params)
{
// Save disk cache, only if ready
if (IsReady() && cache != nullptr) {
cache->Save();
}
}
bool Disk::Init(const unordered_map<string, string>& params)
{ {
StorageDevice::Init(params); StorageDevice::Init(params);
@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map<string, string>& params)
return true; return true;
} }
void Disk::CleanUp()
{
FlushCache();
StorageDevice::CleanUp();
}
void Disk::Dispatch(scsi_command cmd) void Disk::Dispatch(scsi_command cmd)
{ {
// Media changes must be reported on the next access, i.e. not only for TEST UNIT READY // 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); 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 { else {
PrimaryDevice::Dispatch(cmd); PrimaryDevice::Dispatch(cmd);
@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw)
void Disk::FlushCache() void Disk::FlushCache()
{ {
if (cache != nullptr) { if (cache != nullptr && IsReady()) {
cache->Save(); cache->Save();
} }
} }
@ -103,8 +102,8 @@ void Disk::FormatUnit()
CheckReady(); CheckReady();
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST) // FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) { if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 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(); EnterStatusPhase();
@ -115,9 +114,9 @@ void Disk::Read(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) { if (valid) {
GetController()->SetBlocks(blocks); 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 // Set next block
GetController()->SetNext(start + 1); 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 // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 7) != 0) { 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(); EnterStatusPhase();
@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(GetController()->GetCmd(), 12) != 0) { 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(); EnterStatusPhase();
@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const
void Disk::Write(access_mode mode) const void Disk::Write(access_mode mode) const
{ {
if (IsProtected()) { 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); const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode)
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode); const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
if (valid) { if (valid) {
// if BytChk=0 // if BytChk=0
if ((GetController()->GetCmd(1) & 0x02) == 0) { if ((GetController()->GetCmdByte(1) & 0x02) == 0) {
Seek(); Seek();
return; return;
} }
// Test reading // Test reading
GetController()->SetBlocks(blocks); GetController()->SetBlocks(blocks);
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start)); GetController()->SetLength(Read(GetController()->GetBuffer(), start));
// Set next block // Set next block
GetController()->SetNext(start + 1); GetController()->SetNext(start + 1);
@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode)
void Disk::StartStopUnit() void Disk::StartStopUnit()
{ {
const bool start = GetController()->GetCmd(4) & 0x01; const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02; const bool load = GetController()->GetCmdByte(4) & 0x02;
if (load) { if (load) {
GetLogger().Trace(start ? "Loading medium" : "Ejecting medium"); LogTrace(start ? "Loading medium" : "Ejecting medium");
} }
else { else {
GetLogger().Trace(start ? "Starting unit" : "Stopping unit"); LogTrace(start ? "Starting unit" : "Stopping unit");
SetStopped(!start); SetStopped(!start);
} }
@ -217,12 +216,12 @@ void Disk::StartStopUnit()
if (load) { if (load) {
if (IsLocked()) { if (IsLocked()) {
// Cannot be ejected because it is locked // 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 // Eject
if (!Eject(false)) { 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 { else {
@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval()
{ {
CheckReady(); 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); SetLocked(lock);
@ -279,7 +278,7 @@ bool Disk::Eject(bool force)
return status; 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 // Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4]))); 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; 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 // Get length, clear buffer
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7)))); 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; 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()); assert(block < GetBlockCount());
CheckReady(); CheckReady();
if (!cache->ReadSector(buf, static_cast<uint32_t>(block))) { 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(); 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()); assert(block < GetBlockCount());
CheckReady(); CheckReady();
if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) { 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(); CheckReady();
if (GetBlockCount() == 0) { 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(); vector<uint8_t>& buf = GetController()->GetBuffer();
@ -580,7 +579,7 @@ void Disk::ReadCapacity16()
CheckReady(); CheckReady();
if (GetBlockCount() == 0) { 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(); vector<uint8_t>& buf = GetController()->GetBuffer();
@ -604,7 +603,7 @@ void Disk::ReadCapacity16()
void Disk::ReadCapacity16_ReadLong16() void Disk::ReadCapacity16_ReadLong16()
{ {
// The service action determines the actual command // The service action determines the actual command
switch (GetController()->GetCmd(1) & 0x1f) { switch (GetController()->GetCmdByte(1) & 0x1f) {
case 0x10: case 0x10:
ReadCapacity16(); ReadCapacity16();
break; break;
@ -614,7 +613,7 @@ void Disk::ReadCapacity16_ReadLong16()
break; break;
default: 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; 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); const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2);
if (block > GetBlockCount()) { 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)); + 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) { if (mode == RW6 || mode == SEEK6) {
start = GetInt24(GetController()->GetCmd(), 1); start = GetInt24(GetController()->GetCmd(), 1);
count = GetController()->GetCmd(4); count = GetController()->GetCmdByte(4);
if (!count) { if (!count) {
count= 0x100; count= 0x100;
} }
@ -659,13 +658,13 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
stringstream s; stringstream s;
s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start; 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 // Check capacity
if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > 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)); + 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 // Do not process 0 blocks
@ -689,10 +688,8 @@ uint32_t Disk::GetSectorSizeInBytes() const
void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes)
{ {
DeviceFactory device_factory; if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) {
if (const auto& sizes = device_factory.GetSectorSizes(GetType()); throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
!sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) {
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
} }
size_shift_count = CalculateShiftCount(size_in_bytes); 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) bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size)
{ {
if (unordered_set<uint32_t> sizes = device_factory.GetSectorSizes(GetType()); if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) {
sizes.find(configured_size) == sizes.end()) {
return false; return false;
} }
configured_sector_size = configured_size; configured_sector_size = configured_size;
return true; return true;
} }

View File

@ -22,6 +22,7 @@
#include "interfaces/scsi_block_commands.h" #include "interfaces/scsi_block_commands.h"
#include "storage_device.h" #include "storage_device.h"
#include <string> #include <string>
#include <span>
#include <unordered_set> #include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <tuple> #include <tuple>
@ -43,18 +44,18 @@ class Disk : public StorageDevice, private ScsiBlockCommands
public: public:
Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {} using StorageDevice::StorageDevice;
~Disk() override;
bool Init(const unordered_map<string, string>&) override; bool Init(const param_map&) override;
void CleanUp() override;
void Dispatch(scsi_command) override; void Dispatch(scsi_command) override;
bool Eject(bool) 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; uint32_t GetSectorSizeInBytes() const;
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); }
@ -92,8 +93,8 @@ private:
void ValidateBlockAddress(access_mode) const; void ValidateBlockAddress(access_mode) const;
tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const; tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override; int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, 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 = static inline const unordered_map<uint32_t, uint32_t> shift_counts =
{ { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; { { 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 bool DiskCache::Save() const
{ {
// Save valid tracks // 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); }); { return c.disktrk != nullptr && !c.disktrk->Save(sec_path); });
} }
@ -46,7 +46,7 @@ shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
return Assign(track); 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); shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) { if (disktrk == nullptr) {
@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
return disktrk->ReadSector(buf, block & 0xff); 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); shared_ptr<DiskTrack> disktrk = GetTrack(block);
if (disktrk == nullptr) { if (disktrk == nullptr) {

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
// Host Services with realtime clock and shutdown support // Host Services with realtime clock and shutdown support
// //
@ -25,11 +25,13 @@
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include "host_services.h" #include "host_services.h"
#include <algorithm> #include <algorithm>
#include <chrono>
using namespace std::chrono;
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
bool HostServices::Init(const unordered_map<string, string>& params) bool HostServices::Init(const param_map& params)
{ {
ModePageDevice::Init(params); ModePageDevice::Init(params);
@ -49,13 +51,13 @@ void HostServices::TestUnitReady()
vector<uint8_t> HostServices::InquiryInternal() const 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 void HostServices::StartStopUnit() const
{ {
const bool start = GetController()->GetCmd(4) & 0x01; const bool start = GetController()->GetCmdByte(4) & 0x01;
const bool load = GetController()->GetCmd(4) & 0x02; const bool load = GetController()->GetCmdByte(4) & 0x02;
if (!start) { if (!start) {
if (load) { if (load) {
@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const
GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI); GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI);
} }
else { 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(); 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 // Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) { 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]))); 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; 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 // Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) { 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)))); 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); pages[32] = vector<byte>(10);
if (!changeable) { 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; tm localtime;
localtime_r(&t, &localtime); localtime_r(&t, &localtime);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
// Host Services with realtime clock and shutdown support // Host Services with realtime clock and shutdown support
// //
@ -12,6 +12,7 @@
#pragma once #pragma once
#include "mode_page_device.h" #include "mode_page_device.h"
#include <span>
#include <vector> #include <vector>
#include <map> #include <map>
@ -23,7 +24,7 @@ public:
explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {} explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {}
~HostServices() override = default; ~HostServices() override = default;
bool Init(const unordered_map<string, string>&) override; bool Init(const param_map&) override;
vector<uint8_t> InquiryInternal() const override; vector<uint8_t> InquiryInternal() const override;
void TestUnitReady() override; void TestUnitReady() override;
@ -48,8 +49,8 @@ private:
}; };
void StartStopUnit() const; void StartStopUnit() const;
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override; int ModeSense6(cdb_t, vector<uint8_t>&) const override;
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override; int ModeSense10(cdb_t, vector<uint8_t>&) const override;
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 // 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_defs;
using namespace scsi_command_util; using namespace scsi_command_util;
bool ModePageDevice::Init(const unordered_map<string, string>& params) bool ModePageDevice::Init(const param_map& params)
{ {
PrimaryDevice::Init(params); PrimaryDevice::Init(params);
@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map<string, string>& params)
return true; 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; const int max_length = length - offset;
if (max_length < 0) { if (max_length < 0) {
@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
stringstream s; stringstream s;
s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page; 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 // Mode page data mapped to the respective page numbers, C++ maps are ordered by key
map<int, vector<byte>> pages; map<int, vector<byte>> pages;
@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
if (pages.empty()) { if (pages.empty()) {
s << "Unsupported mode page $" << page; s << "Unsupported mode page $" << page;
GetLogger().Trace(s.str()); LogTrace(s.str());
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
} }
// Holds all mode page data // 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) { 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())); const auto size = static_cast<int>(min(static_cast<size_t>(max_length), result.size()));
@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const
EnterDataInPhase(); 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 // There is no default implementation of MODE SELECT
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
} }
void ModePageDevice::ModeSelect6() const void ModePageDevice::ModeSelect6() const
{ {
SaveParametersCheck(GetController()->GetCmd(4)); SaveParametersCheck(GetController()->GetCmdByte(4));
} }
void ModePageDevice::ModeSelect10() const void ModePageDevice::ModeSelect10() const
@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const
void ModePageDevice::SaveParametersCheck(int length) const void ModePageDevice::SaveParametersCheck(int length) const
{ {
if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) { if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) {
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); GetController()->SetLength(length);

View File

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

View File

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

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 // A device implementing mandatory SCSI primary commands, to be used for subclassing
// //
@ -18,6 +18,7 @@
#include "device_logger.h" #include "device_logger.h"
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <span>
#include <functional> #include <functional>
using namespace std; using namespace std;
@ -25,6 +26,8 @@ using namespace scsi_defs;
class PrimaryDevice: private ScsiPrimaryCommands, public Device class PrimaryDevice: private ScsiPrimaryCommands, public Device
{ {
friend class AbstractController;
using operation = function<void()>; using operation = function<void()>;
public: public:
@ -32,15 +35,16 @@ public:
PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {} PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {}
~PrimaryDevice() override = default; ~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); virtual void Dispatch(scsi_command);
int GetId() const override; int GetId() const override;
void SetController(shared_ptr<AbstractController>); virtual bool WriteByteSequence(span<const uint8_t>);
virtual bool WriteByteSequence(vector<uint8_t>&, uint32_t);
int GetSendDelay() const { return send_delay; } int GetSendDelay() const { return send_delay; }
@ -57,8 +61,6 @@ protected:
void AddCommand(scsi_command, const operation&); void AddCommand(scsi_command, const operation&);
const DeviceLogger& GetLogger() const { return logger; }
vector<uint8_t> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const; vector<uint8_t> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;
virtual vector<uint8_t> InquiryInternal() const = 0; virtual vector<uint8_t> InquiryInternal() const = 0;
void CheckReady(); void CheckReady();
@ -69,16 +71,24 @@ protected:
void ReserveUnit() override; void ReserveUnit() override;
void ReleaseUnit() override; void ReleaseUnit() override;
void EnterStatusPhase() const { controller.lock()->Status(); } void EnterStatusPhase() const { controller->Status(); }
void EnterDataInPhase() const { controller.lock()->DataIn(); } void EnterDataInPhase() const { controller->DataIn(); }
void EnterDataOutPhase() const { controller.lock()->DataOut(); } 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: private:
static const int NOT_RESERVED = -2; static const int NOT_RESERVED = -2;
void SetController(AbstractController *);
void TestUnitReady() override; void TestUnitReady() override;
void RequestSense() override; void RequestSense() override;
void ReportLuns() override; void ReportLuns() override;
@ -86,9 +96,11 @@ private:
vector<byte> HandleRequestSense() const; 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; unordered_map<scsi_command, operation> commands;

View File

@ -3,31 +3,31 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "device_logger.h"
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include <spdlog/spdlog.h>
#include <cstring> #include <cstring>
#include <cassert>
#include <sstream> #include <sstream>
#include <iomanip> #include <iomanip>
using namespace scsi_defs; using namespace scsi_defs;
void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector<int>& cdb, string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length, int sector_size)
const vector<uint8_t>& buf, int length, int sector_size)
{ {
assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10); assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10);
assert(length >= 0); assert(length >= 0);
string result;
// PF // PF
if (!(cdb[1] & 0x10)) { if (!(cdb[1] & 0x10)) {
// Vendor-specific parameters (SCSI-1) are not supported. // Vendor-specific parameters (SCSI-1) are not supported.
// Do not report an error in order to support Apple's HD SC Setup. // Do not report an error in order to support Apple's HD SC Setup.
return; return result;
} }
// Skip block descriptors // Skip block descriptors
@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
// Parse the pages // Parse the pages
while (length > 0) { while (length > 0) {
// Format device page // Format device page
if (int page = buf[offset]; page == 0x03) { if (const int page = buf[offset]; page == 0x03) {
if (length < 14) { 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 // 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) { if (GetInt16(buf, offset + 12) != sector_size) {
// With piscsi it is not possible to permanently (by formatting) change the 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 // 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"); 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); throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
} }
has_valid_page_code = true; has_valid_page_code = true;
@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
else { else {
stringstream s; stringstream s;
s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page; s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page;
logger.Warn(s.str()); result = s.str();
} }
// Advance to the next page // 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) { 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) 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 // No changeable area
if (!changeable) { 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)); memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA));
} }
} }
int scsi_command_util::GetInt16(const vector<uint8_t>& buf, int offset) int scsi_command_util::GetInt24(span <const int> 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)
{ {
assert(buf.size() > static_cast<size_t>(offset) + 2); assert(buf.size() > static_cast<size_t>(offset) + 2);
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[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); 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]); (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); 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]); (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) void scsi_command_util::SetInt64(vector<uint8_t>& buf, int offset, uint64_t value)
{ {
assert(buf.size() > static_cast<size_t>(offset) + 7); assert(buf.size() > static_cast<size_t>(offset) + 7);

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include "scsi_host_bridge.h" #include "scsi_host_bridge.h"
#include <arpa/inet.h> #include <arpa/inet.h>
#include <array>
using namespace std; using namespace std;
using namespace scsi_defs; using namespace scsi_defs;
@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun)
SupportsParams(true); SupportsParams(true);
} }
bool SCSIBR::Init(const unordered_map<string, string>& params) bool SCSIBR::Init(const param_map& params)
{ {
PrimaryDevice::Init(params); PrimaryDevice::Init(params);
@ -43,10 +42,8 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); }); AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); });
#ifdef __linux__ #ifdef __linux__
// TAP Driver Generation
m_bTapEnable = tap.Init(GetParams()); m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){ if (!m_bTapEnable){
GetLogger().Error("Unable to open the TAP interface");
return false; return false;
} }
#endif #endif
@ -70,9 +67,14 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
#endif #endif
} }
void SCSIBR::CleanUp()
{
tap.CleanUp();
}
vector<uint8_t> SCSIBR::InquiryInternal() const 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 // The bridge returns more additional bytes than the other devices
buf.resize(0x1F + 8 + 5); buf.resize(0x1F + 8 + 5);
@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady()
EnterStatusPhase(); EnterStatusPhase();
} }
int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf) int SCSIBR::GetMessage10(cdb_t cdb, vector<uint8_t>& buf)
{ {
// Type // Type
const int type = cdb[2]; const int type = cdb[2];
@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
return 0; 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 // Type
const int type = cdb[2]; const int type = cdb[2];
@ -252,7 +254,7 @@ void SCSIBR::GetMessage10()
GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer())); GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer()));
if (GetController()->GetLength() <= 0) { 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 // Set next block
@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const
{ {
GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6)); GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6));
if (GetController()->GetLength() <= 0) { 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) // 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()); 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()); 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; 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); tap.Send(buf.data(), len);
} }
@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data(); auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp); const uint32_t nUnit = ntohl(*dp);
Human68k::capacity_t cap; Human68k::capacity_t cap = {};
fsresult = fs.GetCapacity(nUnit, &cap); fsresult = fs.GetCapacity(nUnit, &cap);
cap.freearea = htons(cap.freearea); cap.freearea = htons(cap.freearea);
@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector<uint8_t>& buf)
auto dp = (uint32_t*)buf.data(); auto dp = (uint32_t*)buf.data();
const uint32_t nUnit = ntohl(*dp); const uint32_t nUnit = ntohl(*dp);
Human68k::dpb_t dpb; Human68k::dpb_t dpb = {};
fsresult = fs.GetDPB(nUnit, &dpb); fsresult = fs.GetDPB(nUnit, &dpb);
dpb.sector_size = htons(dpb.sector_size); dpb.sector_size = htons(dpb.sector_size);

View File

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

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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) // Implementation of a SCSI printer (see SCSI-2 specification for a command description)
// //
@ -32,7 +32,6 @@
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include "scsi_printer.h" #include "scsi_printer.h"
#include <sys/stat.h>
#include <filesystem> #include <filesystem>
using namespace std; using namespace std;
@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun)
SupportsParams(true); SupportsParams(true);
} }
bool SCSIPrinter::Init(const unordered_map<string, string>& params) bool SCSIPrinter::Init(const param_map& params)
{ {
PrimaryDevice::Init(params); PrimaryDevice::Init(params);
@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map<string, string>& params)
AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); }); AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); });
if (GetParam("cmd").find("%f") == string::npos) { if (GetParam("cmd").find("%f") == string::npos) {
GetLogger().Trace("Missing filename specifier %f"); LogTrace("Missing filename specifier %f");
return false; return false;
} }
@ -82,20 +81,20 @@ void SCSIPrinter::TestUnitReady()
vector<uint8_t> SCSIPrinter::InquiryInternal() const 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() void SCSIPrinter::Print()
{ {
const uint32_t length = GetInt24(GetController()->GetCmd(), 2); 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()) { 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"); " 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); GetController()->SetLength(length);
@ -107,9 +106,9 @@ void SCSIPrinter::Print()
void SCSIPrinter::SynchronizeBuffer() void SCSIPrinter::SynchronizeBuffer()
{ {
if (!out.is_open()) { 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"); string cmd = GetParam("cmd");
@ -118,25 +117,24 @@ void SCSIPrinter::SynchronizeBuffer()
cmd.replace(file_position, 2, filename); cmd.replace(file_position, 2, filename);
error_code error; 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)"); LogDebug("Executing print command '" + cmd + "'");
GetLogger().Debug("Executing '" + cmd + "'");
if (system(cmd.c_str())) { 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(); EnterStatusPhase();
} }
bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length) bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
{ {
if (!out.is_open()) { if (!out.is_open()) {
vector<char> f(file_template.begin(), file_template.end()); 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 // There is no C++ API that generates a file with a unique name
const int fd = mkstemp(f.data()); const int fd = mkstemp(f.data());
if (fd == -1) { 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; return false;
} }
close(fd); close(fd);
@ -154,21 +152,23 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
out.open(filename, ios::binary); out.open(filename, ios::binary);
if (out.fail()) { 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(); return !out.fail();
} }
void SCSIPrinter::Cleanup() void SCSIPrinter::CleanUp()
{ {
PrimaryDevice::CleanUp();
if (out.is_open()) { if (out.is_open()) {
out.close(); out.close();

View File

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

View File

@ -21,7 +21,7 @@
using namespace scsi_defs; using namespace scsi_defs;
using namespace scsi_command_util; 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) : Disk(SCCD, lun), scsi_level(level)
{ {
SetSectorSizes(sector_sizes); SetSectorSizes(sector_sizes);
@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::
SetLockable(true); SetLockable(true);
} }
bool SCSICD::Init(const unordered_map<string, string>& params) bool SCSICD::Init(const param_map& params)
{ {
Disk::Init(params); Disk::Init(params);
@ -116,7 +116,7 @@ void SCSICD::OpenIso()
if (rawfile) { if (rawfile) {
if (size % 2536) { 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"); + to_string(size) + " bytes");
} }
@ -165,7 +165,7 @@ void SCSICD::ReadToc()
vector<uint8_t> SCSICD::InquiryInternal() const 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 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(); CheckReady();
const int index = SearchTrack(static_cast<int>(block)); const int index = SearchTrack(static_cast<int>(block));
if (index < 0) { 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]); assert(tracks[index]);
@ -241,15 +241,15 @@ int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
} }
assert(dataindex >= 0); 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(); CheckReady();
// If ready, there is at least one track // If ready, there is at least one track
assert(tracks.size() > 0); assert(!tracks.empty());
assert(tracks[0]); assert(tracks[0]);
// Get allocation length, clear buffer // 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(); const int last = tracks[tracks.size() - 1]->GetTrackNo();
// Except for AA // Except for AA
if (cdb[6] > last && cdb[6] != 0xaa) { 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 // 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 // AA if not found or internal error
if (!tracks[index]) { if (!tracks[index]) {
if (cdb[6] != 0xaa) { 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 // Returns the final LBA+1 because it is AA

View File

@ -17,20 +17,23 @@
#include "cd_track.h" #include "cd_track.h"
#include "disk.h" #include "disk.h"
#include "interfaces/scsi_mmc_commands.h" #include "interfaces/scsi_mmc_commands.h"
#include <span>
#include <vector>
#include <map>
class SCSICD : public Disk, private ScsiMmcCommands class SCSICD : public Disk, private ScsiMmcCommands
{ {
public: 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; ~SCSICD() override = default;
bool Init(const unordered_map<string, string>&) override; bool Init(const param_map&) override;
void Open() override; void Open() override;
vector<uint8_t> InquiryInternal() const 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: protected:
@ -39,7 +42,7 @@ protected:
private: 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 AddCDROMPage(map<int, vector<byte>>&, bool) const;
void AddCDDAPage(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) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker // Copyright (C) akuker
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
@ -14,8 +14,8 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "scsihd.h"
#include "scsi_command_util.h" #include "scsi_command_util.h"
#include "scsihd.h"
using namespace scsi_command_util; using namespace scsi_command_util;
@ -70,7 +70,7 @@ void SCSIHD::Open()
{ {
assert(!IsReady()); assert(!IsReady());
off_t size = GetFileSize(); const off_t size = GetFileSize();
// Sector size (default 512 bytes) and number of blocks // Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
@ -81,12 +81,15 @@ void SCSIHD::Open()
vector<uint8_t> SCSIHD::InquiryInternal() const 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 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) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker // Copyright (C) akuker
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
@ -18,6 +18,7 @@
#include "shared/scsi.h" #include "shared/scsi.h"
#include "disk.h" #include "disk.h"
#include <string> #include <string>
#include <span>
#include <vector> #include <vector>
#include <map> #include <map>
@ -27,7 +28,7 @@ class SCSIHD : public Disk
public: 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; ~SCSIHD() override = default;
void FinalizeSetup(off_t); void FinalizeSetup(off_t);
@ -36,7 +37,7 @@ public:
// Commands // Commands
vector<uint8_t> InquiryInternal() const 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;
void AddFormatPage(map<int, vector<byte>>&, bool) const override; void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override; void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;

View File

@ -1,14 +1,15 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker // Copyright (C) 2021-2023 Uwe Seimet
// Copyright (C) akuker
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder. // See LICENSE file in the project root folder.
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -47,7 +48,7 @@ void SCSIHD_NEC::Open()
FinalizeSetup(image_offset); 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 = {}; array<uint8_t, 512> root_sector = {};
memcpy(root_sector.data(), data.data(), root_sector.size()); 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)"); 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 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 void SCSIHD_NEC::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const

View File

@ -47,7 +47,7 @@ protected:
private: 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 GetInt16LittleEndian(const uint8_t *);
static int GetInt32LittleEndian(const uint8_t *); static int GetInt32LittleEndian(const uint8_t *);

View File

@ -1,14 +1,15 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker // Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder. // 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); SetSectorSizes(sector_sizes);
// 128 MB, 512 bytes per sector, 248826 sectors // 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 // 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 // 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 // 640 MB, 20248 bytes per sector, 310352 sectors
geometries[2048 * 310352] = make_pair(2048, 310352); geometries[2048 * 310352] = { 2048, 310352 };
SetProtectable(true); SetProtectable(true);
SetRemovable(true); SetRemovable(true);
@ -61,7 +62,7 @@ void SCSIMO::Open()
vector<uint8_t> SCSIMO::InquiryInternal() const 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 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 // 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) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS // Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022-2023 Uwe Seimet
// Copyright (C) akuker // Copyright (C) akuker
// //
// Licensed under the BSD 3-Clause License. // Licensed under the BSD 3-Clause License.
@ -15,6 +16,7 @@
#pragma once #pragma once
#include "disk.h" #include "disk.h"
#include <span>
#include <vector> #include <vector>
#include <map> #include <map>
@ -30,7 +32,7 @@ public:
void Open() override; void Open() override;
vector<uint8_t> InquiryInternal() const 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: protected:

View File

@ -3,15 +3,13 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "storage_device.h" #include "storage_device.h"
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <filesystem>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type,
SetStoppable(true); 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() void StorageDevice::ValidateFile()
{ {
if (blocks == 0) { if (blocks == 0) {
throw io_exception(string(GetTypeString()) + " device has 0 blocks"); throw io_exception(string(GetTypeString()) + " device has 0 blocks");
} }
if (!exists(path(filename))) { if (!exists(filename)) {
throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist"); throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist");
} }
if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) { 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) // TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp)
if (access(filename.c_str(), W_OK)) { if (IsReadOnlyFile()) {
// Permanently write-protected // Permanently write-protected
SetReadOnly(true); SetReadOnly(true);
SetProtectable(false); SetProtectable(false);
@ -50,28 +69,28 @@ void StorageDevice::ValidateFile()
SetReady(true); SetReady(true);
} }
void StorageDevice::ReserveFile(const string& file, int id, int lun) const void StorageDevice::ReserveFile() const
{ {
assert(!file.empty()); assert(!filename.empty());
assert(reserved_files.find(file) == reserved_files.end()); assert(!reserved_files.contains(filename.string()));
reserved_files[file] = make_pair(id, lun); reserved_files[filename.string()] = { GetId(), GetLun() };
} }
void StorageDevice::UnreserveFile() void StorageDevice::UnreserveFile()
{ {
reserved_files.erase(filename); reserved_files.erase(filename.string());
filename = ""; filename.clear();
} }
id_set StorageDevice::GetIdsForReservedFile(const string& file) id_set StorageDevice::GetIdsForReservedFile(const string& file)
{ {
if (const auto& it = reserved_files.find(file); it != reserved_files.end()) { 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() void StorageDevice::UnreserveAll()
@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll()
reserved_files.clear(); 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 bool StorageDevice::IsReadOnlyFile() const
@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const
off_t StorageDevice::GetFileSize() const off_t StorageDevice::GetFileSize() const
{ {
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB try {
if (struct stat st; !stat(filename.c_str(), &st)) { return file_size(filename);
return st.st_size; }
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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 // The base class for all mass storage devices with image file support
// //
@ -11,14 +11,14 @@
#pragma once #pragma once
#include "shared/piscsi_util.h"
#include "mode_page_device.h" #include "mode_page_device.h"
#include <unordered_map> #include <unordered_map>
#include <string> #include <string>
#include <filesystem>
using namespace std; using namespace std;
using id_set = pair<int, int>;
class StorageDevice : public ModePageDevice class StorageDevice : public ModePageDevice
{ {
public: public:
@ -26,24 +26,27 @@ public:
StorageDevice(PbDeviceType, int); StorageDevice(PbDeviceType, int);
~StorageDevice() override = default; ~StorageDevice() override = default;
void CleanUp() override;
virtual void Open() = 0; virtual void Open() = 0;
string GetFilename() const { return filename; } string GetFilename() const { return filename.string(); }
void SetFilename(string_view f) { filename = f; } void SetFilename(string_view);
uint64_t GetBlockCount() const { return blocks; } uint64_t GetBlockCount() const { return blocks; }
void ReserveFile(const string&, int, int) const; void ReserveFile() const;
void UnreserveFile(); void UnreserveFile();
// TODO Remove this method, it is only used by the unit tests
static void UnreserveAll(); static void UnreserveAll();
static bool FileExists(const string&); static bool FileExists(string_view);
bool IsReadOnlyFile() const;
void SetMediumChanged(bool b) { medium_changed = b; } void SetMediumChanged(bool b) { medium_changed = b; }
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; } static auto GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& r) { reserved_files = r; } static void SetReservedFiles(const unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>>& r)
{ reserved_files = r; }
static id_set GetIdsForReservedFile(const string&); static id_set GetIdsForReservedFile(const string&);
protected: protected:
@ -58,13 +61,14 @@ protected:
private: private:
// Total number of blocks bool IsReadOnlyFile() const;
uint64_t blocks = 0; uint64_t blocks = 0;
string filename; filesystem::path filename;
bool medium_changed = false; bool medium_changed = false;
// The list of image files in use and the IDs and LUNs using these files // 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 #pragma once
#include "shared/scsi.h" #include "shared/scsi.h"
#include <cstdint>
#include <string> #include <string>
using namespace scsi_defs; using namespace scsi_defs;

View File

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

View File

@ -214,7 +214,7 @@ class GPIOBUS : public BUS
// SEL signal event request // SEL signal event request
struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it
// epoll file descriptor // 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 #endif
}; };

View File

@ -34,7 +34,9 @@
#include <memory> #include <memory>
#include <sstream> #include <sstream>
#include <string.h> #include <string.h>
#ifdef __linux__
#include <sys/epoll.h> #include <sys/epoll.h>
#endif
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/mman.h> #include <sys/mman.h>
@ -43,7 +45,7 @@
#include "hal/pi_defs/bpi-gpio.h" #include "hal/pi_defs/bpi-gpio.h"
#include "hal/sunxi_utils.h" #include "hal/sunxi_utils.h"
#include "hal/systimer.h" #include "hal/systimer.h"
#include "shared/log.h" #include "hal/log.h"
#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0]))) #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); 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) LOGTRACE("Duplicate bank: %d", gpio_bank)
} else { } else {
@ -723,9 +725,9 @@ void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast)
} }
if (sunxi_gpio_state == SunXI::HIGH) if (sunxi_gpio_state == SunXI::HIGH)
pio->DAT &= ~(1 << num); pio->DAT = pio->DAT & ~(1 << num);
else else
pio->DAT |= (1 << num); pio->DAT = pio->DAT | (1 << num);
} }
void GPIOBUS_BananaM2p::DisableIRQ() void GPIOBUS_BananaM2p::DisableIRQ()
@ -978,9 +980,9 @@ void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value)
} }
if (value == 0) if (value == 0)
pio->DAT &= ~(1 << num); pio->DAT = pio->DAT & ~(1 << num);
else 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) 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) else if (pud == SunXI::PUD_UP)
*(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP; *(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP;
else // pud == PUD_OFF else // pud == PUD_OFF
*(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3; *(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3;
SunXI::short_wait(); SunXI::short_wait();
*(gpio_map + clk_offset) = 1 << shift; *(gpio_map + clk_offset) = 1 << shift;
SunXI::short_wait(); 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; *(gpio_map + clk_offset) = 0;
} }

View File

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

View File

@ -15,7 +15,7 @@
#include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus_virtual.h" #include "hal/gpiobus_virtual.h"
#include "hal/sbc_version.h" #include "hal/sbc_version.h"
#include "shared/log.h" #include <spdlog/spdlog.h>
using namespace std; using namespace std;
@ -29,13 +29,13 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
// Also make Init() private. // Also make Init() private.
SBC_Version::Init(); SBC_Version::Init();
if (SBC_Version::IsBananaPi()) { if (SBC_Version::IsBananaPi()) {
LOGTRACE("Creating GPIOBUS_BananaM2p") spdlog::trace("Creating GPIOBUS_BananaM2p");
return_ptr = make_unique<GPIOBUS_BananaM2p>(); return_ptr = make_unique<GPIOBUS_BananaM2p>();
} else if (SBC_Version::IsRaspberryPi()) { } else if (SBC_Version::IsRaspberryPi()) {
LOGTRACE("Creating GPIOBUS_Raspberry") spdlog::trace("Creating GPIOBUS_Raspberry");
return_ptr = make_unique<GPIOBUS_Raspberry>(); return_ptr = make_unique<GPIOBUS_Raspberry>();
} else { } else {
LOGINFO("Creating Virtual GPIOBUS") spdlog::trace("Creating Virtual GPIOBUS");
return_ptr = make_unique<GPIOBUS_Virtual>(); return_ptr = make_unique<GPIOBUS_Virtual>();
} }
if (!return_ptr->Init(mode)) { if (!return_ptr->Init(mode)) {
@ -43,7 +43,7 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
} }
return_ptr->Reset(); return_ptr->Reset();
} catch (const invalid_argument&) { } 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; return_ptr = nullptr;
} }

View File

@ -18,10 +18,12 @@
#include "hal/gpiobus_raspberry.h" #include "hal/gpiobus_raspberry.h"
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "hal/systimer.h" #include "hal/systimer.h"
#include "shared/log.h" #include "hal/log.h"
#include <map> #include <map>
#include <string.h> #include <cstring>
#ifdef __linux__
#include <sys/epoll.h> #include <sys/epoll.h>
#endif
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/time.h> #include <sys/time.h>
@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
// Open /dev/mem // Open /dev/mem
int fd = open("/dev/mem", O_RDWR | O_SYNC); int fd = open("/dev/mem", O_RDWR | O_SYNC);
if (fd == -1) { 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; return false;
} }
// Map peripheral region memory // Map peripheral region memory
void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr); void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
if (map == MAP_FAILED) { 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); close(fd);
return false; return false;
} }

View File

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

View File

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

View File

@ -13,7 +13,6 @@
#include "hal/data_sample_raspberry.h" #include "hal/data_sample_raspberry.h"
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "shared/log.h"
#include "shared/scsi.h" #include "shared/scsi.h"
#include <map> #include <map>
@ -171,4 +170,4 @@ class GPIOBUS_Virtual final : public GPIOBUS
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem; sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
int fd_shm, fd_log; int fd_shm, fd_log;
#endif #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 #pragma once
#include "spdlog/spdlog.h" #include <spdlog/spdlog.h>
static const int LOGBUF_SIZE = 512; static const int LOGBUF_SIZE = 512;

View File

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

View File

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

View File

@ -40,12 +40,6 @@
using namespace std; 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 CYAN = "\033[36m"; /* Cyan */
static const string WHITE = "\033[37m"; /* White */ 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("--- 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, printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB,
WHITE.c_str()); WHITE.c_str());
} }

View File

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

View File

@ -14,7 +14,7 @@
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "shared/log.h" #include "hal/log.h"
const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem"; 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_hi_reg = (1 << 20) - 1; //(0xFFFFF)
hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX; 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; hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1;
// Set reload bit // 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 // 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 // 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 "hal/systimer_raspberry.h"
#include <spdlog/spdlog.h>
#include <memory> #include <memory>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/mman.h> #include <sys/mman.h>
@ -19,8 +20,6 @@
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "hal/sbc_version.h" #include "hal/sbc_version.h"
#include "shared/log.h"
// System timer address // System timer address
volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr; volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr;
// ARM timer address // ARM timer address
@ -40,14 +39,14 @@ void SysTimer_Raspberry::Init()
// Open /dev/mem // Open /dev/mem
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC); int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
if (mem_fd == -1) { 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; return;
} }
// Map peripheral region memory // Map peripheral region memory
void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr); void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr);
if (map == MAP_FAILED) { if (map == MAP_FAILED) {
LOGERROR("Error: Unable to map memory") spdlog::error("Error: Unable to map memory");
close(mem_fd); close(mem_fd);
return; return;
} }

View File

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

View File

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

View File

@ -9,7 +9,7 @@
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/log.h" #include "hal/log.h"
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "sm_reports.h" #include "sm_reports.h"
#include <iostream> #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) 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; ofstream html_ofstream;

View File

@ -10,7 +10,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "hal/data_sample_raspberry.h" #include "hal/data_sample_raspberry.h"
#include "shared/log.h" #include "hal/log.h"
#include "sm_reports.h" #include "sm_reports.h"
#include "string.h" #include "string.h"
#include <fstream> #include <fstream>
@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector<shared_ptr<DataSa
sample_count++; sample_count++;
if (sample_count == UINT32_MAX) { 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; 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) 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; ofstream json_ofstream;
json_ofstream.open(filename.c_str(), ios::out); json_ofstream.open(filename.c_str(), ios::out);

View File

@ -11,7 +11,7 @@
#include "hal/data_sample.h" #include "hal/data_sample.h"
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "shared/log.h" #include "hal/log.h"
#include "sm_core.h" #include "sm_core.h"
#include "sm_reports.h" #include "sm_reports.h"
#include <fstream> #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) 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; ofstream vcd_ofstream;
vcd_ofstream.open(filename.c_str(), ios::out); vcd_ofstream.open(filename.c_str(), ios::out);

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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[]) int main(int argc, char *argv[])
{ {
const vector<char *> args(argv, argv + argc); vector<char *> args(argv, argv + argc);
return Piscsi().run(args); return Piscsi().run(args);
} }

View File

@ -3,24 +3,50 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/log.h" #include "shared/piscsi_exceptions.h"
#include "generated/piscsi_interface.pb.h" #include "shared/protobuf_util.h"
#include "command_context.h" #include "command_context.h"
#include <spdlog/spdlog.h>
#include <iostream> #include <iostream>
using namespace std; using namespace std;
using namespace piscsi_interface; using namespace piscsi_interface;
using namespace protobuf_util;
void CommandContext::Cleanup() bool CommandContext::ReadCommand()
{ {
if (fd != -1) { // Read magic string
close(fd); array<byte, 6> magic;
fd = -1; 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, 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 const string& arg2, const string& arg3) const
{ {
// For the logfile always use English // 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); 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 // Do not log twice if logging has already been done in the localized error handling above
if (log && !status && !msg.empty()) { if (log && !status && !msg.empty()) {
LOGERROR("%s", msg.c_str()) spdlog::error(msg);
} }
if (fd == -1) { if (fd == -1) {
if (!msg.empty()) { if (!msg.empty()) {
if (status) { cerr << "Error: " << msg << endl;
cerr << "Error: " << msg << endl;
}
else {
cout << msg << endl;
}
} }
} }
else { else {
@ -60,8 +81,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
result.set_status(status); result.set_status(status);
result.set_error_code(error_code); result.set_error_code(error_code);
result.set_msg(msg); result.set_msg(msg);
serializer.SerializeMessage(fd, result); WriteResult(result);
} }
return status; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "generated/piscsi_interface.pb.h"
#include "localizer.h" #include "localizer.h"
#include "shared/protobuf_serializer.h" #include "generated/piscsi_interface.pb.h"
#include <string> #include <string>
using namespace std; using namespace std;
@ -19,27 +18,36 @@ using namespace piscsi_interface;
class CommandContext class CommandContext
{ {
const ProtobufSerializer serializer;
const Localizer localizer;
string locale;
int fd;
public: 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; ~CommandContext() = default;
void Cleanup(); string GetDefaultFolder() const { return default_folder; }
void SetDefaultFolder(string_view f) { default_folder = f; }
const ProtobufSerializer& GetSerializer() const { return serializer; } bool ReadCommand();
int GetFd() const { return fd; } void WriteResult(const PbResult&) const;
void SetFd(int f) { fd = f; } void WriteSuccessResult(PbResult&) const;
bool IsValid() const { return fd != -1; } const PbCommand& GetCommand() const { return command; }
bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const; bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const;
bool ReturnLocalizedError(LocalizationKey, PbErrorCode, 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "localizer.h" #include "localizer.h"
#include <cassert> #include <cassert>
#include <string>
#include <algorithm> #include <algorithm>
#include <regex>
using namespace std; using namespace std;
@ -31,12 +29,12 @@ Localizer::Localizer()
Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida"); Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida");
Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作"); Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作");
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %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, "de", "Ungültiger Log-Level '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %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, "fr", "Niveau de journalisation invalide '%1'");
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido"); 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, "zh", "无效的日志级别 '%1'");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID"); Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID");
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-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, "fr", "Nom de fichier manquant");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo"); Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo");
Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名"); 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, "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, "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, "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, "es", "El tipo de dispositivo %1 requiere un nombre de archivo");
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名"); 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, "en", "Image file '%1' is already being used by device %2");
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt"); 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 id %2, enhetsnummer %3"); 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 l'ID %2, unité %3"); 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 el ID %2, unidad %3"); 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、单元 %3 使用"); 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, "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, "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, "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, "es", "No se puede crear información de archivo de imagen para '%1'");
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%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, "en", "Device ID %1 is reserved");
Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert"); Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert");
Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat"); 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, "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, "es", "Comando para ID %1 no existente");
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令"); 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, "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, "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, "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, "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, "es", "Comando para ID %1 inexistente, unidad %2");
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %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, "en", "Unknown device type %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %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, "sv", "Obekant enhetstyp: %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %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, "es", "Tipo de dispositivo desconocido %1");
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %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, "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, "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'"); 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, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN");
Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 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, "en", "Initialization of %1 failed");
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen"); Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen");
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3"); Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 ");
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló"); Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló");
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败"); 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, "en", "%1 operation denied, %2 isn't stoppable");
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar"); 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 // Safeguards against empty messages, duplicate entries and unsupported locales
assert(locale.size()); assert(locale.size());
assert(value.size()); assert(value.size());
assert(supported_languages.find(locale) != supported_languages.end()); assert(supported_languages.contains(locale));
assert(localized_messages[locale][key].empty()); assert(localized_messages[locale][key].empty());
localized_messages[locale][key] = value; localized_messages[locale][key] = value;
} }
string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2, string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2,
const string &arg3) const const string &arg3) const
{ {
string locale_lower = locale; string locale_lower;
transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower); ranges::transform(locale, back_inserter(locale_lower), ::tolower);
auto it = localized_messages.find(locale_lower); auto it = localized_messages.find(locale_lower);
if (it == localized_messages.end()) { if (it == localized_messages.end()) {
@ -268,9 +265,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri
} }
string message = m->second; string message = m->second;
message = regex_replace(message, regex("%1"), arg1); message = regex_replace(message, regex1, arg1);
message = regex_replace(message, regex("%2"), arg2); message = regex_replace(message, regex2, arg2);
message = regex_replace(message, regex("%3"), arg3); message = regex_replace(message, regex3, arg3);
return message; return message;
} }

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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. // Message localization support. Currently only for messages with up to 3 string parameters.
// //
@ -11,9 +11,11 @@
#pragma once #pragma once
#include "shared/piscsi_util.h"
#include <string> #include <string>
#include <unordered_set> #include <unordered_set>
#include <unordered_map> #include <unordered_map>
#include <regex>
using namespace std; using namespace std;
@ -64,8 +66,12 @@ public:
private: private:
void Add(LocalizationKey, const string&, string_view); 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 // 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS // Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2020-2023 Contributors to the PiSCSI project // Copyright (C) 2020-2023 Contributors to the PiSCSI project
// Copyright (C) 2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#include "shared/config.h" #include "shared/config.h"
#include "shared/log.h"
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "shared/protobuf_serializer.h"
#include "shared/protobuf_util.h" #include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h" #include "controllers/scsi_controller.h"
#include "devices/device_logger.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
#include "devices/storage_device.h" #include "devices/storage_device.h"
#include "hal/gpiobus_factory.h" #include "hal/gpiobus_factory.h"
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "hal/systimer.h" #include "hal/systimer.h"
#include "piscsi/piscsi_executor.h"
#include "piscsi/piscsi_core.h" #include "piscsi/piscsi_core.h"
#include "spdlog/sinks/stdout_color_sinks.h" #include <spdlog/spdlog.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <csignal> #include <csignal>
#include <string>
#include <sstream> #include <sstream>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <list> #include <vector>
#include <filesystem>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
using namespace spdlog;
using namespace piscsi_interface; using namespace piscsi_interface;
using namespace piscsi_util; using namespace piscsi_util;
using namespace protobuf_util; using namespace protobuf_util;
using namespace scsi_defs; 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 << piscsi_util::Banner("(Backend Service)");
cout << "Connection type: " << CONNECT_DESC << '\n' << flush; cout << "Connection type: " << CONNECT_DESC << '\n' << flush;
if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){ 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 << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n";
cout << " ID is SCSI device ID (0-7).\n"; cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n";
cout << " LUN is the optional logical unit (0-31).\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 << " 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 << " 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"; 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); bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
if (bus == nullptr) { if (bus == nullptr) {
return false; return false;
} }
auto b = bus; executor = make_unique<PiscsiExecutor>(piscsi_image, *bus, controller_manager);
controller_manager = make_shared<ControllerManager>(*b);
auto c = controller_manager;
executor = make_shared<PiscsiExecutor>(piscsi_image, *c);
return true; return true;
} }
void Piscsi::Cleanup() void Piscsi::CleanUp()
{ {
executor->DetachAll(); if (service.IsRunning()) {
service.Stop();
}
service.Cleanup(); executor->DetachAll();
bus->Cleanup(); bus->Cleanup();
} }
void Piscsi::ReadAccessToken(const string& filename) const void Piscsi::ReadAccessToken(const path& filename)
{ {
struct stat st; if (error_code error; !is_regular_file(filename, error)) {
if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) { throw parser_exception("Access token file '" + filename.string() + "' must be a regular file");
throw parser_exception("Can't access token file '" + filename + "'");
} }
if (st.st_uid || st.st_gid) { if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) {
throw parser_exception("Access token file '" + filename + "' must be owned by root"); throw parser_exception("Access token file '" + filename.string() + "' must be owned by root");
} }
if (const auto perms = filesystem::status(filename).permissions(); if (const auto perms = filesystem::status(filename).permissions();
(perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none || (perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none ||
(perms & perms::group_write) != perms::none || (perms & perms::others_write) != 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); ifstream token_file(filename);
if (token_file.fail()) { 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); getline(token_file, access_token);
if (token_file.fail()) { 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()) { 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; string line;
while (getline(ss, line, '\n')) { 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) void Piscsi::TerminationHandler(int)
{ {
Cleanup(); instance->CleanUp();
// Process will terminate automatically // 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 name;
string id_and_lun;
string locale = GetLocale();
// Avoid duplicate messages while parsing
set_level(level::off);
opterr = 1; opterr = 1;
int opt; 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) { 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) { switch (opt) {
// The following options can not be processed until AFTER // The two options below are kind of a compound option with two letters
// the 'bus' object is created and configured
case 'i': case 'i':
case 'I': case 'I':
case 'b': continue;
case 'd': case 'd':
case 'D': case 'D':
case 'R': id_and_lun = optarg;
case 'n': continue;
case 'r':
case 't': case 'b':
case 'F': if (!GetAsUnsignedInt(optarg, block_size)) {
case 'z': throw parser_exception("Invalid block size " + string(optarg));
{ }
const string optarg_str = optarg == nullptr ? "" : optarg; continue;
optargs.emplace_back(opt, optarg_str);
case 'z':
locale = optarg;
continue;
case 'F':
if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) {
throw parser_exception(error);
}
continue; continue;
}
case 'L': 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; continue;
case 'p': case 'p':
@ -196,92 +204,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector<char *>& args, int& por
ReadAccessToken(optarg); ReadAccessToken(optarg);
continue; 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': case 'r':
if (const string error = executor->SetReservedIds(value); !error.empty()) { reserved_ids = optarg;
throw parser_exception(error);
}
continue; continue;
case 't': case 't':
type = ParseDeviceType(value); type = ParseDeviceType(optarg);
continue; continue;
case 1: case 1:
@ -292,10 +220,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
throw parser_exception("Parser error"); 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 (!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); throw parser_exception(error);
} }
} }
@ -303,7 +237,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
device->set_type(type); device->set_type(type);
device->set_block_size(block_size); device->set_block_size(block_size);
ParseParameters(*device, value); ParseParameters(*device, optarg);
SetProductData(*device, name); SetProductData(*device, name);
@ -313,182 +247,231 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
id_and_lun = ""; id_and_lun = "";
} }
// Attach all specified devices if (!SetLogLevel(log_level)) {
command.set_operation(ATTACH); throw parser_exception("Invalid log level '" + log_level + "'");
if (CommandContext context(locale); !executor->ProcessCmd(context, command)) {
throw parser_exception("Can't execute " + PbOperation_Name(command.operation()));
} }
// Display and log the device list return locale;
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;
} }
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")) { if (!access_token.empty() && access_token != GetParam(command, "token")) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED); return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
} }
if (!PbOperation_IsValid(command.operation())) { 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); 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; PbResult result;
ProtobufSerializer serializer;
switch(command.operation()) { switch(command.operation()) {
case LOG_LEVEL: { case LOG_LEVEL:
const string log_level = GetParam(command, "level"); if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) {
if (const bool status = executor->SetLogLevel(log_level); !status) {
context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level); context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level);
} }
else { else {
current_log_level = log_level; context.ReturnSuccessStatus();
context.ReturnStatus();
} }
break; break;
}
case DEFAULT_FOLDER: { case DEFAULT_FOLDER:
if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) { if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) {
context.ReturnStatus(false, status); context.ReturnErrorStatus(error);
} }
else { else {
context.ReturnStatus(); context.ReturnSuccessStatus();
} }
break; break;
}
case DEVICES_INFO: { case DEVICES_INFO:
piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command, response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder());
piscsi_image.GetDefaultFolder()); context.WriteResult(result);
serializer.SerializeMessage(context.GetFd(), result);
break; break;
}
case DEVICE_TYPES_INFO: { case DEVICE_TYPES_INFO:
result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release()); response.GetDeviceTypesInfo(*result.mutable_device_types_info());
serializer.SerializeMessage(context.GetFd(), result); context.WriteSuccessResult(result);
break; break;
}
case SERVER_INFO: { case SERVER_INFO:
result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(), response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(),
result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(), executor->GetReservedIds(), piscsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
piscsi_image.GetDepth()).release()); context.WriteSuccessResult(result);
serializer.SerializeMessage(context.GetFd(), result);
break; break;
}
case VERSION_INFO: { case VERSION_INFO:
result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release()); response.GetVersionInfo(*result.mutable_version_info());
serializer.SerializeMessage(context.GetFd(), result); context.WriteSuccessResult(result);
break; break;
}
case LOG_LEVEL_INFO: { case LOG_LEVEL_INFO:
result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release()); response.GetLogLevelInfo(*result.mutable_log_level_info());
serializer.SerializeMessage(context.GetFd(), result); context.WriteSuccessResult(result);
break; break;
}
case DEFAULT_IMAGE_FILES_INFO: { case DEFAULT_IMAGE_FILES_INFO:
result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result, response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(),
piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"), GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release()); context.WriteSuccessResult(result);
serializer.SerializeMessage(context.GetFd(), result);
break; break;
}
case IMAGE_FILE_INFO: { case IMAGE_FILE_INFO:
if (string filename = GetParam(command, "file"); filename.empty()) { if (string filename = GetParam(command, "file"); filename.empty()) {
context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME); context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME);
} }
else { else {
auto image_file = make_unique<PbImageFile>(); 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) { if (status) {
result.set_status(true);
result.set_allocated_image_file_info(image_file.get()); result.set_allocated_image_file_info(image_file.get());
serializer.SerializeMessage(context.GetFd(), result); result.set_status(true);
context.WriteResult(result);
} }
else { else {
context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO); context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO);
} }
} }
break; break;
}
case NETWORK_INTERFACES_INFO: { case NETWORK_INTERFACES_INFO:
result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release()); response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info());
serializer.SerializeMessage(context.GetFd(), result); context.WriteSuccessResult(result);
break; break;
}
case MAPPING_INFO: { case MAPPING_INFO:
result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release()); response.GetMappingInfo(*result.mutable_mapping_info());
serializer.SerializeMessage(context.GetFd(), result); context.WriteSuccessResult(result);
break; break;
}
case OPERATION_INFO: { case OPERATION_INFO:
result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result, response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth());
piscsi_image.GetDepth()).release()); context.WriteSuccessResult(result);
serializer.SerializeMessage(context.GetFd(), result);
break; break;
}
case RESERVED_IDS_INFO: { case RESERVED_IDS_INFO:
result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result, response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds());
executor->GetReservedIds()).release()); context.WriteSuccessResult(result);
serializer.SerializeMessage(context.GetFd(), result);
break; break;
}
case SHUT_DOWN: { case SHUT_DOWN:
if (executor->ShutDown(context, GetParam(command, "mode"))) { if (executor->ShutDown(context, GetParam(command, "mode"))) {
TerminationHandler(0); TerminationHandler(0);
} }
break; break;
}
default: { case NO_OPERATION:
// Wait until we become idle 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}; const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000};
while (active) { while (target_is_active) {
nanosleep(&ts, nullptr); nanosleep(&ts, nullptr);
} }
return executor->ProcessCmd(context);
executor->ProcessCmd(context, command);
break;
}
} }
return true; return true;
} }
int Piscsi::run(const vector<char *>& args) int Piscsi::run(span<char *> args)
{ {
GOOGLE_PROTOBUF_VERIFY_VERSION; GOOGLE_PROTOBUF_VERIFY_VERSION;
Banner(args); 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; int port = DEFAULT_PORT;
optargs_type optargs;
try { try {
optargs = ParseArguments(args, port); locale = ParseArguments(args, command, port, reserved_ids);
} }
catch(const parser_exception& e) { catch(const parser_exception& e) {
cerr << "Error: " << e.what() << endl; cerr << "Error: " << e.what() << endl;
@ -496,35 +479,51 @@ int Piscsi::run(const vector<char *>& args)
return EXIT_FAILURE; 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()) { if (!InitBus()) {
cerr << "Error: Can't initialize bus" << endl; cerr << "Error: Can't initialize bus" << endl;
return EXIT_FAILURE; return EXIT_FAILURE;
} }
// We need to wait to create the devices until after the bus/controller/etc objects have been created if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port);
// TODO Try to resolve dependencies so that this work-around can be removed !error.empty()) {
try { cerr << "Error: " << error << endl;
CreateInitialDevices(optargs);
}
catch(const parser_exception& e) {
cerr << "Error: " << e.what() << endl;
Cleanup(); CleanUp();
return EXIT_FAILURE; 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; 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 // Signal handler to detach all devices on a KILL or TERM signal
struct sigaction termination_handler; struct sigaction termination_handler;
termination_handler.sa_handler = TerminationHandler; termination_handler.sa_handler = TerminationHandler;
@ -532,22 +531,30 @@ int Piscsi::run(const vector<char *>& args)
termination_handler.sa_flags = 0; termination_handler.sa_flags = 0;
sigaction(SIGINT, &termination_handler, nullptr); sigaction(SIGINT, &termination_handler, nullptr);
sigaction(SIGTERM, &termination_handler, nullptr); sigaction(SIGTERM, &termination_handler, nullptr);
signal(SIGPIPE, SIG_IGN);
// Set the affinity to a specific processor core // Set the affinity to a specific processor core
FixCpu(3); FixCpu(3);
sched_param schparam; service.Start();
Process();
return EXIT_SUCCESS;
}
void Piscsi::Process()
{
#ifdef USE_SEL_EVENT_ENABLE #ifdef USE_SEL_EVENT_ENABLE
// Scheduling policy setting (highest priority) // 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); schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
sched_setscheduler(0, SCHED_FIFO, &schparam); sched_setscheduler(0, SCHED_FIFO, &schparam);
#else #else
cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl; cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl;
#endif #endif
// Start execution
service.SetRunning(true);
// Main Loop // Main Loop
while (service.IsRunning()) { while (service.IsRunning()) {
#ifdef USE_SEL_EVENT_ENABLE #ifdef USE_SEL_EVENT_ENABLE
@ -571,81 +578,55 @@ int Piscsi::run(const vector<char *>& args)
} }
#endif #endif
// Wait until BSY is released as there is a possibility for the // Only process the SCSI command if the bus is not busy and no other device responded
// initiator to assert it while setting the ID (for up to 3 seconds) if (IsNotBusy() && bus->GetSEL()) {
WaitForNotBusy(); target_is_active = true;
// Stop because the bus is busy or another device responded // Process command on the responsible controller based on the current initiator and target ID
if (bus->GetBSY() || !bus->GetSEL()) { if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT());
continue; shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) {
} // When the bus is free PiSCSI or the Pi may be shut down.
ShutDown(shutdown_mode);
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");
} }
if (controller->Process(initiator_id) == phase_t::selection) { target_is_active = false;
phase = phase_t::selection;
}
} }
// 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()) { if (bus->GetBSY()) {
const uint32_t now = SysTimer::GetTimerLow(); const uint32_t now = SysTimer::GetTimerLow();
@ -654,8 +635,12 @@ void Piscsi::WaitForNotBusy() const
bus->Acquire(); bus->Acquire();
if (!bus->GetBSY()) { if (!bus->GetBSY()) {
break; return true;
} }
} }
return false;
} }
return true;
} }

View File

@ -3,31 +3,31 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "devices/device_logger.h" #include "controllers/controller_manager.h"
#include "controllers/abstract_controller.h"
#include "piscsi/command_context.h" #include "piscsi/command_context.h"
#include "piscsi/piscsi_service.h" #include "piscsi/piscsi_service.h"
#include "piscsi/piscsi_image.h" #include "piscsi/piscsi_image.h"
#include "piscsi/piscsi_response.h" #include "piscsi/piscsi_response.h"
#include "piscsi/piscsi_executor.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include <vector> #include "spdlog/sinks/stdout_color_sinks.h"
#include <span>
#include <string> #include <string>
#include <atomic>
using namespace std; using namespace std;
class BUS; class BUS;
class ControllerManager;
class PiscsiExecutor;
class Piscsi class Piscsi
{ {
using optargs_type = vector<pair<int, string>>;
static const int DEFAULT_PORT = 6868; static const int DEFAULT_PORT = 6868;
public: public:
@ -35,46 +35,47 @@ public:
Piscsi() = default; Piscsi() = default;
~Piscsi() = default; ~Piscsi() = default;
int run(const vector<char *>&); int run(span<char *>);
private: private:
void Banner(const vector<char *>&) const; void Banner(span<char *>) const;
bool InitBus() const; bool InitBus();
static void Cleanup(); void CleanUp();
void ReadAccessToken(const string&) const; void ReadAccessToken(const path&);
void LogDevices(string_view) const; void LogDevices(string_view) const;
PbDeviceType ParseDeviceType(const string&) const;
static void TerminationHandler(int); static void TerminationHandler(int);
optargs_type ParseArguments(const vector<char *>&, int&) const; string ParseArguments(span<char *>, PbCommand&, int&, string&);
void CreateInitialDevices(const optargs_type&) const; void Process();
void WaitForNotBusy() const; bool IsNotBusy() const;
// TODO Should not be static and should be moved to PiscsiService void ShutDown(AbstractController::piscsi_shutdown_mode);
static bool ExecuteCommand(const CommandContext&, const PbCommand&);
DeviceLogger device_logger; bool ExecuteCommand(CommandContext&);
// A static instance is needed because of the signal handler bool SetLogLevel(const string&) const;
static inline shared_ptr<BUS> bus;
// 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 PbDeviceType ParseDeviceType(const string&);
static inline PiscsiImage piscsi_image;
const static inline PiscsiResponse piscsi_response;
static inline shared_ptr<ControllerManager> controller_manager;
static inline shared_ptr<PiscsiExecutor> executor;
// Processing flag // 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 string access_token;
static inline string current_log_level = "info";
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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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/piscsi_util.h"
#include "shared/protobuf_util.h" #include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h" #include "controllers/scsi_controller.h"
#include "devices/device_logger.h"
#include "devices/device_factory.h" #include "devices/device_factory.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "devices/disk.h" #include "devices/disk.h"
#include "piscsi_service.h"
#include "piscsi_image.h" #include "piscsi_image.h"
#include "localizer.h" #include "localizer.h"
#include "command_context.h" #include "command_context.h"
#include "piscsi_executor.h" #include "piscsi_executor.h"
#include <spdlog/spdlog.h>
#include <sstream> #include <sstream>
using namespace spdlog; using namespace spdlog;
using namespace protobuf_util; using namespace protobuf_util;
using namespace piscsi_util; using namespace piscsi_util;
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun)
const PbCommand& command, 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 id = pb_device.id();
const int lun = pb_device.unit(); const int lun = pb_device.unit();
@ -40,14 +36,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return false; return false;
} }
const PbOperation operation = command.operation(); const PbOperation operation = context.GetCommand().operation();
// For all commands except ATTACH the device and LUN must exist // For all commands except ATTACH the device and LUN must exist
if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) { if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) {
return false; return false;
} }
auto device = controller_manager.GetDeviceByIdAndLun(id, lun); auto device = controller_manager.GetDeviceForIdAndLun(id, lun);
if (!ValidateOperationAgainstDevice(context, *device, operation)) { if (!ValidateOperationAgainstDevice(context, *device, operation)) {
return false; return false;
@ -64,7 +60,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return Attach(context, pb_device, dryRun); return Attach(context, pb_device, dryRun);
case DETACH: case DETACH:
return Detach(context, device, dryRun); return Detach(context, *device, dryRun);
case INSERT: case INSERT:
return Insert(context, pb_device, device, dryRun); return Insert(context, pb_device, device, dryRun);
@ -82,7 +78,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
case CHECK_AUTHENTICATION: case CHECK_AUTHENTICATION:
case NO_OPERATION: case NO_OPERATION:
// Do nothing, just log // Do nothing, just log
LOGTRACE("Received %s command", PbOperation_Name(operation).c_str()) spdlog::trace("Received " + PbOperation_Name(operation) + " command");
break; break;
default: default:
@ -92,136 +88,83 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
return true; 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()) { switch (command.operation()) {
case DETACH_ALL: case DETACH_ALL:
DetachAll(); DetachAll();
return context.ReturnStatus(); return context.ReturnSuccessStatus();
case RESERVE_IDS: { case RESERVE_IDS: {
const string ids = GetParam(command, "ids"); const string ids = GetParam(command, "ids");
if (const string error = SetReservedIds(ids); !error.empty()) { 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: case CREATE_IMAGE:
return piscsi_image.CreateImage(context, command); return piscsi_image.CreateImage(context);
case DELETE_IMAGE: case DELETE_IMAGE:
return piscsi_image.DeleteImage(context, command); return piscsi_image.DeleteImage(context);
case RENAME_IMAGE: case RENAME_IMAGE:
return piscsi_image.RenameImage(context, command); return piscsi_image.RenameImage(context);
case COPY_IMAGE: case COPY_IMAGE:
return piscsi_image.CopyImage(context, command); return piscsi_image.CopyImage(context);
case PROTECT_IMAGE: case PROTECT_IMAGE:
case UNPROTECT_IMAGE: case UNPROTECT_IMAGE:
return piscsi_image.SetImagePermissions(context, command); return piscsi_image.SetImagePermissions(context);
default: default:
// This is a device-specific command handled below // This is a device-specific command handled below
break; 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(); const auto& reserved_files = StorageDevice::GetReservedFiles();
for (const auto& device : command.devices()) { const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device)
if (!ProcessDeviceCmd(context, device, command, true)) { { return ProcessDeviceCmd(context, device, true); }) != command.devices().end();
// Dry run failed, restore the file list
StorageDevice::SetReservedFiles(reserved_files);
return false;
}
}
// Restore the list of reserved files before proceeding
StorageDevice::SetReservedFiles(reserved_files); StorageDevice::SetReservedFiles(reserved_files);
if (reserved) {
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())
return false; return false;
} }
DeviceLogger::SetLogIdAndLun(id, lun); if (const string error = EnsureLun0(command); !error.empty()) {
return context.ReturnErrorStatus(error);
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())
} }
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 bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
{ {
if (!dryRun) { 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()) { 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 bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
{ {
if (!dryRun) { 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(); device.Stop();
} }
@ -242,10 +185,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
{ {
if (!dryRun) { 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)) { 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 bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
{ {
if (!dryRun) { if (!dryRun) {
LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), spdlog::info("Write protection requested for " + device.GetIdentifier());
device.GetLun())
device.SetProtected(true); device.SetProtected(true);
} }
@ -267,8 +209,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const
{ {
if (!dryRun) { if (!dryRun) {
LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), spdlog::info("Write unprotection requested for " + device.GetIdentifier());
device.GetLun())
device.SetProtected(false); device.SetProtected(false);
} }
@ -282,15 +223,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
const int lun = pb_device.unit(); const int lun = pb_device.unit();
const PbDeviceType type = pb_device.type(); const PbDeviceType type = pb_device.type();
if (lun >= ScsiController::LUN_MAX) { if (lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX)); 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)); 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)); 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 // If no filename was provided the medium is considered not inserted
auto storage_device = dynamic_pointer_cast<StorageDevice>(device); device->SetRemoved(device->SupportsFile() ? filename.empty() : false);
device->SetRemoved(storage_device != nullptr ? filename.empty() : false);
if (!SetProductData(context, pb_device, *device)) { if (!SetProductData(context, pb_device, *device)) {
return false; return false;
@ -313,14 +254,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return false; return false;
} }
string full_path; const auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
if (device->SupportsFile()) { if (device->SupportsFile()) {
// Only with removable media drives, CD and MO the medium (=file) may be inserted later // Only with removable media drives, CD and MO the medium (=file) may be inserted later
if (!device->IsRemovable() && filename.empty()) { if (!device->IsRemovable() && filename.empty()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); 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; return false;
} }
} }
@ -336,23 +277,22 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
return true; 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()) { if (!device->SupportsFile()) {
// Clients like scsictl might have sent both "file" and "interfaces" // Clients like scsictl might have sent both "file" and "interfaces"
params.erase("file"); params.erase("file");
} }
if (!device->Init(params)) { if (!device->Init(params)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()), return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier());
to_string(id), to_string(lun)); }
if (!controller_manager.AttachToController(bus, id, device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
} }
if (storage_device != nullptr) { if (storage_device != nullptr) {
storage_device->ReserveFile(full_path, id, lun); storage_device->ReserveFile();
}
if (!controller_manager.AttachToScsiController(id, device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
} }
string msg = "Attached "; string msg = "Attached ";
@ -362,8 +302,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
else if (device->IsProtectable() && device->IsProtected()) { else if (device->IsProtectable() && device->IsProtected()) {
msg += "protected "; msg += "protected ";
} }
msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun); msg += device->GetIdentifier();
LOGINFO("%s", msg.c_str()) spdlog::info(msg);
return true; return true;
} }
@ -371,12 +311,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
const shared_ptr<PrimaryDevice>& device, bool dryRun) const const shared_ptr<PrimaryDevice>& device, bool dryRun) const
{ {
auto storage_device = dynamic_pointer_cast<StorageDevice>(device); if (!device->SupportsFile()) {
if (storage_device == nullptr) {
return false; return false;
} }
if (!storage_device->IsRemoved()) { if (!device->IsRemoved()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED); return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED);
} }
@ -394,56 +333,52 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
return true; return true;
} }
LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename +
filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit()) "' 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())) { if (!SetSectorSize(context, storage_device, pb_device.block_size())) {
return false; return false;
} }
string full_path; if (!ValidateImageFile(context, *storage_device, filename)) {
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
return false; return false;
} }
storage_device->SetProtected(pb_device.protected_()); storage_device->SetProtected(pb_device.protected_());
storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun()); storage_device->ReserveFile();
storage_device->SetMediumChanged(true); storage_device->SetMediumChanged(true);
return 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) { if (controller == nullptr) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
} }
// LUN 0 can only be detached if there is no other LUN anymore // 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); return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0);
} }
if (!dryRun) { if (!dryRun) {
// Remember the ID before it gets invalid when removing the device // Remember the device identifier for the log message before the device data become invalid on removal
const int id = device->GetId(); const string identifier = device.GetIdentifier();
if (!controller->RemoveDevice(device)) { if (!controller->RemoveDevice(device)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
} }
// If no LUN is left also delete the controller // 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); return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
} }
if (auto storage_device = dynamic_pointer_cast<StorageDevice>(device); storage_device != nullptr) { spdlog::info("Detached " + identifier);
storage_device->UnreserveFile();
}
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id)
+ ", unit " + to_string(device->GetLun())).c_str())
} }
return true; return true;
@ -452,9 +387,8 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<Prim
void PiscsiExecutor::DetachAll() void PiscsiExecutor::DetachAll()
{ {
controller_manager.DeleteAllControllers(); controller_manager.DeleteAllControllers();
StorageDevice::UnreserveAll();
LOGINFO("Detached all devices") spdlog::info("Detached all devices");
} }
bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode) { 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 // The PiSCSI shutdown mode is "rascsi" instead of "piscsi" for backwards compatibility
if (mode == "rascsi") { if (mode == "rascsi") {
LOGINFO("PiSCSI shutdown requested") spdlog::info("PiSCSI shutdown requested");
serializer.SerializeMessage(context.GetFd(), result); context.WriteResult(result);
return true; return true;
} }
@ -484,145 +418,116 @@ bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode)
} }
if (mode == "system") { if (mode == "system") {
LOGINFO("System shutdown requested") spdlog::info("System shutdown requested");
serializer.SerializeMessage(context.GetFd(), result);
DetachAll(); DetachAll();
context.WriteResult(result);
if (system("init 0") == -1) { if (system("init 0") == -1) {
LOGERROR("System shutdown failed: %s", strerror(errno)) spdlog::error("System shutdown failed");
} }
} }
else if (mode == "reboot") { else if (mode == "reboot") {
LOGINFO("System reboot requested") spdlog::info("System reboot requested");
serializer.SerializeMessage(context.GetFd(), result);
DetachAll(); DetachAll();
context.WriteResult(result);
if (system("init 6") == -1) { if (system("init 6") == -1) {
LOGERROR("System reboot failed: %s", strerror(errno)) spdlog::error("System reboot failed");
} }
} }
else {
assert(false);
}
return false; return false;
} }
string PiscsiExecutor::SetReservedIds(string_view ids) string PiscsiExecutor::SetReservedIds(string_view ids)
{ {
list<string> ids_to_reserve; set<int> ids_to_reserve;
stringstream ss(ids.data()); stringstream ss(ids.data());
string id; string id;
while (getline(ss, 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; int res_id;
if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) { if (!GetAsUnsignedInt(id, res_id) || res_id > 7) {
return "Invalid ID " + id_to_reserve; return "Invalid ID " + id;
}
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);
} }
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 { else {
LOGINFO("Cleared reserved ID(s)") spdlog::info("Cleared reserved ID(s)");
} }
return ""; return "";
} }
bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device, bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device,
const string& filename, string& full_path) const const string& filename) const
{ {
if (filename.empty()) { if (filename.empty()) {
return true; return true;
} }
if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) { if (!CheckForReservedFile(context, filename)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, return false;
to_string(id1), to_string(lun1));
} }
string effective_filename = filename; storage_device.SetFilename(filename);
if (!StorageDevice::FileExists(filename)) { if (!StorageDevice::FileExists(filename)) {
// If the file does not exist search for it in the default image folder // 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) { if (!CheckForReservedFile(context, effective_filename)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, return false;
to_string(id2), to_string(lun2));
} }
if (!StorageDevice::FileExists(effective_filename)) { storage_device.SetFilename(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);
} }
try { try {
storage_device.Open(); storage_device.Open();
} }
catch(const io_exception&) { 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; 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() }; const map<string, string, less<>> params = { command.params().begin(), command.params().end() };
ostringstream s; ostringstream s;
s << (dryRun ? "Validating" : "Executing"); s << "operation=" << PbOperation_Name(command.operation());
s << ": operation=" << PbOperation_Name(command.operation());
if (!params.empty()) { if (!params.empty()) {
s << ", command params="; s << ", command params=";
bool isFirst = true; bool isFirst = true;
for (const auto& [key, value]: params) { for (const auto& [key, value] : params) {
if (!isFirst) { if (!isFirst) {
s << ", "; s << ", ";
} }
@ -632,8 +537,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
} }
} }
s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type=" s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type());
<< PbDeviceType_Name(pb_device.type());
if (pb_device.params_size()) { if (pb_device.params_size()) {
s << ", device params="; s << ", device params=";
@ -649,13 +553,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product() s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product()
<< "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size(); << "', 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 // 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 // Collect LUN bit vectors of new devices
for (const auto& device : command.devices()) { for (const auto& device : command.devices()) {
@ -667,23 +572,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
luns[device->GetId()] |= 1 << device->GetLun(); luns[device->GetId()] |= 1 << device->GetLun();
} }
// LUN 0 must exist for all devices const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } );
for (const auto& [id, lun]: luns) { return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first);
if (!(lun & 0x01)) {
return "LUN 0 is missing for device ID " + to_string(id);
}
}
return "";
} }
bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const 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)); 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)); 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; 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) { if (size) {
auto disk = dynamic_pointer_cast<Disk>(device); const auto disk = dynamic_pointer_cast<Disk>(device);
if (disk != nullptr && disk->IsSectorSizeConfigurable()) { if (disk != nullptr && disk->IsSectorSizeConfigurable()) {
if (!disk->SetConfiguredSectorSize(device_factory, block_size)) { if (!disk->SetConfiguredSectorSize(device_factory, size)) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size)); return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size));
} }
} }
else { else {
@ -725,22 +624,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<Pri
} }
bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const PrimaryDevice& device, bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const PrimaryDevice& device,
const PbOperation& operation) PbOperation operation)
{ {
if ((operation == START || operation == STOP) && !device.IsStoppable()) { 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()) { 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()) { 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()) { 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; return true;
@ -748,15 +651,16 @@ bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& contex
bool PiscsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun) bool PiscsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun)
{ {
// Validate the device ID and LUN
if (id < 0) { if (id < 0) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID); return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID);
} }
if (id >= ControllerManager::DEVICE_MAX) { if (id >= ControllerManager::GetScsiIdMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1)); return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id),
to_string(ControllerManager::GetScsiIdMax() - 1));
} }
if (lun < 0 || lun >= ScsiController::LUN_MAX) { if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) {
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1)); return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
to_string(ControllerManager::GetScsiLunMax() - 1));
} }
return true; return true;
@ -777,7 +681,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic
} }
} }
catch(const invalid_argument& e) { catch(const invalid_argument& e) {
return context.ReturnStatus(false, e.what()); return context.ReturnErrorStatus(e.what());
} }
return true; return true;

View File

@ -3,22 +3,21 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "spdlog/spdlog.h" #include "hal/bus.h"
#include "shared/protobuf_serializer.h" #include "controllers/controller_manager.h"
#include "piscsi/piscsi_response.h" #include "piscsi/piscsi_response.h"
#include <unordered_set> #include <unordered_set>
#include <unordered_map>
class PiscsiImage; class PiscsiImage;
class DeviceFactory; class DeviceFactory;
class ControllerManager;
class PrimaryDevice; class PrimaryDevice;
class StorageDevice;
class CommandContext; class CommandContext;
using namespace spdlog; using namespace spdlog;
@ -27,15 +26,14 @@ class PiscsiExecutor
{ {
public: public:
PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager) PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager)
: piscsi_image(piscsi_image), controller_manager(controller_manager) {} : piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {}
~PiscsiExecutor() = default; ~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 ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool);
bool ProcessCmd(const CommandContext&, const PbCommand&); bool ProcessCmd(const CommandContext&);
bool SetLogLevel(const string&) const;
bool Start(PrimaryDevice&, bool) const; bool Start(PrimaryDevice&, bool) const;
bool Stop(PrimaryDevice&, bool) const; bool Stop(PrimaryDevice&, bool) const;
bool Eject(PrimaryDevice&, bool) const; bool Eject(PrimaryDevice&, bool) const;
@ -43,41 +41,34 @@ public:
bool Unprotect(PrimaryDevice&, bool) const; bool Unprotect(PrimaryDevice&, bool) const;
bool Attach(const CommandContext&, const PbDeviceDefinition&, bool); bool Attach(const CommandContext&, const PbDeviceDefinition&, bool);
bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr<PrimaryDevice>&, bool) const; 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(); void DetachAll();
bool ShutDown(const CommandContext&, const string&); bool ShutDown(const CommandContext&, const string&);
string SetReservedIds(string_view); string SetReservedIds(string_view);
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const; bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const;
void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const;
string ValidateLunSetup(const PbCommand&) const; string EnsureLun0(const PbCommand&) const;
bool VerifyExistingIdAndLun(const CommandContext&, int, int) const; bool VerifyExistingIdAndLun(const CommandContext&, int, int) const;
shared_ptr<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const; shared_ptr<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const;
bool SetSectorSize(const CommandContext&, shared_ptr<PrimaryDevice>, int) 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 ValidateIdAndLun(const CommandContext&, int, int);
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&); static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&);
private: private:
static bool CheckForReservedFile(const CommandContext&, const string&);
const PiscsiResponse piscsi_response; const PiscsiResponse piscsi_response;
PiscsiImage& piscsi_image; PiscsiImage& piscsi_image;
BUS& bus;
ControllerManager& controller_manager; ControllerManager& controller_manager;
const DeviceFactory device_factory; const DeviceFactory device_factory;
const ProtobufSerializer serializer;
unordered_set<int> reserved_ids; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 "devices/disk.h"
#include "command_context.h"
#include "piscsi_image.h" #include "piscsi_image.h"
#include "shared/protobuf_util.h"
#include <spdlog/spdlog.h>
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
#include <fstream> #include <fstream>
#include <string>
#include <array> #include <array>
#include <filesystem>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage()
bool PiscsiImage::CheckDepth(string_view filename) const 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) { if (const auto folder = path(filename).parent_path(); !folder.string().empty()) {
const auto folder = path(filename.substr(0, filename_start));
// Checking for existence first prevents an error if the top-level folder is a softlink // Checking for existence first prevents an error if the top-level folder is a softlink
if (error_code error; exists(folder, error)) { if (error_code error; exists(folder, error)) {
return true; return true;
@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string&
return ChangeOwner(context, folder, false); return ChangeOwner(context, folder, false);
} }
catch(const filesystem_error& e) { 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; return true;
} }
string PiscsiImage::SetDefaultFolder(const string& f) string PiscsiImage::SetDefaultFolder(string_view f)
{ {
if (f.empty()) { if (f.empty()) {
return "Can't set default image folder: Missing folder name"; return "Can't set default image folder: Missing folder name";
} }
string folder = f;
// If a relative path is specified, the path is assumed to be relative to the user's home directory // If a relative path is specified, the path is assumed to be relative to the user's home directory
if (folder[0] != '/') { path folder(f);
folder = GetHomeDir() + "/" + folder; if (folder.is_relative()) {
folder = path(GetHomeDir() + "/" + folder.string());
} }
else {
if (folder.find("/home/") != 0) { if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) {
return "Default image folder must be located in '/home/'"; return "Default image folder must be located in '" + home_root.string() + "'";
}
} }
// Resolve a potential symlink // Resolve a potential symlink
auto p = path(folder); if (error_code error; is_symlink(folder, error)) {
if (error_code error; is_symlink(p, error)) { folder = read_symlink(folder);
p = read_symlink(p);
} }
if (error_code error; !is_directory(p, error)) { if (error_code error; !is_directory(folder)) {
return "'" + string(p) + "' is not a valid 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 ""; 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()) { if (filename.empty()) {
return context.ReturnStatus(false, "Can't create image file: Missing image filename"); return context.ReturnErrorStatus("Missing image filename");
} }
if (!CheckDepth(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); const string full_filename = GetFullName(filename);
if (!IsValidDstFilename(full_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()) { 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; off_t len;
@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
len = stoull(size); len = stoull(size);
} }
catch(const invalid_argument&) { 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&) { 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)) { 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)) { if (!CreateImageFolder(context, full_filename)) {
return false; return false;
} }
const bool read_only = GetParam(command, "read_only") == "true"; const bool read_only = GetParam(context.GetCommand(), "read_only") == "true";
error_code error; error_code error;
path file(full_filename); path file(full_filename);
@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
catch(const filesystem_error& e) { catch(const filesystem_error& e) {
remove(file, error); 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 + spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes").c_str()) "' 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()) { if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename"); return context.ReturnErrorStatus("Missing image filename");
} }
if (!CheckDepth(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)); const auto full_filename = path(GetFullName(filename));
if (!exists(full_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 (!IsReservedFile(context, full_filename, "delete")) {
if (id != -1 || lun != -1) { return false;
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 (error_code error; !remove(full_filename, error)) { 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 // Delete empty subfolders
@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
} }
if (error_code error; !remove(full_folder)) { 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('/'); 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 from;
string to; string to;
if (!ValidateParams(context, command, "rename/move", from, to)) { if (!ValidateParams(context, "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)) {
return false; return false;
} }
@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co
rename(path(from), path(to)); rename(path(from), path(to));
} }
catch(const filesystem_error& e) { 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 from;
string to; string to;
if (!ValidateParams(context, command, "copy", from, to)) { if (!ValidateParams(context, "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)) {
return false; return false;
} }
@ -268,119 +233,134 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
copy_symlink(f, t); copy_symlink(f, t);
} }
catch(const filesystem_error& e) { 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 { try {
copy_file(f, t); 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_read | perms::group_read | perms::others_read | perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write); perms::owner_write | perms::group_write);
} }
catch(const filesystem_error& e) { 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()) { if (filename.empty()) {
return context.ReturnStatus(false, "Missing image filename"); return context.ReturnErrorStatus("Missing image filename");
} }
if (!CheckDepth(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); const string full_filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) { if (!IsValidSrcFilename(full_filename)) {
return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type"); 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 { 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_read | perms::group_read | perms::others_read | perms::owner_read | perms::group_read | perms::others_read |
perms::owner_write | perms::group_write); perms::owner_write | perms::group_write);
} }
catch(const filesystem_error& e) { catch(const filesystem_error& e) {
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
filename + "': " + e.what()); full_filename + "': " + e.what());
} }
if (protect) { spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'");
LOGINFO("Protected image file '%s'", filename.c_str())
}
else {
LOGINFO("Unprotected image file '%s'", filename.c_str())
}
return context.ReturnStatus(); return context.ReturnSuccessStatus();
} }
bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation, bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op)
string& from, string& to) const
{ {
from = GetParam(command, "from"); const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file);
if (from.empty()) { if (id != -1) {
return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename"); return context.ReturnErrorStatus("Can't " + op + " image file '" + file +
} "', it is currently being used by device " + to_string(id) + ":" + to_string(lun));
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");
} }
return true; 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 // Source file must exist and must be a regular file or a symlink
path file(filename); 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 // Destination file must not yet exist
try { try {
return !exists(path(filename)); return !exists(path(filename));
} }
catch(const filesystem_error&) { catch(const filesystem_error&) {
return true; return false;
} }
} }
@ -394,7 +374,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam
error_code error; error_code error;
remove(filename, 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 ? permissions(filename, read_only ?
@ -437,5 +417,5 @@ pair<int, int> PiscsiImage::GetUidAndGid()
gid = pwd.pw_gid; gid = pwd.pw_gid;
} }
return make_pair(uid, gid); return { uid, gid };
} }

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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; } void SetDepth(int d) { depth = d; }
int GetDepth() const { return depth; } int GetDepth() const { return depth; }
string GetDefaultFolder() const { return default_folder; } string GetDefaultFolder() const { return default_folder; }
string SetDefaultFolder(const string&); string SetDefaultFolder(string_view);
bool CreateImage(const CommandContext&, const PbCommand&) const; bool CreateImage(const CommandContext&) const;
bool DeleteImage(const CommandContext&, const PbCommand&) const; bool DeleteImage(const CommandContext&) const;
bool RenameImage(const CommandContext&, const PbCommand&) const; bool RenameImage(const CommandContext&) const;
bool CopyImage(const CommandContext&, const PbCommand&) const; bool CopyImage(const CommandContext&) const;
bool SetImagePermissions(const CommandContext&, const PbCommand&) const; bool SetImagePermissions(const CommandContext&) const;
private: private:
bool CheckDepth(string_view) const; bool CheckDepth(string_view) const;
string GetFullName(const string& filename) const { return default_folder + "/" + filename; } string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
bool CreateImageFolder(const CommandContext&, const string&) const; bool CreateImageFolder(const CommandContext&, string_view) const;
bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) 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 IsValidSrcFilename(string_view);
static bool IsValidDstFilename(const string&); static bool IsValidDstFilename(string_view);
static bool ChangeOwner(const CommandContext&, const path&, bool); static bool ChangeOwner(const CommandContext&, const path&, bool);
static string GetHomeDir(); static string GetHomeDir();
static pair<int, int> GetUidAndGid(); static pair<int, int> GetUidAndGid();

View File

@ -3,50 +3,48 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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/protobuf_util.h"
#include "shared/network_util.h"
#include "shared/piscsi_util.h"
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "devices/disk.h" #include "devices/disk.h"
#include "devices/device_factory.h"
#include "generated/piscsi_interface.pb.h"
#include "piscsi_response.h" #include "piscsi_response.h"
#include <spdlog/spdlog.h>
#include <filesystem> #include <filesystem>
using namespace std; using namespace std;
using namespace filesystem; using namespace filesystem;
using namespace piscsi_interface; using namespace piscsi_interface;
using namespace piscsi_util;
using namespace network_util;
using namespace protobuf_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>(); properties.set_luns(ControllerManager::GetScsiLunMax());
properties.set_read_only(device.IsReadOnly());
// Currently there is only a SCSI controller, i.e. there can always be 32 LUNs properties.set_protectable(device.IsProtectable());
properties->set_luns(32); properties.set_stoppable(device.IsStoppable());
properties->set_read_only(device.IsReadOnly()); properties.set_removable(device.IsRemovable());
properties->set_protectable(device.IsProtectable()); properties.set_lockable(device.IsLockable());
properties->set_stoppable(device.IsStoppable()); properties.set_supports_file(device.SupportsFile());
properties->set_removable(device.IsRemovable()); properties.set_supports_params(device.SupportsParams());
properties->set_lockable(device.IsLockable());
properties->set_supports_file(device.SupportsFile());
properties->set_supports_params(device.SupportsParams());
if (device.SupportsParams()) { if (device.SupportsParams()) {
for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { 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; map[key] = value;
} }
} }
for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { 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 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(); auto type_properties = device_types_info.add_properties();
type_properties->set_type(type); type_properties->set_type(type);
const auto device = device_factory.CreateDevice(type, 0, ""); const auto device = device_factory.CreateDevice(type, 0, "");
type_properties->set_allocated_properties(GetDeviceProperties(*device).release()); GetDeviceProperties(*device, *type_properties->mutable_properties());
} //NOSONAR The allocated memory is managed by protobuf }
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. // Start with 2 instead of 1. 1 was the removed SASI drive type.
int ordinal = 2; 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_revision(device.GetRevision());
pb_device.set_type(device.GetType()); 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 auto status = pb_device.mutable_status();
pb_device.set_allocated_status(status);
status->set_protected_(device.IsProtected()); status->set_protected_(device.IsProtected());
status->set_stopped(device.IsStopped()); status->set_stopped(device.IsStopped());
status->set_removed(device.IsRemoved()); status->set_removed(device.IsRemoved());
status->set_locked(device.IsLocked()); status->set_locked(device.IsLocked());
if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf if (device.SupportsParams()) {
for (const auto& [key, value] : device.GetParams()) { for (const auto& [key, value] : device.GetParams()) {
SetParam(pb_device, key, value); 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); const auto storage_device = dynamic_cast<const StorageDevice *>(&device);
if (storage_device != nullptr) { if (storage_device != nullptr) {
auto image_file = make_unique<PbImageFile>().release(); GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : "");
GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : "");
pb_device.set_allocated_file(image_file);
} }
} //NOSONAR The allocated memory is managed by protobuf }
bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const 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_name(filename);
image_file.set_type(device_factory.GetTypeForFile(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 error_code error;
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) {
image_file.set_size(st.st_size); image_file.set_size(file_size(p));
return true; 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, 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; return;
} }
string folder_pattern_lower = folder_pattern; string folder_pattern_lower;
transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower); ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower);
string file_pattern_lower = file_pattern; string file_pattern_lower;
transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower); ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower);
DIR *d = opendir(folder.c_str()); for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink);
if (d == nullptr) { it != recursive_directory_iterator(); it++) {
return; if (it.depth() > scan_depth) {
} it.disable_recursion_pending();
// 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()) {
continue; continue;
} }
string name_lower = dir->d_name; const string parent = it->path().parent_path().string();
if (!file_pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
if (dir->d_type == DT_DIR) { const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : "";
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);
}
if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) {
continue; continue;
} }
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) { if (!ValidateImageFile(it->path())) {
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(*image_file.get(), default_folder, filename)) { continue;
GetImageFile(*image_files_info.add_image_files(), default_folder, }
filename.substr(default_folder.length() + 1));
} 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 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); GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth);
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;
} }
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 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); server_info.mutable_image_files_info()->set_default_image_folder(default_folder);
image_files_info->set_default_image_folder(default_folder);
server_info.set_allocated_image_files_info(image_files_info.release());
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf 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) { 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, 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 no device list was provided in the command get information on all devices
if (!command.devices_size()) { if (!command.devices_size()) {
for (const auto& device : devices) { 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 // 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& [id, lun] : id_sets) {
for (const auto& d : devices) { for (const auto& d : devices) {
if (d->GetId() == id && d->GetLun() == lun) { 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); 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>(); GetVersionInfo(*server_info.mutable_version_info());
GetLogLevelInfo(*server_info.mutable_log_level_info());
GetAllDeviceTypeProperties(*device_types_info); GetDeviceTypesInfo(*server_info.mutable_device_types_info());
GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth);
result.set_status(true); GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
GetMappingInfo(*server_info.mutable_mapping_info());
return device_types_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, void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const
PbResult& result, const unordered_set<int>& reserved_ids, const string& current_log_level,
const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
{ {
auto server_info = make_unique<PbServerInfo>(); version_info.set_major_version(piscsi_major_version);
version_info.set_minor_version(piscsi_minor_version);
server_info->set_allocated_version_info(GetVersionInfo(result).release()); version_info.set_patch_version(piscsi_patch_version);
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;
} }
unique_ptr<PbVersionInfo> PiscsiResponse::GetVersionInfo(PbResult& result) const void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const
{ {
auto version_info = make_unique<PbVersionInfo>(); for (const auto& log_level : spdlog::level::level_string_views) {
log_level_info.add_log_levels(log_level.data());
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);
} }
log_level_info->set_current_log_level(current_log_level); log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data());
result.set_status(true);
return log_level_info;
} }
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 : GetNetworkInterfaces()) {
network_interfaces_info.add_name(network_interface);
for (const auto& network_interface : device_factory.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()) { 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"); CreateOperation(operation_info, DETACH, "Detach 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").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"); CreateOperation(operation_info, EJECT, "Eject 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").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) { 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(); AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
operation.release();
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) { 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(); AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
operation.release();
operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file"); operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release(); AddOperationParameter(*operation, "file", "Image file name", "", true);
operation.release();
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"); operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder");
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release(); AddOperationParameter(*operation, "folder", "Default image file folder name", "", true);
operation.release();
operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level"); operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level");
AddOperationParameter(*operation, "level", "New log level", "", true).release(); AddOperationParameter(*operation, "level", "New log level", "", true);
operation.release();
operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs"); operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs");
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release(); AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true);
operation.release();
operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot"); operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot");
auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release(); if (getuid()) {
parameter->add_permitted_values("piscsi"); AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } );
// System shutdown/reboot requires root permissions }
if (!getuid()) { else {
parameter->add_permitted_values("system"); // System shutdown/reboot requires root permissions
parameter->add_permitted_values("reboot"); AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } );
} }
operation.release();
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file"); operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release(); AddOperationParameter(*operation, "file", "Image file name", "", true);
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release(); AddOperationParameter(*operation, "size", "Image file size in bytes", "", true);
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file"); operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release(); AddOperationParameter(*operation, "file", "Image file name", "", true);
operation.release();
operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file"); operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); AddOperationParameter(*operation, "to", "Destination image file name", "", true);
operation.release();
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file"); operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file");
AddOperationParameter(*operation, "from", "Source image file name", "", true).release(); AddOperationParameter(*operation, "from", "Source image file name", "", true);
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release(); AddOperationParameter(*operation, "to", "Destination image file name", "", true);
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release(); AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
parameter->add_permitted_values("true");
parameter->add_permitted_values("false");
operation.release();
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file"); operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file");
AddOperationParameter(*operation, "file", "Image file name", "", true).release(); AddOperationParameter(*operation, "file", "Image file name", "", true);
operation.release();
operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable"); operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable");
AddOperationParameter(*operation, "file", "Image file name", "", true).release(); AddOperationParameter(*operation, "file", "Image file name", "", true);
operation.release();
operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid"); operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release(); AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true);
operation.release();
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release(); CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data");
result.set_status(true);
return operation_info;
} }
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 const string& description) const
{ {
auto meta_data = make_unique<PbOperationMetaData>(); PbOperationMetaData meta_data;
meta_data->set_server_side_name(PbOperation_Name(operation)); meta_data.set_server_side_name(PbOperation_Name(operation));
meta_data->set_description(description); meta_data.set_description(description);
int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index(); int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index();
(*operation_info.mutable_operations())[ordinal] = *meta_data.release(); (*operation_info.mutable_operations())[ordinal] = meta_data;
return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]); return &(*operation_info.mutable_operations())[ordinal];
} }
unique_ptr<PbOperationParameter> PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name,
const string& name, const string& description, const string& default_value, bool is_mandatory) const 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_name(name);
parameter->set_description(description); parameter->set_description(description);
parameter->set_default_value(default_value); parameter->set_default_value(default_value);
parameter->set_is_mandatory(is_mandatory); parameter->set_is_mandatory(is_mandatory);
for (const auto& permitted_value : permitted_values) {
return parameter; parameter->add_permitted_values(permitted_value);
}
} }
set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbResult& result, 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; bool has_device = false;
for (const auto& d : devices) { for (const auto& d : devices) {
if (d->GetId() == device.id() && d->GetLun() == device.unit()) { 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; has_device = true;
break; break;
} }
@ -513,7 +418,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
id_sets.clear(); id_sets.clear();
result.set_status(false); 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; break;
} }
@ -522,30 +427,45 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
return id_sets; 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 (path.filename().string().starts_with(".")) {
if ((dir->d_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK) return false;
|| dir->d_name[0] == '.') {
return "";
} }
const string filename = folder + "/" + dir->d_name; filesystem::path p(path);
const bool file_exists = exists(path(filename)); // Follow symlink
if (is_symlink(p)) {
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB p = read_symlink(p);
struct stat st; if (!exists(p)) {
stat(filename.c_str(), &st); spdlog::warn("Image file symlink '" + path.string() + "' is broken");
if (dir->d_type == DT_REG && file_exists && !st.st_size) { return false;
LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str()) }
return "";
} }
if (dir->d_type == DT_LNK && !file_exists) { if (is_directory(p) || (is_other(p) && !is_block_file(p))) {
LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str()) return false;
return "";
} }
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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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/device_factory.h"
#include "devices/primary_device.h" #include "devices/primary_device.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include <dirent.h>
#include <array>
#include <string> #include <string>
#include <span>
using namespace std; using namespace std;
using namespace filesystem;
using namespace piscsi_interface; using namespace piscsi_interface;
class PiscsiResponse class PiscsiResponse
{ {
using id_set = pair<int, int>;
public: public:
PiscsiResponse() = default; PiscsiResponse() = default;
~PiscsiResponse() = default; ~PiscsiResponse() = default;
bool GetImageFile(PbImageFile&, const string&, const string&) const; bool GetImageFile(PbImageFile&, const string&, const string&) const;
unique_ptr<PbImageFilesInfo> GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const; void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
unique_ptr<PbReservedIdsInfo> GetReservedIds(PbResult&, const unordered_set<int>&) const; void GetReservedIds(PbReservedIdsInfo&, const unordered_set<int>&) const;
void GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbServerInfo&, const string&) 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; void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const; void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const; void GetVersionInfo(PbVersionInfo&) const;
unique_ptr<PbServerInfo> GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const unordered_set<int>&, void GetServerInfo(PbServerInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&, const unordered_set<int>&,
const string&, const string&, const string&, const string&, int) const; const string&, const string&, const string&, int) const;
unique_ptr<PbNetworkInterfacesInfo> GetNetworkInterfacesInfo(PbResult&) const; void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
unique_ptr<PbMappingInfo> GetMappingInfo(PbResult&) const; void GetMappingInfo(PbMappingInfo&) const;
unique_ptr<PbLogLevelInfo> GetLogLevelInfo(PbResult&, const string&) const; void GetLogLevelInfo(PbLogLevelInfo&) const;
unique_ptr<PbOperationInfo> GetOperationInfo(PbResult&, int) const; void GetOperationInfo(PbOperationInfo&, int) const;
private: 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 GetDevice(const Device&, PbDevice&, const string&) const;
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const;
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const; void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const; void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const; void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const;
unique_ptr<PbOperationMetaData> CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const; PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
unique_ptr<PbOperationParameter> AddOperationParameter(PbOperationMetaData&, const string&, const string&, void AddOperationParameter(PbOperationMetaData&, const string&, const string&,
const string& = "", bool = false) const; const string& = "", bool = false, const vector<string>& = EMPTY_VECTOR) const;
set<id_set> MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&) 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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/piscsi_util.h"
#include "shared/protobuf_serializer.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "command_context.h" #include "command_context.h"
#include "localizer.h"
#include "piscsi_service.h" #include "piscsi_service.h"
#include <spdlog/spdlog.h>
#include <sys/socket.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h>
#include <csignal> #include <csignal>
#include <cassert>
using namespace piscsi_interface;
using namespace piscsi_util; 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) { 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); service_socket = socket(PF_INET, SOCK_STREAM, 0);
if (service_socket == -1) { if (service_socket == -1) {
LOGERROR("Unable to create socket") return "Unable to create service socket: " + string(strerror(errno));
return false;
} }
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_family = PF_INET;
server.sin_port = htons((uint16_t)port); server.sin_port = htons((uint16_t)port);
server.sin_addr.s_addr = htonl(INADDR_ANY); 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
// Allow address reuse Stop();
if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { return "Port " + to_string(port) + " is in use, is piscsi already running?";
return false;
} }
signal(SIGPIPE, SIG_IGN); if (listen(service_socket, 2) == -1) {
Stop();
if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) { return "Can't listen to service socket: " + string(strerror(errno));
cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl;
return false;
} }
execute = cb; execute = cb;
monthread = thread(&PiscsiService::Execute, this); return "";
monthread.detach(); }
// Interrupt handler settings void PiscsiService::Start()
return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR {
&& signal(SIGTERM, KillHandler) != SIG_ERR; 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 void PiscsiService::Execute() const
{ {
#ifdef __linux__ #ifdef __linux__
// Scheduler Settings // Run this thread with very low priority
sched_param schedparam; sched_param schedparam = { .sched_priority = 0 };
schedparam.sched_priority = 0;
sched_setscheduler(0, SCHED_IDLE, &schedparam); sched_setscheduler(0, SCHED_IDLE, &schedparam);
#endif #endif
// Set the affinity to a specific processor core // TODO Accept more than one command instead of closing the socket after a single command
FixCpu(2); while (service_socket != -1) {
const int fd = accept(service_socket, nullptr, nullptr);
// Wait for the execution to start if (fd != -1) {
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000}; ExecuteCommand(fd);
while (!running) { close(fd);
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);
}
} }
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 CommandContext context(fd);
sockaddr client = {}; try {
socklen_t socklen = sizeof(client); if (context.ReadCommand()) {
const int fd = accept(service_socket, &client, &socklen); execute(context);
if (fd == -1) { }
throw io_exception("accept() failed");
} }
catch(const io_exception& e) {
spdlog::warn(e.what());
PbCommand command; // Try to return an error message (this may fail if the exception was caused when returning the actual result)
PbResult result;
// Read magic string result.set_msg(e.what());
vector<byte> magic(6); try {
const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic); context.WriteResult(result);
if (!bytes_read) { }
return command; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "generated/piscsi_interface.pb.h"
#include <functional> #include <functional>
#include <thread> #include <thread>
#include <string>
class CommandContext; class CommandContext;
using namespace std; using namespace std;
using namespace piscsi_interface;
class PiscsiService class PiscsiService
{ {
using callback = function<bool(const CommandContext&, piscsi_interface::PbCommand&)>; using callback = function<bool(CommandContext&)>;
callback execute;
int service_socket = -1;
thread monthread;
static inline volatile bool running = false;
public: public:
PiscsiService() = default; PiscsiService() = default;
~PiscsiService() = default; ~PiscsiService() = default;
bool Init(const callback&, int); string Init(const callback&, int);
void Cleanup() const; void Start();
void Stop();
bool IsRunning() const { return running; } bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); }
void SetRunning(bool b) const { running = b; }
private: private:
void Execute() const; 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 // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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 // The available device types
enum PbDeviceType { enum PbDeviceType {
UNDEFINED = 0; 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]; SAHD = 1 [deprecated = true];
// Non-removable SCSI drive // Non-removable SCSI drive
SCHD = 2; SCHD = 2;

View File

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

View File

@ -3,13 +3,12 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021-2022 Uwe Seimet // Copyright (C) 2021-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
#pragma once #pragma once
#include "shared/protobuf_serializer.h"
#include "generated/piscsi_interface.pb.h" #include "generated/piscsi_interface.pb.h"
#include "scsictl_display.h" #include "scsictl_display.h"
#include <string> #include <string>
@ -26,40 +25,38 @@ public:
: command(command), hostname(hostname), port(port) {} : command(command), hostname(hostname), port(port) {}
~ScsictlCommands() = default; ~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(); bool CommandDevicesInfo();
private: private:
bool CommandLogLevel(const string&); bool CommandLogLevel(string_view);
bool CommandReserveIds(const string&); bool CommandReserveIds(string_view);
bool CommandCreateImage(const string&); bool CommandCreateImage(string_view);
bool CommandDeleteImage(const string&); bool CommandDeleteImage(string_view);
bool CommandRenameImage(const string&); bool CommandRenameImage(string_view);
bool CommandCopyImage(const string&); bool CommandCopyImage(string_view);
bool CommandDefaultImageFolder(const string&); bool CommandDefaultImageFolder(string_view);
bool CommandDeviceInfo(); bool CommandDeviceInfo();
bool CommandDeviceTypesInfo(); bool CommandDeviceTypesInfo();
bool CommandVersionInfo(); bool CommandVersionInfo();
bool CommandServerInfo(); bool CommandServerInfo();
bool CommandDefaultImageFilesInfo(); bool CommandDefaultImageFilesInfo();
bool CommandImageFileInfo(const string&); bool CommandImageFileInfo(string_view);
bool CommandNetworkInterfacesInfo(); bool CommandNetworkInterfacesInfo();
bool CommandLogLevelInfo(); bool CommandLogLevelInfo();
bool CommandReservedIdsInfo(); bool CommandReservedIdsInfo();
bool CommandMappingInfo(); bool CommandMappingInfo();
bool CommandOperationInfo(); bool CommandOperationInfo();
bool SendCommand(); bool SendCommand();
bool EvaluateParams(string_view, const string&, const string&);
static bool ResolveHostName(const string&, sockaddr_in *);
ProtobufSerializer serializer;
PbCommand& command; PbCommand& command;
string hostname; string hostname;
int port; int port;
PbResult result; 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 "controllers/scsi_controller.h"
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "shared/protobuf_util.h" #include "shared/protobuf_util.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "shared/piscsi_version.h" #include "shared/piscsi_version.h"
#include "generated/piscsi_interface.pb.h"
#include "scsictl/scsictl_parser.h" #include "scsictl/scsictl_parser.h"
#include "scsictl/scsictl_commands.h" #include "scsictl/scsictl_commands.h"
#include "scsictl/scsictl_core.h" #include "scsictl/scsictl_core.h"
@ -30,27 +30,27 @@ using namespace protobuf_util;
void ScsiCtl::Banner(const vector<char *>& args) const void ScsiCtl::Banner(const vector<char *>& args) const
{ {
if (args.size() < 2) { if (args.size() < 2) {
cout << piscsi_util::Banner("(Controller App)"); 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] "
cout << "\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] "
cout << "[-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] "
cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "; << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "
cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "; << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"
cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"; << " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "},"
cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n"; << " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n"
cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"; << " 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"; << " 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"; << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"
cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"; << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"
cout << " FILE|PARAM := image file path or device-specific parameter\n"; << " FILE|PARAM := image file path or device-specific parameter\n"
cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"; << " IMAGE_FOLDER := default location for image files, default is '~/images'\n"
cout << " HOST := piscsi host to connect to, default is 'localhost'\n"; << " HOST := piscsi host to connect to, default is 'localhost'\n"
cout << " PORT := piscsi port to connect to, default is 6868\n"; << " PORT := piscsi port to connect to, default is 6868\n"
cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; << " 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"; << " 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"; << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"
cout << "Usage: " << args[0] << " -l\n"; << "Usage: " << args[0] << " -l\n"
cout << " Print device list.\n" << flush; << " Print device list.\n" << flush;
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
} }
@ -77,10 +77,7 @@ int ScsiCtl::run(const vector<char *>& args) const
string token; string token;
bool list = false; bool list = false;
const char *locale = setlocale(LC_MESSAGES, ""); string locale = GetLocale();
if (locale == nullptr || !strcmp(locale, "C")) {
locale = "en";
}
opterr = 1; opterr = 1;
int opt; 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) { "e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
switch (opt) { switch (opt) {
case 'i': 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; cerr << "Error: " << error << endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@ -216,7 +213,7 @@ int ScsiCtl::run(const vector<char *>& args) const
break; break;
case 'v': case 'v':
cout << "scsictl version: " << piscsi_get_version_string() << endl; cout << "scsictl version: " << piscsi_get_version_string() << '\n';
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
break; break;
@ -263,7 +260,7 @@ int ScsiCtl::run(const vector<char *>& args) const
bool status; bool status;
try { try {
// Listing devices is a special case (rasctl backwards compatibility) // Listing devices is a special case (legacy rasctl backwards compatibility)
if (list) { if (list) {
command.clear_devices(); command.clear_devices();
command.set_operation(DEVICES_INFO); command.set_operation(DEVICES_INFO);

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@
// SCSI Target Emulator PiSCSI // SCSI Target Emulator PiSCSI
// for Raspberry Pi // 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[]) int main(int argc, char *argv[])
{ {
const vector<char *> args(argv, argv + argc); vector<char *> args(argv, argv + argc);
return ScsiDump().run(args); return ScsiDump().run(args);
} }

View File

@ -5,8 +5,8 @@
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.
// Copyright (C) 2016-2020 GIMONS // Copyright (C) 2016-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
// Copyright (C) 2022 akuker // Copyright (C) 2022 akuker
// Copyright (C) 2022-2023 Uwe Seimet
// //
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@ -17,10 +17,11 @@
#include "hal/gpiobus.h" #include "hal/gpiobus.h"
#include "hal/gpiobus_factory.h" #include "hal/gpiobus_factory.h"
#include "hal/systimer.h" #include "hal/systimer.h"
#include "shared/log.h" #include "controllers/controller_manager.h"
#include "shared/piscsi_exceptions.h" #include "shared/piscsi_exceptions.h"
#include "shared/piscsi_util.h" #include "shared/piscsi_util.h"
#include "shared/piscsi_version.h" #include <spdlog/spdlog.h>
#include <filesystem>
#include <chrono> #include <chrono>
#include <csignal> #include <csignal>
#include <cstddef> #include <cstddef>
@ -28,10 +29,10 @@
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
using namespace std; using namespace std;
using namespace filesystem;
using namespace spdlog; using namespace spdlog;
using namespace scsi_defs; using namespace scsi_defs;
using namespace piscsi_util; using namespace piscsi_util;
@ -50,17 +51,18 @@ void ScsiDump::KillHandler(int)
exit(EXIT_SUCCESS); 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)"); cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") { 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" 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" << " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
<< " LUN is the optional target device LUN (0-7). Default is 0.\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" << " BID is the PiSCSI board ID (0-7). Default is 7.\n"
<< " FILE is the dump file path.\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" << " bytes. Default is 1 MiB.\n"
<< " -v Enable verbose logging.\n" << " -v Enable verbose logging.\n"
<< " -r Restore instead of dump.\n" << " -r Restore instead of dump.\n"
@ -86,7 +88,7 @@ bool ScsiDump::Init() const
return bus != nullptr; return bus != nullptr;
} }
void ScsiDump::ParseArguments(const vector<char*>& args) void ScsiDump::ParseArguments(span<char *> args)
{ {
int opt; int opt;
@ -107,13 +109,13 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
case 's': case 's':
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) { 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; break;
case 't': { case 't': {
const string error = ProcessId(optarg, 8, target_id, target_lun); const string error = ProcessId(optarg, target_id, target_lun);
if (!error.empty()) { if (!error.empty()) {
throw parser_exception(error); throw parser_exception(error);
} }
@ -149,7 +151,7 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
void ScsiDump::WaitPhase(phase_t phase) const 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) // Timeout (3000ms)
const uint32_t now = SysTimer::GetTimerLow(); 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 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(); 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 + 2]) << 8) |
static_cast<uint32_t>(buffer[sector_size_offset + 3]); 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) 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)) { if (!Banner(args)) {
return EXIT_SUCCESS; return EXIT_SUCCESS;
@ -436,12 +438,12 @@ int ScsiDump::DumpRestore()
if (restore) { if (restore) {
cout << "Starting restore\n" << flush; 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; off_t size;
if (struct stat st; !stat(filename.c_str(), &st)) { try {
size = st.st_size; size = file_size(path(filename));
} else { }
throw parser_exception("Can't determine file size"); catch (const filesystem_error& e) {
throw parser_exception(string("Can't determine file size: ") + e.what());
} }
cout << "Restore file size: " << size << " bytes\n"; cout << "Restore file size: " << size << " bytes\n";
@ -455,11 +457,11 @@ int ScsiDump::DumpRestore()
} }
// Dump by buffer size // 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; 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; int i;
for (i = 0; i < dnum; i++) { for (i = 0; i < dnum; i++) {
@ -501,16 +503,16 @@ int ScsiDump::DumpRestore()
cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush; 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 << divider_str << "\n";
cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes [" cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
<< to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n"; << 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 << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration) cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
<< " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024) << " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
<< " KiB per second)\n"; << " KiB per second)\n";
cout << divider_str << "\n"; cout << divider_str << "\n";
@ -554,8 +556,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
cout << "Revision: " << str.data() << "\n" << flush; cout << "Revision: " << str.data() << "\n" << flush;
inq_info.revision = string(str.data()); inq_info.revision = string(str.data());
if (auto type = static_cast<device_type>(buffer[0]); 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) { 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"); 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(); RequestSense();
const auto [capacity, sector_size] = ReadCapacity(); const auto [capacity, sector_size] = ReadCapacity();
inq_info.capacity = capacity; inq_info.capacity = capacity;
inq_info.sector_size = sector_size; inq_info.sector_size = sector_size;
cout << "Sectors: " << capacity << "\n" cout << "Sectors: " << capacity << "\n"
<< "Sector size: " << sector_size << " bytes\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) void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
{ {
string prop_filename = filename + ".properties"; const string prop_filename = filename + ".properties";
string prop_str; stringstream prop_stream;
stringstream prop_stream(prop_str);
prop_stream << "{" << endl; prop_stream << "{" << endl;
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl; prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl;
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl; prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl;
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << 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; prop_stream << "}" << endl;
FILE* fp = fopen(prop_filename.c_str(), "w"); FILE* fp = fopen(prop_filename.c_str(), "w");
if (fp) { if (fp) {
fputs(prop_stream.str().c_str(), fp); fputs(prop_stream.str().c_str(), fp);
} else { } else {
LOGWARN("Unable to open output file %s", prop_filename.c_str()) spdlog::warn("Unable to open output file '" + prop_filename + "'");
return; return;
} }
fclose(fp); fclose(fp);
} }

View File

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

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