Merge branch 'develop' into feature_meta_data

This commit is contained in:
Uwe Seimet 2021-12-19 12:07:34 +01:00
commit 29fa5c2f96
13 changed files with 243 additions and 102 deletions

View File

@ -5,6 +5,8 @@ rascsi \- Emulates SCSI devices using the Raspberry Pi GPIO pins
.B rascsi
[\fB\-F\f® \fIFOLDER\fR]
[\fB\-L\f® \fILOG_LEVEL\fR]
[\fB\-P\f® \fIACCESS_TOKEN_FILE\fR]
[\fB\-R\fR \fISCAN_DEPTH\fR]
[\fB\-h\fR]
[\fB\-n\fR \fIVENDOR:PRODUCT:REVISION\fR]
[\fB\-p\f® \fIPORT\fR]
@ -52,6 +54,12 @@ The default folder for image files. For files in this folder no absolute path ne
.BR \-L\fI " " \fILOG_LEVEL
The rascsi log level (trace, debug, info, warn, err, critical, off). The default log level is 'info'.
.TP
.BR \-P\fI " " \fIACCESS_TOKEN_FILE
Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only.
.TP
.BR \-R\fI " " \fISCAN_DEPTH
Scan for image files recursively, up to a depth of SCAN_DEPTH. Be careful when using this option with many sub-folders in the default image folder.
.TP
.BR \-h\fI " " \fI
Show a help page.
.TP

View File

@ -1,16 +1,15 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating\n\n
rascsi(1) General Commands Manual rascsi(1)
NAME
rascsi - Emulates SCSI devices using the Raspberry Pi GPIO pins
SYNOPSIS
rascsi [-F[u00AE] FOLDER] [-L[u00AE] LOG_LEVEL] [-h] [-n VENDOR:PROD
UCT:REVISION] [-p[u00AE] PORT] [-r RESERVED_IDS] [-n TYPE] [-v]
[-IDn:[u] FILE] [-HDn[:u] FILE]...
rascsi [-F[u00AE] FOLDER] [-L[u00AE] LOG_LEVEL] [-P[u00AE] ACCESS_TO
KEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p[u00AE]
PORT] [-r RESERVED_IDS] [-n TYPE] [-v] [-IDn:[u] FILE] [-HDn[:u]
FILE]...
DESCRIPTION
rascsi Emulates SCSI devices using the Raspberry Pi GPIO pins.
@ -67,6 +66,16 @@ OPTIONS
The rascsi log level (trace, debug, info, warn, err, critical,
off). The default log level is 'info'.
-P ACCESS_TOKEN_FILE
Enable authentication and read the access token from the speci
fied file. The access token file must be owned by root and must
be readable by root only.
-R SCAN_DEPTH
Scan for image files recursively, up to a depth of SCAN_DEPTH.
Be careful when using this option with many sub-folders in the
default image folder.
-h Show a help page.
-n VENDOR:PRODUCT:REVISION

View File

@ -13,6 +13,7 @@ rasctl \- Sends management commands to the rascsi process
\fB\-I\fR |
\fB\-L\fR |
\fB\-O\fR |
\fB\-P\fR |
\fB\-T\fR |
\fB\-V\fR |
\fB\-X\fR |
@ -73,6 +74,9 @@ Lists all available network interfaces provided that they are up.
.BR \-O\fI
Display the available rascsi server log levels and the current log level.
.TP
.BR \-P\fI
Prompt for the access token in case rascsi requires authentication.
.TP
.BR \-l\fI
List all of the devices that are currently being emulated by RaSCSI, as well as their current status.
.TP

View File

