mirror of https://github.com/akuker/RASCSI.git
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:
parent
c1f6f3ffea
commit
41bdcd4aed
40
cpp/Makefile
40
cpp/Makefile
|
@ -7,8 +7,6 @@
|
|||
CROSS_COMPILE =
|
||||
|
||||
CXX = $(CROSS_COMPILE)g++
|
||||
AR = $(CROSS_COMPILE)ar
|
||||
RANLIB = $(CROSS_COMPILE)ranlib
|
||||
|
||||
## DEBUG=1 : A Debug build includes the debugger symbols
|
||||
## and disables compiler optimization. Typically,
|
||||
|
@ -16,30 +14,21 @@ RANLIB = $(CROSS_COMPILE)ranlib
|
|||
DEBUG ?= 0
|
||||
ifeq ($(DEBUG), 1)
|
||||
# Debug compiler flags
|
||||
CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG
|
||||
BUILD_TYPE = Debug
|
||||
CXXFLAGS += -Og -g -Wall -Wextra -DDEBUG
|
||||
else
|
||||
# Release compiler flags
|
||||
CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG
|
||||
BUILD_TYPE = Release
|
||||
endif
|
||||
ifeq ("$(shell uname -s)","Linux")
|
||||
# -Wno-psabi might not work on non-Linux platforms
|
||||
CXXFLAGS += -Wno-psabi
|
||||
endif
|
||||
|
||||
# Depending on the GCC version the compilation flags differ
|
||||
GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10)
|
||||
|
||||
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
|
||||
CXXFLAGS += -std=c++20 -iquote . -D_FILE_OFFSET_BITS=64 -DFMT_HEADER_ONLY -DSPDLOG_FMT_EXTERNAL -MD -MP
|
||||
|
||||
## EXTRA_FLAGS : Can be used to pass special purpose flags
|
||||
CXXFLAGS += $(EXTRA_FLAGS)
|
||||
|
||||
ifeq "$(GCCVERSION10)" "1"
|
||||
CXXFLAGS += -DFMT_HEADER_ONLY
|
||||
endif
|
||||
|
||||
|
||||
## CONNECT_TYPE=FULLSPEC : Specify the type of PiSCSI board type
|
||||
## that you are using. The typical options are
|
||||
|
@ -86,12 +75,12 @@ SRC_PROTOC = piscsi_interface.proto
|
|||
SRC_GENERATED = $(GENERATED_DIR)/piscsi_interface.pb.cpp
|
||||
|
||||
SRC_PROTOBUF = \
|
||||
shared/protobuf_util.cpp \
|
||||
shared/protobuf_serializer.cpp
|
||||
shared/protobuf_util.cpp
|
||||
|
||||
SRC_SHARED = \
|
||||
shared/piscsi_version.cpp \
|
||||
shared/piscsi_util.cpp
|
||||
shared/piscsi_util.cpp \
|
||||
shared/network_util.cpp
|
||||
|
||||
SRC_PISCSI_CORE = $(shell find ./piscsi -name '*.cpp')
|
||||
SRC_PISCSI_CORE += $(shell find ./controllers -name '*.cpp')
|
||||
|
@ -167,7 +156,7 @@ $(SRC_GENERATED) : $(SRC_PROTOC)
|
|||
protoc --cpp_out=$(GENERATED_DIR) $(SRC_PROTOC)
|
||||
mv $(GENERATED_DIR)/piscsi_interface.pb.cc $@
|
||||
|
||||
$(OBJ_GENERATED) : $(SRC_GENERATED)
|
||||
$(OBJ_GENERATED) : $(SRC_GENERATED) | $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
## Build Targets:
|
||||
|
@ -180,10 +169,11 @@ $(OBJ_GENERATED) : $(SRC_GENERATED)
|
|||
## Note that you have to run 'make clean' before switching
|
||||
## between coverage and non-coverage builds.
|
||||
.DEFAULT_GOAL := all
|
||||
.PHONY: all ALL docs test coverage lcov
|
||||
all: $(BIN_ALL) docs
|
||||
.PHONY: all docs test coverage lcov
|
||||
|
||||
test: $(BINDIR)/$(PISCSI_TEST)
|
||||
all: $(SRC_GENERATED) $(BIN_ALL) docs
|
||||
|
||||
test: $(SRC_GENERATED) $(BINDIR)/$(PISCSI_TEST)
|
||||
$(BINDIR)/$(PISCSI_TEST)
|
||||
|
||||
coverage: CXXFLAGS += --coverage
|
||||
|
@ -198,10 +188,10 @@ docs: $(DOC_DIR)/piscsi_man_page.txt $(DOC_DIR)/scsictl_man_page.txt $(DOC_DIR)/
|
|||
|
||||
$(SRC_PISCSI_CORE) $(SRC_SCSICTL_CORE) : $(OBJ_GENERATED)
|
||||
|
||||
$(BINDIR)/$(PISCSI): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs
|
||||
$(BINDIR)/$(PISCSI): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_PISCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf
|
||||
|
||||
$(BINDIR)/$(SCSICTL): $(SRC_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(BINDIR)/$(SCSICTL): $(OBJ_GENERATED) $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SCSICTL_CORE) $(OBJ_SCSICTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lprotobuf
|
||||
|
||||
$(BINDIR)/$(SCSIDUMP): $(OBJ_SCSIDUMP) $(OBJ_SHARED) | $(BINDIR)
|
||||
|
@ -213,8 +203,8 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(OBJ_SHARED) | $(BINDIR)
|
|||
$(BINDIR)/$(SCSILOOP): $(OBJ_SHARED) $(OBJ_SCSILOOP) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) -o $@ $(OBJ_SHARED) $(OBJ_SCSILOOP)
|
||||
|
||||
$(BINDIR)/$(PISCSI_TEST): $(SRC_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
|
||||
$(BINDIR)/$(PISCSI_TEST): $(OBJ_GENERATED) $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SCSICTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(TEST_WRAPS) -o $@ $(OBJ_PISCSI_CORE) $(OBJ_SCSICTL_CORE) $(OBJ_PISCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(OBJ_GENERATED) -lpthread -lpcap -lprotobuf -lgmock -lgtest
|
||||
|
||||
# Phony rules for building individual utilities
|
||||
.PHONY: $(PISCSI) $(SCSICTL) $(SCSIDUMP) $(SCSIMON) $(PISCSI_TEST) $(SCSILOOP)
|
||||
|
|
|
@ -3,16 +3,22 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "abstract_controller.h"
|
||||
#include <ranges>
|
||||
|
||||
using namespace scsi_defs;
|
||||
|
||||
AbstractController::AbstractController(BUS& bus, int target_id, int max_luns) : bus(bus), target_id(target_id), max_luns(max_luns)
|
||||
{
|
||||
device_logger.SetIdAndLun(target_id, -1);
|
||||
}
|
||||
|
||||
void AbstractController::AllocateCmd(size_t size)
|
||||
{
|
||||
if (size > ctrl.cmd.size()) {
|
||||
|
@ -40,9 +46,8 @@ unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
|
|||
{
|
||||
unordered_set<shared_ptr<PrimaryDevice>> devices;
|
||||
|
||||
for (const auto& [id, lun] : luns) {
|
||||
devices.insert(lun);
|
||||
}
|
||||
// "luns | views:values" is not supported by the bullseye compiler
|
||||
ranges::transform(luns, inserter(devices, devices.begin()), [] (const auto& l) { return l.second; } );
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
@ -56,100 +61,66 @@ void AbstractController::Reset()
|
|||
{
|
||||
SetPhase(phase_t::busfree);
|
||||
|
||||
ctrl.status = status::GOOD;
|
||||
ctrl.message = 0x00;
|
||||
ctrl.blocks = 0;
|
||||
ctrl.next = 0;
|
||||
ctrl.offset = 0;
|
||||
ctrl.length = 0;
|
||||
ctrl = {};
|
||||
|
||||
SetByteTransfer(false);
|
||||
|
||||
// Reset all LUNs
|
||||
for (const auto& [lun, device] : luns) {
|
||||
for (const auto& [_, device] : luns) {
|
||||
device->Reset();
|
||||
}
|
||||
|
||||
GetBus().Reset();
|
||||
}
|
||||
|
||||
void AbstractController::ProcessPhase()
|
||||
void AbstractController::ProcessOnController(int id_data)
|
||||
{
|
||||
switch (GetPhase()) {
|
||||
case phase_t::busfree:
|
||||
BusFree();
|
||||
break;
|
||||
device_logger.SetIdAndLun(GetTargetId(), -1);
|
||||
|
||||
case phase_t::selection:
|
||||
Selection();
|
||||
break;
|
||||
const int initiator_id = ExtractInitiatorId(id_data);
|
||||
if (initiator_id != UNKNOWN_INITIATOR_ID) {
|
||||
LogTrace("++++ Starting processing for initiator ID " + to_string(initiator_id));
|
||||
}
|
||||
else {
|
||||
LogTrace("++++ Starting processing for unknown initiator ID");
|
||||
}
|
||||
|
||||
case phase_t::dataout:
|
||||
DataOut();
|
||||
break;
|
||||
|
||||
case phase_t::datain:
|
||||
DataIn();
|
||||
break;
|
||||
|
||||
case phase_t::command:
|
||||
Command();
|
||||
break;
|
||||
|
||||
case phase_t::status:
|
||||
Status();
|
||||
break;
|
||||
|
||||
case phase_t::msgout:
|
||||
MsgOut();
|
||||
break;
|
||||
|
||||
case phase_t::msgin:
|
||||
MsgIn();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
break;
|
||||
while (Process(initiator_id)) {
|
||||
// Handle bus phases until the bus is free for the next command
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
|
||||
{
|
||||
if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
|
||||
const int lun = device->GetLun();
|
||||
|
||||
if (lun < 0 || lun >= GetMaxLuns() || HasDeviceForLun(lun) || device->GetController()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
luns[device->GetLun()] = device;
|
||||
device->SetController(shared_from_this());
|
||||
luns[lun] = device;
|
||||
device->SetController(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AbstractController::RemoveDevice(shared_ptr<PrimaryDevice> device)
|
||||
bool AbstractController::RemoveDevice(PrimaryDevice& device)
|
||||
{
|
||||
device->SetController(nullptr);
|
||||
device.CleanUp();
|
||||
|
||||
return luns.erase(device->GetLun()) == 1;
|
||||
return luns.erase(device.GetLun()) == 1;
|
||||
}
|
||||
|
||||
bool AbstractController::HasDeviceForLun(int lun) const
|
||||
{
|
||||
return luns.find(lun) != luns.end();
|
||||
return luns.contains(lun);
|
||||
}
|
||||
|
||||
int AbstractController::ExtractInitiatorId(int id_data) const
|
||||
{
|
||||
int initiator_id = UNKNOWN_INITIATOR_ID;
|
||||
|
||||
if (int tmp = id_data - (1 << target_id); tmp) {
|
||||
initiator_id = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
tmp >>= 1;
|
||||
if (tmp) {
|
||||
initiator_id++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (const int id_data_without_target = id_data - (1 << target_id); id_data_without_target) {
|
||||
return static_cast<int>(log2(id_data_without_target & -id_data_without_target));
|
||||
}
|
||||
|
||||
return initiator_id;
|
||||
return UNKNOWN_INITIATOR_ID;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Base class for device controllers
|
||||
//
|
||||
|
@ -14,17 +14,19 @@
|
|||
#include "shared/scsi.h"
|
||||
#include "hal/bus.h"
|
||||
#include "phase_handler.h"
|
||||
#include "controller_manager.h"
|
||||
#include "devices/device_logger.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class PrimaryDevice;
|
||||
|
||||
class AbstractController : public PhaseHandler, public enable_shared_from_this<AbstractController>
|
||||
class AbstractController : public PhaseHandler
|
||||
{
|
||||
public:
|
||||
|
||||
|
@ -37,19 +39,19 @@ public:
|
|||
RESTART_PI
|
||||
};
|
||||
|
||||
AbstractController(shared_ptr<ControllerManager> controller_manager, int target_id, int max_luns)
|
||||
: controller_manager(controller_manager), target_id(target_id), max_luns(max_luns) {}
|
||||
AbstractController(BUS&, int, int);
|
||||
~AbstractController() override = default;
|
||||
|
||||
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||
scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0;
|
||||
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::no_additional_sense_information,
|
||||
scsi_defs::status = scsi_defs::status::check_condition) = 0;
|
||||
virtual void Reset();
|
||||
virtual int GetInitiatorId() const = 0;
|
||||
|
||||
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
|
||||
virtual int GetEffectiveLun() const = 0;
|
||||
|
||||
virtual void ScheduleShutdown(piscsi_shutdown_mode) = 0;
|
||||
void ScheduleShutdown(piscsi_shutdown_mode mode) { shutdown_mode = mode; }
|
||||
piscsi_shutdown_mode GetShutdownMode() const { return shutdown_mode; }
|
||||
|
||||
int GetTargetId() const { return target_id; }
|
||||
int GetMaxLuns() const { return max_luns; }
|
||||
|
@ -58,52 +60,59 @@ public:
|
|||
unordered_set<shared_ptr<PrimaryDevice>> GetDevices() const;
|
||||
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
|
||||
bool AddDevice(shared_ptr<PrimaryDevice>);
|
||||
bool RemoveDevice(shared_ptr<PrimaryDevice>);
|
||||
bool RemoveDevice(PrimaryDevice&);
|
||||
bool HasDeviceForLun(int) const;
|
||||
int ExtractInitiatorId(int) const;
|
||||
void ProcessOnController(int);
|
||||
|
||||
// TODO These should probably be extracted into a new TransferHandler class
|
||||
void AllocateBuffer(size_t);
|
||||
vector<uint8_t>& GetBuffer() { return ctrl.buffer; }
|
||||
scsi_defs::status GetStatus() const { return ctrl.status; }
|
||||
auto& GetBuffer() { return ctrl.buffer; }
|
||||
auto GetStatus() const { return ctrl.status; }
|
||||
void SetStatus(scsi_defs::status s) { ctrl.status = s; }
|
||||
uint32_t GetLength() const { return ctrl.length; }
|
||||
auto GetLength() const { return ctrl.length; }
|
||||
void SetLength(uint32_t l) { ctrl.length = l; }
|
||||
uint32_t GetBlocks() const { return ctrl.blocks; }
|
||||
bool HasBlocks() const { return ctrl.blocks; }
|
||||
void SetBlocks(uint32_t b) { ctrl.blocks = b; }
|
||||
void DecrementBlocks() { --ctrl.blocks; }
|
||||
uint64_t GetNext() const { return ctrl.next; }
|
||||
auto GetNext() const { return ctrl.next; }
|
||||
void SetNext(uint64_t n) { ctrl.next = n; }
|
||||
void IncrementNext() { ++ctrl.next; }
|
||||
int GetMessage() const { return ctrl.message; }
|
||||
void SetMessage(int m) { ctrl.message = m; }
|
||||
vector<int>& GetCmd() { return ctrl.cmd; }
|
||||
int GetCmd(int index) const { return ctrl.cmd[index]; }
|
||||
auto GetCmd() const { return ctrl.cmd; }
|
||||
int GetCmdByte(int index) const { return ctrl.cmd[index]; }
|
||||
bool IsByteTransfer() const { return is_byte_transfer; }
|
||||
void SetByteTransfer(bool);
|
||||
uint32_t GetBytesToTransfer() const { return bytes_to_transfer; }
|
||||
auto GetBytesToTransfer() const { return bytes_to_transfer; }
|
||||
void SetBytesToTransfer(uint32_t b) { bytes_to_transfer = b; }
|
||||
|
||||
protected:
|
||||
|
||||
shared_ptr<ControllerManager> GetControllerManager() const { return controller_manager.lock(); }
|
||||
inline BUS& GetBus() const { return controller_manager.lock()->GetBus(); }
|
||||
BUS& GetBus() const { return bus; }
|
||||
|
||||
scsi_defs::scsi_command GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
|
||||
auto GetOpcode() const { return static_cast<scsi_defs::scsi_command>(ctrl.cmd[0]); }
|
||||
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
|
||||
|
||||
void ProcessPhase();
|
||||
|
||||
void AllocateCmd(size_t);
|
||||
|
||||
void SetCmdByte(int index, int value) { ctrl.cmd[index] = value; }
|
||||
|
||||
// TODO These should probably be extracted into a new TransferHandler class
|
||||
bool HasValidLength() const { return ctrl.length != 0; }
|
||||
int GetOffset() const { return ctrl.offset; }
|
||||
void ResetOffset() { ctrl.offset = 0; }
|
||||
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; }
|
||||
|
||||
void LogTrace(const string& s) const { device_logger.Trace(s); }
|
||||
void LogDebug(const string& s) const { device_logger.Debug(s); }
|
||||
void LogInfo(const string& s) const { device_logger.Info(s); }
|
||||
void LogWarn(const string& s) const { device_logger.Warn(s); }
|
||||
void LogError(const string& s) const { device_logger.Error(s); }
|
||||
|
||||
private:
|
||||
|
||||
int ExtractInitiatorId(int) const;
|
||||
|
||||
using ctrl_t = struct _ctrl_t {
|
||||
// Command data, dynamically resized if required
|
||||
vector<int> cmd = vector<int>(16);
|
||||
|
@ -121,7 +130,9 @@ private:
|
|||
|
||||
ctrl_t ctrl = {};
|
||||
|
||||
weak_ptr<ControllerManager> controller_manager;
|
||||
BUS& bus;
|
||||
|
||||
DeviceLogger device_logger;
|
||||
|
||||
// Logical units of this controller mapped to their LUN numbers
|
||||
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
|
||||
|
@ -132,4 +143,6 @@ private:
|
|||
|
||||
bool is_byte_transfer = false;
|
||||
uint32_t bytes_to_transfer = 0;
|
||||
|
||||
piscsi_shutdown_mode shutdown_mode = piscsi_shutdown_mode::NONE;
|
||||
};
|
||||
|
|
|
@ -3,29 +3,34 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "devices/device_factory.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "scsi_controller.h"
|
||||
#include "controller_manager.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
|
||||
shared_ptr<ScsiController> ControllerManager::CreateScsiController(BUS& bus, int id) const
|
||||
{
|
||||
auto controller = FindController(id);
|
||||
if (controller != nullptr) {
|
||||
return make_shared<ScsiController>(bus, id);
|
||||
}
|
||||
|
||||
bool ControllerManager::AttachToController(BUS& bus, int id, shared_ptr<PrimaryDevice> device)
|
||||
{
|
||||
if (auto controller = FindController(id); controller != nullptr) {
|
||||
if (controller->HasDeviceForLun(device->GetLun())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return controller->AddDevice(device);
|
||||
}
|
||||
|
||||
// If there is no LUN yet the first LUN must be LUN 0
|
||||
if (device->GetLun() == 0) {
|
||||
controller = make_shared<ScsiController>(shared_from_this(), id);
|
||||
|
||||
if (controller->AddDevice(device)) {
|
||||
// If this is LUN 0 create a new controller
|
||||
if (!device->GetLun()) {
|
||||
if (auto controller = CreateScsiController(bus, id); controller->AddDevice(device)) {
|
||||
controllers[id] = controller;
|
||||
|
||||
return true;
|
||||
|
@ -35,20 +40,37 @@ bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice>
|
|||
return false;
|
||||
}
|
||||
|
||||
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
|
||||
bool ControllerManager::DeleteController(const AbstractController& controller)
|
||||
{
|
||||
return controllers.erase(controller->GetTargetId()) == 1;
|
||||
}
|
||||
|
||||
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
|
||||
{
|
||||
for (const auto& [id, controller] : controllers) {
|
||||
if (data & (1 << controller->GetTargetId())) {
|
||||
return controller;
|
||||
}
|
||||
for (const auto& device : controller.GetDevices()) {
|
||||
device->CleanUp();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return controllers.erase(controller.GetTargetId()) == 1;
|
||||
}
|
||||
|
||||
void ControllerManager::DeleteAllControllers()
|
||||
{
|
||||
unordered_set<shared_ptr<AbstractController>> values;
|
||||
ranges::transform(controllers, inserter(values, values.begin()), [] (const auto& controller) { return controller.second; } );
|
||||
|
||||
for (const auto& controller : values) {
|
||||
DeleteController(*controller);
|
||||
}
|
||||
|
||||
assert(controllers.empty());
|
||||
}
|
||||
|
||||
AbstractController::piscsi_shutdown_mode ControllerManager::ProcessOnController(int id_data) const
|
||||
{
|
||||
if (const auto& it = ranges::find_if(controllers, [&] (const auto& c) { return (id_data & (1 << c.first)); } );
|
||||
it != controllers.end()) {
|
||||
(*it).second->ProcessOnController(id_data);
|
||||
|
||||
return (*it).second->GetShutdownMode();
|
||||
}
|
||||
|
||||
return AbstractController::piscsi_shutdown_mode::NONE;
|
||||
}
|
||||
|
||||
shared_ptr<AbstractController> ControllerManager::FindController(int target_id) const
|
||||
|
@ -57,11 +79,15 @@ shared_ptr<AbstractController> ControllerManager::FindController(int target_id)
|
|||
return it == controllers.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
bool ControllerManager::HasController(int target_id) const {
|
||||
return controllers.contains(target_id);
|
||||
}
|
||||
|
||||
unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() const
|
||||
{
|
||||
unordered_set<shared_ptr<PrimaryDevice>> devices;
|
||||
|
||||
for (const auto& [id, controller] : controllers) {
|
||||
for (const auto& [_, controller] : controllers) {
|
||||
const auto& d = controller->GetDevices();
|
||||
devices.insert(d.begin(), d.end());
|
||||
}
|
||||
|
@ -69,12 +95,12 @@ unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() cons
|
|||
return devices;
|
||||
}
|
||||
|
||||
void ControllerManager::DeleteAllControllers()
|
||||
bool ControllerManager::HasDeviceForIdAndLun(int id, int lun) const
|
||||
{
|
||||
controllers.clear();
|
||||
return GetDeviceForIdAndLun(id, lun) != nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
|
||||
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceForIdAndLun(int id, int lun) const
|
||||
{
|
||||
if (const auto& controller = FindController(id); controller != nullptr) {
|
||||
return controller->GetDeviceForLun(lun);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Keeps track of and manages the controllers
|
||||
//
|
||||
|
@ -11,36 +11,41 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "hal/bus.h"
|
||||
#include "controllers/abstract_controller.h"
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
#include "hal/bus.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class AbstractController;
|
||||
class ScsiController;
|
||||
class PrimaryDevice;
|
||||
|
||||
class ControllerManager : public enable_shared_from_this<ControllerManager>
|
||||
class ControllerManager
|
||||
{
|
||||
BUS& bus;
|
||||
|
||||
unordered_map<int, shared_ptr<AbstractController>> controllers;
|
||||
|
||||
public:
|
||||
|
||||
explicit ControllerManager(BUS& bus) : bus(bus) {}
|
||||
ControllerManager() = default;
|
||||
~ControllerManager() = default;
|
||||
|
||||
// Maximum number of controller devices
|
||||
static const int DEVICE_MAX = 8;
|
||||
|
||||
inline BUS& GetBus() const { return bus; }
|
||||
bool AttachToScsiController(int, shared_ptr<PrimaryDevice>);
|
||||
bool DeleteController(shared_ptr<AbstractController>);
|
||||
shared_ptr<AbstractController> IdentifyController(int) const;
|
||||
shared_ptr<AbstractController> FindController(int) const;
|
||||
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
|
||||
bool AttachToController(BUS&, int, shared_ptr<PrimaryDevice>);
|
||||
bool DeleteController(const AbstractController&);
|
||||
void DeleteAllControllers();
|
||||
shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
|
||||
AbstractController::piscsi_shutdown_mode ProcessOnController(int) const;
|
||||
shared_ptr<AbstractController> FindController(int) const;
|
||||
bool HasController(int) const;
|
||||
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
|
||||
bool HasDeviceForIdAndLun(int, int) const;
|
||||
shared_ptr<PrimaryDevice> GetDeviceForIdAndLun(int, int) const;
|
||||
|
||||
static int GetScsiIdMax() { return 8; }
|
||||
static int GetScsiLunMax() { return 32; }
|
||||
|
||||
private:
|
||||
|
||||
shared_ptr<ScsiController> CreateScsiController(BUS&, int) const;
|
||||
|
||||
// Controllers mapped to their device IDs
|
||||
unordered_map<int, shared_ptr<AbstractController>> controllers;
|
||||
};
|
||||
|
|
|
@ -3,15 +3,18 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shared/scsi.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
using namespace scsi_defs;
|
||||
using namespace scsi_defs;
|
||||
|
||||
class PhaseHandler
|
||||
{
|
||||
|
@ -31,7 +34,7 @@ public:
|
|||
virtual void MsgIn() = 0;
|
||||
virtual void MsgOut() = 0;
|
||||
|
||||
virtual phase_t Process(int) = 0;
|
||||
virtual bool Process(int) = 0;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -45,4 +48,27 @@ protected:
|
|||
bool IsDataOut() const { return phase == phase_t::dataout; }
|
||||
bool IsMsgIn() const { return phase == phase_t::msgin; }
|
||||
bool IsMsgOut() const { return phase == phase_t::msgout; }
|
||||
|
||||
void ProcessPhase() const
|
||||
{
|
||||
try {
|
||||
phase_executors.at(phase)();
|
||||
}
|
||||
catch(const out_of_range&) {
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const unordered_map<phase_t, function<void()>> phase_executors = {
|
||||
{ phase_t::busfree, [this] () { BusFree(); } },
|
||||
{ phase_t::selection, [this] () { Selection(); } },
|
||||
{ phase_t::dataout, [this] () { DataOut(); } },
|
||||
{ phase_t::datain, [this] () { DataIn(); } },
|
||||
{ phase_t::command, [this] () { Command(); } },
|
||||
{ phase_t::status, [this] () { Status(); } },
|
||||
{ phase_t::msgout, [this] () { MsgOut(); } },
|
||||
{ phase_t::msgin, [this] () { MsgIn(); } },
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
|
@ -16,7 +16,9 @@
|
|||
#include "shared/piscsi_exceptions.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "devices/interfaces/byte_writer.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "devices/scsi_host_bridge.h"
|
||||
#include "devices/scsi_daynaport.h"
|
||||
#include "devices/mode_page_device.h"
|
||||
#include "devices/disk.h"
|
||||
#include "scsi_controller.h"
|
||||
|
@ -28,11 +30,8 @@
|
|||
|
||||
using namespace scsi_defs;
|
||||
|
||||
ScsiController::ScsiController(shared_ptr<ControllerManager> controller_manager, int target_id)
|
||||
: AbstractController(controller_manager, target_id, LUN_MAX)
|
||||
ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, ControllerManager::GetScsiLunMax())
|
||||
{
|
||||
logger.SetIdAndLun(target_id, -1);
|
||||
|
||||
// The initial buffer size will default to either the default buffer size OR
|
||||
// the size of an Ethernet message, whichever is larger.
|
||||
AllocateBuffer(std::max(DEFAULT_BUFFER_SIZE, ETH_FRAME_LEN + 16 + ETH_FCS_LEN));
|
||||
|
@ -44,26 +43,21 @@ void ScsiController::Reset()
|
|||
|
||||
execstart = 0;
|
||||
identified_lun = -1;
|
||||
initiator_id = UNKNOWN_INITIATOR_ID;
|
||||
|
||||
scsi.atnmsg = false;
|
||||
scsi.msc = 0;
|
||||
scsi.msb = {};
|
||||
|
||||
SetByteTransfer(false);
|
||||
scsi = {};
|
||||
}
|
||||
|
||||
phase_t ScsiController::Process(int id)
|
||||
bool ScsiController::Process(int id)
|
||||
{
|
||||
GetBus().Acquire();
|
||||
|
||||
if (GetBus().GetRST()) {
|
||||
logger.Warn("RESET signal received!");
|
||||
LogWarn("RESET signal received!");
|
||||
|
||||
Reset();
|
||||
|
||||
GetBus().Reset();
|
||||
|
||||
return GetPhase();
|
||||
return false;
|
||||
}
|
||||
|
||||
initiator_id = id;
|
||||
|
@ -72,23 +66,20 @@ phase_t ScsiController::Process(int id)
|
|||
ProcessPhase();
|
||||
}
|
||||
catch(const scsi_exception&) {
|
||||
// Any exception should have been handled during the phase processing
|
||||
logger.Error("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
|
||||
LogError("Unhandled SCSI error, resetting controller and bus and entering bus free phase");
|
||||
|
||||
Reset();
|
||||
GetBus().Reset();
|
||||
|
||||
BusFree();
|
||||
}
|
||||
|
||||
return GetPhase();
|
||||
return !IsBusFree();
|
||||
}
|
||||
|
||||
void ScsiController::BusFree()
|
||||
{
|
||||
if (!IsBusFree()) {
|
||||
logger.Trace("Bus free phase");
|
||||
|
||||
LogTrace("Bus Free phase");
|
||||
SetPhase(phase_t::busfree);
|
||||
|
||||
GetBus().SetREQ(false);
|
||||
|
@ -98,7 +89,7 @@ void ScsiController::BusFree()
|
|||
GetBus().SetBSY(false);
|
||||
|
||||
// Initialize status and message
|
||||
SetStatus(status::GOOD);
|
||||
SetStatus(status::good);
|
||||
SetMessage(0x00);
|
||||
|
||||
// Initialize ATN message reception status
|
||||
|
@ -108,39 +99,6 @@ void ScsiController::BusFree()
|
|||
|
||||
SetByteTransfer(false);
|
||||
|
||||
if (shutdown_mode != piscsi_shutdown_mode::NONE) {
|
||||
// Prepare the shutdown by flushing all caches
|
||||
for (const auto& device : GetControllerManager()->GetAllDevices()) {
|
||||
device->FlushCache();
|
||||
}
|
||||
}
|
||||
|
||||
// When the bus is free PiSCSI or the Pi may be shut down.
|
||||
// This code has to be executed in the bus free phase and thus has to be located in the controller.
|
||||
switch(shutdown_mode) {
|
||||
case piscsi_shutdown_mode::STOP_PISCSI:
|
||||
logger.Info("PiSCSI shutdown requested");
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
case piscsi_shutdown_mode::STOP_PI:
|
||||
logger.Info("Raspberry Pi shutdown requested");
|
||||
if (system("init 0") == -1) {
|
||||
logger.Error("Raspberry Pi shutdown failed: " + string(strerror(errno)));
|
||||
}
|
||||
break;
|
||||
|
||||
case piscsi_shutdown_mode::RESTART_PI:
|
||||
logger.Info("Raspberry Pi restart requested");
|
||||
if (system("init 6") == -1) {
|
||||
logger.Error("Raspberry Pi restart failed: " + string(strerror(errno)));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -153,18 +111,7 @@ void ScsiController::BusFree()
|
|||
void ScsiController::Selection()
|
||||
{
|
||||
if (!IsSelection()) {
|
||||
// A different device controller was selected
|
||||
if (int id = 1 << GetTargetId(); (static_cast<int>(GetBus().GetDAT()) & id) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Abort if there is no LUN for this controller
|
||||
if (!GetLunCount()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.Trace("Selection phase");
|
||||
|
||||
LogTrace("Selection phase");
|
||||
SetPhase(phase_t::selection);
|
||||
|
||||
// Raise BSY and respond
|
||||
|
@ -174,6 +121,8 @@ void ScsiController::Selection()
|
|||
|
||||
// Selection completed
|
||||
if (!GetBus().GetSEL() && GetBus().GetBSY()) {
|
||||
LogTrace("Selection completed");
|
||||
|
||||
// Message out phase if ATN=1, otherwise command phase
|
||||
if (GetBus().GetATN()) {
|
||||
MsgOut();
|
||||
|
@ -186,8 +135,7 @@ void ScsiController::Selection()
|
|||
void ScsiController::Command()
|
||||
{
|
||||
if (!IsCommand()) {
|
||||
logger.Trace("Command phase");
|
||||
|
||||
LogTrace("Command phase");
|
||||
SetPhase(phase_t::command);
|
||||
|
||||
GetBus().SetMSG(false);
|
||||
|
@ -198,9 +146,9 @@ void ScsiController::Command()
|
|||
if (actual_count == 0) {
|
||||
stringstream s;
|
||||
s << "Received unknown command: $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
|
||||
logger.Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
Error(sense_key::illegal_request, asc::invalid_command_operation_code);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -210,16 +158,16 @@ void ScsiController::Command()
|
|||
if (actual_count != command_byte_count) {
|
||||
stringstream s;
|
||||
s << "Command byte count mismatch for command $" << setfill('0') << setw(2) << hex << GetBuffer()[0];
|
||||
logger.Error(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
|
||||
LogError(s.str() + ": expected " + to_string(command_byte_count) + " bytes, received"
|
||||
+ to_string(actual_count) + " byte(s)");
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
|
||||
// Command data transfer
|
||||
AllocateCmd(command_byte_count);
|
||||
for (int i = 0; i < command_byte_count; i++) {
|
||||
GetCmd()[i] = GetBuffer()[i];
|
||||
SetCmdByte(i, GetBuffer()[i]);
|
||||
}
|
||||
|
||||
SetLength(0);
|
||||
|
@ -234,9 +182,9 @@ void ScsiController::Execute()
|
|||
s << "Controller is executing " << command_mapping.find(GetOpcode())->second.second << ", CDB $"
|
||||
<< setfill('0') << hex;
|
||||
for (int i = 0; i < BUS::GetCommandByteCount(static_cast<uint8_t>(GetOpcode())); i++) {
|
||||
s << setw(2) << GetCmd(i);
|
||||
s << setw(2) << GetCmdByte(i);
|
||||
}
|
||||
logger.Debug(s.str());
|
||||
LogDebug(s.str());
|
||||
|
||||
// Initialization for data transfer
|
||||
ResetOffset();
|
||||
|
@ -245,15 +193,15 @@ void ScsiController::Execute()
|
|||
|
||||
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
||||
if (GetOpcode() != scsi_command::eCmdRequestSense) {
|
||||
SetStatus(status::GOOD);
|
||||
SetStatus(status::good);
|
||||
}
|
||||
|
||||
int lun = GetEffectiveLun();
|
||||
if (!HasDeviceForLun(lun)) {
|
||||
if (GetOpcode() != scsi_command::eCmdInquiry && GetOpcode() != scsi_command::eCmdRequestSense) {
|
||||
logger.Trace("Invalid LUN " + to_string(lun));
|
||||
LogTrace("Invalid LUN " + to_string(lun));
|
||||
|
||||
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
|
||||
Error(sense_key::illegal_request, asc::invalid_lun);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -265,7 +213,7 @@ void ScsiController::Execute()
|
|||
|
||||
// SCSI-2 4.4.3 Incorrect logical unit handling
|
||||
if (GetOpcode() == scsi_command::eCmdInquiry && !HasDeviceForLun(lun)) {
|
||||
logger.Trace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
|
||||
LogTrace("Reporting LUN" + to_string(GetEffectiveLun()) + " as not supported");
|
||||
|
||||
GetBuffer().data()[0] = 0x7f;
|
||||
|
||||
|
@ -279,7 +227,7 @@ void ScsiController::Execute()
|
|||
device->SetStatusCode(0);
|
||||
}
|
||||
|
||||
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmd(4) & 0x01)) {
|
||||
if (device->CheckReservation(initiator_id, GetOpcode(), GetCmdByte(4) & 0x01)) {
|
||||
try {
|
||||
device->Dispatch(GetOpcode());
|
||||
}
|
||||
|
@ -288,7 +236,7 @@ void ScsiController::Execute()
|
|||
}
|
||||
}
|
||||
else {
|
||||
Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
|
||||
Error(sense_key::aborted_command, asc::no_additional_sense_information, status::reservation_conflict);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -304,9 +252,8 @@ void ScsiController::Status()
|
|||
}
|
||||
|
||||
stringstream s;
|
||||
s << "Status Phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
|
||||
logger.Trace(s.str());
|
||||
|
||||
s << "Status phase, status is $" << setfill('0') << setw(2) << hex << static_cast<int>(GetStatus());
|
||||
LogTrace(s.str());
|
||||
SetPhase(phase_t::status);
|
||||
|
||||
// Signal line operated by the target
|
||||
|
@ -329,8 +276,7 @@ void ScsiController::Status()
|
|||
void ScsiController::MsgIn()
|
||||
{
|
||||
if (!IsMsgIn()) {
|
||||
logger.Trace("Message In phase");
|
||||
|
||||
LogTrace("Message In phase");
|
||||
SetPhase(phase_t::msgin);
|
||||
|
||||
GetBus().SetMSG(true);
|
||||
|
@ -347,8 +293,6 @@ void ScsiController::MsgIn()
|
|||
void ScsiController::MsgOut()
|
||||
{
|
||||
if (!IsMsgOut()) {
|
||||
logger.Trace("Message Out phase");
|
||||
|
||||
// process the IDENTIFY message
|
||||
if (IsSelection()) {
|
||||
scsi.atnmsg = true;
|
||||
|
@ -356,6 +300,7 @@ void ScsiController::MsgOut()
|
|||
scsi.msb = {};
|
||||
}
|
||||
|
||||
LogTrace("Message Out phase");
|
||||
SetPhase(phase_t::msgout);
|
||||
|
||||
GetBus().SetMSG(true);
|
||||
|
@ -387,8 +332,7 @@ void ScsiController::DataIn()
|
|||
return;
|
||||
}
|
||||
|
||||
logger.Trace("Entering Data In phase");
|
||||
|
||||
LogTrace("Data In phase");
|
||||
SetPhase(phase_t::datain);
|
||||
|
||||
GetBus().SetMSG(false);
|
||||
|
@ -417,11 +361,9 @@ void ScsiController::DataOut()
|
|||
return;
|
||||
}
|
||||
|
||||
logger.Trace("Data Out phase");
|
||||
|
||||
LogTrace("Data Out phase");
|
||||
SetPhase(phase_t::dataout);
|
||||
|
||||
// Signal line operated by the target
|
||||
GetBus().SetMSG(false);
|
||||
GetBus().SetCD(false);
|
||||
GetBus().SetIO(false);
|
||||
|
@ -441,7 +383,6 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
|
|||
// Reset check
|
||||
if (GetBus().GetRST()) {
|
||||
Reset();
|
||||
GetBus().Reset();
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -453,9 +394,9 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
|
|||
}
|
||||
|
||||
int lun = GetEffectiveLun();
|
||||
if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) {
|
||||
if (!HasDeviceForLun(lun) || asc == asc::invalid_lun) {
|
||||
if (!HasDeviceForLun(0)) {
|
||||
logger.Error("No LUN 0");
|
||||
LogError("No LUN 0");
|
||||
|
||||
SetStatus(status);
|
||||
SetMessage(0x00);
|
||||
|
@ -468,11 +409,11 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
|
|||
lun = 0;
|
||||
}
|
||||
|
||||
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
|
||||
if (sense_key != sense_key::no_sense || asc != asc::no_additional_sense_information) {
|
||||
stringstream s;
|
||||
s << setfill('0') << setw(2) << hex << "Error status: Sense Key $" << static_cast<int>(sense_key)
|
||||
<< ", ASC $" << static_cast<int>(asc);
|
||||
logger.Debug(s.str());
|
||||
LogDebug(s.str());
|
||||
|
||||
// Set Sense Key and ASC for a subsequent REQUEST SENSE
|
||||
GetDeviceForLun(lun)->SetStatusCode((static_cast<int>(sense_key) << 16) | (static_cast<int>(asc) << 8));
|
||||
|
@ -481,7 +422,7 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
|
|||
SetStatus(status);
|
||||
SetMessage(0x00);
|
||||
|
||||
logger.Trace("Error (to status phase)");
|
||||
LogTrace("Error (to status phase)");
|
||||
|
||||
Status();
|
||||
}
|
||||
|
@ -492,7 +433,7 @@ void ScsiController::Send()
|
|||
assert(GetBus().GetIO());
|
||||
|
||||
if (HasValidLength()) {
|
||||
logger.Trace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
|
||||
LogTrace("Sending data, offset: " + to_string(GetOffset()) + ", length: " + to_string(GetLength()));
|
||||
|
||||
// The delay should be taken from the respective LUN, but as there are no Daynaport drivers for
|
||||
// LUNs other than 0 this work-around works.
|
||||
|
@ -500,7 +441,7 @@ void ScsiController::Send()
|
|||
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
|
||||
len != static_cast<int>(GetLength())) {
|
||||
// If you cannot send all, move to status phase
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -510,33 +451,30 @@ void ScsiController::Send()
|
|||
}
|
||||
|
||||
DecrementBlocks();
|
||||
bool result = true;
|
||||
|
||||
// Processing after data collection (read/data-in only)
|
||||
if (IsDataIn() && GetBlocks() != 0) {
|
||||
if (IsDataIn() && HasBlocks()) {
|
||||
// set next buffer (set offset, length)
|
||||
result = XferIn(GetBuffer());
|
||||
logger.Trace("Processing after data collection. Blocks: " + to_string(GetBlocks()));
|
||||
if (!XferIn(GetBuffer())) {
|
||||
// If result FALSE, move to status phase
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
|
||||
LogTrace("Processing after data collection");
|
||||
}
|
||||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue sending if block !=0
|
||||
if (GetBlocks() != 0){
|
||||
logger.Trace("Continuing to send. Blocks: " + to_string(GetBlocks()));
|
||||
// Continue sending if blocks != 0
|
||||
if (HasBlocks()) {
|
||||
LogTrace("Continuing to send");
|
||||
assert(HasValidLength());
|
||||
assert(GetOffset() == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next phase
|
||||
logger.Trace("Moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
LogTrace("All data transferred, moving to next phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
switch (GetPhase()) {
|
||||
// Message in phase
|
||||
case phase_t::msgin:
|
||||
// Completed sending response to extended message of IDENTIFY message
|
||||
if (scsi.atnmsg) {
|
||||
|
@ -551,13 +489,11 @@ void ScsiController::Send()
|
|||
}
|
||||
break;
|
||||
|
||||
// Data-in Phase
|
||||
case phase_t::datain:
|
||||
// status phase
|
||||
Status();
|
||||
break;
|
||||
|
||||
// status phase
|
||||
case phase_t::status:
|
||||
// Message in phase
|
||||
SetLength(1);
|
||||
|
@ -578,13 +514,13 @@ void ScsiController::Receive()
|
|||
assert(!GetBus().GetIO());
|
||||
|
||||
if (HasValidLength()) {
|
||||
logger.Trace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
|
||||
LogTrace("Receiving data, transfer length: " + to_string(GetLength()) + " byte(s)");
|
||||
|
||||
// If not able to receive all, move to status phase
|
||||
if (uint32_t len = GetBus().ReceiveHandShake(GetBuffer().data() + GetOffset(), GetLength()); len != GetLength()) {
|
||||
logger.Error("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
|
||||
LogError("Not able to receive " + to_string(GetLength()) + " byte(s) of data, only received "
|
||||
+ to_string(len));
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -603,10 +539,10 @@ void ScsiController::Receive()
|
|||
bool result = true;
|
||||
|
||||
// Processing after receiving data (by phase)
|
||||
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
switch (GetPhase()) {
|
||||
case phase_t::dataout:
|
||||
if (GetBlocks() == 0) {
|
||||
if (!HasBlocks()) {
|
||||
// End with this buffer
|
||||
result = XferOut(false);
|
||||
} else {
|
||||
|
@ -633,12 +569,12 @@ void ScsiController::Receive()
|
|||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue to receive if block != 0
|
||||
if (GetBlocks() != 0) {
|
||||
// Continue to receive if blocks != 0
|
||||
if (HasBlocks()) {
|
||||
assert(HasValidLength());
|
||||
assert(GetOffset() == 0);
|
||||
return;
|
||||
|
@ -692,7 +628,7 @@ void ScsiController::ReceiveBytes()
|
|||
bool result = true;
|
||||
|
||||
// Processing after receiving data (by phase)
|
||||
logger.Trace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
LogTrace("Phase: " + string(BUS::GetPhaseStrRaw(GetPhase())));
|
||||
switch (GetPhase()) {
|
||||
case phase_t::dataout:
|
||||
result = XferOut(false);
|
||||
|
@ -716,7 +652,7 @@ void ScsiController::ReceiveBytes()
|
|||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error(sense_key::ABORTED_COMMAND);
|
||||
Error(sense_key::aborted_command);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -752,7 +688,7 @@ bool ScsiController::XferOut(bool cont)
|
|||
SetByteTransfer(false);
|
||||
|
||||
auto device = GetDeviceForLun(GetEffectiveLun());
|
||||
return device != nullptr ? device->WriteByteSequence(GetBuffer(), count) : false;
|
||||
return device != nullptr ? device->WriteByteSequence(span(GetBuffer().data(), count)) : false;
|
||||
}
|
||||
|
||||
void ScsiController::DataOutNonBlockOriented()
|
||||
|
@ -760,7 +696,6 @@ void ScsiController::DataOutNonBlockOriented()
|
|||
assert(IsDataOut());
|
||||
|
||||
switch (GetOpcode()) {
|
||||
// TODO Check why these cases are needed
|
||||
case scsi_command::eCmdWrite6:
|
||||
case scsi_command::eCmdWrite10:
|
||||
case scsi_command::eCmdWrite16:
|
||||
|
@ -777,7 +712,7 @@ void ScsiController::DataOutNonBlockOriented()
|
|||
device->ModeSelect(GetOpcode(), GetCmd(), GetBuffer(), GetOffset());
|
||||
}
|
||||
else {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
@ -790,7 +725,7 @@ void ScsiController::DataOutNonBlockOriented()
|
|||
stringstream s;
|
||||
s << "Unexpected Data Out phase for command $" << setfill('0') << setw(2) << hex
|
||||
<< static_cast<int>(GetOpcode());
|
||||
logger.Warn(s.str());
|
||||
LogWarn(s.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -807,9 +742,9 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
|
|||
|
||||
stringstream s;
|
||||
s << "Command: $" << setfill('0') << setw(2) << hex << static_cast<int>(GetOpcode());
|
||||
logger.Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
int lun = GetEffectiveLun();
|
||||
const int lun = GetEffectiveLun();
|
||||
if (!HasDeviceForLun(lun)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -821,7 +756,7 @@ bool ScsiController::XferIn(vector<uint8_t>& buf)
|
|||
case scsi_command::eCmdRead16:
|
||||
// Read from disk
|
||||
try {
|
||||
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(GetCmd(), buf, GetNext()));
|
||||
SetLength(dynamic_pointer_cast<Disk>(GetDeviceForLun(lun))->Read(buf, GetNext()));
|
||||
}
|
||||
catch(const scsi_exception&) {
|
||||
// If there is an error, go to the status phase
|
||||
|
@ -878,13 +813,23 @@ bool ScsiController::XferOutBlockOriented(bool cont)
|
|||
case scsi_command::eCmdWrite6:
|
||||
case scsi_command::eCmdWrite10:
|
||||
case scsi_command::eCmdWrite16:
|
||||
// TODO Verify has to verify, not to write
|
||||
// TODO Verify has to verify, not to write, see https://github.com/PiSCSI/piscsi/issues/807
|
||||
case scsi_command::eCmdVerify10:
|
||||
case scsi_command::eCmdVerify16:
|
||||
{
|
||||
// Special case for SCBR and SCDP
|
||||
if (auto byte_writer = dynamic_pointer_cast<ByteWriter>(device); byte_writer) {
|
||||
if (!byte_writer->WriteBytes(GetCmd(), GetBuffer(), GetLength())) {
|
||||
// TODO Get rid of this special case for SCBR
|
||||
if (auto bridge = dynamic_pointer_cast<SCSIBR>(device); bridge) {
|
||||
if (!bridge->ReadWrite(GetCmd(), GetBuffer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetOffset();
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO Get rid of this special case for SCDP
|
||||
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(device); daynaport) {
|
||||
if (!daynaport->Write(GetCmd(), GetBuffer())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -898,7 +843,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
|
|||
}
|
||||
|
||||
try {
|
||||
disk->Write(GetCmd(), GetBuffer(), GetNext() - 1);
|
||||
disk->Write(GetBuffer(), GetNext() - 1);
|
||||
}
|
||||
catch(const scsi_exception& e) {
|
||||
Error(e.get_sense_key(), e.get_asc());
|
||||
|
@ -918,14 +863,14 @@ bool ScsiController::XferOutBlockOriented(bool cont)
|
|||
}
|
||||
|
||||
case scsi_command::eCmdSetMcastAddr:
|
||||
logger.Trace("Done with DaynaPort Set Multicast Address");
|
||||
LogTrace("Done with DaynaPort Set Multicast Address");
|
||||
break;
|
||||
|
||||
default:
|
||||
stringstream s;
|
||||
s << "Received an unexpected command ($" << setfill('0') << setw(2) << hex
|
||||
<< static_cast<int>(GetOpcode()) << ")";
|
||||
logger.Warn(s.str());
|
||||
LogWarn(s.str());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -934,15 +879,15 @@ bool ScsiController::XferOutBlockOriented(bool cont)
|
|||
|
||||
void ScsiController::ProcessCommand()
|
||||
{
|
||||
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
|
||||
const uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
|
||||
|
||||
stringstream s;
|
||||
s << "CDB=$" << setfill('0') << setw(2) << hex;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
GetCmd()[i] = GetBuffer()[i];
|
||||
s << GetCmd(i);
|
||||
SetCmdByte(i, GetBuffer()[i]);
|
||||
s << GetCmdByte(i);
|
||||
}
|
||||
logger.Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
Execute();
|
||||
}
|
||||
|
@ -954,13 +899,13 @@ void ScsiController::ParseMessage()
|
|||
const uint8_t message_type = scsi.msb[i];
|
||||
|
||||
if (message_type == 0x06) {
|
||||
logger.Trace("Received ABORT message");
|
||||
LogTrace("Received ABORT message");
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
if (message_type == 0x0C) {
|
||||
logger.Trace("Received BUS DEVICE RESET message");
|
||||
LogTrace("Received BUS DEVICE RESET message");
|
||||
scsi.syncoffset = 0;
|
||||
if (auto device = GetDeviceForLun(identified_lun); device != nullptr) {
|
||||
device->DiscardReservation();
|
||||
|
@ -971,11 +916,11 @@ void ScsiController::ParseMessage()
|
|||
|
||||
if (message_type >= 0x80) {
|
||||
identified_lun = static_cast<int>(message_type) & 0x1F;
|
||||
logger.Trace("Received IDENTIFY message for LUN " + to_string(identified_lun));
|
||||
LogTrace("Received IDENTIFY message for LUN " + to_string(identified_lun));
|
||||
}
|
||||
|
||||
if (message_type == 0x01) {
|
||||
logger.Trace("Received EXTENDED MESSAGE");
|
||||
LogTrace("Received EXTENDED MESSAGE");
|
||||
|
||||
// Check only when synchronous transfer is possible
|
||||
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
||||
|
@ -1046,4 +991,4 @@ void ScsiController::Sleep()
|
|||
SysTimer::SleepUsec(MIN_EXEC_TIME - time);
|
||||
}
|
||||
execstart = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,8 +15,6 @@
|
|||
#pragma once
|
||||
|
||||
#include "shared/scsi.h"
|
||||
#include "controller_manager.h"
|
||||
#include "devices/device_logger.h"
|
||||
#include "abstract_controller.h"
|
||||
#include <array>
|
||||
|
||||
|
@ -52,20 +50,17 @@ class ScsiController : public AbstractController
|
|||
|
||||
public:
|
||||
|
||||
// Maximum number of logical units
|
||||
static inline const int LUN_MAX = 32;
|
||||
|
||||
explicit ScsiController(shared_ptr<ControllerManager>, int);
|
||||
ScsiController(BUS&, int);
|
||||
~ScsiController() override = default;
|
||||
|
||||
void Reset() override;
|
||||
|
||||
phase_t Process(int) override;
|
||||
bool Process(int) override;
|
||||
|
||||
int GetEffectiveLun() const override;
|
||||
|
||||
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
|
||||
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::no_additional_sense_information,
|
||||
scsi_defs::status status = scsi_defs::status::check_condition) override;
|
||||
|
||||
int GetInitiatorId() const override { return initiator_id; }
|
||||
|
||||
|
@ -79,16 +74,8 @@ public:
|
|||
void DataIn() override;
|
||||
void DataOut() override;
|
||||
|
||||
// TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC,
|
||||
// e.g. by using ifdef __arm__. Currently the unit tests require this method to be public.
|
||||
virtual void Execute();
|
||||
|
||||
void ScheduleShutdown(piscsi_shutdown_mode mode) override { shutdown_mode = mode; }
|
||||
|
||||
private:
|
||||
|
||||
DeviceLogger logger;
|
||||
|
||||
// Execution start time
|
||||
uint32_t execstart = 0;
|
||||
|
||||
|
@ -109,6 +96,9 @@ private:
|
|||
void DataOutNonBlockOriented();
|
||||
void Receive();
|
||||
|
||||
// TODO Make non-virtual as soon as SysTimer calls do not segfault anymore on a regular PC, e.g. by using ifdef __arm__.
|
||||
virtual void Execute();
|
||||
|
||||
void ProcessCommand();
|
||||
void ParseMessage();
|
||||
void ProcessMessage();
|
||||
|
@ -116,7 +106,5 @@ private:
|
|||
void Sleep();
|
||||
|
||||
scsi_t scsi = {};
|
||||
|
||||
AbstractController::piscsi_shutdown_mode shutdown_mode = AbstractController::piscsi_shutdown_mode::NONE;
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "cfilesystem.h"
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <iconv.h>
|
||||
#include <utime.h>
|
||||
|
@ -3507,18 +3507,18 @@ int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const
|
|||
// Acquire sector data
|
||||
if (!m_cEntry.GetCapacityCache(nUnit, &cap)) {
|
||||
// Carry out an extra media check here because it may be skipped when doing a manual eject
|
||||
if (!m_cEntry.isEnable(nUnit))
|
||||
goto none;
|
||||
|
||||
// Media check
|
||||
if (m_cEntry.isMediaOffline(nUnit))
|
||||
goto none;
|
||||
|
||||
// Get drive status
|
||||
m_cEntry.GetCapacity(nUnit, &cap);
|
||||
if (!m_cEntry.isEnable(nUnit) || m_cEntry.isMediaOffline(nUnit)) {
|
||||
cap.clusters = 4; // This is totally fine, right?
|
||||
cap.sectors = 64;
|
||||
cap.bytes = 512;
|
||||
}
|
||||
else {
|
||||
// Get drive status
|
||||
m_cEntry.GetCapacity(nUnit, &cap);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
none:
|
||||
cap.clusters = 4; // This is totally fine, right?
|
||||
cap.sectors = 64;
|
||||
cap.bytes = 512;
|
||||
|
|
|
@ -13,6 +13,15 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using TCHAR = char;
|
||||
|
||||
static const int FILEPATH_MAX = 260;
|
||||
|
|
|
@ -9,126 +9,71 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "shared/network_util.h"
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "ctapdriver.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/sockios.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_util;
|
||||
using namespace network_util;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
|
||||
const string CTapDriver::BRIDGE_NAME = "piscsi_bridge";
|
||||
|
||||
static string br_setif(int br_socket_fd, const string& bridgename, const string& ifname, bool add) {
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
return "if_nametoindex: Linux is required";
|
||||
#else
|
||||
ifreq ifr;
|
||||
ifr.ifr_ifindex = if_nametoindex(ifname);
|
||||
ifr.ifr_ifindex = if_nametoindex(ifname.c_str());
|
||||
if (ifr.ifr_ifindex == 0) {
|
||||
LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno))
|
||||
return false;
|
||||
return "Can't if_nametoindex " + ifname;
|
||||
}
|
||||
strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1);
|
||||
strncpy(ifr.ifr_name, bridgename.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
||||
if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) {
|
||||
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno))
|
||||
return false;
|
||||
return "Can't ioctl " + string(add ? "SIOCBRADDIF" : "SIOCBRDELIF");
|
||||
}
|
||||
return true;
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
CTapDriver::~CTapDriver()
|
||||
{
|
||||
if (m_hTAP != -1) {
|
||||
if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
LOGERROR("Can't open bridge socket: %s", strerror(errno))
|
||||
} else {
|
||||
LOGDEBUG("brctl delif %s piscsi0", BRIDGE_NAME)
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false)) { //NOSONAR No exception is raised here
|
||||
LOGWARN("Warning: Removing piscsi0 from the bridge failed.")
|
||||
LOGWARN("You may need to manually remove the piscsi0 tap device from the bridge")
|
||||
}
|
||||
close(br_socket_fd);
|
||||
}
|
||||
|
||||
// Release TAP defice
|
||||
close(m_hTAP);
|
||||
}
|
||||
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_dump_close(m_pcap_dumper);
|
||||
}
|
||||
|
||||
if (m_pcap != nullptr) {
|
||||
pcap_close(m_pcap);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ip_link(int fd, const char* ifname, bool up) {
|
||||
string ip_link(int fd, const char* ifname, bool up) {
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
return "Can't ip_link: Linux is required";
|
||||
#else
|
||||
ifreq ifr;
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator
|
||||
int err = ioctl(fd, SIOCGIFFLAGS, &ifr);
|
||||
if (err) {
|
||||
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno))
|
||||
return false;
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
||||
if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
|
||||
return "Can't ioctl SIOCGIFFLAGS";
|
||||
}
|
||||
ifr.ifr_flags &= ~IFF_UP;
|
||||
if (up) {
|
||||
ifr.ifr_flags |= IFF_UP;
|
||||
}
|
||||
err = ioctl(fd, SIOCSIFFLAGS, &ifr);
|
||||
if (err) {
|
||||
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno))
|
||||
return false;
|
||||
if (ioctl(fd, SIOCSIFFLAGS, &ifr)) {
|
||||
return "Can't ioctl SIOCSIFFLAGS";
|
||||
}
|
||||
return true;
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool is_interface_up(string_view interface) {
|
||||
string file = "/sys/class/net/";
|
||||
file += interface;
|
||||
file += "/carrier";
|
||||
|
||||
bool status = true;
|
||||
FILE *fp = fopen(file.c_str(), "r");
|
||||
if (!fp || fgetc(fp) != '1') {
|
||||
status = false;
|
||||
}
|
||||
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
bool CTapDriver::Init(const param_map& const_params)
|
||||
{
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
#else
|
||||
unordered_map<string, string> params = const_params;
|
||||
param_map params = const_params;
|
||||
stringstream s(params["interface"]);
|
||||
string interface;
|
||||
while (getline(s, interface, ',')) {
|
||||
|
@ -136,36 +81,33 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
|||
}
|
||||
inet = params["inet"];
|
||||
|
||||
LOGTRACE("Opening tap device")
|
||||
spdlog::trace("Opening tap device");
|
||||
// TAP device initilization
|
||||
if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) {
|
||||
LOGERROR("Can't open tun: %s", strerror(errno))
|
||||
LogErrno("Can't open tun");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("Opened tap device %d", m_hTAP)
|
||||
|
||||
// IFF_NO_PI for no extra packet information
|
||||
ifreq ifr = {};
|
||||
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
||||
string dev = "piscsi0";
|
||||
strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1);
|
||||
strncpy(ifr.ifr_name, "piscsi0", IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
||||
|
||||
LOGTRACE("Going to open %s", ifr.ifr_name)
|
||||
spdlog::trace("Going to open " + string(ifr.ifr_name));
|
||||
|
||||
int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
|
||||
const int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
|
||||
if (ret < 0) {
|
||||
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno))
|
||||
LogErrno("Can't ioctl TUNSETIFF");
|
||||
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("Return code from ioctl was %d", ret)
|
||||
spdlog::trace("Return code from ioctl was " + to_string(ret));
|
||||
|
||||
const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
if (ip_fd < 0) {
|
||||
LOGERROR("Can't open ip socket: %s", strerror(errno))
|
||||
LogErrno("Can't open ip socket");
|
||||
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
|
@ -173,179 +115,72 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
|||
|
||||
const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
|
||||
if (br_socket_fd < 0) {
|
||||
LOGERROR("Can't open bridge socket: %s", strerror(errno))
|
||||
LogErrno("Can't open bridge socket");
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cleanUp = [&] (const string& error) {
|
||||
LogErrno(error);
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check if the bridge has already been created
|
||||
string sys_file = "/sys/class/net/";
|
||||
sys_file += BRIDGE_NAME;
|
||||
if (access(sys_file.c_str(), F_OK)) {
|
||||
LOGINFO("%s is not yet available", BRIDGE_NAME)
|
||||
// TODO Find an alternative to accessing a file, there is most likely a system call/ioctl
|
||||
if (access(string("/sys/class/net/" + BRIDGE_NAME).c_str(), F_OK)) {
|
||||
spdlog::trace("Checking which interface is available for creating the bridge " + BRIDGE_NAME);
|
||||
|
||||
LOGTRACE("Checking which interface is available for creating the bridge")
|
||||
|
||||
string bridge_interface;
|
||||
for (const string& iface : interfaces) {
|
||||
if (is_interface_up(iface)) {
|
||||
LOGTRACE("%s", string("Interface " + iface + " is up").c_str())
|
||||
|
||||
bridge_interface = iface;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str())
|
||||
}
|
||||
const auto& it = ranges::find_if(interfaces, [] (const string& iface) { return IsInterfaceUp(iface); } );
|
||||
if (it == interfaces.end()) {
|
||||
return cleanUp("No interface is up, not creating bridge " + BRIDGE_NAME);
|
||||
}
|
||||
|
||||
if (bridge_interface.empty()) {
|
||||
LOGERROR("No interface is up, not creating bridge")
|
||||
return false;
|
||||
}
|
||||
const string bridge_interface = *it;
|
||||
|
||||
LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str())
|
||||
spdlog::info("Creating " + BRIDGE_NAME + " for interface " + bridge_interface);
|
||||
|
||||
if (bridge_interface == "eth0") {
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
|
||||
|
||||
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str())
|
||||
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) {
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
if (const string error = SetUpEth0(br_socket_fd, bridge_interface); !error.empty()) {
|
||||
return cleanUp(error);
|
||||
}
|
||||
}
|
||||
else {
|
||||
string address = inet;
|
||||
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
|
||||
if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) {
|
||||
address = inet.substr(0, separatorPos);
|
||||
|
||||
int m;
|
||||
if (!GetAsUnsignedInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) {
|
||||
LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str())
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
// long long is required for compatibility with 32 bit platforms
|
||||
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
|
||||
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
|
||||
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
|
||||
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
|
||||
|
||||
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
ifreq ifr_a;
|
||||
ifr_a.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ);
|
||||
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
|
||||
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
ifreq ifr_n;
|
||||
ifr_n.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ);
|
||||
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
|
||||
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME)
|
||||
|
||||
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
else if (const string error = SetUpNonEth0(br_socket_fd, ip_fd, inet); !error.empty()) {
|
||||
return cleanUp(error);
|
||||
}
|
||||
|
||||
LOGTRACE("ip link set dev %s up", BRIDGE_NAME)
|
||||
spdlog::trace(">ip link set dev " + BRIDGE_NAME + " up");
|
||||
|
||||
if (!ip_link(ip_fd, BRIDGE_NAME, true)) {
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
if (const string error = ip_link(ip_fd, BRIDGE_NAME.c_str(), true); !error.empty()) {
|
||||
return cleanUp(error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGINFO("%s is already available", BRIDGE_NAME)
|
||||
else {
|
||||
spdlog::info(BRIDGE_NAME + " is already available");
|
||||
}
|
||||
|
||||
LOGTRACE("ip link set piscsi0 up")
|
||||
spdlog::trace(">ip link set piscsi0 up");
|
||||
|
||||
if (!ip_link(ip_fd, "piscsi0", true)) {
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
if (const string error = ip_link(ip_fd, "piscsi0", true); !error.empty()) {
|
||||
return cleanUp(error);
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addif %s piscsi0", BRIDGE_NAME)
|
||||
spdlog::trace(">brctl addif " + BRIDGE_NAME + " piscsi0");
|
||||
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true)) {
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", true); !error.empty()) {
|
||||
return cleanUp(error);
|
||||
}
|
||||
|
||||
// Get MAC address
|
||||
LOGTRACE("Getting the MAC address")
|
||||
spdlog::trace("Getting the MAC address");
|
||||
|
||||
ifr.ifr_addr.sa_family = AF_INET;
|
||||
if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
return cleanUp("Can't ioctl SIOCGIFHWADDR");
|
||||
}
|
||||
LOGTRACE("Got the MAC")
|
||||
|
||||
// Save MAC address
|
||||
memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size());
|
||||
|
@ -353,50 +188,124 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
|||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
|
||||
LOGINFO("Tap device %s created", ifr.ifr_name)
|
||||
spdlog::info("Tap device " + string(ifr.ifr_name) + " created");
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CTapDriver::OpenDump(const string& path) {
|
||||
if (m_pcap == nullptr) {
|
||||
m_pcap = pcap_open_dead(DLT_EN10MB, 65535);
|
||||
}
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_dump_close(m_pcap_dumper);
|
||||
}
|
||||
m_pcap_dumper = pcap_dump_open(m_pcap, path.c_str());
|
||||
if (m_pcap_dumper == nullptr) {
|
||||
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap))
|
||||
throw io_exception("Can't open pcap file");
|
||||
}
|
||||
void CTapDriver::CleanUp() const
|
||||
{
|
||||
if (m_hTAP != -1) {
|
||||
if (const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0); br_socket_fd < 0) {
|
||||
LogErrno("Can't open bridge socket");
|
||||
} else {
|
||||
spdlog::trace(">brctl delif " + BRIDGE_NAME + " piscsi0");
|
||||
if (const string error = br_setif(br_socket_fd, BRIDGE_NAME, "piscsi0", false); !error.empty()) {
|
||||
spdlog::warn("Warning: Removing piscsi0 from the bridge failed: " + error);
|
||||
spdlog::warn("You may need to manually remove the piscsi0 tap device from the bridge");
|
||||
}
|
||||
close(br_socket_fd);
|
||||
}
|
||||
|
||||
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.c_str())
|
||||
// Release TAP device
|
||||
close(m_hTAP);
|
||||
}
|
||||
}
|
||||
|
||||
bool CTapDriver::Enable() const
|
||||
pair<string, string> CTapDriver::ExtractAddressAndMask(const string& s)
|
||||
{
|
||||
string address = s;
|
||||
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
|
||||
if (const auto& components = Split(s, '/', 2); components.size() == 2) {
|
||||
address = components[0];
|
||||
|
||||
int m;
|
||||
if (!GetAsUnsignedInt(components[1], m) || m < 8 || m > 32) {
|
||||
spdlog::error("Invalid CIDR netmask notation '" + components[1] + "'");
|
||||
return { "", "" };
|
||||
}
|
||||
|
||||
// long long is required for compatibility with 32 bit platforms
|
||||
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
|
||||
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
|
||||
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
|
||||
}
|
||||
|
||||
return { address, netmask };
|
||||
}
|
||||
|
||||
string CTapDriver::SetUpEth0(int socket_fd, const string& bridge_interface)
|
||||
{
|
||||
#ifdef __linux__
|
||||
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
|
||||
|
||||
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
|
||||
return "Can't ioctl SIOCBRADDBR";
|
||||
}
|
||||
|
||||
spdlog::trace(">brctl addif " + BRIDGE_NAME + " " + bridge_interface);
|
||||
|
||||
if (const string error = br_setif(socket_fd, BRIDGE_NAME, bridge_interface, true); !error.empty()) {
|
||||
return error;
|
||||
}
|
||||
#endif
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
string CTapDriver::SetUpNonEth0(int socket_fd, int ip_fd, const string& s)
|
||||
{
|
||||
#ifdef __linux__
|
||||
const auto [address, netmask] = ExtractAddressAndMask(s);
|
||||
if (address.empty() || netmask.empty()) {
|
||||
return "Error extracting inet address and netmask";
|
||||
}
|
||||
|
||||
spdlog::trace(">brctl addbr " + BRIDGE_NAME);
|
||||
|
||||
if (ioctl(socket_fd, SIOCBRADDBR, BRIDGE_NAME.c_str()) < 0) {
|
||||
return "Can't ioctl SIOCBRADDBR";
|
||||
}
|
||||
|
||||
ifreq ifr_a;
|
||||
ifr_a.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_a.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
||||
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
|
||||
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
|
||||
return "Can't convert '" + address + "' into a network address";
|
||||
}
|
||||
|
||||
ifreq ifr_n;
|
||||
ifr_n.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_n.ifr_name, BRIDGE_NAME.c_str(), IFNAMSIZ - 1); //NOSONAR Using strncpy is safe
|
||||
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
|
||||
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
|
||||
return "Can't convert '" + netmask + "' into a netmask";
|
||||
}
|
||||
|
||||
spdlog::trace(">ip address add " + s + " dev " + BRIDGE_NAME);
|
||||
|
||||
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
|
||||
return "Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK";
|
||||
}
|
||||
#endif
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
string CTapDriver::IpLink(bool enable) const
|
||||
{
|
||||
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
LOGDEBUG("%s: ip link set piscsi0 up", __PRETTY_FUNCTION__)
|
||||
const bool result = ip_link(fd, "piscsi0", true);
|
||||
spdlog::trace(string(">ip link set piscsi0 ") + (enable ? "up" : "down"));
|
||||
const string result = ip_link(fd, "piscsi0", enable);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CTapDriver::Disable() const
|
||||
void CTapDriver::Flush() const
|
||||
{
|
||||
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
LOGDEBUG("%s: ip link set piscsi0 down", __PRETTY_FUNCTION__)
|
||||
const bool result = ip_link(fd, "piscsi0", false);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CTapDriver::Flush()
|
||||
{
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
while (PendingPackets()) {
|
||||
while (HasPendingPackets()) {
|
||||
array<uint8_t, ETH_FRAME_LEN> m_garbage_buffer;
|
||||
(void)Receive(m_garbage_buffer.data());
|
||||
}
|
||||
|
@ -409,12 +318,7 @@ void CTapDriver::GetMacAddr(uint8_t *mac) const
|
|||
memcpy(mac, m_MacAddr.data(), m_MacAddr.size());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Receive
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool CTapDriver::PendingPackets() const
|
||||
bool CTapDriver::HasPendingPackets() const
|
||||
{
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
|
@ -424,20 +328,16 @@ bool CTapDriver::PendingPackets() const
|
|||
fds.events = POLLIN | POLLERR;
|
||||
fds.revents = 0;
|
||||
poll(&fds, 1, 0);
|
||||
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents)
|
||||
if (!(fds.revents & POLLIN)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
spdlog::trace(to_string(fds.revents) + " revents");
|
||||
return !(fds.revents & POLLIN);
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
|
||||
uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
|
||||
uint32_t CTapDriver::Crc32(span<const uint8_t> data) {
|
||||
uint32_t crc = 0xffffffff;
|
||||
for (int i = 0; i < length; i++) {
|
||||
crc ^= buf[i];
|
||||
for (int j = 0; j < 8; j++) {
|
||||
for (const auto d: data) {
|
||||
crc ^= d;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
const uint32_t mask = -(static_cast<int>(crc) & 1);
|
||||
crc = (crc >> 1) ^ (0xEDB88320 & mask);
|
||||
}
|
||||
|
@ -445,19 +345,19 @@ uint32_t CTapDriver::Crc32(const uint8_t *buf, int length) {
|
|||
return ~crc;
|
||||
}
|
||||
|
||||
int CTapDriver::Receive(uint8_t *buf)
|
||||
int CTapDriver::Receive(uint8_t *buf) const
|
||||
{
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
// Check if there is data that can be received
|
||||
if (!PendingPackets()) {
|
||||
if (!HasPendingPackets()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Receive
|
||||
auto dwReceived = static_cast<uint32_t>(read(m_hTAP, buf, ETH_FRAME_LEN));
|
||||
if (dwReceived == static_cast<uint32_t>(-1)) {
|
||||
LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__)
|
||||
spdlog::warn("Error occured while receiving a packet");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -466,49 +366,28 @@ int CTapDriver::Receive(uint8_t *buf)
|
|||
// We need to add the Frame Check Status (FCS) CRC back onto the end of the packet.
|
||||
// The Linux network subsystem removes it, since most software apps shouldn't ever
|
||||
// need it.
|
||||
const int crc = Crc32(buf, dwReceived);
|
||||
const int crc = Crc32(span(buf, dwReceived));
|
||||
|
||||
buf[dwReceived + 0] = (uint8_t)((crc >> 0) & 0xFF);
|
||||
buf[dwReceived + 1] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
buf[dwReceived + 2] = (uint8_t)((crc >> 16) & 0xFF);
|
||||
buf[dwReceived + 3] = (uint8_t)((crc >> 24) & 0xFF);
|
||||
|
||||
LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3])
|
||||
spdlog::trace("CRC is " + to_string(crc) + " - " + to_string(buf[dwReceived+0]) + " " + to_string(buf[dwReceived+1]) +
|
||||
" " + to_string(buf[dwReceived+2]) + " " + to_string(buf[dwReceived+3]));
|
||||
|
||||
// Add FCS size to the received message size
|
||||
dwReceived += 4;
|
||||
}
|
||||
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_pkthdr h = {
|
||||
.ts = {},
|
||||
.caplen = dwReceived,
|
||||
.len = dwReceived
|
||||
};
|
||||
gettimeofday(&h.ts, nullptr);
|
||||
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1])
|
||||
}
|
||||
|
||||
// Return the number of bytes
|
||||
return dwReceived;
|
||||
}
|
||||
|
||||
int CTapDriver::Send(const uint8_t *buf, int len)
|
||||
int CTapDriver::Send(const uint8_t *buf, int len) const
|
||||
{
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_pkthdr h = {
|
||||
.ts = {},
|
||||
.caplen = (bpf_u_int32)len,
|
||||
.len = (bpf_u_int32)len,
|
||||
};
|
||||
gettimeofday(&h.ts, nullptr);
|
||||
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1])
|
||||
}
|
||||
|
||||
// Start sending
|
||||
return static_cast<int>(write(m_hTAP, buf, len));
|
||||
}
|
||||
|
|
|
@ -11,46 +11,55 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <pcap/pcap.h>
|
||||
#include <net/ethernet.h>
|
||||
#include "devices/device.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#ifndef ETH_FRAME_LEN
|
||||
static const int ETH_FRAME_LEN = 1514;
|
||||
#endif
|
||||
#ifndef ETH_FCS_LEN
|
||||
static const int ETH_FCS_LEN = 4;
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
|
||||
class CTapDriver
|
||||
{
|
||||
static constexpr const char *BRIDGE_NAME = "piscsi_bridge";
|
||||
static const string BRIDGE_NAME;
|
||||
|
||||
public:
|
||||
|
||||
CTapDriver() = default;
|
||||
~CTapDriver();
|
||||
~CTapDriver() = default;
|
||||
CTapDriver(CTapDriver&) = default;
|
||||
CTapDriver& operator=(const CTapDriver&) = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&);
|
||||
void OpenDump(const string& path); // Capture packets
|
||||
void GetMacAddr(uint8_t *mac) const;
|
||||
int Receive(uint8_t *buf);
|
||||
int Send(const uint8_t *buf, int len);
|
||||
bool PendingPackets() const; // Check if there are IP packets available
|
||||
bool Enable() const; // Enable the piscsi0 interface
|
||||
bool Disable() const; // Disable the piscsi0 interface
|
||||
void Flush(); // Purge all of the packets that are waiting to be processed
|
||||
bool Init(const param_map&);
|
||||
void CleanUp() const;
|
||||
|
||||
static uint32_t Crc32(const uint8_t *, int);
|
||||
void GetMacAddr(uint8_t *) const;
|
||||
int Receive(uint8_t *) const;
|
||||
int Send(const uint8_t *, int) const;
|
||||
bool HasPendingPackets() const; // Check if there are IP packets available
|
||||
string IpLink(bool) const; // Enable/Disable the piscsi0 interface
|
||||
void Flush() const; // Purge all of the packets that are waiting to be processed
|
||||
|
||||
static uint32_t Crc32(span<const uint8_t>);
|
||||
|
||||
private:
|
||||
|
||||
static string SetUpEth0(int, const string&);
|
||||
static string SetUpNonEth0(int, int, const string&);
|
||||
static pair<string, string> ExtractAddressAndMask(const string&);
|
||||
|
||||
array<byte, 6> m_MacAddr; // MAC Address
|
||||
|
||||
int m_hTAP = -1; // File handle
|
||||
|
||||
pcap_t *m_pcap = nullptr;
|
||||
pcap_dumper_t *m_pcap_dumper = nullptr;
|
||||
|
||||
// Prioritized comma-separated list of interfaces to create the bridge for
|
||||
vector<string> interfaces;
|
||||
|
||||
|
|
|
@ -3,22 +3,24 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "device.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
Device::Device(PbDeviceType type, int lun) : type(type), lun(lun)
|
||||
{
|
||||
ostringstream os;
|
||||
os << setw(2) << setfill('0') << piscsi_major_version << setw(2) << setfill('0') << piscsi_minor_version;
|
||||
os << setfill('0') << setw(2) << piscsi_major_version << setw(2) << piscsi_minor_version;
|
||||
revision = os.str();
|
||||
}
|
||||
|
||||
|
@ -39,7 +41,7 @@ void Device::SetProtected(bool b)
|
|||
void Device::SetVendor(const string& v)
|
||||
{
|
||||
if (v.empty() || v.length() > 8) {
|
||||
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
|
||||
throw invalid_argument("Vendor '" + v + "' must have between 1 and 8 characters");
|
||||
}
|
||||
|
||||
vendor = v;
|
||||
|
@ -48,7 +50,7 @@ void Device::SetVendor(const string& v)
|
|||
void Device::SetProduct(const string& p, bool force)
|
||||
{
|
||||
if (p.empty() || p.length() > 16) {
|
||||
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
|
||||
throw invalid_argument("Product '" + p + "' must have between 1 and 16 characters");
|
||||
}
|
||||
|
||||
// Changing vital product data is not SCSI compliant
|
||||
|
@ -62,7 +64,7 @@ void Device::SetProduct(const string& p, bool force)
|
|||
void Device::SetRevision(const string& r)
|
||||
{
|
||||
if (r.empty() || r.length() > 4) {
|
||||
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
|
||||
throw invalid_argument("Revision '" + r + "' must have between 1 and 4 characters");
|
||||
}
|
||||
|
||||
revision = r;
|
||||
|
@ -85,7 +87,7 @@ string Device::GetParam(const string& key) const
|
|||
return it == params.end() ? "" : it->second;
|
||||
}
|
||||
|
||||
void Device::SetParams(const unordered_map<string, string>& set_params)
|
||||
void Device::SetParams(const param_map& set_params)
|
||||
{
|
||||
params = default_params;
|
||||
|
||||
|
@ -96,11 +98,11 @@ void Device::SetParams(const unordered_map<string, string>& set_params)
|
|||
|
||||
for (const auto& [key, value] : set_params) {
|
||||
// It is assumed that there are default parameters for all supported parameters
|
||||
if (params.find(key) != params.end()) {
|
||||
if (params.contains(key)) {
|
||||
params[key] = value;
|
||||
}
|
||||
else {
|
||||
LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str())
|
||||
spdlog::warn("Ignored unknown parameter '" + key + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,20 +3,26 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
|
||||
// A combination of device ID and LUN
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
// The map used for storing/passing device parameters
|
||||
using param_map = unordered_map<string, string, piscsi_util::StringHash, equal_to<>>;
|
||||
|
||||
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
|
||||
{
|
||||
const string DEFAULT_VENDOR = "PiSCSI";
|
||||
|
@ -60,10 +66,10 @@ class Device //NOSONAR The number of fields and methods is justified, the comple
|
|||
string revision;
|
||||
|
||||
// The parameters the device was created with
|
||||
unordered_map<string, string> params;
|
||||
param_map params;
|
||||
|
||||
// The default parameters
|
||||
unordered_map<string, string> default_params;
|
||||
param_map default_params;
|
||||
|
||||
// Sense Key and ASC
|
||||
// MSB Reserved (0x00)
|
||||
|
@ -90,14 +96,15 @@ protected:
|
|||
int GetStatusCode() const { return status_code; }
|
||||
|
||||
string GetParam(const string&) const;
|
||||
void SetParams(const unordered_map<string, string>&);
|
||||
void SetParams(const param_map&);
|
||||
|
||||
public:
|
||||
|
||||
virtual ~Device() = default;
|
||||
|
||||
PbDeviceType GetType() const { return type; }
|
||||
const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); }
|
||||
string GetTypeString() const { return PbDeviceType_Name(type); }
|
||||
string GetIdentifier() const { return GetTypeString() + " " + to_string(GetId()) + ":" + to_string(lun); }
|
||||
|
||||
bool IsReady() const { return ready; }
|
||||
virtual void Reset();
|
||||
|
@ -131,8 +138,8 @@ public:
|
|||
bool SupportsFile() const { return supports_file; }
|
||||
void SupportsParams(bool b) { supports_params = b; }
|
||||
void SupportsFile(bool b) { supports_file = b; }
|
||||
unordered_map<string, string> GetParams() const { return params; }
|
||||
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
|
||||
auto GetParams() const { return params; }
|
||||
void SetDefaultParams(const param_map& p) { default_params = p; }
|
||||
|
||||
void SetStatusCode(int s) { status_code = s; }
|
||||
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/network_util.h"
|
||||
#include "scsihd.h"
|
||||
#include "scsihd_nec.h"
|
||||
#include "scsimo.h"
|
||||
|
@ -17,15 +17,10 @@
|
|||
#include "scsi_daynaport.h"
|
||||
#include "host_services.h"
|
||||
#include "device_factory.h"
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
using namespace piscsi_util;
|
||||
using namespace network_util;
|
||||
|
||||
DeviceFactory::DeviceFactory()
|
||||
{
|
||||
|
@ -34,19 +29,11 @@ DeviceFactory::DeviceFactory()
|
|||
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCCD] = { 512, 2048};
|
||||
|
||||
string network_interfaces;
|
||||
for (const auto& network_interface : GetNetworkInterfaces()) {
|
||||
if (network_interface.rfind("dummy", 0) == string::npos) {
|
||||
if (!network_interfaces.empty()) {
|
||||
network_interfaces += ",";
|
||||
}
|
||||
network_interfaces += network_interface;
|
||||
}
|
||||
}
|
||||
const string interfaces = Join(GetNetworkInterfaces(), ",");
|
||||
|
||||
default_params[SCBR]["interface"] = network_interfaces;
|
||||
default_params[SCBR]["interface"] = interfaces;
|
||||
default_params[SCBR]["inet"] = DEFAULT_IP;
|
||||
default_params[SCDP]["interface"] = network_interfaces;
|
||||
default_params[SCDP]["interface"] = interfaces;
|
||||
default_params[SCDP]["inet"] = DEFAULT_IP;
|
||||
default_params[SCLP]["cmd"] = "lp -oraw %f";
|
||||
|
||||
|
@ -96,8 +83,8 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
|
||||
device = make_shared<SCSIHD_NEC>(lun);
|
||||
} else {
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCHD)->second, false,
|
||||
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, false,
|
||||
ext == "hd1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
|
||||
|
||||
// Some Apple tools require a particular drive identification
|
||||
if (ext == "hda") {
|
||||
|
@ -109,18 +96,18 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
}
|
||||
|
||||
case SCRM:
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes.find(SCRM)->second, true);
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes.find(type)->second, true);
|
||||
device->SetProduct("SCSI HD (REM.)");
|
||||
break;
|
||||
|
||||
case SCMO:
|
||||
device = make_shared<SCSIMO>(lun, sector_sizes.find(SCMO)->second);
|
||||
device = make_shared<SCSIMO>(lun, sector_sizes.find(type)->second);
|
||||
device->SetProduct("SCSI MO");
|
||||
break;
|
||||
|
||||
case SCCD:
|
||||
device = make_shared<SCSICD>(lun, sector_sizes.find(SCCD)->second,
|
||||
GetExtensionLowerCase(filename) == "is1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
|
||||
device = make_shared<SCSICD>(lun, sector_sizes.find(type)->second,
|
||||
GetExtensionLowerCase(filename) == "is1" ? scsi_level::scsi_1_ccs : scsi_level::scsi_2);
|
||||
device->SetProduct("SCSI CD-ROM");
|
||||
break;
|
||||
|
||||
|
@ -128,7 +115,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
device = make_shared<SCSIBR>(lun);
|
||||
// Since this is an emulation for a specific driver the product name has to be set accordingly
|
||||
device->SetProduct("RASCSI BRIDGE");
|
||||
device->SetDefaultParams(default_params.find(SCBR)->second);
|
||||
device->SetDefaultParams(default_params.find(type)->second);
|
||||
break;
|
||||
|
||||
case SCDP:
|
||||
|
@ -137,7 +124,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
device->SetVendor("Dayna");
|
||||
device->SetProduct("SCSI/Link");
|
||||
device->SetRevision("1.4a");
|
||||
device->SetDefaultParams(default_params.find(SCDP)->second);
|
||||
device->SetDefaultParams(default_params.find(type)->second);
|
||||
break;
|
||||
|
||||
case SCHS:
|
||||
|
@ -150,7 +137,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
case SCLP:
|
||||
device = make_shared<SCSIPrinter>(lun);
|
||||
device->SetProduct("SCSI PRINTER");
|
||||
device->SetDefaultParams(default_params.find(SCLP)->second);
|
||||
device->SetDefaultParams(default_params.find(type)->second);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -163,44 +150,11 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(PbDeviceType type, int lun
|
|||
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
|
||||
{
|
||||
const auto& it = sector_sizes.find(type);
|
||||
return it != sector_sizes.end() ? it->second : empty_set;
|
||||
return it != sector_sizes.end() ? it->second : EMPTY_SET;
|
||||
}
|
||||
|
||||
const unordered_map<string, string>& DeviceFactory::GetDefaultParams(PbDeviceType type) const
|
||||
const param_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const
|
||||
{
|
||||
const auto& it = default_params.find(type);
|
||||
return it != default_params.end() ? it->second : empty_map;
|
||||
}
|
||||
|
||||
vector<string> DeviceFactory::GetNetworkInterfaces() const
|
||||
{
|
||||
vector<string> network_interfaces;
|
||||
|
||||
#ifdef __linux__
|
||||
ifaddrs *addrs;
|
||||
getifaddrs(&addrs);
|
||||
ifaddrs *tmp = addrs;
|
||||
|
||||
while (tmp) {
|
||||
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET &&
|
||||
strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "piscsi_bridge")) {
|
||||
const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
|
||||
ifreq ifr = {};
|
||||
strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here
|
||||
// Only list interfaces that are up
|
||||
if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) {
|
||||
network_interfaces.emplace_back(tmp->ifa_name);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
tmp = tmp->ifa_next;
|
||||
}
|
||||
|
||||
freeifaddrs(addrs);
|
||||
#endif
|
||||
|
||||
return network_interfaces;
|
||||
return it != default_params.end() ? it->second : EMPTY_PARAM_MAP;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
// The DeviceFactory creates devices based on their type and the image file extension
|
||||
//
|
||||
|
@ -11,8 +11,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "devices/device.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
|
@ -34,20 +35,19 @@ public:
|
|||
shared_ptr<PrimaryDevice> CreateDevice(PbDeviceType, int, const string&) const;
|
||||
PbDeviceType GetTypeForFile(const string&) const;
|
||||
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
|
||||
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) const;
|
||||
vector<string> GetNetworkInterfaces() const;
|
||||
const unordered_map<string, PbDeviceType>& GetExtensionMapping() const { return extension_mapping; }
|
||||
const param_map& GetDefaultParams(PbDeviceType type) const;
|
||||
const auto& GetExtensionMapping() const { return extension_mapping; }
|
||||
|
||||
private:
|
||||
|
||||
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
|
||||
|
||||
unordered_map<PbDeviceType, unordered_map<string, string>> default_params;
|
||||
unordered_map<PbDeviceType, param_map> default_params;
|
||||
|
||||
unordered_map<string, PbDeviceType> extension_mapping;
|
||||
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> extension_mapping;
|
||||
|
||||
unordered_map<string, PbDeviceType> device_mapping;
|
||||
unordered_map<string, PbDeviceType, piscsi_util::StringHash, equal_to<>> device_mapping;
|
||||
|
||||
unordered_set<uint32_t> empty_set;
|
||||
unordered_map<string, string> empty_map;
|
||||
inline static const unordered_set<uint32_t> EMPTY_SET;
|
||||
inline static const param_map EMPTY_PARAM_MAP;
|
||||
};
|
||||
|
|
|
@ -3,63 +3,52 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "device_logger.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace spdlog;
|
||||
|
||||
void DeviceLogger::Trace(const string& message) const
|
||||
{
|
||||
if (const string m = GetLogMessage(message); !m.empty()) {
|
||||
LOGTRACE("%s", m.c_str())
|
||||
}
|
||||
Log(level::trace, message);
|
||||
}
|
||||
|
||||
void DeviceLogger::Debug(const string& message) const
|
||||
{
|
||||
if (const string m = GetLogMessage(message); !m.empty()) {
|
||||
LOGDEBUG("%s", m.c_str())
|
||||
}
|
||||
Log(level::debug, message);
|
||||
}
|
||||
|
||||
void DeviceLogger::Info(const string& message) const
|
||||
{
|
||||
if (const string m = GetLogMessage(message); !m.empty()) {
|
||||
LOGINFO("%s", m.c_str())
|
||||
}
|
||||
Log(level::info, message);
|
||||
}
|
||||
|
||||
void DeviceLogger::Warn(const string& message) const
|
||||
{
|
||||
if (const string m = GetLogMessage(message); !m.empty()) {
|
||||
LOGWARN("%s", m.c_str())
|
||||
}
|
||||
Log(level::warn, message);
|
||||
}
|
||||
|
||||
void DeviceLogger::Error(const string& message) const
|
||||
{
|
||||
if (const string m = GetLogMessage(message); !m.empty()) {
|
||||
LOGERROR("%s", m.c_str())
|
||||
}
|
||||
Log(level::err, message);
|
||||
}
|
||||
|
||||
string DeviceLogger::GetLogMessage(const string& message) const
|
||||
void DeviceLogger::Log(level::level_enum level, const string& message) const
|
||||
{
|
||||
if (log_device_id == -1 || (log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))
|
||||
{
|
||||
if (!message.empty() &&
|
||||
(log_device_id == -1 ||
|
||||
(log_device_id == id && (log_device_lun == -1 || log_device_lun == lun)))) {
|
||||
if (lun == -1) {
|
||||
return "(ID " + to_string(id) + ") - " + message;
|
||||
log(level, "(ID " + to_string(id) + ") - " + message);
|
||||
}
|
||||
else {
|
||||
return "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message;
|
||||
log(level, "(ID:LUN " + to_string(id) + ":" + to_string(lun) + ") - " + message);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void DeviceLogger::SetIdAndLun(int i, int l)
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
@ -27,14 +28,13 @@ public:
|
|||
void Warn(const string&) const;
|
||||
void Error(const string&) const;
|
||||
|
||||
string GetLogMessage(const string&) const;
|
||||
|
||||
void SetIdAndLun(int, int);
|
||||
|
||||
static void SetLogIdAndLun(int, int);
|
||||
|
||||
private:
|
||||
|
||||
void Log(spdlog::level::level_enum, const string&) const;
|
||||
|
||||
int id = -1;
|
||||
int lun = -1;
|
||||
|
||||
|
|
|
@ -23,15 +23,7 @@
|
|||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
Disk::~Disk()
|
||||
{
|
||||
// Save disk cache, only if ready
|
||||
if (IsReady() && cache != nullptr) {
|
||||
cache->Save();
|
||||
}
|
||||
}
|
||||
|
||||
bool Disk::Init(const unordered_map<string, string>& params)
|
||||
bool Disk::Init(const param_map& params)
|
||||
{
|
||||
StorageDevice::Init(params);
|
||||
|
||||
|
@ -64,6 +56,13 @@ bool Disk::Init(const unordered_map<string, string>& params)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Disk::CleanUp()
|
||||
{
|
||||
FlushCache();
|
||||
|
||||
StorageDevice::CleanUp();
|
||||
}
|
||||
|
||||
void Disk::Dispatch(scsi_command cmd)
|
||||
{
|
||||
// Media changes must be reported on the next access, i.e. not only for TEST UNIT READY
|
||||
|
@ -72,7 +71,7 @@ void Disk::Dispatch(scsi_command cmd)
|
|||
|
||||
SetMediumChanged(false);
|
||||
|
||||
GetController()->Error(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
|
||||
GetController()->Error(sense_key::unit_attention, asc::not_ready_to_ready_change);
|
||||
}
|
||||
else {
|
||||
PrimaryDevice::Dispatch(cmd);
|
||||
|
@ -93,7 +92,7 @@ void Disk::ResizeCache(const string& path, bool raw)
|
|||
|
||||
void Disk::FlushCache()
|
||||
{
|
||||
if (cache != nullptr) {
|
||||
if (cache != nullptr && IsReady()) {
|
||||
cache->Save();
|
||||
}
|
||||
}
|
||||
|
@ -103,8 +102,8 @@ void Disk::FormatUnit()
|
|||
CheckReady();
|
||||
|
||||
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
|
||||
if ((GetController()->GetCmd(1) & 0x10) != 0 && GetController()->GetCmd(4) != 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if ((GetController()->GetCmdByte(1) & 0x10) != 0 && GetController()->GetCmdByte(4) != 0) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
@ -115,9 +114,9 @@ void Disk::Read(access_mode mode)
|
|||
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
|
||||
if (valid) {
|
||||
GetController()->SetBlocks(blocks);
|
||||
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
|
||||
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
|
||||
|
||||
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
|
||||
LogTrace("Length is " + to_string(GetController()->GetLength()));
|
||||
|
||||
// Set next block
|
||||
GetController()->SetNext(start + 1);
|
||||
|
@ -135,7 +134,7 @@ void Disk::ReadWriteLong10() const
|
|||
|
||||
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
|
||||
if (GetInt16(GetController()->GetCmd(), 7) != 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
@ -147,7 +146,7 @@ void Disk::ReadWriteLong16() const
|
|||
|
||||
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
|
||||
if (GetInt16(GetController()->GetCmd(), 12) != 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
@ -156,7 +155,7 @@ void Disk::ReadWriteLong16() const
|
|||
void Disk::Write(access_mode mode) const
|
||||
{
|
||||
if (IsProtected()) {
|
||||
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
|
||||
throw scsi_exception(sense_key::data_protect, asc::write_protected);
|
||||
}
|
||||
|
||||
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
|
||||
|
@ -179,14 +178,14 @@ void Disk::Verify(access_mode mode)
|
|||
const auto& [valid, start, blocks] = CheckAndGetStartAndCount(mode);
|
||||
if (valid) {
|
||||
// if BytChk=0
|
||||
if ((GetController()->GetCmd(1) & 0x02) == 0) {
|
||||
if ((GetController()->GetCmdByte(1) & 0x02) == 0) {
|
||||
Seek();
|
||||
return;
|
||||
}
|
||||
|
||||
// Test reading
|
||||
GetController()->SetBlocks(blocks);
|
||||
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), start));
|
||||
GetController()->SetLength(Read(GetController()->GetBuffer(), start));
|
||||
|
||||
// Set next block
|
||||
GetController()->SetNext(start + 1);
|
||||
|
@ -200,14 +199,14 @@ void Disk::Verify(access_mode mode)
|
|||
|
||||
void Disk::StartStopUnit()
|
||||
{
|
||||
const bool start = GetController()->GetCmd(4) & 0x01;
|
||||
const bool load = GetController()->GetCmd(4) & 0x02;
|
||||
const bool start = GetController()->GetCmdByte(4) & 0x01;
|
||||
const bool load = GetController()->GetCmdByte(4) & 0x02;
|
||||
|
||||
if (load) {
|
||||
GetLogger().Trace(start ? "Loading medium" : "Ejecting medium");
|
||||
LogTrace(start ? "Loading medium" : "Ejecting medium");
|
||||
}
|
||||
else {
|
||||
GetLogger().Trace(start ? "Starting unit" : "Stopping unit");
|
||||
LogTrace(start ? "Starting unit" : "Stopping unit");
|
||||
|
||||
SetStopped(!start);
|
||||
}
|
||||
|
@ -217,12 +216,12 @@ void Disk::StartStopUnit()
|
|||
if (load) {
|
||||
if (IsLocked()) {
|
||||
// Cannot be ejected because it is locked
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
|
||||
}
|
||||
|
||||
// Eject
|
||||
if (!Eject(false)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::load_or_eject_failed);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -237,9 +236,9 @@ void Disk::PreventAllowMediumRemoval()
|
|||
{
|
||||
CheckReady();
|
||||
|
||||
const bool lock = GetController()->GetCmd(4) & 0x01;
|
||||
const bool lock = GetController()->GetCmdByte(4) & 0x01;
|
||||
|
||||
GetLogger().Trace(lock ? "Locking medium" : "Unlocking medium");
|
||||
LogTrace(lock ? "Locking medium" : "Unlocking medium");
|
||||
|
||||
SetLocked(lock);
|
||||
|
||||
|
@ -279,7 +278,7 @@ bool Disk::Eject(bool force)
|
|||
return status;
|
||||
}
|
||||
|
||||
int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
|
||||
int Disk::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
|
||||
{
|
||||
// Get length, clear buffer
|
||||
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
|
||||
|
@ -315,7 +314,7 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
|
|||
return size;
|
||||
}
|
||||
|
||||
int Disk::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
|
||||
int Disk::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
|
||||
{
|
||||
// Get length, clear buffer
|
||||
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
|
||||
|
@ -497,27 +496,27 @@ void Disk::AddCachePage(map<int, vector<byte>>& pages, bool changeable) const
|
|||
pages[8] = buf;
|
||||
}
|
||||
|
||||
int Disk::Read(const vector<int>&, vector<uint8_t>& buf, uint64_t block)
|
||||
int Disk::Read(span<uint8_t> buf, uint64_t block)
|
||||
{
|
||||
assert(block < GetBlockCount());
|
||||
|
||||
CheckReady();
|
||||
|
||||
if (!cache->ReadSector(buf, static_cast<uint32_t>(block))) {
|
||||
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
|
||||
throw scsi_exception(sense_key::medium_error, asc::read_fault);
|
||||
}
|
||||
|
||||
return GetSectorSizeInBytes();
|
||||
}
|
||||
|
||||
void Disk::Write(const vector<int>&, const vector<uint8_t>& buf, uint64_t block)
|
||||
void Disk::Write(span<const uint8_t> buf, uint64_t block)
|
||||
{
|
||||
assert(block < GetBlockCount());
|
||||
|
||||
CheckReady();
|
||||
|
||||
if (!cache->WriteSector(buf, static_cast<uint32_t>(block))) {
|
||||
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT);
|
||||
throw scsi_exception(sense_key::medium_error, asc::write_fault);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -553,7 +552,7 @@ void Disk::ReadCapacity10()
|
|||
CheckReady();
|
||||
|
||||
if (GetBlockCount() == 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
|
||||
}
|
||||
|
||||
vector<uint8_t>& buf = GetController()->GetBuffer();
|
||||
|
@ -580,7 +579,7 @@ void Disk::ReadCapacity16()
|
|||
CheckReady();
|
||||
|
||||
if (GetBlockCount() == 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::medium_not_present);
|
||||
}
|
||||
|
||||
vector<uint8_t>& buf = GetController()->GetBuffer();
|
||||
|
@ -604,7 +603,7 @@ void Disk::ReadCapacity16()
|
|||
void Disk::ReadCapacity16_ReadLong16()
|
||||
{
|
||||
// The service action determines the actual command
|
||||
switch (GetController()->GetCmd(1) & 0x1f) {
|
||||
switch (GetController()->GetCmdByte(1) & 0x1f) {
|
||||
case 0x10:
|
||||
ReadCapacity16();
|
||||
break;
|
||||
|
@ -614,7 +613,7 @@ void Disk::ReadCapacity16_ReadLong16()
|
|||
break;
|
||||
|
||||
default:
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -624,9 +623,9 @@ void Disk::ValidateBlockAddress(access_mode mode) const
|
|||
const uint64_t block = mode == RW16 ? GetInt64(GetController()->GetCmd(), 2) : GetInt32(GetController()->GetCmd(), 2);
|
||||
|
||||
if (block > GetBlockCount()) {
|
||||
GetLogger().Trace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
|
||||
LogTrace("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block "
|
||||
+ to_string(block));
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -638,7 +637,7 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
|
|||
if (mode == RW6 || mode == SEEK6) {
|
||||
start = GetInt24(GetController()->GetCmd(), 1);
|
||||
|
||||
count = GetController()->GetCmd(4);
|
||||
count = GetController()->GetCmdByte(4);
|
||||
if (!count) {
|
||||
count= 0x100;
|
||||
}
|
||||
|
@ -659,13 +658,13 @@ tuple<bool, uint64_t, uint32_t> Disk::CheckAndGetStartAndCount(access_mode mode)
|
|||
|
||||
stringstream s;
|
||||
s << "READ/WRITE/VERIFY/SEEK, start block: $" << setfill('0') << setw(8) << hex << start;
|
||||
GetLogger().Trace(s.str() + ", blocks: " + to_string(count));
|
||||
LogTrace(s.str() + ", blocks: " + to_string(count));
|
||||
|
||||
// Check capacity
|
||||
if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) {
|
||||
GetLogger().Trace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
|
||||
LogTrace("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
|
||||
+ to_string(start) + ", block count " + to_string(count));
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
|
||||
}
|
||||
|
||||
// Do not process 0 blocks
|
||||
|
@ -689,10 +688,8 @@ uint32_t Disk::GetSectorSizeInBytes() const
|
|||
|
||||
void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes)
|
||||
{
|
||||
DeviceFactory device_factory;
|
||||
if (const auto& sizes = device_factory.GetSectorSizes(GetType());
|
||||
!sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) {
|
||||
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
|
||||
if (DeviceFactory device_factory; !device_factory.GetSectorSizes(GetType()).contains(size_in_bytes)) {
|
||||
throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)");
|
||||
}
|
||||
|
||||
size_shift_count = CalculateShiftCount(size_in_bytes);
|
||||
|
@ -706,12 +703,11 @@ uint32_t Disk::GetConfiguredSectorSize() const
|
|||
|
||||
bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t configured_size)
|
||||
{
|
||||
if (unordered_set<uint32_t> sizes = device_factory.GetSectorSizes(GetType());
|
||||
sizes.find(configured_size) == sizes.end()) {
|
||||
if (!device_factory.GetSectorSizes(GetType()).contains(configured_size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
configured_sector_size = configured_size;
|
||||
configured_sector_size = configured_size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "interfaces/scsi_block_commands.h"
|
||||
#include "storage_device.h"
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <tuple>
|
||||
|
@ -43,18 +44,18 @@ class Disk : public StorageDevice, private ScsiBlockCommands
|
|||
|
||||
public:
|
||||
|
||||
Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) {}
|
||||
~Disk() override;
|
||||
using StorageDevice::StorageDevice;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
void CleanUp() override;
|
||||
|
||||
void Dispatch(scsi_command) override;
|
||||
|
||||
bool Eject(bool) override;
|
||||
|
||||
virtual void Write(const vector<int>&, const vector<uint8_t>&, uint64_t);
|
||||
virtual void Write(span<const uint8_t>, uint64_t);
|
||||
|
||||
virtual int Read(const vector<int>&, vector<uint8_t>& , uint64_t);
|
||||
virtual int Read(span<uint8_t> , uint64_t);
|
||||
|
||||
uint32_t GetSectorSizeInBytes() const;
|
||||
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); }
|
||||
|
@ -92,8 +93,8 @@ private:
|
|||
void ValidateBlockAddress(access_mode) const;
|
||||
tuple<bool, uint64_t, uint32_t> CheckAndGetStartAndCount(access_mode) const;
|
||||
|
||||
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
|
||||
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
|
||||
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
|
||||
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
|
||||
|
||||
static inline const unordered_map<uint32_t, uint32_t> shift_counts =
|
||||
{ { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } };
|
||||
|
|
|
@ -30,7 +30,7 @@ DiskCache::DiskCache(const string& path, int size, uint32_t blocks, off_t imgoff
|
|||
bool DiskCache::Save() const
|
||||
{
|
||||
// Save valid tracks
|
||||
return none_of(cache.begin(), cache.end(), [this](const cache_t& c)
|
||||
return ranges::none_of(cache.begin(), cache.end(), [this](const cache_t& c)
|
||||
{ return c.disktrk != nullptr && !c.disktrk->Save(sec_path); });
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
|
|||
return Assign(track);
|
||||
}
|
||||
|
||||
bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
|
||||
bool DiskCache::ReadSector(span<uint8_t> buf, uint32_t block)
|
||||
{
|
||||
shared_ptr<DiskTrack> disktrk = GetTrack(block);
|
||||
if (disktrk == nullptr) {
|
||||
|
@ -57,7 +57,7 @@ bool DiskCache::ReadSector(vector<uint8_t>& buf, uint32_t block)
|
|||
return disktrk->ReadSector(buf, block & 0xff);
|
||||
}
|
||||
|
||||
bool DiskCache::WriteSector(const vector<uint8_t>& buf, uint32_t block)
|
||||
bool DiskCache::WriteSector(span<const uint8_t> buf, uint32_t block)
|
||||
{
|
||||
shared_ptr<DiskTrack> disktrk = GetTrack(block);
|
||||
if (disktrk == nullptr) {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -41,8 +42,8 @@ public:
|
|||
|
||||
// Access
|
||||
bool Save() const; // Save and release all
|
||||
bool ReadSector(vector<uint8_t>&, uint32_t); // Sector Read
|
||||
bool WriteSector(const vector<uint8_t>&, uint32_t); // Sector Write
|
||||
bool ReadSector(span<uint8_t>, uint32_t); // Sector Read
|
||||
bool WriteSector(span<const uint8_t>, uint32_t); // Sector Write
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "disk_track.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
|
||||
DiskTrack::~DiskTrack()
|
||||
|
@ -75,7 +77,7 @@ bool DiskTrack::Load(const string& path)
|
|||
|
||||
if (dt.buffer == nullptr) {
|
||||
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||
LOGWARN("posix_memalign failed")
|
||||
spdlog::warn("posix_memalign failed");
|
||||
}
|
||||
dt.length = length;
|
||||
}
|
||||
|
@ -88,14 +90,14 @@ bool DiskTrack::Load(const string& path)
|
|||
if (dt.length != static_cast<uint32_t>(length)) {
|
||||
free(dt.buffer);
|
||||
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||
LOGWARN("posix_memalign failed")
|
||||
spdlog::warn("posix_memalign failed");
|
||||
}
|
||||
dt.length = length;
|
||||
}
|
||||
|
||||
// Resize and clear changemap
|
||||
dt.changemap.resize(dt.sectors);
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false);
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
|
||||
|
||||
ifstream in(path, ios::binary);
|
||||
if (in.fail()) {
|
||||
|
@ -209,13 +211,13 @@ bool DiskTrack::Save(const string& path)
|
|||
}
|
||||
|
||||
// Drop the change flag and exit
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false);
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false); //NOSONAR ranges::fill() cannot be applied to vector<bool>
|
||||
dt.changed = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
|
||||
bool DiskTrack::ReadSector(span<uint8_t> buf, int sec) const
|
||||
{
|
||||
assert(sec >= 0 && sec < 0x100);
|
||||
|
||||
|
@ -238,7 +240,7 @@ bool DiskTrack::ReadSector(vector<uint8_t>& buf, int sec) const
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DiskTrack::WriteSector(const vector<uint8_t>& buf, int sec)
|
||||
bool DiskTrack::WriteSector(span<const uint8_t> buf, int sec)
|
||||
{
|
||||
assert((sec >= 0) && (sec < 0x100));
|
||||
assert(!dt.raw);
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
|
@ -52,8 +54,8 @@ private:
|
|||
bool Save(const string& path);
|
||||
|
||||
// Read / Write
|
||||
bool ReadSector(vector<uint8_t>&, int) const; // Sector Read
|
||||
bool WriteSector(const vector<uint8_t>& buf, int); // Sector Write
|
||||
bool ReadSector(span<uint8_t>, int) const; // Sector Read
|
||||
bool WriteSector(span<const uint8_t> buf, int); // Sector Write
|
||||
|
||||
int GetTrack() const { return dt.track; } // Get track
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Host Services with realtime clock and shutdown support
|
||||
//
|
||||
|
@ -25,11 +25,13 @@
|
|||
#include "scsi_command_util.h"
|
||||
#include "host_services.h"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
bool HostServices::Init(const unordered_map<string, string>& params)
|
||||
bool HostServices::Init(const param_map& params)
|
||||
{
|
||||
ModePageDevice::Init(params);
|
||||
|
||||
|
@ -49,13 +51,13 @@ void HostServices::TestUnitReady()
|
|||
|
||||
vector<uint8_t> HostServices::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
|
||||
return HandleInquiry(device_type::processor, scsi_level::spc_3, false);
|
||||
}
|
||||
|
||||
void HostServices::StartStopUnit() const
|
||||
{
|
||||
const bool start = GetController()->GetCmd(4) & 0x01;
|
||||
const bool load = GetController()->GetCmd(4) & 0x02;
|
||||
const bool start = GetController()->GetCmdByte(4) & 0x01;
|
||||
const bool load = GetController()->GetCmdByte(4) & 0x02;
|
||||
|
||||
if (!start) {
|
||||
if (load) {
|
||||
|
@ -69,17 +71,17 @@ void HostServices::StartStopUnit() const
|
|||
GetController()->ScheduleShutdown(AbstractController::piscsi_shutdown_mode::RESTART_PI);
|
||||
}
|
||||
else {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
}
|
||||
|
||||
int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
|
||||
int HostServices::ModeSense6(cdb_t cdb, vector<uint8_t>& buf) const
|
||||
{
|
||||
// Block descriptors cannot be returned
|
||||
if (!(cdb[1] & 0x08)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(cdb[4])));
|
||||
|
@ -93,11 +95,11 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<uint8_t>& buf) const
|
|||
return size;
|
||||
}
|
||||
|
||||
int HostServices::ModeSense10(const vector<int>& cdb, vector<uint8_t>& buf) const
|
||||
int HostServices::ModeSense10(cdb_t cdb, vector<uint8_t>& buf) const
|
||||
{
|
||||
// Block descriptors cannot be returned
|
||||
if (!(cdb[1] & 0x08)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
const auto length = static_cast<int>(min(buf.size(), static_cast<size_t>(GetInt16(cdb, 7))));
|
||||
|
@ -123,7 +125,8 @@ void HostServices::AddRealtimeClockPage(map<int, vector<byte>>& pages, bool chan
|
|||
pages[32] = vector<byte>(10);
|
||||
|
||||
if (!changeable) {
|
||||
time_t t = time(nullptr);
|
||||
const auto now = system_clock::now();
|
||||
const time_t t = system_clock::to_time_t(now);
|
||||
tm localtime;
|
||||
localtime_r(&t, &localtime);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Host Services with realtime clock and shutdown support
|
||||
//
|
||||
|
@ -12,6 +12,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "mode_page_device.h"
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
@ -23,7 +24,7 @@ public:
|
|||
explicit HostServices(int lun) : ModePageDevice(SCHS, lun) {}
|
||||
~HostServices() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
void TestUnitReady() override;
|
||||
|
@ -48,8 +49,8 @@ private:
|
|||
};
|
||||
|
||||
void StartStopUnit() const;
|
||||
int ModeSense6(const vector<int>&, vector<uint8_t>&) const override;
|
||||
int ModeSense10(const vector<int>&, vector<uint8_t>&) const override;
|
||||
int ModeSense6(cdb_t, vector<uint8_t>&) const override;
|
||||
int ModeSense10(cdb_t, vector<uint8_t>&) const override;
|
||||
|
||||
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// A basic device with mode page support, to be used for subclassing
|
||||
//
|
||||
|
@ -20,7 +20,7 @@ using namespace std;
|
|||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
bool ModePageDevice::Init(const unordered_map<string, string>& params)
|
||||
bool ModePageDevice::Init(const param_map& params)
|
||||
{
|
||||
PrimaryDevice::Init(params);
|
||||
|
||||
|
@ -32,7 +32,7 @@ bool ModePageDevice::Init(const unordered_map<string, string>& params)
|
|||
return true;
|
||||
}
|
||||
|
||||
int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
|
||||
int ModePageDevice::AddModePages(cdb_t cdb, vector<uint8_t>& buf, int offset, int length, int max_size) const
|
||||
{
|
||||
const int max_length = length - offset;
|
||||
if (max_length < 0) {
|
||||
|
@ -46,7 +46,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
|
|||
|
||||
stringstream s;
|
||||
s << "Requesting mode page $" << setfill('0') << setw(2) << hex << page;
|
||||
GetLogger().Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
// Mode page data mapped to the respective page numbers, C++ maps are ordered by key
|
||||
map<int, vector<byte>> pages;
|
||||
|
@ -54,8 +54,8 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
|
|||
|
||||
if (pages.empty()) {
|
||||
s << "Unsupported mode page $" << page;
|
||||
GetLogger().Trace(s.str());
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
LogTrace(s.str());
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Holds all mode page data
|
||||
|
@ -90,7 +90,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<uint8_t>& buf, i
|
|||
}
|
||||
|
||||
if (static_cast<int>(result.size()) > max_size) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
const auto size = static_cast<int>(min(static_cast<size_t>(max_length), result.size()));
|
||||
|
@ -114,15 +114,15 @@ void ModePageDevice::ModeSense10() const
|
|||
EnterDataInPhase();
|
||||
}
|
||||
|
||||
void ModePageDevice::ModeSelect(scsi_command, const vector<int>&, const vector<uint8_t>&, int) const
|
||||
void ModePageDevice::ModeSelect(scsi_command, cdb_t, span<const uint8_t>, int) const
|
||||
{
|
||||
// There is no default implementation of MDOE SELECT
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
// There is no default implementation of MODE SELECT
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
|
||||
}
|
||||
|
||||
void ModePageDevice::ModeSelect6() const
|
||||
{
|
||||
SaveParametersCheck(GetController()->GetCmd(4));
|
||||
SaveParametersCheck(GetController()->GetCmdByte(4));
|
||||
}
|
||||
|
||||
void ModePageDevice::ModeSelect10() const
|
||||
|
@ -134,8 +134,8 @@ void ModePageDevice::ModeSelect10() const
|
|||
|
||||
void ModePageDevice::SaveParametersCheck(int length) const
|
||||
{
|
||||
if (!SupportsSaveParameters() && (GetController()->GetCmd(1) & 0x01)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if (!SupportsSaveParameters() && (GetController()->GetCmdByte(1) & 0x01)) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
GetController()->SetLength(length);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -11,25 +11,25 @@
|
|||
|
||||
#include "primary_device.h"
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
// TODO Maybe this should better be a mixin class because not all storage devicess have mode pages
|
||||
class ModePageDevice : public PrimaryDevice
|
||||
{
|
||||
public:
|
||||
|
||||
using PrimaryDevice::PrimaryDevice;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
|
||||
virtual void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const;
|
||||
virtual void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const;
|
||||
|
||||
protected:
|
||||
|
||||
bool SupportsSaveParameters() const { return supports_save_parameters; }
|
||||
void SupportsSaveParameters(bool b) { supports_save_parameters = b; }
|
||||
int AddModePages(const vector<int>&, vector<uint8_t>&, int, int, int) const;
|
||||
int AddModePages(cdb_t, vector<uint8_t>&, int, int, int) const;
|
||||
virtual void SetUpModePages(map<int, vector<byte>>&, int, bool) const = 0;
|
||||
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const {
|
||||
// Nothing to add by default
|
||||
|
@ -39,8 +39,8 @@ private:
|
|||
|
||||
bool supports_save_parameters = false;
|
||||
|
||||
virtual int ModeSense6(const vector<int>&, vector<uint8_t>&) const = 0;
|
||||
virtual int ModeSense10(const vector<int>&, vector<uint8_t>&) const = 0;
|
||||
virtual int ModeSense6(cdb_t, vector<uint8_t>&) const = 0;
|
||||
virtual int ModeSense10(cdb_t, vector<uint8_t>&) const = 0;
|
||||
|
||||
void ModeSense6() const;
|
||||
void ModeSense10() const;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -17,7 +17,7 @@ using namespace std;
|
|||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
bool PrimaryDevice::Init(const unordered_map<string, string>& params)
|
||||
bool PrimaryDevice::Init(const param_map& params)
|
||||
{
|
||||
// Mandatory SCSI primary commands
|
||||
AddCommand(scsi_command::eCmdTestUnitReady, [this] { TestUnitReady(); });
|
||||
|
@ -35,9 +35,9 @@ bool PrimaryDevice::Init(const unordered_map<string, string>& params)
|
|||
return true;
|
||||
}
|
||||
|
||||
void PrimaryDevice::AddCommand(scsi_command opcode, const operation& execute)
|
||||
void PrimaryDevice::AddCommand(scsi_command cmd, const operation& execute)
|
||||
{
|
||||
commands[opcode] = execute;
|
||||
commands[cmd] = execute;
|
||||
}
|
||||
|
||||
void PrimaryDevice::Dispatch(scsi_command cmd)
|
||||
|
@ -46,15 +46,14 @@ void PrimaryDevice::Dispatch(scsi_command cmd)
|
|||
s << "$" << setfill('0') << setw(2) << hex << static_cast<int>(cmd);
|
||||
|
||||
if (const auto& it = commands.find(cmd); it != commands.end()) {
|
||||
GetLogger().Debug("Device is executing " + string(command_mapping.find(cmd)->second.second) +
|
||||
" (" + s.str() + ")");
|
||||
LogDebug("Device is executing " + command_mapping.find(cmd)->second.second + " (" + s.str() + ")");
|
||||
|
||||
it->second();
|
||||
}
|
||||
else {
|
||||
GetLogger().Trace("Received unsupported command: " + s.str());
|
||||
LogTrace("Received unsupported command: " + s.str());
|
||||
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,18 +66,14 @@ void PrimaryDevice::Reset()
|
|||
|
||||
int PrimaryDevice::GetId() const
|
||||
{
|
||||
if (GetController() == nullptr) {
|
||||
GetLogger().Error("Device is missing its controller");
|
||||
}
|
||||
|
||||
return GetController() != nullptr ? GetController()->GetTargetId() : -1;
|
||||
}
|
||||
|
||||
void PrimaryDevice::SetController(shared_ptr<AbstractController> c)
|
||||
void PrimaryDevice::SetController(AbstractController *c)
|
||||
{
|
||||
controller = c;
|
||||
|
||||
logger.SetIdAndLun(c != nullptr ? c->GetTargetId() : -1, GetLun());
|
||||
device_logger.SetIdAndLun(GetId(), GetLun());
|
||||
}
|
||||
|
||||
void PrimaryDevice::TestUnitReady()
|
||||
|
@ -91,11 +86,11 @@ void PrimaryDevice::TestUnitReady()
|
|||
void PrimaryDevice::Inquiry()
|
||||
{
|
||||
// EVPD and page code check
|
||||
if ((GetController()->GetCmd(1) & 0x01) || GetController()->GetCmd(2)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if ((GetController()->GetCmdByte(1) & 0x01) || GetController()->GetCmdByte(2)) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
vector<uint8_t> buf = InquiryInternal();
|
||||
const vector<uint8_t> buf = InquiryInternal();
|
||||
|
||||
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetInt16(GetController()->GetCmd(), 3)));
|
||||
|
||||
|
@ -103,8 +98,8 @@ void PrimaryDevice::Inquiry()
|
|||
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
|
||||
|
||||
// Report if the device does not support the requested LUN
|
||||
if (int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
|
||||
GetLogger().Trace("LUN is not available");
|
||||
if (const int lun = GetController()->GetEffectiveLun(); !GetController()->HasDeviceForLun(lun)) {
|
||||
LogTrace("LUN is not available");
|
||||
|
||||
// Signal that the requested LUN does not exist
|
||||
GetController()->GetBuffer().data()[0] = 0x7f;
|
||||
|
@ -116,8 +111,8 @@ void PrimaryDevice::Inquiry()
|
|||
void PrimaryDevice::ReportLuns()
|
||||
{
|
||||
// Only SELECT REPORT mode 0 is supported
|
||||
if (GetController()->GetCmd(2)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if (GetController()->GetCmdByte(2)) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
const uint32_t allocation_length = GetInt32(GetController()->GetCmd(), 6);
|
||||
|
@ -155,14 +150,14 @@ void PrimaryDevice::RequestSense()
|
|||
lun = 0;
|
||||
|
||||
// Do not raise an exception here because the rest of the code must be executed
|
||||
GetController()->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
|
||||
GetController()->Error(sense_key::illegal_request, asc::invalid_lun);
|
||||
|
||||
GetController()->SetStatus(status::GOOD);
|
||||
GetController()->SetStatus(status::good);
|
||||
}
|
||||
|
||||
vector<byte> buf = GetController()->GetDeviceForLun(lun)->HandleRequestSense();
|
||||
|
||||
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmd(4)));
|
||||
const size_t allocation_length = min(buf.size(), static_cast<size_t>(GetController()->GetCmdByte(4)));
|
||||
|
||||
memcpy(GetController()->GetBuffer().data(), buf.data(), allocation_length);
|
||||
GetController()->SetLength(static_cast<uint32_t>(allocation_length));
|
||||
|
@ -173,13 +168,13 @@ void PrimaryDevice::RequestSense()
|
|||
void PrimaryDevice::SendDiagnostic()
|
||||
{
|
||||
// Do not support PF bit
|
||||
if (GetController()->GetCmd(1) & 0x10) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if (GetController()->GetCmdByte(1) & 0x10) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Do not support parameter list
|
||||
if ((GetController()->GetCmd(3) != 0) || (GetController()->GetCmd(4) != 0)) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if ((GetController()->GetCmdByte(3) != 0) || (GetController()->GetCmdByte(4) != 0)) {
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
@ -190,24 +185,24 @@ void PrimaryDevice::CheckReady()
|
|||
// Not ready if reset
|
||||
if (IsReset()) {
|
||||
SetReset(false);
|
||||
GetLogger().Trace("Device in reset");
|
||||
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET);
|
||||
LogTrace("Device in reset");
|
||||
throw scsi_exception(sense_key::unit_attention, asc::power_on_or_reset);
|
||||
}
|
||||
|
||||
// Not ready if it needs attention
|
||||
if (IsAttn()) {
|
||||
SetAttn(false);
|
||||
GetLogger().Trace("Device in needs attention");
|
||||
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
|
||||
LogTrace("Device in needs attention");
|
||||
throw scsi_exception(sense_key::unit_attention, asc::not_ready_to_ready_change);
|
||||
}
|
||||
|
||||
// Return status if not ready
|
||||
if (!IsReady()) {
|
||||
GetLogger().Trace("Device not ready");
|
||||
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
|
||||
LogTrace("Device not ready");
|
||||
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
|
||||
}
|
||||
|
||||
GetLogger().Trace("Device is ready");
|
||||
LogTrace("Device is ready");
|
||||
}
|
||||
|
||||
vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bool is_removable) const
|
||||
|
@ -223,8 +218,8 @@ vector<uint8_t> PrimaryDevice::HandleInquiry(device_type type, scsi_level level,
|
|||
buf[0] = static_cast<uint8_t>(type);
|
||||
buf[1] = is_removable ? 0x80 : 0x00;
|
||||
buf[2] = static_cast<uint8_t>(level);
|
||||
buf[3] = level >= scsi_level::SCSI_2 ?
|
||||
static_cast<uint8_t>(scsi_level::SCSI_2) : static_cast<uint8_t>(scsi_level::SCSI_1_CCS);
|
||||
buf[3] = level >= scsi_level::scsi_2 ?
|
||||
static_cast<uint8_t>(scsi_level::scsi_2) : static_cast<uint8_t>(scsi_level::scsi_1_ccs);
|
||||
buf[4] = 0x1F;
|
||||
|
||||
// Padded vendor, product, revision
|
||||
|
@ -237,7 +232,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
|
|||
{
|
||||
// Return not ready only if there are no errors
|
||||
if (!GetStatusCode() && !IsReady()) {
|
||||
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
|
||||
throw scsi_exception(sense_key::not_ready, asc::medium_not_present);
|
||||
}
|
||||
|
||||
// Set 18 bytes including extended sense data
|
||||
|
@ -257,14 +252,14 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
|
|||
<< "Status $" << static_cast<int>(GetController()->GetStatus())
|
||||
<< ", Sense Key $" << static_cast<int>(buf[2])
|
||||
<< ", ASC $" << static_cast<int>(buf[12]);
|
||||
GetLogger().Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool PrimaryDevice::WriteByteSequence(vector<uint8_t>&, uint32_t)
|
||||
bool PrimaryDevice::WriteByteSequence(span<const uint8_t>)
|
||||
{
|
||||
GetLogger().Error("Writing bytes is not supported by this device");
|
||||
LogError("Writing bytes is not supported by this device");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -274,10 +269,10 @@ void PrimaryDevice::ReserveUnit()
|
|||
reserving_initiator = GetController()->GetInitiatorId();
|
||||
|
||||
if (reserving_initiator != -1) {
|
||||
GetLogger().Trace("Reserved device for initiator ID " + to_string(reserving_initiator));
|
||||
LogTrace("Reserved device for initiator ID " + to_string(reserving_initiator));
|
||||
}
|
||||
else {
|
||||
GetLogger().Trace("Reserved device for unknown initiator");
|
||||
LogTrace("Reserved device for unknown initiator");
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
@ -286,10 +281,10 @@ void PrimaryDevice::ReserveUnit()
|
|||
void PrimaryDevice::ReleaseUnit()
|
||||
{
|
||||
if (reserving_initiator != -1) {
|
||||
GetLogger().Trace("Released device reserved by initiator ID " + to_string(reserving_initiator));
|
||||
LogTrace("Released device reserved by initiator ID " + to_string(reserving_initiator));
|
||||
}
|
||||
else {
|
||||
GetLogger().Trace("Released device reserved by unknown initiator");
|
||||
LogTrace("Released device reserved by unknown initiator");
|
||||
}
|
||||
|
||||
DiscardReservation();
|
||||
|
@ -313,10 +308,10 @@ bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool pr
|
|||
}
|
||||
|
||||
if (initiator_id != -1) {
|
||||
GetLogger().Trace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
|
||||
LogTrace("Initiator ID " + to_string(initiator_id) + " tries to access reserved device");
|
||||
}
|
||||
else {
|
||||
GetLogger().Trace("Unknown initiator tries to access reserved device");
|
||||
LogTrace("Unknown initiator tries to access reserved device");
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// A device implementing mandatory SCSI primary commands, to be used for subclassing
|
||||
//
|
||||
|
@ -18,6 +18,7 @@
|
|||
#include "device_logger.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <span>
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
|
@ -25,6 +26,8 @@ using namespace scsi_defs;
|
|||
|
||||
class PrimaryDevice: private ScsiPrimaryCommands, public Device
|
||||
{
|
||||
friend class AbstractController;
|
||||
|
||||
using operation = function<void()>;
|
||||
|
||||
public:
|
||||
|
@ -32,15 +35,16 @@ public:
|
|||
PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) {}
|
||||
~PrimaryDevice() override = default;
|
||||
|
||||
virtual bool Init(const unordered_map<string, string>&);
|
||||
virtual bool Init(const param_map&);
|
||||
virtual void CleanUp() {
|
||||
// Override if cleanup work is required for a derived device
|
||||
};
|
||||
|
||||
virtual void Dispatch(scsi_command);
|
||||
|
||||
int GetId() const override;
|
||||
|
||||
void SetController(shared_ptr<AbstractController>);
|
||||
|
||||
virtual bool WriteByteSequence(vector<uint8_t>&, uint32_t);
|
||||
virtual bool WriteByteSequence(span<const uint8_t>);
|
||||
|
||||
int GetSendDelay() const { return send_delay; }
|
||||
|
||||
|
@ -57,8 +61,6 @@ protected:
|
|||
|
||||
void AddCommand(scsi_command, const operation&);
|
||||
|
||||
const DeviceLogger& GetLogger() const { return logger; }
|
||||
|
||||
vector<uint8_t> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;
|
||||
virtual vector<uint8_t> InquiryInternal() const = 0;
|
||||
void CheckReady();
|
||||
|
@ -69,16 +71,24 @@ protected:
|
|||
void ReserveUnit() override;
|
||||
void ReleaseUnit() override;
|
||||
|
||||
void EnterStatusPhase() const { controller.lock()->Status(); }
|
||||
void EnterDataInPhase() const { controller.lock()->DataIn(); }
|
||||
void EnterDataOutPhase() const { controller.lock()->DataOut(); }
|
||||
void EnterStatusPhase() const { controller->Status(); }
|
||||
void EnterDataInPhase() const { controller->DataIn(); }
|
||||
void EnterDataOutPhase() const { controller->DataOut(); }
|
||||
|
||||
inline shared_ptr<AbstractController> GetController() const { return controller.lock(); }
|
||||
auto GetController() const { return controller; }
|
||||
|
||||
void LogTrace(const string& s) const { device_logger.Trace(s); }
|
||||
void LogDebug(const string& s) const { device_logger.Debug(s); }
|
||||
void LogInfo(const string& s) const { device_logger.Info(s); }
|
||||
void LogWarn(const string& s) const { device_logger.Warn(s); }
|
||||
void LogError(const string& s) const { device_logger.Error(s); }
|
||||
|
||||
private:
|
||||
|
||||
static const int NOT_RESERVED = -2;
|
||||
|
||||
void SetController(AbstractController *);
|
||||
|
||||
void TestUnitReady() override;
|
||||
void RequestSense() override;
|
||||
void ReportLuns() override;
|
||||
|
@ -86,9 +96,11 @@ private:
|
|||
|
||||
vector<byte> HandleRequestSense() const;
|
||||
|
||||
DeviceLogger logger;
|
||||
// TODO Try to remove this field and use controller->Log*() methods instead
|
||||
DeviceLogger device_logger;
|
||||
|
||||
weak_ptr<AbstractController> controller;
|
||||
// Owned by the controller manager
|
||||
AbstractController *controller = nullptr;
|
||||
|
||||
unordered_map<scsi_command, operation> commands;
|
||||
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "device_logger.h"
|
||||
#include "scsi_command_util.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <cstring>
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace scsi_defs;
|
||||
|
||||
void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd, const vector<int>& cdb,
|
||||
const vector<uint8_t>& buf, int length, int sector_size)
|
||||
string scsi_command_util::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length, int sector_size)
|
||||
{
|
||||
assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10);
|
||||
assert(length >= 0);
|
||||
|
||||
string result;
|
||||
|
||||
// PF
|
||||
if (!(cdb[1] & 0x10)) {
|
||||
// Vendor-specific parameters (SCSI-1) are not supported.
|
||||
// Do not report an error in order to support Apple's HD SC Setup.
|
||||
return;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Skip block descriptors
|
||||
|
@ -45,9 +45,9 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
|
|||
// Parse the pages
|
||||
while (length > 0) {
|
||||
// Format device page
|
||||
if (int page = buf[offset]; page == 0x03) {
|
||||
if (const int page = buf[offset]; page == 0x03) {
|
||||
if (length < 14) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
|
||||
}
|
||||
|
||||
// With this page the sector size for a subsequent FORMAT can be selected, but only very few
|
||||
|
@ -56,8 +56,8 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
|
|||
if (GetInt16(buf, offset + 12) != sector_size) {
|
||||
// With piscsi it is not possible to permanently (by formatting) change the sector size,
|
||||
// because the size is an externally configurable setting only
|
||||
logger.Warn("In order to change the sector size use the -b option when launching piscsi");
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
|
||||
spdlog::warn("In order to change the sector size use the -b option when launching piscsi");
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
|
||||
}
|
||||
|
||||
has_valid_page_code = true;
|
||||
|
@ -65,7 +65,7 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
|
|||
else {
|
||||
stringstream s;
|
||||
s << "Unknown MODE SELECT page code: $" << setfill('0') << setw(2) << hex << page;
|
||||
logger.Warn(s.str());
|
||||
result = s.str();
|
||||
}
|
||||
|
||||
// Advance to the next page
|
||||
|
@ -76,8 +76,10 @@ void scsi_command_util::ModeSelect(const DeviceLogger& logger, scsi_command cmd,
|
|||
}
|
||||
|
||||
if (!has_valid_page_code) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_parameter_list);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void scsi_command_util::EnrichFormatPage(map<int, vector<byte>>& pages, bool changeable, int sector_size)
|
||||
|
@ -97,33 +99,19 @@ void scsi_command_util::AddAppleVendorModePage(map<int, vector<byte>>& pages, bo
|
|||
|
||||
// No changeable area
|
||||
if (!changeable) {
|
||||
const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
|
||||
constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
|
||||
memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA));
|
||||
}
|
||||
}
|
||||
|
||||
int scsi_command_util::GetInt16(const vector<uint8_t>& buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
|
||||
}
|
||||
|
||||
int scsi_command_util::GetInt16(const vector<int>& buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
return (buf[offset] << 8) | buf[offset + 1];
|
||||
}
|
||||
|
||||
int scsi_command_util::GetInt24(const vector<int>& buf, int offset)
|
||||
int scsi_command_util::GetInt24(span <const int> buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 2);
|
||||
|
||||
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
|
||||
}
|
||||
|
||||
uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
|
||||
uint32_t scsi_command_util::GetInt32(span <const int> buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 3);
|
||||
|
||||
|
@ -131,7 +119,7 @@ uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
|
|||
(static_cast<uint32_t>(buf[offset + 2]) << 8) | static_cast<uint32_t>(buf[offset + 3]);
|
||||
}
|
||||
|
||||
uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
|
||||
uint64_t scsi_command_util::GetInt64(span<const int> buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 7);
|
||||
|
||||
|
@ -141,42 +129,6 @@ uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
|
|||
(static_cast<uint64_t>(buf[offset + 6]) << 8) | static_cast<uint64_t>(buf[offset + 7]);
|
||||
}
|
||||
|
||||
void scsi_command_util::SetInt16(vector<byte>& buf, int offset, int value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
buf[offset] = static_cast<byte>(value >> 8);
|
||||
buf[offset + 1] = static_cast<byte>(value);
|
||||
}
|
||||
|
||||
void scsi_command_util::SetInt32(vector<byte>& buf, int offset, uint32_t value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 3);
|
||||
|
||||
buf[offset] = static_cast<byte>(value >> 24);
|
||||
buf[offset + 1] = static_cast<byte>(value >> 16);
|
||||
buf[offset + 2] = static_cast<byte>(value >> 8);
|
||||
buf[offset + 3] = static_cast<byte>(value);
|
||||
}
|
||||
|
||||
void scsi_command_util::SetInt16(vector<uint8_t>& buf, int offset, int value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
buf[offset] = static_cast<uint8_t>(value >> 8);
|
||||
buf[offset + 1] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void scsi_command_util::SetInt32(vector<uint8_t>& buf, int offset, uint32_t value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 3);
|
||||
|
||||
buf[offset] = static_cast<uint8_t>(value >> 24);
|
||||
buf[offset + 1] = static_cast<uint8_t>(value >> 16);
|
||||
buf[offset + 2] = static_cast<uint8_t>(value >> 8);
|
||||
buf[offset + 3] = static_cast<uint8_t>(value);
|
||||
}
|
||||
|
||||
void scsi_command_util::SetInt64(vector<uint8_t>& buf, int offset, uint64_t value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 7);
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Shared code for SCSI command implementations
|
||||
//
|
||||
|
@ -12,27 +12,49 @@
|
|||
#pragma once
|
||||
|
||||
#include "shared/scsi.h"
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DeviceLogger;
|
||||
|
||||
namespace scsi_command_util
|
||||
{
|
||||
void ModeSelect(const DeviceLogger&, scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int, int);
|
||||
string ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int, int);
|
||||
void EnrichFormatPage(map<int, vector<byte>>&, bool, int);
|
||||
void AddAppleVendorModePage(map<int, vector<byte>>&, bool);
|
||||
|
||||
int GetInt16(const vector<uint8_t>&, int);
|
||||
int GetInt16(const vector<int>&, int);
|
||||
int GetInt24(const vector<int>&, int);
|
||||
uint32_t GetInt32(const vector<int>&, int);
|
||||
uint64_t GetInt64(const vector<int>&, int);
|
||||
void SetInt16(vector<byte>&, int, int);
|
||||
void SetInt32(vector<byte>&, int, uint32_t);
|
||||
void SetInt16(vector<uint8_t>&, int, int);
|
||||
void SetInt32(vector<uint8_t>&, int, uint32_t);
|
||||
int GetInt16(const auto buf, int offset)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
return (static_cast<int>(buf[offset]) << 8) | buf[offset + 1];
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
void SetInt16(vector<T>& buf, int offset, int value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 1);
|
||||
|
||||
buf[offset] = static_cast<T>(value >> 8);
|
||||
buf[offset + 1] = static_cast<T>(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void SetInt32(vector<T>& buf, int offset, uint32_t value)
|
||||
{
|
||||
assert(buf.size() > static_cast<size_t>(offset) + 3);
|
||||
|
||||
buf[offset] = static_cast<T>(value >> 24);
|
||||
buf[offset + 1] = static_cast<T>(value >> 16);
|
||||
buf[offset + 2] = static_cast<T>(value >> 8);
|
||||
buf[offset + 3] = static_cast<T>(value);
|
||||
}
|
||||
|
||||
int GetInt24(span<const int>, int);
|
||||
uint32_t GetInt32(span <const int>, int);
|
||||
uint64_t GetInt64(span<const int>, int);
|
||||
void SetInt64(vector<uint8_t>&, int, uint64_t);
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : PrimaryDevice(SCDP, lun)
|
|||
SupportsParams(true);
|
||||
}
|
||||
|
||||
bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
|
||||
bool SCSIDaynaPort::Init(const param_map& params)
|
||||
{
|
||||
PrimaryDevice::Init(params);
|
||||
|
||||
|
@ -55,15 +55,13 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
|
|||
SetSendDelay(DAYNAPORT_READ_HEADER_SZ);
|
||||
|
||||
m_bTapEnable = m_tap.Init(GetParams());
|
||||
if(!m_bTapEnable){
|
||||
GetLogger().Error("Unable to open the TAP interface");
|
||||
|
||||
if (!m_bTapEnable) {
|
||||
// Not terminating on regular Linux PCs is helpful for testing
|
||||
#if !defined(__x86_64__) && !defined(__X86__)
|
||||
return false;
|
||||
return false;
|
||||
#endif
|
||||
} else {
|
||||
GetLogger().Trace("Tap interface created");
|
||||
LogTrace("Tap interface created");
|
||||
}
|
||||
|
||||
Reset();
|
||||
|
@ -73,9 +71,14 @@ bool SCSIDaynaPort::Init(const unordered_map<string, string>& params)
|
|||
return true;
|
||||
}
|
||||
|
||||
void SCSIDaynaPort::CleanUp()
|
||||
{
|
||||
m_tap.CleanUp();
|
||||
}
|
||||
|
||||
vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
|
||||
{
|
||||
vector<uint8_t> buf = HandleInquiry(device_type::PROCESSOR, scsi_level::SCSI_2, false);
|
||||
vector<uint8_t> buf = HandleInquiry(device_type::processor, scsi_level::scsi_2, false);
|
||||
|
||||
// The Daynaport driver for the Mac expects 37 bytes: Increase additional length and
|
||||
// add a vendor-specific byte in order to satisfy this driver.
|
||||
|
@ -116,14 +119,14 @@ vector<uint8_t> SCSIDaynaPort::InquiryInternal() const
|
|||
// - The SCSI/Link apparently has about 6KB buffer space for packets.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
|
||||
int SCSIDaynaPort::Read(cdb_t cdb, vector<uint8_t>& buf, uint64_t) const
|
||||
{
|
||||
int rx_packet_size = 0;
|
||||
const auto response = (scsi_resp_read_t*)buf.data();
|
||||
|
||||
const int requested_length = cdb[4];
|
||||
|
||||
GetLogger().Trace("Read maximum length: " + to_string(requested_length));
|
||||
LogTrace("Read maximum length: " + to_string(requested_length));
|
||||
|
||||
// At startup the host may send a READ(6) command with a sector count of 1 to read the root sector.
|
||||
// We should respond by going into the status mode with a code of 0x02.
|
||||
|
@ -146,13 +149,13 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
|
|||
|
||||
// If we didn't receive anything, return size of 0
|
||||
if (rx_packet_size <= 0) {
|
||||
GetLogger().Trace("No packet received");
|
||||
LogTrace("No packet received");
|
||||
response->length = 0;
|
||||
response->flags = read_data_flags_t::e_no_more_data;
|
||||
return DAYNAPORT_READ_HEADER_SZ;
|
||||
}
|
||||
|
||||
GetLogger().Trace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
|
||||
LogTrace("Packet Size " + to_string(rx_packet_size) + ", read count: " + to_string(read_count));
|
||||
|
||||
// This is a very basic filter to prevent unnecessary packets from
|
||||
// being sent to the SCSI initiator.
|
||||
|
@ -187,11 +190,11 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
|
|||
for (int i = 0 ; i < 6; i++) {
|
||||
s << " $" << static_cast<int>(response->data[i]);
|
||||
}
|
||||
GetLogger().Debug(s.str());
|
||||
LogDebug(s.str());
|
||||
|
||||
// If there are pending packets to be processed, we'll tell the host that the read
|
||||
// length was 0.
|
||||
if (!m_tap.PendingPackets()) {
|
||||
if (!m_tap.HasPendingPackets()) {
|
||||
response->length = 0;
|
||||
response->flags = read_data_flags_t::e_no_more_data;
|
||||
return DAYNAPORT_READ_HEADER_SZ;
|
||||
|
@ -216,7 +219,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
|
|||
size = 64;
|
||||
}
|
||||
SetInt16(buf, 0, size);
|
||||
SetInt32(buf, 2, m_tap.PendingPackets() ? 0x10 : 0x00);
|
||||
SetInt32(buf, 2, m_tap.HasPendingPackets() ? 0x10 : 0x00);
|
||||
|
||||
// Return the packet size + 2 for the length + 4 for the flag field
|
||||
// The CRC was already appended by the ctapdriver
|
||||
|
@ -249,25 +252,23 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t)
|
|||
// XX XX ... is the actual packet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
|
||||
bool SCSIDaynaPort::Write(cdb_t cdb, span<const uint8_t> buf) const
|
||||
{
|
||||
const int data_format = cdb[5];
|
||||
int data_length = GetInt16(cdb, 3);
|
||||
|
||||
if (data_format == 0x00) {
|
||||
if (const int data_format = cdb[5]; data_format == 0x00) {
|
||||
const int data_length = GetInt16(cdb, 3);
|
||||
m_tap.Send(buf.data(), data_length);
|
||||
GetLogger().Trace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
|
||||
LogTrace("Transmitted " + to_string(data_length) + " byte(s) (00 format)");
|
||||
}
|
||||
else if (data_format == 0x80) {
|
||||
// The data length is specified in the first 2 bytes of the payload
|
||||
data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
|
||||
const int data_length = buf[1] + ((static_cast<int>(buf[0]) & 0xff) << 8);
|
||||
m_tap.Send(&(buf.data()[4]), data_length);
|
||||
GetLogger().Trace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
|
||||
LogTrace("Transmitted " + to_string(data_length) + "byte(s) (80 format)");
|
||||
}
|
||||
else {
|
||||
stringstream s;
|
||||
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
|
||||
GetLogger().Warn(s.str());
|
||||
LogWarn(s.str());
|
||||
}
|
||||
|
||||
GetController()->SetBlocks(0);
|
||||
|
@ -291,7 +292,7 @@ bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uin
|
|||
// - long #3: frames lost
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int SCSIDaynaPort::RetrieveStats(const vector<int>& cdb, vector<uint8_t>& buf) const
|
||||
int SCSIDaynaPort::RetrieveStats(cdb_t cdb, vector<uint8_t>& buf) const
|
||||
{
|
||||
memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats));
|
||||
|
||||
|
@ -304,7 +305,7 @@ void SCSIDaynaPort::TestUnitReady()
|
|||
EnterStatusPhase();
|
||||
}
|
||||
|
||||
void SCSIDaynaPort::Read6()
|
||||
void SCSIDaynaPort::Read6() const
|
||||
{
|
||||
// Get record number and block number
|
||||
const uint32_t record = GetInt24(GetController()->GetCmd(), 1) & 0x1fffff;
|
||||
|
@ -312,17 +313,17 @@ void SCSIDaynaPort::Read6()
|
|||
|
||||
// If any commands have a bogus control value, they were probably not
|
||||
// generated by the DaynaPort driver so ignore them
|
||||
if (GetController()->GetCmd(5) != 0xc0 && GetController()->GetCmd(5) != 0x80) {
|
||||
GetLogger().Trace("Control value: " + to_string(GetController()->GetCmd(5)));
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
if (GetController()->GetCmdByte(5) != 0xc0 && GetController()->GetCmdByte(5) != 0x80) {
|
||||
LogTrace("Control value: " + to_string(GetController()->GetCmdByte(5)));
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
stringstream s;
|
||||
s << "READ(6) command, record: $" << setfill('0') << setw(8) << hex << record;
|
||||
GetLogger().Trace(s.str() + ", blocks: " + to_string(GetController()->GetBlocks()));
|
||||
LogTrace(s.str());
|
||||
|
||||
GetController()->SetLength(Read(GetController()->GetCmd(), GetController()->GetBuffer(), record));
|
||||
GetLogger().Trace("Length is " + to_string(GetController()->GetLength()));
|
||||
LogTrace("Length is " + to_string(GetController()->GetLength()));
|
||||
|
||||
// Set next block
|
||||
GetController()->SetNext(record + 1);
|
||||
|
@ -335,7 +336,7 @@ void SCSIDaynaPort::Write6() const
|
|||
// Ensure a sufficient buffer size (because it is not transfer for each block)
|
||||
GetController()->AllocateBuffer(DAYNAPORT_BUFFER_SIZE);
|
||||
|
||||
const int data_format = GetController()->GetCmd(5);
|
||||
const int data_format = GetController()->GetCmdByte(5);
|
||||
|
||||
if (data_format == 0x00) {
|
||||
GetController()->SetLength(GetInt16(GetController()->GetCmd(), 3));
|
||||
|
@ -346,15 +347,15 @@ void SCSIDaynaPort::Write6() const
|
|||
else {
|
||||
stringstream s;
|
||||
s << "Unknown data format: " << setfill('0') << setw(2) << hex << data_format;
|
||||
GetLogger().Warn(s.str());
|
||||
LogWarn(s.str());
|
||||
}
|
||||
|
||||
stringstream s;
|
||||
s << "Length: " << GetController()->GetLength() << ", format: $" << setfill('0') << setw(2) << hex << data_format;
|
||||
GetLogger().Trace(s.str());
|
||||
LogTrace(s.str());
|
||||
|
||||
if (GetController()->GetLength() <= 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Set next block
|
||||
|
@ -406,7 +407,7 @@ void SCSIDaynaPort::SetInterfaceMode() const
|
|||
// Check whether this command is telling us to "Set Interface Mode" or "Set MAC Address"
|
||||
|
||||
GetController()->SetLength(RetrieveStats(GetController()->GetCmd(), GetController()->GetBuffer()));
|
||||
switch(GetController()->GetCmd(5)){
|
||||
switch(GetController()->GetCmdByte(5)){
|
||||
case CMD_SCSILINK_SETMODE:
|
||||
// Not implemented, do nothing
|
||||
EnterStatusPhase();
|
||||
|
@ -419,21 +420,21 @@ void SCSIDaynaPort::SetInterfaceMode() const
|
|||
|
||||
default:
|
||||
stringstream s;
|
||||
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(5);
|
||||
GetLogger().Warn(s.str());
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
s << "Unsupported SetInterface command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(5);
|
||||
LogWarn(s.str());
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_command_operation_code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SCSIDaynaPort::SetMcastAddr() const
|
||||
{
|
||||
GetController()->SetLength(GetController()->GetCmd(4));
|
||||
GetController()->SetLength(GetController()->GetCmdByte(4));
|
||||
if (GetController()->GetLength() == 0) {
|
||||
stringstream s;
|
||||
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmd(2);
|
||||
GetLogger().Warn(s.str());
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
s << "Unsupported SetMcastAddr command: " << setfill('0') << setw(2) << hex << GetController()->GetCmdByte(2);
|
||||
LogWarn(s.str());
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
EnterDataOutPhase();
|
||||
|
@ -451,27 +452,27 @@ void SCSIDaynaPort::SetMcastAddr() const
|
|||
// seconds
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDaynaPort::EnableInterface()
|
||||
void SCSIDaynaPort::EnableInterface() const
|
||||
{
|
||||
if (GetController()->GetCmd(5) & 0x80) {
|
||||
if (!m_tap.Enable()) {
|
||||
GetLogger().Warn("Unable to enable the DaynaPort Interface");
|
||||
if (GetController()->GetCmdByte(5) & 0x80) {
|
||||
if (const string error = m_tap.IpLink(true); !error.empty()) {
|
||||
LogWarn("Unable to enable the DaynaPort Interface: " + error);
|
||||
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
|
||||
m_tap.Flush();
|
||||
|
||||
GetLogger().Info("The DaynaPort interface has been ENABLED");
|
||||
LogInfo("The DaynaPort interface has been ENABLED");
|
||||
}
|
||||
else {
|
||||
if (!m_tap.Disable()) {
|
||||
GetLogger().Warn("Unable to disable the DaynaPort Interface");
|
||||
if (const string error = m_tap.IpLink(false); !error.empty()) {
|
||||
LogWarn("Unable to disable the DaynaPort Interface: " + error);
|
||||
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
|
||||
GetLogger().Info("The DaynaPort interface has been DISABLED");
|
||||
LogInfo("The DaynaPort interface has been DISABLED");
|
||||
}
|
||||
|
||||
EnterStatusPhase();
|
||||
|
|
|
@ -29,10 +29,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "interfaces/byte_writer.h"
|
||||
#include "primary_device.h"
|
||||
#include "ctapdriver.h"
|
||||
#include <net/ethernet.h>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
|
||||
|
@ -41,29 +42,30 @@
|
|||
// DaynaPort SCSI Link
|
||||
//
|
||||
//===========================================================================
|
||||
class SCSIDaynaPort : public PrimaryDevice, public ByteWriter
|
||||
class SCSIDaynaPort : public PrimaryDevice
|
||||
{
|
||||
public:
|
||||
|
||||
explicit SCSIDaynaPort(int);
|
||||
~SCSIDaynaPort() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
void CleanUp() override;
|
||||
|
||||
// Commands
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
int Read(const vector<int>&, vector<uint8_t>&, uint64_t);
|
||||
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
|
||||
int Read(cdb_t, vector<uint8_t>&, uint64_t) const;
|
||||
bool Write(cdb_t, span<const uint8_t>) const;
|
||||
|
||||
int RetrieveStats(const vector<int>&, vector<uint8_t>&) const;
|
||||
int RetrieveStats(cdb_t, vector<uint8_t>&) const;
|
||||
|
||||
void TestUnitReady() override;
|
||||
void Read6();
|
||||
void Read6() const;
|
||||
void Write6() const;
|
||||
void RetrieveStatistics() const;
|
||||
void SetInterfaceMode() const;
|
||||
void SetMcastAddr() const;
|
||||
void EnableInterface();
|
||||
void EnableInterface() const;
|
||||
|
||||
static const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include "scsi_command_util.h"
|
||||
#include "scsi_host_bridge.h"
|
||||
#include <arpa/inet.h>
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
using namespace scsi_defs;
|
||||
|
@ -31,7 +30,7 @@ SCSIBR::SCSIBR(int lun) : PrimaryDevice(SCBR, lun)
|
|||
SupportsParams(true);
|
||||
}
|
||||
|
||||
bool SCSIBR::Init(const unordered_map<string, string>& params)
|
||||
bool SCSIBR::Init(const param_map& params)
|
||||
{
|
||||
PrimaryDevice::Init(params);
|
||||
|
||||
|
@ -43,10 +42,8 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
|
|||
AddCommand(scsi_command::eCmdSendMessage10, [this] { SendMessage10(); });
|
||||
|
||||
#ifdef __linux__
|
||||
// TAP Driver Generation
|
||||
m_bTapEnable = tap.Init(GetParams());
|
||||
if (!m_bTapEnable){
|
||||
GetLogger().Error("Unable to open the TAP interface");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
@ -70,9 +67,14 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
|
|||
#endif
|
||||
}
|
||||
|
||||
void SCSIBR::CleanUp()
|
||||
{
|
||||
tap.CleanUp();
|
||||
}
|
||||
|
||||
vector<uint8_t> SCSIBR::InquiryInternal() const
|
||||
{
|
||||
vector<uint8_t> buf = HandleInquiry(device_type::COMMUNICATIONS, scsi_level::SCSI_2, false);
|
||||
vector<uint8_t> buf = HandleInquiry(device_type::communications, scsi_level::scsi_2, false);
|
||||
|
||||
// The bridge returns more additional bytes than the other devices
|
||||
buf.resize(0x1F + 8 + 5);
|
||||
|
@ -99,7 +101,7 @@ void SCSIBR::TestUnitReady()
|
|||
EnterStatusPhase();
|
||||
}
|
||||
|
||||
int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
|
||||
int SCSIBR::GetMessage10(cdb_t cdb, vector<uint8_t>& buf)
|
||||
{
|
||||
// Type
|
||||
const int type = cdb[2];
|
||||
|
@ -187,7 +189,7 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<uint8_t>& buf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool SCSIBR::WriteBytes(const vector<int>& cdb, vector<uint8_t>& buf, uint32_t)
|
||||
bool SCSIBR::ReadWrite(cdb_t cdb, vector<uint8_t>& buf)
|
||||
{
|
||||
// Type
|
||||
const int type = cdb[2];
|
||||
|
@ -252,7 +254,7 @@ void SCSIBR::GetMessage10()
|
|||
|
||||
GetController()->SetLength(GetMessage10(GetController()->GetCmd(), GetController()->GetBuffer()));
|
||||
if (GetController()->GetLength() <= 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Set next block
|
||||
|
@ -273,7 +275,7 @@ void SCSIBR::SendMessage10() const
|
|||
{
|
||||
GetController()->SetLength(GetInt24(GetController()->GetCmd(), 6));
|
||||
if (GetController()->GetLength() <= 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Ensure a sufficient buffer size (because it is not a transfer for each block)
|
||||
|
@ -292,7 +294,7 @@ int SCSIBR::GetMacAddr(vector<uint8_t>& mac) const
|
|||
return static_cast<int>(mac_addr.size());
|
||||
}
|
||||
|
||||
void SCSIBR::SetMacAddr(const vector<uint8_t>& mac)
|
||||
void SCSIBR::SetMacAddr(span<const uint8_t> mac)
|
||||
{
|
||||
memcpy(mac_addr.data(), mac.data(), mac_addr.size());
|
||||
}
|
||||
|
@ -339,7 +341,7 @@ void SCSIBR::GetPacketBuf(vector<uint8_t>& buf, int index)
|
|||
packet_enable = false;
|
||||
}
|
||||
|
||||
void SCSIBR::SendPacket(const vector<uint8_t>& buf, int len)
|
||||
void SCSIBR::SendPacket(span<const uint8_t> buf, int len) const
|
||||
{
|
||||
tap.Send(buf.data(), len);
|
||||
}
|
||||
|
@ -837,7 +839,7 @@ void SCSIBR::FS_GetCapacity(vector<uint8_t>& buf)
|
|||
auto dp = (uint32_t*)buf.data();
|
||||
const uint32_t nUnit = ntohl(*dp);
|
||||
|
||||
Human68k::capacity_t cap;
|
||||
Human68k::capacity_t cap = {};
|
||||
fsresult = fs.GetCapacity(nUnit, &cap);
|
||||
|
||||
cap.freearea = htons(cap.freearea);
|
||||
|
@ -878,7 +880,7 @@ void SCSIBR::FS_GetDPB(vector<uint8_t>& buf)
|
|||
auto dp = (uint32_t*)buf.data();
|
||||
const uint32_t nUnit = ntohl(*dp);
|
||||
|
||||
Human68k::dpb_t dpb;
|
||||
Human68k::dpb_t dpb = {};
|
||||
fsresult = fs.GetDPB(nUnit, &dpb);
|
||||
|
||||
dpb.sector_size = htons(dpb.sector_size);
|
||||
|
|
|
@ -17,16 +17,16 @@
|
|||
//---------------------------------------------------------------------------
|
||||
#pragma once
|
||||
|
||||
#include "interfaces/byte_writer.h"
|
||||
#include "primary_device.h"
|
||||
#include "ctapdriver.h"
|
||||
#include "cfilesystem.h"
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class SCSIBR : public PrimaryDevice, public ByteWriter
|
||||
class SCSIBR : public PrimaryDevice
|
||||
{
|
||||
static constexpr const array<uint8_t, 6> bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||
|
||||
|
@ -35,12 +35,13 @@ public:
|
|||
explicit SCSIBR(int);
|
||||
~SCSIBR() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
void CleanUp() override;
|
||||
|
||||
// Commands
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
int GetMessage10(const vector<int>&, vector<uint8_t>&);
|
||||
bool WriteBytes(const vector<int>&, vector<uint8_t>&, uint32_t) override;
|
||||
int GetMessage10(cdb_t, vector<uint8_t>&);
|
||||
bool ReadWrite(cdb_t, vector<uint8_t>&);
|
||||
void TestUnitReady() override;
|
||||
void GetMessage10();
|
||||
void SendMessage10() const;
|
||||
|
@ -48,16 +49,16 @@ public:
|
|||
private:
|
||||
|
||||
int GetMacAddr(vector<uint8_t>&) const; // Get MAC address
|
||||
void SetMacAddr(const vector<uint8_t>&); // Set MAC address
|
||||
void SetMacAddr(span<const uint8_t>); // Set MAC address
|
||||
void ReceivePacket(); // Receive a packet
|
||||
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
|
||||
void SendPacket(const vector<uint8_t>&, int); // Send a packet
|
||||
void GetPacketBuf(vector<uint8_t>&, int); // Get a packet
|
||||
void SendPacket(span<const uint8_t>, int) const; // Send a packet
|
||||
|
||||
CTapDriver tap; // TAP driver
|
||||
bool m_bTapEnable = false; // TAP valid flag
|
||||
array<uint8_t, 6> mac_addr = {}; // MAC Address
|
||||
array<uint8_t, 6> mac_addr = {}; // MAC Address
|
||||
int packet_len = 0; // Receive packet size
|
||||
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
|
||||
array<uint8_t, 0x1000> packet_buf; // Receive packet buffer
|
||||
bool packet_enable = false; // Received packet valid
|
||||
|
||||
int ReadFsResult(vector<uint8_t>&) const; // Read filesystem (result code)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
|
||||
//
|
||||
|
@ -32,7 +32,6 @@
|
|||
#include "shared/piscsi_exceptions.h"
|
||||
#include "scsi_command_util.h"
|
||||
#include "scsi_printer.h"
|
||||
#include <sys/stat.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
|
@ -45,7 +44,7 @@ SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun)
|
|||
SupportsParams(true);
|
||||
}
|
||||
|
||||
bool SCSIPrinter::Init(const unordered_map<string, string>& params)
|
||||
bool SCSIPrinter::Init(const param_map& params)
|
||||
{
|
||||
PrimaryDevice::Init(params);
|
||||
|
||||
|
@ -61,7 +60,7 @@ bool SCSIPrinter::Init(const unordered_map<string, string>& params)
|
|||
AddCommand(scsi_command::eCmdSendDiagnostic, [this] { SendDiagnostic(); });
|
||||
|
||||
if (GetParam("cmd").find("%f") == string::npos) {
|
||||
GetLogger().Trace("Missing filename specifier %f");
|
||||
LogTrace("Missing filename specifier %f");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -82,20 +81,20 @@ void SCSIPrinter::TestUnitReady()
|
|||
|
||||
vector<uint8_t> SCSIPrinter::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false);
|
||||
return HandleInquiry(device_type::printer, scsi_level::scsi_2, false);
|
||||
}
|
||||
|
||||
void SCSIPrinter::Print()
|
||||
{
|
||||
const uint32_t length = GetInt24(GetController()->GetCmd(), 2);
|
||||
|
||||
GetLogger().Trace("Receiving " + to_string(length) + " byte(s) to be printed");
|
||||
LogTrace("Expecting to receive " + to_string(length) + " byte(s) to be printed");
|
||||
|
||||
if (length > GetController()->GetBuffer().size()) {
|
||||
GetLogger().Error("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
|
||||
LogError("Transfer buffer overflow: Buffer size is " + to_string(GetController()->GetBuffer().size()) +
|
||||
" bytes, " + to_string(length) + " bytes expected");
|
||||
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
GetController()->SetLength(length);
|
||||
|
@ -107,9 +106,9 @@ void SCSIPrinter::Print()
|
|||
void SCSIPrinter::SynchronizeBuffer()
|
||||
{
|
||||
if (!out.is_open()) {
|
||||
GetLogger().Warn("Nothing to print");
|
||||
LogWarn("Nothing to print");
|
||||
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
|
||||
string cmd = GetParam("cmd");
|
||||
|
@ -118,25 +117,24 @@ void SCSIPrinter::SynchronizeBuffer()
|
|||
cmd.replace(file_position, 2, filename);
|
||||
|
||||
error_code error;
|
||||
LogTrace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
|
||||
|
||||
GetLogger().Trace("Printing file '" + filename + "' with " + to_string(file_size(path(filename), error)) + " byte(s)");
|
||||
|
||||
GetLogger().Debug("Executing '" + cmd + "'");
|
||||
LogDebug("Executing print command '" + cmd + "'");
|
||||
|
||||
if (system(cmd.c_str())) {
|
||||
GetLogger().Error("Printing file '" + filename + "' failed, the printing system might not be configured");
|
||||
LogError("Printing file '" + filename + "' failed, the printing system might not be configured");
|
||||
|
||||
Cleanup();
|
||||
CleanUp();
|
||||
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
|
||||
Cleanup();
|
||||
CleanUp();
|
||||
|
||||
EnterStatusPhase();
|
||||
}
|
||||
|
||||
bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
|
||||
bool SCSIPrinter::WriteByteSequence(span<const uint8_t> buf)
|
||||
{
|
||||
if (!out.is_open()) {
|
||||
vector<char> f(file_template.begin(), file_template.end());
|
||||
|
@ -145,7 +143,7 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
|
|||
// There is no C++ API that generates a file with a unique name
|
||||
const int fd = mkstemp(f.data());
|
||||
if (fd == -1) {
|
||||
GetLogger().Error("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
|
||||
LogError("Can't create printer output file for pattern '" + filename + "': " + strerror(errno));
|
||||
return false;
|
||||
}
|
||||
close(fd);
|
||||
|
@ -154,21 +152,23 @@ bool SCSIPrinter::WriteByteSequence(vector<uint8_t>& buf, uint32_t length)
|
|||
|
||||
out.open(filename, ios::binary);
|
||||
if (out.fail()) {
|
||||
throw scsi_exception(sense_key::ABORTED_COMMAND);
|
||||
throw scsi_exception(sense_key::aborted_command);
|
||||
}
|
||||
|
||||
GetLogger().Trace("Created printer output file '" + filename + "'");
|
||||
LogTrace("Created printer output file '" + filename + "'");
|
||||
}
|
||||
|
||||
GetLogger().Trace("Appending " + to_string(length) + " byte(s) to printer output file ''" + filename + "'");
|
||||
LogTrace("Appending " + to_string(buf.size()) + " byte(s) to printer output file ''" + filename + "'");
|
||||
|
||||
out.write((const char*)buf.data(), length);
|
||||
out.write((const char *)buf.data(), buf.size());
|
||||
|
||||
return !out.fail();
|
||||
}
|
||||
|
||||
void SCSIPrinter::Cleanup()
|
||||
void SCSIPrinter::CleanUp()
|
||||
{
|
||||
PrimaryDevice::CleanUp();
|
||||
|
||||
if (out.is_open()) {
|
||||
out.close();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// Implementation of a SCSI printer (see SCSI-2 specification for a command description)
|
||||
//
|
||||
|
@ -15,6 +15,7 @@
|
|||
#include <fstream>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <span>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -29,11 +30,12 @@ public:
|
|||
explicit SCSIPrinter(int);
|
||||
~SCSIPrinter() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
void CleanUp() override;
|
||||
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
|
||||
bool WriteByteSequence(vector<uint8_t>&, uint32_t) override;
|
||||
bool WriteByteSequence(span<const uint8_t>) override;
|
||||
|
||||
private:
|
||||
|
||||
|
@ -44,8 +46,6 @@ private:
|
|||
void Print() override;
|
||||
void SynchronizeBuffer();
|
||||
|
||||
void Cleanup();
|
||||
|
||||
string file_template;
|
||||
|
||||
string filename;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
|
||||
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::scsi_level level)
|
||||
: Disk(SCCD, lun), scsi_level(level)
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
|
@ -31,7 +31,7 @@ SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes, scsi_defs::
|
|||
SetLockable(true);
|
||||
}
|
||||
|
||||
bool SCSICD::Init(const unordered_map<string, string>& params)
|
||||
bool SCSICD::Init(const param_map& params)
|
||||
{
|
||||
Disk::Init(params);
|
||||
|
||||
|
@ -116,7 +116,7 @@ void SCSICD::OpenIso()
|
|||
|
||||
if (rawfile) {
|
||||
if (size % 2536) {
|
||||
GetLogger().Warn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
|
||||
LogWarn("Raw ISO CD-ROM file size is not a multiple of 2536 bytes but is "
|
||||
+ to_string(size) + " bytes");
|
||||
}
|
||||
|
||||
|
@ -165,7 +165,7 @@ void SCSICD::ReadToc()
|
|||
|
||||
vector<uint8_t> SCSICD::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::CD_ROM, scsi_level, true);
|
||||
return HandleInquiry(device_type::cd_rom, scsi_level, true);
|
||||
}
|
||||
|
||||
void SCSICD::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
|
||||
|
@ -216,13 +216,13 @@ void SCSICD::AddVendorPage(map<int, vector<byte>>& pages, int page, bool changea
|
|||
}
|
||||
}
|
||||
|
||||
int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
|
||||
int SCSICD::Read(span<uint8_t> buf, uint64_t block)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
const int index = SearchTrack(static_cast<int>(block));
|
||||
if (index < 0) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::lba_out_of_range);
|
||||
}
|
||||
|
||||
assert(tracks[index]);
|
||||
|
@ -241,15 +241,15 @@ int SCSICD::Read(const vector<int>& cdb, vector<uint8_t>& buf, uint64_t block)
|
|||
}
|
||||
|
||||
assert(dataindex >= 0);
|
||||
return Disk::Read(cdb, buf, block);
|
||||
return Disk::Read(buf, block);
|
||||
}
|
||||
|
||||
int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
|
||||
int SCSICD::ReadTocInternal(cdb_t cdb, vector<uint8_t>& buf)
|
||||
{
|
||||
CheckReady();
|
||||
|
||||
// If ready, there is at least one track
|
||||
assert(tracks.size() > 0);
|
||||
assert(!tracks.empty());
|
||||
assert(tracks[0]);
|
||||
|
||||
// Get allocation length, clear buffer
|
||||
|
@ -263,7 +263,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
|
|||
const int last = tracks[tracks.size() - 1]->GetTrackNo();
|
||||
// Except for AA
|
||||
if (cdb[6] > last && cdb[6] != 0xaa) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Check start index
|
||||
|
@ -280,7 +280,7 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<uint8_t>& buf)
|
|||
// AA if not found or internal error
|
||||
if (!tracks[index]) {
|
||||
if (cdb[6] != 0xaa) {
|
||||
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
|
||||
throw scsi_exception(sense_key::illegal_request, asc::invalid_field_in_cdb);
|
||||
}
|
||||
|
||||
// Returns the final LBA+1 because it is AA
|
||||
|
|
|
@ -17,20 +17,23 @@
|
|||
#include "cd_track.h"
|
||||
#include "disk.h"
|
||||
#include "interfaces/scsi_mmc_commands.h"
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class SCSICD : public Disk, private ScsiMmcCommands
|
||||
{
|
||||
public:
|
||||
|
||||
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::SCSI_2);
|
||||
SCSICD(int, const unordered_set<uint32_t>&, scsi_defs::scsi_level = scsi_level::scsi_2);
|
||||
~SCSICD() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
bool Init(const param_map&) override;
|
||||
|
||||
void Open() override;
|
||||
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
int Read(const vector<int>&, vector<uint8_t>&, uint64_t) override;
|
||||
int Read(span<uint8_t>, uint64_t) override;
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -39,7 +42,7 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
int ReadTocInternal(const vector<int>&, vector<uint8_t>&);
|
||||
int ReadTocInternal(cdb_t, vector<uint8_t>&);
|
||||
|
||||
void AddCDROMPage(map<int, vector<byte>>&, bool) const;
|
||||
void AddCDDAPage(map<int, vector<byte>>&, bool) const;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
|
@ -14,8 +14,8 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "scsihd.h"
|
||||
#include "scsi_command_util.h"
|
||||
#include "scsihd.h"
|
||||
|
||||
using namespace scsi_command_util;
|
||||
|
||||
|
@ -70,7 +70,7 @@ void SCSIHD::Open()
|
|||
{
|
||||
assert(!IsReady());
|
||||
|
||||
off_t size = GetFileSize();
|
||||
const off_t size = GetFileSize();
|
||||
|
||||
// Sector size (default 512 bytes) and number of blocks
|
||||
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
|
||||
|
@ -81,12 +81,15 @@ void SCSIHD::Open()
|
|||
|
||||
vector<uint8_t> SCSIHD::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable());
|
||||
return HandleInquiry(device_type::direct_access, scsi_level, IsRemovable());
|
||||
}
|
||||
|
||||
void SCSIHD::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
|
||||
void SCSIHD::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
|
||||
{
|
||||
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
|
||||
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
|
||||
!result.empty()) {
|
||||
LogWarn(result);
|
||||
}
|
||||
}
|
||||
|
||||
void SCSIHD::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
|
@ -18,6 +18,7 @@
|
|||
#include "shared/scsi.h"
|
||||
#include "disk.h"
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
@ -27,7 +28,7 @@ class SCSIHD : public Disk
|
|||
|
||||
public:
|
||||
|
||||
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
|
||||
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::scsi_2);
|
||||
~SCSIHD() override = default;
|
||||
|
||||
void FinalizeSetup(off_t);
|
||||
|
@ -36,7 +37,7 @@ public:
|
|||
|
||||
// Commands
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
|
||||
|
||||
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
|
||||
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -47,7 +48,7 @@ void SCSIHD_NEC::Open()
|
|||
FinalizeSetup(image_offset);
|
||||
}
|
||||
|
||||
pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
|
||||
pair<int, int> SCSIHD_NEC::SetParameters(span<const char> data, int size)
|
||||
{
|
||||
array<uint8_t, 512> root_sector = {};
|
||||
memcpy(root_sector.data(), data.data(), root_sector.size());
|
||||
|
@ -107,12 +108,12 @@ pair<int, int> SCSIHD_NEC::SetParameters(const array<char, 512>& data, int size)
|
|||
throw io_exception("Invalid NEC sector size of " + to_string(sector_size) + " byte(s)");
|
||||
}
|
||||
|
||||
return make_pair(image_size, sector_size);
|
||||
return { image_size, sector_size };
|
||||
}
|
||||
|
||||
vector<uint8_t> SCSIHD_NEC::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false);
|
||||
return HandleInquiry(device_type::direct_access, scsi_level::scsi_1_ccs, false);
|
||||
}
|
||||
|
||||
void SCSIHD_NEC::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const
|
||||
|
|
|
@ -47,7 +47,7 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
pair<int, int> SetParameters(const array<char, 512>&, int);
|
||||
pair<int, int> SetParameters(span<const char>, int);
|
||||
|
||||
static int GetInt16LittleEndian(const uint8_t *);
|
||||
static int GetInt32LittleEndian(const uint8_t *);
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -23,13 +24,13 @@ SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk(SCMO
|
|||
SetSectorSizes(sector_sizes);
|
||||
|
||||
// 128 MB, 512 bytes per sector, 248826 sectors
|
||||
geometries[512 * 248826] = make_pair(512, 248826);
|
||||
geometries[512 * 248826] = { 512, 248826 };
|
||||
// 230 MB, 512 bytes per block, 446325 sectors
|
||||
geometries[512 * 446325] = make_pair(512, 446325);
|
||||
geometries[512 * 446325] = { 512, 446325 };
|
||||
// 540 MB, 512 bytes per sector, 1041500 sectors
|
||||
geometries[512 * 1041500] = make_pair(512, 1041500);
|
||||
geometries[512 * 1041500] = { 512, 1041500 };
|
||||
// 640 MB, 20248 bytes per sector, 310352 sectors
|
||||
geometries[2048 * 310352] = make_pair(2048, 310352);
|
||||
geometries[2048 * 310352] = { 2048, 310352 };
|
||||
|
||||
SetProtectable(true);
|
||||
SetRemovable(true);
|
||||
|
@ -61,7 +62,7 @@ void SCSIMO::Open()
|
|||
|
||||
vector<uint8_t> SCSIMO::InquiryInternal() const
|
||||
{
|
||||
return HandleInquiry(device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, true);
|
||||
return HandleInquiry(device_type::optical_memory, scsi_level::scsi_2, true);
|
||||
}
|
||||
|
||||
void SCSIMO::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
|
||||
|
@ -89,9 +90,12 @@ void SCSIMO::AddOptionPage(map<int, vector<byte>>& pages, bool) const
|
|||
// Do not report update blocks
|
||||
}
|
||||
|
||||
void SCSIMO::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<uint8_t>& buf, int length) const
|
||||
void SCSIMO::ModeSelect(scsi_command cmd, cdb_t cdb, span<const uint8_t> buf, int length) const
|
||||
{
|
||||
scsi_command_util::ModeSelect(GetLogger(), cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
|
||||
if (const string result = scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount());
|
||||
!result.empty()) {
|
||||
LogWarn(result);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
|
@ -15,6 +16,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "disk.h"
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
|
@ -30,7 +32,7 @@ public:
|
|||
void Open() override;
|
||||
|
||||
vector<uint8_t> InquiryInternal() const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<uint8_t>&, int) const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, cdb_t, span<const uint8_t>, int) const override;
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -3,15 +3,13 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "storage_device.h"
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
|
@ -22,22 +20,43 @@ StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type,
|
|||
SetStoppable(true);
|
||||
}
|
||||
|
||||
void StorageDevice::CleanUp()
|
||||
{
|
||||
UnreserveFile();
|
||||
|
||||
ModePageDevice::CleanUp();
|
||||
}
|
||||
|
||||
void StorageDevice::SetFilename(string_view f)
|
||||
{
|
||||
filename = filesystem::path(f);
|
||||
|
||||
// Permanently write-protected
|
||||
SetReadOnly(IsReadOnlyFile());
|
||||
|
||||
SetProtectable(!IsReadOnlyFile());
|
||||
|
||||
if (IsReadOnlyFile()) {
|
||||
SetProtected(false);
|
||||
}
|
||||
}
|
||||
|
||||
void StorageDevice::ValidateFile()
|
||||
{
|
||||
if (blocks == 0) {
|
||||
throw io_exception(string(GetTypeString()) + " device has 0 blocks");
|
||||
}
|
||||
|
||||
if (!exists(path(filename))) {
|
||||
throw file_not_found_exception("Image file '" + filename + "' for " + GetTypeString() + " device does not exist");
|
||||
if (!exists(filename)) {
|
||||
throw file_not_found_exception("Image file '" + filename.string() + "' for " + GetTypeString() + " device does not exist");
|
||||
}
|
||||
|
||||
if (GetFileSize() > 2LL * 1024 * 1024 * 1024 * 1024) {
|
||||
throw io_exception("Drive capacity cannot exceed 2 TiB");
|
||||
throw io_exception("Image files > 2 TiB are not supported");
|
||||
}
|
||||
|
||||
// TODO Check for duplicate handling of these properties (-> piscsi_executor.cpp)
|
||||
if (access(filename.c_str(), W_OK)) {
|
||||
if (IsReadOnlyFile()) {
|
||||
// Permanently write-protected
|
||||
SetReadOnly(true);
|
||||
SetProtectable(false);
|
||||
|
@ -50,28 +69,28 @@ void StorageDevice::ValidateFile()
|
|||
SetReady(true);
|
||||
}
|
||||
|
||||
void StorageDevice::ReserveFile(const string& file, int id, int lun) const
|
||||
void StorageDevice::ReserveFile() const
|
||||
{
|
||||
assert(!file.empty());
|
||||
assert(reserved_files.find(file) == reserved_files.end());
|
||||
assert(!filename.empty());
|
||||
assert(!reserved_files.contains(filename.string()));
|
||||
|
||||
reserved_files[file] = make_pair(id, lun);
|
||||
reserved_files[filename.string()] = { GetId(), GetLun() };
|
||||
}
|
||||
|
||||
void StorageDevice::UnreserveFile()
|
||||
{
|
||||
reserved_files.erase(filename);
|
||||
reserved_files.erase(filename.string());
|
||||
|
||||
filename = "";
|
||||
filename.clear();
|
||||
}
|
||||
|
||||
id_set StorageDevice::GetIdsForReservedFile(const string& file)
|
||||
{
|
||||
if (const auto& it = reserved_files.find(file); it != reserved_files.end()) {
|
||||
return make_pair(it->second.first, it->second.second);
|
||||
return { it->second.first, it->second.second };
|
||||
}
|
||||
|
||||
return make_pair(-1, -1);
|
||||
return { -1, -1 };
|
||||
}
|
||||
|
||||
void StorageDevice::UnreserveAll()
|
||||
|
@ -79,9 +98,9 @@ void StorageDevice::UnreserveAll()
|
|||
reserved_files.clear();
|
||||
}
|
||||
|
||||
bool StorageDevice::FileExists(const string& file)
|
||||
bool StorageDevice::FileExists(string_view file)
|
||||
{
|
||||
return exists(file);
|
||||
return exists(path(file));
|
||||
}
|
||||
|
||||
bool StorageDevice::IsReadOnlyFile() const
|
||||
|
@ -91,10 +110,10 @@ bool StorageDevice::IsReadOnlyFile() const
|
|||
|
||||
off_t StorageDevice::GetFileSize() const
|
||||
{
|
||||
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
|
||||
if (struct stat st; !stat(filename.c_str(), &st)) {
|
||||
return st.st_size;
|
||||
try {
|
||||
return file_size(filename);
|
||||
}
|
||||
catch (const filesystem_error& e) {
|
||||
throw io_exception("Can't get size of '" + filename.string() + "': " + e.what());
|
||||
}
|
||||
|
||||
throw io_exception("Can't get size of '" + filename + "'");
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
// The base class for all mass storage devices with image file support
|
||||
//
|
||||
|
@ -11,14 +11,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "mode_page_device.h"
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
class StorageDevice : public ModePageDevice
|
||||
{
|
||||
public:
|
||||
|
@ -26,24 +26,27 @@ public:
|
|||
StorageDevice(PbDeviceType, int);
|
||||
~StorageDevice() override = default;
|
||||
|
||||
void CleanUp() override;
|
||||
|
||||
virtual void Open() = 0;
|
||||
|
||||
string GetFilename() const { return filename; }
|
||||
void SetFilename(string_view f) { filename = f; }
|
||||
string GetFilename() const { return filename.string(); }
|
||||
void SetFilename(string_view);
|
||||
|
||||
uint64_t GetBlockCount() const { return blocks; }
|
||||
|
||||
void ReserveFile(const string&, int, int) const;
|
||||
void ReserveFile() const;
|
||||
void UnreserveFile();
|
||||
// TODO Remove this method, it is only used by the unit tests
|
||||
static void UnreserveAll();
|
||||
|
||||
static bool FileExists(const string&);
|
||||
bool IsReadOnlyFile() const;
|
||||
static bool FileExists(string_view);
|
||||
|
||||
void SetMediumChanged(bool b) { medium_changed = b; }
|
||||
|
||||
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
|
||||
static void SetReservedFiles(const unordered_map<string, id_set>& r) { reserved_files = r; }
|
||||
static auto GetReservedFiles() { return reserved_files; }
|
||||
static void SetReservedFiles(const unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>>& r)
|
||||
{ reserved_files = r; }
|
||||
static id_set GetIdsForReservedFile(const string&);
|
||||
|
||||
protected:
|
||||
|
@ -58,13 +61,14 @@ protected:
|
|||
|
||||
private:
|
||||
|
||||
// Total number of blocks
|
||||
bool IsReadOnlyFile() const;
|
||||
|
||||
uint64_t blocks = 0;
|
||||
|
||||
string filename;
|
||||
filesystem::path filename;
|
||||
|
||||
bool medium_changed = false;
|
||||
|
||||
// The list of image files in use and the IDs and LUNs using these files
|
||||
static inline unordered_map<string, id_set> reserved_files;
|
||||
static inline unordered_map<string, id_set, piscsi_util::StringHash, equal_to<>> reserved_files;
|
||||
};
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "shared/scsi.h"
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
using namespace scsi_defs;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "hal/gpiobus.h"
|
||||
#include "hal/sbc_version.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "shared/log.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <array>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
@ -280,8 +280,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
|
|||
if (actmode == mode_e::TARGET) {
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i == delay_after_bytes) {
|
||||
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
|
||||
(int)delay_after_bytes)
|
||||
spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " +
|
||||
to_string(delay_after_bytes) + " bytes");
|
||||
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
|
||||
}
|
||||
|
||||
|
@ -325,8 +325,8 @@ int GPIOBUS::SendHandShake(uint8_t *buf, int count, int delay_after_bytes)
|
|||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (i == delay_after_bytes) {
|
||||
LOGTRACE("%s DELAYING for %dus after %d bytes", __PRETTY_FUNCTION__, SCSI_DELAY_SEND_DATA_DAYNAPORT_US,
|
||||
(int)delay_after_bytes)
|
||||
spdlog::trace("DELAYING for " + to_string(SCSI_DELAY_SEND_DATA_DAYNAPORT_US) + " after " +
|
||||
to_string(delay_after_bytes) + " bytes");
|
||||
SysTimer::SleepUsec(SCSI_DELAY_SEND_DATA_DAYNAPORT_US);
|
||||
}
|
||||
|
||||
|
@ -392,7 +392,7 @@ bool GPIOBUS::PollSelectEvent()
|
|||
return false;
|
||||
#else
|
||||
GPIO_FUNCTION_TRACE
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
spdlog::trace(__PRETTY_FUNCTION__);
|
||||
errno = 0;
|
||||
int prev_mode = -1;
|
||||
if (SBC_Version::IsBananaPi()) {
|
||||
|
@ -401,12 +401,12 @@ bool GPIOBUS::PollSelectEvent()
|
|||
}
|
||||
|
||||
if (epoll_event epev; epoll_wait(epfd, &epev, 1, -1) <= 0) {
|
||||
LOGWARN("%s epoll_wait failed", __PRETTY_FUNCTION__)
|
||||
spdlog::warn("epoll_wait failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gpioevent_data gpev; read(selevreq.fd, &gpev, sizeof(gpev)) < 0) {
|
||||
LOGWARN("%s read failed", __PRETTY_FUNCTION__)
|
||||
spdlog::warn("read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -455,4 +455,4 @@ bool GPIOBUS::WaitSignal(int pin, bool ast)
|
|||
|
||||
// We timed out waiting for the signal
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,7 +214,7 @@ class GPIOBUS : public BUS
|
|||
// SEL signal event request
|
||||
struct gpioevent_request selevreq = {}; // NOSONAR: This protected so derived classes can access it
|
||||
// epoll file descriptor
|
||||
int epfd; // NOSONAR: This protected so derived classes can access it
|
||||
int epfd = 0; // NOSONAR: This is protected so derived classes can access it
|
||||
|
||||
#endif
|
||||
};
|
||||
|
|
|
@ -34,7 +34,9 @@
|
|||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <string.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
|
@ -43,7 +45,7 @@
|
|||
#include "hal/pi_defs/bpi-gpio.h"
|
||||
#include "hal/sunxi_utils.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / (sizeof(x[0])))
|
||||
|
||||
|
@ -62,7 +64,7 @@ bool GPIOBUS_BananaM2p::Init(mode_e mode)
|
|||
|
||||
int gpio_bank = SunXI::GPIO_BANK(gpio_num);
|
||||
|
||||
if (std::find(gpio_banks.begin(), gpio_banks.end(), gpio_bank) != gpio_banks.end()) {
|
||||
if (std::ranges::find(gpio_banks, gpio_bank) != gpio_banks.end()) {
|
||||
LOGTRACE("Duplicate bank: %d", gpio_bank)
|
||||
|
||||
} else {
|
||||
|
@ -723,9 +725,9 @@ void GPIOBUS_BananaM2p::SetSignal(int pin, bool ast)
|
|||
}
|
||||
|
||||
if (sunxi_gpio_state == SunXI::HIGH)
|
||||
pio->DAT &= ~(1 << num);
|
||||
pio->DAT = pio->DAT & ~(1 << num);
|
||||
else
|
||||
pio->DAT |= (1 << num);
|
||||
pio->DAT = pio->DAT | (1 << num);
|
||||
}
|
||||
|
||||
void GPIOBUS_BananaM2p::DisableIRQ()
|
||||
|
@ -978,9 +980,9 @@ void GPIOBUS_BananaM2p::sunxi_output_gpio(int pin, int value)
|
|||
}
|
||||
|
||||
if (value == 0)
|
||||
pio->DAT &= ~(1 << num);
|
||||
pio->DAT = pio->DAT & ~(1 << num);
|
||||
else
|
||||
pio->DAT |= (1 << num);
|
||||
pio->DAT = pio->DAT | (1 << num);
|
||||
}
|
||||
|
||||
void GPIOBUS_BananaM2p::sunxi_set_all_gpios(array<uint32_t, 12> &mask, array<uint32_t, 12> &value)
|
||||
|
@ -1043,11 +1045,11 @@ void GPIOBUS_BananaM2p::set_pullupdn(int pin, int pud)
|
|||
else if (pud == SunXI::PUD_UP)
|
||||
*(gpio_map + SunXI::PULLUPDN_OFFSET) = (*(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3) | SunXI::PUD_UP;
|
||||
else // pud == PUD_OFF
|
||||
*(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;
|
||||
*(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3;
|
||||
|
||||
SunXI::short_wait();
|
||||
*(gpio_map + clk_offset) = 1 << shift;
|
||||
SunXI::short_wait();
|
||||
*(gpio_map + SunXI::PULLUPDN_OFFSET) &= ~3;
|
||||
*(gpio_map + SunXI::PULLUPDN_OFFSET) = *(gpio_map + SunXI::PULLUPDN_OFFSET) & ~3;
|
||||
*(gpio_map + clk_offset) = 0;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "hal/pi_defs/bpi-gpio.h"
|
||||
#include "hal/sbc_version.h"
|
||||
#include "hal/sunxi_utils.h"
|
||||
#include "shared/log.h"
|
||||
#include "shared/scsi.h"
|
||||
#include <map>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include "hal/gpiobus_raspberry.h"
|
||||
#include "hal/gpiobus_virtual.h"
|
||||
#include "hal/sbc_version.h"
|
||||
#include "shared/log.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -29,13 +29,13 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
|
|||
// Also make Init() private.
|
||||
SBC_Version::Init();
|
||||
if (SBC_Version::IsBananaPi()) {
|
||||
LOGTRACE("Creating GPIOBUS_BananaM2p")
|
||||
spdlog::trace("Creating GPIOBUS_BananaM2p");
|
||||
return_ptr = make_unique<GPIOBUS_BananaM2p>();
|
||||
} else if (SBC_Version::IsRaspberryPi()) {
|
||||
LOGTRACE("Creating GPIOBUS_Raspberry")
|
||||
spdlog::trace("Creating GPIOBUS_Raspberry");
|
||||
return_ptr = make_unique<GPIOBUS_Raspberry>();
|
||||
} else {
|
||||
LOGINFO("Creating Virtual GPIOBUS")
|
||||
spdlog::trace("Creating Virtual GPIOBUS");
|
||||
return_ptr = make_unique<GPIOBUS_Virtual>();
|
||||
}
|
||||
if (!return_ptr->Init(mode)) {
|
||||
|
@ -43,7 +43,7 @@ unique_ptr<BUS> GPIOBUS_Factory::Create(BUS::mode_e mode)
|
|||
}
|
||||
return_ptr->Reset();
|
||||
} catch (const invalid_argument&) {
|
||||
LOGERROR("Exception while trying to initialize GPIO bus. Are you running as root?")
|
||||
spdlog::error("Exception while trying to initialize GPIO bus. Are you running as root?");
|
||||
return_ptr = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,10 +18,12 @@
|
|||
#include "hal/gpiobus_raspberry.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include <map>
|
||||
#include <string.h>
|
||||
#include <cstring>
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
|
@ -80,14 +82,14 @@ bool GPIOBUS_Raspberry::Init(mode_e mode)
|
|||
// Open /dev/mem
|
||||
int fd = open("/dev/mem", O_RDWR | O_SYNC);
|
||||
if (fd == -1) {
|
||||
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
|
||||
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map peripheral region memory
|
||||
void *map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
|
||||
if (map == MAP_FAILED) {
|
||||
LOGERROR("Error: Unable to map memory: %s", strerror(errno))
|
||||
spdlog::error("Error: Unable to map memory: "+ string(strerror(errno)));
|
||||
close(fd);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include "hal/data_sample_raspberry.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "shared/log.h"
|
||||
#include "shared/scsi.h"
|
||||
#include <map>
|
||||
|
||||
|
|
|
@ -13,12 +13,14 @@
|
|||
#include "hal/gpiobus_virtual.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#endif
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/time.h>
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include "hal/data_sample_raspberry.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "shared/log.h"
|
||||
#include "shared/scsi.h"
|
||||
|
||||
#include <map>
|
||||
|
@ -171,4 +170,4 @@ class GPIOBUS_Virtual final : public GPIOBUS
|
|||
sem_t *mutex_sem, *buffer_count_sem, *spool_signal_sem;
|
||||
int fd_shm, fd_log;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
|
|
|
@ -9,9 +9,11 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// The legacy code in this file is deprecated and can cause a buffer overflow. Use spdlog directly instead.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
static const int LOGBUF_SIZE = 512;
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "sbc_version.h"
|
||||
#include "shared/log.h"
|
||||
#include "log.h"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
|
|
|
@ -40,12 +40,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
static const string BLACK = "\033[30m"; /* Black */
|
||||
static const string RED = "\033[31m"; /* Red */
|
||||
static const string GREEN = "\033[32m"; /* Green */
|
||||
static const string YELLOW = "\033[33m"; /* Yellow */
|
||||
static const string BLUE = "\033[34m"; /* Blue */
|
||||
static const string MAGENTA = "\033[35m"; /* Magenta */
|
||||
static const string CYAN = "\033[36m"; /* Cyan */
|
||||
static const string WHITE = "\033[37m"; /* White */
|
||||
|
||||
|
@ -62,4 +56,4 @@ void dump_gpio_registers(const SunXI::sunxi_gpio_reg_t *regs)
|
|||
printf("--- GPIO INT CFG: %08X %08X %08X\n", regs->gpio_int.CFG[0], regs->gpio_int.CFG[1], regs->gpio_int.CFG[2]);
|
||||
printf("--- CTL: (%08X) STA: %08X DEB: %08X\n %s", regs->gpio_int.CTL, regs->gpio_int.STA, regs->gpio_int.DEB,
|
||||
WHITE.c_str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,13 +14,12 @@
|
|||
#include "hal/systimer.h"
|
||||
#include "hal/systimer_allwinner.h"
|
||||
#include "hal/systimer_raspberry.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/sbc_version.h"
|
||||
|
||||
#include "shared/log.h"
|
||||
|
||||
bool SysTimer::initialized = false;
|
||||
bool SysTimer::is_allwinnner = false;
|
||||
bool SysTimer::is_raspberry = false;
|
||||
|
@ -29,7 +28,7 @@ std::unique_ptr<PlatformSpecificTimer> SysTimer::systimer_ptr;
|
|||
|
||||
void SysTimer::Init()
|
||||
{
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
spdlog::trace(__PRETTY_FUNCTION__);
|
||||
|
||||
if (!initialized) {
|
||||
if (SBC_Version::IsRaspberryPi()) {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include "hal/gpiobus.h"
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
|
||||
const std::string SysTimer_AllWinner::dev_mem_filename = "/dev/mem";
|
||||
|
||||
|
@ -71,14 +71,14 @@ void SysTimer_AllWinner::enable_hs_timer()
|
|||
hsitimer_regs->hs_tmr_intv_hi_reg = (1 << 20) - 1; //(0xFFFFF)
|
||||
hsitimer_regs->hs_tmr_intv_lo_reg = UINT32_MAX;
|
||||
|
||||
// Select prescale value of 1, continuouse mode
|
||||
// Select prescale value of 1, continuous mode
|
||||
hsitimer_regs->hs_tmr_ctrl_reg = HS_TMR_CLK_PRE_SCALE_1;
|
||||
|
||||
// Set reload bit
|
||||
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_RELOAD;
|
||||
hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_RELOAD;
|
||||
|
||||
// Enable HSTimer
|
||||
hsitimer_regs->hs_tmr_ctrl_reg |= HS_TMR_EN;
|
||||
hsitimer_regs->hs_tmr_ctrl_reg = hsitimer_regs->hs_tmr_ctrl_reg | HS_TMR_EN;
|
||||
}
|
||||
|
||||
// TODO: According to the data sheet, we should turn off the HS timer when we're done with it. But, its just going to
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "hal/systimer_raspberry.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <memory>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
|
@ -19,8 +20,6 @@
|
|||
#include "hal/gpiobus.h"
|
||||
#include "hal/sbc_version.h"
|
||||
|
||||
#include "shared/log.h"
|
||||
|
||||
// System timer address
|
||||
volatile uint32_t *SysTimer_Raspberry::systaddr = nullptr;
|
||||
// ARM timer address
|
||||
|
@ -40,14 +39,14 @@ void SysTimer_Raspberry::Init()
|
|||
// Open /dev/mem
|
||||
int mem_fd = open("/dev/mem", O_RDWR | O_SYNC);
|
||||
if (mem_fd == -1) {
|
||||
LOGERROR("Error: Unable to open /dev/mem. Are you running as root?")
|
||||
spdlog::error("Error: Unable to open /dev/mem. Are you running as root?");
|
||||
return;
|
||||
}
|
||||
|
||||
// Map peripheral region memory
|
||||
void *map = mmap(nullptr, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, mem_fd, baseaddr);
|
||||
if (map == MAP_FAILED) {
|
||||
LOGERROR("Error: Unable to map memory")
|
||||
spdlog::error("Error: Unable to map memory");
|
||||
close(mem_fd);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -10,11 +10,10 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "monitor/sm_core.h"
|
||||
#include "hal/data_sample.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/gpiobus_factory.h"
|
||||
#include "monitor/sm_reports.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include <climits>
|
||||
|
@ -77,28 +76,28 @@ void ScsiMon::ParseArguments(const vector<char *> &args)
|
|||
|
||||
void ScsiMon::PrintHelpText(const vector<char *> &args) const
|
||||
{
|
||||
LOGINFO("%s -i [input file json] -b [buffer size] [output file]", args[0])
|
||||
LOGINFO(" -i [input file json] - scsimon will parse the json file instead of capturing new data")
|
||||
LOGINFO(" If -i option is not specified, scsimon will read the gpio pins")
|
||||
LOGINFO(" -b [buffer size] - Override the default buffer size of %d.", buff_size)
|
||||
LOGINFO(" [output file] - Base name of the output files. The file extension (ex: .json)")
|
||||
LOGINFO(" will be appended to this file name")
|
||||
spdlog::info(string(args[0]) + " -i [input file json] -b [buffer size] [output file]");
|
||||
spdlog::info(" -i [input file json] - scsimon will parse the json file instead of capturing new data");
|
||||
spdlog::info(" If -i option is not specified, scsimon will read the gpio pins");
|
||||
spdlog::info(" -b [buffer size] - Override the default buffer size of " + to_string(buff_size));
|
||||
spdlog::info(" [output file] - Base name of the output files. The file extension (ex: .json)");
|
||||
spdlog::info(" will be appended to this file name");
|
||||
}
|
||||
|
||||
void ScsiMon::Banner() const
|
||||
{
|
||||
if (import_data) {
|
||||
LOGINFO("Reading input file: %s", input_file_name.c_str())
|
||||
spdlog::info("Reading input file: " + input_file_name);
|
||||
} else {
|
||||
LOGINFO("Reading live data from the GPIO pins")
|
||||
LOGINFO(" Connection type : %s", CONNECT_DESC.c_str())
|
||||
spdlog::info("Reading live data from the GPIO pins");
|
||||
spdlog::info(" Connection type: " + CONNECT_DESC);
|
||||
}
|
||||
LOGINFO(" Data buffer size: %u", buff_size)
|
||||
LOGINFO(" ")
|
||||
LOGINFO("Generating output files:")
|
||||
LOGINFO(" %s - Value Change Dump file that can be opened with GTKWave", vcd_file_name.c_str())
|
||||
LOGINFO(" %s - JSON file with raw data", json_file_name.c_str())
|
||||
LOGINFO(" %s - HTML file with summary of commands", html_file_name.c_str())
|
||||
spdlog::info(" Data buffer size: " + to_string(buff_size));
|
||||
spdlog::info(" ");
|
||||
spdlog::info("Generating output files:");
|
||||
spdlog::info(" " + vcd_file_name + " - Value Change Dump file that can be opened with GTKWave");
|
||||
spdlog::info(" " + json_file_name + " - JSON file with raw data");
|
||||
spdlog::info(" " + html_file_name + " - HTML file with summary of commands");
|
||||
}
|
||||
|
||||
bool ScsiMon::Init()
|
||||
|
@ -116,7 +115,7 @@ bool ScsiMon::Init()
|
|||
|
||||
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
|
||||
if (bus == nullptr) {
|
||||
LOGERROR("Unable to intiailize the GPIO bus. Exiting....")
|
||||
spdlog::error("Unable to intiailize the GPIO bus. Exiting....");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -128,14 +127,14 @@ bool ScsiMon::Init()
|
|||
void ScsiMon::Cleanup() const
|
||||
{
|
||||
if (!import_data) {
|
||||
LOGINFO("Stopping data collection....")
|
||||
spdlog::info("Stopping data collection ...");
|
||||
}
|
||||
LOGINFO(" ")
|
||||
LOGINFO("Generating %s...", vcd_file_name.c_str())
|
||||
spdlog::info(" ");
|
||||
spdlog::info("Generating " + vcd_file_name + "...");
|
||||
scsimon_generate_value_change_dump(vcd_file_name, data_buffer);
|
||||
LOGINFO("Generating %s...", json_file_name.c_str())
|
||||
spdlog::info("Generating " + json_file_name + "...");
|
||||
scsimon_generate_json(json_file_name, data_buffer);
|
||||
LOGINFO("Generating %s...", html_file_name.c_str())
|
||||
spdlog::info("Generating " + html_file_name + "...");
|
||||
scsimon_generate_html(html_file_name, data_buffer);
|
||||
|
||||
bus->Cleanup();
|
||||
|
@ -179,15 +178,15 @@ int ScsiMon::run(const vector<char *> &args)
|
|||
if (import_data) {
|
||||
data_idx = scsimon_read_json(input_file_name, data_buffer);
|
||||
if (data_idx > 0) {
|
||||
LOGDEBUG("Read %d samples from %s", data_idx, input_file_name.c_str())
|
||||
spdlog::debug("Read " + to_string(data_idx) + " samples from '" + input_file_name + "'");
|
||||
Cleanup();
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
LOGINFO(" ")
|
||||
LOGINFO("Now collecting data.... Press CTRL-C to stop.")
|
||||
LOGINFO(" ")
|
||||
spdlog::info(" ");
|
||||
spdlog::info("Now collecting data.... Press CTRL-C to stop.");
|
||||
spdlog::info(" ");
|
||||
|
||||
// Initialize
|
||||
int ret = 0;
|
||||
|
@ -219,12 +218,12 @@ int ScsiMon::run(const vector<char *> &args)
|
|||
while (running) {
|
||||
loop_count++;
|
||||
if (loop_count > LLONG_MAX - 1) {
|
||||
LOGINFO("Maximum amount of time has elapsed. SCSIMON is terminating.")
|
||||
spdlog::info("Maximum amount of time has elapsed. SCSIMON is terminating.");
|
||||
running = false;
|
||||
}
|
||||
|
||||
if (data_idx >= (buff_size - 2)) {
|
||||
LOGINFO("Internal data buffer is full. SCSIMON is terminating.")
|
||||
spdlog::info("Internal data buffer is full. SCSIMON is terminating.");
|
||||
running = false;
|
||||
}
|
||||
|
||||
|
@ -249,15 +248,13 @@ int ScsiMon::run(const vector<char *> &args)
|
|||
timersub(&stop_time, &start_time, &time_diff);
|
||||
|
||||
elapsed_us = ((time_diff.tv_sec * 1000000) + time_diff.tv_usec);
|
||||
LOGINFO("%s", ("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) +
|
||||
" seconds")
|
||||
.c_str())
|
||||
LOGINFO("%s", ("Collected " + to_string(data_idx) + " changes").c_str())
|
||||
spdlog::info("Elapsed time: " + to_string(elapsed_us) + " microseconds (" + to_string(elapsed_us / 1000000) +
|
||||
" seconds");
|
||||
spdlog::info("Collected " + to_string(data_idx) + " changes");
|
||||
|
||||
ns_per_loop = (double)(elapsed_us * 1000) / (double)loop_count;
|
||||
LOGINFO("%s", ("Read the SCSI bus " + to_string(loop_count) + " times with an average of " +
|
||||
to_string(ns_per_loop) + " ns for each read")
|
||||
.c_str())
|
||||
spdlog::info("Read the SCSI bus " + to_string(loop_count) + " times with an average of " +
|
||||
to_string(ns_per_loop) + " ns for each read");
|
||||
|
||||
Cleanup();
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include "hal/data_sample.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -37,7 +38,7 @@ class ScsiMon
|
|||
|
||||
static void KillHandler(int);
|
||||
|
||||
static inline volatile bool running;
|
||||
static inline atomic<bool> running;
|
||||
|
||||
shared_ptr<BUS> bus;
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "sm_reports.h"
|
||||
#include <iostream>
|
||||
|
@ -173,7 +173,7 @@ static void print_html_data(ofstream& html_fp, const vector<shared_ptr<DataSampl
|
|||
|
||||
void scsimon_generate_html(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
|
||||
{
|
||||
LOGINFO("Creating HTML report file (%s)", filename.c_str())
|
||||
spdlog::info("Creating HTML report file (" + filename + ")");
|
||||
|
||||
ofstream html_ofstream;
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "hal/data_sample_raspberry.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include "sm_reports.h"
|
||||
#include "string.h"
|
||||
#include <fstream>
|
||||
|
@ -52,7 +52,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector<shared_ptr<DataSa
|
|||
|
||||
sample_count++;
|
||||
if (sample_count == UINT32_MAX) {
|
||||
LOGWARN("Maximum number of samples read. Some data may not be included.")
|
||||
spdlog::warn("Maximum number of samples read. Some data may not be included.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ uint32_t scsimon_read_json(const string &json_filename, vector<shared_ptr<DataSa
|
|||
//---------------------------------------------------------------------------
|
||||
void scsimon_generate_json(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
|
||||
{
|
||||
LOGTRACE("Creating JSON file (%s)", filename.c_str())
|
||||
spdlog::trace("Creating JSON file (" + filename + ")");
|
||||
ofstream json_ofstream;
|
||||
json_ofstream.open(filename.c_str(), ios::out);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
#include "hal/data_sample.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include "sm_core.h"
|
||||
#include "sm_reports.h"
|
||||
#include <fstream>
|
||||
|
@ -77,7 +77,7 @@ static void vcd_output_if_changed_byte(ofstream &fp, uint8_t data, int pin, char
|
|||
|
||||
void scsimon_generate_value_change_dump(const string &filename, const vector<shared_ptr<DataSample>> &data_capture_array)
|
||||
{
|
||||
LOGTRACE("Creating Value Change Dump file (%s)", filename.c_str())
|
||||
spdlog::trace("Creating Value Change Dump file (" + filename + ")");
|
||||
ofstream vcd_ofstream;
|
||||
vcd_ofstream.open(filename.c_str(), ios::out);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -13,7 +13,7 @@ using namespace std;
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const vector<char *> args(argv, argv + argc);
|
||||
vector<char *> args(argv, argv + argc);
|
||||
|
||||
return Piscsi().run(args);
|
||||
}
|
||||
|
|
|
@ -3,24 +3,50 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "command_context.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
using namespace protobuf_util;
|
||||
|
||||
void CommandContext::Cleanup()
|
||||
bool CommandContext::ReadCommand()
|
||||
{
|
||||
if (fd != -1) {
|
||||
close(fd);
|
||||
fd = -1;
|
||||
// Read magic string
|
||||
array<byte, 6> magic;
|
||||
if (const size_t bytes_read = ReadBytes(fd, magic); bytes_read) {
|
||||
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
|
||||
throw io_exception("Invalid magic");
|
||||
}
|
||||
|
||||
// Fetch the command
|
||||
DeserializeMessage(fd, command);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void CommandContext::WriteResult(const PbResult& result) const
|
||||
{
|
||||
// The descriptor is -1 when devices are not attached via the remote interface but by the piscsi tool
|
||||
if (fd != -1) {
|
||||
SerializeMessage(fd, result);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandContext::WriteSuccessResult(PbResult& result) const
|
||||
{
|
||||
result.set_status(true);
|
||||
WriteResult(result);
|
||||
}
|
||||
|
||||
bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2,
|
||||
|
@ -33,7 +59,7 @@ bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error
|
|||
const string& arg2, const string& arg3) const
|
||||
{
|
||||
// For the logfile always use English
|
||||
LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
|
||||
spdlog::error(localizer.Localize(key, "en", arg1, arg2, arg3));
|
||||
|
||||
return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false);
|
||||
}
|
||||
|
@ -42,17 +68,12 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
|
|||
{
|
||||
// Do not log twice if logging has already been done in the localized error handling above
|
||||
if (log && !status && !msg.empty()) {
|
||||
LOGERROR("%s", msg.c_str())
|
||||
spdlog::error(msg);
|
||||
}
|
||||
|
||||
if (fd == -1) {
|
||||
if (!msg.empty()) {
|
||||
if (status) {
|
||||
cerr << "Error: " << msg << endl;
|
||||
}
|
||||
else {
|
||||
cout << msg << endl;
|
||||
}
|
||||
cerr << "Error: " << msg << endl;
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -60,8 +81,18 @@ bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode er
|
|||
result.set_status(status);
|
||||
result.set_error_code(error_code);
|
||||
result.set_msg(msg);
|
||||
serializer.SerializeMessage(fd, result);
|
||||
WriteResult(result);
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool CommandContext::ReturnSuccessStatus() const
|
||||
{
|
||||
return ReturnStatus(true, "", PbErrorCode::NO_ERROR_CODE, true);
|
||||
}
|
||||
|
||||
bool CommandContext::ReturnErrorStatus(const string& msg) const
|
||||
{
|
||||
return ReturnStatus(false, msg, PbErrorCode::NO_ERROR_CODE, true);
|
||||
}
|
||||
|
|
|
@ -3,15 +3,14 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "localizer.h"
|
||||
#include "shared/protobuf_serializer.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
@ -19,27 +18,36 @@ using namespace piscsi_interface;
|
|||
|
||||
class CommandContext
|
||||
{
|
||||
const ProtobufSerializer serializer;
|
||||
|
||||
const Localizer localizer;
|
||||
|
||||
string locale;
|
||||
|
||||
int fd;
|
||||
|
||||
public:
|
||||
|
||||
CommandContext(const std::string& s = "", int f = -1) : locale(s), fd(f) {}
|
||||
CommandContext(const PbCommand& cmd, string_view f, string_view l) : command(cmd), default_folder(f), locale(l) {}
|
||||
explicit CommandContext(int f) : fd(f) {}
|
||||
~CommandContext() = default;
|
||||
|
||||
void Cleanup();
|
||||
|
||||
const ProtobufSerializer& GetSerializer() const { return serializer; }
|
||||
int GetFd() const { return fd; }
|
||||
void SetFd(int f) { fd = f; }
|
||||
bool IsValid() const { return fd != -1; }
|
||||
string GetDefaultFolder() const { return default_folder; }
|
||||
void SetDefaultFolder(string_view f) { default_folder = f; }
|
||||
bool ReadCommand();
|
||||
void WriteResult(const PbResult&) const;
|
||||
void WriteSuccessResult(PbResult&) const;
|
||||
const PbCommand& GetCommand() const { return command; }
|
||||
|
||||
bool ReturnLocalizedError(LocalizationKey, const string& = "", const string& = "", const string& = "") const;
|
||||
bool ReturnLocalizedError(LocalizationKey, PbErrorCode, const string& = "", const string& = "", const string& = "") const;
|
||||
bool ReturnStatus(bool = true, const string& = "", PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true) const;
|
||||
bool ReturnSuccessStatus() const;
|
||||
bool ReturnErrorStatus(const string&) const;
|
||||
|
||||
private:
|
||||
|
||||
bool ReturnStatus(bool, const string&, PbErrorCode, bool) const;
|
||||
|
||||
const Localizer localizer;
|
||||
|
||||
PbCommand command;
|
||||
|
||||
string default_folder;
|
||||
|
||||
string locale;
|
||||
|
||||
int fd = -1;
|
||||
};
|
||||
|
|
|
@ -3,15 +3,13 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "localizer.h"
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <regex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -31,12 +29,12 @@ Localizer::Localizer()
|
|||
Add(LocalizationKey::ERROR_OPERATION, "es", "Operación desconocida");
|
||||
Add(LocalizationKey::ERROR_OPERATION, "zh", "未知操作");
|
||||
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level %1");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level %1");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå %1");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide %1");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro %1 no válido");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 %1");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "en", "Invalid log level '%1'");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "de", "Ungültiger Log-Level '%1'");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "sv", "Ogiltig loggnivå '%1'");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "fr", "Niveau de journalisation invalide '%1'");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "es", "Nivel de registro '%1' no válido");
|
||||
Add(LocalizationKey::ERROR_LOG_LEVEL, "zh", "无效的日志级别 '%1'");
|
||||
|
||||
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "en", "Missing device ID");
|
||||
Add(LocalizationKey::ERROR_MISSING_DEVICE_ID, "de", "Fehlende Geräte-ID");
|
||||
|
@ -51,26 +49,26 @@ Localizer::Localizer()
|
|||
Add(LocalizationKey::ERROR_MISSING_FILENAME, "fr", "Nom de fichier manquant");
|
||||
Add(LocalizationKey::ERROR_MISSING_FILENAME, "es", "Falta el nombre del archivo");
|
||||
Add(LocalizationKey::ERROR_MISSING_FILENAME, "zh", "缺少文件名");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "en", "Device type %1 requires a filename");
|
||||
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "de", "Gerätetyp %1 erfordert einen Dateinamen");
|
||||
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "sv", "Enhetstypen %1 kräver ett filnamn");
|
||||
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "es", "El tipo de dispositivo %1 requiere un nombre de archivo");
|
||||
Add(LocalizationKey::ERROR_DEVICE_MISSING_FILENAME, "zh", "设备类型 %1 需要一个文件名");
|
||||
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by ID %2, unit %3");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von ID %2, Einheit %3 benutzt");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av id %2, enhetsnummer %3");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par l'ID %2, unité %3");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por el ID %2, unidad %3");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2、单元 %3 使用");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "en", "Image file '%1' is already being used by device %2");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "de", "Image-Datei '%1' wird bereits von Gerät %2 benutzt");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "sv", "Skivbildsfilen '%1' används redan av nhet %2");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "fr", "Le fichier d'image '%1' est déjà utilisé par périphérique %2");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "es", "El archivo de imagen '%1' ya está siendo utilizado por dispositivo %2");
|
||||
Add(LocalizationKey::ERROR_IMAGE_IN_USE, "zh", "图像文件%1已被 ID %2");
|
||||
|
||||
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "en", "Can't create image file info for '%1'");
|
||||
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "de", "Image-Datei-Information für '%1' kann nicht erzeugt werden");
|
||||
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "sv", "Kunde ej skapa skivbildsfilsinfo för '%1'");
|
||||
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "es", "No se puede crear información de archivo de imagen para '%1'");
|
||||
Add(LocalizationKey::ERROR_IMAGE_FILE_INFO, "zh", "无法为'%1'创建图像文件信息");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_RESERVED_ID, "en", "Device ID %1 is reserved");
|
||||
Add(LocalizationKey::ERROR_RESERVED_ID, "de", "Geräte-ID %1 ist reserviert");
|
||||
Add(LocalizationKey::ERROR_RESERVED_ID, "sv", "Enhets-id %1 är reserverat");
|
||||
|
@ -84,21 +82,21 @@ Localizer::Localizer()
|
|||
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "fr", "Commande pour ID %1 non-existant");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "es", "Comando para ID %1 no existente");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_DEVICE, "zh", "不存在的 ID %1 的指令");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "en", "Command for non-existing ID %1, unit %2");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "de", "Kommando für nicht existente ID %1, Einheit %2");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "sv", "Kommando för id %1, enhetsnummer %2 som ej existerar");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "fr", "Command pour ID %1, unité %2 non-existant");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "es", "Comando para ID %1 inexistente, unidad %2");
|
||||
Add(LocalizationKey::ERROR_NON_EXISTING_UNIT, "zh", "不存在的 ID %1, 单元 %2 的指令");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "en", "Unknown device type %1");
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "de", "Unbekannter Gerätetyp %1");
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "sv", "Obekant enhetstyp: %1");
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "fr", "Type de périphérique inconnu %1");
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "es", "Tipo de dispositivo desconocido %1");
|
||||
Add(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, "zh", "未知设备类型 %1");
|
||||
|
||||
|
||||
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "en", "Device type required for unknown extension of file '%1'");
|
||||
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "de", "Gerätetyp erforderlich für unbekannte Extension der Datei '%1'");
|
||||
Add(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, "sv", "Man måste ange enhetstyp för obekant filändelse '%1'");
|
||||
|
@ -199,11 +197,11 @@ Localizer::Localizer()
|
|||
Add(LocalizationKey::ERROR_LUN0, "es", "El LUN 0 no se puede desconectar mientras haya otro LUN");
|
||||
Add(LocalizationKey::ERROR_LUN0, "zh", "LUN 0 无法卸载,因为当前仍有另一个 LUN。");
|
||||
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 device, ID %2, LUN %3 failed");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1-Gerät, ID %2, LUN %3 fehlgeschlagen");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera enheten %1 med id %2 och enhetsnummer %3");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del dispositivo %1, ID %2, LUN %3 falló");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 设备、ID %2、LUN %3 的初始化失败");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "en", "Initialization of %1 failed");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "de", "Initialisierung von %1 fehlgeschlagen");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "sv", "Kunde ej initialisera %1 ");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "es", "La inicialización del %1 falló");
|
||||
Add(LocalizationKey::ERROR_INITIALIZATION, "zh", "%1 的初始化失败");
|
||||
|
||||
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "en", "%1 operation denied, %2 isn't stoppable");
|
||||
Add(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, "de", "%1-Operation verweigert, %2 ist nicht stopbar");
|
||||
|
@ -235,17 +233,16 @@ void Localizer::Add(LocalizationKey key, const string& locale, string_view value
|
|||
// Safeguards against empty messages, duplicate entries and unsupported locales
|
||||
assert(locale.size());
|
||||
assert(value.size());
|
||||
assert(supported_languages.find(locale) != supported_languages.end());
|
||||
assert(supported_languages.contains(locale));
|
||||
assert(localized_messages[locale][key].empty());
|
||||
|
||||
localized_messages[locale][key] = value;
|
||||
}
|
||||
|
||||
string Localizer::Localize(LocalizationKey key, const string& locale, const string& arg1, const string& arg2,
|
||||
const string &arg3) const
|
||||
{
|
||||
string locale_lower = locale;
|
||||
transform(locale_lower.begin(), locale_lower.end(), locale_lower.begin(), ::tolower);
|
||||
string locale_lower;
|
||||
ranges::transform(locale, back_inserter(locale_lower), ::tolower);
|
||||
|
||||
auto it = localized_messages.find(locale_lower);
|
||||
if (it == localized_messages.end()) {
|
||||
|
@ -268,9 +265,9 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri
|
|||
}
|
||||
|
||||
string message = m->second;
|
||||
message = regex_replace(message, regex("%1"), arg1);
|
||||
message = regex_replace(message, regex("%2"), arg2);
|
||||
message = regex_replace(message, regex("%3"), arg3);
|
||||
message = regex_replace(message, regex1, arg1);
|
||||
message = regex_replace(message, regex2, arg2);
|
||||
message = regex_replace(message, regex3, arg3);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
// Message localization support. Currently only for messages with up to 3 string parameters.
|
||||
//
|
||||
|
@ -11,9 +11,11 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "shared/piscsi_util.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <regex>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -64,8 +66,12 @@ public:
|
|||
private:
|
||||
|
||||
void Add(LocalizationKey, const string&, string_view);
|
||||
unordered_map<string, unordered_map<LocalizationKey, string>> localized_messages;
|
||||
unordered_map<string, unordered_map<LocalizationKey, string>, piscsi_util::StringHash, equal_to<>> localized_messages;
|
||||
|
||||
// Supported locales, always lower case
|
||||
unordered_set<string> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
|
||||
unordered_set<string, piscsi_util::StringHash, equal_to<>> supported_languages = { "en", "de", "sv", "fr", "es", "zh" };
|
||||
|
||||
const regex regex1 = regex("%1");
|
||||
const regex regex2 = regex("%2");
|
||||
const regex regex3 = regex("%3");
|
||||
};
|
||||
|
|
|
@ -1,57 +1,52 @@
|
|||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2020-2023 Contributors to the PiSCSI project
|
||||
// Copyright (C) 2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/config.h"
|
||||
#include "shared/log.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_serializer.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "controllers/scsi_controller.h"
|
||||
#include "devices/device_logger.h"
|
||||
#include "devices/device_factory.h"
|
||||
#include "devices/storage_device.h"
|
||||
#include "hal/gpiobus_factory.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "piscsi/piscsi_executor.h"
|
||||
#include "piscsi/piscsi_core.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <netinet/in.h>
|
||||
#include <csignal>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
using namespace spdlog;
|
||||
using namespace piscsi_interface;
|
||||
using namespace piscsi_util;
|
||||
using namespace protobuf_util;
|
||||
using namespace scsi_defs;
|
||||
|
||||
void Piscsi::Banner(const vector<char *>& args) const
|
||||
void Piscsi::Banner(span<char *> args) const
|
||||
{
|
||||
cout << piscsi_util::Banner("(Backend Service)");
|
||||
cout << "Connection type: " << CONNECT_DESC << '\n' << flush;
|
||||
|
||||
if ((args.size() > 1 && strcmp(args[1], "-h") == 0) || (args.size() > 1 && strcmp(args[1], "--help") == 0)){
|
||||
cout << "\nUsage: " << args[0] << " [-idID[:LUN] FILE] ...\n\n";
|
||||
cout << " ID is SCSI device ID (0-7).\n";
|
||||
cout << " LUN is the optional logical unit (0-31).\n";
|
||||
cout << " ID is SCSI device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n";
|
||||
cout << " LUN is the optional logical unit (0-" << (ControllerManager::GetScsiLunMax() - 1) <<").\n";
|
||||
cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n";
|
||||
cout << " Image type is detected based on file extension if no explicit type is specified.\n";
|
||||
cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n";
|
||||
|
@ -69,59 +64,57 @@ void Piscsi::Banner(const vector<char *>& args) const
|
|||
}
|
||||
}
|
||||
|
||||
bool Piscsi::InitBus() const
|
||||
bool Piscsi::InitBus()
|
||||
{
|
||||
bus = GPIOBUS_Factory::Create(BUS::mode_e::TARGET);
|
||||
if (bus == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto b = bus;
|
||||
controller_manager = make_shared<ControllerManager>(*b);
|
||||
auto c = controller_manager;
|
||||
executor = make_shared<PiscsiExecutor>(piscsi_image, *c);
|
||||
executor = make_unique<PiscsiExecutor>(piscsi_image, *bus, controller_manager);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Piscsi::Cleanup()
|
||||
void Piscsi::CleanUp()
|
||||
{
|
||||
executor->DetachAll();
|
||||
if (service.IsRunning()) {
|
||||
service.Stop();
|
||||
}
|
||||
|
||||
service.Cleanup();
|
||||
executor->DetachAll();
|
||||
|
||||
bus->Cleanup();
|
||||
}
|
||||
|
||||
void Piscsi::ReadAccessToken(const string& filename) const
|
||||
void Piscsi::ReadAccessToken(const path& filename)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(filename.c_str(), &st) || !S_ISREG(st.st_mode)) {
|
||||
throw parser_exception("Can't access token file '" + filename + "'");
|
||||
if (error_code error; !is_regular_file(filename, error)) {
|
||||
throw parser_exception("Access token file '" + filename.string() + "' must be a regular file");
|
||||
}
|
||||
|
||||
if (st.st_uid || st.st_gid) {
|
||||
throw parser_exception("Access token file '" + filename + "' must be owned by root");
|
||||
if (struct stat st; stat(filename.c_str(), &st) || st.st_uid || st.st_gid) {
|
||||
throw parser_exception("Access token file '" + filename.string() + "' must be owned by root");
|
||||
}
|
||||
|
||||
if (const auto perms = filesystem::status(filename).permissions();
|
||||
(perms & perms::group_read) != perms::none || (perms & perms::others_read) != perms::none ||
|
||||
(perms & perms::group_write) != perms::none || (perms & perms::others_write) != perms::none) {
|
||||
throw parser_exception("Access token file '" + filename + "' must be readable by root only");
|
||||
throw parser_exception("Access token file '" + filename.string() + "' must be readable by root only");
|
||||
}
|
||||
|
||||
ifstream token_file(filename);
|
||||
if (token_file.fail()) {
|
||||
throw parser_exception("Can't open access token file '" + filename + "'");
|
||||
throw parser_exception("Can't open access token file '" + filename.string() + "'");
|
||||
}
|
||||
|
||||
getline(token_file, access_token);
|
||||
if (token_file.fail()) {
|
||||
throw parser_exception("Can't read access token file '" + filename + "'");
|
||||
throw parser_exception("Can't read access token file '" + filename.string() + "'");
|
||||
}
|
||||
|
||||
if (access_token.empty()) {
|
||||
throw parser_exception("Access token file '" + filename + "' must not be empty");
|
||||
throw parser_exception("Access token file '" + filename.string() + "' must not be empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,59 +124,74 @@ void Piscsi::LogDevices(string_view devices) const
|
|||
string line;
|
||||
|
||||
while (getline(ss, line, '\n')) {
|
||||
LOGINFO("%s", line.c_str())
|
||||
spdlog::info(line);
|
||||
}
|
||||
}
|
||||
|
||||
PbDeviceType Piscsi::ParseDeviceType(const string& value) const
|
||||
{
|
||||
string t = value;
|
||||
PbDeviceType type;
|
||||
transform(t.begin(), t.end(), t.begin(), ::toupper);
|
||||
if (!PbDeviceType_Parse(t, &type)) {
|
||||
throw parser_exception("Illegal device type '" + value + "'");
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
void Piscsi::TerminationHandler(int)
|
||||
{
|
||||
Cleanup();
|
||||
instance->CleanUp();
|
||||
|
||||
// Process will terminate automatically
|
||||
}
|
||||
|
||||
Piscsi::optargs_type Piscsi::ParseArguments(const vector<char *>& args, int& port) const
|
||||
string Piscsi::ParseArguments(span<char *> args, PbCommand& command, int& port, string& reserved_ids)
|
||||
{
|
||||
optargs_type optargs;
|
||||
string log_level = "info";
|
||||
PbDeviceType type = UNDEFINED;
|
||||
int block_size = 0;
|
||||
string name;
|
||||
string id_and_lun;
|
||||
|
||||
string locale = GetLocale();
|
||||
|
||||
// Avoid duplicate messages while parsing
|
||||
set_level(level::off);
|
||||
|
||||
opterr = 1;
|
||||
int opt;
|
||||
while ((opt = getopt(static_cast<int>(args.size()), args.data(), "-Iib:d:n:p:r:t:z:D:F:L:P:R:C:v")) != -1) {
|
||||
switch (opt) {
|
||||
// The following options can not be processed until AFTER
|
||||
// the 'bus' object is created and configured
|
||||
// The two options below are kind of a compound option with two letters
|
||||
case 'i':
|
||||
case 'I':
|
||||
case 'b':
|
||||
continue;
|
||||
|
||||
case 'd':
|
||||
case 'D':
|
||||
case 'R':
|
||||
case 'n':
|
||||
case 'r':
|
||||
case 't':
|
||||
case 'F':
|
||||
case 'z':
|
||||
{
|
||||
const string optarg_str = optarg == nullptr ? "" : optarg;
|
||||
optargs.emplace_back(opt, optarg_str);
|
||||
id_and_lun = optarg;
|
||||
continue;
|
||||
|
||||
case 'b':
|
||||
if (!GetAsUnsignedInt(optarg, block_size)) {
|
||||
throw parser_exception("Invalid block size " + string(optarg));
|
||||
}
|
||||
continue;
|
||||
|
||||
case 'z':
|
||||
locale = optarg;
|
||||
continue;
|
||||
|
||||
case 'F':
|
||||
if (const string error = piscsi_image.SetDefaultFolder(optarg); !error.empty()) {
|
||||
throw parser_exception(error);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
case 'L':
|
||||
current_log_level = optarg;
|
||||
log_level = optarg;
|
||||
continue;
|
||||
|
||||
case 'R':
|
||||
int depth;
|
||||
if (!GetAsUnsignedInt(optarg, depth)) {
|
||||
throw parser_exception("Invalid image file scan depth " + string(optarg));
|
||||
}
|
||||
piscsi_image.SetDepth(depth);
|
||||
continue;
|
||||
|
||||
case 'n':
|
||||
name = optarg;
|
||||
continue;
|
||||
|
||||
case 'p':
|
||||
|
@ -196,92 +204,12 @@ Piscsi::optargs_type Piscsi::ParseArguments(const vector<char *>& args, int& por
|
|||
ReadAccessToken(optarg);
|
||||
continue;
|
||||
|
||||
case 'v':
|
||||
cout << piscsi_get_version_string() << endl;
|
||||
exit(0);
|
||||
|
||||
case 1:
|
||||
{
|
||||
// Encountered filename
|
||||
const string optarg_str = (optarg == nullptr) ? "" : string(optarg);
|
||||
optargs.emplace_back(opt, optarg_str);
|
||||
continue;
|
||||
}
|
||||
|
||||
default:
|
||||
throw parser_exception("Parser error");
|
||||
}
|
||||
|
||||
if (optopt) {
|
||||
throw parser_exception("Parser error");
|
||||
}
|
||||
}
|
||||
|
||||
return optargs;
|
||||
}
|
||||
|
||||
void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
|
||||
{
|
||||
PbCommand command;
|
||||
PbDeviceType type = UNDEFINED;
|
||||
int block_size = 0;
|
||||
string name;
|
||||
string log_level;
|
||||
string id_and_lun;
|
||||
|
||||
const char *locale = setlocale(LC_MESSAGES, "");
|
||||
if (locale == nullptr || !strcmp(locale, "C")) {
|
||||
locale = "en";
|
||||
}
|
||||
|
||||
opterr = 1;
|
||||
for (const auto& [option, value] : optargs) {
|
||||
switch (option) {
|
||||
case 'i':
|
||||
case 'I':
|
||||
continue;
|
||||
|
||||
case 'd':
|
||||
case 'D':
|
||||
id_and_lun = value;
|
||||
continue;
|
||||
|
||||
case 'b':
|
||||
if (!GetAsUnsignedInt(value, block_size)) {
|
||||
throw parser_exception("Invalid block size " + value);
|
||||
}
|
||||
continue;
|
||||
|
||||
case 'z':
|
||||
locale = value.c_str();
|
||||
continue;
|
||||
|
||||
case 'F':
|
||||
if (const string error = piscsi_image.SetDefaultFolder(value); !error.empty()) {
|
||||
throw parser_exception(error);
|
||||
}
|
||||
continue;
|
||||
|
||||
case 'R':
|
||||
int depth;
|
||||
if (!GetAsUnsignedInt(value, depth)) {
|
||||
throw parser_exception("Invalid image file scan depth " + value);
|
||||
}
|
||||
piscsi_image.SetDepth(depth);
|
||||
continue;
|
||||
|
||||
case 'n':
|
||||
name = value;
|
||||
continue;
|
||||
|
||||
case 'r':
|
||||
if (const string error = executor->SetReservedIds(value); !error.empty()) {
|
||||
throw parser_exception(error);
|
||||
}
|
||||
reserved_ids = optarg;
|
||||
continue;
|
||||
|
||||
case 't':
|
||||
type = ParseDeviceType(value);
|
||||
type = ParseDeviceType(optarg);
|
||||
continue;
|
||||
|
||||
case 1:
|
||||
|
@ -292,10 +220,16 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
|
|||
throw parser_exception("Parser error");
|
||||
}
|
||||
|
||||
PbDeviceDefinition *device = command.add_devices();
|
||||
if (optopt) {
|
||||
throw parser_exception("Parser error");
|
||||
}
|
||||
|
||||
// Set up the device data
|
||||
|
||||
auto device = command.add_devices();
|
||||
|
||||
if (!id_and_lun.empty()) {
|
||||
if (const string error = SetIdAndLun(*device, id_and_lun, ScsiController::LUN_MAX); !error.empty()) {
|
||||
if (const string error = SetIdAndLun(*device, id_and_lun); !error.empty()) {
|
||||
throw parser_exception(error);
|
||||
}
|
||||
}
|
||||
|
@ -303,7 +237,7 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
|
|||
device->set_type(type);
|
||||
device->set_block_size(block_size);
|
||||
|
||||
ParseParameters(*device, value);
|
||||
ParseParameters(*device, optarg);
|
||||
|
||||
SetProductData(*device, name);
|
||||
|
||||
|
@ -313,182 +247,231 @@ void Piscsi::CreateInitialDevices(const optargs_type& optargs) const
|
|||
id_and_lun = "";
|
||||
}
|
||||
|
||||
// Attach all specified devices
|
||||
command.set_operation(ATTACH);
|
||||
|
||||
if (CommandContext context(locale); !executor->ProcessCmd(context, command)) {
|
||||
throw parser_exception("Can't execute " + PbOperation_Name(command.operation()));
|
||||
if (!SetLogLevel(log_level)) {
|
||||
throw parser_exception("Invalid log level '" + log_level + "'");
|
||||
}
|
||||
|
||||
// Display and log the device list
|
||||
PbServerInfo server_info;
|
||||
piscsi_response.GetDevices(controller_manager->GetAllDevices(), server_info, piscsi_image.GetDefaultFolder());
|
||||
const list<PbDevice>& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
|
||||
const string device_list = ListDevices(devices);
|
||||
LogDevices(device_list);
|
||||
cout << device_list << flush;
|
||||
return locale;
|
||||
}
|
||||
|
||||
bool Piscsi::ExecuteCommand(const CommandContext& context, const PbCommand& command)
|
||||
PbDeviceType Piscsi::ParseDeviceType(const string& value)
|
||||
{
|
||||
string t;
|
||||
ranges::transform(value, back_inserter(t), ::toupper);
|
||||
if (PbDeviceType type; PbDeviceType_Parse(t, &type)) {
|
||||
return type;
|
||||
}
|
||||
|
||||
throw parser_exception("Illegal device type '" + value + "'");
|
||||
}
|
||||
|
||||
bool Piscsi::SetLogLevel(const string& log_level) const
|
||||
{
|
||||
int id = -1;
|
||||
int lun = -1;
|
||||
string level = log_level;
|
||||
|
||||
if (const auto& components = Split(log_level, COMPONENT_SEPARATOR, 2); !components.empty()) {
|
||||
level = components[0];
|
||||
|
||||
if (components.size() > 1) {
|
||||
if (const string error = ProcessId(components[1], id, lun); !error.empty()) {
|
||||
spdlog::warn("Error setting log level: " + error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const level::level_enum l = level::from_str(level);
|
||||
// Compensate for spdlog using 'off' for unknown levels
|
||||
if (to_string_view(l) != level) {
|
||||
spdlog::warn("Invalid log level '" + level + "'");
|
||||
return false;
|
||||
}
|
||||
|
||||
set_level(l);
|
||||
DeviceLogger::SetLogIdAndLun(id, lun);
|
||||
|
||||
if (id != -1) {
|
||||
if (lun == -1) {
|
||||
spdlog::info("Set log level for device " + to_string(id) + " to '" + level + "'");
|
||||
}
|
||||
else {
|
||||
spdlog::info("Set log level for device " + to_string(id) + ":" + to_string(lun) + " to '" + level + "'");
|
||||
}
|
||||
}
|
||||
else {
|
||||
spdlog::info("Set log level to '" + level + "'");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Piscsi::ExecuteCommand(CommandContext& context)
|
||||
{
|
||||
context.SetDefaultFolder(piscsi_image.GetDefaultFolder());
|
||||
|
||||
const PbCommand& command = context.GetCommand();
|
||||
|
||||
if (!access_token.empty() && access_token != GetParam(command, "token")) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
|
||||
}
|
||||
|
||||
if (!PbOperation_IsValid(command.operation())) {
|
||||
LOGERROR("Received unknown command with operation opcode %d", command.operation())
|
||||
spdlog::error("Received unknown command with operation opcode " + to_string(command.operation()));
|
||||
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION);
|
||||
}
|
||||
|
||||
LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str())
|
||||
spdlog::trace("Received " + PbOperation_Name(command.operation()) + " command");
|
||||
|
||||
PbResult result;
|
||||
ProtobufSerializer serializer;
|
||||
|
||||
switch(command.operation()) {
|
||||
case LOG_LEVEL: {
|
||||
const string log_level = GetParam(command, "level");
|
||||
if (const bool status = executor->SetLogLevel(log_level); !status) {
|
||||
case LOG_LEVEL:
|
||||
if (const string log_level = GetParam(command, "level"); !SetLogLevel(log_level)) {
|
||||
context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level);
|
||||
}
|
||||
else {
|
||||
current_log_level = log_level;
|
||||
|
||||
context.ReturnStatus();
|
||||
context.ReturnSuccessStatus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DEFAULT_FOLDER: {
|
||||
if (const string status = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) {
|
||||
context.ReturnStatus(false, status);
|
||||
case DEFAULT_FOLDER:
|
||||
if (const string error = piscsi_image.SetDefaultFolder(GetParam(command, "folder")); !error.empty()) {
|
||||
context.ReturnErrorStatus(error);
|
||||
}
|
||||
else {
|
||||
context.ReturnStatus();
|
||||
context.ReturnSuccessStatus();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DEVICES_INFO: {
|
||||
piscsi_response.GetDevicesInfo(controller_manager->GetAllDevices(), result, command,
|
||||
piscsi_image.GetDefaultFolder());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case DEVICES_INFO:
|
||||
response.GetDevicesInfo(controller_manager.GetAllDevices(), result, command, piscsi_image.GetDefaultFolder());
|
||||
context.WriteResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case DEVICE_TYPES_INFO: {
|
||||
result.set_allocated_device_types_info(piscsi_response.GetDeviceTypesInfo(result).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case DEVICE_TYPES_INFO:
|
||||
response.GetDeviceTypesInfo(*result.mutable_device_types_info());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case SERVER_INFO: {
|
||||
result.set_allocated_server_info(piscsi_response.GetServerInfo(controller_manager->GetAllDevices(),
|
||||
result, executor->GetReservedIds(), current_log_level, piscsi_image.GetDefaultFolder(),
|
||||
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"),
|
||||
piscsi_image.GetDepth()).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case SERVER_INFO:
|
||||
response.GetServerInfo(*result.mutable_server_info(), controller_manager.GetAllDevices(),
|
||||
executor->GetReservedIds(), piscsi_image.GetDefaultFolder(),
|
||||
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case VERSION_INFO: {
|
||||
result.set_allocated_version_info(piscsi_response.GetVersionInfo(result).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case VERSION_INFO:
|
||||
response.GetVersionInfo(*result.mutable_version_info());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case LOG_LEVEL_INFO: {
|
||||
result.set_allocated_log_level_info(piscsi_response.GetLogLevelInfo(result, current_log_level).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case LOG_LEVEL_INFO:
|
||||
response.GetLogLevelInfo(*result.mutable_log_level_info());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case DEFAULT_IMAGE_FILES_INFO: {
|
||||
result.set_allocated_image_files_info(piscsi_response.GetAvailableImages(result,
|
||||
piscsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"),
|
||||
GetParam(command, "file_pattern"), piscsi_image.GetDepth()).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case DEFAULT_IMAGE_FILES_INFO:
|
||||
response.GetImageFilesInfo(*result.mutable_image_files_info(), piscsi_image.GetDefaultFolder(),
|
||||
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"), piscsi_image.GetDepth());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case IMAGE_FILE_INFO: {
|
||||
case IMAGE_FILE_INFO:
|
||||
if (string filename = GetParam(command, "file"); filename.empty()) {
|
||||
context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME);
|
||||
}
|
||||
else {
|
||||
auto image_file = make_unique<PbImageFile>();
|
||||
const bool status = piscsi_response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(), filename);
|
||||
const bool status = response.GetImageFile(*image_file.get(), piscsi_image.GetDefaultFolder(),
|
||||
filename);
|
||||
if (status) {
|
||||
result.set_status(true);
|
||||
result.set_allocated_image_file_info(image_file.get());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
result.set_status(true);
|
||||
context.WriteResult(result);
|
||||
}
|
||||
else {
|
||||
context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case NETWORK_INTERFACES_INFO: {
|
||||
result.set_allocated_network_interfaces_info(piscsi_response.GetNetworkInterfacesInfo(result).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case NETWORK_INTERFACES_INFO:
|
||||
response.GetNetworkInterfacesInfo(*result.mutable_network_interfaces_info());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case MAPPING_INFO: {
|
||||
result.set_allocated_mapping_info(piscsi_response.GetMappingInfo(result).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case MAPPING_INFO:
|
||||
response.GetMappingInfo(*result.mutable_mapping_info());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPERATION_INFO: {
|
||||
result.set_allocated_operation_info(piscsi_response.GetOperationInfo(result,
|
||||
piscsi_image.GetDepth()).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case OPERATION_INFO:
|
||||
response.GetOperationInfo(*result.mutable_operation_info(), piscsi_image.GetDepth());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case RESERVED_IDS_INFO: {
|
||||
result.set_allocated_reserved_ids_info(piscsi_response.GetReservedIds(result,
|
||||
executor->GetReservedIds()).release());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
case RESERVED_IDS_INFO:
|
||||
response.GetReservedIds(*result.mutable_reserved_ids_info(), executor->GetReservedIds());
|
||||
context.WriteSuccessResult(result);
|
||||
break;
|
||||
}
|
||||
|
||||
case SHUT_DOWN: {
|
||||
case SHUT_DOWN:
|
||||
if (executor->ShutDown(context, GetParam(command, "mode"))) {
|
||||
TerminationHandler(0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// Wait until we become idle
|
||||
case NO_OPERATION:
|
||||
context.ReturnSuccessStatus();
|
||||
break;
|
||||
|
||||
// TODO The image operations below can most likely directly be executed without calling the executor,
|
||||
// because they do not require the target to be idle
|
||||
case CREATE_IMAGE:
|
||||
case DELETE_IMAGE:
|
||||
case RENAME_IMAGE:
|
||||
case COPY_IMAGE:
|
||||
case PROTECT_IMAGE:
|
||||
case UNPROTECT_IMAGE:
|
||||
case RESERVE_IDS:
|
||||
return executor->ProcessCmd(context);
|
||||
|
||||
// The remaining commands can only be executed when the target is idle
|
||||
// TODO What happens when the target becomes active while the command is still being executed?
|
||||
// A field 'mutex locker' can probably avoid SCSI commands and ProcessCmd() being executed at the same time
|
||||
default:
|
||||
// TODO Find a better way to wait
|
||||
const timespec ts = { .tv_sec = 0, .tv_nsec = 500'000'000};
|
||||
while (active) {
|
||||
while (target_is_active) {
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
|
||||
executor->ProcessCmd(context, command);
|
||||
break;
|
||||
}
|
||||
return executor->ProcessCmd(context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Piscsi::run(const vector<char *>& args)
|
||||
int Piscsi::run(span<char *> args)
|
||||
{
|
||||
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
||||
|
||||
Banner(args);
|
||||
|
||||
// The -v option shall result in no other action except displaying the version
|
||||
if (ranges::find_if(args, [] (const char *arg) { return !strcasecmp(arg, "-v"); } ) != args.end()) {
|
||||
cout << piscsi_get_version_string() << '\n';
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
PbCommand command;
|
||||
string locale;
|
||||
string reserved_ids;
|
||||
int port = DEFAULT_PORT;
|
||||
optargs_type optargs;
|
||||
try {
|
||||
optargs = ParseArguments(args, port);
|
||||
locale = ParseArguments(args, command, port, reserved_ids);
|
||||
}
|
||||
catch(const parser_exception& e) {
|
||||
cerr << "Error: " << e.what() << endl;
|
||||
|
@ -496,35 +479,51 @@ int Piscsi::run(const vector<char *>& args)
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// current_log_level may have been updated by ParseArguments()
|
||||
executor->SetLogLevel(current_log_level);
|
||||
|
||||
// Create a thread-safe stdout logger to process the log messages
|
||||
const auto logger = stdout_color_mt("piscsi stdout logger");
|
||||
|
||||
if (!InitBus()) {
|
||||
cerr << "Error: Can't initialize bus" << endl;
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// We need to wait to create the devices until after the bus/controller/etc objects have been created
|
||||
// TODO Try to resolve dependencies so that this work-around can be removed
|
||||
try {
|
||||
CreateInitialDevices(optargs);
|
||||
}
|
||||
catch(const parser_exception& e) {
|
||||
cerr << "Error: " << e.what() << endl;
|
||||
if (const string error = service.Init([this] (CommandContext& context) { return ExecuteCommand(context); }, port);
|
||||
!error.empty()) {
|
||||
cerr << "Error: " << error << endl;
|
||||
|
||||
Cleanup();
|
||||
CleanUp();
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (!service.Init(&ExecuteCommand, port)) {
|
||||
if (const string error = executor->SetReservedIds(reserved_ids); !error.empty()) {
|
||||
cerr << "Error: " << error << endl;
|
||||
|
||||
CleanUp();
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (command.devices_size()) {
|
||||
// Attach all specified devices
|
||||
command.set_operation(ATTACH);
|
||||
|
||||
if (const CommandContext context(command, piscsi_image.GetDefaultFolder(), locale); !executor->ProcessCmd(context)) {
|
||||
cerr << "Error: Can't attach devices" << endl;
|
||||
|
||||
CleanUp();
|
||||
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Display and log the device list
|
||||
PbServerInfo server_info;
|
||||
response.GetDevices(controller_manager.GetAllDevices(), server_info, piscsi_image.GetDefaultFolder());
|
||||
const vector<PbDevice>& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
|
||||
const string device_list = ListDevices(devices);
|
||||
LogDevices(device_list);
|
||||
cout << device_list << flush;
|
||||
|
||||
instance = this;
|
||||
// Signal handler to detach all devices on a KILL or TERM signal
|
||||
struct sigaction termination_handler;
|
||||
termination_handler.sa_handler = TerminationHandler;
|
||||
|
@ -532,22 +531,30 @@ int Piscsi::run(const vector<char *>& args)
|
|||
termination_handler.sa_flags = 0;
|
||||
sigaction(SIGINT, &termination_handler, nullptr);
|
||||
sigaction(SIGTERM, &termination_handler, nullptr);
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
// Set the affinity to a specific processor core
|
||||
FixCpu(3);
|
||||
|
||||
sched_param schparam;
|
||||
service.Start();
|
||||
|
||||
Process();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Piscsi::Process()
|
||||
{
|
||||
#ifdef USE_SEL_EVENT_ENABLE
|
||||
// Scheduling policy setting (highest priority)
|
||||
// TODO Check whether this results in any performance gain
|
||||
sched_param schparam;
|
||||
schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
sched_setscheduler(0, SCHED_FIFO, &schparam);
|
||||
#else
|
||||
cout << "Note: No PiSCSI hardware support, only client interface calls are supported" << endl;
|
||||
#endif
|
||||
|
||||
// Start execution
|
||||
service.SetRunning(true);
|
||||
|
||||
// Main Loop
|
||||
while (service.IsRunning()) {
|
||||
#ifdef USE_SEL_EVENT_ENABLE
|
||||
|
@ -571,81 +578,55 @@ int Piscsi::run(const vector<char *>& args)
|
|||
}
|
||||
#endif
|
||||
|
||||
// Wait until BSY is released as there is a possibility for the
|
||||
// initiator to assert it while setting the ID (for up to 3 seconds)
|
||||
WaitForNotBusy();
|
||||
// Only process the SCSI command if the bus is not busy and no other device responded
|
||||
if (IsNotBusy() && bus->GetSEL()) {
|
||||
target_is_active = true;
|
||||
|
||||
// Stop because the bus is busy or another device responded
|
||||
if (bus->GetBSY() || !bus->GetSEL()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int initiator_id = AbstractController::UNKNOWN_INITIATOR_ID;
|
||||
|
||||
// The initiator and target ID
|
||||
const uint8_t id_data = bus->GetDAT();
|
||||
|
||||
phase_t phase = phase_t::busfree;
|
||||
|
||||
// Identify the responsible controller
|
||||
auto controller = controller_manager->IdentifyController(id_data);
|
||||
if (controller != nullptr) {
|
||||
device_logger.SetIdAndLun(controller->GetTargetId(), -1);
|
||||
|
||||
initiator_id = controller->ExtractInitiatorId(id_data);
|
||||
|
||||
if (initiator_id != AbstractController::UNKNOWN_INITIATOR_ID) {
|
||||
device_logger.Trace("++++ Starting processing for initiator ID " + to_string(initiator_id));
|
||||
}
|
||||
else {
|
||||
device_logger.Trace("++++ Starting processing for unknown initiator ID");
|
||||
// Process command on the responsible controller based on the current initiator and target ID
|
||||
if (const auto shutdown_mode = controller_manager.ProcessOnController(bus->GetDAT());
|
||||
shutdown_mode != AbstractController::piscsi_shutdown_mode::NONE) {
|
||||
// When the bus is free PiSCSI or the Pi may be shut down.
|
||||
ShutDown(shutdown_mode);
|
||||
}
|
||||
|
||||
if (controller->Process(initiator_id) == phase_t::selection) {
|
||||
phase = phase_t::selection;
|
||||
}
|
||||
target_is_active = false;
|
||||
}
|
||||
|
||||
// Return to bus monitoring if the selection phase has not started
|
||||
if (phase != phase_t::selection) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Start target device
|
||||
active = true;
|
||||
|
||||
#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__)
|
||||
// Scheduling policy setting (highest priority)
|
||||
schparam.sched_priority = sched_get_priority_max(SCHED_FIFO);
|
||||
sched_setscheduler(0, SCHED_FIFO, &schparam);
|
||||
#endif
|
||||
|
||||
// Loop until the bus is free
|
||||
while (service.IsRunning()) {
|
||||
// Target drive
|
||||
phase = controller->Process(initiator_id);
|
||||
|
||||
// End when the bus is free
|
||||
if (phase == phase_t::busfree) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(USE_SEL_EVENT_ENABLE) && defined(__linux__)
|
||||
// Set the scheduling priority back to normal
|
||||
schparam.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_OTHER, &schparam);
|
||||
#endif
|
||||
|
||||
// End the target travel
|
||||
active = false;
|
||||
}
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Piscsi::WaitForNotBusy() const
|
||||
void Piscsi::ShutDown(AbstractController::piscsi_shutdown_mode shutdown_mode)
|
||||
{
|
||||
CleanUp();
|
||||
|
||||
switch(shutdown_mode) {
|
||||
case AbstractController::piscsi_shutdown_mode::STOP_PISCSI:
|
||||
spdlog::info("PiSCSI shutdown requested");
|
||||
break;
|
||||
|
||||
case AbstractController::piscsi_shutdown_mode::STOP_PI:
|
||||
spdlog::info("Raspberry Pi shutdown requested");
|
||||
if (system("init 0") == -1) {
|
||||
spdlog::error("Raspberry Pi shutdown failed");
|
||||
}
|
||||
break;
|
||||
|
||||
case AbstractController::piscsi_shutdown_mode::RESTART_PI:
|
||||
spdlog::info("Raspberry Pi restart requested");
|
||||
if (system("init 6") == -1) {
|
||||
spdlog::error("Raspberry Pi restart failed");
|
||||
}
|
||||
break;
|
||||
|
||||
case AbstractController::piscsi_shutdown_mode::NONE:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Piscsi::IsNotBusy() const
|
||||
{
|
||||
// Wait until BSY is released as there is a possibility for the
|
||||
// initiator to assert it while setting the ID (for up to 3 seconds)
|
||||
if (bus->GetBSY()) {
|
||||
const uint32_t now = SysTimer::GetTimerLow();
|
||||
|
||||
|
@ -654,8 +635,12 @@ void Piscsi::WaitForNotBusy() const
|
|||
bus->Acquire();
|
||||
|
||||
if (!bus->GetBSY()) {
|
||||
break;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,31 +3,31 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "devices/device_logger.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "controllers/abstract_controller.h"
|
||||
#include "piscsi/command_context.h"
|
||||
#include "piscsi/piscsi_service.h"
|
||||
#include "piscsi/piscsi_image.h"
|
||||
#include "piscsi/piscsi_response.h"
|
||||
#include "piscsi/piscsi_executor.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include <vector>
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <atomic>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class BUS;
|
||||
class ControllerManager;
|
||||
class PiscsiExecutor;
|
||||
|
||||
class Piscsi
|
||||
{
|
||||
using optargs_type = vector<pair<int, string>>;
|
||||
|
||||
static const int DEFAULT_PORT = 6868;
|
||||
|
||||
public:
|
||||
|
@ -35,46 +35,47 @@ public:
|
|||
Piscsi() = default;
|
||||
~Piscsi() = default;
|
||||
|
||||
int run(const vector<char *>&);
|
||||
int run(span<char *>);
|
||||
|
||||
private:
|
||||
|
||||
void Banner(const vector<char *>&) const;
|
||||
bool InitBus() const;
|
||||
static void Cleanup();
|
||||
void ReadAccessToken(const string&) const;
|
||||
void Banner(span<char *>) const;
|
||||
bool InitBus();
|
||||
void CleanUp();
|
||||
void ReadAccessToken(const path&);
|
||||
void LogDevices(string_view) const;
|
||||
PbDeviceType ParseDeviceType(const string&) const;
|
||||
static void TerminationHandler(int);
|
||||
optargs_type ParseArguments(const vector<char *>&, int&) const;
|
||||
void CreateInitialDevices(const optargs_type&) const;
|
||||
void WaitForNotBusy() const;
|
||||
string ParseArguments(span<char *>, PbCommand&, int&, string&);
|
||||
void Process();
|
||||
bool IsNotBusy() const;
|
||||
|
||||
// TODO Should not be static and should be moved to PiscsiService
|
||||
static bool ExecuteCommand(const CommandContext&, const PbCommand&);
|
||||
void ShutDown(AbstractController::piscsi_shutdown_mode);
|
||||
|
||||
DeviceLogger device_logger;
|
||||
bool ExecuteCommand(CommandContext&);
|
||||
|
||||
// A static instance is needed because of the signal handler
|
||||
static inline shared_ptr<BUS> bus;
|
||||
bool SetLogLevel(const string&) const;
|
||||
|
||||
// TODO These fields should not be static
|
||||
const shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("piscsi stdout logger");
|
||||
|
||||
static inline PiscsiService service;
|
||||
|
||||
static inline PiscsiImage piscsi_image;
|
||||
|
||||
const static inline PiscsiResponse piscsi_response;
|
||||
|
||||
static inline shared_ptr<ControllerManager> controller_manager;
|
||||
|
||||
static inline shared_ptr<PiscsiExecutor> executor;
|
||||
static PbDeviceType ParseDeviceType(const string&);
|
||||
|
||||
// Processing flag
|
||||
static inline volatile bool active;
|
||||
atomic_bool target_is_active;
|
||||
|
||||
// Some versions of spdlog do not support get_log_level(), so we have to remember the level
|
||||
static inline string current_log_level = "info";
|
||||
string access_token;
|
||||
|
||||
static inline string access_token;
|
||||
PiscsiImage piscsi_image;
|
||||
|
||||
PiscsiResponse response;
|
||||
|
||||
PiscsiService service;
|
||||
|
||||
unique_ptr<PiscsiExecutor> executor;
|
||||
|
||||
ControllerManager controller_manager;
|
||||
|
||||
unique_ptr<BUS> bus;
|
||||
|
||||
// Required for the termination handler
|
||||
static inline Piscsi *instance;
|
||||
};
|
||||
|
|
|
@ -3,35 +3,31 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "controllers/scsi_controller.h"
|
||||
#include "devices/device_logger.h"
|
||||
#include "devices/device_factory.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "devices/disk.h"
|
||||
#include "piscsi_service.h"
|
||||
#include "piscsi_image.h"
|
||||
#include "localizer.h"
|
||||
#include "command_context.h"
|
||||
#include "piscsi_executor.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sstream>
|
||||
|
||||
using namespace spdlog;
|
||||
using namespace protobuf_util;
|
||||
using namespace piscsi_util;
|
||||
|
||||
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device,
|
||||
const PbCommand& command, bool dryRun)
|
||||
bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, bool dryRun)
|
||||
{
|
||||
PrintCommand(command, pb_device, dryRun);
|
||||
spdlog::info((dryRun ? "Validating: " : "Executing: ") + PrintCommand(context.GetCommand(), pb_device));
|
||||
|
||||
const int id = pb_device.id();
|
||||
const int lun = pb_device.unit();
|
||||
|
@ -40,14 +36,14 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
|
|||
return false;
|
||||
}
|
||||
|
||||
const PbOperation operation = command.operation();
|
||||
const PbOperation operation = context.GetCommand().operation();
|
||||
|
||||
// For all commands except ATTACH the device and LUN must exist
|
||||
if (operation != ATTACH && !VerifyExistingIdAndLun(context, id, lun)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto device = controller_manager.GetDeviceByIdAndLun(id, lun);
|
||||
auto device = controller_manager.GetDeviceForIdAndLun(id, lun);
|
||||
|
||||
if (!ValidateOperationAgainstDevice(context, *device, operation)) {
|
||||
return false;
|
||||
|
@ -64,7 +60,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
|
|||
return Attach(context, pb_device, dryRun);
|
||||
|
||||
case DETACH:
|
||||
return Detach(context, device, dryRun);
|
||||
return Detach(context, *device, dryRun);
|
||||
|
||||
case INSERT:
|
||||
return Insert(context, pb_device, device, dryRun);
|
||||
|
@ -82,7 +78,7 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
|
|||
case CHECK_AUTHENTICATION:
|
||||
case NO_OPERATION:
|
||||
// Do nothing, just log
|
||||
LOGTRACE("Received %s command", PbOperation_Name(operation).c_str())
|
||||
spdlog::trace("Received " + PbOperation_Name(operation) + " command");
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -92,136 +88,83 @@ bool PiscsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDev
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& command)
|
||||
bool PiscsiExecutor::ProcessCmd(const CommandContext& context)
|
||||
{
|
||||
const PbCommand& command = context.GetCommand();
|
||||
|
||||
switch (command.operation()) {
|
||||
case DETACH_ALL:
|
||||
DetachAll();
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
|
||||
case RESERVE_IDS: {
|
||||
const string ids = GetParam(command, "ids");
|
||||
if (const string error = SetReservedIds(ids); !error.empty()) {
|
||||
return context.ReturnStatus(false, error);
|
||||
return context.ReturnErrorStatus(error);
|
||||
}
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
case CREATE_IMAGE:
|
||||
return piscsi_image.CreateImage(context, command);
|
||||
return piscsi_image.CreateImage(context);
|
||||
|
||||
case DELETE_IMAGE:
|
||||
return piscsi_image.DeleteImage(context, command);
|
||||
return piscsi_image.DeleteImage(context);
|
||||
|
||||
case RENAME_IMAGE:
|
||||
return piscsi_image.RenameImage(context, command);
|
||||
return piscsi_image.RenameImage(context);
|
||||
|
||||
case COPY_IMAGE:
|
||||
return piscsi_image.CopyImage(context, command);
|
||||
return piscsi_image.CopyImage(context);
|
||||
|
||||
case PROTECT_IMAGE:
|
||||
case UNPROTECT_IMAGE:
|
||||
return piscsi_image.SetImagePermissions(context, command);
|
||||
return piscsi_image.SetImagePermissions(context);
|
||||
|
||||
default:
|
||||
// This is a device-specific command handled below
|
||||
break;
|
||||
}
|
||||
|
||||
// Remember the list of reserved files, than run the dry run
|
||||
// Remember the list of reserved files during the dry run
|
||||
const auto& reserved_files = StorageDevice::GetReservedFiles();
|
||||
for (const auto& device : command.devices()) {
|
||||
if (!ProcessDeviceCmd(context, device, command, true)) {
|
||||
// Dry run failed, restore the file list
|
||||
StorageDevice::SetReservedFiles(reserved_files);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the list of reserved files before proceeding
|
||||
const bool reserved = ranges::find_if_not(context.GetCommand().devices(), [&] (const auto& device)
|
||||
{ return ProcessDeviceCmd(context, device, true); }) != command.devices().end();
|
||||
StorageDevice::SetReservedFiles(reserved_files);
|
||||
|
||||
if (const string result = ValidateLunSetup(command); !result.empty()) {
|
||||
return context.ReturnStatus(false, result);
|
||||
}
|
||||
|
||||
for (const auto& device : command.devices()) {
|
||||
if (!ProcessDeviceCmd(context, device, command, false)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ATTACH and DETACH return the device list
|
||||
if (context.IsValid() && (command.operation() == ATTACH || command.operation() == DETACH)) {
|
||||
// A new command with an empty device list is required here in order to return data for all devices
|
||||
PbCommand cmd;
|
||||
PbResult result;
|
||||
piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd,
|
||||
piscsi_image.GetDefaultFolder());
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
return true;
|
||||
}
|
||||
|
||||
return context.ReturnStatus();
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::SetLogLevel(const string& log_level) const
|
||||
{
|
||||
int id = -1;
|
||||
int lun = -1;
|
||||
string level = log_level;
|
||||
|
||||
if (size_t separator_pos = log_level.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
|
||||
level = log_level.substr(0, separator_pos);
|
||||
|
||||
const string l = log_level.substr(separator_pos + 1);
|
||||
separator_pos = l.find(COMPONENT_SEPARATOR);
|
||||
if (separator_pos != string::npos) {
|
||||
const string error = ProcessId(l, ScsiController::LUN_MAX, id, lun);
|
||||
if (!error.empty()) {
|
||||
LOGWARN("Invalid device ID/LUN specifier '%s'", l.c_str())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!GetAsUnsignedInt(l, id)) {
|
||||
LOGWARN("Invalid device ID specifier '%s'", l.c_str())
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto& it = log_level_mapping.find(level); it != log_level_mapping.end()) {
|
||||
set_level(it->second);
|
||||
}
|
||||
else {
|
||||
LOGWARN("Invalid log level '%s'", log_level.c_str())
|
||||
if (reserved) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceLogger::SetLogIdAndLun(id, lun);
|
||||
|
||||
if (id != -1) {
|
||||
if (lun == -1) {
|
||||
LOGINFO("Set log level for device ID %d to '%s'", id, level.c_str())
|
||||
}
|
||||
else {
|
||||
LOGINFO("Set log level for device ID %d, LUN %d to '%s'", id, lun, level.c_str())
|
||||
}
|
||||
}
|
||||
else {
|
||||
LOGINFO("Set log level to '%s'", level.c_str())
|
||||
if (const string error = EnsureLun0(command); !error.empty()) {
|
||||
return context.ReturnErrorStatus(error);
|
||||
}
|
||||
|
||||
return true;
|
||||
if (ranges::find_if_not(command.devices(), [&] (const auto& device)
|
||||
{ return ProcessDeviceCmd(context, device, false); } ) != command.devices().end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ATTACH and DETACH return the device list
|
||||
if (command.operation() == ATTACH || command.operation() == DETACH) {
|
||||
// A new command with an empty device list is required here in order to return data for all devices
|
||||
PbCommand cmd;
|
||||
PbResult result;
|
||||
piscsi_response.GetDevicesInfo(controller_manager.GetAllDevices(), result, cmd, context.GetDefaultFolder());
|
||||
context.WriteResult(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
|
||||
{
|
||||
if (!dryRun) {
|
||||
LOGINFO("Start requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
|
||||
spdlog::info("Start requested for " + device.GetIdentifier());
|
||||
|
||||
if (!device.Start()) {
|
||||
LOGWARN("Starting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
|
||||
spdlog::warn("Starting " + device.GetIdentifier() + " failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +174,7 @@ bool PiscsiExecutor::Start(PrimaryDevice& device, bool dryRun) const
|
|||
bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
|
||||
{
|
||||
if (!dryRun) {
|
||||
LOGINFO("Stop requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
|
||||
spdlog::info("Stop requested for " + device.GetIdentifier());
|
||||
|
||||
device.Stop();
|
||||
}
|
||||
|
@ -242,10 +185,10 @@ bool PiscsiExecutor::Stop(PrimaryDevice& device, bool dryRun) const
|
|||
bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
|
||||
{
|
||||
if (!dryRun) {
|
||||
LOGINFO("Eject requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(), device.GetLun())
|
||||
spdlog::info("Eject requested for " + device.GetIdentifier());
|
||||
|
||||
if (!device.Eject(true)) {
|
||||
LOGWARN("Ejecting %s ID %d, unit %d failed", device.GetTypeString(), device.GetId(), device.GetLun())
|
||||
spdlog::warn("Ejecting " + device.GetIdentifier() + " failed");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,8 +198,7 @@ bool PiscsiExecutor::Eject(PrimaryDevice& device, bool dryRun) const
|
|||
bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
|
||||
{
|
||||
if (!dryRun) {
|
||||
LOGINFO("Write protection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
|
||||
device.GetLun())
|
||||
spdlog::info("Write protection requested for " + device.GetIdentifier());
|
||||
|
||||
device.SetProtected(true);
|
||||
}
|
||||
|
@ -267,8 +209,7 @@ bool PiscsiExecutor::Protect(PrimaryDevice& device, bool dryRun) const
|
|||
bool PiscsiExecutor::Unprotect(PrimaryDevice& device, bool dryRun) const
|
||||
{
|
||||
if (!dryRun) {
|
||||
LOGINFO("Write unprotection requested for %s ID %d, unit %d", device.GetTypeString(), device.GetId(),
|
||||
device.GetLun())
|
||||
spdlog::info("Write unprotection requested for " + device.GetIdentifier());
|
||||
|
||||
device.SetProtected(false);
|
||||
}
|
||||
|
@ -282,15 +223,16 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
const int lun = pb_device.unit();
|
||||
const PbDeviceType type = pb_device.type();
|
||||
|
||||
if (lun >= ScsiController::LUN_MAX) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX));
|
||||
if (lun >= ControllerManager::GetScsiLunMax()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
|
||||
to_string(ControllerManager::GetScsiLunMax()));
|
||||
}
|
||||
|
||||
if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) {
|
||||
if (controller_manager.HasDeviceForIdAndLun(id, lun)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun));
|
||||
}
|
||||
|
||||
if (reserved_ids.find(id) != reserved_ids.end()) {
|
||||
if (reserved_ids.contains(id)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id));
|
||||
}
|
||||
|
||||
|
@ -302,8 +244,7 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
}
|
||||
|
||||
// If no filename was provided the medium is considered not inserted
|
||||
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
|
||||
device->SetRemoved(storage_device != nullptr ? filename.empty() : false);
|
||||
device->SetRemoved(device->SupportsFile() ? filename.empty() : false);
|
||||
|
||||
if (!SetProductData(context, pb_device, *device)) {
|
||||
return false;
|
||||
|
@ -313,14 +254,14 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
return false;
|
||||
}
|
||||
|
||||
string full_path;
|
||||
const auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
|
||||
if (device->SupportsFile()) {
|
||||
// Only with removable media drives, CD and MO the medium (=file) may be inserted later
|
||||
if (!device->IsRemovable() && filename.empty()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type));
|
||||
}
|
||||
|
||||
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
|
||||
if (!ValidateImageFile(context, *storage_device, filename)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -336,23 +277,22 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
return true;
|
||||
}
|
||||
|
||||
unordered_map<string, string> params = { pb_device.params().begin(), pb_device.params().end() };
|
||||
param_map params = { pb_device.params().begin(), pb_device.params().end() };
|
||||
if (!device->SupportsFile()) {
|
||||
// Clients like scsictl might have sent both "file" and "interfaces"
|
||||
params.erase("file");
|
||||
}
|
||||
|
||||
if (!device->Init(params)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(device->GetType()),
|
||||
to_string(id), to_string(lun));
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, device->GetIdentifier());
|
||||
}
|
||||
|
||||
if (!controller_manager.AttachToController(bus, id, device)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
|
||||
}
|
||||
|
||||
if (storage_device != nullptr) {
|
||||
storage_device->ReserveFile(full_path, id, lun);
|
||||
}
|
||||
|
||||
if (!controller_manager.AttachToScsiController(id, device)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
|
||||
storage_device->ReserveFile();
|
||||
}
|
||||
|
||||
string msg = "Attached ";
|
||||
|
@ -362,8 +302,8 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
else if (device->IsProtectable() && device->IsProtected()) {
|
||||
msg += "protected ";
|
||||
}
|
||||
msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun);
|
||||
LOGINFO("%s", msg.c_str())
|
||||
msg += device->GetIdentifier();
|
||||
spdlog::info(msg);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -371,12 +311,11 @@ bool PiscsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
|
|||
bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device,
|
||||
const shared_ptr<PrimaryDevice>& device, bool dryRun) const
|
||||
{
|
||||
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
|
||||
if (storage_device == nullptr) {
|
||||
if (!device->SupportsFile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!storage_device->IsRemoved()) {
|
||||
if (!device->IsRemoved()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED);
|
||||
}
|
||||
|
||||
|
@ -394,56 +333,52 @@ bool PiscsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
|
|||
return true;
|
||||
}
|
||||
|
||||
LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "",
|
||||
filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit())
|
||||
spdlog::info("Insert " + string(pb_device.protected_() ? "protected " : "") + "file '" + filename +
|
||||
"' requested into " + device->GetIdentifier());
|
||||
|
||||
// TODO It may be better to add PrimaryDevice::Insert for all device-specific insert operations
|
||||
auto storage_device = dynamic_pointer_cast<StorageDevice>(device);
|
||||
if (!SetSectorSize(context, storage_device, pb_device.block_size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string full_path;
|
||||
if (!ValidateImageFile(context, *storage_device, filename, full_path)) {
|
||||
if (!ValidateImageFile(context, *storage_device, filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
storage_device->SetProtected(pb_device.protected_());
|
||||
storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun());
|
||||
storage_device->ReserveFile();
|
||||
storage_device->SetMediumChanged(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<PrimaryDevice>& device, bool dryRun) const
|
||||
bool PiscsiExecutor::Detach(const CommandContext& context, PrimaryDevice& device, bool dryRun)
|
||||
{
|
||||
auto controller = controller_manager.FindController(device->GetId());
|
||||
auto controller = controller_manager.FindController(device.GetId());
|
||||
if (controller == nullptr) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
|
||||
}
|
||||
|
||||
// LUN 0 can only be detached if there is no other LUN anymore
|
||||
if (!device->GetLun() && controller->GetLunCount() > 1) {
|
||||
if (!device.GetLun() && controller->GetLunCount() > 1) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0);
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
// Remember the ID before it gets invalid when removing the device
|
||||
const int id = device->GetId();
|
||||
// Remember the device identifier for the log message before the device data become invalid on removal
|
||||
const string identifier = device.GetIdentifier();
|
||||
|
||||
if (!controller->RemoveDevice(device)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
|
||||
}
|
||||
|
||||
// If no LUN is left also delete the controller
|
||||
if (!controller->GetLunCount() && !controller_manager.DeleteController(controller)) {
|
||||
if (!controller->GetLunCount() && !controller_manager.DeleteController(*controller)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
|
||||
}
|
||||
|
||||
if (auto storage_device = dynamic_pointer_cast<StorageDevice>(device); storage_device != nullptr) {
|
||||
storage_device->UnreserveFile();
|
||||
}
|
||||
|
||||
LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + to_string(id)
|
||||
+ ", unit " + to_string(device->GetLun())).c_str())
|
||||
spdlog::info("Detached " + identifier);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -452,9 +387,8 @@ bool PiscsiExecutor::Detach(const CommandContext& context, const shared_ptr<Prim
|
|||
void PiscsiExecutor::DetachAll()
|
||||
{
|
||||
controller_manager.DeleteAllControllers();
|
||||
StorageDevice::UnreserveAll();
|
||||
|
||||
LOGINFO("Detached all devices")
|
||||
spdlog::info("Detached all devices");
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode) {
|
||||
|
@ -467,9 +401,9 @@ bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode)
|
|||
|
||||
// The PiSCSI shutdown mode is "rascsi" instead of "piscsi" for backwards compatibility
|
||||
if (mode == "rascsi") {
|
||||
LOGINFO("PiSCSI shutdown requested")
|
||||
spdlog::info("PiSCSI shutdown requested");
|
||||
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
context.WriteResult(result);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -484,145 +418,116 @@ bool PiscsiExecutor::ShutDown(const CommandContext& context, const string& mode)
|
|||
}
|
||||
|
||||
if (mode == "system") {
|
||||
LOGINFO("System shutdown requested")
|
||||
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
spdlog::info("System shutdown requested");
|
||||
|
||||
DetachAll();
|
||||
|
||||
context.WriteResult(result);
|
||||
|
||||
if (system("init 0") == -1) {
|
||||
LOGERROR("System shutdown failed: %s", strerror(errno))
|
||||
spdlog::error("System shutdown failed");
|
||||
}
|
||||
}
|
||||
else if (mode == "reboot") {
|
||||
LOGINFO("System reboot requested")
|
||||
|
||||
serializer.SerializeMessage(context.GetFd(), result);
|
||||
spdlog::info("System reboot requested");
|
||||
|
||||
DetachAll();
|
||||
|
||||
context.WriteResult(result);
|
||||
|
||||
if (system("init 6") == -1) {
|
||||
LOGERROR("System reboot failed: %s", strerror(errno))
|
||||
spdlog::error("System reboot failed");
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
string PiscsiExecutor::SetReservedIds(string_view ids)
|
||||
{
|
||||
list<string> ids_to_reserve;
|
||||
set<int> ids_to_reserve;
|
||||
stringstream ss(ids.data());
|
||||
string id;
|
||||
while (getline(ss, id, ',')) {
|
||||
if (!id.empty()) {
|
||||
ids_to_reserve.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
set<int> reserved;
|
||||
for (const string& id_to_reserve : ids_to_reserve) {
|
||||
int res_id;
|
||||
if (!GetAsUnsignedInt(id_to_reserve, res_id) || res_id > 7) {
|
||||
return "Invalid ID " + id_to_reserve;
|
||||
}
|
||||
|
||||
if (controller_manager.FindController(res_id) != nullptr) {
|
||||
return "ID " + id_to_reserve + " is currently in use";
|
||||
}
|
||||
|
||||
reserved.insert(res_id);
|
||||
}
|
||||
|
||||
reserved_ids = { reserved.begin(), reserved.end() };
|
||||
|
||||
if (!reserved_ids.empty()) {
|
||||
string s;
|
||||
bool isFirst = true;
|
||||
for (const auto& reserved_id : reserved) {
|
||||
if (!isFirst) {
|
||||
s += ", ";
|
||||
}
|
||||
isFirst = false;
|
||||
s += to_string(reserved_id);
|
||||
if (!GetAsUnsignedInt(id, res_id) || res_id > 7) {
|
||||
return "Invalid ID " + id;
|
||||
}
|
||||
|
||||
LOGINFO("Reserved ID(s) set to %s", s.c_str())
|
||||
if (controller_manager.HasController(res_id)) {
|
||||
return "ID " + id + " is currently in use";
|
||||
}
|
||||
|
||||
ids_to_reserve.insert(res_id);
|
||||
}
|
||||
|
||||
reserved_ids = { ids_to_reserve.begin(), ids_to_reserve.end() };
|
||||
|
||||
if (!ids_to_reserve.empty()) {
|
||||
spdlog::info("Reserved ID(s) set to " + Join(ids_to_reserve));
|
||||
}
|
||||
else {
|
||||
LOGINFO("Cleared reserved ID(s)")
|
||||
spdlog::info("Cleared reserved ID(s)");
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::ValidateImageFile(const CommandContext& context, StorageDevice& storage_device,
|
||||
const string& filename, string& full_path) const
|
||||
const string& filename) const
|
||||
{
|
||||
if (filename.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (const auto [id1, lun1] = StorageDevice::GetIdsForReservedFile(filename); id1 != -1 || lun1 != -1) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
|
||||
to_string(id1), to_string(lun1));
|
||||
if (!CheckForReservedFile(context, filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string effective_filename = filename;
|
||||
storage_device.SetFilename(filename);
|
||||
|
||||
if (!StorageDevice::FileExists(filename)) {
|
||||
// If the file does not exist search for it in the default image folder
|
||||
effective_filename = piscsi_image.GetDefaultFolder() + "/" + filename;
|
||||
const string effective_filename = context.GetDefaultFolder() + "/" + filename;
|
||||
|
||||
if (const auto [id2, lun2] = StorageDevice::GetIdsForReservedFile(effective_filename); id2 != -1 || lun2 != -1) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
|
||||
to_string(id2), to_string(lun2));
|
||||
if (!CheckForReservedFile(context, effective_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!StorageDevice::FileExists(effective_filename)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
|
||||
}
|
||||
}
|
||||
|
||||
storage_device.SetFilename(effective_filename);
|
||||
|
||||
if (storage_device.IsReadOnlyFile()) {
|
||||
// Permanently write-protected
|
||||
storage_device.SetReadOnly(true);
|
||||
storage_device.SetProtectable(false);
|
||||
}
|
||||
else {
|
||||
storage_device.SetReadOnly(false);
|
||||
storage_device.SetProtectable(true);
|
||||
storage_device.SetFilename(effective_filename);
|
||||
}
|
||||
|
||||
try {
|
||||
storage_device.Open();
|
||||
}
|
||||
catch(const io_exception&) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename);
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, storage_device.GetFilename());
|
||||
}
|
||||
|
||||
full_path = effective_filename;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device, bool dryRun) const
|
||||
bool PiscsiExecutor::CheckForReservedFile(const CommandContext& context, const string& filename)
|
||||
{
|
||||
if (const auto [id, lun] = StorageDevice::GetIdsForReservedFile(filename); id != -1) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
|
||||
to_string(id) + ":" + to_string(lun));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefinition& pb_device) const
|
||||
{
|
||||
const map<string, string, less<>> params = { command.params().begin(), command.params().end() };
|
||||
|
||||
ostringstream s;
|
||||
s << (dryRun ? "Validating" : "Executing");
|
||||
s << ": operation=" << PbOperation_Name(command.operation());
|
||||
s << "operation=" << PbOperation_Name(command.operation());
|
||||
|
||||
if (!params.empty()) {
|
||||
s << ", command params=";
|
||||
bool isFirst = true;
|
||||
for (const auto& [key, value]: params) {
|
||||
for (const auto& [key, value] : params) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
|
@ -632,8 +537,7 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
|
|||
}
|
||||
}
|
||||
|
||||
s << ", device id=" << pb_device.id() << ", lun=" << pb_device.unit() << ", type="
|
||||
<< PbDeviceType_Name(pb_device.type());
|
||||
s << ", device=" << pb_device.id() << ":" << pb_device.unit() << ", type=" << PbDeviceType_Name(pb_device.type());
|
||||
|
||||
if (pb_device.params_size()) {
|
||||
s << ", device params=";
|
||||
|
@ -649,13 +553,14 @@ void PiscsiExecutor::PrintCommand(const PbCommand& command, const PbDeviceDefini
|
|||
|
||||
s << ", vendor='" << pb_device.vendor() << "', product='" << pb_device.product()
|
||||
<< "', revision='" << pb_device.revision() << "', block size=" << pb_device.block_size();
|
||||
LOGINFO("%s", s.str().c_str())
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
|
||||
string PiscsiExecutor::EnsureLun0(const PbCommand& command) const
|
||||
{
|
||||
// Mapping of available LUNs (bit vector) to devices
|
||||
unordered_map<uint32_t, uint32_t> luns;
|
||||
unordered_map<int32_t, int32_t> luns;
|
||||
|
||||
// Collect LUN bit vectors of new devices
|
||||
for (const auto& device : command.devices()) {
|
||||
|
@ -667,23 +572,17 @@ string PiscsiExecutor::ValidateLunSetup(const PbCommand& command) const
|
|||
luns[device->GetId()] |= 1 << device->GetLun();
|
||||
}
|
||||
|
||||
// LUN 0 must exist for all devices
|
||||
for (const auto& [id, lun]: luns) {
|
||||
if (!(lun & 0x01)) {
|
||||
return "LUN 0 is missing for device ID " + to_string(id);
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
const auto& it = ranges::find_if_not(luns, [] (const auto& l) { return l.second & 0x01; } );
|
||||
return it == luns.end() ? "" : "LUN 0 is missing for device ID " + to_string((*it).first);
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const
|
||||
{
|
||||
if (controller_manager.FindController(id) == nullptr) {
|
||||
if (!controller_manager.HasController(id)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id));
|
||||
}
|
||||
|
||||
if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) {
|
||||
if (!controller_manager.HasDeviceForIdAndLun(id, lun)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun));
|
||||
}
|
||||
|
||||
|
@ -706,13 +605,13 @@ shared_ptr<PrimaryDevice> PiscsiExecutor::CreateDevice(const CommandContext& con
|
|||
return device;
|
||||
}
|
||||
|
||||
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int block_size) const
|
||||
bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int size) const
|
||||
{
|
||||
if (block_size) {
|
||||
auto disk = dynamic_pointer_cast<Disk>(device);
|
||||
if (size) {
|
||||
const auto disk = dynamic_pointer_cast<Disk>(device);
|
||||
if (disk != nullptr && disk->IsSectorSizeConfigurable()) {
|
||||
if (!disk->SetConfiguredSectorSize(device_factory, block_size)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size));
|
||||
if (!disk->SetConfiguredSectorSize(device_factory, size)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(size));
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -725,22 +624,26 @@ bool PiscsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<Pri
|
|||
}
|
||||
|
||||
bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const PrimaryDevice& device,
|
||||
const PbOperation& operation)
|
||||
PbOperation operation)
|
||||
{
|
||||
if ((operation == START || operation == STOP) && !device.IsStoppable()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, device.GetTypeString());
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, PbOperation_Name(operation),
|
||||
device.GetTypeString());
|
||||
}
|
||||
|
||||
if ((operation == INSERT || operation == EJECT) && !device.IsRemovable()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, device.GetTypeString());
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, PbOperation_Name(operation),
|
||||
device.GetTypeString());
|
||||
}
|
||||
|
||||
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsProtectable()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, device.GetTypeString());
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, PbOperation_Name(operation),
|
||||
device.GetTypeString());
|
||||
}
|
||||
|
||||
if ((operation == PROTECT || operation == UNPROTECT) && !device.IsReady()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, device.GetTypeString());
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, PbOperation_Name(operation),
|
||||
device.GetTypeString());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -748,15 +651,16 @@ bool PiscsiExecutor::ValidateOperationAgainstDevice(const CommandContext& contex
|
|||
|
||||
bool PiscsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int lun)
|
||||
{
|
||||
// Validate the device ID and LUN
|
||||
if (id < 0) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID);
|
||||
}
|
||||
if (id >= ControllerManager::DEVICE_MAX) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1));
|
||||
if (id >= ControllerManager::GetScsiIdMax()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id),
|
||||
to_string(ControllerManager::GetScsiIdMax() - 1));
|
||||
}
|
||||
if (lun < 0 || lun >= ScsiController::LUN_MAX) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1));
|
||||
if (lun < 0 || lun >= ControllerManager::GetScsiLunMax()) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun),
|
||||
to_string(ControllerManager::GetScsiLunMax() - 1));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -777,7 +681,7 @@ bool PiscsiExecutor::SetProductData(const CommandContext& context, const PbDevic
|
|||
}
|
||||
}
|
||||
catch(const invalid_argument& e) {
|
||||
return context.ReturnStatus(false, e.what());
|
||||
return context.ReturnErrorStatus(e.what());
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
|
@ -3,22 +3,21 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "shared/protobuf_serializer.h"
|
||||
#include "hal/bus.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "piscsi/piscsi_response.h"
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
class PiscsiImage;
|
||||
class DeviceFactory;
|
||||
class ControllerManager;
|
||||
class PrimaryDevice;
|
||||
class StorageDevice;
|
||||
class CommandContext;
|
||||
|
||||
using namespace spdlog;
|
||||
|
@ -27,15 +26,14 @@ class PiscsiExecutor
|
|||
{
|
||||
public:
|
||||
|
||||
PiscsiExecutor(PiscsiImage& piscsi_image, ControllerManager& controller_manager)
|
||||
: piscsi_image(piscsi_image), controller_manager(controller_manager) {}
|
||||
PiscsiExecutor(PiscsiImage& piscsi_image, BUS& bus, ControllerManager& controller_manager)
|
||||
: piscsi_image(piscsi_image), bus(bus), controller_manager(controller_manager) {}
|
||||
~PiscsiExecutor() = default;
|
||||
|
||||
unordered_set<int> GetReservedIds() const { return reserved_ids; }
|
||||
auto GetReservedIds() const { return reserved_ids; }
|
||||
|
||||
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool);
|
||||
bool ProcessCmd(const CommandContext&, const PbCommand&);
|
||||
bool SetLogLevel(const string&) const;
|
||||
bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, bool);
|
||||
bool ProcessCmd(const CommandContext&);
|
||||
bool Start(PrimaryDevice&, bool) const;
|
||||
bool Stop(PrimaryDevice&, bool) const;
|
||||
bool Eject(PrimaryDevice&, bool) const;
|
||||
|
@ -43,41 +41,34 @@ public:
|
|||
bool Unprotect(PrimaryDevice&, bool) const;
|
||||
bool Attach(const CommandContext&, const PbDeviceDefinition&, bool);
|
||||
bool Insert(const CommandContext&, const PbDeviceDefinition&, const shared_ptr<PrimaryDevice>&, bool) const;
|
||||
bool Detach(const CommandContext&, const shared_ptr<PrimaryDevice>&, bool) const;
|
||||
bool Detach(const CommandContext&, PrimaryDevice&, bool);
|
||||
void DetachAll();
|
||||
bool ShutDown(const CommandContext&, const string&);
|
||||
string SetReservedIds(string_view);
|
||||
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&, string&) const;
|
||||
void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const;
|
||||
string ValidateLunSetup(const PbCommand&) const;
|
||||
bool ValidateImageFile(const CommandContext&, StorageDevice&, const string&) const;
|
||||
string PrintCommand(const PbCommand&, const PbDeviceDefinition&) const;
|
||||
string EnsureLun0(const PbCommand&) const;
|
||||
bool VerifyExistingIdAndLun(const CommandContext&, int, int) const;
|
||||
shared_ptr<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const;
|
||||
bool SetSectorSize(const CommandContext&, shared_ptr<PrimaryDevice>, int) const;
|
||||
|
||||
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, const PbOperation&);
|
||||
static bool ValidateOperationAgainstDevice(const CommandContext&, const PrimaryDevice&, PbOperation);
|
||||
static bool ValidateIdAndLun(const CommandContext&, int, int);
|
||||
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, PrimaryDevice&);
|
||||
|
||||
private:
|
||||
|
||||
static bool CheckForReservedFile(const CommandContext&, const string&);
|
||||
|
||||
const PiscsiResponse piscsi_response;
|
||||
|
||||
PiscsiImage& piscsi_image;
|
||||
|
||||
BUS& bus;
|
||||
|
||||
ControllerManager& controller_manager;
|
||||
|
||||
const DeviceFactory device_factory;
|
||||
|
||||
const ProtobufSerializer serializer;
|
||||
|
||||
unordered_set<int> reserved_ids;
|
||||
|
||||
static inline const unordered_map<string, level::level_enum> log_level_mapping = {
|
||||
{ "trace", level::trace },
|
||||
{ "debug", level::debug },
|
||||
{ "info", level::info },
|
||||
{ "warn", level::warn },
|
||||
{ "err", level::err },
|
||||
{ "off", level::off }
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,21 +3,18 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "devices/disk.h"
|
||||
#include "command_context.h"
|
||||
#include "piscsi_image.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
|
@ -32,14 +29,12 @@ PiscsiImage::PiscsiImage()
|
|||
|
||||
bool PiscsiImage::CheckDepth(string_view filename) const
|
||||
{
|
||||
return count(filename.begin(), filename.end(), '/') <= depth;
|
||||
return ranges::count(filename, '/') <= depth;
|
||||
}
|
||||
|
||||
bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string& filename) const
|
||||
bool PiscsiImage::CreateImageFolder(const CommandContext& context, string_view filename) const
|
||||
{
|
||||
if (const size_t filename_start = filename.rfind('/'); filename_start != string::npos) {
|
||||
const auto folder = path(filename.substr(0, filename_start));
|
||||
|
||||
if (const auto folder = path(filename).parent_path(); !folder.string().empty()) {
|
||||
// Checking for existence first prevents an error if the top-level folder is a softlink
|
||||
if (error_code error; exists(folder, error)) {
|
||||
return true;
|
||||
|
@ -51,67 +46,64 @@ bool PiscsiImage::CreateImageFolder(const CommandContext& context, const string&
|
|||
return ChangeOwner(context, folder, false);
|
||||
}
|
||||
catch(const filesystem_error& e) {
|
||||
return context.ReturnStatus(false, "Can't create image folder '" + string(folder) + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't create image folder '" + folder.string() + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
string PiscsiImage::SetDefaultFolder(const string& f)
|
||||
string PiscsiImage::SetDefaultFolder(string_view f)
|
||||
{
|
||||
if (f.empty()) {
|
||||
return "Can't set default image folder: Missing folder name";
|
||||
}
|
||||
|
||||
string folder = f;
|
||||
|
||||
// If a relative path is specified, the path is assumed to be relative to the user's home directory
|
||||
if (folder[0] != '/') {
|
||||
folder = GetHomeDir() + "/" + folder;
|
||||
path folder(f);
|
||||
if (folder.is_relative()) {
|
||||
folder = path(GetHomeDir() + "/" + folder.string());
|
||||
}
|
||||
else {
|
||||
if (folder.find("/home/") != 0) {
|
||||
return "Default image folder must be located in '/home/'";
|
||||
}
|
||||
|
||||
if (path home_root = path(GetHomeDir()).parent_path(); !folder.string().starts_with(home_root.string())) {
|
||||
return "Default image folder must be located in '" + home_root.string() + "'";
|
||||
}
|
||||
|
||||
// Resolve a potential symlink
|
||||
auto p = path(folder);
|
||||
if (error_code error; is_symlink(p, error)) {
|
||||
p = read_symlink(p);
|
||||
if (error_code error; is_symlink(folder, error)) {
|
||||
folder = read_symlink(folder);
|
||||
}
|
||||
|
||||
if (error_code error; !is_directory(p, error)) {
|
||||
return "'" + string(p) + "' is not a valid folder";
|
||||
if (error_code error; !is_directory(folder)) {
|
||||
return string("'") + folder.string() + "' is not a valid image folder";
|
||||
}
|
||||
|
||||
default_folder = string(p);
|
||||
default_folder = folder.string();
|
||||
|
||||
LOGINFO("Default image folder set to '%s'", default_folder.c_str())
|
||||
spdlog::info("Default image folder set to '" + default_folder + "'");
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const
|
||||
bool PiscsiImage::CreateImage(const CommandContext& context) const
|
||||
{
|
||||
const string filename = GetParam(command, "file");
|
||||
const string filename = GetParam(context.GetCommand(), "file");
|
||||
if (filename.empty()) {
|
||||
return context.ReturnStatus(false, "Can't create image file: Missing image filename");
|
||||
return context.ReturnErrorStatus("Missing image filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(filename)) {
|
||||
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
|
||||
return context.ReturnErrorStatus(("Invalid folder hierarchy depth '" + filename + "'").c_str());
|
||||
}
|
||||
|
||||
const string full_filename = GetFullName(filename);
|
||||
if (!IsValidDstFilename(full_filename)) {
|
||||
return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists");
|
||||
return context.ReturnErrorStatus("Can't create image file: '" + full_filename + "': File already exists");
|
||||
}
|
||||
|
||||
const string size = GetParam(command, "size");
|
||||
const string size = GetParam(context.GetCommand(), "size");
|
||||
if (size.empty()) {
|
||||
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing file size");
|
||||
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Missing file size");
|
||||
}
|
||||
|
||||
off_t len;
|
||||
|
@ -119,20 +111,20 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
|
|||
len = stoull(size);
|
||||
}
|
||||
catch(const invalid_argument&) {
|
||||
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
|
||||
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
|
||||
}
|
||||
catch(const out_of_range&) {
|
||||
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
|
||||
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': Invalid file size " + size);
|
||||
}
|
||||
if (len < 512 || (len & 0x1ff)) {
|
||||
return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)");
|
||||
return context.ReturnErrorStatus("Invalid image file size " + to_string(len) + " (not a multiple of 512)");
|
||||
}
|
||||
|
||||
if (!CreateImageFolder(context, full_filename)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool read_only = GetParam(command, "read_only") == "true";
|
||||
const bool read_only = GetParam(context.GetCommand(), "read_only") == "true";
|
||||
|
||||
error_code error;
|
||||
path file(full_filename);
|
||||
|
@ -149,40 +141,37 @@ bool PiscsiImage::CreateImage(const CommandContext& context, const PbCommand& co
|
|||
catch(const filesystem_error& e) {
|
||||
remove(file, error);
|
||||
|
||||
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't create image file '" + full_filename + "': " + e.what());
|
||||
}
|
||||
|
||||
LOGINFO("%s", string("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
|
||||
"' with a size of " + to_string(len) + " bytes").c_str())
|
||||
spdlog::info("Created " + string(read_only ? "read-only " : "") + "image file '" + full_filename +
|
||||
"' with a size of " + to_string(len) + " bytes");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& command) const
|
||||
bool PiscsiImage::DeleteImage(const CommandContext& context) const
|
||||
{
|
||||
const string filename = GetParam(command, "file");
|
||||
const string filename = GetParam(context.GetCommand(), "file");
|
||||
if (filename.empty()) {
|
||||
return context.ReturnStatus(false, "Missing image filename");
|
||||
return context.ReturnErrorStatus("Missing image filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(filename)) {
|
||||
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
|
||||
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
|
||||
}
|
||||
|
||||
const auto full_filename = path(GetFullName(filename));
|
||||
|
||||
if (!exists(full_filename)) {
|
||||
return context.ReturnStatus(false, "Image file '" + string(full_filename) + "' does not exist");
|
||||
return context.ReturnErrorStatus("Image file '" + full_filename.string() + "' does not exist");
|
||||
}
|
||||
|
||||
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(full_filename);
|
||||
if (id != -1 || lun != -1) {
|
||||
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) +
|
||||
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
|
||||
if (!IsReservedFile(context, full_filename, "delete")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (error_code error; !remove(full_filename, error)) {
|
||||
return context.ReturnStatus(false, "Can't delete image file '" + string(full_filename) + "'");
|
||||
return context.ReturnErrorStatus("Can't delete image file '" + full_filename.string() + "'");
|
||||
}
|
||||
|
||||
// Delete empty subfolders
|
||||
|
@ -196,32 +185,22 @@ bool PiscsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
|
|||
}
|
||||
|
||||
if (error_code error; !remove(full_folder)) {
|
||||
return context.ReturnStatus(false, "Can't delete empty image folder '" + string(full_folder) + "'");
|
||||
return context.ReturnErrorStatus("Can't delete empty image folder '" + full_folder.string() + "'");
|
||||
}
|
||||
|
||||
last_slash = folder.rfind('/');
|
||||
}
|
||||
|
||||
LOGINFO("Deleted image file '%s'", full_filename.c_str())
|
||||
spdlog::info("Deleted image file '" + full_filename.string() + "'");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const
|
||||
bool PiscsiImage::RenameImage(const CommandContext& context) const
|
||||
{
|
||||
string from;
|
||||
string to;
|
||||
if (!ValidateParams(context, command, "rename/move", from, to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
|
||||
if (id != -1 || lun != -1) {
|
||||
return context.ReturnStatus(false, "Can't rename/move image file '" + from +
|
||||
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
|
||||
}
|
||||
|
||||
if (!CreateImageFolder(context, to)) {
|
||||
if (!ValidateParams(context, "rename/move", from, to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -229,33 +208,19 @@ bool PiscsiImage::RenameImage(const CommandContext& context, const PbCommand& co
|
|||
rename(path(from), path(to));
|
||||
}
|
||||
catch(const filesystem_error& e) {
|
||||
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't rename/move image file '" + from + "': " + e.what());
|
||||
}
|
||||
|
||||
LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str())
|
||||
spdlog::info("Renamed/Moved image file '" + from + "' to '" + to + "'");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const
|
||||
bool PiscsiImage::CopyImage(const CommandContext& context) const
|
||||
{
|
||||
string from;
|
||||
string to;
|
||||
if (!ValidateParams(context, command, "copy", from, to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (access(from.c_str(), R_OK)) {
|
||||
return context.ReturnStatus(false, "Can't read source image file '" + from + "'");
|
||||
}
|
||||
|
||||
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(from);
|
||||
if (id != -1 || lun != -1) {
|
||||
return context.ReturnStatus(false, "Can't copy image file '" + from +
|
||||
"', it is currently being used by device ID " + to_string(id) + ", LUN " + to_string(lun));
|
||||
}
|
||||
|
||||
if (!CreateImageFolder(context, to)) {
|
||||
if (!ValidateParams(context, "copy", from, to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -268,119 +233,134 @@ bool PiscsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
|
|||
copy_symlink(f, t);
|
||||
}
|
||||
catch(const filesystem_error& e) {
|
||||
return context.ReturnStatus(false, "Can't copy image file symlink '" + from + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't copy image file symlink '" + from + "': " + e.what());
|
||||
}
|
||||
|
||||
LOGINFO("Copied image file symlink '%s' to '%s'", from.c_str(), to.c_str())
|
||||
spdlog::info("Copied image file symlink '" + from + "' to '" + to + "'");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
try {
|
||||
copy_file(f, t);
|
||||
|
||||
permissions(t, GetParam(command, "read_only") == "true" ?
|
||||
permissions(t, GetParam(context.GetCommand(), "read_only") == "true" ?
|
||||
perms::owner_read | perms::group_read | perms::others_read :
|
||||
perms::owner_read | perms::group_read | perms::others_read |
|
||||
perms::owner_write | perms::group_write);
|
||||
}
|
||||
catch(const filesystem_error& e) {
|
||||
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't copy image file '" + from + "': " + e.what());
|
||||
}
|
||||
|
||||
LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str())
|
||||
spdlog::info("Copied image file '" + from + "' to '" + to + "'");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiImage::SetImagePermissions(const CommandContext& context, const PbCommand& command) const
|
||||
bool PiscsiImage::SetImagePermissions(const CommandContext& context) const
|
||||
{
|
||||
string filename = GetParam(command, "file");
|
||||
const string filename = GetParam(context.GetCommand(), "file");
|
||||
if (filename.empty()) {
|
||||
return context.ReturnStatus(false, "Missing image filename");
|
||||
return context.ReturnErrorStatus("Missing image filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(filename)) {
|
||||
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
|
||||
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + filename + "'");
|
||||
}
|
||||
|
||||
filename = GetFullName(filename);
|
||||
if (!IsValidSrcFilename(filename)) {
|
||||
return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type");
|
||||
const string full_filename = GetFullName(filename);
|
||||
if (!IsValidSrcFilename(full_filename)) {
|
||||
return context.ReturnErrorStatus("Can't modify image file '" + full_filename + "': Invalid name or type");
|
||||
}
|
||||
|
||||
const bool protect = command.operation() == PROTECT_IMAGE;
|
||||
const bool protect = context.GetCommand().operation() == PROTECT_IMAGE;
|
||||
|
||||
try {
|
||||
permissions(path(filename), protect ?
|
||||
permissions(path(full_filename), protect ?
|
||||
perms::owner_read | perms::group_read | perms::others_read :
|
||||
perms::owner_read | perms::group_read | perms::others_read |
|
||||
perms::owner_write | perms::group_write);
|
||||
}
|
||||
catch(const filesystem_error& e) {
|
||||
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
|
||||
filename + "': " + e.what());
|
||||
return context.ReturnErrorStatus("Can't " + string(protect ? "protect" : "unprotect") + " image file '" +
|
||||
full_filename + "': " + e.what());
|
||||
}
|
||||
|
||||
if (protect) {
|
||||
LOGINFO("Protected image file '%s'", filename.c_str())
|
||||
}
|
||||
else {
|
||||
LOGINFO("Unprotected image file '%s'", filename.c_str())
|
||||
}
|
||||
spdlog::info((protect ? "Protected" : "Unprotected") + string(" image file '") + full_filename + "'");
|
||||
|
||||
return context.ReturnStatus();
|
||||
return context.ReturnSuccessStatus();
|
||||
}
|
||||
|
||||
bool PiscsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation,
|
||||
string& from, string& to) const
|
||||
bool PiscsiImage::IsReservedFile(const CommandContext& context, const string& file, const string& op)
|
||||
{
|
||||
from = GetParam(command, "from");
|
||||
if (from.empty()) {
|
||||
return context.ReturnStatus(false, "Can't " + operation + " image file: Missing source filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(from)) {
|
||||
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
|
||||
}
|
||||
|
||||
from = GetFullName(from);
|
||||
if (!IsValidSrcFilename(from)) {
|
||||
return context.ReturnStatus(false, "Can't " + operation + " image file: '" + from + "': Invalid name or type");
|
||||
}
|
||||
|
||||
to = GetParam(command, "to");
|
||||
if (to.empty()) {
|
||||
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "': Missing destination filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(to)) {
|
||||
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
|
||||
}
|
||||
|
||||
to = GetFullName(to);
|
||||
if (!IsValidDstFilename(to)) {
|
||||
return context.ReturnStatus(false, "Can't " + operation + " image file '" + from + "' to '" + to + "': File already exists");
|
||||
const auto [id, lun] = StorageDevice::GetIdsForReservedFile(file);
|
||||
if (id != -1) {
|
||||
return context.ReturnErrorStatus("Can't " + op + " image file '" + file +
|
||||
"', it is currently being used by device " + to_string(id) + ":" + to_string(lun));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiscsiImage::IsValidSrcFilename(const string& filename)
|
||||
bool PiscsiImage::ValidateParams(const CommandContext& context, const string& op, string& from, string& to) const
|
||||
{
|
||||
from = GetParam(context.GetCommand(), "from");
|
||||
if (from.empty()) {
|
||||
return context.ReturnErrorStatus("Can't " + op + " image file: Missing source filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(from)) {
|
||||
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + from + "'");
|
||||
}
|
||||
|
||||
to = GetParam(context.GetCommand(), "to");
|
||||
if (to.empty()) {
|
||||
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "': Missing destination filename");
|
||||
}
|
||||
|
||||
if (!CheckDepth(to)) {
|
||||
return context.ReturnErrorStatus("Invalid folder hierarchy depth '" + to + "'");
|
||||
}
|
||||
|
||||
from = GetFullName(from);
|
||||
if (!IsValidSrcFilename(from)) {
|
||||
return context.ReturnErrorStatus("Can't " + op + " image file: '" + from + "': Invalid name or type");
|
||||
}
|
||||
|
||||
to = GetFullName(to);
|
||||
if (!IsValidDstFilename(to)) {
|
||||
return context.ReturnErrorStatus("Can't " + op + " image file '" + from + "' to '" + to + "': File already exists");
|
||||
}
|
||||
|
||||
if (!IsReservedFile(context, from, op)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateImageFolder(context, to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiscsiImage::IsValidSrcFilename(string_view filename)
|
||||
{
|
||||
// Source file must exist and must be a regular file or a symlink
|
||||
path file(filename);
|
||||
return is_regular_file(file) || is_symlink(file);
|
||||
|
||||
error_code error;
|
||||
return is_regular_file(file, error) || is_symlink(file, error);
|
||||
}
|
||||
|
||||
bool PiscsiImage::IsValidDstFilename(const string& filename)
|
||||
bool PiscsiImage::IsValidDstFilename(string_view filename)
|
||||
{
|
||||
// Destination file must not yet exist
|
||||
try {
|
||||
return !exists(path(filename));
|
||||
}
|
||||
catch(const filesystem_error&) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -394,7 +374,7 @@ bool PiscsiImage::ChangeOwner(const CommandContext& context, const path& filenam
|
|||
error_code error;
|
||||
remove(filename, error);
|
||||
|
||||
return context.ReturnStatus(false, "Can't change ownership of '" + string(filename) + "': " + strerror(e));
|
||||
return context.ReturnErrorStatus("Can't change ownership of '" + filename.string() + "': " + strerror(e));
|
||||
}
|
||||
|
||||
permissions(filename, read_only ?
|
||||
|
@ -437,5 +417,5 @@ pair<int, int> PiscsiImage::GetUidAndGid()
|
|||
gid = pwd.pw_gid;
|
||||
}
|
||||
|
||||
return make_pair(uid, gid);
|
||||
return { uid, gid };
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -28,22 +28,23 @@ public:
|
|||
void SetDepth(int d) { depth = d; }
|
||||
int GetDepth() const { return depth; }
|
||||
string GetDefaultFolder() const { return default_folder; }
|
||||
string SetDefaultFolder(const string&);
|
||||
bool CreateImage(const CommandContext&, const PbCommand&) const;
|
||||
bool DeleteImage(const CommandContext&, const PbCommand&) const;
|
||||
bool RenameImage(const CommandContext&, const PbCommand&) const;
|
||||
bool CopyImage(const CommandContext&, const PbCommand&) const;
|
||||
bool SetImagePermissions(const CommandContext&, const PbCommand&) const;
|
||||
string SetDefaultFolder(string_view);
|
||||
bool CreateImage(const CommandContext&) const;
|
||||
bool DeleteImage(const CommandContext&) const;
|
||||
bool RenameImage(const CommandContext&) const;
|
||||
bool CopyImage(const CommandContext&) const;
|
||||
bool SetImagePermissions(const CommandContext&) const;
|
||||
|
||||
private:
|
||||
|
||||
bool CheckDepth(string_view) const;
|
||||
string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
|
||||
bool CreateImageFolder(const CommandContext&, const string&) const;
|
||||
bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const;
|
||||
bool CreateImageFolder(const CommandContext&, string_view) const;
|
||||
static bool IsReservedFile(const CommandContext&, const string&, const string&);
|
||||
bool ValidateParams(const CommandContext&, const string&, string&, string&) const;
|
||||
|
||||
static bool IsValidSrcFilename(const string&);
|
||||
static bool IsValidDstFilename(const string&);
|
||||
static bool IsValidSrcFilename(string_view);
|
||||
static bool IsValidDstFilename(string_view);
|
||||
static bool ChangeOwner(const CommandContext&, const path&, bool);
|
||||
static string GetHomeDir();
|
||||
static pair<int, int> GetUidAndGid();
|
||||
|
|
|
@ -3,50 +3,48 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "shared/network_util.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "devices/disk.h"
|
||||
#include "devices/device_factory.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "piscsi_response.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
using namespace piscsi_interface;
|
||||
using namespace piscsi_util;
|
||||
using namespace network_util;
|
||||
using namespace protobuf_util;
|
||||
|
||||
unique_ptr<PbDeviceProperties> PiscsiResponse::GetDeviceProperties(const Device& device) const
|
||||
void PiscsiResponse::GetDeviceProperties(const Device& device, PbDeviceProperties& properties) const
|
||||
{
|
||||
auto properties = make_unique<PbDeviceProperties>();
|
||||
|
||||
// Currently there is only a SCSI controller, i.e. there can always be 32 LUNs
|
||||
properties->set_luns(32);
|
||||
properties->set_read_only(device.IsReadOnly());
|
||||
properties->set_protectable(device.IsProtectable());
|
||||
properties->set_stoppable(device.IsStoppable());
|
||||
properties->set_removable(device.IsRemovable());
|
||||
properties->set_lockable(device.IsLockable());
|
||||
properties->set_supports_file(device.SupportsFile());
|
||||
properties->set_supports_params(device.SupportsParams());
|
||||
properties.set_luns(ControllerManager::GetScsiLunMax());
|
||||
properties.set_read_only(device.IsReadOnly());
|
||||
properties.set_protectable(device.IsProtectable());
|
||||
properties.set_stoppable(device.IsStoppable());
|
||||
properties.set_removable(device.IsRemovable());
|
||||
properties.set_lockable(device.IsLockable());
|
||||
properties.set_supports_file(device.SupportsFile());
|
||||
properties.set_supports_params(device.SupportsParams());
|
||||
|
||||
if (device.SupportsParams()) {
|
||||
for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) {
|
||||
auto& map = *properties->mutable_default_params();
|
||||
auto& map = *properties.mutable_default_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) {
|
||||
properties->add_block_sizes(block_size);
|
||||
properties.add_block_sizes(block_size);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) const
|
||||
|
@ -54,10 +52,10 @@ void PiscsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_inf
|
|||
auto type_properties = device_types_info.add_properties();
|
||||
type_properties->set_type(type);
|
||||
const auto device = device_factory.CreateDevice(type, 0, "");
|
||||
type_properties->set_allocated_properties(GetDeviceProperties(*device).release());
|
||||
} //NOSONAR The allocated memory is managed by protobuf
|
||||
GetDeviceProperties(*device, *type_properties->mutable_properties());
|
||||
}
|
||||
|
||||
void PiscsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) const
|
||||
void PiscsiResponse::GetDeviceTypesInfo(PbDeviceTypesInfo& device_types_info) const
|
||||
{
|
||||
// Start with 2 instead of 1. 1 was the removed SASI drive type.
|
||||
int ordinal = 2;
|
||||
|
@ -78,16 +76,15 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
|
|||
pb_device.set_revision(device.GetRevision());
|
||||
pb_device.set_type(device.GetType());
|
||||
|
||||
pb_device.set_allocated_properties(GetDeviceProperties(device).release());
|
||||
GetDeviceProperties(device, *pb_device.mutable_properties());
|
||||
|
||||
auto status = make_unique<PbDeviceStatus>().release(); //NOSONAR The allocated memory is managed by protobuf
|
||||
pb_device.set_allocated_status(status);
|
||||
auto status = pb_device.mutable_status();
|
||||
status->set_protected_(device.IsProtected());
|
||||
status->set_stopped(device.IsStopped());
|
||||
status->set_removed(device.IsRemoved());
|
||||
status->set_locked(device.IsLocked());
|
||||
|
||||
if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf
|
||||
if (device.SupportsParams()) {
|
||||
for (const auto& [key, value] : device.GetParams()) {
|
||||
SetParam(pb_device, key, value);
|
||||
}
|
||||
|
@ -100,11 +97,9 @@ void PiscsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
|
|||
|
||||
const auto storage_device = dynamic_cast<const StorageDevice *>(&device);
|
||||
if (storage_device != nullptr) {
|
||||
auto image_file = make_unique<PbImageFile>().release();
|
||||
GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : "");
|
||||
pb_device.set_allocated_file(image_file);
|
||||
GetImageFile(*pb_device.mutable_file(), default_folder, device.IsReady() ? storage_device->GetFilename() : "");
|
||||
}
|
||||
} //NOSONAR The allocated memory is managed by protobuf
|
||||
}
|
||||
|
||||
bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default_folder, const string& filename) const
|
||||
{
|
||||
|
@ -112,13 +107,13 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
|
|||
image_file.set_name(filename);
|
||||
image_file.set_type(device_factory.GetTypeForFile(filename));
|
||||
|
||||
const string f = filename[0] == '/' ? filename : default_folder + "/" + filename;
|
||||
const path p(filename[0] == '/' ? filename : default_folder + "/" + filename);
|
||||
|
||||
image_file.set_read_only(access(f.c_str(), W_OK));
|
||||
image_file.set_read_only(access(p.c_str(), W_OK));
|
||||
|
||||
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
|
||||
if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) {
|
||||
image_file.set_size(st.st_size);
|
||||
error_code error;
|
||||
if (is_regular_file(p, error) || (is_symlink(p, error) && !is_block_file(p, error))) {
|
||||
image_file.set_size(file_size(p));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -127,92 +122,68 @@ bool PiscsiResponse::GetImageFile(PbImageFile& image_file, const string& default
|
|||
}
|
||||
|
||||
void PiscsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_folder,
|
||||
const string& folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
|
||||
const string& folder_pattern, const string& file_pattern, int scan_depth) const
|
||||
{
|
||||
if (scan_depth-- < 0) {
|
||||
const path default_path(default_folder);
|
||||
if (!is_directory(default_path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
string folder_pattern_lower = folder_pattern;
|
||||
transform(folder_pattern_lower.begin(), folder_pattern_lower.end(), folder_pattern_lower.begin(), ::tolower);
|
||||
string folder_pattern_lower;
|
||||
ranges::transform(folder_pattern, back_inserter(folder_pattern_lower), ::tolower);
|
||||
|
||||
string file_pattern_lower = file_pattern;
|
||||
transform(file_pattern_lower.begin(), file_pattern_lower.end(), file_pattern_lower.begin(), ::tolower);
|
||||
string file_pattern_lower;
|
||||
ranges::transform(file_pattern, back_inserter(file_pattern_lower), ::tolower);
|
||||
|
||||
DIR *d = opendir(folder.c_str());
|
||||
if (d == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// C++ filesystem cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
|
||||
const dirent *dir;
|
||||
while ((dir = readdir(d))) {
|
||||
string filename = GetNextImageFile(dir, folder);
|
||||
if (filename.empty()) {
|
||||
for (auto it = recursive_directory_iterator(default_path, directory_options::follow_directory_symlink);
|
||||
it != recursive_directory_iterator(); it++) {
|
||||
if (it.depth() > scan_depth) {
|
||||
it.disable_recursion_pending();
|
||||
continue;
|
||||
}
|
||||
|
||||
string name_lower = dir->d_name;
|
||||
if (!file_pattern.empty()) {
|
||||
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
|
||||
}
|
||||
const string parent = it->path().parent_path().string();
|
||||
|
||||
if (dir->d_type == DT_DIR) {
|
||||
if (folder_pattern_lower.empty() || name_lower.find(folder_pattern_lower) != string::npos) {
|
||||
GetAvailableImages(image_files_info, default_folder, filename, folder_pattern,
|
||||
file_pattern, scan_depth);
|
||||
}
|
||||
const string folder = parent.size() > default_folder.size() ? parent.substr(default_folder.size() + 1) : "";
|
||||
|
||||
if (!FilterMatches(folder, folder_pattern_lower) || !FilterMatches(it->path().filename().string(), file_pattern_lower)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_pattern_lower.empty() || name_lower.find(file_pattern_lower) != string::npos) {
|
||||
if (auto image_file = make_unique<PbImageFile>(); GetImageFile(*image_file.get(), default_folder, filename)) {
|
||||
GetImageFile(*image_files_info.add_image_files(), default_folder,
|
||||
filename.substr(default_folder.length() + 1));
|
||||
}
|
||||
if (!ValidateImageFile(it->path())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const string filename = folder.empty() ?
|
||||
it->path().filename().string() : folder + "/" + it->path().filename().string();
|
||||
if (PbImageFile image_file; GetImageFile(image_file, default_folder, filename)) {
|
||||
GetImageFile(*image_files_info.add_image_files(), default_folder, filename);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
}
|
||||
|
||||
unique_ptr<PbImageFilesInfo> PiscsiResponse::GetAvailableImages(PbResult& result, const string& default_folder,
|
||||
void PiscsiResponse::GetImageFilesInfo(PbImageFilesInfo& image_files_info, const string& default_folder,
|
||||
const string& folder_pattern, const string& file_pattern, int scan_depth) const
|
||||
{
|
||||
auto image_files_info = make_unique<PbImageFilesInfo>();
|
||||
image_files_info.set_default_image_folder(default_folder);
|
||||
image_files_info.set_depth(scan_depth);
|
||||
|
||||
image_files_info->set_default_image_folder(default_folder);
|
||||
image_files_info->set_depth(scan_depth);
|
||||
|
||||
GetAvailableImages(*image_files_info, default_folder, default_folder, folder_pattern,
|
||||
file_pattern, scan_depth);
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return image_files_info;
|
||||
GetAvailableImages(image_files_info, default_folder, folder_pattern, file_pattern, scan_depth);
|
||||
}
|
||||
|
||||
void PiscsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& default_folder,
|
||||
void PiscsiResponse::GetAvailableImages(PbServerInfo& server_info, const string& default_folder,
|
||||
const string& folder_pattern, const string& file_pattern, int scan_depth) const
|
||||
{
|
||||
auto image_files_info = GetAvailableImages(result, default_folder, folder_pattern, file_pattern, scan_depth);
|
||||
image_files_info->set_default_image_folder(default_folder);
|
||||
server_info.set_allocated_image_files_info(image_files_info.release());
|
||||
server_info.mutable_image_files_info()->set_default_image_folder(default_folder);
|
||||
|
||||
result.set_status(true); //NOSONAR The allocated memory is managed by protobuf
|
||||
GetImageFilesInfo(*server_info.mutable_image_files_info(), default_folder, folder_pattern, file_pattern, scan_depth);
|
||||
}
|
||||
|
||||
unique_ptr<PbReservedIdsInfo> PiscsiResponse::GetReservedIds(PbResult& result, const unordered_set<int>& ids) const
|
||||
void PiscsiResponse::GetReservedIds(PbReservedIdsInfo& reserved_ids_info, const unordered_set<int>& ids) const
|
||||
{
|
||||
auto reserved_ids_info = make_unique<PbReservedIdsInfo>();
|
||||
for (const int id : ids) {
|
||||
reserved_ids_info->add_ids(id);
|
||||
reserved_ids_info.add_ids(id);
|
||||
}
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return reserved_ids_info;
|
||||
}
|
||||
|
||||
void PiscsiResponse::GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbServerInfo& server_info,
|
||||
|
@ -232,7 +203,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
|
|||
// If no device list was provided in the command get information on all devices
|
||||
if (!command.devices_size()) {
|
||||
for (const auto& device : devices) {
|
||||
id_sets.insert(make_pair(device->GetId(), device->GetLun()));
|
||||
id_sets.insert({ device->GetId(), device->GetLun() });
|
||||
}
|
||||
}
|
||||
// Otherwise get information on the devices provided in the command
|
||||
|
@ -243,8 +214,7 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
|
|||
}
|
||||
}
|
||||
|
||||
auto devices_info = make_unique<PbDevicesInfo>();
|
||||
|
||||
auto devices_info = result.mutable_devices_info();
|
||||
for (const auto& [id, lun] : id_sets) {
|
||||
for (const auto& d : devices) {
|
||||
if (d->GetId() == id && d->GetLun() == lun) {
|
||||
|
@ -254,244 +224,179 @@ void PiscsiResponse::GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice
|
|||
}
|
||||
}
|
||||
|
||||
result.set_allocated_devices_info(devices_info.release());
|
||||
result.set_status(true);
|
||||
}
|
||||
|
||||
unique_ptr<PbDeviceTypesInfo> PiscsiResponse::GetDeviceTypesInfo(PbResult& result) const
|
||||
void PiscsiResponse::GetServerInfo(PbServerInfo& server_info, const unordered_set<shared_ptr<PrimaryDevice>>& devices,
|
||||
const unordered_set<int>& reserved_ids, const string& default_folder, const string& folder_pattern,
|
||||
const string& file_pattern, int scan_depth) const
|
||||
{
|
||||
auto device_types_info = make_unique<PbDeviceTypesInfo>();
|
||||
|
||||
GetAllDeviceTypeProperties(*device_types_info);
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return device_types_info;
|
||||
GetVersionInfo(*server_info.mutable_version_info());
|
||||
GetLogLevelInfo(*server_info.mutable_log_level_info());
|
||||
GetDeviceTypesInfo(*server_info.mutable_device_types_info());
|
||||
GetAvailableImages(server_info, default_folder, folder_pattern, file_pattern, scan_depth);
|
||||
GetNetworkInterfacesInfo(*server_info.mutable_network_interfaces_info());
|
||||
GetMappingInfo(*server_info.mutable_mapping_info());
|
||||
GetDevices(devices, server_info, default_folder);
|
||||
GetReservedIds(*server_info.mutable_reserved_ids_info(), reserved_ids);
|
||||
GetOperationInfo(*server_info.mutable_operation_info(), scan_depth);
|
||||
}
|
||||
|
||||
unique_ptr<PbServerInfo> PiscsiResponse::GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>& devices,
|
||||
PbResult& result, const unordered_set<int>& reserved_ids, const string& current_log_level,
|
||||
const string& default_folder, const string& folder_pattern, const string& file_pattern, int scan_depth) const
|
||||
void PiscsiResponse::GetVersionInfo(PbVersionInfo& version_info) const
|
||||
{
|
||||
auto server_info = make_unique<PbServerInfo>();
|
||||
|
||||
server_info->set_allocated_version_info(GetVersionInfo(result).release());
|
||||
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level).release()); //NOSONAR The allocated memory is managed by protobuf
|
||||
GetAllDeviceTypeProperties(*server_info->mutable_device_types_info()); //NOSONAR The allocated memory is managed by protobuf
|
||||
GetAvailableImages(result, *server_info, default_folder, folder_pattern, file_pattern, scan_depth);
|
||||
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result).release());
|
||||
server_info->set_allocated_mapping_info(GetMappingInfo(result).release()); //NOSONAR The allocated memory is managed by protobuf
|
||||
GetDevices(devices, *server_info, default_folder); //NOSONAR The allocated memory is managed by protobuf
|
||||
server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids).release());
|
||||
server_info->set_allocated_operation_info(GetOperationInfo(result, scan_depth).release()); //NOSONAR The allocated memory is managed by protobuf
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return server_info;
|
||||
version_info.set_major_version(piscsi_major_version);
|
||||
version_info.set_minor_version(piscsi_minor_version);
|
||||
version_info.set_patch_version(piscsi_patch_version);
|
||||
}
|
||||
|
||||
unique_ptr<PbVersionInfo> PiscsiResponse::GetVersionInfo(PbResult& result) const
|
||||
void PiscsiResponse::GetLogLevelInfo(PbLogLevelInfo& log_level_info) const
|
||||
{
|
||||
auto version_info = make_unique<PbVersionInfo>();
|
||||
|
||||
version_info->set_major_version(piscsi_major_version);
|
||||
version_info->set_minor_version(piscsi_minor_version);
|
||||
version_info->set_patch_version(piscsi_patch_version);
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return version_info;
|
||||
}
|
||||
|
||||
unique_ptr<PbLogLevelInfo> PiscsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) const
|
||||
{
|
||||
auto log_level_info = make_unique<PbLogLevelInfo>();
|
||||
|
||||
for (const auto& log_level : log_levels) {
|
||||
log_level_info->add_log_levels(log_level);
|
||||
for (const auto& log_level : spdlog::level::level_string_views) {
|
||||
log_level_info.add_log_levels(log_level.data());
|
||||
}
|
||||
|
||||
log_level_info->set_current_log_level(current_log_level);
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return log_level_info;
|
||||
log_level_info.set_current_log_level(spdlog::level::level_string_views[spdlog::get_level()].data());
|
||||
}
|
||||
|
||||
unique_ptr<PbNetworkInterfacesInfo> PiscsiResponse::GetNetworkInterfacesInfo(PbResult& result) const
|
||||
void PiscsiResponse::GetNetworkInterfacesInfo(PbNetworkInterfacesInfo& network_interfaces_info) const
|
||||
{
|
||||
auto network_interfaces_info = make_unique<PbNetworkInterfacesInfo>();
|
||||
|
||||
for (const auto& network_interface : device_factory.GetNetworkInterfaces()) {
|
||||
network_interfaces_info->add_name(network_interface);
|
||||
for (const auto& network_interface : GetNetworkInterfaces()) {
|
||||
network_interfaces_info.add_name(network_interface);
|
||||
}
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return network_interfaces_info;
|
||||
}
|
||||
|
||||
unique_ptr<PbMappingInfo> PiscsiResponse::GetMappingInfo(PbResult& result) const
|
||||
void PiscsiResponse::GetMappingInfo(PbMappingInfo& mapping_info) const
|
||||
{
|
||||
auto mapping_info = make_unique<PbMappingInfo>();
|
||||
|
||||
for (const auto& [name, type] : device_factory.GetExtensionMapping()) {
|
||||
(*mapping_info->mutable_mapping())[name] = type;
|
||||
(*mapping_info.mutable_mapping())[name] = type;
|
||||
}
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return mapping_info;
|
||||
}
|
||||
|
||||
unique_ptr<PbOperationInfo> PiscsiResponse::GetOperationInfo(PbResult& result, int depth) const
|
||||
void PiscsiResponse::GetOperationInfo(PbOperationInfo& operation_info, int depth) const
|
||||
{
|
||||
auto operation_info = make_unique<PbOperationInfo>();
|
||||
auto operation = CreateOperation(operation_info, ATTACH, "Attach device, device-specific parameters are required");
|
||||
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device");
|
||||
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list");
|
||||
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge");
|
||||
AddOperationParameter(*operation, "cmd", "Print command for the printer device");
|
||||
|
||||
auto operation = CreateOperation(*operation_info, ATTACH, "Attach device, device-specific parameters are required");
|
||||
AddOperationParameter(*operation, "name", "Image file name in case of a mass storage device").release();
|
||||
AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release();
|
||||
AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release();
|
||||
AddOperationParameter(*operation, "cmd", "Print command for the printer device").release();
|
||||
operation.release();
|
||||
CreateOperation(operation_info, DETACH, "Detach device, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release();
|
||||
CreateOperation(operation_info, DETACH_ALL, "Detach all devices");
|
||||
|
||||
CreateOperation(*operation_info, DETACH_ALL, "Detach all devices").release();
|
||||
CreateOperation(operation_info, START, "Start device, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, START, "Start device, device-specific parameters are required").release();
|
||||
CreateOperation(operation_info, STOP, "Stop device, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, STOP, "Stop device, device-specific parameters are required").release();
|
||||
operation = CreateOperation(operation_info, INSERT, "Insert medium, device-specific parameters are required");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, INSERT, "Insert medium, device-specific parameters are required");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
operation.release();
|
||||
CreateOperation(operation_info, EJECT, "Eject medium, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, EJECT, "Eject medium, device-specific parameters are required").release();
|
||||
CreateOperation(operation_info, PROTECT, "Protect medium, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, PROTECT, "Protect medium, device-specific parameters are required").release();
|
||||
CreateOperation(operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required");
|
||||
|
||||
CreateOperation(*operation_info, UNPROTECT, "Unprotect medium, device-specific parameters are required").release();
|
||||
|
||||
operation = CreateOperation(*operation_info, SERVER_INFO, "Get piscsi server information");
|
||||
operation = CreateOperation(operation_info, SERVER_INFO, "Get piscsi server information");
|
||||
if (depth) {
|
||||
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
|
||||
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
|
||||
}
|
||||
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
|
||||
operation.release();
|
||||
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
|
||||
|
||||
CreateOperation(*operation_info, VERSION_INFO, "Get piscsi server version").release();
|
||||
CreateOperation(operation_info, VERSION_INFO, "Get piscsi server version");
|
||||
|
||||
CreateOperation(*operation_info, DEVICES_INFO, "Get information on attached devices").release();
|
||||
CreateOperation(operation_info, DEVICES_INFO, "Get information on attached devices");
|
||||
|
||||
CreateOperation(*operation_info, DEVICE_TYPES_INFO, "Get device properties by device type").release();
|
||||
CreateOperation(operation_info, DEVICE_TYPES_INFO, "Get device properties by device type");
|
||||
|
||||
operation = CreateOperation(*operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
|
||||
operation = CreateOperation(operation_info, DEFAULT_IMAGE_FILES_INFO, "Get information on available image files");
|
||||
if (depth) {
|
||||
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names").release();
|
||||
AddOperationParameter(*operation, "folder_pattern", "Pattern for filtering image folder names");
|
||||
}
|
||||
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names").release();
|
||||
operation.release();
|
||||
AddOperationParameter(*operation, "file_pattern", "Pattern for filtering image file names");
|
||||
|
||||
operation = CreateOperation(*operation_info, IMAGE_FILE_INFO, "Get information on image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, IMAGE_FILE_INFO, "Get information on image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
|
||||
CreateOperation(*operation_info, LOG_LEVEL_INFO, "Get log level information").release();
|
||||
CreateOperation(operation_info, LOG_LEVEL_INFO, "Get log level information");
|
||||
|
||||
CreateOperation(*operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces").release();
|
||||
CreateOperation(operation_info, NETWORK_INTERFACES_INFO, "Get the available network interfaces");
|
||||
|
||||
CreateOperation(*operation_info, MAPPING_INFO, "Get mapping of extensions to device types").release();
|
||||
CreateOperation(operation_info, MAPPING_INFO, "Get mapping of extensions to device types");
|
||||
|
||||
CreateOperation(*operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs").release();
|
||||
CreateOperation(operation_info, RESERVED_IDS_INFO, "Get list of reserved device IDs");
|
||||
|
||||
operation = CreateOperation(*operation_info, DEFAULT_FOLDER, "Set default image file folder");
|
||||
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, DEFAULT_FOLDER, "Set default image file folder");
|
||||
AddOperationParameter(*operation, "folder", "Default image file folder name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, LOG_LEVEL, "Set log level");
|
||||
AddOperationParameter(*operation, "level", "New log level", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, LOG_LEVEL, "Set log level");
|
||||
AddOperationParameter(*operation, "level", "New log level", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, RESERVE_IDS, "Reserve device IDs");
|
||||
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, RESERVE_IDS, "Reserve device IDs");
|
||||
AddOperationParameter(*operation, "ids", "Comma-separated device ID list", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, SHUT_DOWN, "Shut down or reboot");
|
||||
auto parameter = AddOperationParameter(*operation, "mode", "Shutdown mode", "", true).release();
|
||||
parameter->add_permitted_values("piscsi");
|
||||
// System shutdown/reboot requires root permissions
|
||||
if (!getuid()) {
|
||||
parameter->add_permitted_values("system");
|
||||
parameter->add_permitted_values("reboot");
|
||||
operation = CreateOperation(operation_info, SHUT_DOWN, "Shut down or reboot");
|
||||
if (getuid()) {
|
||||
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi" } );
|
||||
}
|
||||
else {
|
||||
// System shutdown/reboot requires root permissions
|
||||
AddOperationParameter(*operation, "mode", "Shutdown mode", "", true, { "rascsi", "system", "reboot" } );
|
||||
}
|
||||
operation.release();
|
||||
|
||||
operation = CreateOperation(*operation_info, CREATE_IMAGE, "Create an image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true).release();
|
||||
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
|
||||
parameter->add_permitted_values("true");
|
||||
parameter->add_permitted_values("false");
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, CREATE_IMAGE, "Create an image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
AddOperationParameter(*operation, "size", "Image file size in bytes", "", true);
|
||||
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
|
||||
|
||||
operation = CreateOperation(*operation_info, DELETE_IMAGE, "Delete image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, DELETE_IMAGE, "Delete image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, RENAME_IMAGE, "Rename image file");
|
||||
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
|
||||
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, RENAME_IMAGE, "Rename image file");
|
||||
AddOperationParameter(*operation, "from", "Source image file name", "", true);
|
||||
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, COPY_IMAGE, "Copy image file");
|
||||
AddOperationParameter(*operation, "from", "Source image file name", "", true).release();
|
||||
AddOperationParameter(*operation, "to", "Destination image file name", "", true).release();
|
||||
parameter = AddOperationParameter(*operation, "read_only", "Read-only flag", "false").release();
|
||||
parameter->add_permitted_values("true");
|
||||
parameter->add_permitted_values("false");
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, COPY_IMAGE, "Copy image file");
|
||||
AddOperationParameter(*operation, "from", "Source image file name", "", true);
|
||||
AddOperationParameter(*operation, "to", "Destination image file name", "", true);
|
||||
AddOperationParameter(*operation, "read_only", "Read-only flag", "false", false, { "true", "false" } );
|
||||
|
||||
operation = CreateOperation(*operation_info, PROTECT_IMAGE, "Write-protect image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, PROTECT_IMAGE, "Write-protect image file");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, UNPROTECT_IMAGE, "Make image file writable");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, UNPROTECT_IMAGE, "Make image file writable");
|
||||
AddOperationParameter(*operation, "file", "Image file name", "", true);
|
||||
|
||||
operation = CreateOperation(*operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
|
||||
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true).release();
|
||||
operation.release();
|
||||
operation = CreateOperation(operation_info, CHECK_AUTHENTICATION, "Check whether an authentication token is valid");
|
||||
AddOperationParameter(*operation, "token", "Authentication token to be checked", "", true);
|
||||
|
||||
CreateOperation(*operation_info, OPERATION_INFO, "Get operation meta data").release();
|
||||
|
||||
result.set_status(true);
|
||||
|
||||
return operation_info;
|
||||
CreateOperation(operation_info, OPERATION_INFO, "Get operation meta data");
|
||||
}
|
||||
|
||||
unique_ptr<PbOperationMetaData> PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
|
||||
// This method returns a raw pointer because protobuf does not have support for smart pointers
|
||||
PbOperationMetaData *PiscsiResponse::CreateOperation(PbOperationInfo& operation_info, const PbOperation& operation,
|
||||
const string& description) const
|
||||
{
|
||||
auto meta_data = make_unique<PbOperationMetaData>();
|
||||
meta_data->set_server_side_name(PbOperation_Name(operation));
|
||||
meta_data->set_description(description);
|
||||
PbOperationMetaData meta_data;
|
||||
meta_data.set_server_side_name(PbOperation_Name(operation));
|
||||
meta_data.set_description(description);
|
||||
int ordinal = PbOperation_descriptor()->FindValueByName(PbOperation_Name(operation))->index();
|
||||
(*operation_info.mutable_operations())[ordinal] = *meta_data.release();
|
||||
return unique_ptr<PbOperationMetaData>(&(*operation_info.mutable_operations())[ordinal]);
|
||||
(*operation_info.mutable_operations())[ordinal] = meta_data;
|
||||
return &(*operation_info.mutable_operations())[ordinal];
|
||||
}
|
||||
|
||||
unique_ptr<PbOperationParameter> PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data,
|
||||
const string& name, const string& description, const string& default_value, bool is_mandatory) const
|
||||
void PiscsiResponse::AddOperationParameter(PbOperationMetaData& meta_data, const string& name,
|
||||
const string& description, const string& default_value, bool is_mandatory,
|
||||
const vector<string>& permitted_values) const
|
||||
{
|
||||
auto parameter = unique_ptr<PbOperationParameter>(meta_data.add_parameters());
|
||||
auto parameter = meta_data.add_parameters();
|
||||
parameter->set_name(name);
|
||||
parameter->set_description(description);
|
||||
parameter->set_default_value(default_value);
|
||||
parameter->set_is_mandatory(is_mandatory);
|
||||
|
||||
return parameter;
|
||||
for (const auto& permitted_value : permitted_values) {
|
||||
parameter->add_permitted_values(permitted_value);
|
||||
}
|
||||
}
|
||||
|
||||
set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>& devices, PbResult& result,
|
||||
|
@ -503,7 +408,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
|
|||
bool has_device = false;
|
||||
for (const auto& d : devices) {
|
||||
if (d->GetId() == device.id() && d->GetLun() == device.unit()) {
|
||||
id_sets.insert(make_pair(device.id(), device.unit()));
|
||||
id_sets.insert({ device.id(), device.unit() });
|
||||
has_device = true;
|
||||
break;
|
||||
}
|
||||
|
@ -513,7 +418,7 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
|
|||
id_sets.clear();
|
||||
|
||||
result.set_status(false);
|
||||
result.set_msg("No device for ID " + to_string(device.id()) + ", unit " + to_string(device.unit()));
|
||||
result.set_msg("No device for " + to_string(device.id()) + ":" + to_string(device.unit()));
|
||||
|
||||
break;
|
||||
}
|
||||
|
@ -522,30 +427,45 @@ set<id_set> PiscsiResponse::MatchDevices(const unordered_set<shared_ptr<PrimaryD
|
|||
return id_sets;
|
||||
}
|
||||
|
||||
string PiscsiResponse::GetNextImageFile(const dirent *dir, const string& folder)
|
||||
bool PiscsiResponse::ValidateImageFile(const path& path)
|
||||
{
|
||||
// Ignore unknown folder types and folder names starting with '.'
|
||||
if ((dir->d_type != DT_REG && dir->d_type != DT_DIR && dir->d_type != DT_LNK && dir->d_type != DT_BLK)
|
||||
|| dir->d_name[0] == '.') {
|
||||
return "";
|
||||
if (path.filename().string().starts_with(".")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const string filename = folder + "/" + dir->d_name;
|
||||
filesystem::path p(path);
|
||||
|
||||
const bool file_exists = exists(path(filename));
|
||||
|
||||
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle files of more than 2 GiB
|
||||
struct stat st;
|
||||
stat(filename.c_str(), &st);
|
||||
if (dir->d_type == DT_REG && file_exists && !st.st_size) {
|
||||
LOGWARN("File '%s' in image folder '%s' is empty", dir->d_name, folder.c_str())
|
||||
return "";
|
||||
// Follow symlink
|
||||
if (is_symlink(p)) {
|
||||
p = read_symlink(p);
|
||||
if (!exists(p)) {
|
||||
spdlog::warn("Image file symlink '" + path.string() + "' is broken");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dir->d_type == DT_LNK && !file_exists) {
|
||||
LOGWARN("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str())
|
||||
return "";
|
||||
if (is_directory(p) || (is_other(p) && !is_block_file(p))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filename;
|
||||
if (!is_block_file(p) && file_size(p) < 256) {
|
||||
spdlog::warn("Image file '" + p.string() + "' is invalid");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PiscsiResponse::FilterMatches(const string& input, string_view pattern_lower)
|
||||
{
|
||||
if (!pattern_lower.empty()) {
|
||||
string name_lower;
|
||||
ranges::transform(input, back_inserter(name_lower), ::tolower);
|
||||
|
||||
if (name_lower.find(pattern_lower) == string::npos) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -12,52 +12,51 @@
|
|||
#include "devices/device_factory.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include <dirent.h>
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <span>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
using namespace piscsi_interface;
|
||||
|
||||
class PiscsiResponse
|
||||
{
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
public:
|
||||
|
||||
PiscsiResponse() = default;
|
||||
~PiscsiResponse() = default;
|
||||
|
||||
bool GetImageFile(PbImageFile&, const string&, const string&) const;
|
||||
unique_ptr<PbImageFilesInfo> GetAvailableImages(PbResult&, const string&, const string&, const string&, int) const;
|
||||
unique_ptr<PbReservedIdsInfo> GetReservedIds(PbResult&, const unordered_set<int>&) const;
|
||||
void GetImageFilesInfo(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
|
||||
void GetReservedIds(PbReservedIdsInfo&, const unordered_set<int>&) const;
|
||||
void GetDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbServerInfo&, const string&) const;
|
||||
void GetDevicesInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&, const string&) const;
|
||||
unique_ptr<PbDeviceTypesInfo> GetDeviceTypesInfo(PbResult&) const;
|
||||
unique_ptr<PbVersionInfo> GetVersionInfo(PbResult&) const;
|
||||
unique_ptr<PbServerInfo> GetServerInfo(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const unordered_set<int>&,
|
||||
const string&, const string&, const string&, const string&, int) const;
|
||||
unique_ptr<PbNetworkInterfacesInfo> GetNetworkInterfacesInfo(PbResult&) const;
|
||||
unique_ptr<PbMappingInfo> GetMappingInfo(PbResult&) const;
|
||||
unique_ptr<PbLogLevelInfo> GetLogLevelInfo(PbResult&, const string&) const;
|
||||
unique_ptr<PbOperationInfo> GetOperationInfo(PbResult&, int) const;
|
||||
void GetDeviceTypesInfo(PbDeviceTypesInfo&) const;
|
||||
void GetVersionInfo(PbVersionInfo&) const;
|
||||
void GetServerInfo(PbServerInfo&, const unordered_set<shared_ptr<PrimaryDevice>>&, const unordered_set<int>&,
|
||||
const string&, const string&, const string&, int) const;
|
||||
void GetNetworkInterfacesInfo(PbNetworkInterfacesInfo&) const;
|
||||
void GetMappingInfo(PbMappingInfo&) const;
|
||||
void GetLogLevelInfo(PbLogLevelInfo&) const;
|
||||
void GetOperationInfo(PbOperationInfo&, int) const;
|
||||
|
||||
private:
|
||||
|
||||
DeviceFactory device_factory;
|
||||
inline static const vector<string> EMPTY_VECTOR;
|
||||
|
||||
const inline static array<string, 6> log_levels = { "trace", "debug", "info", "warn", "err", "off" };
|
||||
const DeviceFactory device_factory;
|
||||
|
||||
unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
|
||||
void GetDeviceProperties(const Device&, PbDeviceProperties&) const;
|
||||
void GetDevice(const Device&, PbDevice&, const string&) const;
|
||||
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&) const;
|
||||
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType) const;
|
||||
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, const string&, int) const;
|
||||
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, const string&, const string&, int) const;
|
||||
unique_ptr<PbOperationMetaData> CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
|
||||
unique_ptr<PbOperationParameter> AddOperationParameter(PbOperationMetaData&, const string&, const string&,
|
||||
const string& = "", bool = false) const;
|
||||
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int) const;
|
||||
void GetAvailableImages(PbServerInfo&, const string&, const string&, const string&, int) const;
|
||||
PbOperationMetaData *CreateOperation(PbOperationInfo&, const PbOperation&, const string&) const;
|
||||
void AddOperationParameter(PbOperationMetaData&, const string&, const string&,
|
||||
const string& = "", bool = false, const vector<string>& = EMPTY_VECTOR) const;
|
||||
set<id_set> MatchDevices(const unordered_set<shared_ptr<PrimaryDevice>>&, PbResult&, const PbCommand&) const;
|
||||
|
||||
static string GetNextImageFile(const dirent *, const string&);
|
||||
static bool ValidateImageFile(const path&);
|
||||
|
||||
static bool FilterMatches(const string&, string_view);
|
||||
};
|
||||
|
|
|
@ -3,140 +3,114 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_serializer.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "command_context.h"
|
||||
#include "localizer.h"
|
||||
#include "piscsi_service.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <csignal>
|
||||
#include <cassert>
|
||||
|
||||
using namespace piscsi_interface;
|
||||
using namespace piscsi_util;
|
||||
|
||||
void PiscsiService::Cleanup() const
|
||||
string PiscsiService::Init(const callback& cb, int port)
|
||||
{
|
||||
running = false;
|
||||
assert(service_socket == -1);
|
||||
|
||||
if (service_socket != -1) {
|
||||
close(service_socket);
|
||||
}
|
||||
}
|
||||
|
||||
bool PiscsiService::Init(const callback& cb, int port)
|
||||
{
|
||||
if (port <= 0 || port > 65535) {
|
||||
return false;
|
||||
return "Invalid port number " + to_string(port);
|
||||
}
|
||||
|
||||
// Create socket for monitor
|
||||
sockaddr_in server = {};
|
||||
service_socket = socket(PF_INET, SOCK_STREAM, 0);
|
||||
if (service_socket == -1) {
|
||||
LOGERROR("Unable to create socket")
|
||||
return false;
|
||||
return "Unable to create service socket: " + string(strerror(errno));
|
||||
}
|
||||
|
||||
if (const int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
|
||||
Stop();
|
||||
return "Can't reuse address";
|
||||
}
|
||||
|
||||
sockaddr_in server = {};
|
||||
server.sin_family = PF_INET;
|
||||
server.sin_port = htons((uint16_t)port);
|
||||
server.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
|
||||
// Allow address reuse
|
||||
if (int yes = 1; setsockopt(service_socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
|
||||
return false;
|
||||
server.sin_addr.s_addr = INADDR_ANY;
|
||||
if (bind(service_socket, reinterpret_cast<const sockaddr *>(&server), sizeof(sockaddr_in)) < 0) { //NOSONAR bit_cast is not supported by the bullseye compiler
|
||||
Stop();
|
||||
return "Port " + to_string(port) + " is in use, is piscsi already running?";
|
||||
}
|
||||
|
||||
signal(SIGPIPE, SIG_IGN);
|
||||
|
||||
if (bind(service_socket, (sockaddr *)&server, sizeof(sockaddr_in)) < 0) {
|
||||
cerr << "Error: Port " << port << " is in use, is piscsi or rascsi already running?" << endl;
|
||||
return false;
|
||||
if (listen(service_socket, 2) == -1) {
|
||||
Stop();
|
||||
return "Can't listen to service socket: " + string(strerror(errno));
|
||||
}
|
||||
|
||||
execute = cb;
|
||||
|
||||
monthread = thread(&PiscsiService::Execute, this);
|
||||
monthread.detach();
|
||||
return "";
|
||||
}
|
||||
|
||||
// Interrupt handler settings
|
||||
return signal(SIGINT, KillHandler) != SIG_ERR && signal(SIGHUP, KillHandler) != SIG_ERR
|
||||
&& signal(SIGTERM, KillHandler) != SIG_ERR;
|
||||
void PiscsiService::Start()
|
||||
{
|
||||
assert(service_socket != -1);
|
||||
|
||||
service_thread = jthread([this] () { Execute(); } );
|
||||
}
|
||||
|
||||
void PiscsiService::Stop()
|
||||
{
|
||||
assert(service_socket != -1);
|
||||
|
||||
shutdown(service_socket, SHUT_RD);
|
||||
close(service_socket);
|
||||
|
||||
service_socket = -1;
|
||||
}
|
||||
|
||||
void PiscsiService::Execute() const
|
||||
{
|
||||
#ifdef __linux__
|
||||
// Scheduler Settings
|
||||
sched_param schedparam;
|
||||
schedparam.sched_priority = 0;
|
||||
// Run this thread with very low priority
|
||||
sched_param schedparam = { .sched_priority = 0 };
|
||||
sched_setscheduler(0, SCHED_IDLE, &schedparam);
|
||||
#endif
|
||||
|
||||
// Set the affinity to a specific processor core
|
||||
FixCpu(2);
|
||||
|
||||
// Wait for the execution to start
|
||||
const timespec ts = { .tv_sec = 0, .tv_nsec = 1000};
|
||||
while (!running) {
|
||||
nanosleep(&ts, nullptr);
|
||||
}
|
||||
|
||||
// Set up the monitor socket to receive commands
|
||||
listen(service_socket, 1);
|
||||
|
||||
while (true) {
|
||||
CommandContext context;
|
||||
|
||||
try {
|
||||
PbCommand command = ReadCommand(context);
|
||||
if (context.IsValid()) {
|
||||
execute(context, command);
|
||||
}
|
||||
// TODO Accept more than one command instead of closing the socket after a single command
|
||||
while (service_socket != -1) {
|
||||
const int fd = accept(service_socket, nullptr, nullptr);
|
||||
if (fd != -1) {
|
||||
ExecuteCommand(fd);
|
||||
close(fd);
|
||||
}
|
||||
catch(const io_exception& e) {
|
||||
LOGWARN("%s", e.what())
|
||||
|
||||
// Fall through
|
||||
}
|
||||
|
||||
context.Cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
PbCommand PiscsiService::ReadCommand(CommandContext& context) const
|
||||
void PiscsiService::ExecuteCommand(int fd) const
|
||||
{
|
||||
// Wait for connection
|
||||
sockaddr client = {};
|
||||
socklen_t socklen = sizeof(client);
|
||||
const int fd = accept(service_socket, &client, &socklen);
|
||||
if (fd == -1) {
|
||||
throw io_exception("accept() failed");
|
||||
CommandContext context(fd);
|
||||
try {
|
||||
if (context.ReadCommand()) {
|
||||
execute(context);
|
||||
}
|
||||
}
|
||||
catch(const io_exception& e) {
|
||||
spdlog::warn(e.what());
|
||||
|
||||
PbCommand command;
|
||||
|
||||
// Read magic string
|
||||
vector<byte> magic(6);
|
||||
const size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic);
|
||||
if (!bytes_read) {
|
||||
return command;
|
||||
// Try to return an error message (this may fail if the exception was caused when returning the actual result)
|
||||
PbResult result;
|
||||
result.set_msg(e.what());
|
||||
try {
|
||||
context.WriteResult(result);
|
||||
}
|
||||
catch(const io_exception&) { //NOSONAR Not handled on purpose
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
|
||||
throw io_exception("Invalid magic");
|
||||
}
|
||||
|
||||
// Fetch the command
|
||||
context.GetSerializer().DeserializeMessage(fd, command);
|
||||
|
||||
context.SetFd(fd);
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,49 +3,42 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
class CommandContext;
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
|
||||
class PiscsiService
|
||||
{
|
||||
using callback = function<bool(const CommandContext&, piscsi_interface::PbCommand&)>;
|
||||
|
||||
callback execute;
|
||||
|
||||
int service_socket = -1;
|
||||
|
||||
thread monthread;
|
||||
|
||||
static inline volatile bool running = false;
|
||||
using callback = function<bool(CommandContext&)>;
|
||||
|
||||
public:
|
||||
|
||||
PiscsiService() = default;
|
||||
~PiscsiService() = default;
|
||||
|
||||
bool Init(const callback&, int);
|
||||
void Cleanup() const;
|
||||
|
||||
bool IsRunning() const { return running; }
|
||||
void SetRunning(bool b) const { running = b; }
|
||||
string Init(const callback&, int);
|
||||
void Start();
|
||||
void Stop();
|
||||
bool IsRunning() const { return service_socket != -1 && service_thread.joinable(); }
|
||||
|
||||
private:
|
||||
|
||||
void Execute() const;
|
||||
void ExecuteCommand(int) const;
|
||||
|
||||
PbCommand ReadCommand(CommandContext&) const;
|
||||
callback execute;
|
||||
|
||||
static void KillHandler(int) { running = false; }
|
||||
jthread service_thread;
|
||||
|
||||
int service_socket = -1;
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -23,7 +23,7 @@ package piscsi_interface;
|
|||
// The available device types
|
||||
enum PbDeviceType {
|
||||
UNDEFINED = 0;
|
||||
// Non-removable SASI drive, not supported anymore
|
||||
// Non-removable SASI drive, not supported anymore but must not be removed because of backwards compatibility
|
||||
SAHD = 1 [deprecated = true];
|
||||
// Non-removable SCSI drive
|
||||
SCHD = 2;
|
||||
|
|
|
@ -3,26 +3,31 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/network_util.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "scsictl_commands.h"
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
using namespace network_util;
|
||||
using namespace piscsi_util;
|
||||
using namespace protobuf_util;
|
||||
|
||||
bool ScsictlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids,
|
||||
const string& image_params, const string& filename)
|
||||
bool ScsictlCommands::Execute(string_view log_level, string_view default_folder, string_view reserved_ids,
|
||||
string_view image_params, string_view filename)
|
||||
{
|
||||
switch(command.operation()) {
|
||||
case LOG_LEVEL:
|
||||
|
@ -79,6 +84,9 @@ bool ScsictlCommands::Execute(const string& log_level, const string& default_fol
|
|||
case OPERATION_INFO:
|
||||
return CommandOperationInfo();
|
||||
|
||||
case NO_OPERATION:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return SendCommand();
|
||||
}
|
||||
|
@ -112,8 +120,8 @@ bool ScsictlCommands::SendCommand()
|
|||
throw io_exception("Can't write magic");
|
||||
}
|
||||
|
||||
serializer.SerializeMessage(fd, command);
|
||||
serializer.DeserializeMessage(fd, result);
|
||||
SerializeMessage(fd, command);
|
||||
DeserializeMessage(fd, result);
|
||||
|
||||
close(fd);
|
||||
|
||||
|
@ -137,27 +145,23 @@ bool ScsictlCommands::CommandDevicesInfo()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandLogLevel(const string& log_level)
|
||||
bool ScsictlCommands::CommandLogLevel(string_view log_level)
|
||||
{
|
||||
SetParam(command, "level", log_level);
|
||||
|
||||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandReserveIds(const string& reserved_ids)
|
||||
bool ScsictlCommands::CommandReserveIds(string_view reserved_ids)
|
||||
{
|
||||
SetParam(command, "ids", reserved_ids);
|
||||
|
||||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandCreateImage(const string& image_params)
|
||||
bool ScsictlCommands::CommandCreateImage(string_view image_params)
|
||||
{
|
||||
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
|
||||
SetParam(command, "file", string_view(image_params).substr(0, separator_pos));
|
||||
SetParam(command, "size", string_view(image_params).substr(separator_pos + 1));
|
||||
}
|
||||
else {
|
||||
if (!EvaluateParams(image_params, "file", "size")) {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl;
|
||||
|
||||
return false;
|
||||
|
@ -168,20 +172,16 @@ bool ScsictlCommands::CommandCreateImage(const string& image_params)
|
|||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandDeleteImage(const string& filename)
|
||||
bool ScsictlCommands::CommandDeleteImage(string_view filename)
|
||||
{
|
||||
SetParam(command, "file", filename);
|
||||
|
||||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandRenameImage(const string& image_params)
|
||||
bool ScsictlCommands::CommandRenameImage(string_view image_params)
|
||||
{
|
||||
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
|
||||
SetParam(command, "from", string_view(image_params).substr(0, separator_pos));
|
||||
SetParam(command, "to", string_view(image_params).substr(separator_pos + 1));
|
||||
}
|
||||
else {
|
||||
if (!EvaluateParams(image_params, "from", "to")) {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
|
||||
|
||||
return false;
|
||||
|
@ -190,13 +190,9 @@ bool ScsictlCommands::CommandRenameImage(const string& image_params)
|
|||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandCopyImage(const string& image_params)
|
||||
bool ScsictlCommands::CommandCopyImage(string_view image_params)
|
||||
{
|
||||
if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) {
|
||||
SetParam(command, "from", string_view(image_params).substr(0, separator_pos));
|
||||
SetParam(command, "to", string_view(image_params).substr(separator_pos + 1));
|
||||
}
|
||||
else {
|
||||
if (!EvaluateParams(image_params, "from", "to")) {
|
||||
cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl;
|
||||
|
||||
return false;
|
||||
|
@ -205,7 +201,7 @@ bool ScsictlCommands::CommandCopyImage(const string& image_params)
|
|||
return SendCommand();
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandDefaultImageFolder(const string& folder)
|
||||
bool ScsictlCommands::CommandDefaultImageFolder(string_view folder)
|
||||
{
|
||||
SetParam(command, "folder", folder);
|
||||
|
||||
|
@ -259,8 +255,8 @@ bool ScsictlCommands::CommandServerInfo()
|
|||
cout << scsictl_display.DisplayOperationInfo(server_info.operation_info());
|
||||
|
||||
if (server_info.devices_info().devices_size()) {
|
||||
list<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
|
||||
sorted_devices.sort([](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); });
|
||||
vector<PbDevice> sorted_devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() };
|
||||
ranges::sort(sorted_devices, [](const auto& a, const auto& b) { return a.id() < b.id() || a.unit() < b.unit(); });
|
||||
|
||||
cout << "Attached devices:\n";
|
||||
|
||||
|
@ -283,7 +279,7 @@ bool ScsictlCommands::CommandDefaultImageFilesInfo()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScsictlCommands::CommandImageFileInfo(const string& filename)
|
||||
bool ScsictlCommands::CommandImageFileInfo(string_view filename)
|
||||
{
|
||||
SetParam(command, "file", filename);
|
||||
|
||||
|
@ -339,15 +335,12 @@ bool ScsictlCommands::CommandOperationInfo()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScsictlCommands::ResolveHostName(const string& host, sockaddr_in *addr)
|
||||
bool ScsictlCommands::EvaluateParams(string_view image_params, const string& key1, const string& key2)
|
||||
{
|
||||
addrinfo hints = {};
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
if (const auto& components = Split(string(image_params), COMPONENT_SEPARATOR, 2); components.size() == 2) {
|
||||
SetParam(command, key1, components[0]);
|
||||
SetParam(command, key2, components[1]);
|
||||
|
||||
if (addrinfo *result; !getaddrinfo(host.c_str(), nullptr, &hints, &result)) {
|
||||
*addr = *(sockaddr_in *)(result->ai_addr);
|
||||
freeaddrinfo(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "shared/protobuf_serializer.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "scsictl_display.h"
|
||||
#include <string>
|
||||
|
@ -26,40 +25,38 @@ public:
|
|||
: command(command), hostname(hostname), port(port) {}
|
||||
~ScsictlCommands() = default;
|
||||
|
||||
bool Execute(const string&, const string&, const string&, const string&, const string&);
|
||||
bool Execute(string_view, string_view, string_view, string_view, string_view);
|
||||
|
||||
bool CommandDevicesInfo();
|
||||
|
||||
private:
|
||||
|
||||
bool CommandLogLevel(const string&);
|
||||
bool CommandReserveIds(const string&);
|
||||
bool CommandCreateImage(const string&);
|
||||
bool CommandDeleteImage(const string&);
|
||||
bool CommandRenameImage(const string&);
|
||||
bool CommandCopyImage(const string&);
|
||||
bool CommandDefaultImageFolder(const string&);
|
||||
bool CommandLogLevel(string_view);
|
||||
bool CommandReserveIds(string_view);
|
||||
bool CommandCreateImage(string_view);
|
||||
bool CommandDeleteImage(string_view);
|
||||
bool CommandRenameImage(string_view);
|
||||
bool CommandCopyImage(string_view);
|
||||
bool CommandDefaultImageFolder(string_view);
|
||||
bool CommandDeviceInfo();
|
||||
bool CommandDeviceTypesInfo();
|
||||
bool CommandVersionInfo();
|
||||
bool CommandServerInfo();
|
||||
bool CommandDefaultImageFilesInfo();
|
||||
bool CommandImageFileInfo(const string&);
|
||||
bool CommandImageFileInfo(string_view);
|
||||
bool CommandNetworkInterfacesInfo();
|
||||
bool CommandLogLevelInfo();
|
||||
bool CommandReservedIdsInfo();
|
||||
bool CommandMappingInfo();
|
||||
bool CommandOperationInfo();
|
||||
bool SendCommand();
|
||||
bool EvaluateParams(string_view, const string&, const string&);
|
||||
|
||||
static bool ResolveHostName(const string&, sockaddr_in *);
|
||||
|
||||
ProtobufSerializer serializer;
|
||||
PbCommand& command;
|
||||
string hostname;
|
||||
int port;
|
||||
|
||||
PbResult result;
|
||||
|
||||
ScsictlDisplay scsictl_display;
|
||||
[[no_unique_address]] const ScsictlDisplay scsictl_display;
|
||||
};
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "controllers/scsi_controller.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "scsictl/scsictl_parser.h"
|
||||
#include "scsictl/scsictl_commands.h"
|
||||
#include "scsictl/scsictl_core.h"
|
||||
|
@ -30,27 +30,27 @@ using namespace protobuf_util;
|
|||
void ScsiCtl::Banner(const vector<char *>& args) const
|
||||
{
|
||||
if (args.size() < 2) {
|
||||
cout << piscsi_util::Banner("(Controller App)");
|
||||
|
||||
cout << "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] ";
|
||||
cout << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] ";
|
||||
cout << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] ";
|
||||
cout << "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] ";
|
||||
cout << "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n";
|
||||
cout << " where ID[:LUN] ID := {0-7}, LUN := {0-31}, default is 0\n";
|
||||
cout << " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n";
|
||||
cout << " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n";
|
||||
cout << " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n";
|
||||
cout << " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n";
|
||||
cout << " FILE|PARAM := image file path or device-specific parameter\n";
|
||||
cout << " IMAGE_FOLDER := default location for image files, default is '~/images'\n";
|
||||
cout << " HOST := piscsi host to connect to, default is 'localhost'\n";
|
||||
cout << " PORT := piscsi port to connect to, default is 6868\n";
|
||||
cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n";
|
||||
cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n";
|
||||
cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n";
|
||||
cout << "Usage: " << args[0] << " -l\n";
|
||||
cout << " Print device list.\n" << flush;
|
||||
cout << piscsi_util::Banner("(Controller App)")
|
||||
<< "\nUsage: " << args[0] << " -i ID[:LUN] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "
|
||||
<< "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "
|
||||
<< "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] "
|
||||
<< "[-x CURRENT_NAME:NEW_NAME] [-z LOCALE] "
|
||||
<< "[-e] [-E FILENAME] [-D] [-I] [-l] [-m] [o] [-O] [-P] [-s] [-v] [-V] [-y] [-X]\n"
|
||||
<< " where ID[:LUN] ID := {0-" << (ControllerManager::GetScsiIdMax() - 1) << "},"
|
||||
<< " LUN := {0-" << (ControllerManager::GetScsiLunMax() - 1) << "}, default is 0\n"
|
||||
<< " CMD := {attach|detach|insert|eject|protect|unprotect|show}\n"
|
||||
<< " TYPE := {schd|scrm|sccd|scmo|scbr|scdp} or convenience type {hd|rm|mo|cd|bridge|daynaport}\n"
|
||||
<< " BLOCK_SIZE := {512|1024|2048|4096) bytes per hard disk drive block\n"
|
||||
<< " NAME := name of device to attach (VENDOR:PRODUCT:REVISION)\n"
|
||||
<< " FILE|PARAM := image file path or device-specific parameter\n"
|
||||
<< " IMAGE_FOLDER := default location for image files, default is '~/images'\n"
|
||||
<< " HOST := piscsi host to connect to, default is 'localhost'\n"
|
||||
<< " PORT := piscsi port to connect to, default is 6868\n"
|
||||
<< " RESERVED_IDS := comma-separated list of IDs to reserve\n"
|
||||
<< " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"
|
||||
<< " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"
|
||||
<< "Usage: " << args[0] << " -l\n"
|
||||
<< " Print device list.\n" << flush;
|
||||
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
@ -77,10 +77,7 @@ int ScsiCtl::run(const vector<char *>& args) const
|
|||
string token;
|
||||
bool list = false;
|
||||
|
||||
const char *locale = setlocale(LC_MESSAGES, "");
|
||||
if (locale == nullptr || !strcmp(locale, "C")) {
|
||||
locale = "en";
|
||||
}
|
||||
string locale = GetLocale();
|
||||
|
||||
opterr = 1;
|
||||
int opt;
|
||||
|
@ -88,7 +85,7 @@ int ScsiCtl::run(const vector<char *>& args) const
|
|||
"e::lmos::vDINOTVXa:b:c:d:f:h:i:n:p:r:t:x:z:C:E:F:L:P::R:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
if (const string error = SetIdAndLun(*device, optarg, ScsiController::LUN_MAX); !error.empty()) {
|
||||
if (const string error = SetIdAndLun(*device, optarg); !error.empty()) {
|
||||
cerr << "Error: " << error << endl;
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
@ -216,7 +213,7 @@ int ScsiCtl::run(const vector<char *>& args) const
|
|||
break;
|
||||
|
||||
case 'v':
|
||||
cout << "scsictl version: " << piscsi_get_version_string() << endl;
|
||||
cout << "scsictl version: " << piscsi_get_version_string() << '\n';
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
|
||||
|
@ -263,7 +260,7 @@ int ScsiCtl::run(const vector<char *>& args) const
|
|||
|
||||
bool status;
|
||||
try {
|
||||
// Listing devices is a special case (rasctl backwards compatibility)
|
||||
// Listing devices is a special case (legacy rasctl backwards compatibility)
|
||||
if (list) {
|
||||
command.clear_devices();
|
||||
command.set_operation(DEVICES_INFO);
|
||||
|
|
|
@ -3,26 +3,29 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/protobuf_util.h"
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include "scsictl_display.h"
|
||||
#include <sstream>
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <iomanip>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
using namespace piscsi_util;
|
||||
using namespace protobuf_util;
|
||||
|
||||
string ScsictlDisplay::DisplayDevicesInfo(const PbDevicesInfo& devices_info) const
|
||||
{
|
||||
ostringstream s;
|
||||
|
||||
const list<PbDevice>& devices = { devices_info.devices().begin(), devices_info.devices().end() };
|
||||
const vector<PbDevice> devices(devices_info.devices().begin(), devices_info.devices().end());
|
||||
|
||||
s << ListDevices(devices);
|
||||
|
||||
|
@ -50,51 +53,33 @@ string ScsictlDisplay::DisplayDeviceInfo(const PbDevice& pb_device) const
|
|||
|
||||
s << " ";
|
||||
|
||||
bool hasProperty = false;
|
||||
vector<string> properties;
|
||||
|
||||
if (pb_device.properties().read_only()) {
|
||||
s << "read-only";
|
||||
hasProperty = true;
|
||||
properties.emplace_back("read-only");
|
||||
}
|
||||
|
||||
if (pb_device.properties().protectable() && pb_device.status().protected_()) {
|
||||
if (hasProperty) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "protected";
|
||||
hasProperty = true;
|
||||
properties.emplace_back("protected");
|
||||
}
|
||||
|
||||
if (pb_device.properties().stoppable() && pb_device.status().stopped()) {
|
||||
if (hasProperty) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "stopped";
|
||||
hasProperty = true;
|
||||
properties.emplace_back("stopped");
|
||||
}
|
||||
|
||||
if (pb_device.properties().removable() && pb_device.status().removed()) {
|
||||
if (hasProperty) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "removed";
|
||||
hasProperty = true;
|
||||
properties.emplace_back("removed");
|
||||
}
|
||||
|
||||
if (pb_device.properties().lockable() && pb_device.status().locked()) {
|
||||
if (hasProperty) {
|
||||
s << ", ";
|
||||
}
|
||||
s << "locked";
|
||||
properties.emplace_back("locked");
|
||||
}
|
||||
|
||||
if (hasProperty) {
|
||||
s << " ";
|
||||
if (!properties.empty()) {
|
||||
s << Join(properties) << " ";
|
||||
}
|
||||
|
||||
DisplayParams(s, pb_device);
|
||||
|
||||
s << '\n';
|
||||
s << DisplayParams(pb_device) << '\n';
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
@ -103,8 +88,8 @@ string ScsictlDisplay::DisplayVersionInfo(const PbVersionInfo& version_info) con
|
|||
{
|
||||
ostringstream s;
|
||||
|
||||
s << "piscsi server version: " << setw(2) << setfill('0') << version_info.major_version() << "."
|
||||
<< setw(2) << setfill('0') << version_info.minor_version();
|
||||
s << "piscsi server version: " << setfill('0') << setw(2) << version_info.major_version() << "."
|
||||
<< setw(2) << version_info.minor_version();
|
||||
|
||||
if (version_info.patch_version() > 0) {
|
||||
s << "." << version_info.patch_version();
|
||||
|
@ -149,7 +134,7 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty
|
|||
|
||||
const PbDeviceProperties& properties = device_type_info.properties();
|
||||
|
||||
DisplayAttributes(s, properties);
|
||||
s << DisplayAttributes(properties);
|
||||
|
||||
if (properties.supports_file()) {
|
||||
s << "Image file support\n ";
|
||||
|
@ -159,9 +144,9 @@ string ScsictlDisplay::DisplayDeviceTypesInfo(const PbDeviceTypesInfo& device_ty
|
|||
s << "Parameter support\n ";
|
||||
}
|
||||
|
||||
DisplayDefaultParameters(s, properties);
|
||||
s << DisplayDefaultParameters(properties);
|
||||
|
||||
DisplayBlockSizes(s, properties);
|
||||
s << DisplayBlockSizes(properties);
|
||||
}
|
||||
|
||||
s << '\n';
|
||||
|
@ -174,20 +159,8 @@ string ScsictlDisplay::DisplayReservedIdsInfo(const PbReservedIdsInfo& reserved_
|
|||
ostringstream s;
|
||||
|
||||
if (reserved_ids_info.ids_size()) {
|
||||
s << "Reserved device IDs: ";
|
||||
|
||||
for (int i = 0; i < reserved_ids_info.ids_size(); i++) {
|
||||
if(i) {
|
||||
s << ", ";
|
||||
}
|
||||
|
||||
s << reserved_ids_info.ids(i);
|
||||
}
|
||||
|
||||
s << '\n';
|
||||
}
|
||||
else {
|
||||
s << "No reserved device IDs\n";
|
||||
const set<int32_t> sorted_ids(reserved_ids_info.ids().begin(), reserved_ids_info.ids().end());
|
||||
s << "Reserved device IDs: " << Join(sorted_ids) << '\n';
|
||||
}
|
||||
|
||||
return s.str();
|
||||
|
@ -219,12 +192,9 @@ string ScsictlDisplay::DisplayImageFilesInfo(const PbImageFilesInfo& image_files
|
|||
s << "Default image file folder: " << image_files_info.default_image_folder() << '\n';
|
||||
s << "Supported folder depth: " << image_files_info.depth() << '\n';
|
||||
|
||||
if (image_files_info.image_files().empty()) {
|
||||
s << " No image files available\n";
|
||||
}
|
||||
else {
|
||||
list<PbImageFile> image_files = { image_files_info.image_files().begin(), image_files_info.image_files().end() };
|
||||
image_files.sort([](const auto& a, const auto& b) { return a.name() < b.name(); });
|
||||
if (!image_files_info.image_files().empty()) {
|
||||
vector<PbImageFile> image_files(image_files_info.image_files().begin(), image_files_info.image_files().end());
|
||||
ranges::sort(image_files, [](const auto& a, const auto& b) { return a.name() < b.name(); });
|
||||
|
||||
s << "Available image files:\n";
|
||||
for (const auto& image_file : image_files) {
|
||||
|
@ -241,24 +211,8 @@ string ScsictlDisplay::DisplayNetworkInterfaces(const PbNetworkInterfacesInfo& n
|
|||
{
|
||||
ostringstream s;
|
||||
|
||||
s << "Available (up) network interfaces:\n";
|
||||
|
||||
const list<string> sorted_interfaces = { network_interfaces_info.name().begin(), network_interfaces_info.name().end() };
|
||||
|
||||
bool isFirst = true;
|
||||
for (const auto& interface : sorted_interfaces) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
else {
|
||||
s << " ";
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
s << interface;
|
||||
}
|
||||
|
||||
s << '\n';
|
||||
const set<string, less<>> sorted_interfaces(network_interfaces_info.name().begin(), network_interfaces_info.name().end());
|
||||
s << "Available (up) network interfaces: " << Join(sorted_interfaces) << '\n';
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
@ -269,9 +223,8 @@ string ScsictlDisplay::DisplayMappingInfo(const PbMappingInfo& mapping_info) con
|
|||
|
||||
s << "Supported image file extension to device type mappings:\n";
|
||||
|
||||
const map<string, PbDeviceType, less<>> sorted_mappings = { mapping_info.mapping().begin(), mapping_info.mapping().end() };
|
||||
|
||||
for (const auto& [extension, type] : sorted_mappings) {
|
||||
for (const map<string, PbDeviceType, less<>> sorted_mappings(mapping_info.mapping().begin(), mapping_info.mapping().end());
|
||||
const auto& [extension, type] : sorted_mappings) {
|
||||
s << " " << extension << "->" << PbDeviceType_Name(type) << '\n';
|
||||
}
|
||||
|
||||
|
@ -282,7 +235,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
|
|||
{
|
||||
ostringstream s;
|
||||
|
||||
const map<int, PbOperationMetaData, less<>> operations = { operation_info.operations().begin(), operation_info.operations().end() };
|
||||
const map<int, PbOperationMetaData, less<>> operations(operation_info.operations().begin(), operation_info.operations().end());
|
||||
|
||||
// Copies result into a map sorted by operation name
|
||||
auto unknown_operation = make_unique<PbOperationMetaData>();
|
||||
|
@ -308,7 +261,7 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
|
|||
}
|
||||
s << '\n';
|
||||
|
||||
DisplayParameters(s, meta_data);
|
||||
s << DisplayParameters(meta_data);
|
||||
}
|
||||
else {
|
||||
s << " " << name << " (Unknown server-side operation)\n";
|
||||
|
@ -318,96 +271,82 @@ string ScsictlDisplay::DisplayOperationInfo(const PbOperationInfo& operation_inf
|
|||
return s.str();
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayParams(ostringstream& s, const PbDevice& pb_device) const
|
||||
string ScsictlDisplay::DisplayParams(const PbDevice& pb_device) const
|
||||
{
|
||||
const map<string, string, less<>> sorted_params = { pb_device.params().begin(), pb_device.params().end() };
|
||||
ostringstream s;
|
||||
|
||||
bool isFirst = true;
|
||||
for (const auto& [key, value] : sorted_params) {
|
||||
if (!isFirst) {
|
||||
s << ":";
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
s << key << "=" << value;
|
||||
}
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayAttributes(ostringstream& s, const PbDeviceProperties& properties) const
|
||||
{
|
||||
if (properties.read_only() || properties.protectable() || properties.stoppable() || properties.lockable()) {
|
||||
s << "Properties: ";
|
||||
|
||||
bool has_property = false;
|
||||
|
||||
if (properties.read_only()) {
|
||||
s << "read-only";
|
||||
has_property = true;
|
||||
}
|
||||
|
||||
if (properties.protectable()) {
|
||||
s << (has_property ? ", " : "") << "protectable";
|
||||
has_property = true;
|
||||
}
|
||||
if (properties.stoppable()) {
|
||||
s << (has_property ? ", " : "") << "stoppable";
|
||||
has_property = true;
|
||||
}
|
||||
if (properties.removable()) {
|
||||
s << (has_property ? ", " : "") << "removable";
|
||||
has_property = true;
|
||||
}
|
||||
if (properties.lockable()) {
|
||||
s << (has_property ? ", " : "") << "lockable";
|
||||
}
|
||||
|
||||
s << "\n ";
|
||||
}
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayDefaultParameters(ostringstream& s, const PbDeviceProperties& properties) const
|
||||
{
|
||||
if (properties.supports_params() && properties.default_params_size()) {
|
||||
s << "Default parameters: ";
|
||||
|
||||
const map<string, string, less<>> sorted_params = { properties.default_params().begin(), properties.default_params().end() };
|
||||
|
||||
bool isFirst = true;
|
||||
for (const auto& [key, value] : sorted_params) {
|
||||
if (!isFirst) {
|
||||
s << "\n ";
|
||||
}
|
||||
s << key << "=" << value;
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
set<string, less<>> params;
|
||||
for (const auto& [key, value] : pb_device.params()) {
|
||||
params.insert(key + "=" + value);
|
||||
}
|
||||
|
||||
s << Join(params, ":");
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayBlockSizes(ostringstream& s, const PbDeviceProperties& properties) const
|
||||
string ScsictlDisplay::DisplayAttributes(const PbDeviceProperties& props) const
|
||||
{
|
||||
ostringstream s;
|
||||
|
||||
vector<string> properties;
|
||||
if (props.read_only()) {
|
||||
properties.emplace_back("read-only");
|
||||
}
|
||||
if (props.protectable()) {
|
||||
properties.emplace_back("protectable");
|
||||
}
|
||||
if (props.stoppable()) {
|
||||
properties.emplace_back("stoppable");
|
||||
}
|
||||
if (props.removable()) {
|
||||
properties.emplace_back("removable");
|
||||
}
|
||||
if (props.lockable()) {
|
||||
properties.emplace_back("lockable");
|
||||
}
|
||||
|
||||
if (!properties.empty()) {
|
||||
s << "Properties: " << Join(properties) << "\n ";
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
string ScsictlDisplay::DisplayDefaultParameters(const PbDeviceProperties& properties) const
|
||||
{
|
||||
ostringstream s;
|
||||
|
||||
if (!properties.default_params().empty()) {
|
||||
set<string, less<>> params;
|
||||
for (const auto& [key, value] : properties.default_params()) {
|
||||
params.insert(key + "=" + value);
|
||||
}
|
||||
|
||||
s << "Default parameters: " << Join(params, "\n ");
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
string ScsictlDisplay::DisplayBlockSizes(const PbDeviceProperties& properties) const
|
||||
{
|
||||
ostringstream s;
|
||||
|
||||
if (properties.block_sizes_size()) {
|
||||
s << "Configurable block sizes in bytes: ";
|
||||
|
||||
const set<uint32_t> sorted_sizes = { properties.block_sizes().begin(), properties.block_sizes().end() };
|
||||
|
||||
bool isFirst = true;
|
||||
for (const auto& size : sorted_sizes) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
s << size;
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
const set<uint32_t> sorted_sizes(properties.block_sizes().begin(), properties.block_sizes().end());
|
||||
s << "Configurable block sizes in bytes: " << Join(sorted_sizes);
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaData& meta_data) const
|
||||
string ScsictlDisplay::DisplayParameters(const PbOperationMetaData& meta_data) const
|
||||
{
|
||||
list<PbOperationParameter> sorted_parameters = { meta_data.parameters().begin(), meta_data.parameters().end() };
|
||||
sorted_parameters.sort([](const auto& a, const auto& b) { return a.name() < b.name(); });
|
||||
vector<PbOperationParameter> sorted_parameters(meta_data.parameters().begin(), meta_data.parameters().end());
|
||||
ranges::sort(sorted_parameters, [](const auto& a, const auto& b) { return a.name() < b.name(); });
|
||||
|
||||
ostringstream s;
|
||||
|
||||
for (const auto& parameter : sorted_parameters) {
|
||||
s << " " << parameter.name() << ": "
|
||||
|
@ -418,30 +357,23 @@ void ScsictlDisplay::DisplayParameters(ostringstream& s, const PbOperationMetaDa
|
|||
}
|
||||
s << '\n';
|
||||
|
||||
DisplayPermittedValues(s, parameter);
|
||||
s << DisplayPermittedValues(parameter);
|
||||
|
||||
if (!parameter.default_value().empty()) {
|
||||
s << " Default value: " << parameter.default_value() << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
void ScsictlDisplay::DisplayPermittedValues(ostringstream& s, const PbOperationParameter& parameter) const
|
||||
string ScsictlDisplay::DisplayPermittedValues(const PbOperationParameter& parameter) const
|
||||
{
|
||||
ostringstream s;
|
||||
if (parameter.permitted_values_size()) {
|
||||
s << " Permitted values: ";
|
||||
|
||||
bool isFirst = true;
|
||||
|
||||
for (const auto& permitted_value : parameter.permitted_values()) {
|
||||
if (!isFirst) {
|
||||
s << ", ";
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
s << permitted_value;
|
||||
}
|
||||
|
||||
s << '\n';
|
||||
const set<string, less<>> sorted_values(parameter.permitted_values().begin(), parameter.permitted_values().end());
|
||||
s << " Permitted values: " << Join(parameter.permitted_values()) << '\n';
|
||||
}
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
// Copyright (C) 2021-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
|||
|
||||
#include "generated/piscsi_interface.pb.h"
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace piscsi_interface;
|
||||
|
@ -37,10 +36,10 @@ public:
|
|||
|
||||
private:
|
||||
|
||||
void DisplayParams(ostringstream&, const PbDevice&) const;
|
||||
void DisplayAttributes(ostringstream&, const PbDeviceProperties&) const;
|
||||
void DisplayDefaultParameters(ostringstream&, const PbDeviceProperties&) const;
|
||||
void DisplayBlockSizes(ostringstream&, const PbDeviceProperties&) const;
|
||||
void DisplayParameters(ostringstream&, const PbOperationMetaData&) const;
|
||||
void DisplayPermittedValues(ostringstream&, const PbOperationParameter&) const;
|
||||
string DisplayParams(const PbDevice&) const;
|
||||
string DisplayAttributes(const PbDeviceProperties&) const;
|
||||
string DisplayDefaultParameters(const PbDeviceProperties&) const;
|
||||
string DisplayBlockSizes(const PbDeviceProperties&) const;
|
||||
string DisplayParameters(const PbOperationMetaData&) const;
|
||||
string DisplayPermittedValues(const PbOperationParameter&) const;
|
||||
};
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "scsictl_parser.h"
|
||||
|
||||
PbOperation ScsictlParser::ParseOperation(const string& operation) const
|
||||
PbOperation ScsictlParser::ParseOperation(string_view operation) const
|
||||
{
|
||||
const auto& it = operations.find(tolower(operation[0]));
|
||||
return it != operations.end() ? it->second : NO_OPERATION;
|
||||
|
@ -17,8 +17,8 @@ PbOperation ScsictlParser::ParseOperation(const string& operation) const
|
|||
|
||||
PbDeviceType ScsictlParser::ParseType(const string& type) const
|
||||
{
|
||||
string t = type;
|
||||
transform(t.begin(), t.end(), t.begin(), ::toupper);
|
||||
string t;
|
||||
ranges::transform(type, back_inserter(t), ::toupper);
|
||||
|
||||
if (PbDeviceType parsed_type; PbDeviceType_Parse(t, &parsed_type)) {
|
||||
return parsed_type;
|
||||
|
|
|
@ -22,12 +22,12 @@ public:
|
|||
ScsictlParser() = default;
|
||||
~ScsictlParser() = default;
|
||||
|
||||
PbOperation ParseOperation(const string&) const;
|
||||
PbOperation ParseOperation(string_view) const;
|
||||
PbDeviceType ParseType(const string&) const;
|
||||
|
||||
private:
|
||||
|
||||
unordered_map<int, PbOperation> operations = {
|
||||
const unordered_map<int, PbOperation> operations = {
|
||||
{ 'a', ATTACH },
|
||||
{ 'd', DETACH },
|
||||
{ 'e', EJECT },
|
||||
|
@ -37,7 +37,7 @@ private:
|
|||
{ 'u', UNPROTECT }
|
||||
};
|
||||
|
||||
unordered_map<int, PbDeviceType> device_types = {
|
||||
const unordered_map<int, PbDeviceType> device_types = {
|
||||
{ 'b', SCBR },
|
||||
{ 'c', SCCD },
|
||||
{ 'd', SCDP },
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -13,7 +13,7 @@ using namespace std;
|
|||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const vector<char *> args(argv, argv + argc);
|
||||
vector<char *> args(argv, argv + argc);
|
||||
|
||||
return ScsiDump().run(args);
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
//
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022 akuker
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -17,10 +17,11 @@
|
|||
#include "hal/gpiobus.h"
|
||||
#include "hal/gpiobus_factory.h"
|
||||
#include "hal/systimer.h"
|
||||
#include "shared/log.h"
|
||||
#include "controllers/controller_manager.h"
|
||||
#include "shared/piscsi_exceptions.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <filesystem>
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <cstddef>
|
||||
|
@ -28,10 +29,10 @@
|
|||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
using namespace spdlog;
|
||||
using namespace scsi_defs;
|
||||
using namespace piscsi_util;
|
||||
|
@ -50,17 +51,18 @@ void ScsiDump::KillHandler(int)
|
|||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
bool ScsiDump::Banner(const vector<char*>& args) const
|
||||
bool ScsiDump::Banner(span<char *> args) const
|
||||
{
|
||||
cout << piscsi_util::Banner("(Hard Disk Dump/Restore Utility)");
|
||||
|
||||
if (args.size() < 2 || string(args[1]) == "-h" || string(args[1]) == "--help") {
|
||||
cout << "Usage: " << args[0] << " -t ID[:LUN] [-i BID] -f FILE [-v] [-r] [-s BUFFER_SIZE] [-p]\n"
|
||||
<< " ID is the target device ID (0-7).\n"
|
||||
<< " LUN is the optional target device LUN (0-7). Default is 0.\n"
|
||||
<< " ID is the target device ID (0-" << (ControllerManager::GetScsiIdMax() - 1) << ").\n"
|
||||
<< " LUN is the optional target device LUN (0-" << (ControllerManager::GetScsiLunMax() -1 ) << ")."
|
||||
<< " Default is 0.\n"
|
||||
<< " BID is the PiSCSI board ID (0-7). Default is 7.\n"
|
||||
<< " FILE is the dump file path.\n"
|
||||
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << to_string(MINIMUM_BUFFER_SIZE)
|
||||
<< " BUFFER_SIZE is the transfer buffer size in bytes, at least " << MINIMUM_BUFFER_SIZE
|
||||
<< " bytes. Default is 1 MiB.\n"
|
||||
<< " -v Enable verbose logging.\n"
|
||||
<< " -r Restore instead of dump.\n"
|
||||
|
@ -86,7 +88,7 @@ bool ScsiDump::Init() const
|
|||
return bus != nullptr;
|
||||
}
|
||||
|
||||
void ScsiDump::ParseArguments(const vector<char*>& args)
|
||||
void ScsiDump::ParseArguments(span<char *> args)
|
||||
{
|
||||
int opt;
|
||||
|
||||
|
@ -107,13 +109,13 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
|
|||
|
||||
case 's':
|
||||
if (!GetAsUnsignedInt(optarg, buffer_size) || buffer_size < MINIMUM_BUFFER_SIZE) {
|
||||
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + "KiB");
|
||||
throw parser_exception("Buffer size must be at least " + to_string(MINIMUM_BUFFER_SIZE / 1024) + " KiB");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 't': {
|
||||
const string error = ProcessId(optarg, 8, target_id, target_lun);
|
||||
const string error = ProcessId(optarg, target_id, target_lun);
|
||||
if (!error.empty()) {
|
||||
throw parser_exception(error);
|
||||
}
|
||||
|
@ -149,7 +151,7 @@ void ScsiDump::ParseArguments(const vector<char*>& args)
|
|||
|
||||
void ScsiDump::WaitPhase(phase_t phase) const
|
||||
{
|
||||
LOGDEBUG("Waiting for %s phase", BUS::GetPhaseStrRaw(phase))
|
||||
spdlog::debug(string("Waiting for ") + BUS::GetPhaseStrRaw(phase) + " phase");
|
||||
|
||||
// Timeout (3000ms)
|
||||
const uint32_t now = SysTimer::GetTimerLow();
|
||||
|
@ -180,7 +182,7 @@ void ScsiDump::Selection() const
|
|||
|
||||
void ScsiDump::Command(scsi_command cmd, vector<uint8_t>& cdb) const
|
||||
{
|
||||
LOGDEBUG("Executing %s", command_mapping.find(cmd)->second.second)
|
||||
spdlog::debug("Executing " + command_mapping.find(cmd)->second.second);
|
||||
|
||||
Selection();
|
||||
|
||||
|
@ -324,7 +326,7 @@ pair<uint64_t, uint32_t> ScsiDump::ReadCapacity()
|
|||
(static_cast<uint32_t>(buffer[sector_size_offset + 2]) << 8) |
|
||||
static_cast<uint32_t>(buffer[sector_size_offset + 3]);
|
||||
|
||||
return make_pair(capacity, sector_size);
|
||||
return { capacity, sector_size };
|
||||
}
|
||||
|
||||
void ScsiDump::Read10(uint32_t bstart, uint32_t blength, uint32_t length)
|
||||
|
@ -387,7 +389,7 @@ void ScsiDump::WaitForBusy() const
|
|||
}
|
||||
}
|
||||
|
||||
int ScsiDump::run(const vector<char*>& args)
|
||||
int ScsiDump::run(span<char *> args)
|
||||
{
|
||||
if (!Banner(args)) {
|
||||
return EXIT_SUCCESS;
|
||||
|
@ -436,12 +438,12 @@ int ScsiDump::DumpRestore()
|
|||
if (restore) {
|
||||
cout << "Starting restore\n" << flush;
|
||||
|
||||
// filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handle more than 2 GiB
|
||||
off_t size;
|
||||
if (struct stat st; !stat(filename.c_str(), &st)) {
|
||||
size = st.st_size;
|
||||
} else {
|
||||
throw parser_exception("Can't determine file size");
|
||||
try {
|
||||
size = file_size(path(filename));
|
||||
}
|
||||
catch (const filesystem_error& e) {
|
||||
throw parser_exception(string("Can't determine file size: ") + e.what());
|
||||
}
|
||||
|
||||
cout << "Restore file size: " << size << " bytes\n";
|
||||
|
@ -455,11 +457,11 @@ int ScsiDump::DumpRestore()
|
|||
}
|
||||
|
||||
// Dump by buffer size
|
||||
auto dsiz = static_cast<int>(buffer.size());
|
||||
auto dsiz = static_cast<int>(buffer.size());
|
||||
const int duni = dsiz / inq_info.sector_size;
|
||||
auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
|
||||
auto dnum = static_cast<int>((inq_info.capacity * inq_info.sector_size) / dsiz);
|
||||
|
||||
auto start_time = chrono::high_resolution_clock::now();
|
||||
const auto start_time = chrono::high_resolution_clock::now();
|
||||
|
||||
int i;
|
||||
for (i = 0; i < dnum; i++) {
|
||||
|
@ -501,16 +503,16 @@ int ScsiDump::DumpRestore()
|
|||
cout << "100% (" << inq_info.capacity << "/" << inq_info.capacity << ")\n" << flush;
|
||||
}
|
||||
|
||||
auto stop_time = chrono::high_resolution_clock::now();
|
||||
const auto stop_time = chrono::high_resolution_clock::now();
|
||||
|
||||
auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
|
||||
const auto duration = chrono::duration_cast<chrono::seconds>(stop_time - start_time).count();
|
||||
|
||||
cout << divider_str << "\n";
|
||||
cout << "Transfered : " << to_string(inq_info.capacity * inq_info.sector_size) << " bytes ["
|
||||
<< to_string(inq_info.capacity * inq_info.sector_size / 1024 / 1024) << "MiB]\n";
|
||||
cout << "Total time: " << to_string(duration) << " seconds (" << to_string(duration / 60) << " minutes\n";
|
||||
cout << "Averate transfer rate: " << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration)
|
||||
<< " bytes per second (" << to_string((inq_info.capacity * inq_info.sector_size / 8) / duration / 1024)
|
||||
cout << "Transfered : " << inq_info.capacity * inq_info.sector_size << " bytes ["
|
||||
<< inq_info.capacity * inq_info.sector_size / 1024 / 1024 << "MiB]\n";
|
||||
cout << "Total time: " << duration << " seconds (" << duration / 60 << " minutes\n";
|
||||
cout << "Averate transfer rate: " << (inq_info.capacity * inq_info.sector_size / 8) / duration
|
||||
<< " bytes per second (" << (inq_info.capacity * inq_info.sector_size / 8) / duration / 1024
|
||||
<< " KiB per second)\n";
|
||||
cout << divider_str << "\n";
|
||||
|
||||
|
@ -554,8 +556,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
|
|||
cout << "Revision: " << str.data() << "\n" << flush;
|
||||
inq_info.revision = string(str.data());
|
||||
|
||||
if (auto type = static_cast<device_type>(buffer[0]);
|
||||
type != device_type::DIRECT_ACCESS && type != device_type::CD_ROM && type != device_type::OPTICAL_MEMORY) {
|
||||
if (const auto type = static_cast<device_type>(buffer[0]);
|
||||
type != device_type::direct_access && type != device_type::cd_rom && type != device_type::optical_memory) {
|
||||
throw parser_exception("Invalid device type, supported types are DIRECT ACCESS, CD-ROM and OPTICAL MEMORY");
|
||||
}
|
||||
|
||||
|
@ -564,8 +566,8 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
|
|||
RequestSense();
|
||||
|
||||
const auto [capacity, sector_size] = ReadCapacity();
|
||||
inq_info.capacity = capacity;
|
||||
inq_info.sector_size = sector_size;
|
||||
inq_info.capacity = capacity;
|
||||
inq_info.sector_size = sector_size;
|
||||
|
||||
cout << "Sectors: " << capacity << "\n"
|
||||
<< "Sector size: " << sector_size << " bytes\n"
|
||||
|
@ -579,24 +581,23 @@ ScsiDump::inquiry_info_t ScsiDump::GetDeviceInfo()
|
|||
|
||||
void ScsiDump::GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info)
|
||||
{
|
||||
string prop_filename = filename + ".properties";
|
||||
string prop_str;
|
||||
stringstream prop_stream(prop_str);
|
||||
const string prop_filename = filename + ".properties";
|
||||
stringstream prop_stream;
|
||||
|
||||
prop_stream << "{" << endl;
|
||||
prop_stream << " \"vendor\": \"" << inq_info.vendor << "\"," << endl;
|
||||
prop_stream << " \"product\": \"" << inq_info.product << "\"," << endl;
|
||||
prop_stream << " \"revision\": \"" << inq_info.revision << "\"," << endl;
|
||||
prop_stream << " \"block_size\": \"" << to_string(inq_info.sector_size) << "\"," << endl;
|
||||
prop_stream << " \"block_size\": \"" << inq_info.sector_size << "\"," << endl;
|
||||
prop_stream << "}" << endl;
|
||||
|
||||
FILE* fp = fopen(prop_filename.c_str(), "w");
|
||||
if (fp) {
|
||||
fputs(prop_stream.str().c_str(), fp);
|
||||
} else {
|
||||
LOGWARN("Unable to open output file %s", prop_filename.c_str())
|
||||
spdlog::warn("Unable to open output file '" + prop_filename + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
// SCSI Target Emulator PiSCSI
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
// Copyright (C) 2022-2023 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -13,6 +13,7 @@
|
|||
#include "shared/scsi.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
@ -26,7 +27,7 @@ class ScsiDump
|
|||
ScsiDump() = default;
|
||||
~ScsiDump() = default;
|
||||
|
||||
int run(const vector<char*>&);
|
||||
int run(const span<char *>);
|
||||
|
||||
struct inquiry_info_struct {
|
||||
string vendor;
|
||||
|
@ -42,9 +43,9 @@ class ScsiDump
|
|||
static void GeneratePropertiesFile(const string& filename, const inquiry_info_t& inq_info);
|
||||
|
||||
private:
|
||||
bool Banner(const vector<char*>&) const;
|
||||
bool Banner(span<char *>) const;
|
||||
bool Init() const;
|
||||
void ParseArguments(const vector<char*>&);
|
||||
void ParseArguments(span<char *>);
|
||||
int DumpRestore();
|
||||
inquiry_info_t GetDeviceInfo();
|
||||
void WaitPhase(phase_t) const;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "shared/log.h"
|
||||
#include "hal/log.h"
|
||||
#include "shared/piscsi_version.h"
|
||||
#include "shared/piscsi_util.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
|
|
|
@ -30,11 +30,11 @@ void ScsiLoop_Cout::FinishTest(const string &test_name, int failures)
|
|||
}
|
||||
}
|
||||
|
||||
void ScsiLoop_Cout::PrintErrors(vector<string> &test_errors)
|
||||
void ScsiLoop_Cout::PrintErrors(const vector<string> &test_errors)
|
||||
{
|
||||
if (test_errors.size() > 0) {
|
||||
for (auto err_string : test_errors) {
|
||||
if (!test_errors.empty()) {
|
||||
for (auto& err_string : test_errors) {
|
||||
cout << RED << err_string << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue