mirror of
https://github.com/akuker/RASCSI.git
synced 2025-02-16 19:31:09 +00:00
protobuf-based rasctl/rascsi command interface (#129)
* Initial protobuf definition * protobuf result message draft * Merge with develop branch * Makefile generates protobuf-based source files * Interface update * Fixed typo * Fixed typo * Updated returning status * Serialize return data * Use correct descriptor * Made interface fields required * Deserialize result * Serialization update * Updated serialization * Serialization update * Updated deserialization * status handling update * Evaluate status * Revert "Evaluate status" This reverts commit 3d8f2c3252a10618dede5f5fd80c84115551fc7d. * Completed sense_key enum * Renaming * Added protobuf Command * Updated command evaluation * Interface update * Interface update * Added DeviceType enum * Improved type-safety * Fixed typo * Type-safety update * Fixed typo * Error handling update * Updated list handling * Error handling update * Use more C++ strings * protobuf enums can provide their names * Fixed listing devices * Updated logging * Enum usage cleanup * More enum cleanup * Fixed command check * Updated type check * Updated enums * Removed unused variable * Removed goto, added exception * Socket handling cleanup * Code locality cleanup * Added helper method * Extracted code * Updated socket/file handling * Use C++ I/O * Use tolower() * Renaming * Simplified has_suffix * Fixed typo * Use spdlog namespace * Simplified formatting (endl) of error messages * Added -s option for changing the runtime log level to rasctl * Renaming * Renaming * Updated error reporting * Fixed log string formatting * String conversion cleanup * Fixed typo * Log mmap error (happens on 64 bit) * Improved proto3 compatibility, updated error handling * CHanges based on review * Fixed comment * Comment update * Updated ListDevice to not directly write to the stream * Use size_t * Renaming * Buffering update * MapController should not use fp * Use fd, not fp * rasctl has to display *all* results * rasctl has to display *all* results * Error handling update * Updated to proto3 protocol * Optimization by using protobuf-lite * RaspBian outdated protoc does not support _Name * Added protobuf-compiler to easyinstall.sh Co-authored-by: akuker <34318535+akuker@users.noreply.github.com>
This commit is contained in:
parent
6136b29515
commit
af6e311e6e
@ -41,7 +41,7 @@ function initialChecks() {
|
||||
}
|
||||
|
||||
function installPackages() {
|
||||
sudo apt-get update && sudo apt install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv nginx libpcap-dev -y
|
||||
sudo apt-get update && sudo apt install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv nginx libpcap-dev protobuf-compiler -y
|
||||
}
|
||||
|
||||
# install all dependency packages for RaSCSI Service
|
||||
|
@ -73,19 +73,27 @@ BINDIR := ./bin/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]')
|
||||
# for my specific use case. If you need them - add them back in!
|
||||
BIN_ALL = $(BINDIR)/$(RASCSI) $(BINDIR)/$(RASCTL) $(BINDIR)/$(SCSIMON)
|
||||
|
||||
SRC_PROTOC = \
|
||||
rasctl_interface.proto
|
||||
|
||||
SRC_PROTOBUF = \
|
||||
rasctl_interface.pb.cpp
|
||||
|
||||
SRC_RASCSI = \
|
||||
rascsi.cpp \
|
||||
scsi.cpp \
|
||||
gpiobus.cpp \
|
||||
filepath.cpp \
|
||||
fileio.cpp\
|
||||
rascsi_version.cpp
|
||||
rascsi_version.cpp \
|
||||
rasutil.cpp
|
||||
# os.cpp
|
||||
# rasctl_command.cpp
|
||||
# rascsi_mgr.cpp
|
||||
# command_thread.cpp
|
||||
SRC_RASCSI += $(shell find ./controllers -name '*.cpp')
|
||||
SRC_RASCSI += $(shell find ./devices -name '*.cpp')
|
||||
SRC_RASCSI += $(SRC_PROTOBUF)
|
||||
|
||||
SRC_SCSIMON = \
|
||||
scsimon.cpp \
|
||||
@ -97,8 +105,10 @@ SRC_SCSIMON = \
|
||||
|
||||
SRC_RASCTL = \
|
||||
rasctl.cpp\
|
||||
rascsi_version.cpp
|
||||
rascsi_version.cpp \
|
||||
rasutil.cpp
|
||||
# rasctl_command.cpp
|
||||
SRC_RASCTL += $(SRC_PROTOBUF)
|
||||
|
||||
SRC_RASDUMP = \
|
||||
rasdump.cpp \
|
||||
@ -127,8 +137,8 @@ OBJ_RASCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL:%.cpp=%.o)))
|
||||
OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o)))
|
||||
OBJ_SASIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SASIDUMP:%.cpp=%.o)))
|
||||
OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o)))
|
||||
#OBJ_ALL := $(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_RASDUMP) $(OBJ_SASIDUMP) $(OBJ_SCSIMON)
|
||||
OBJ_ALL := $(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_RASDUMP) $(OBJ_SASIDUMP) $(OBJ_SCSIMON)
|
||||
|
||||
GEN_PROTOBUF := $(SRC_PROTOBUF) rasctl_interface.pb.h
|
||||
|
||||
|
||||
# The following will include all of the auto-generated dependency files (*.d)
|
||||
@ -143,6 +153,11 @@ $(OBJDIR) $(BINDIR):
|
||||
$(OBJDIR)/%.o: %.cpp | $(OBJDIR)
|
||||
$(CXX) $(CXXFLAGS) -c $< -o $@
|
||||
|
||||
$(SRC_PROTOBUF): $(SRC_PROTOC)
|
||||
echo "-- Generating protobuf-based source files"
|
||||
protoc --cpp_out=. $(SRC_PROTOC)
|
||||
mv rasctl_interface.pb.cc $@
|
||||
|
||||
## Build Targets:
|
||||
## all : Rebuild all of the executable files and re-generate
|
||||
## the text versions of the manpages
|
||||
@ -155,10 +170,10 @@ ALL: all
|
||||
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
|
||||
|
||||
$(BINDIR)/$(RASCSI): $(OBJ_RASCSI) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lz -lpcap
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lz -lpcap -lprotobuf-lite
|
||||
|
||||
$(BINDIR)/$(RASCTL): $(OBJ_RASCTL) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lprotobuf-lite
|
||||
|
||||
$(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASDUMP)
|
||||
@ -173,7 +188,7 @@ $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR)
|
||||
## compiler files and executable files
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(BINDIR)
|
||||
rm -rf $(OBJDIR) $(BINDIR) $(GEN_PROTOBUF)
|
||||
|
||||
## run : Launches RaSCSI using some pre-defined drive
|
||||
## images. Useful for debugging when you're building
|
||||
|
@ -12,20 +12,34 @@
|
||||
#if !defined(exceptions_h)
|
||||
#define exceptions_h
|
||||
|
||||
#include <exception>
|
||||
|
||||
class lunexception : public std::exception {
|
||||
private:
|
||||
DWORD lun;
|
||||
int lun;
|
||||
|
||||
public:
|
||||
lunexception(DWORD lun) {
|
||||
this->lun = lun;
|
||||
}
|
||||
lunexception(int _lun) : lun(_lun) { }
|
||||
|
||||
~lunexception() { }
|
||||
|
||||
DWORD getlun() const {
|
||||
int getlun() const {
|
||||
return lun;
|
||||
}
|
||||
};
|
||||
|
||||
class ioexception : public std::exception {
|
||||
private:
|
||||
const char *msg;
|
||||
|
||||
public:
|
||||
ioexception(const char *_msg) : msg(_msg) { }
|
||||
|
||||
~ioexception() { }
|
||||
|
||||
const char *getmsg() const {
|
||||
return msg;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -179,9 +179,9 @@ BOOL FASTCALL GPIOBUS::Init(mode_e mode)
|
||||
}
|
||||
|
||||
// Map peripheral region memory
|
||||
map = mmap(NULL, 0x1000100,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
|
||||
map = mmap(NULL, 0x1000100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, baseaddr);
|
||||
if (map == MAP_FAILED) {
|
||||
LOGERROR("Error: Unable to map memory");
|
||||
close(fd);
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -25,15 +25,20 @@
|
||||
#include "controllers/scsidev_ctrl.h"
|
||||
#include "controllers/sasidev_ctrl.h"
|
||||
#include "gpiobus.h"
|
||||
#include "exceptions.h"
|
||||
#include "rascsi_version.h"
|
||||
#include "rasctl.h"
|
||||
#include "rasutil.h"
|
||||
#include "rasctl_interface.pb.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "spdlog/sinks/stdout_color_sinks.h"
|
||||
#include <spdlog/async.h>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace spdlog;
|
||||
using namespace rasctl_interface;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
@ -128,8 +133,6 @@ void Banner(int argc, char* argv[])
|
||||
//---------------------------------------------------------------------------
|
||||
BOOL Init()
|
||||
{
|
||||
int i;
|
||||
|
||||
#ifndef BAREMETAL
|
||||
struct sockaddr_in server;
|
||||
int yes, result;
|
||||
@ -188,12 +191,12 @@ BOOL Init()
|
||||
bus->Reset();
|
||||
|
||||
// Controller initialization
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
ctrl[i] = NULL;
|
||||
}
|
||||
|
||||
// Disk Initialization
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
disk[i] = NULL;
|
||||
}
|
||||
|
||||
@ -211,10 +214,8 @@ BOOL Init()
|
||||
//---------------------------------------------------------------------------
|
||||
void Cleanup()
|
||||
{
|
||||
int i;
|
||||
|
||||
// Delete the disks
|
||||
for (i = 0; i < CtrlMax * UnitNum; i++) {
|
||||
for (int i = 0; i < CtrlMax * UnitNum; i++) {
|
||||
if (disk[i]) {
|
||||
delete disk[i];
|
||||
disk[i] = NULL;
|
||||
@ -222,7 +223,7 @@ void Cleanup()
|
||||
}
|
||||
|
||||
// Delete the Controllers
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
if (ctrl[i]) {
|
||||
delete ctrl[i];
|
||||
ctrl[i] = NULL;
|
||||
@ -252,10 +253,8 @@ void Cleanup()
|
||||
//---------------------------------------------------------------------------
|
||||
void Reset()
|
||||
{
|
||||
int i;
|
||||
|
||||
// Reset all of the controllers
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
if (ctrl[i]) {
|
||||
ctrl[i]->Reset();
|
||||
}
|
||||
@ -270,25 +269,20 @@ void Reset()
|
||||
// List Devices
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void ListDevice(FILE *fp)
|
||||
{
|
||||
int i;
|
||||
int id;
|
||||
int un;
|
||||
Disk *pUnit;
|
||||
Filepath filepath;
|
||||
BOOL find;
|
||||
string ListDevice() {
|
||||
char type[5];
|
||||
char dev_status[_MAX_FNAME+26];
|
||||
ostringstream s;
|
||||
|
||||
find = FALSE;
|
||||
bool has_header = false;
|
||||
type[4] = 0;
|
||||
for (i = 0; i < CtrlMax * UnitNum; i++) {
|
||||
|
||||
for (int i = 0; i < CtrlMax * UnitNum; i++) {
|
||||
strncpy(dev_status,"",sizeof(dev_status));
|
||||
// Initialize ID and unit number
|
||||
id = i / UnitNum;
|
||||
un = i % UnitNum;
|
||||
pUnit = disk[i];
|
||||
int id = i / UnitNum;
|
||||
int un = i % UnitNum;
|
||||
Disk *pUnit = disk[i];
|
||||
|
||||
// skip if unit does not exist or null disk
|
||||
if (pUnit == NULL || pUnit->IsNULL()) {
|
||||
@ -296,14 +290,15 @@ void ListDevice(FILE *fp)
|
||||
}
|
||||
|
||||
// Output the header
|
||||
if (!find) {
|
||||
FPRT(fp, "\n");
|
||||
FPRT(fp, "+----+----+------+-------------------------------------\n");
|
||||
if (!has_header) {
|
||||
s << endl
|
||||
<< "+----+----+------+-------------------------------------" << endl
|
||||
<< "| ID | UN | TYPE | DEVICE STATUS" << endl
|
||||
<< "+----+----+------+-------------------------------------" << endl;
|
||||
LOGINFO( "+----+----+------+-------------------------------------");
|
||||
FPRT(fp, "| ID | UN | TYPE | DEVICE STATUS\n");
|
||||
LOGINFO( "| ID | UN | TYPE | DEVICE STATUS\n");
|
||||
FPRT(fp, "+----+----+------+-------------------------------------\n");
|
||||
find = TRUE;
|
||||
LOGINFO( "| ID | UN | TYPE | DEVICE STATUS");
|
||||
LOGINFO( "+----+----+------+-------------------------------------\n");
|
||||
has_header = true;
|
||||
}
|
||||
|
||||
// ID,UNIT,Type,Device Status
|
||||
@ -318,6 +313,7 @@ void ListDevice(FILE *fp)
|
||||
} else if (pUnit->GetID() == MAKEID('S', 'C', 'D', 'P')) {
|
||||
strncpy(dev_status,"DaynaPort SCSI/Link",sizeof(dev_status));
|
||||
} else {
|
||||
Filepath filepath;
|
||||
pUnit->GetPath(filepath);
|
||||
snprintf(dev_status, sizeof(dev_status), "%s",
|
||||
(pUnit->IsRemovable() && !pUnit->IsReady()) ?
|
||||
@ -328,19 +324,20 @@ void ListDevice(FILE *fp)
|
||||
if (pUnit->IsRemovable() && pUnit->IsReady() && pUnit->IsWriteP()) {
|
||||
strcat(dev_status, " (WRITEPROTECT)");
|
||||
}
|
||||
FPRT(fp, "| %d | %d | %s | %s\n", id, un, type, dev_status);
|
||||
s << "| " << id << " | " << un << " | " << type << " | " << dev_status << endl;
|
||||
LOGINFO( "| %d | %d | %s | %s", id, un, type, dev_status);
|
||||
|
||||
}
|
||||
|
||||
// If there is no controller, find will be null
|
||||
if (!find) {
|
||||
FPRT(fp, "No images currently attached.\n");
|
||||
return;
|
||||
if (!has_header) {
|
||||
return "No images currently attached.\n";
|
||||
}
|
||||
|
||||
FPRT(fp, "+----+----+------+-------------------------------------\n");
|
||||
s << "+----+----+------+-------------------------------------" << endl;
|
||||
LOGINFO( "+----+----+------+-------------------------------------");
|
||||
|
||||
return s.str();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -348,21 +345,17 @@ void ListDevice(FILE *fp)
|
||||
// Controller Mapping
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void MapControler(FILE *fp, Disk **map)
|
||||
bool MapController(Disk **map)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int unitno;
|
||||
int sasi_num;
|
||||
int scsi_num;
|
||||
bool status = true;
|
||||
|
||||
// Take ownership of the ctrl data structure
|
||||
pthread_mutex_lock(&ctrl_mutex);
|
||||
|
||||
// Replace the changed unit
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (j = 0; j < UnitNum; j++) {
|
||||
unitno = i * UnitNum + j;
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
for (int j = 0; j < UnitNum; j++) {
|
||||
int unitno = i * UnitNum + j;
|
||||
if (disk[unitno] != map[unitno]) {
|
||||
// Check if the original unit exists
|
||||
if (disk[unitno]) {
|
||||
@ -382,12 +375,12 @@ void MapControler(FILE *fp, Disk **map)
|
||||
}
|
||||
|
||||
// Reconfigure all of the controllers
|
||||
for (i = 0; i < CtrlMax; i++) {
|
||||
for (int i = 0; i < CtrlMax; i++) {
|
||||
// Examine the unit configuration
|
||||
sasi_num = 0;
|
||||
scsi_num = 0;
|
||||
for (j = 0; j < UnitNum; j++) {
|
||||
unitno = i * UnitNum + j;
|
||||
int sasi_num = 0;
|
||||
int scsi_num = 0;
|
||||
for (int j = 0; j < UnitNum; j++) {
|
||||
int unitno = i * UnitNum + j;
|
||||
// branch by unit type
|
||||
if (disk[unitno]) {
|
||||
if (disk[unitno]->IsSASI()) {
|
||||
@ -416,7 +409,7 @@ void MapControler(FILE *fp, Disk **map)
|
||||
|
||||
// Mixture of SCSI and SASI
|
||||
if (sasi_num > 0 && scsi_num > 0) {
|
||||
FPRT(fp, "Error : SASI and SCSI can't be mixed\n");
|
||||
status = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -451,8 +444,8 @@ void MapControler(FILE *fp, Disk **map)
|
||||
}
|
||||
|
||||
// connect all units
|
||||
for (j = 0; j < UnitNum; j++) {
|
||||
unitno = i * UnitNum + j;
|
||||
for (int j = 0; j < UnitNum; j++) {
|
||||
int unitno = i * UnitNum + j;
|
||||
if (disk[unitno]) {
|
||||
// Add the unit connection
|
||||
ctrl[i]->SetUnit(j, disk[unitno]);
|
||||
@ -460,6 +453,55 @@ void MapControler(FILE *fp, Disk **map)
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&ctrl_mutex);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool ReturnStatus(FILE *fp, bool status = true, const string msg = "") {
|
||||
#ifdef BAREMETAL
|
||||
if (msg.length()) {
|
||||
FPRT(fp, msg.c_str());
|
||||
FPRT(fp, "\n");
|
||||
}
|
||||
#else
|
||||
Result result;
|
||||
result.set_msg(msg + "\n");
|
||||
result.set_status(status);
|
||||
|
||||
string data;
|
||||
result.SerializeToString(&data);
|
||||
SerializeProtobufData(fp, data);
|
||||
#endif
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void SetLogLevel(const string& log_level) {
|
||||
if (log_level == "trace") {
|
||||
set_level(level::trace);
|
||||
}
|
||||
else if (log_level == "debug") {
|
||||
set_level(level::debug);
|
||||
}
|
||||
else if (log_level == "info") {
|
||||
set_level(level::info);
|
||||
}
|
||||
else if (log_level == "warn") {
|
||||
set_level(level::warn);
|
||||
}
|
||||
else if (log_level == "err") {
|
||||
set_level(level::err);
|
||||
}
|
||||
else if (log_level == "critical") {
|
||||
set_level(level::critical);
|
||||
}
|
||||
else if (log_level == "off") {
|
||||
set_level(level::off);
|
||||
}
|
||||
else {
|
||||
LOGWARN("Invalid log level '%s', falling back to 'trace'", log_level.c_str());
|
||||
set_level(level::trace);
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -467,114 +509,108 @@ void MapControler(FILE *fp, Disk **map)
|
||||
// Command Processing
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
BOOL ProcessCmd(FILE *fp, int id, int un, int cmd, int type, char *file)
|
||||
bool ProcessCmd(FILE *fp, const Command &command)
|
||||
{
|
||||
Disk *map[CtrlMax * UnitNum];
|
||||
int len;
|
||||
char *ext;
|
||||
Filepath filepath;
|
||||
Disk *pUnit;
|
||||
char type_str[5];
|
||||
|
||||
int id = command.id();
|
||||
int un = command.un();
|
||||
Operation cmd = command.cmd();
|
||||
DeviceType type = command.type();
|
||||
string params = command.params().c_str();
|
||||
|
||||
ostringstream s;
|
||||
s << "Processing: cmd=" << cmd << ", id=" << id << ", un=" << un << ", type=" << type << ", params=" << params << endl;
|
||||
LOGINFO("%s", s.str().c_str());
|
||||
|
||||
// Copy the Unit List
|
||||
memcpy(map, disk, sizeof(disk));
|
||||
|
||||
// Check the Controller Number
|
||||
if (id < 0 || id >= CtrlMax) {
|
||||
FPRT(fp, "Error : Invalid ID\n");
|
||||
return FALSE;
|
||||
return ReturnStatus(fp, false, "Error : Invalid ID");
|
||||
}
|
||||
|
||||
// Check the Unit Number
|
||||
if (un < 0 || un >= UnitNum) {
|
||||
FPRT(fp, "Error : Invalid unit number\n");
|
||||
return FALSE;
|
||||
return ReturnStatus(fp, false, "Error : Invalid unit number");
|
||||
}
|
||||
|
||||
// Connect Command
|
||||
if (cmd == 0) { // ATTACH
|
||||
if (cmd == ATTACH) {
|
||||
string ext;
|
||||
|
||||
// Distinguish between SASI and SCSI
|
||||
ext = NULL;
|
||||
pUnit = NULL;
|
||||
if (type == rasctl_dev_sasi_hd) {
|
||||
// Passed the check
|
||||
if (!file) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Check that command is at least 5 characters long
|
||||
len = strlen(file);
|
||||
if (len < 5) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (type == SASI_HD) {
|
||||
// Check the extension
|
||||
if (file[len - 4] != '.') {
|
||||
return FALSE;
|
||||
int len = params.length();
|
||||
if (len < 5 || params[len - 4] != '.') {
|
||||
return ReturnStatus(fp, false);
|
||||
}
|
||||
|
||||
// If the extension is not SASI type, replace with SCSI
|
||||
ext = &file[len - 3];
|
||||
if (xstrcasecmp(ext, "hdf") != 0) {
|
||||
type = rasctl_dev_scsi_hd;
|
||||
ext = params.substr(len - 3);
|
||||
if (ext != "hdf") {
|
||||
type = SCSI_HD;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new drive, based upon type
|
||||
pUnit = NULL;
|
||||
switch (type) {
|
||||
case rasctl_dev_sasi_hd: // HDF
|
||||
case SASI_HD: // HDF
|
||||
pUnit = new SASIHD();
|
||||
break;
|
||||
case rasctl_dev_scsi_hd: // HDS/HDN/HDI/NHD/HDA
|
||||
if (ext == NULL) {
|
||||
break;
|
||||
}
|
||||
if (xstrcasecmp(ext, "hdn") == 0 ||
|
||||
xstrcasecmp(ext, "hdi") == 0 || xstrcasecmp(ext, "nhd") == 0) {
|
||||
case SCSI_HD: // HDS/HDN/HDI/NHD/HDA
|
||||
if (ext == "hdn" || ext == "hdi" || ext == "nhd") {
|
||||
pUnit = new SCSIHD_NEC();
|
||||
} else if (xstrcasecmp(ext, "hda") == 0) {
|
||||
} else if (ext == "hda") {
|
||||
pUnit = new SCSIHD_APPLE();
|
||||
} else {
|
||||
pUnit = new SCSIHD();
|
||||
}
|
||||
break;
|
||||
case rasctl_dev_mo: // MO
|
||||
case MO:
|
||||
pUnit = new SCSIMO();
|
||||
break;
|
||||
case rasctl_dev_cd: // CD
|
||||
case CD:
|
||||
pUnit = new SCSICD();
|
||||
break;
|
||||
case rasctl_dev_br: // BRIDGE
|
||||
case BR:
|
||||
pUnit = new SCSIBR();
|
||||
break;
|
||||
// case rasctl_dev_nuvolink: // Nuvolink
|
||||
// case NUVOLINK:
|
||||
// pUnit = new SCSINuvolink();
|
||||
// break;
|
||||
case rasctl_dev_daynaport: // DaynaPort SCSI Link
|
||||
case DAYNAPORT:
|
||||
pUnit = new SCSIDaynaPort();
|
||||
LOGTRACE("Done creating SCSIDaynaPort");
|
||||
break;
|
||||
default:
|
||||
FPRT(fp, "Error : Invalid device type\n");
|
||||
LOGWARN("rasctl sent a command for an invalid drive type: %d", type);
|
||||
return FALSE;
|
||||
ostringstream msg;
|
||||
msg << "rasctl sent a command for an invalid drive type: " << type;
|
||||
return ReturnStatus(fp, false, msg.str());
|
||||
}
|
||||
|
||||
// drive checks files
|
||||
if (type <= rasctl_dev_scsi_hd || ((type <= rasctl_dev_cd || type == rasctl_dev_daynaport) && xstrcasecmp(file, "-") != 0)) {
|
||||
if (type != BR && type != DAYNAPORT && !command.params().empty()) {
|
||||
// Strip the image file extension from device file names, so that device files can be used as drive images
|
||||
string f = file;
|
||||
string effective_file = f.find("/dev/") ? f : f.substr(0, f.length() - 4);
|
||||
string file = params.find("/dev/") ? params : params.substr(0, params.length() - 4);
|
||||
|
||||
// Set the Path
|
||||
filepath.SetPath(effective_file.c_str());
|
||||
filepath.SetPath(file.c_str());
|
||||
|
||||
// Open the file path
|
||||
if (!pUnit->Open(filepath)) {
|
||||
FPRT(fp, "Error : File open error [%s]\n", file);
|
||||
LOGWARN("rasctl tried to open an invalid file %s", file);
|
||||
delete pUnit;
|
||||
return FALSE;
|
||||
|
||||
LOGWARN("rasctl tried to open an invalid file %s", file.c_str());
|
||||
|
||||
ostringstream msg;
|
||||
msg << "Error : File open error [" << file << "]";
|
||||
return ReturnStatus(fp, false, msg.str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -585,109 +621,103 @@ BOOL ProcessCmd(FILE *fp, int id, int un, int cmd, int type, char *file)
|
||||
map[id * UnitNum + un] = pUnit;
|
||||
|
||||
// Re-map the controller
|
||||
MapControler(fp, map);
|
||||
bool status = MapController(map);
|
||||
if (status) {
|
||||
type_str[0] = (char)(pUnit->GetID() >> 24);
|
||||
type_str[1] = (char)(pUnit->GetID() >> 16);
|
||||
type_str[2] = (char)(pUnit->GetID() >> 8);
|
||||
type_str[3] = (char)(pUnit->GetID());
|
||||
type_str[4] = '\0';
|
||||
|
||||
LOGINFO("rasctl added new %s device. ID: %d UN: %d", type_str, id, un);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Is this a valid command?
|
||||
if (cmd > 4) {
|
||||
FPRT(fp, "Error : Invalid command\n");
|
||||
LOGINFO("rasctl sent an invalid command: %d",cmd);
|
||||
return FALSE;
|
||||
return ReturnStatus(fp, status, status ? "" : "Error : SASI and SCSI can't be mixed\n");
|
||||
}
|
||||
|
||||
// Does the controller exist?
|
||||
if (ctrl[id] == NULL) {
|
||||
FPRT(fp, "Error : No such device\n");
|
||||
LOGWARN("rasctl sent a command for invalid controller %d", id);
|
||||
return FALSE;
|
||||
|
||||
return ReturnStatus(fp, false, "Error : No such device");
|
||||
}
|
||||
|
||||
// Does the unit exist?
|
||||
pUnit = disk[id * UnitNum + un];
|
||||
if (pUnit == NULL) {
|
||||
FPRT(fp, "Error : No such device\n");
|
||||
LOGWARN("rasctl sent a command for invalid unit ID %d UN %d", id, un);
|
||||
return FALSE;
|
||||
}
|
||||
type_str[0] = (char)(map[id * UnitNum + un]->GetID() >> 24);
|
||||
type_str[1] = (char)(map[id * UnitNum + un]->GetID() >> 16);
|
||||
type_str[2] = (char)(map[id * UnitNum + un]->GetID() >> 8);
|
||||
type_str[3] = (char)(map[id * UnitNum + un]->GetID());
|
||||
type_str[4] = '\0';
|
||||
|
||||
return ReturnStatus(fp, false, "Error : No such device");
|
||||
}
|
||||
|
||||
type_str[0] = (char)(pUnit->GetID() >> 24);
|
||||
type_str[1] = (char)(pUnit->GetID() >> 16);
|
||||
type_str[2] = (char)(pUnit->GetID() >> 8);
|
||||
type_str[3] = (char)(pUnit->GetID());
|
||||
type_str[4] = '\0';
|
||||
|
||||
// Disconnect Command
|
||||
if (cmd == 1) { // DETACH
|
||||
type_str[0] = (char)(map[id * UnitNum + un]->GetID() >> 24);
|
||||
type_str[1] = (char)(map[id * UnitNum + un]->GetID() >> 16);
|
||||
type_str[2] = (char)(map[id * UnitNum + un]->GetID() >> 8);
|
||||
type_str[3] = (char)(map[id * UnitNum + un]->GetID());
|
||||
type_str[4] = '\0';
|
||||
if (cmd == DETACH) {
|
||||
LOGINFO("rasctl command disconnect %s at ID: %d UN: %d", type_str, id, un);
|
||||
|
||||
// Free the existing unit
|
||||
map[id * UnitNum + un] = NULL;
|
||||
|
||||
// Re-map the controller
|
||||
MapControler(fp, map);
|
||||
return TRUE;
|
||||
bool status = MapController(map);
|
||||
return ReturnStatus(fp, status, status ? "" : "Error : SASI and SCSI can't be mixed\n");
|
||||
}
|
||||
|
||||
// Valid only for MO or CD
|
||||
if (pUnit->GetID() != MAKEID('S', 'C', 'M', 'O') &&
|
||||
pUnit->GetID() != MAKEID('S', 'C', 'C', 'D')) {
|
||||
FPRT(fp, "Error : Operation denied (Device type %s isn't removable)\n", type_str);
|
||||
LOGWARN("rasctl sent an Insert/Eject/Protect command (%d) for incompatible type %s", cmd, type_str);
|
||||
return FALSE;
|
||||
|
||||
ostringstream msg;
|
||||
msg << "Error : Operation denied (Device type " << type_str << " isn't removable)";
|
||||
return ReturnStatus(fp, false, msg.str());
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 2: // INSERT
|
||||
// Set the file path
|
||||
filepath.SetPath(file);
|
||||
LOGINFO("rasctl commanded insert file %s into %s ID: %d UN: %d", file, type_str, id, un);
|
||||
case INSERT:
|
||||
filepath.SetPath(params.c_str());
|
||||
LOGINFO("rasctl commanded insert file %s into %s ID: %d UN: %d", params.c_str(), type_str, id, un);
|
||||
|
||||
// Open the file
|
||||
if (!pUnit->Open(filepath)) {
|
||||
FPRT(fp, "Error : File open error [%s]\n", file);
|
||||
return FALSE;
|
||||
ostringstream msg;
|
||||
msg << "Error : File open error [" << params << "]";
|
||||
|
||||
return ReturnStatus(fp, false, msg.str());
|
||||
}
|
||||
break;
|
||||
|
||||
case 3: // EJECT
|
||||
case EJECT:
|
||||
LOGINFO("rasctl commands eject %s ID: %d UN: %d", type_str, id, un);
|
||||
pUnit->Eject(TRUE);
|
||||
break;
|
||||
|
||||
case 4: // PROTECT
|
||||
case PROTECT:
|
||||
if (pUnit->GetID() != MAKEID('S', 'C', 'M', 'O')) {
|
||||
FPRT(fp, "Error : Operation denied(Deveice isn't MO)\n");
|
||||
LOGWARN("rasctl sent an invalid PROTECT command for %s ID: %d UN: %d", type_str, id, un);
|
||||
return FALSE;
|
||||
|
||||
return ReturnStatus(fp, false, "Error : Operation denied (Device isn't MO)");
|
||||
}
|
||||
LOGINFO("rasctl is setting write protect to %d for %s ID: %d UN: %d",!pUnit->IsWriteP(), type_str, id, un);
|
||||
pUnit->WriteP(!pUnit->IsWriteP());
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGWARN("Received unknown command from rasctl: %d", cmd);
|
||||
ASSERT(FALSE);
|
||||
return FALSE;
|
||||
ostringstream msg;
|
||||
msg << "Received unknown command from rasctl: " << cmd;
|
||||
LOGWARN("%s", msg.str().c_str());
|
||||
return ReturnStatus(fp, false, msg.str());
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
return ReturnStatus(fp, true);
|
||||
}
|
||||
|
||||
bool has_suffix(const char* string, const char* suffix) {
|
||||
int string_len = strlen(string);
|
||||
int suffix_len = strlen(suffix);
|
||||
return (string_len > suffix_len)
|
||||
&& (xstrcasecmp(string + (string_len - suffix_len), suffix) == 0);
|
||||
bool has_suffix(const string& filename, const string& suffix) {
|
||||
return filename.size() >= suffix.size() && !filename.compare(filename.size() - suffix.size(), suffix.size(), suffix);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -698,19 +728,18 @@ bool has_suffix(const char* string, const char* suffix) {
|
||||
#ifdef BAREMETAL
|
||||
BOOL ParseConfig(int argc, char* argv[])
|
||||
{
|
||||
FRESULT fr;
|
||||
FIL fp;
|
||||
char line[512];
|
||||
int id;
|
||||
int un;
|
||||
int type;
|
||||
DeviceType type;
|
||||
char *argID;
|
||||
char *argPath;
|
||||
int len;
|
||||
char *ext;
|
||||
|
||||
// Mount the SD card
|
||||
fr = f_mount(&fatfs, "", 1);
|
||||
FRESULT fr = f_mount(&fatfs, "", 1);
|
||||
if (fr != FR_OK) {
|
||||
FPRT(stderr, "Error : SD card mount failed.\n");
|
||||
return FALSE;
|
||||
@ -807,12 +836,9 @@ BOOL ParseConfig(int argc, char* argv[])
|
||||
continue;
|
||||
}
|
||||
|
||||
// Initialize device type
|
||||
type = -1;
|
||||
|
||||
// Check ethernet and host bridge
|
||||
if (xstrcasecmp(argPath, "bridge") == 0) {
|
||||
type = 4;
|
||||
if (!strcasecmp(argPath, "bridge")) {
|
||||
type = BR;
|
||||
} else {
|
||||
// Check the path length
|
||||
len = strlen(argPath);
|
||||
@ -832,29 +858,24 @@ BOOL ParseConfig(int argc, char* argv[])
|
||||
|
||||
// Figure out what the type is
|
||||
ext = &argPath[len - 3];
|
||||
if (xstrcasecmp(ext, "hdf") == 0 ||
|
||||
xstrcasecmp(ext, "hds") == 0 ||
|
||||
xstrcasecmp(ext, "hdn") == 0 ||
|
||||
xstrcasecmp(ext, "hdi") == 0 || xstrcasecmp(ext, "nhd") == 0 ||
|
||||
xstrcasecmp(ext, "hda") == 0) {
|
||||
// HD(SASI/SCSI)
|
||||
type = 0;
|
||||
} else if (strcasecmp(ext, "mos") == 0) {
|
||||
// MO
|
||||
type = 2;
|
||||
} else if (strcasecmp(ext, "iso") == 0) {
|
||||
// CD
|
||||
type = 3;
|
||||
} else {
|
||||
// Cannot determine the file type
|
||||
FPRT(stderr,
|
||||
"Error : Invalid argument(file type) [%s]\n", ext);
|
||||
goto parse_error;
|
||||
if (!strcasecmp(ext, "hdf") || !strcasecmp(ext, "hds") || !strcasecmp(ext, "hdn") ||
|
||||
!strcasecmp(ext, "hdi") || !strcasecmp(ext, "nhd") || !strcasecmp(ext, "hda")) {
|
||||
type = SASI_HD;
|
||||
} else if (!strcasecmp(ext, "mos")) {
|
||||
type = MO;
|
||||
} else if (!strcasecmp(ext, "iso")) {
|
||||
type = CD;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
if (!ProcessCmd(stderr, id, un, 0, type, argPath)) {
|
||||
Command command;
|
||||
command.set_id(id);
|
||||
command.set_un(un);
|
||||
command.set_cmd(0);
|
||||
command.set_type(type);
|
||||
command.set_file(argPath);
|
||||
if (!ProcessCmd(stderr, command)) {
|
||||
goto parse_error;
|
||||
}
|
||||
}
|
||||
@ -863,7 +884,7 @@ BOOL ParseConfig(int argc, char* argv[])
|
||||
f_close(&fp);
|
||||
|
||||
// Display the device list
|
||||
ListDevice(stdout);
|
||||
fprintf(stdout, "%s", ListDevice().c_str());
|
||||
|
||||
return TRUE;
|
||||
|
||||
@ -884,33 +905,28 @@ bool ParseArgument(int argc, char* argv[])
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "-IiHhL:l:D:d:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'I':
|
||||
switch (tolower(opt)) {
|
||||
case 'i':
|
||||
is_sasi = false;
|
||||
max_id = 7;
|
||||
id = -1;
|
||||
continue;
|
||||
|
||||
case 'H':
|
||||
case 'h':
|
||||
is_sasi = true;
|
||||
max_id = 15;
|
||||
id = -1;
|
||||
continue;
|
||||
|
||||
case 'L':
|
||||
case 'l':
|
||||
log_level = optarg;
|
||||
continue;
|
||||
|
||||
case 'D':
|
||||
case 'd': {
|
||||
char* end;
|
||||
id = strtol(optarg, &end, 10);
|
||||
if (*end || (id < 0) || (max_id < id)) {
|
||||
fprintf(stderr, "%s: invalid %s (0-%d)\n",
|
||||
optarg, is_sasi ? "HD" : "ID", max_id);
|
||||
if (*end || id < 0 || max_id < id) {
|
||||
cerr << optarg << ": invalid " << (is_sasi ? "HD" : "ID") << " (0-" << max_id << ")" << endl;
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@ -924,34 +940,28 @@ bool ParseArgument(int argc, char* argv[])
|
||||
}
|
||||
|
||||
if (id < 0) {
|
||||
fprintf(stderr, "%s: ID not specified\n", optarg);
|
||||
cerr << optarg << ": ID not specified" << endl;
|
||||
return false;
|
||||
} else if (disk[id] && !disk[id]->IsNULL()) {
|
||||
fprintf(stderr, "%d: duplicate ID\n", id);
|
||||
cerr << id << ": duplicate ID" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
char* path = optarg;
|
||||
int type = -1;
|
||||
if (has_suffix(path, ".hdf")
|
||||
|| has_suffix(path, ".hds")
|
||||
|| has_suffix(path, ".hdn")
|
||||
|| has_suffix(path, ".hdi")
|
||||
|| has_suffix(path, ".hda")
|
||||
|| has_suffix(path, ".nhd")) {
|
||||
type = rasctl_dev_sasi_hd;
|
||||
string path = optarg;
|
||||
DeviceType type = SASI_HD;
|
||||
if (has_suffix(path, ".hdf") || has_suffix(path, ".hds") || has_suffix(path, ".hdn")
|
||||
|| has_suffix(path, ".hdi") || has_suffix(path, ".hda") || has_suffix(path, ".nhd")) {
|
||||
type = SASI_HD;
|
||||
} else if (has_suffix(path, ".mos")) {
|
||||
type = rasctl_dev_mo;
|
||||
type = MO;
|
||||
} else if (has_suffix(path, ".iso")) {
|
||||
type = rasctl_dev_cd;
|
||||
} else if (xstrcasecmp(path, "bridge") == 0) {
|
||||
type = rasctl_dev_br;
|
||||
} else if (xstrcasecmp(path, "daynaport") == 0) {
|
||||
type = rasctl_dev_daynaport;
|
||||
type = CD;
|
||||
} else if (path == "bridge") {
|
||||
type = BR;
|
||||
} else if (path == "daynaport") {
|
||||
type = DAYNAPORT;
|
||||
} else {
|
||||
// Cannot determine the file type or the basename is missing
|
||||
fprintf(stderr,
|
||||
"%s: unknown file extension or basename is missing\n", path);
|
||||
cerr << path << ": unknown file extension or basename is missing" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -962,40 +972,22 @@ bool ParseArgument(int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
if (!ProcessCmd(stderr, id, un, 0, type, path)) {
|
||||
Command command;
|
||||
command.set_id(id);
|
||||
command.set_un(un);
|
||||
command.set_cmd(ATTACH);
|
||||
command.set_type(type);
|
||||
command.set_params(path);
|
||||
if (!ProcessCmd(stderr, command)) {
|
||||
return false;
|
||||
}
|
||||
id = -1;
|
||||
}
|
||||
|
||||
// Evaluate log level
|
||||
if (log_level == "trace") {
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
}
|
||||
else if (log_level == "debug") {
|
||||
spdlog::set_level(spdlog::level::debug);
|
||||
}
|
||||
else if (log_level == "info") {
|
||||
spdlog::set_level(spdlog::level::info);
|
||||
}
|
||||
else if (log_level == "warn") {
|
||||
spdlog::set_level(spdlog::level::warn);
|
||||
}
|
||||
else if (log_level == "err") {
|
||||
spdlog::set_level(spdlog::level::err);
|
||||
}
|
||||
else if (log_level == "critical") {
|
||||
spdlog::set_level(spdlog::level::critical);
|
||||
}
|
||||
else if (log_level == "off") {
|
||||
spdlog::set_level(spdlog::level::off);
|
||||
}
|
||||
else {
|
||||
cerr << "Invalid log level '" << log_level << "', falling back to 'trace'" << endl;
|
||||
}
|
||||
SetLogLevel(log_level);
|
||||
|
||||
// Display the device list
|
||||
ListDevice(stdout);
|
||||
fprintf(stdout, "%s", ListDevice().c_str());
|
||||
return true;
|
||||
}
|
||||
#endif // BAREMETAL
|
||||
@ -1008,13 +1000,11 @@ bool ParseArgument(int argc, char* argv[])
|
||||
//---------------------------------------------------------------------------
|
||||
void FixCpu(int cpu)
|
||||
{
|
||||
cpu_set_t cpuset;
|
||||
int cpus;
|
||||
|
||||
// Get the number of CPUs
|
||||
cpu_set_t cpuset;
|
||||
CPU_ZERO(&cpuset);
|
||||
sched_getaffinity(0, sizeof(cpu_set_t), &cpuset);
|
||||
cpus = CPU_COUNT(&cpuset);
|
||||
int cpus = CPU_COUNT(&cpuset);
|
||||
|
||||
// Set the thread affinity
|
||||
if (cpu < cpus) {
|
||||
@ -1031,22 +1021,11 @@ void FixCpu(int cpu)
|
||||
//---------------------------------------------------------------------------
|
||||
static void *MonThread(void *param)
|
||||
{
|
||||
struct sched_param schedparam;
|
||||
struct sockaddr_in client;
|
||||
socklen_t len;
|
||||
int fd;
|
||||
FILE *fp;
|
||||
char buf[BUFSIZ];
|
||||
char *p;
|
||||
int i;
|
||||
char *argv[5];
|
||||
int id;
|
||||
int un;
|
||||
int cmd;
|
||||
int type;
|
||||
char *file;
|
||||
|
||||
// Scheduler Settings
|
||||
struct sched_param schedparam;
|
||||
schedparam.sched_priority = 0;
|
||||
sched_setscheduler(0, SCHED_IDLE, &schedparam);
|
||||
|
||||
@ -1061,78 +1040,64 @@ static void *MonThread(void *param)
|
||||
// Set up the monitor socket to receive commands
|
||||
listen(monsocket, 1);
|
||||
|
||||
while (1) {
|
||||
try {
|
||||
while (true) {
|
||||
// Wait for connection
|
||||
memset(&client, 0, sizeof(client));
|
||||
len = sizeof(client);
|
||||
fd = accept(monsocket, (struct sockaddr*)&client, &len);
|
||||
struct sockaddr_in client;
|
||||
socklen_t socklen = sizeof(client);
|
||||
memset(&client, 0, socklen);
|
||||
fd = accept(monsocket, (struct sockaddr*)&client, &socklen);
|
||||
if (fd < 0) {
|
||||
break;
|
||||
throw ioexception("accept() failed");
|
||||
}
|
||||
|
||||
// Fetch the command
|
||||
fp = fdopen(fd, "r+");
|
||||
p = fgets(buf, BUFSIZ, fp);
|
||||
|
||||
// Failed to get the command
|
||||
if (!p) {
|
||||
goto next;
|
||||
if (!fp) {
|
||||
throw ioexception("fdopen() failed");
|
||||
}
|
||||
|
||||
// Remove the newline character
|
||||
p[strlen(p) - 1] = 0;
|
||||
Command command;
|
||||
command.ParseFromString(DeserializeProtobufData(fd));
|
||||
|
||||
// List all of the devices
|
||||
if (xstrncasecmp(p, "list", 4) == 0) {
|
||||
ListDevice(fp);
|
||||
goto next;
|
||||
if (command.cmd() == LIST) {
|
||||
Result result;
|
||||
result.set_msg(ListDevice() + "\n");
|
||||
result.set_status(true);
|
||||
|
||||
string data;
|
||||
result.SerializeToString(&data);
|
||||
SerializeProtobufData(fp, data);
|
||||
}
|
||||
|
||||
// Parameter separation
|
||||
argv[0] = p;
|
||||
for (i = 1; i < 5; i++) {
|
||||
// Skip parameter values
|
||||
while (*p && (*p != ' ')) {
|
||||
p++;
|
||||
else if (command.cmd() == LOG_LEVEL) {
|
||||
SetLogLevel(command.params());
|
||||
}
|
||||
|
||||
// Replace spaces with null characters
|
||||
while (*p && (*p == ' ')) {
|
||||
*p++ = 0;
|
||||
}
|
||||
|
||||
// The parameters were lost
|
||||
if (!*p) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Recognized as a parameter
|
||||
argv[i] = p;
|
||||
}
|
||||
|
||||
// Failed to get all parameters
|
||||
if (i < 5) {
|
||||
goto next;
|
||||
}
|
||||
|
||||
// ID, unit, command, type, file
|
||||
id = atoi(argv[0]);
|
||||
un = atoi(argv[1]);
|
||||
cmd = atoi(argv[2]);
|
||||
type = atoi(argv[3]);
|
||||
file = argv[4];
|
||||
|
||||
// Wait until we becom idle
|
||||
else {
|
||||
// Wait until we become idle
|
||||
while (active) {
|
||||
usleep(500 * 1000);
|
||||
}
|
||||
|
||||
// Execute the command
|
||||
ProcessCmd(fp, id, un, cmd, type, file);
|
||||
ProcessCmd(fp, command);
|
||||
}
|
||||
|
||||
next:
|
||||
// Release the connection
|
||||
fclose(fp);
|
||||
fp = NULL;
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
}
|
||||
catch(const ioexception& e) {
|
||||
LOGERROR("%s", e.getmsg());
|
||||
|
||||
// Fall through
|
||||
}
|
||||
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
@ -1156,7 +1121,6 @@ int main(int argc, char* argv[])
|
||||
{
|
||||
#endif // BAREMETAL
|
||||
int i;
|
||||
int ret;
|
||||
int actid;
|
||||
DWORD now;
|
||||
BUS::phase_t phase;
|
||||
@ -1167,15 +1131,15 @@ int main(int argc, char* argv[])
|
||||
struct sched_param schparam;
|
||||
#endif // BAREMETAL
|
||||
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
set_level(level::trace);
|
||||
// Create a thread-safe stdout logger to process the log messages
|
||||
auto logger = spdlog::stdout_color_mt("rascsi stdout logger");
|
||||
auto logger = stdout_color_mt("rascsi stdout logger");
|
||||
|
||||
// Output the Banner
|
||||
Banner(argc, argv);
|
||||
|
||||
// Initialize
|
||||
ret = 0;
|
||||
int ret = 0;
|
||||
if (!Init()) {
|
||||
ret = EPERM;
|
||||
goto init_exit;
|
||||
|
@ -11,51 +11,62 @@
|
||||
|
||||
#include "os.h"
|
||||
#include "rascsi_version.h"
|
||||
#include "rasctl.h"
|
||||
#include "exceptions.h"
|
||||
#include "rasutil.h"
|
||||
#include "rasctl_interface.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace rasctl_interface;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Send Command
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
BOOL SendCommand(char *buf)
|
||||
bool SendCommand(const Command& command)
|
||||
{
|
||||
int fd;
|
||||
struct sockaddr_in server;
|
||||
FILE *fp;
|
||||
|
||||
// Create a socket to send the command
|
||||
fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
struct sockaddr_in server;
|
||||
memset(&server, 0, sizeof(server));
|
||||
server.sin_family = PF_INET;
|
||||
server.sin_port = htons(6868);
|
||||
server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
int fd = socket(PF_INET, SOCK_STREAM, 0);
|
||||
|
||||
// Connect
|
||||
if (connect(fd, (struct sockaddr *)&server,
|
||||
sizeof(struct sockaddr_in)) < 0) {
|
||||
fprintf(stderr, "Error : Can't connect to rascsi process\n");
|
||||
return FALSE;
|
||||
if (connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) < 0) {
|
||||
cerr << "Error : Can't connect to rascsi process" << endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send the command
|
||||
fp = fdopen(fd, "r+");
|
||||
setvbuf(fp, NULL, _IONBF, 0);
|
||||
fputs(buf, fp);
|
||||
FILE *fp = fdopen(fd, "r+");
|
||||
|
||||
string data;
|
||||
command.SerializeToString(&data);
|
||||
SerializeProtobufData(fp, data);
|
||||
|
||||
// Receive the message
|
||||
while (1) {
|
||||
if (fgets((char *)buf, BUFSIZ, fp) == NULL) {
|
||||
break;
|
||||
bool status = true;
|
||||
try {
|
||||
Result result;
|
||||
result.ParseFromString(DeserializeProtobufData(fd));
|
||||
|
||||
status = result.status();
|
||||
|
||||
if (!result.msg().empty()) {
|
||||
cout << result.msg();
|
||||
}
|
||||
printf("%s", buf);
|
||||
}
|
||||
catch(const ioexception& e) {
|
||||
cerr << "Error : " << e.getmsg() << endl;
|
||||
|
||||
// Fall through
|
||||
}
|
||||
|
||||
// Close the socket when we're done
|
||||
fclose(fp);
|
||||
close(fd);
|
||||
|
||||
return TRUE;
|
||||
return status;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -65,48 +76,34 @@ BOOL SendCommand(char *buf)
|
||||
//---------------------------------------------------------------------------
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int opt;
|
||||
int id;
|
||||
int un;
|
||||
int cmd;
|
||||
int type;
|
||||
char *file;
|
||||
BOOL list;
|
||||
int len;
|
||||
char *ext;
|
||||
char buf[BUFSIZ];
|
||||
|
||||
id = -1;
|
||||
un = 0;
|
||||
cmd = -1;
|
||||
type = -1;
|
||||
file = NULL;
|
||||
list = FALSE;
|
||||
|
||||
// Display help
|
||||
if (argc < 2) {
|
||||
fprintf(stderr, "SCSI Target Emulator RaSCSI Controller\n");
|
||||
fprintf(stderr, "version %s (%s, %s)\n",
|
||||
rascsi_get_version_string(),
|
||||
__DATE__,
|
||||
__TIME__);
|
||||
fprintf(stderr,
|
||||
"Usage: %s -i ID [-u UNIT] [-c CMD] [-t TYPE] [-f FILE]\n",
|
||||
argv[0]);
|
||||
fprintf(stderr, " where ID := {0|1|2|3|4|5|6|7}\n");
|
||||
fprintf(stderr, " UNIT := {0|1} default setting is 0.\n");
|
||||
fprintf(stderr, " CMD := {attach|detach|insert|eject|protect}\n");
|
||||
fprintf(stderr, " TYPE := {hd|mo|cd|bridge|daynaport}\n");
|
||||
fprintf(stderr, " FILE := image file path\n");
|
||||
fprintf(stderr, " CMD is 'attach' or 'insert' and FILE parameter is required.\n");
|
||||
fprintf(stderr, "Usage: %s -l\n", argv[0]);
|
||||
fprintf(stderr, " Print device list.\n");
|
||||
cerr << "SCSI Target Emulator RaSCSI Controller" << endl;
|
||||
cerr << "version " << rascsi_get_version_string() << " (" << __DATE__ << ", " << __TIME__ << ")" << endl;
|
||||
cerr << "Usage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-t TYPE] [-f FILE] [-s LOG_LEVEL]" << endl;
|
||||
cerr << " where ID := {0|1|2|3|4|5|6|7}" << endl;
|
||||
cerr << " UNIT := {0|1} default setting is 0." << endl;
|
||||
cerr << " CMD := {attach|detach|insert|eject|protect}" << endl;
|
||||
cerr << " TYPE := {hd|mo|cd|bridge|daynaport}" << endl;
|
||||
cerr << " FILE := image file path" << endl;
|
||||
cerr << " LOG_LEVEL := log level" << endl;
|
||||
cerr << " If CMD is 'attach' or 'insert' the FILE parameter is required." << endl;
|
||||
cerr << "Usage: " << argv[0] << " -l" << endl;
|
||||
cerr << " Print device list." << endl;
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Parse the arguments
|
||||
int opt;
|
||||
int id = -1;
|
||||
int un = 0;
|
||||
Operation cmd = LIST;
|
||||
DeviceType type = UNDEFINED;
|
||||
string params;
|
||||
opterr = 0;
|
||||
while ((opt = getopt(argc, argv, "i:u:c:t:f:l")) != -1) {
|
||||
|
||||
while ((opt = getopt(argc, argv, "i:u:c:t:f:s:l")) != -1) {
|
||||
switch (opt) {
|
||||
case 'i':
|
||||
id = optarg[0] - '0';
|
||||
@ -117,158 +114,142 @@ int main(int argc, char* argv[])
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
switch (optarg[0]) {
|
||||
case 'a': // ATTACH
|
||||
case 'A':
|
||||
cmd = 0;
|
||||
switch (tolower(optarg[0])) {
|
||||
case 'a':
|
||||
cmd = ATTACH;
|
||||
break;
|
||||
case 'd': // DETACH
|
||||
case 'D':
|
||||
cmd = 1;
|
||||
|
||||
case 'd':
|
||||
cmd = DETACH;
|
||||
break;
|
||||
case 'i': // INSERT
|
||||
case 'I':
|
||||
cmd = 2;
|
||||
|
||||
case 'i':
|
||||
cmd = INSERT;
|
||||
break;
|
||||
case 'e': // EJECT
|
||||
case 'E':
|
||||
cmd = 3;
|
||||
|
||||
case 'e':
|
||||
cmd = EJECT;
|
||||
break;
|
||||
case 'p': // PROTECT
|
||||
case 'P':
|
||||
cmd = 4;
|
||||
|
||||
case 'p':
|
||||
cmd = PROTECT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
switch (optarg[0]) {
|
||||
case 's': // HD(SASI)
|
||||
case 'S':
|
||||
case 'h': // HD(SCSI)
|
||||
case 'H':
|
||||
// rascsi will figure out if this should be SASI or
|
||||
// SCSI later in the process....
|
||||
type = rasctl_dev_sasi_hd;
|
||||
switch (tolower(optarg[0])) {
|
||||
case 's':
|
||||
type = SASI_HD;
|
||||
break;
|
||||
case 'm': // MO
|
||||
case 'M':
|
||||
type = rasctl_dev_mo;
|
||||
|
||||
case 'h':
|
||||
type = SCSI_HD;
|
||||
break;
|
||||
case 'c': // CD
|
||||
case 'C':
|
||||
type = rasctl_dev_cd;
|
||||
|
||||
case 'm':
|
||||
type = MO;
|
||||
break;
|
||||
case 'b': // BRIDGE
|
||||
case 'B':
|
||||
type = rasctl_dev_br;
|
||||
|
||||
case 'c':
|
||||
type = CD;
|
||||
break;
|
||||
// case 'n': // Nuvolink
|
||||
// case 'N':
|
||||
// type = rasctl_dev_nuvolink;
|
||||
|
||||
case 'b':
|
||||
type = BR;
|
||||
break;
|
||||
|
||||
// case 'n':
|
||||
// type = NUVOLINK;
|
||||
// break;
|
||||
case 'd': // DaynaPort
|
||||
case 'D':
|
||||
type = rasctl_dev_daynaport;
|
||||
|
||||
case 'd':
|
||||
type = DAYNAPORT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
file = optarg;
|
||||
params = optarg;
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
list = TRUE;
|
||||
cmd = LIST;
|
||||
break;
|
||||
|
||||
case 's':
|
||||
cmd = LOG_LEVEL;
|
||||
params = optarg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Command command;
|
||||
|
||||
if (cmd == LOG_LEVEL) {
|
||||
command.set_cmd(LOG_LEVEL);
|
||||
command.set_params(params);
|
||||
SendCommand(command);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// List display only
|
||||
if (id < 0 && cmd < 0 && type < 0 && file == NULL && list) {
|
||||
sprintf(buf, "list\n");
|
||||
SendCommand(buf);
|
||||
if (cmd == LIST || (id < 0 && type == UNDEFINED && params.empty())) {
|
||||
command.set_cmd(LIST);
|
||||
SendCommand(command);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// Check the ID number
|
||||
if (id < 0 || id > 7) {
|
||||
fprintf(stderr, "%s Error : Invalid ID %d \n", __PRETTY_FUNCTION__, id);
|
||||
cerr << __PRETTY_FUNCTION__ << " Error : Invalid ID " << id << endl;
|
||||
exit(EINVAL);
|
||||
}
|
||||
|
||||
// Check the unit number
|
||||
if (un < 0 || un > 1) {
|
||||
fprintf(stderr, "%s Error : Invalid UNIT %d \n", __PRETTY_FUNCTION__, un);
|
||||
cerr << __PRETTY_FUNCTION__ << " Error : Invalid UNIT " << un << endl;
|
||||
exit(EINVAL);
|
||||
}
|
||||
|
||||
// Command check
|
||||
if (cmd < 0) {
|
||||
cmd = 0; // Default command is ATTATCH
|
||||
}
|
||||
|
||||
// Type Check
|
||||
if (cmd == 0 && type < 0) {
|
||||
|
||||
if (cmd == ATTACH && type == UNDEFINED) {
|
||||
// Try to determine the file type from the extension
|
||||
len = file ? strlen(file) : 0;
|
||||
if (len > 4 && file[len - 4] == '.') {
|
||||
ext = &file[len - 3];
|
||||
if (xstrcasecmp(ext, "hdf") == 0 ||
|
||||
xstrcasecmp(ext, "hds") == 0 ||
|
||||
xstrcasecmp(ext, "hdn") == 0 ||
|
||||
xstrcasecmp(ext, "hdi") == 0 ||
|
||||
xstrcasecmp(ext, "nhd") == 0 ||
|
||||
xstrcasecmp(ext, "hda") == 0) {
|
||||
// HD(SASI/SCSI)
|
||||
type = 0;
|
||||
} else if (xstrcasecmp(ext, "mos") == 0) {
|
||||
// MO
|
||||
type = 2;
|
||||
} else if (xstrcasecmp(ext, "iso") == 0) {
|
||||
// CD
|
||||
type = 3;
|
||||
int len = params.length();
|
||||
if (len > 4 && params[len - 4] == '.') {
|
||||
string ext = params.substr(len - 3);
|
||||
if (ext == "hdf" || ext == "hds" || ext == "hdn" || ext == "hdi" || ext == "nhd" || ext == "hda") {
|
||||
type = SASI_HD;
|
||||
} else if (ext == "mos") {
|
||||
type = MO;
|
||||
} else if (ext == "iso") {
|
||||
type = CD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type < 0) {
|
||||
fprintf(stderr, "Error : Invalid type\n");
|
||||
// File check (command is ATTACH and type is HD, for CD and MO the medium (=file) may be inserted later)
|
||||
if (cmd == ATTACH && (type == SASI_HD || type == SCSI_HD) && params.empty()) {
|
||||
cerr << "Error : Invalid file path" << endl;
|
||||
exit(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
// File check (command is ATTACH and type is HD)
|
||||
if (cmd == 0 && type >= 0 && type <= 1) {
|
||||
if (!file) {
|
||||
fprintf(stderr, "Error : Invalid file path\n");
|
||||
exit(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
// File check (command is INSERT)
|
||||
if (cmd == 2) {
|
||||
if (!file) {
|
||||
fprintf(stderr, "Error : Invalid file path\n");
|
||||
if (cmd == INSERT && params.empty()) {
|
||||
cerr << "Error : Invalid file path" << endl;
|
||||
exit(EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
// Set unnecessary type to 0
|
||||
if (type < 0) {
|
||||
type = 0;
|
||||
}
|
||||
|
||||
// Generate the command and send it
|
||||
sprintf(buf, "%d %d %d %d %s\n", id, un, cmd, type, file ? file : "-");
|
||||
if (!SendCommand(buf)) {
|
||||
exit(ENOTCONN);
|
||||
command.set_id(id);
|
||||
command.set_un(un);
|
||||
command.set_cmd(cmd);
|
||||
command.set_type(type);
|
||||
if (!params.empty()) {
|
||||
command.set_params(params);
|
||||
}
|
||||
|
||||
// Display the list
|
||||
if (list) {
|
||||
sprintf(buf, "list\n");
|
||||
SendCommand(buf);
|
||||
if (!SendCommand(command)) {
|
||||
exit(ENOTCONN);
|
||||
}
|
||||
|
||||
// All done!
|
||||
|
@ -1,24 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// [ Send Control Command ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
enum rasctl_dev_type : int {
|
||||
rasctl_dev_invalid = -1,
|
||||
rasctl_dev_sasi_hd = 0,
|
||||
rasctl_dev_scsi_hd = 1,
|
||||
rasctl_dev_mo = 2,
|
||||
rasctl_dev_cd = 3,
|
||||
rasctl_dev_br = 4,
|
||||
rasctl_dev_nuvolink = 5,
|
||||
rasctl_dev_daynaport = 6,
|
||||
};
|
39
src/raspberrypi/rasctl_interface.proto
Normal file
39
src/raspberrypi/rasctl_interface.proto
Normal file
@ -0,0 +1,39 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
package rasctl_interface;
|
||||
|
||||
enum Operation {
|
||||
LIST = 0;
|
||||
ATTACH = 1;
|
||||
DETACH = 2;
|
||||
INSERT = 3;
|
||||
EJECT = 4;
|
||||
PROTECT = 5;
|
||||
LOG_LEVEL = 6;
|
||||
}
|
||||
|
||||
enum DeviceType {
|
||||
UNDEFINED = 0;
|
||||
SASI_HD = 1;
|
||||
SCSI_HD = 2;
|
||||
MO = 3;
|
||||
CD = 4;
|
||||
BR = 5;
|
||||
NUVOLINK = 6;
|
||||
DAYNAPORT = 7;
|
||||
}
|
||||
|
||||
message Command {
|
||||
Operation cmd = 1;
|
||||
int32 id = 2;
|
||||
int32 un = 3;
|
||||
DeviceType type = 4;
|
||||
string params = 5;
|
||||
}
|
||||
|
||||
message Result {
|
||||
bool status = 1;
|
||||
string msg = 2;
|
||||
}
|
65
src/raspberrypi/rasutil.cpp
Normal file
65
src/raspberrypi/rasutil.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2020 akuker
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
#include "exceptions.h"
|
||||
#include "rasutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Serialize/Deserialize protobuf data: Length followed by the actual data
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
void SerializeProtobufData(FILE *fp, const string& data)
|
||||
{
|
||||
// Write the size of the protobuf data as a header
|
||||
size_t size = data.length();
|
||||
fwrite(&size, sizeof(size), 1, fp);
|
||||
|
||||
// Write the actual protobuf data
|
||||
void *buf = malloc(size);
|
||||
memcpy(buf, data.data(), size);
|
||||
fwrite(buf, size, 1, fp);
|
||||
fflush(fp);
|
||||
|
||||
free(buf);
|
||||
}
|
||||
|
||||
string DeserializeProtobufData(int fd)
|
||||
{
|
||||
// First read the header with the size of the protobuf data
|
||||
size_t size;
|
||||
size_t res = read(fd, &size, sizeof(int));
|
||||
if (res != sizeof(int)) {
|
||||
// No more data
|
||||
return "";
|
||||
}
|
||||
|
||||
// Read the actual protobuf data
|
||||
void *buf = malloc(size);
|
||||
res = read(fd, buf, size);
|
||||
if (res != size) {
|
||||
free(buf);
|
||||
|
||||
throw ioexception("Missing protobuf data");
|
||||
}
|
||||
|
||||
// Read protobuf data into a string, to be converted into a protobuf data structure by the caller
|
||||
string data((const char *)buf, size);
|
||||
free(buf);
|
||||
|
||||
return data;
|
||||
}
|
21
src/raspberrypi/rasutil.h
Normal file
21
src/raspberrypi/rasutil.h
Normal file
@ -0,0 +1,21 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI (*^..^*)
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Powered by XM6 TypeG Technology.
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) 2020 akuker
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#if !defined(rasutil_h)
|
||||
#define rasutil_h
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
void SerializeProtobufData(FILE *fp, const std::string& data);
|
||||
std::string DeserializeProtobufData(int fd);
|
||||
|
||||
#endif
|
@ -23,7 +23,20 @@ class ERROR_CODES
|
||||
public:
|
||||
enum sense_key : int {
|
||||
NO_SENSE = 0x00,
|
||||
ILLEGAL_REQUEST = 0x05
|
||||
RECOVERED_ERROR = 0x01,
|
||||
NOT_READY = 0x02,
|
||||
MEDIUM_ERROR = 0x03,
|
||||
HARDWARE_ERROR = 0x04,
|
||||
ILLEGAL_REQUEST = 0x05,
|
||||
UNIT_ATTENTION = 0x06,
|
||||
DATA_PROTECT = 0x07,
|
||||
BLANK_CHECK = 0x08,
|
||||
VENDOR_SPECIFIC = 0x09,
|
||||
COPY_ABORTED = 0x0a,
|
||||
ABORTED_COMMAND = 0x0b,
|
||||
VOLUME_OVERFLOW = 0x0d,
|
||||
MISCOMPARE = 0x0e,
|
||||
COMPLETED = 0x0f
|
||||
};
|
||||
|
||||
enum asc : int {
|
||||
|
Loading…
x
Reference in New Issue
Block a user