@ -6,8 +6,8 @@ NAME
rasctl - Sends management commands to the rascsi process
SYNOPSIS
rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -T | -V | -X |
[-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR
rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V |
-X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR
RENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST]
[-i ID [-n NAME] [-p PORT] [-r RESERVED_IDS] [-t TYPE] [-u UNIT] [-x
CURRENT_NAME:NEW_NAME]
@ -53,6 +53,9 @@ OPTIONS
-O Display the available rascsi server log levels and the current
log level.
-P Prompt for the access token in case rascsi requires authentica
tion.
-l List all of the devices that are currently being emulated by
RaSCSI, as well as their current status.

View File

@ -120,7 +120,7 @@ int protobuf_util::ReadNBytes(int fd, uint8_t *buf, int n)
}
bool protobuf_util::ReturnStatus(int fd, bool status, const string msg)
bool protobuf_util::ReturnStatus(int fd, bool status, const string msg, const PbErrorCode error_code)
{
if (!status && !msg.empty()) {
LOGERROR("%s", msg.c_str());
@ -142,6 +142,7 @@ bool protobuf_util::ReturnStatus(int fd, bool status, const string msg)
else {
PbResult result;
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
SerializeMessage(fd, result);
}

View File

@ -29,6 +29,6 @@ namespace protobuf_util
void SerializeMessage(int, const google::protobuf::Message&);
void DeserializeMessage(int, google::protobuf::Message&);
int ReadNBytes(int, uint8_t *, int);
bool ReturnStatus(int, bool = true, const string = "");
bool ReturnStatus(int, bool = true, const string = "", const PbErrorCode error_code = PbErrorCode::NO_ERROR_CODE);
bool ReturnStatus(int, bool, const ostringstream&);
}

View File

@ -33,6 +33,7 @@
#include <string>
#include <sstream>
#include <iostream>
#include <fstream>
#include <list>
#include <vector>
#include <map>
@ -67,7 +68,9 @@ pthread_t monthread; // Monitor Thread
pthread_mutex_t ctrl_mutex; // Semaphore for the ctrl array
static void *MonThread(void *param);
string current_log_level; // Some versions of spdlog do not support get_log_level()
string access_token;
set<int> reserved_ids;
int scan_depth = 0;
DeviceFactory& device_factory = DeviceFactory::instance();
RascsiImage rascsi_image;
RascsiResponse rascsi_response(&device_factory, &rascsi_image);
@ -373,6 +376,43 @@ bool MapController(Device **map)
return status;
}
bool ReadAccessToken(const char *filename)
{
struct stat st;
if (stat(filename, &st) || !S_ISREG(st.st_mode)) {
cerr << "Can't access token file '" << optarg << "'" << endl;
return false;
}
if (st.st_uid || st.st_gid || (st.st_mode & (S_IROTH | S_IWOTH | S_IRGRP | S_IWGRP))) {
cerr << "Access token file '" << optarg << "' must be owned by root and readable by root only" << endl;
return false;
}
ifstream token_file(filename, ifstream::in);
if (token_file.fail()) {
cerr << "Can't open access token file '" << optarg << "'" << endl;
return false;
}
getline(token_file, access_token);
if (token_file.fail()) {
token_file.close();
cerr << "Can't read access token file '" << optarg << "'" << endl;
return false;
}
if (access_token.empty()) {
token_file.close();
cerr << "Access token file '" << optarg << "' must not be empty" << endl;
return false;
}
token_file.close();
return true;
}
string ValidateLunSetup(const PbCommand& command, const vector<Device *>& existing_devices)
{
// Mapping of available LUNs (bit vector) to devices
@ -972,7 +1012,8 @@ bool ProcessCmd(int fd, const PbDeviceDefinition& pb_device, const PbCommand& co
assert(dryRun);
break;
case NONE:
case CHECK_AUTHENTICATION:
case NO_OPERATION:
// Do nothing, just log
LOGTRACE("Received %s command", PbOperation_Name(operation).c_str());
break;
@ -1159,7 +1200,7 @@ bool ParseArgument(int argc, char* argv[], int& port)
opterr = 1;
int opt;
while ((opt = getopt(argc, argv, "-IiHhb:d:n:p:r:t:D:F:L:")) != -1) {
while ((opt = getopt(argc, argv, "-IiHhb:d:n:p:r:t:D:F:L:P:R:")) != -1) {
switch (opt) {
// The three options below are kind of a compound option with two letters
case 'i':
@ -1204,6 +1245,13 @@ bool ParseArgument(int argc, char* argv[], int& port)
log_level = optarg;
continue;
case 'R':
if (!GetAsInt(optarg, scan_depth) || scan_depth < 0) {
cerr << "Invalid image file scan depth " << optarg << endl;
return false;
}
continue;
case 'n':
name = optarg;
continue;
@ -1215,6 +1263,12 @@ bool ParseArgument(int argc, char* argv[], int& port)
}
continue;
case 'P':
if (!ReadAccessToken(optarg)) {
return false;
}
continue;
case 'r': {
string error = SetReservedIds(optarg);
if (!error.empty()) {
@ -1370,6 +1424,13 @@ static void *MonThread(void *param)
PbCommand command;
DeserializeMessage(fd, command);
if (!access_token.empty()) {
if (access_token != GetParam(command, "token")) {
ReturnStatus(fd, false, "Authentication failed", PbErrorCode::UNAUTHORIZED);
continue;
}
}
switch(command.operation()) {
case LOG_LEVEL: {
LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str());
@ -1427,7 +1488,8 @@ static void *MonThread(void *param)
PbResult result;
result.set_allocated_server_info(rascsi_response.GetServerInfo(
result, devices, reserved_ids, current_log_level));
result, devices, reserved_ids, current_log_level, GetParam(command, "filename_pattern"),
scan_depth));
SerializeMessage(fd, result);
break;
}
@ -1454,7 +1516,8 @@ static void *MonThread(void *param)
LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str());
PbResult result;
result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result));
result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result,
GetParam(command, "filename_pattern"), scan_depth));
SerializeMessage(fd, result);
break;
}

