Moved rascsi/rasctl specific classes to sub-folders, cleaned up code, fixed SonarCloud issues (#889)

* Moved rasctl/rascsi core code to folders

* Improved granularity in order to add more unit tests

* Pointer handling update

* Updated ID and controller handling

* Updated memory management

* Added unit tests

* Fixed SonarCloud issues
This commit is contained in:
Uwe Seimet 2022-10-06 16:15:19 +02:00 committed by GitHub
parent 52259c374f
commit a30438279e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 453 additions and 399 deletions

View File

@ -90,20 +90,15 @@ SRC_RASCSI_CORE = \
filepath.cpp \
fileio.cpp \
rascsi_version.cpp \
rascsi_image.cpp \
rascsi_response.cpp \
rascsi_executor.cpp \
rasutil.cpp \
command_util.cpp \
protobuf_serializer.cpp \
localizer.cpp
protobuf_serializer.cpp
SRC_RASCSI_CORE += $(shell find ./rascsi -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp')
SRC_RASCSI_CORE += $(SRC_PROTOBUF)
SRC_RASCSI = rascsi.cpp \
rascsi_service.cpp
SRC_RASCSI = rascsi.cpp
SRC_SCSIMON = \
scsimon.cpp \
@ -114,14 +109,11 @@ SRC_SCSIMON += $(shell find ./hal -name '*.cpp')
SRC_RASCTL = \
rasctl.cpp\
rasctl_commands.cpp \
rasctl_display.cpp \
rascsi_version.cpp \
rasutil.cpp \
command_util.cpp \
protobuf_serializer.cpp \
localizer.cpp
SRC_RASCTL += $(SRC_PROTOBUF)
protobuf_serializer.cpp
SRC_RASCTL += $(shell find ./rasctl -name '*.cpp')
SRC_RASDUMP = \
rasdump.cpp \
@ -134,8 +126,8 @@ SRC_RASDUMP += $(shell find ./hal -name '*.cpp')
SRC_RASCSI_TEST = $(shell find ./test -name '*.cpp')
vpath %.h ./ ./controllers ./devices ./monitor ./hal
vpath %.cpp ./ ./controllers ./devices ./monitor ./test ./hal
vpath %.h ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl
vpath %.cpp ./ ./controllers ./devices ./monitor ./test ./hal ./rascsi ./rasctl
vpath %.o ./$(OBJDIR)
vpath ./$(BINDIR)
@ -146,6 +138,7 @@ OBJ_RASCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL:%.cpp=%.o)))
OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o)))
OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o)))
OBJ_RASCSI_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI_TEST:%.cpp=%.o)))
OBJ_PROTOBUF := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PROTOBUF:%.cpp=%.o)))
GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h
@ -193,11 +186,13 @@ lcov: test
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) -lpthread -lpcap -lprotobuf -lstdc++fs
$(SRC_RASCSI_CORE): $(SRC_PROTOBUF)
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lpthread -lprotobuf
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_PROTOBUF) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) $(OBJ_PROTOBUF) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) $(OBJ_PROTOBUF) -lpthread -lprotobuf
$(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASDUMP)
@ -205,8 +200,8 @@ $(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) | $(BINDIR)
$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_SCSIMON) -lpthread
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) $(OBJ_PROTOBUF) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
# Phony rules for building individual utilities

View File

@ -1,29 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include <string>
class ProtobufSerializer;
class Localizer;
class CommandContext
{
public:
CommandContext(const ProtobufSerializer& c, const Localizer& l, int f, const std::string& s)
: serializer(c), localizer(l), fd(f), locale(s) {}
~CommandContext() = default;
const ProtobufSerializer& serializer;
const Localizer& localizer;
int fd;
std::string locale;
};

View File

@ -9,7 +9,6 @@
#include "log.h"
#include "rascsi_interface.pb.h"
#include "localizer.h"
#include "protobuf_serializer.h"
#include "command_util.h"
#include <sstream>
@ -86,51 +85,3 @@ void command_util::AddParam(PbDeviceDefinition& device, const string& key, strin
map[key] = value;
}
}
bool command_util::ReturnLocalizedError(const CommandContext& context, LocalizationKey key,
const string& arg1, const string& arg2, const string& arg3)
{
return ReturnLocalizedError(context, key, NO_ERROR_CODE, arg1, arg2, arg3);
}
bool command_util::ReturnLocalizedError(const CommandContext& context, LocalizationKey key,
PbErrorCode error_code, const string& arg1, const string& arg2, const string& arg3)
{
// For the logfile always use English
LOGERROR("%s", context.localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
return ReturnStatus(context, false, context.localizer.Localize(key, context.locale, arg1, arg2, arg3), error_code,
false);
}
bool command_util::ReturnStatus(const CommandContext& context, bool status, const string& msg,
PbErrorCode error_code, bool log)
{
// 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())
}
if (context.fd == -1) {
if (!msg.empty()) {
if (status) {
FPRT(stderr, "Error: ");
FPRT(stderr, "%s", msg.c_str());
FPRT(stderr, "\n");
}
else {
FPRT(stdout, "%s", msg.c_str());
FPRT(stderr, "\n");
}
}
}
else {
PbResult result;
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
context.serializer.SerializeMessage(context.fd, result);
}
return status;
}

View File

@ -13,10 +13,9 @@
#include "google/protobuf/message.h"
#include "rascsi_interface.pb.h"
#include "command_context.h"
#include "localizer.h"
#include <string>
using namespace std;
using namespace rascsi_interface;
namespace command_util
@ -27,10 +26,4 @@ namespace command_util
void AddParam(PbCommand&, const string&, string_view);
void AddParam(PbDevice&, const string&, string_view);
void AddParam(PbDeviceDefinition&, const string&, string_view);
bool ReturnLocalizedError(const CommandContext&, LocalizationKey, const string& = "", const string& = "",
const string& = "");
bool ReturnLocalizedError(const CommandContext&, LocalizationKey, PbErrorCode, const string& = "",
const string& = "", const string& = "");
bool ReturnStatus(const CommandContext&, bool = true, const string& = "",
PbErrorCode = PbErrorCode::NO_ERROR_CODE, bool = true);
}

View File

