Recursive image file scan and image file filter (#532)

* Extracted code

* Initial support for (optional) recursive image file listings

* Manpage update

* Added support for image file filter

* Updated filtering

* Made image scan depth configurable
This commit is contained in:
Uwe Seimet 2021-12-19 11:49:17 +01:00 committed by GitHub
parent 39c65beb99
commit e32211ef73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 47 deletions

View File

@ -5,6 +5,7 @@ rascsi \- Emulates SCSI devices using the Raspberry Pi GPIO pins
.B rascsi
[\fB\-F\f® \fIFOLDER\fR]
[\fB\-L\f® \fILOG_LEVEL\fR]
[\fB\-R\fR \fISCAN_DEPTH\fR]
[\fB\-h\fR]
[\fB\-n\fR \fIVENDOR:PRODUCT:REVISION\fR]
[\fB\-p\f® \fIPORT\fR]
@ -52,6 +53,9 @@ 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 \-R\fI " " \fISCAN_DEPTH
Scan for image files recursively, up to a depth -f 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,14 @@
!! ------ 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] [-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 +65,11 @@ OPTIONS
The rascsi log level (trace, debug, info, warn, err, critical,
off). The default log level is 'info'.
-R SCAN_DEPTH
Scan for image files recursively, up to a depth -f 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

@ -68,6 +68,7 @@ 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()
set<int> reserved_ids;
int scan_depth = 0;
DeviceFactory& device_factory = DeviceFactory::instance();
RascsiImage rascsi_image;
RascsiResponse rascsi_response(&device_factory, &rascsi_image);
@ -1159,7 +1160,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:R:")) != -1) {
switch (opt) {
// The three options below are kind of a compound option with two letters
case 'i':
@ -1204,6 +1205,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;
@ -1427,7 +1435,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 +1463,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

@ -64,7 +64,9 @@ enum PbOperation {
// Make medium writable (not possible for read-only media)
UNPROTECT = 9;
// Gets the 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 version information (PbVersionInfo)
@ -78,6 +80,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.

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,13 +29,13 @@ 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&);
PbMappingInfo *GetMappingInfo(PbResult&);
PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&);
@ -51,5 +51,6 @@ 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);
};