View File

@ -2,6 +2,8 @@
// Each rascsi message sent to the rascsi server is preceded by the magic string "RASCSI".
// A message starts with a little endian 32 bit header which contains the protobuf message size.
// Unless explicitly specified the order of repeated data returned is undefined.
// All operations accept an optional access token, specified by the "token" parameter.
// Only the VERSION_INFO operation never requires authentication.
//
syntax = "proto3";
@ -29,7 +31,7 @@ enum PbDeviceType {
// rascsi remote operations, returning PbResult
enum PbOperation {
NONE = 0;
NO_OPERATION = 0;
// Attach devices and return the new device list (PbDevicesInfo)
// Parameters (mutually exclusive):
@ -64,7 +66,9 @@ enum PbOperation {
// Make medium writable (not possible for read-only media)
UNPROTECT = 9;
// Gets rascsi server information (PbServerInfo)
// Gets the server information (PbServerInfo). Calling this operation should be avoided because it
// may return a lot of data. More specific other operations should be used instead.
// "filename_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned
SERVER_INFO = 10;
// Get rascsi server version (PbVersionInfo)
@ -78,6 +82,8 @@ enum PbOperation {
DEVICE_TYPES_INFO = 13;
// Get information on available image files in the default image folder (PbImageFilesInfo)
// Parameters:
// "filename_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned
DEFAULT_IMAGE_FILES_INFO = 14;
// Get information on an image file (not necessarily in the default image folder) based on an absolute path.
@ -155,8 +161,13 @@ enum PbOperation {
// "file": The filename, relative to the default image folder. It must not contain a slash.
UNPROTECT_IMAGE = 29;
// Get operation meta data (PbOperationParameters)
OPERATION_INFO = 30;
// Check whether an authentication token is valid. A client can use this in operationin order to
// find out whether rascsi authentication is enable or to use rascsi authentication for securing
// client-internal operations.
CHECK_AUTHENTICATION = 30;
// Get operation meta data (PbOperationInfo)
OPERATION_INFO = 31;
}
// The operation parameter meta data. The parameter data type is provided by the protobuf API.
@ -186,6 +197,14 @@ message PbOperationInfo {
map<int32, PbOperationMetaData> operations = 1;
}
// rascsi special purpose error codes for cases where a textual error message is not sufficient
enum PbErrorCode {
// No error code available
NO_ERROR_CODE = 0;
// Authentication/Authorization error
UNAUTHORIZED = 1;
}
// The supported file extensions mapped to their respective device types
message PbMappingInfo {
map<string, PbDeviceType> mapping = 1;
@ -335,6 +354,8 @@ message PbResult {
bool status = 1;
// An optional error or information message, depending on the status. A string without trailing CR/LF.
string msg = 2;
// An optional error code. Only to be used in cases where textual information is not sufficient.
PbErrorCode error_code = 14;
// Optional additional result data
oneof result {
// The result of a SERVER_INFO command

View File

@ -140,51 +140,71 @@ bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filenam
return false;
}
PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result)
void RascsiResponse::GetAvailableImages(PbImageFilesInfo& image_files_info, const string& default_image_folder,
const string& folder, const string& pattern, int scan_depth) {
string pattern_lower = pattern;
transform(pattern_lower.begin(), pattern_lower.end(), pattern_lower.begin(), ::tolower);
if (scan_depth-- >= 0) {
DIR *d = opendir(folder.c_str());
if (d) {
struct dirent *dir;
while ((dir = readdir(d))) {
string filename = folder + "/" + dir->d_name;
string name_lower = filename;
if (!pattern.empty()) {
transform(name_lower.begin(), name_lower.end(), name_lower.begin(), ::tolower);
}
bool is_supported_type = dir->d_type == DT_REG || dir->d_type == DT_DIR || dir->d_type == DT_LNK || dir->d_type == DT_BLK;
if (is_supported_type && dir->d_name[0] != '.') {
struct stat st;
if (dir->d_type == DT_REG && !stat(filename.c_str(), &st)) {
if (!st.st_size) {
LOGTRACE("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, folder.c_str());
continue;
}
} else if (dir->d_type == DT_LNK && stat(filename.c_str(), &st)) {
LOGTRACE("Symlink '%s' in image folder '%s' is broken", dir->d_name, folder.c_str());
continue;
} else if (dir->d_type == DT_DIR) {
GetAvailableImages(image_files_info, default_image_folder, filename, pattern, scan_depth);
continue;
}
if (pattern.empty() || name_lower.find(pattern_lower) != string::npos) {
PbImageFile *image_file = new PbImageFile();
if (GetImageFile(image_file, filename)) {
GetImageFile(image_files_info.add_image_files(), filename.substr(default_image_folder.length() + 1));
}
delete image_file;
}
}
}
closedir(d);
}
}
}
PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result, const string& pattern, int scan_depth)
{
PbImageFilesInfo *image_files_info = new PbImageFilesInfo();
string default_image_folder = rascsi_image->GetDefaultImageFolder();
image_files_info->set_default_image_folder(default_image_folder);
// filesystem::directory_iterator cannot be used because libstdc++ 8.3.0 does not support big files
DIR *d = opendir(default_image_folder.c_str());
if (d) {
struct dirent *dir;
while ((dir = readdir(d))) {
if (dir->d_type == DT_REG || dir->d_type == DT_LNK || dir->d_type == DT_BLK) {
string filename = default_image_folder + "/" + dir->d_name;
struct stat st;
if (dir->d_type == DT_REG && !stat(filename.c_str(), &st)) {
if (!st.st_size) {
LOGTRACE("File '%s' in image folder '%s' has a size of 0 bytes", dir->d_name, default_image_folder.c_str());
continue;
}
} else if (dir->d_type == DT_LNK && stat(filename.c_str(), &st)) {
LOGTRACE("Symlink '%s' in image folder '%s' is broken", dir->d_name, default_image_folder.c_str());
continue;
}
PbImageFile *image_file = new PbImageFile();
if (GetImageFile(image_file, dir->d_name)) {
GetImageFile(image_files_info->add_image_files(), dir->d_name);
}
delete image_file;
}
}
closedir(d);
}
GetAvailableImages(*image_files_info, default_image_folder, default_image_folder, pattern, scan_depth);
result.set_status(true);
return image_files_info;
}
void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info)
void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& pattern, int scan_depth)
{
PbImageFilesInfo *image_files_info = GetAvailableImages(result);
PbImageFilesInfo *image_files_info = GetAvailableImages(result, pattern, scan_depth);
image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder());
server_info.set_allocated_image_files_info(image_files_info);
@ -263,14 +283,14 @@ PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result, const Pb
}
PbServerInfo *RascsiResponse::GetServerInfo(PbResult& result, const vector<Device *>& devices, const set<int>& reserved_ids,
const string& current_log_level)
const string& current_log_level, const string& filename_pattern, int scan_depth)
{
PbServerInfo *server_info = new PbServerInfo();
server_info->set_allocated_version_info(GetVersionInfo(result));
server_info->set_allocated_log_level_info(GetLogLevelInfo(result, current_log_level));
GetAllDeviceTypeProperties(*server_info->mutable_device_types_info());
GetAvailableImages(result, *server_info);
GetAvailableImages(result, *server_info, filename_pattern, scan_depth);
server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result));
server_info->set_allocated_mapping_info(GetMappingInfo(result));
GetDevices(*server_info, devices);