@ -245,7 +245,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
else {
string address = inet;
string netmask = "255.255.255.0";
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);
@ -447,12 +447,12 @@ bool CTapDriver::PendingPackets() const
}
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
uint32_t crc32(const BYTE *buf, int length) {
uint32_t CTapDriver::Crc32(const BYTE *buf, int length) {
uint32_t crc = 0xffffffff;
for (int i = 0; i < length; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
uint32_t mask = -(crc & 1);
uint32_t mask = -((int)crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
@ -480,7 +480,7 @@ int CTapDriver::Receive(BYTE *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.
int crc = crc32(buf, dwReceived);
int crc = Crc32(buf, dwReceived);
buf[dwReceived + 0] = (BYTE)((crc >> 0) & 0xFF);
buf[dwReceived + 1] = (BYTE)((crc >> 8) & 0xFF);

View File

@ -46,6 +46,8 @@ public:
bool Disable() const; // Disable the ras0 interface
void Flush(); // Purge all of the packets that are waiting to be processed
static uint32_t Crc32(const BYTE *, int);
private:
array<byte, 6> m_MacAddr; // MAC Address

View File

@ -41,9 +41,9 @@ DeviceFactory::DeviceFactory()
}
default_params[SCBR]["interface"] = network_interfaces;
default_params[SCBR]["inet"] = "10.10.20.1/24";
default_params[SCBR]["inet"] = DEFAULT_IP;
default_params[SCDP]["interface"] = network_interfaces;
default_params[SCDP]["inet"] = "10.10.20.1/24";
default_params[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f";
default_params[SCLP]["timeout"] = "30";

View File

@ -11,11 +11,10 @@
#pragma once
#include <string>
#include <list>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include "rascsi_interface.pb.h"
using namespace std;
@ -26,6 +25,8 @@ class PrimaryDevice;
class DeviceFactory
{
const string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe
public:
DeviceFactory();
@ -41,14 +42,14 @@ public:
private:
string GetExtension(const string&) const;
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
unordered_map<PbDeviceType, unordered_map<string, string>> default_params;
unordered_map<string, PbDeviceType> extension_mapping;
string GetExtension(const string&) const;
unordered_set<uint32_t> empty_set;
unordered_map<string, string> empty_map;
};

View File

@ -23,10 +23,8 @@
using namespace scsi_command_util;
SCSIHD::SCSIHD(int lun, const unordered_set<uint32_t>& sector_sizes, bool removable, scsi_defs::scsi_level level)
: Disk(removable ? "SCRM" : "SCHD", lun)
: Disk(removable ? "SCRM" : "SCHD", lun), scsi_level(level)
{
scsi_level = level;
SetSectorSizes(sector_sizes);
}

View File

@ -14,11 +14,6 @@
#include "fileio.h"
#include <libgen.h>
Filepath::Filepath()
{
Clear();
}
Filepath& Filepath::operator=(const Filepath& path)
{
// Set path (split internally)
@ -27,15 +22,6 @@ Filepath& Filepath::operator=(const Filepath& path)
return *this;
}
void Filepath::Clear()
{
// Clear the path and each part
m_szPath[0] = '\0';
m_szDir[0] = '\0';
m_szFile[0] = '\0';
m_szExt[0] = '\0';
}
//---------------------------------------------------------------------------
//
// File settings (user) for MBCS

View File

@ -14,8 +14,6 @@
using TCHAR = char;
class Fileio;
static const int _MAX_EXT = 256;
static const int _MAX_DIR = 256;
static const int _MAX_PATH = 260;
@ -30,15 +28,13 @@ static const int FILEPATH_MAX = _MAX_PATH;
//===========================================================================
class Filepath
{
public:
Filepath();
virtual ~Filepath() = default;
Filepath() = default;
~Filepath() = default;
Filepath(Filepath&) = default;
Filepath& operator=(const Filepath&);
void Clear();
void SetPath(const char *); // File settings (user) for MBCS
const char *GetPath() const { return m_szPath; } // Get path name
const char *GetFileExt() const; // Get short name (LPCTSTR)
@ -46,10 +42,10 @@ public:
private:
void Split(); // Split the path
TCHAR m_szPath[_MAX_PATH]; // File path
TCHAR m_szDir[_MAX_DIR]; // Directory
TCHAR m_szFile[_MAX_FNAME]; // File
TCHAR m_szExt[_MAX_EXT]; // Extension
TCHAR m_szPath[_MAX_PATH] = {}; // File path
TCHAR m_szDir[_MAX_DIR] = {}; // Directory
TCHAR m_szFile[_MAX_FNAME] = {}; // File
TCHAR m_szExt[_MAX_EXT] = {}; // Extension
static TCHAR FileExt[_MAX_FNAME + _MAX_DIR]; // Short name (TCHAR)
};

View File

@ -8,8 +8,8 @@
//---------------------------------------------------------------------------
#include "rascsi_interface.pb.h"
#include "rascsi_exceptions.h"
#include "protobuf_serializer.h"
#include "rascsi_exceptions.h"
#include <unistd.h>
#include <sstream>

View File

@ -14,6 +14,8 @@
#include "google/protobuf/message.h"
#include <vector>
using namespace std;
class ProtobufSerializer
{
public:

View File

@ -11,11 +11,10 @@
//---------------------------------------------------------------------------
#include "config.h"
#include "log.h"
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "devices/device_factory.h"
#include "devices/device.h"
#include "devices/disk.h"
#include "devices/file_support.h"
#include "hal/gpiobus.h"
#include "hal/systimer.h"
@ -23,11 +22,11 @@
#include "protobuf_serializer.h"
#include "command_util.h"
#include "rascsi_version.h"
#include "rascsi_executor.h"
#include "rascsi_response.h"
#include "rascsi_image.h"
#include "rascsi_interface.pb.h"
#include "rascsi_service.h"
#include "rascsi/rascsi_executor.h"
#include "rascsi/rascsi_response.h"
#include "rascsi/rascsi_image.h"
#include "rascsi/rascsi_service.h"
#include "rasutil.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
@ -51,7 +50,6 @@ using namespace command_util;
// Constant declarations
//
//---------------------------------------------------------------------------
#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ )
static const int DEFAULT_PORT = 6868;
static const char COMPONENT_SEPARATOR = ':';
@ -74,32 +72,28 @@ const ProtobufSerializer serializer;
void Banner(int argc, char* argv[])
{
FPRT(stdout,"SCSI Target Emulator RaSCSI Reloaded ");
FPRT(stdout,"version %s (%s, %s)\n",
rascsi_get_version_string().c_str(),
__DATE__,
__TIME__);
FPRT(stdout,"Powered by XM6 TypeG Technology / ");
FPRT(stdout,"Copyright (C) 2016-2020 GIMONS\n");
FPRT(stdout,"Copyright (C) 2020-2022 Contributors to the RaSCSI Reloaded project\n");
FPRT(stdout,"Connect type: %s\n", CONNECT_DESC.c_str());
cout << "SCSI Target Emulator RaSCSI Reloaded version " << rascsi_get_version_string()
<< " (" << __DATE__ << ' ' << __TIME__ << ')' << endl;
cout << "Powered by XM6 TypeG Technology / ";
cout << "Copyright (C) 2016-2020 GIMONS" << endl;
cout << "Copyright (C) 2020-2022 Contributors to the RaSCSI Reloaded project" << endl;
cout << "Connect type: " << CONNECT_DESC << endl;
if ((argc > 1 && strcmp(argv[1], "-h") == 0) ||
(argc > 1 && strcmp(argv[1], "--help") == 0)){
FPRT(stdout,"\n");
FPRT(stdout,"Usage: %s [-idn[:m] FILE] ...\n\n", argv[0]);
FPRT(stdout," n is SCSI device ID (0-7).\n");
FPRT(stdout," m is the optional logical unit (LUN) (0-31).\n");
FPRT(stdout," FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\".\n\n");
FPRT(stdout," Image type is detected based on file extension if no explicit type is specified.\n");
FPRT(stdout," hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n");
FPRT(stdout," hds : SCSI HD image (Non-removable generic SCSI HD image)\n");
FPRT(stdout," hdr : SCSI HD image (Removable generic HD image)\n");
FPRT(stdout," hdn : SCSI HD image (NEC GENUINE)\n");
FPRT(stdout," hdi : SCSI HD image (Anex86 HD image)\n");
FPRT(stdout," nhd : SCSI HD image (T98Next HD image)\n");
FPRT(stdout," mos : SCSI MO image (MO image)\n");
FPRT(stdout," iso : SCSI CD image (ISO 9660 image)\n");
if ((argc > 1 && strcmp(argv[1], "-h") == 0) || (argc > 1 && strcmp(argv[1], "--help") == 0)){
cout << endl;
cout << "Usage: " << argv[0] << " [-idn[:m] FILE] ..." << endl << endl;
cout << " n is SCSI device ID (0-7)." << endl;
cout << " m is the optional logical unit (LUN) (0-31)." << endl;
cout << " FILE is a disk image file, \"daynaport\", \"bridge\", \"printer\" or \"services\"." << endl << endl;
cout << " Image type is detected based on file extension if no explicit type is specified." << endl;
cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)" << endl;
cout << " hds : SCSI HD image (Non-removable generic SCSI HD image)" << endl;
cout << " hdr : SCSI HD image (Removable generic HD image)" << endl;
cout << " hdn : SCSI HD image (NEC GENUINE)" << endl;
cout << " hdi : SCSI HD image (Anex86 HD image)" << endl;
cout << " nhd : SCSI HD image (T98Next HD image)" << endl;
cout << " mos : SCSI MO image (MO image)" << endl;
cout << " iso : SCSI CD image (ISO 9660 image)" << endl;
exit(EXIT_SUCCESS);
}
@ -122,6 +116,8 @@ void Cleanup()
{
executor.DetachAll();
service.Cleanup();
// Clean up and discard the bus
bus.Cleanup();
}
@ -359,9 +355,7 @@ bool ParseArgument(int argc, char* argv[], int& port)
// Attach all specified devices
command.set_operation(ATTACH);
Localizer localizer;
CommandContext context(serializer, localizer, -1, locale);
if (!executor.ProcessCmd(context, command)) {
if (CommandContext context(locale); !executor.ProcessCmd(context, command)) {
return false;
}
@ -376,21 +370,16 @@ bool ParseArgument(int argc, char* argv[], int& port)
return true;
}
static bool ExecuteCommand(PbCommand& command, CommandContext& context)
static bool ExecuteCommand(const CommandContext& context, PbCommand& command)
{
context.locale = GetParam(command, "locale");
if (context.locale.empty()) {
context.locale = "en";
}
if (!access_token.empty() && access_token != GetParam(command, "token")) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
return context.ReturnLocalizedError(LocalizationKey::ERROR_AUTHENTICATION, UNAUTHORIZED);
}
if (!PbOperation_IsValid(command.operation())) {
LOGERROR("Received unknown command with operation opcode %d", command.operation())
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION, UNKNOWN_OPERATION);
}
LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str())
@ -401,35 +390,35 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
case LOG_LEVEL: {
string log_level = GetParam(command, "level");
if (bool status = executor.SetLogLevel(log_level); !status) {
ReturnLocalizedError(context, LocalizationKey::ERROR_LOG_LEVEL, log_level);
context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL, log_level);
}
else {
current_log_level = log_level;
ReturnStatus(context);
context.ReturnStatus();
}
break;
}
case DEFAULT_FOLDER: {
if (string status = rascsi_image.SetDefaultFolder(GetParam(command, "folder")); !status.empty()) {
ReturnStatus(context, false, status);
context.ReturnStatus(false, status);
}
else {
ReturnStatus(context);
context.ReturnStatus();
}
break;
}
case DEVICES_INFO: {
rascsi_response.GetDevicesInfo(result, command, rascsi_image.GetDefaultFolder());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case DEVICE_TYPES_INFO: {
result.set_allocated_device_types_info(rascsi_response.GetDeviceTypesInfo(result).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
@ -438,19 +427,19 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
result, executor.GetReservedIds(), current_log_level, rascsi_image.GetDefaultFolder(),
GetParam(command, "folder_pattern"), GetParam(command, "file_pattern"),
rascsi_image.GetDepth()).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case VERSION_INFO: {
result.set_allocated_version_info(rascsi_response.GetVersionInfo(result).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case LOG_LEVEL_INFO: {
result.set_allocated_log_level_info(rascsi_response.GetLogLevelInfo(result, current_log_level).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
@ -458,13 +447,13 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result,
rascsi_image.GetDefaultFolder(), GetParam(command, "folder_pattern"),
GetParam(command, "file_pattern"), rascsi_image.GetDepth()).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case IMAGE_FILE_INFO: {
if (string filename = GetParam(command, "file"); filename.empty()) {
ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME);
context.ReturnLocalizedError( LocalizationKey::ERROR_MISSING_FILENAME);
}
else {
auto image_file = make_unique<PbImageFile>();
@ -472,10 +461,10 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
if (status) {
result.set_status(true);
result.set_allocated_image_file_info(image_file.get());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
}
else {
ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_FILE_INFO);
context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_FILE_INFO);
}
}
break;
@ -483,27 +472,27 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
case NETWORK_INTERFACES_INFO: {
result.set_allocated_network_interfaces_info(rascsi_response.GetNetworkInterfacesInfo(result).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case MAPPING_INFO: {
result.set_allocated_mapping_info(rascsi_response.GetMappingInfo(result).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case OPERATION_INFO: {
result.set_allocated_operation_info(rascsi_response.GetOperationInfo(result,
rascsi_image.GetDepth()).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
case RESERVED_IDS_INFO: {
result.set_allocated_reserved_ids_info(rascsi_response.GetReservedIds(result,
executor.GetReservedIds()).release());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
break;
}
@ -516,8 +505,9 @@ static bool ExecuteCommand(PbCommand& command, CommandContext& context)
default: {
// Wait until we become idle
timespec ts = { .tv_sec = 0, .tv_nsec = 500 * 1000 * 1000};
while (active) {
usleep(500 * 1000);
nanosleep(&ts, nullptr);
}
executor.ProcessCmd(context, command);
@ -609,7 +599,8 @@ int main(int argc, char* argv[])
#else
bus.Acquire();
if (!bus.GetSEL()) {
usleep(0);
timespec ts = { .tv_sec = 0, .tv_nsec = 0};
nanosleep(&ts, nullptr);
continue;
}
#endif

View File

@ -0,0 +1,66 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "log.h"
#include "rascsi_interface.pb.h"
#include "command_context.h"
#include <iostream>
using namespace std;
using namespace rascsi_interface;
void CommandContext::Cleanup() const
{
if (fd != -1) {
close(fd);
}
}
bool CommandContext::ReturnLocalizedError(LocalizationKey key, const string& arg1, const string& arg2,
const string& arg3) const
{
return ReturnLocalizedError(key, NO_ERROR_CODE, arg1, arg2, arg3);
}
bool CommandContext::ReturnLocalizedError(LocalizationKey key, PbErrorCode error_code, const string& arg1,
const string& arg2, const string& arg3) const
{
// For the logfile always use English
LOGERROR("%s", localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
return ReturnStatus(false, localizer.Localize(key, locale, arg1, arg2, arg3), error_code, false);
}
bool CommandContext::ReturnStatus(bool status, const string& msg, PbErrorCode error_code, bool log) const
{
// 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())
}
if (fd == -1) {
if (!msg.empty()) {
if (status) {
cerr << "Error: " << msg << endl;
}
else {
cout << msg << endl;
}
}
}
else {
PbResult result;
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
serializer.SerializeMessage(fd, result);
}
return status;
}

View File

@ -0,0 +1,45 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "rascsi_interface.pb.h"
#include "localizer.h"
#include "protobuf_serializer.h"
#include <string>
using namespace std;
using namespace rascsi_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() = default;
void Cleanup() const;
const ProtobufSerializer& GetSerializer() const { return serializer; }
int GetFd() const { return fd; }
void SetFd(int f) { fd = f; }
bool IsValid() const { return fd != -1; }
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;
};

View File

@ -20,6 +20,7 @@
#include "rascsi_exceptions.h"
#include "localizer.h"
#include "command_util.h"
#include "command_context.h"
#include "rasutil.h"
#include "spdlog/spdlog.h"
#include "rascsi_executor.h"
@ -87,7 +88,7 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbDeviceDef
break;
default:
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION);
}
return true;
@ -98,15 +99,15 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand&
switch (command.operation()) {
case DETACH_ALL:
DetachAll();
return ReturnStatus(context);
return context.ReturnStatus();
case RESERVE_IDS: {
const string ids = GetParam(command, "ids");
if (string error = SetReservedIds(ids); !error.empty()) {
return ReturnStatus(context, false, error);
return context.ReturnStatus(false, error);
}
return ReturnStatus(context);
return context.ReturnStatus();
}
case CREATE_IMAGE:
@ -144,7 +145,7 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand&
FileSupport::SetReservedFiles(reserved_files);
if (string result = ValidateLunSetup(command); !result.empty()) {
return ReturnStatus(context, false, result);
return context.ReturnStatus(false, result);
}
for (const auto& device : command.devices()) {
@ -154,16 +155,16 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand&
}
// ATTACH and DETACH return the device list
if (context.fd != -1 && (command.operation() == ATTACH || command.operation() == DETACH)) {
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;
rascsi_response.GetDevicesInfo(result, cmd, rascsi_image.GetDefaultFolder());
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
return true;
}
return ReturnStatus(context);
return context.ReturnStatus();
}
bool RascsiExecutor::SetLogLevel(const string& log_level) const
@ -271,15 +272,15 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
const PbDeviceType type = pb_device.type();
if (lun >= ScsiController::LUN_MAX) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX));
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) != nullptr) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun));
return context.ReturnLocalizedError(LocalizationKey::ERROR_DUPLICATE_ID, to_string(id), to_string(lun));
}
if (reserved_ids.find(id) != reserved_ids.end()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_RESERVED_ID, to_string(id));
return context.ReturnLocalizedError(LocalizationKey::ERROR_RESERVED_ID, to_string(id));
}
string filename = GetParam(pb_device, "file");
@ -305,7 +306,7 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
if (file_support != nullptr) {
// File check (type is HD, for removable media drives, CD and MO the medium (=file) may be inserted later
if (!device->IsRemovable() && filename.empty()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type));
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type));
}
if (!ValidateImageFile(context, device, filename, full_path)) {
@ -330,12 +331,12 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit
}
if (!device->Init(params)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(type),
return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(type),
to_string(id), to_string(lun));
}
if (!controller_manager.AttachToScsiController(id, device)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_SCSI_CONTROLLER);
return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER);
}
Filepath filepath;
@ -359,16 +360,16 @@ bool RascsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit
shared_ptr<PrimaryDevice> device, bool dryRun) const
{
if (!device->IsRemoved()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_EJECT_REQUIRED);
return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED);
}
if (!pb_device.vendor().empty() || !pb_device.product().empty() || !pb_device.revision().empty()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_DEVICE_NAME_UPDATE);
return context.ReturnLocalizedError(LocalizationKey::ERROR_DEVICE_NAME_UPDATE);
}
string filename = GetParam(pb_device, "file");
if (filename.empty()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_FILENAME);
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME);
}
if (dryRun) {
@ -408,7 +409,7 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDev
{
// LUN 0 can only be detached if there is no other LUN anymore
if (!device->GetLun() && controller_manager.FindController(device->GetId())->GetLunCount() > 1) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_LUN0);
return context.ReturnLocalizedError(LocalizationKey::ERROR_LUN0);
}
if (!dryRun) {
@ -422,7 +423,7 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDev
auto controller = controller_manager.FindController(device->GetId());
if (controller == nullptr || !controller->DeleteDevice(device)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_DETACH);
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
}
// If no LUN is left also delete the controller
@ -446,7 +447,7 @@ void RascsiExecutor::DetachAll()
bool RascsiExecutor::ShutDown(const CommandContext& context, const string& mode) {
if (mode.empty()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING);
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_MISSING);
}
PbResult result;
@ -455,24 +456,24 @@ bool RascsiExecutor::ShutDown(const CommandContext& context, const string& mode)
if (mode == "rascsi") {
LOGINFO("RaSCSI shutdown requested")
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
return true;
}
if (mode != "system" && mode != "reboot") {
return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, mode);
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_MODE_INVALID, mode);
}
// The root user has UID 0
if (getuid()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_SHUTDOWN_PERMISSION);
return context.ReturnLocalizedError(LocalizationKey::ERROR_SHUTDOWN_PERMISSION);
}
if (mode == "system") {
LOGINFO("System shutdown requested")
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
DetachAll();
@ -483,7 +484,7 @@ bool RascsiExecutor::ShutDown(const CommandContext& context, const string& mode)
else if (mode == "reboot") {
LOGINFO("System reboot requested")
serializer.SerializeMessage(context.fd, result);
serializer.SerializeMessage(context.GetFd(), result);
DetachAll();
@ -559,7 +560,7 @@ bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr
filepath.SetPath(filename.c_str());
if (FileSupport::GetIdsForReservedFile(filepath, id, lun)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename,
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id), to_string(lun));
}
@ -571,7 +572,7 @@ bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr
filepath.SetPath((rascsi_image.GetDefaultFolder() + "/" + filename).c_str());
if (FileSupport::GetIdsForReservedFile(filepath, id, lun)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_IMAGE_IN_USE, filename,
return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename,
to_string(id), to_string(lun));
}
@ -579,7 +580,7 @@ bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr
}
}
catch(const io_exception& e) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_FILE_OPEN, initial_filename, e.get_msg());
return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, initial_filename, e.get_msg());
}
full_path = filepath.GetPath();
@ -656,11 +657,11 @@ string RascsiExecutor::ValidateLunSetup(const PbCommand& command) const
bool RascsiExecutor::VerifyExistingIdAndLun(const CommandContext& context, int id, int lun) const
{
if (controller_manager.FindController(id) == nullptr) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id));
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_DEVICE, to_string(id));
}
if (controller_manager.GetDeviceByIdAndLun(id, lun) == nullptr) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun));
return context.ReturnLocalizedError(LocalizationKey::ERROR_NON_EXISTING_UNIT, to_string(id), to_string(lun));
}
return true;
@ -672,10 +673,10 @@ shared_ptr<PrimaryDevice> RascsiExecutor::CreateDevice(const CommandContext& con
auto device = device_factory.CreateDevice(controller_manager, type, lun, filename);
if (device == nullptr) {
if (type == UNDEFINED) {
ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_TYPE, filename);
context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_TYPE, filename);
}
else {
ReturnLocalizedError(context, LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, PbDeviceType_Name(type));
context.ReturnLocalizedError(LocalizationKey::ERROR_UNKNOWN_DEVICE_TYPE, PbDeviceType_Name(type));
}
}
@ -689,11 +690,11 @@ bool RascsiExecutor::SetSectorSize(const CommandContext& context, const string&
auto disk = dynamic_pointer_cast<Disk>(device);
if (disk != nullptr && disk->IsSectorSizeConfigurable()) {
if (!disk->SetConfiguredSectorSize(device_factory, block_size)) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size));
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE, to_string(block_size));
}
}
else {
return ReturnLocalizedError(context, LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, type);
return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, type);
}
}
@ -706,19 +707,19 @@ bool RascsiExecutor::ValidationOperationAgainstDevice(const CommandContext& cont
const string& type = device->GetType();
if ((operation == START || operation == STOP) && !device->IsStoppable()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, type);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, type);
}
if ((operation == INSERT || operation == EJECT) && !device->IsRemovable()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, type);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, type);
}
if ((operation == PROTECT || operation == UNPROTECT) && !device->IsProtectable()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, type);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, type);
}
if ((operation == PROTECT || operation == UNPROTECT) && !device->IsReady()) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_OPERATION_DENIED_READY, type);
return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, type);
}
return true;
@ -728,13 +729,13 @@ bool RascsiExecutor::ValidateIdAndLun(const CommandContext& context, int id, int
{
// Validate the device ID and LUN
if (id < 0) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_MISSING_DEVICE_ID);
return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_DEVICE_ID);
}
if (id >= ControllerManager::DEVICE_MAX) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1));
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_ID, to_string(id), to_string(ControllerManager::DEVICE_MAX - 1));
}
if (lun < 0 || lun >= ScsiController::LUN_MAX) {
return ReturnLocalizedError(context, LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1));
return context.ReturnLocalizedError(LocalizationKey::ERROR_INVALID_LUN, to_string(lun), to_string(ScsiController::LUN_MAX - 1));
}
return true;
@ -755,7 +756,7 @@ bool RascsiExecutor::SetProductData(const CommandContext& context, const PbDevic
}
}
catch(const invalid_argument& e) {
return ReturnStatus(context, false, e.what());
return context.ReturnStatus(false, e.what());
}
return true;

