From e32211ef737d1a435ee8ca1d7244b3609202b28f Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 19 Dec 2021 11:49:17 +0100 Subject: [PATCH] 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 --- doc/rascsi.1 | 4 ++ doc/rascsi_man_page.txt | 15 +++-- src/raspberrypi/rascsi.cpp | 16 ++++- src/raspberrypi/rascsi_interface.proto | 6 +- src/raspberrypi/rascsi_response.cpp | 88 ++++++++++++++++---------- src/raspberrypi/rascsi_response.h | 7 +- 6 files changed, 89 insertions(+), 47 deletions(-) diff --git a/doc/rascsi.1 b/doc/rascsi.1 index e8c8e1f4..aabe7cbf 100644 --- a/doc/rascsi.1 +++ b/doc/rascsi.1 @@ -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 diff --git a/doc/rascsi_man_page.txt b/doc/rascsi_man_page.txt index ca6b25cc..665fc037 100644 --- a/doc/rascsi_man_page.txt +++ b/doc/rascsi_man_page.txt @@ -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 diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 804548a3..2fa03277 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -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 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; } diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto index b719eede..04054e5c 100644 --- a/src/raspberrypi/rascsi_interface.proto +++ b/src/raspberrypi/rascsi_interface.proto @@ -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. diff --git a/src/raspberrypi/rascsi_response.cpp b/src/raspberrypi/rascsi_response.cpp index 7636447f..4a055725 100644 --- a/src/raspberrypi/rascsi_response.cpp +++ b/src/raspberrypi/rascsi_response.cpp @@ -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& devices, const set& 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); diff --git a/src/raspberrypi/rascsi_response.h b/src/raspberrypi/rascsi_response.h index a4c7893a..d11ca74b 100644 --- a/src/raspberrypi/rascsi_response.h +++ b/src/raspberrypi/rascsi_response.h @@ -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&); void GetDevices(PbServerInfo&, const vector&); void GetDevicesInfo(PbResult&, const PbCommand&, const vector&, int); PbDeviceTypesInfo *GetDeviceTypesInfo(PbResult&, const PbCommand&); PbVersionInfo *GetVersionInfo(PbResult&); - PbServerInfo *GetServerInfo(PbResult&, const vector&, const set&, const string&); + PbServerInfo *GetServerInfo(PbResult&, const vector&, const set&, 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); };