View File

@ -29,16 +29,16 @@ public:
~RascsiResponse() {};
bool GetImageFile(PbImageFile *, const string&);
PbImageFilesInfo *GetAvailableImages(PbResult&);
PbImageFilesInfo *GetAvailableImages(PbResult&, const string&, int);
PbReservedIdsInfo *GetReservedIds(PbResult&, const set<int>&);
void GetDevices(PbServerInfo&, const vector<Device *>&);
void GetDevicesInfo(PbResult&, const PbCommand&, const vector<Device *>&, int);
PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&, const PbCommand&);
PbVersionInfo *GetVersionInfo(PbResult&);
PbServerInfo *GetServerInfo(PbResult&, const vector<Device *>&, const set<int>&, const string&);
PbServerInfo *GetServerInfo(PbResult&, const vector<Device *>&, const set<int>&, const string&, const string&, int);
PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&);
PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&);
PbReservedIdsInfo *GetReservedIds(PbResult&, const set<int>&);
PbMappingInfo *GetMappingInfo(PbResult&);
PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&);
PbOperationInfo *GetOperationInfo(PbResult&);
private:
@ -52,7 +52,8 @@ private:
void GetDevice(const Device *, PbDevice *);
void GetAllDeviceTypeProperties(PbDeviceTypesInfo&);
void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType);
void GetAvailableImages(PbResult& result, PbServerInfo&);
void GetAvailableImages(PbImageFilesInfo&, const string&, const string&, const string&, int);
void GetAvailableImages(PbResult& result, PbServerInfo&, const string&, int);
void CreateOperation(PbOperationInfo *, PbOperationMetaData *, const PbOperation&, const string&);
PbOperationParameter *AddOperationParameter(PbOperationMetaData *, const string&, const string&,
const string& = "");