View File

@ -14,6 +14,7 @@
#include "spdlog/spdlog.h"
#include "devices/file_support.h"
#include "command_util.h"
#include "command_context.h"
#include "rascsi_image.h"
#include <string>
#include <array>
@ -48,7 +49,7 @@ bool RascsiImage::CreateImageFolder(const CommandContext& context, const string&
std::error_code error;
filesystem::create_directories(folder, error);
if (error) {
ReturnStatus(context, false, "Can't create image folder '" + folder + "': " + strerror(errno));
context.ReturnStatus(false, "Can't create image folder '" + folder + "': " + strerror(errno));
return false;
}
}
@ -106,21 +107,21 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
{
string filename = GetParam(command, "file");
if (filename.empty()) {
return ReturnStatus(context, false, "Can't create image file: Missing image filename");
return context.ReturnStatus(false, "Can't create image file: Missing image filename");
}
if (!CheckDepth(filename)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
string full_filename = GetFullName(filename);
if (!IsValidDstFilename(full_filename)) {
return ReturnStatus(context, false, "Can't create image file: '" + full_filename + "': File already exists");
return context.ReturnStatus(false, "Can't create image file: '" + full_filename + "': File already exists");
}
const string size = GetParam(command, "size");
if (size.empty()) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Missing image size");
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Missing image size");
}
off_t len;
@ -128,13 +129,13 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
len = stoull(size);
}
catch(const invalid_argument&) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
}
catch(const out_of_range&) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': Invalid file size " + size);
}
if (len < 512 || (len & 0x1ff)) {
return ReturnStatus(context, false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)");
return context.ReturnStatus(false, "Invalid image file size " + to_string(len) + " (not a multiple of 512)");
}
if (!CreateImageFolder(context, full_filename)) {
@ -148,7 +149,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
int image_fd = open(full_filename.c_str(), O_CREAT|O_WRONLY, permissions);
if (image_fd == -1) {
return ReturnStatus(context, false, "Can't create image file '" + full_filename + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't create image file '" + full_filename + "': " + string(strerror(errno)));
}
#ifndef __linux__
@ -163,7 +164,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
unlink(full_filename.c_str());
return ReturnStatus(context, false, "Can't allocate space for image file '" + full_filename + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't allocate space for image file '" + full_filename + "': " + string(strerror(errno)));
}
close(image_fd);
@ -171,7 +172,7 @@ bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& co
LOGINFO("%s", string("Created " + string(permissions & S_IWUSR ? "": "read-only ") + "image file '" + full_filename +
"' with a size of " + to_string(len) + " bytes").c_str())
return ReturnStatus(context);
return context.ReturnStatus();
#endif
}
@ -179,11 +180,11 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
{
string filename = GetParam(command, "file");
if (filename.empty()) {
return ReturnStatus(context, false, "Missing image filename");
return context.ReturnStatus(false, "Missing image filename");
}
if (!CheckDepth(filename)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
string full_filename = GetFullName(filename);
@ -193,12 +194,12 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
Filepath filepath;
filepath.SetPath(full_filename.c_str());
if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) {
return ReturnStatus(context, false, "Can't delete image file '" + full_filename +
return context.ReturnStatus(false, "Can't delete image file '" + full_filename +
"', it is currently being used by device ID " + to_string(id) + ", unit " + to_string(unit));
}
if (remove(full_filename.c_str())) {
return ReturnStatus(context, false, "Can't delete image file '" + full_filename + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't delete image file '" + full_filename + "': " + string(strerror(errno)));
}
// Delete empty subfolders
@ -212,7 +213,7 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
}
if (remove(full_folder.c_str())) {
return ReturnStatus(context, false, "Can't delete empty image folder '" + full_folder + "'");
return context.ReturnStatus(false, "Can't delete empty image folder '" + full_folder + "'");
}
last_slash = folder.rfind('/');
@ -220,37 +221,15 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co
LOGINFO("Deleted image file '%s'", full_filename.c_str())
return ReturnStatus(context);
return context.ReturnStatus();
}
bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& command) const
{
string from = GetParam(command, "from");
if (from.empty()) {
return ReturnStatus(context, false, "Can't rename/move image file: Missing source filename");
}
if (!CheckDepth(from)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't rename/move image file: '" + from + "': Invalid name or type");
}
string to = GetParam(command, "to");
if (to.empty()) {
return ReturnStatus(context, false, "Can't rename/move image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': File already exists");
string from;
string to;
if (!ValidateParams(context, command, "rename/move", from, to)) {
return false;
}
if (!CreateImageFolder(context, to)) {
@ -258,47 +237,25 @@ bool RascsiImage::RenameImage(const CommandContext& context, const PbCommand& co
}
if (rename(from.c_str(), to.c_str())) {
return ReturnStatus(context, false, "Can't rename/move image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't rename/move image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
}
LOGINFO("Renamed/Moved image file '%s' to '%s'", from.c_str(), to.c_str())
return ReturnStatus(context);
return context.ReturnStatus();
}
bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& command) const
{
string from = GetParam(command, "from");
if (from.empty()) {
return ReturnStatus(context, false, "Can't copy image file: Missing source filename");
}
if (!CheckDepth(from)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + from + "'").c_str());
}
from = GetFullName(from);
if (!IsValidSrcFilename(from)) {
return ReturnStatus(context, false, "Can't copy image file: '" + from + "': Invalid name or type");
}
string to = GetParam(command, "to");
if (to.empty()) {
return ReturnStatus(context, false, "Can't copy image file '" + from + "': Missing destination filename");
}
if (!CheckDepth(to)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + to + "'").c_str());
}
to = GetFullName(to);
if (!IsValidDstFilename(to)) {
return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists");
string from;
string to;
if (!ValidateParams(context, command, "copy", from, to)) {
return false;
}
struct stat st;
if (lstat(from.c_str(), &st)) {
return ReturnStatus(context, false, "Can't access source image file '" + from + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't access source image file '" + from + "': " + string(strerror(errno)));
}
if (!CreateImageFolder(context, to)) {
@ -308,17 +265,17 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
// Symbolic links need a special handling
if ((st.st_mode & S_IFMT) == S_IFLNK) {
if (symlink(filesystem::read_symlink(from).c_str(), to.c_str())) {
return ReturnStatus(context, false, "Can't copy symlink '" + from + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't copy symlink '" + from + "': " + string(strerror(errno)));
}
LOGINFO("Copied symlink '%s' to '%s'", from.c_str(), to.c_str())
return ReturnStatus(context);
return context.ReturnStatus();
}
int fd_src = open(from.c_str(), O_RDONLY, 0);
if (fd_src == -1) {
return ReturnStatus(context, false, "Can't open source image file '" + from + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't open source image file '" + from + "': " + string(strerror(errno)));
}
string permission = GetParam(command, "read_only");
@ -330,7 +287,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
if (fd_dst == -1) {
close(fd_src);
return ReturnStatus(context, false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't open destination image file '" + to + "': " + string(strerror(errno)));
}
#ifndef __linux__
@ -339,6 +296,8 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
unlink(to.c_str());
LOGWARN("Copying image files is only supported under Linux")
return false;
#else
if (sendfile(fd_dst, fd_src, nullptr, st.st_size) == -1) {
@ -347,7 +306,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
unlink(to.c_str());
return ReturnStatus(context, false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
return context.ReturnStatus(false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno)));
}
close(fd_dst);
@ -355,7 +314,7 @@ bool RascsiImage::CopyImage(const CommandContext& context, const PbCommand& comm
LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str())
return ReturnStatus(context);
return context.ReturnStatus();
#endif
}
@ -363,23 +322,23 @@ bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCom
{
string filename = GetParam(command, "file");
if (filename.empty()) {
return ReturnStatus(context, false, "Missing image filename");
return context.ReturnStatus(false, "Missing image filename");
}
if (!CheckDepth(filename)) {
return ReturnStatus(context, false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
return context.ReturnStatus(false, ("Invalid folder hierarchy depth '" + filename + "'").c_str());
}
filename = GetFullName(filename);
if (!IsValidSrcFilename(filename)) {
return ReturnStatus(context, false, "Can't modify image file '" + filename + "': Invalid name or type");
return context.ReturnStatus(false, "Can't modify image file '" + filename + "': Invalid name or type");
}
bool protect = command.operation() == PROTECT_IMAGE;
if (int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
chmod(filename.c_str(), permissions) == -1) {
return ReturnStatus(context, false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + filename + "': " +
return context.ReturnStatus(false, "Can't " + string(protect ? "protect" : "unprotect") + " image file '" + filename + "': " +
strerror(errno));
}
@ -390,10 +349,44 @@ bool RascsiImage::SetImagePermissions(const CommandContext& context, const PbCom
LOGINFO("Unprotected image file '%s'", filename.c_str())
}
return ReturnStatus(context);
return context.ReturnStatus();
}
string RascsiImage::GetHomeDir() const
bool RascsiImage::ValidateParams(const CommandContext& context, const PbCommand& command, const string& operation,
string& from, string& to) const
{
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");
}
return true;
}
string RascsiImage::GetHomeDir()
{
int uid = getuid();
if (const char *sudo_user = getenv("SUDO_UID"); sudo_user != nullptr) {

View File

@ -13,6 +13,7 @@
#include "command_context.h"
#include <string>
using namespace std;
using namespace rascsi_interface;
class RascsiImage
@ -24,22 +25,24 @@ public:
void SetDepth(int d) { depth = d; }
int GetDepth() const { return depth; }
bool CheckDepth(string_view) const;
bool CreateImageFolder(const CommandContext&, const string&) const;
string GetDefaultFolder() const { return default_folder; }
string SetDefaultFolder(const string&);
bool IsValidSrcFilename(const string&) const;
bool IsValidDstFilename(const string&) const;
bool CreateImage(const CommandContext&, const PbCommand&) const;
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 GetFullName(const string& filename) const { return default_folder + "/" + filename; }
private:
string GetHomeDir() const;
bool CheckDepth(string_view) const;
string GetFullName(const string& filename) const { return default_folder + "/" + filename; }
bool CreateImageFolder(const CommandContext&, const string&) const;
bool IsValidSrcFilename(const string&) const;
bool IsValidDstFilename(const string&) const;
bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const;
static string GetHomeDir();
string default_folder;

View File

@ -83,7 +83,7 @@ void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const
pb_device.set_allocated_properties(GetDeviceProperties(device).release());
auto status = make_unique<PbDeviceStatus>().release();
auto status = make_unique<PbDeviceStatus>().release(); //NOSONAR The allocated memory is managed by protobuf
pb_device.set_allocated_status(status);
status->set_protected_(device.IsProtected());
status->set_stopped(device.IsStopped());

View File

@ -21,25 +21,25 @@ using namespace rascsi_interface;
volatile bool RascsiService::running = false;
RascsiService::~RascsiService()
void RascsiService::Cleanup() const
{
if (service_socket != -1) {
close(service_socket);
}
}
bool RascsiService::Init(bool (e)(PbCommand&, CommandContext&), int port)
bool RascsiService::Init(const callback& cb, int port)
{
// Create socket for monitor
sockaddr_in server = {};
service_socket = socket(PF_INET, SOCK_STREAM, 0);
if (service_socket == -1) {
LOGERROR("Unable to create socket");
LOGERROR("Unable to create socket")
return false;
}
server.sin_family = PF_INET;
server.sin_port = htons(port);
server.sin_port = htons((uint16_t)port);
server.sin_addr.s_addr = htonl(INADDR_ANY);
// Allow address reuse
@ -54,7 +54,7 @@ bool RascsiService::Init(bool (e)(PbCommand&, CommandContext&), int port)
return false;
}
execute = e;
execute = cb;
monthread = thread(&RascsiService::Execute, this);
monthread.detach();
@ -64,7 +64,7 @@ bool RascsiService::Init(bool (e)(PbCommand&, CommandContext&), int port)
&& signal(SIGTERM, KillHandler) != SIG_ERR;
}
void RascsiService::Execute()
void RascsiService::Execute() const
{
#ifdef __linux__
// Scheduler Settings
@ -77,62 +77,57 @@ void RascsiService::Execute()
ras_util::FixCpu(2);
// Wait for the execution to start
timespec ts = { .tv_sec = 0, .tv_nsec = 1000};
while (!running) {
usleep(1);
nanosleep(&ts, nullptr);
}
// Set up the monitor socket to receive commands
listen(service_socket, 1);
ProtobufSerializer serializer;
Localizer localizer;
while (true) {
CommandContext context(serializer, localizer, -1, "");
CommandContext context;
try {
PbCommand command;
context.fd = ReadCommand(serializer, command);
if (context.fd == -1) {
continue;
PbCommand command = ReadCommand(context);
if (context.IsValid()) {
execute(context, command);
}
execute(command, context);
}
catch(const io_exception& e) {
LOGWARN("%s", e.get_msg().c_str())
// Fall through
// Fall through
}
if (context.fd != -1) {
close(context.fd);
}
context.Cleanup();
}
}
int RascsiService::ReadCommand(ProtobufSerializer& serializer, PbCommand& command)
PbCommand RascsiService::ReadCommand(CommandContext& context) const
{
// Wait for connection
sockaddr client = {};
socklen_t socklen = sizeof(client);
int fd = accept(service_socket, &client, &socklen);
if (fd < 0) {
if (fd == -1) {
throw io_exception("accept() failed");
}
PbCommand command;
// Read magic string
vector<byte> magic(6);
size_t bytes_read = serializer.ReadBytes(fd, magic);
if (!bytes_read) {
return -1;
}
if (bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
if (size_t bytes_read = context.GetSerializer().ReadBytes(fd, magic);
bytes_read != magic.size() || memcmp(magic.data(), "RASCSI", magic.size())) {
throw io_exception("Invalid magic");
}
// Fetch the command
serializer.DeserializeMessage(fd, command);
context.GetSerializer().DeserializeMessage(fd, command);
return fd;
context.SetFd(fd);
return command;
}

View File

@ -10,14 +10,18 @@
#pragma once
#include "rascsi_interface.pb.h"
#include <functional>
#include <thread>
class CommandContext;
class ProtobufSerializer;
using namespace std;
class RascsiService
{
bool (*execute)(rascsi_interface::PbCommand&, CommandContext&) = nullptr;
using callback = function<bool(const CommandContext&, rascsi_interface::PbCommand&)>;
callback execute;
int service_socket = -1;
@ -28,16 +32,17 @@ class RascsiService
public:
RascsiService() = default;
~RascsiService();
~RascsiService() = default;
bool Init(bool (ExecuteCommand)(rascsi_interface::PbCommand&, CommandContext&), int);
bool Init(const callback&, int);
void Cleanup() const;
bool IsRunning() const { return running; }
void SetRunning(bool b) const { running = b; }
void Execute();
void Execute() const;
int ReadCommand(ProtobufSerializer&, rascsi_interface::PbCommand&);
PbCommand ReadCommand(CommandContext&) const;
static void KillHandler(int) { running = false; }
};

View File

@ -14,8 +14,8 @@
#include "rascsi_version.h"
#include "command_util.h"
#include "rasutil.h"
#include "rasctl_commands.h"
#include "rascsi_interface.pb.h"
#include "rasctl/rasctl_commands.h"
#include <unistd.h>
#include <clocale>
#include <iostream>

View File

@ -18,6 +18,7 @@
#include "hal/gpiobus.h"
#include "hal/systimer.h"
#include "rascsi_version.h"
#include <cstring>
#include <iostream>
#include <array>
@ -258,7 +259,9 @@ bool Selection(int id)
// wait for busy
count = 10000;
do {
usleep(20);
// Wait 20 microseconds
timespec ts = { .tv_sec = 0, .tv_nsec = 20 * 1000};
nanosleep(&ts, nullptr);
bus.Acquire();
if (bus.GetBSY()) {
break;
@ -289,7 +292,7 @@ bool Command(BYTE *buf, int length)
// Send Command
count = bus.SendHandShake(buf, length, BUS::SEND_NO_DELAY);
// Success if the transmission result is the same as the number
// Success if the transmission result is the same as the number
// of requests
if (count == length) {
return true;
@ -851,7 +854,9 @@ int main(int argc, char* argv[])
// Assert reset signal
bus.SetRST(true);
usleep(1000);
// Wait 1 ms
timespec ts = { .tv_sec = 0, .tv_nsec = 1000 * 1000};
nanosleep(&ts, nullptr);
bus.SetRST(false);
// Start dump

View File

@ -0,0 +1,20 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include "rascsi/command_context.h"
using namespace rascsi_interface;
TEST(CommandContext, ReturnLocalizedError)
{
MockCommandContext context;
EXPECT_FALSE(context.ReturnLocalizedError(LocalizationKey::ERROR_LOG_LEVEL));
}

View File

@ -59,10 +59,3 @@ TEST(CommandUtil, ParseParameters)
TestSpecialDevice("printer");
TestSpecialDevice("services");
}
TEST(CommandUtil, ReturnLocalizedError)
{
MockCommandContext context;
EXPECT_FALSE(ReturnLocalizedError(context, LocalizationKey::ERROR_LOG_LEVEL));
}

View File

@ -0,0 +1,41 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include "devices/ctapdriver.h"
TEST(CTapDriverTest, Crc32)
{
array<BYTE, ETH_FRAME_LEN> buf;
buf.fill(0x00);
EXPECT_EQ(0xe3d887bb, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
buf.fill(0xff);
EXPECT_EQ(0x814765f4, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
buf.fill(0x10);
EXPECT_EQ(0xb7288Cd3, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
buf.fill(0x7f);
EXPECT_EQ(0x4b543477, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
buf.fill(0x80);
EXPECT_EQ(0x29cbd638, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
for (size_t i = 0; i < buf.size(); i++) {
buf[i] = (BYTE)i;
}
EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
for (size_t i = buf.size() - 1; i > 0; i--) {
buf[i] = (BYTE)i;
}
EXPECT_EQ(0xe7870705, CTapDriver::Crc32(buf.data(), ETH_FRAME_LEN));
}

View File

@ -18,9 +18,7 @@
#include "devices/scsicd.h"
#include "devices/scsimo.h"
#include "devices/host_services.h"
#include "protobuf_serializer.h"
#include "command_context.h"
#include "localizer.h"
#include "rascsi/command_context.h"
class MockBus final : public BUS //NOSONAR Having many fields/methods cannot be avoided
{
@ -273,11 +271,10 @@ class MockHostServices final : public HostServices
class MockCommandContext : public CommandContext
{
ProtobufSerializer s;
Localizer l;
public:
MockCommandContext() : CommandContext(s, l, STDOUT_FILENO, "") {}
MockCommandContext() {
SetFd(open("/dev/null", O_WRONLY));
}
~MockCommandContext() = default;
};

View File

@ -19,6 +19,9 @@ TEST(ProtobufSerializerTest, SerializeMessage)
PbResult message;
ProtobufSerializer serializer;
serializer.SerializeMessage(STDOUT_FILENO, message);
int fd = open("/dev/null", O_WRONLY);
ASSERT_NE(-1, fd);
serializer.SerializeMessage(fd, message);
close(fd);
EXPECT_THROW(serializer.SerializeMessage(-1, message), io_exception);
}

View File

@ -8,12 +8,13 @@
//---------------------------------------------------------------------------
#include "mocks.h"
#include "command_util.h"
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "command_util.h"
#include "rascsi_response.h"
#include "rascsi_image.h"
#include "rascsi_executor.h"
#include "rascsi/command_context.h"
#include "rascsi/rascsi_response.h"
#include "rascsi/rascsi_image.h"
#include "rascsi/rascsi_executor.h"
using namespace rascsi_interface;
using namespace command_util;

View File

@ -11,7 +11,7 @@
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "rascsi_interface.pb.h"
#include "rascsi_response.h"
#include "rascsi/rascsi_response.h"
using namespace rascsi_interface;