View File

@ -52,7 +52,7 @@ PbOperation ParseOperation(const char *optarg)
return DEVICES_INFO;
default:
return NONE;
return NO_OPERATION;
}
}
@ -134,11 +134,12 @@ int main(int argc, char* argv[])
string reserved_ids;
string image_params;
string filename;
string token;
bool list = false;
opterr = 1;
int opt;
while ((opt = getopt(argc, argv, "elmosvDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:C:E:F:L:R:")) != -1) {
while ((opt = getopt(argc, argv, "elmosvDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:C:E:F:L:R:P::")) != -1) {
switch (opt) {
case 'i': {
int id;
@ -176,7 +177,7 @@ int main(int argc, char* argv[])
case 'c':
command.set_operation(ParseOperation(optarg));
if (command.operation() == NONE) {
if (command.operation() == NO_OPERATION) {
cerr << "Error: Unknown operation '" << optarg << "'" << endl;
exit(EXIT_FAILURE);
}
@ -305,6 +306,10 @@ int main(int argc, char* argv[])
exit(EXIT_SUCCESS);
break;
case 'P':
token = optarg ? optarg : getpass("Password: ");
break;
case 'V':
command.set_operation(VERSION_INFO);
break;
@ -333,7 +338,7 @@ int main(int argc, char* argv[])
if (list) {
PbCommand command_list;
command_list.set_operation(DEVICES_INFO);
RasctlCommands rasctl_commands(command_list, hostname, port);
RasctlCommands rasctl_commands(command_list, hostname, port, token);
rasctl_commands.CommandDevicesInfo();
exit(EXIT_SUCCESS);
}
@ -344,7 +349,7 @@ int main(int argc, char* argv[])
AddParam(*device, "file", param);
}
RasctlCommands rasctl_commands(command, hostname, port);
RasctlCommands rasctl_commands(command, hostname, port, token);
switch(command.operation()) {
case LOG_LEVEL:

View File

@ -25,15 +25,20 @@ using namespace std;
using namespace rascsi_interface;
using namespace protobuf_util;
RasctlCommands::RasctlCommands(PbCommand& command, const string& hostname, int port)
RasctlCommands::RasctlCommands(PbCommand& command, const string& hostname, int port, const string& token)
{
this->command = command;
this->hostname = hostname;
this->port = port;
this->token = token;
}
void RasctlCommands::SendCommand()
{
if (!token.empty()) {
AddParam(command, "token", token);
}
// Send command
int fd = -1;
try {

View File

@ -20,7 +20,7 @@ class RasctlCommands
{
public:
RasctlCommands(PbCommand&, const string&, int);
RasctlCommands(PbCommand&, const string&, int, const string&);
~RasctlCommands() {};
void SendCommand();
@ -49,6 +49,7 @@ private:
PbCommand command;
string hostname;
int port;
string token;
PbResult result;