From 62a104f75d5e40816cdd3bfecfc055f182a86e2e Mon Sep 17 00:00:00 2001 From: Uwe Seimet <48174652+uweseimet@users.noreply.github.com> Date: Sun, 17 Oct 2021 08:51:14 +0200 Subject: [PATCH] Added SHUT_DOWN command, split rascsi.cpp (#334) * Added TERMINATE command * Help text update * Created ras_util namespace * Created protobuf_util namespace * Extracted RascsiImage from rascsi for better modularity * Fixed wrong filenames (class name differed) * No need for response handler to be a singleton * Class renaming * Renaming * Moved code * Moved code * Image folder handling optimizations * Updated device factory handling * DeviceFactory constructor should be private (singleton) * Renamed TERMINATE to SHUT_DOWN * Fixed capacity calculation for INQUIRY data * ATTACH and DETACH return the resulting device list for convenience * Interface comment update * Moved code, check fd * Use new command when returning result for ATTACH/DETACH * Updated interface comments --- doc/rasctl.1 | 4 + doc/rasctl_man_page.txt | 6 +- src/raspberrypi/Makefile | 3 +- src/raspberrypi/devices/device_factory.h | 5 +- src/raspberrypi/devices/scsihd.cpp | 7 +- src/raspberrypi/protobuf_util.cpp | 54 ++- src/raspberrypi/protobuf_util.h | 22 +- src/raspberrypi/rascsi.cpp | 441 +++--------------- src/raspberrypi/rascsi_image.cpp | 343 ++++++++++++++ src/raspberrypi/rascsi_image.h | 38 ++ src/raspberrypi/rascsi_interface.proto | 42 +- ...esponse_helper.cpp => rascsi_response.cpp} | 94 ++-- ...uf_response_helper.h => rascsi_response.h} | 29 +- src/raspberrypi/rasctl.cpp | 10 +- src/raspberrypi/rasctl_commands.cpp | 1 + src/raspberrypi/rasctl_display.cpp | 1 + src/raspberrypi/rasutil.cpp | 4 +- src/raspberrypi/rasutil.h | 7 +- 18 files changed, 617 insertions(+), 494 deletions(-) create mode 100644 src/raspberrypi/rascsi_image.cpp create mode 100644 src/raspberrypi/rascsi_image.h rename src/raspberrypi/{protobuf_response_helper.cpp => rascsi_response.cpp} (70%) rename src/raspberrypi/{protobuf_response_helper.h => rascsi_response.h} (63%) diff --git a/doc/rasctl.1 b/doc/rasctl.1 index 65231b6a..eff1aa6b 100644 --- a/doc/rasctl.1 +++ b/doc/rasctl.1 @@ -14,6 +14,7 @@ rasctl \- Sends management commands to the rascsi process \fB\-O\fR | \fB\-T\fR | \fB\-V\fR | +\fB\-X\fR | [\fB\-C\fR \fIFILENAME:FILESIZE\fR] [\fB\-E\fR \fIFILENAME\fR] [\fB\-F\fR \fIIMAGE_FOLDER\fR] @@ -98,6 +99,9 @@ Display the rascsi server version. .BR \-V\fI " " \fI Display the rasctl version. .TP +.BR \-X\fI " " \fI +Shut down the rascsi process. +.TP .BR \-d\fI " "\fIFILENAME Delete an image file in the default image folder. .TP diff --git a/doc/rasctl_man_page.txt b/doc/rasctl_man_page.txt index c4ba25b3..eaa76af3 100644 --- a/doc/rasctl_man_page.txt +++ b/doc/rasctl_man_page.txt @@ -6,8 +6,8 @@ NAME rasctl - Sends management commands to the rascsi process SYNOPSIS - rasctl -e | -l | -m | -s | -v | -D | -I | -L | -O | -T | -V | [-C FILE‐ - NAME:FILESIZE] [-E FILENAME] [-F IMAGE_FOLDER] [-R CUR‐ + rasctl -e | -l | -m | -s | -v | -D | -I | -L | -O | -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] @@ -77,6 +77,8 @@ OPTIONS -V Display the rasctl version. + -X Shut down the rascsi process. + -d FILENAME Delete an image file in the default image folder. diff --git a/src/raspberrypi/Makefile b/src/raspberrypi/Makefile index 423c7a07..20b5f27a 100644 --- a/src/raspberrypi/Makefile +++ b/src/raspberrypi/Makefile @@ -84,8 +84,9 @@ SRC_RASCSI = \ filepath.cpp \ fileio.cpp\ rascsi_version.cpp \ + rascsi_image.cpp \ + rascsi_response.cpp \ rasutil.cpp \ - protobuf_response_helper.cpp \ protobuf_util.cpp SRC_RASCSI += $(shell find ./controllers -name '*.cpp') SRC_RASCSI += $(shell find ./devices -name '*.cpp') diff --git a/src/raspberrypi/devices/device_factory.h b/src/raspberrypi/devices/device_factory.h index ad00ee3b..d6f73f63 100644 --- a/src/raspberrypi/devices/device_factory.h +++ b/src/raspberrypi/devices/device_factory.h @@ -26,11 +26,12 @@ class Device; class DeviceFactory { -public: - +private: DeviceFactory(); ~DeviceFactory() {}; +public: + static DeviceFactory& instance(); Device *CreateDevice(PbDeviceType, const string&); diff --git a/src/raspberrypi/devices/scsihd.cpp b/src/raspberrypi/devices/scsihd.cpp index 70c7ce64..5f6fd274 100644 --- a/src/raspberrypi/devices/scsihd.cpp +++ b/src/raspberrypi/devices/scsihd.cpp @@ -45,14 +45,13 @@ void SCSIHD::FinalizeSetup(const Filepath &path, off_t size) // For non-removable media drives set the default product name based on the drive capacity if (!IsRemovable()) { - int capacity; + uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); string unit; - if (GetBlockCount() >> 11 >= 1) { - capacity = GetBlockCount() >> 11; + if (capacity >= 1000000) { + capacity /= 1000000; unit = "MB"; } else { - capacity = GetBlockCount() >> 1; unit = "KB"; } stringstream product; diff --git a/src/raspberrypi/protobuf_util.cpp b/src/raspberrypi/protobuf_util.cpp index 4791c5f5..476b0038 100644 --- a/src/raspberrypi/protobuf_util.cpp +++ b/src/raspberrypi/protobuf_util.cpp @@ -8,6 +8,8 @@ //--------------------------------------------------------------------------- #include +#include "os.h" +#include "log.h" #include "rascsi_interface.pb.h" #include "exceptions.h" #include "protobuf_util.h" @@ -15,20 +17,21 @@ using namespace std; using namespace rascsi_interface; +#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) -const string GetParam(const PbCommand& command, const string& key) +const string protobuf_util::GetParam(const PbCommand& command, const string& key) { auto map = command.params(); return map[key]; } -const string GetParam(const PbDeviceDefinition& device, const string& key) +const string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& key) { auto map = device.params(); return map[key]; } -void AddParam(PbCommand& command, const string& key, const string& value) +void protobuf_util::AddParam(PbCommand& command, const string& key, const string& value) { if (!key.empty() && !value.empty()) { auto& map = *command.mutable_params(); @@ -36,7 +39,7 @@ void AddParam(PbCommand& command, const string& key, const string& value) } } -void AddParam(PbDevice& device, const string& key, const string& value) +void protobuf_util::AddParam(PbDevice& device, const string& key, const string& value) { if (!key.empty() && !value.empty()) { auto& map = *device.mutable_params(); @@ -44,7 +47,7 @@ void AddParam(PbDevice& device, const string& key, const string& value) } } -void AddParam(PbDeviceDefinition& device, const string& key, const string& value) +void protobuf_util::AddParam(PbDeviceDefinition& device, const string& key, const string& value) { if (!key.empty() && !value.empty()) { auto& map = *device.mutable_params(); @@ -59,7 +62,7 @@ void AddParam(PbDeviceDefinition& device, const string& key, const string& value // //--------------------------------------------------------------------------- -void SerializeMessage(int fd, const google::protobuf::Message& message) +void protobuf_util::SerializeMessage(int fd, const google::protobuf::Message& message) { string data; message.SerializeToString(&data); @@ -76,7 +79,7 @@ void SerializeMessage(int fd, const google::protobuf::Message& message) } } -void DeserializeMessage(int fd, google::protobuf::Message& message) +void protobuf_util::DeserializeMessage(int fd, google::protobuf::Message& message) { // Read the header with the size of the protobuf data uint8_t header_buf[4]; @@ -98,7 +101,7 @@ void DeserializeMessage(int fd, google::protobuf::Message& message) message.ParseFromString(data); } -int ReadNBytes(int fd, uint8_t *buf, int n) +int protobuf_util::ReadNBytes(int fd, uint8_t *buf, int n) { int offset = 0; while (offset < n) { @@ -112,3 +115,38 @@ int ReadNBytes(int fd, uint8_t *buf, int n) return offset; } + + +bool protobuf_util::ReturnStatus(int fd, bool status, const string msg) +{ + if (!status && !msg.empty()) { + LOGERROR("%s", msg.c_str()); + } + + if (fd == -1) { + if (!msg.empty()) { + if (status) { + FPRT(stderr, "Error: "); + FPRT(stderr, "%s", msg.c_str()); + FPRT(stderr, "\n"); + } + else { + FPRT(stdout, "%s", msg.c_str()); + FPRT(stderr, "\n"); + } + } + } + else { + PbResult result; + result.set_status(status); + result.set_msg(msg); + SerializeMessage(fd, result); + } + + return status; +} + +bool protobuf_util::ReturnStatus(int fd, bool status, const ostringstream& msg) +{ + return ReturnStatus(fd, status, msg.str()); +} diff --git a/src/raspberrypi/protobuf_util.h b/src/raspberrypi/protobuf_util.h index d327932f..8482fe16 100644 --- a/src/raspberrypi/protobuf_util.h +++ b/src/raspberrypi/protobuf_util.h @@ -13,16 +13,22 @@ #include "google/protobuf/message.h" #include "rascsi_interface.pb.h" +#include #include using namespace std; using namespace rascsi_interface; -const string GetParam(const PbCommand&, const string&); -const string GetParam(const PbDeviceDefinition&, const string&); -void AddParam(PbCommand&, const string&, const string&); -void AddParam(PbDevice&, const string&, const string&); -void AddParam(PbDeviceDefinition&, const string&, const string&); -void SerializeMessage(int, const google::protobuf::Message&); -void DeserializeMessage(int, google::protobuf::Message&); -int ReadNBytes(int, uint8_t *, int); +namespace protobuf_util +{ + const string GetParam(const PbCommand&, const string&); + const string GetParam(const PbDeviceDefinition&, const string&); + void AddParam(PbCommand&, const string&, const string&); + void AddParam(PbDevice&, const string&, const string&); + void AddParam(PbDeviceDefinition&, const string&, const string&); + 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, const ostringstream&); +} diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 5fd131af..f297db61 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -23,15 +23,15 @@ #include "devices/file_support.h" #include "gpiobus.h" #include "exceptions.h" -#include "protobuf_response_helper.h" #include "protobuf_util.h" #include "rascsi_version.h" +#include "rascsi_response.h" #include "rasutil.h" +#include "rascsi_image.h" #include "rascsi_interface.pb.h" #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" #include -#include #include #include #include @@ -45,6 +45,8 @@ using namespace std; using namespace spdlog; using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; //--------------------------------------------------------------------------- // @@ -70,10 +72,10 @@ 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 default_image_folder; set reserved_ids; DeviceFactory& device_factory = DeviceFactory::instance(); -ProtobufResponseHandler& response_helper = ProtobufResponseHandler::instance(); +RascsiImage rascsi_image; +RascsiResponse rascsi_response(&device_factory, &rascsi_image); //--------------------------------------------------------------------------- // @@ -417,40 +419,6 @@ string ValidateLunSetup(const PbCommand& command, const vector& existi return ""; } -bool ReturnStatus(int fd, bool status = true, const string msg = "") -{ - if (!status && !msg.empty()) { - LOGERROR("%s", msg.c_str()); - } - - if (fd == -1) { - if (!msg.empty()) { - if (status) { - FPRT(stderr, "Error: "); - FPRT(stderr, "%s", msg.c_str()); - FPRT(stderr, "\n"); - } - else { - FPRT(stdout, "%s", msg.c_str()); - FPRT(stderr, "\n"); - } - } - } - else { - PbResult result; - result.set_status(status); - result.set_msg(msg); - SerializeMessage(fd, result); - } - - return status; -} - -bool ReturnStatus(int fd, bool status, const ostringstream& msg) -{ - return ReturnStatus(fd, status, msg.str()); -} - bool SetLogLevel(const string& log_level) { if (log_level == "trace") { @@ -493,44 +461,6 @@ void LogDevices(const string& devices) } } -string SetDefaultImageFolder(const string& f) -{ - string folder = f; - - // If a relative path is specified the path is assumed to be relative to the user's home directory - if (folder[0] != '/') { - int uid = getuid(); - const char *sudo_user = getenv("SUDO_UID"); - if (sudo_user) { - uid = stoi(sudo_user); - } - - const passwd *passwd = getpwuid(uid); - if (passwd) { - folder = passwd->pw_dir; - folder += "/"; - folder += f; - } - } - else { - if (folder.find("/home/") != 0) { - return "Default image folder must be located in '/home/'"; - } - } - - struct stat info; - stat(folder.c_str(), &info); - if (!S_ISDIR(info.st_mode) || access(folder.c_str(), F_OK) == -1) { - return string("Folder '" + f + "' does not exist or is not accessible"); - } - - default_image_folder = folder; - - LOGINFO("Default image folder set to '%s'", default_image_folder.c_str()); - - return ""; -} - string SetReservedIds(const string& ids) { list ids_to_reserve; @@ -578,264 +508,6 @@ string SetReservedIds(const string& ids) return ""; } -bool IsValidSrcFilename(const string& filename) -{ - // Source file must exist and must be a regular file or a symlink - struct stat st; - return !stat(filename.c_str(), &st) && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)); -} - -bool IsValidDstFilename(const string& filename) -{ - // Destination file must not yet exist - struct stat st; - return stat(filename.c_str(), &st); -} - -bool CreateImage(int fd, const PbCommand& command) -{ - string filename = GetParam(command, "file"); - if (filename.empty()) { - return ReturnStatus(fd, false, "Can't create image file: Missing image filename"); - } - if (filename.find('/') != string::npos) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Filename must not contain a path"); - } - filename = default_image_folder + "/" + filename; - if (!IsValidDstFilename(filename)) { - return ReturnStatus(fd, false, "Can't create image file: '" + filename + "': File already exists"); - } - - const string size = GetParam(command, "size"); - if (size.empty()) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Missing image size"); - } - - off_t len; - try { - len = stoul(size); - } - catch(const invalid_argument& e) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size); - } - catch(const out_of_range& e) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size); - } - if (len < 512 || (len & 0x1ff)) { - ostringstream error; - error << "Invalid image file size " << len; - return ReturnStatus(fd, false, error.str()); - } - - struct stat st; - if (!stat(filename.c_str(), &st)) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': File already exists"); - } - - string permission = GetParam(command, "read_only"); - // Since rascsi is running as root ensure that others can access the file - int permissions = !strcasecmp(permission.c_str(), "true") ? - S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - - int image_fd = open(filename.c_str(), O_CREAT|O_WRONLY, permissions); - if (image_fd == -1) { - return ReturnStatus(fd, false, "Can't create image file '" + filename + "': " + string(strerror(errno))); - } - - if (fallocate(image_fd, 0, 0, len) == -1) { - close(image_fd); - - return ReturnStatus(fd, false, "Can't allocate space for image file '" + filename + "': " + string(strerror(errno))); - } - - close(image_fd); - - ostringstream msg; - msg << "Created " << (permissions & S_IWUSR ? "": "read-only ") << "image file '" << filename + "' with a size of " << len << " bytes"; - LOGINFO("%s", msg.str().c_str()); - - return ReturnStatus(fd); -} - -bool DeleteImage(int fd, const PbCommand& command) -{ - string filename = GetParam(command, "file"); - if (filename.empty()) { - return ReturnStatus(fd, false, "Missing image filename"); - } - - if (!IsValidDstFilename(filename)) { - return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': File already exists"); - } - - if (filename.find('/') != string::npos) { - return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path"); - } - - filename = default_image_folder + "/" + filename; - - int id; - int unit; - Filepath filepath; - filepath.SetPath(filename.c_str()); - if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { - ostringstream msg; - msg << "Can't delete image file '" << filename << "', it is used by device ID " << id << ", unit " << unit; - return ReturnStatus(fd, false, msg.str()); - } - - if (unlink(filename.c_str())) { - return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': " + string(strerror(errno))); - } - - LOGINFO("Deleted image file '%s'", filename.c_str()); - - return ReturnStatus(fd); -} - -bool RenameImage(int fd, const PbCommand& command) -{ - string from = GetParam(command, "from"); - if (from.empty()) { - return ReturnStatus(fd, false, "Can't rename image file: Missing source filename"); - } - if (from.find('/') != string::npos) { - return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path"); - } - from = default_image_folder + "/" + from; - if (!IsValidSrcFilename(from)) { - return ReturnStatus(fd, false, "Can't rename image file: '" + from + "': Invalid name or type"); - } - - string to = GetParam(command, "to"); - if (to.empty()) { - return ReturnStatus(fd, false, "Can't rename image file '" + from + "': Missing destination filename"); - } - if (to.find('/') != string::npos) { - return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path"); - } - to = default_image_folder + "/" + to; - if (!IsValidDstFilename(to)) { - return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': File already exists"); - } - - if (rename(from.c_str(), to.c_str())) { - return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': " + string(strerror(errno))); - } - - LOGINFO("Renamed image file '%s' to '%s'", from.c_str(), to.c_str()); - - return ReturnStatus(fd); -} - -bool CopyImage(int fd, const PbCommand& command) -{ - string from = GetParam(command, "from"); - if (from.empty()) { - return ReturnStatus(fd, false, "Can't copy image file: Missing source filename"); - } - if (from.find('/') != string::npos) { - return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path"); - } - from = default_image_folder + "/" + from; - if (!IsValidSrcFilename(from)) { - return ReturnStatus(fd, false, "Can't copy image file: '" + from + "': Invalid name or type"); - } - - string to = GetParam(command, "to"); - if (to.empty()) { - return ReturnStatus(fd, false, "Can't copy image file '" + from + "': Missing destination filename"); - } - if (to.find('/') != string::npos) { - return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path"); - } - to = default_image_folder + "/" + to; - if (!IsValidDstFilename(to)) { - return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists"); - } - - struct stat st; - if (lstat(from.c_str(), &st)) { - return ReturnStatus(fd, false, "Can't access source image file '" + from + "': " + string(strerror(errno))); - } - - // Symbolic links need a special handling - if ((st.st_mode & S_IFMT) == S_IFLNK) { - if (symlink(filesystem::read_symlink(from).c_str(), to.c_str())) { - return ReturnStatus(fd, false, "Can't copy symlink '" + from + "': " + string(strerror(errno))); - } - - LOGINFO("Copied symlink '%s' to '%s'", from.c_str(), to.c_str()); - - return ReturnStatus(fd); - } - - int fd_src = open(from.c_str(), O_RDONLY, 0); - if (fd_src == -1) { - return ReturnStatus(fd, false, "Can't open source image file '" + from + "': " + string(strerror(errno))); - } - - string permission = GetParam(command, "read_only"); - // Since rascsi is running as root ensure that others can access the file - int permissions = !strcasecmp(permission.c_str(), "true") ? - S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - - int fd_dst = open(to.c_str(), O_WRONLY | O_CREAT, permissions); - if (fd_dst == -1) { - close(fd_src); - - return ReturnStatus(fd, false, "Can't open destination image file '" + to + "': " + string(strerror(errno))); - } - - if (sendfile(fd_dst, fd_src, 0, st.st_size) == -1) { - close(fd_dst); - close(fd_src); - - return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno))); - } - - close(fd_dst); - close(fd_src); - - LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()); - - return ReturnStatus(fd); -} - -bool SetImagePermissions(int fd, const PbCommand& command) -{ - string filename = GetParam(command, "file"); - if (filename.empty()) { - return ReturnStatus(fd, false, "Missing image filename"); - } - if (filename.find('/') != string::npos) { - return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path"); - } - filename = default_image_folder + "/" + filename; - if (!IsValidSrcFilename(filename)) { - return ReturnStatus(fd, false, "Can't modify image file '" + filename + "': Invalid name or type"); - } - - bool protect = command.operation() == PROTECT_IMAGE; - - int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; - - if (chmod(filename.c_str(), permissions) == -1) { - ostringstream error; - error << "Can't " << (protect ? "protect" : "unprotect") << " image file '" << filename << "': " << strerror(errno); - return ReturnStatus(fd, false, error.str()); - } - - if (protect) { - LOGINFO("Protected image file '%s'", filename.c_str()); - } - else { - LOGINFO("Unprotected image file '%s'", filename.c_str()); - } - - return ReturnStatus(fd); -} - void DetachAll() { Device *map[devices.size()]; @@ -951,7 +623,7 @@ bool Attach(int fd, const PbDeviceDefinition& pb_device, Device *map[], bool dry } catch(const file_not_found_exception&) { // If the file does not exist search for it in the default image folder - filepath.SetPath(string(default_image_folder + "/" + filename).c_str()); + filepath.SetPath(string(rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); file_support->Open(filepath); } } @@ -1020,8 +692,7 @@ bool Attach(int fd, const PbDeviceDefinition& pb_device, Device *map[], bool dry bool Detach(int fd, Device *device, Device *map[], bool dryRun) { if (!dryRun) { - for (size_t i = devices.size() - 1; i > 0; i--) { - Device *d = map[i]; + for (auto const& d : devices) { // Detach all LUNs equal to or higher than the LUN specified if (d && d->GetId() == device->GetId() && d->GetLun() >= device->GetLun()) { map[d->GetId() * UnitNum + d->GetLun()] = NULL; @@ -1033,10 +704,10 @@ bool Detach(int fd, Device *device, Device *map[], bool dryRun) LOGINFO("Detached %s device with ID %d, unit %d", d->GetType().c_str(), d->GetId(), d->GetLun()); } - - // Re-map the controller - MapController(map); } + + // Re-map the controller + MapController(map); } return true; @@ -1096,7 +767,7 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr } catch(const file_not_found_exception&) { // If the file does not exist search for it in the default image folder - filepath.SetPath(string(default_image_folder + "/" + filename).c_str()); + filepath.SetPath((rascsi_image.GetDefaultImageFolder() + "/" + filename).c_str()); file_support->Open(filepath); } } @@ -1114,6 +785,13 @@ bool Insert(int fd, const PbDeviceDefinition& pb_device, Device *device, bool dr return true; } +void TerminationHandler(int signum) +{ + DetachAll(); + + exit(signum); +} + //--------------------------------------------------------------------------- // // Command Processing @@ -1306,20 +984,20 @@ bool ProcessCmd(const int fd, const PbCommand& command) } case CREATE_IMAGE: - return CreateImage(fd, command); + return rascsi_image.CreateImage(fd, command); case DELETE_IMAGE: - return DeleteImage(fd, command); + return rascsi_image.DeleteImage(fd, command); case RENAME_IMAGE: - return RenameImage(fd, command); + return rascsi_image.RenameImage(fd, command); case COPY_IMAGE: - return CopyImage(fd, command); + return rascsi_image.CopyImage(fd, command); case PROTECT_IMAGE: case UNPROTECT_IMAGE: - return SetImagePermissions(fd, command); + return rascsi_image.SetImagePermissions(fd, command); default: // This is a device-specific command handled below @@ -1350,6 +1028,16 @@ bool ProcessCmd(const int fd, const PbCommand& command) } } + // ATTACH and DETACH return the device list + if (fd != -1 && (command.operation() == ATTACH || command.operation() == DETACH)) { + // A new command with an empty device list is required here in order to return data for all devices + PbCommand command; + PbResult result; + rascsi_response.GetDevicesInfo(result, command, devices, UnitNum); + SerializeMessage(fd, result); + return true; + } + return ReturnStatus(fd); } @@ -1434,7 +1122,7 @@ bool ParseArgument(int argc, char* argv[], int& port) } case 'F': { - string result = SetDefaultImageFolder(optarg); + string result = rascsi_image.SetDefaultImageFolder(optarg); if (!result.empty()) { cerr << result << endl; return false; @@ -1532,7 +1220,7 @@ bool ParseArgument(int argc, char* argv[], int& port) // Display and log the device list PbServerInfo server_info; - response_helper.GetDevices(server_info, devices, default_image_folder); + rascsi_response.GetDevices(server_info, devices); const list& devices = { server_info.devices_info().devices().begin(), server_info.devices_info().devices().end() }; const string device_list = ListDevices(devices); LogDevices(device_list); @@ -1625,7 +1313,7 @@ static void *MonThread(void *param) ReturnStatus(fd, false, "Can't set default image folder: Missing folder name"); } - string result = SetDefaultImageFolder(folder); + string result = rascsi_image.SetDefaultImageFolder(folder); if (!result.empty()) { ReturnStatus(fd, false, result); } @@ -1639,7 +1327,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - response_helper.GetDevicesInfo(result, command, devices, default_image_folder, UnitNum); + rascsi_response.GetDevicesInfo(result, command, devices, UnitNum); SerializeMessage(fd, result); // For backwards compatibility: Log device list if information on all devices was requested. @@ -1654,7 +1342,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_device_types_info(response_helper.GetDeviceTypesInfo(result, command)); + result.set_allocated_device_types_info(rascsi_response.GetDeviceTypesInfo(result, command)); SerializeMessage(fd, result); break; } @@ -1663,8 +1351,8 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_server_info(response_helper.GetServerInfo( - result, devices, reserved_ids, default_image_folder, current_log_level)); + result.set_allocated_server_info(rascsi_response.GetServerInfo( + result, devices, reserved_ids, current_log_level)); SerializeMessage(fd, result); break; } @@ -1673,7 +1361,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_version_info(response_helper.GetVersionInfo(result)); + result.set_allocated_version_info(rascsi_response.GetVersionInfo(result)); SerializeMessage(fd, result); break; } @@ -1682,7 +1370,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_log_level_info(response_helper.GetLogLevelInfo(result, current_log_level)); + result.set_allocated_log_level_info(rascsi_response.GetLogLevelInfo(result, current_log_level)); SerializeMessage(fd, result); break; } @@ -1691,7 +1379,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_image_files_info(response_helper.GetAvailableImages(result, default_image_folder)); + result.set_allocated_image_files_info(rascsi_response.GetAvailableImages(result)); SerializeMessage(fd, result); break; } @@ -1706,7 +1394,7 @@ static void *MonThread(void *param) else { PbResult result; PbImageFile* image_file = new PbImageFile(); - bool status = response_helper.GetImageFile(image_file, filename, default_image_folder); + bool status = rascsi_response.GetImageFile(image_file, filename); if (status) { result.set_status(true); result.set_allocated_image_file_info(image_file); @@ -1723,7 +1411,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_network_interfaces_info(response_helper.GetNetworkInterfacesInfo(result)); + result.set_allocated_network_interfaces_info(rascsi_response.GetNetworkInterfacesInfo(result)); SerializeMessage(fd, result); break; } @@ -1732,7 +1420,7 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_mapping_info(response_helper.GetMappingInfo(result)); + result.set_allocated_mapping_info(rascsi_response.GetMappingInfo(result)); SerializeMessage(fd, result); break; } @@ -1741,11 +1429,22 @@ static void *MonThread(void *param) LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); PbResult result; - result.set_allocated_reserved_ids_info(response_helper.GetReservedIds(result, reserved_ids)); + result.set_allocated_reserved_ids_info(rascsi_response.GetReservedIds(result, reserved_ids)); SerializeMessage(fd, result); break; } + case SHUT_DOWN: { + LOGTRACE("Received %s command", PbOperation_Name(command.operation()).c_str()); + + PbResult result; + result.set_status(true); + SerializeMessage(fd, result); + + TerminationHandler(0); + break; + } + default: { // Wait until we become idle while (active) { @@ -1771,13 +1470,6 @@ static void *MonThread(void *param) return NULL; } -void TerminationHandler(int signum) -{ - DetachAll(); - - exit(signum); -} - //--------------------------------------------------------------------------- // // Main processing @@ -1810,21 +1502,6 @@ int main(int argc, char* argv[]) // Create a thread-safe stdout logger to process the log messages auto logger = stdout_color_mt("rascsi stdout logger"); - // ~/images is the default folder for device image files, for the root user it is /home/pi/images - int uid = getuid(); - const char *sudo_user = getenv("SUDO_UID"); - if (sudo_user) { - uid = stoi(sudo_user); - } - const passwd *passwd = getpwuid(uid); - if (uid && passwd) { - default_image_folder = passwd->pw_dir; - default_image_folder += "/images"; - } - else { - default_image_folder = "/home/pi/images"; - } - int port = 6868; if (!InitBus()) { diff --git a/src/raspberrypi/rascsi_image.cpp b/src/raspberrypi/rascsi_image.cpp new file mode 100644 index 00000000..24fa8cbd --- /dev/null +++ b/src/raspberrypi/rascsi_image.cpp @@ -0,0 +1,343 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include +#include +#include "os.h" +#include "log.h" +#include "filepath.h" +#include "spdlog/spdlog.h" +#include "devices/file_support.h" +#include "protobuf_util.h" +#include "rascsi_image.h" +#include +#include +#include + +using namespace std; +using namespace spdlog; +using namespace rascsi_interface; +using namespace protobuf_util; + +#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) + +RascsiImage::RascsiImage() +{ + // ~/images is the default folder for device image files, for the root user it is /home/pi/images + int uid = getuid(); + const char *sudo_user = getenv("SUDO_UID"); + if (sudo_user) { + uid = stoi(sudo_user); + } + + const passwd *passwd = getpwuid(uid); + if (uid && passwd) { + default_image_folder = passwd->pw_dir; + default_image_folder += "/images"; + } + else { + default_image_folder = "/home/pi/images"; + } +} + +string RascsiImage::SetDefaultImageFolder(const string& f) +{ + string folder = f; + + // If a relative path is specified the path is assumed to be relative to the user's home directory + if (folder[0] != '/') { + int uid = getuid(); + const char *sudo_user = getenv("SUDO_UID"); + if (sudo_user) { + uid = stoi(sudo_user); + } + + const passwd *passwd = getpwuid(uid); + if (passwd) { + folder = passwd->pw_dir; + folder += "/"; + folder += f; + } + } + else { + if (folder.find("/home/") != 0) { + return "Default image folder must be located in '/home/'"; + } + } + + struct stat info; + stat(folder.c_str(), &info); + if (!S_ISDIR(info.st_mode) || access(folder.c_str(), F_OK) == -1) { + return "Folder '" + f + "' does not exist or is not accessible"; + } + + default_image_folder = folder; + + LOGINFO("Default image folder set to '%s'", default_image_folder.c_str()); + + return ""; +} + +bool RascsiImage::IsValidSrcFilename(const string& filename) +{ + // Source file must exist and must be a regular file or a symlink + struct stat st; + return !stat(filename.c_str(), &st) && (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)); +} + +bool RascsiImage::IsValidDstFilename(const string& filename) +{ + // Destination file must not yet exist + struct stat st; + return stat(filename.c_str(), &st); +} + +bool RascsiImage::CreateImage(int fd, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(fd, false, "Can't create image file: Missing image filename"); + } + if (filename.find('/') != string::npos) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Filename must not contain a path"); + } + filename = default_image_folder + "/" + filename; + if (!IsValidDstFilename(filename)) { + return ReturnStatus(fd, false, "Can't create image file: '" + filename + "': File already exists"); + } + + const string size = GetParam(command, "size"); + if (size.empty()) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Missing image size"); + } + + off_t len; + try { + len = stoul(size); + } + catch(const invalid_argument& e) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size); + } + catch(const out_of_range& e) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': Invalid file size " + size); + } + if (len < 512 || (len & 0x1ff)) { + ostringstream error; + error << "Invalid image file size " << len; + return ReturnStatus(fd, false, error.str()); + } + + struct stat st; + if (!stat(filename.c_str(), &st)) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': File already exists"); + } + + string permission = GetParam(command, "read_only"); + // Since rascsi is running as root ensure that others can access the file + int permissions = !strcasecmp(permission.c_str(), "true") ? + S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + int image_fd = open(filename.c_str(), O_CREAT|O_WRONLY, permissions); + if (image_fd == -1) { + return ReturnStatus(fd, false, "Can't create image file '" + filename + "': " + string(strerror(errno))); + } + + if (fallocate(image_fd, 0, 0, len) == -1) { + close(image_fd); + + return ReturnStatus(fd, false, "Can't allocate space for image file '" + filename + "': " + string(strerror(errno))); + } + + close(image_fd); + + ostringstream msg; + msg << "Created " << (permissions & S_IWUSR ? "": "read-only ") << "image file '" << filename + "' with a size of " << len << " bytes"; + LOGINFO("%s", msg.str().c_str()); + + return ReturnStatus(fd); +} + +bool RascsiImage::DeleteImage(int fd, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(fd, false, "Missing image filename"); + } + + if (!IsValidDstFilename(filename)) { + return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': File already exists"); + } + + if (filename.find('/') != string::npos) { + return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path"); + } + + filename = default_image_folder + "/" + filename; + + int id; + int unit; + Filepath filepath; + filepath.SetPath(filename.c_str()); + if (FileSupport::GetIdsForReservedFile(filepath, id, unit)) { + ostringstream msg; + msg << "Can't delete image file '" << filename << "', it is used by device ID " << id << ", unit " << unit; + return ReturnStatus(fd, false, msg.str()); + } + + if (unlink(filename.c_str())) { + return ReturnStatus(fd, false, "Can't delete image file '" + filename + "': " + string(strerror(errno))); + } + + LOGINFO("Deleted image file '%s'", filename.c_str()); + + return ReturnStatus(fd); +} + +bool RascsiImage::RenameImage(int fd, const PbCommand& command) +{ + string from = GetParam(command, "from"); + if (from.empty()) { + return ReturnStatus(fd, false, "Can't rename image file: Missing source filename"); + } + if (from.find('/') != string::npos) { + return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path"); + } + from = default_image_folder + "/" + from; + if (!IsValidSrcFilename(from)) { + return ReturnStatus(fd, false, "Can't rename image file: '" + from + "': Invalid name or type"); + } + + string to = GetParam(command, "to"); + if (to.empty()) { + return ReturnStatus(fd, false, "Can't rename image file '" + from + "': Missing destination filename"); + } + if (to.find('/') != string::npos) { + return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path"); + } + to = default_image_folder + "/" + to; + if (!IsValidDstFilename(to)) { + return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': File already exists"); + } + + if (rename(from.c_str(), to.c_str())) { + return ReturnStatus(fd, false, "Can't rename image file '" + from + "' to '" + to + "': " + string(strerror(errno))); + } + + LOGINFO("Renamed image file '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(fd); +} + +bool RascsiImage::CopyImage(int fd, const PbCommand& command) +{ + string from = GetParam(command, "from"); + if (from.empty()) { + return ReturnStatus(fd, false, "Can't copy image file: Missing source filename"); + } + if (from.find('/') != string::npos) { + return ReturnStatus(fd, false, "The source filename '" + from + "' must not contain a path"); + } + from = default_image_folder + "/" + from; + if (!IsValidSrcFilename(from)) { + return ReturnStatus(fd, false, "Can't copy image file: '" + from + "': Invalid name or type"); + } + + string to = GetParam(command, "to"); + if (to.empty()) { + return ReturnStatus(fd, false, "Can't copy image file '" + from + "': Missing destination filename"); + } + if (to.find('/') != string::npos) { + return ReturnStatus(fd, false, "The destination filename '" + to + "' must not contain a path"); + } + to = default_image_folder + "/" + to; + if (!IsValidDstFilename(to)) { + return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': File already exists"); + } + + struct stat st; + if (lstat(from.c_str(), &st)) { + return ReturnStatus(fd, false, "Can't access source image file '" + from + "': " + string(strerror(errno))); + } + + // Symbolic links need a special handling + if ((st.st_mode & S_IFMT) == S_IFLNK) { + if (symlink(filesystem::read_symlink(from).c_str(), to.c_str())) { + return ReturnStatus(fd, false, "Can't copy symlink '" + from + "': " + string(strerror(errno))); + } + + LOGINFO("Copied symlink '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(fd); + } + + int fd_src = open(from.c_str(), O_RDONLY, 0); + if (fd_src == -1) { + return ReturnStatus(fd, false, "Can't open source image file '" + from + "': " + string(strerror(errno))); + } + + string permission = GetParam(command, "read_only"); + // Since rascsi is running as root ensure that others can access the file + int permissions = !strcasecmp(permission.c_str(), "true") ? + S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + int fd_dst = open(to.c_str(), O_WRONLY | O_CREAT, permissions); + if (fd_dst == -1) { + close(fd_src); + + return ReturnStatus(fd, false, "Can't open destination image file '" + to + "': " + string(strerror(errno))); + } + + if (sendfile(fd_dst, fd_src, 0, st.st_size) == -1) { + close(fd_dst); + close(fd_src); + + return ReturnStatus(fd, false, "Can't copy image file '" + from + "' to '" + to + "': " + string(strerror(errno))); + } + + close(fd_dst); + close(fd_src); + + LOGINFO("Copied image file '%s' to '%s'", from.c_str(), to.c_str()); + + return ReturnStatus(fd); +} + +bool RascsiImage::SetImagePermissions(int fd, const PbCommand& command) +{ + string filename = GetParam(command, "file"); + if (filename.empty()) { + return ReturnStatus(fd, false, "Missing image filename"); + } + if (filename.find('/') != string::npos) { + return ReturnStatus(fd, false, "The image filename '" + filename + "' must not contain a path"); + } + filename = default_image_folder + "/" + filename; + if (!IsValidSrcFilename(filename)) { + return ReturnStatus(fd, false, "Can't modify image file '" + filename + "': Invalid name or type"); + } + + bool protect = command.operation() == PROTECT_IMAGE; + + int permissions = protect ? S_IRUSR | S_IRGRP | S_IROTH : S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; + + if (chmod(filename.c_str(), permissions) == -1) { + ostringstream error; + error << "Can't " << (protect ? "protect" : "unprotect") << " image file '" << filename << "': " << strerror(errno); + return ReturnStatus(fd, false, error.str()); + } + + if (protect) { + LOGINFO("Protected image file '%s'", filename.c_str()); + } + else { + LOGINFO("Unprotected image file '%s'", filename.c_str()); + } + + return ReturnStatus(fd); +} diff --git a/src/raspberrypi/rascsi_image.h b/src/raspberrypi/rascsi_image.h new file mode 100644 index 00000000..21b28e2c --- /dev/null +++ b/src/raspberrypi/rascsi_image.h @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI (*^..^*) +// for Raspberry Pi +// +// Copyright (C) 2021 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "rascsi_interface.pb.h" +#include + +using namespace std; +using namespace rascsi_interface; + +class RascsiImage +{ +public: + + RascsiImage(); + ~RascsiImage() {}; + + string GetDefaultImageFolder() const { return default_image_folder; } + string SetDefaultImageFolder(const string&); + bool IsValidSrcFilename(const string&); + bool IsValidDstFilename(const string&); + bool CreateImage(int, const PbCommand&); + bool DeleteImage(int, const PbCommand&); + bool RenameImage(int, const PbCommand&); + bool CopyImage(int, const PbCommand&); + bool SetImagePermissions(int, const PbCommand&); + +private: + + string default_image_folder; +}; diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto index 210c80fd..54bb7f8c 100644 --- a/src/raspberrypi/rascsi_interface.proto +++ b/src/raspberrypi/rascsi_interface.proto @@ -31,13 +31,14 @@ enum PbDeviceType { enum PbOperation { NONE = 0; - // Attach devices + // Attach devices and return the new device list (PbDevicesInfo) // Parameters (mutually exclusive): // "file": The filename relative to the default image folder. It must not contain a slash. // "interfaces": A prioritized comma-separated list of interfaces to create a network bridge for. ATTACH = 1; - // Detach a device. Detaches all LUNs of that device which are equal to or higher than the LUN specified. + // Detach a device and return the new device list (PbDevicesInfo) + // Detaches all LUNs of that device which are equal to or higher than the LUN specified. DETACH = 2; // Detach all devices, does not require a device list @@ -63,19 +64,20 @@ enum PbOperation { // Make medium writable (not possible for read-only media) UNPROTECT = 9; - // Gets the server information + // Gets the server information (PbServerInfo) SERVER_INFO = 10; - // Get rascsi version information + // Get rascsi version information (PbVersionInfo) VERSION_INFO = 11; - // Get information on attached devices. Returns data for all attached devices if the device list is empty. + // Get information on attached devices (PbDevicesInfo) + // Returns data for all attached devices if the device list is empty. DEVICES_INFO = 12; - // Get device properties by device type + // Get device properties by device type (PbDeviceTypesInfo) DEVICE_TYPES_INFO = 13; - // Get information on available image files in the default image folder. + // Get information on available image files in the default image folder (PbImageFilesInfo) DEFAULT_IMAGE_FILES_INFO = 14; // Get information on an image file (not necessarily in the default image folder) based on an absolute path. @@ -83,16 +85,17 @@ enum PbOperation { // "file": The filename. Either an absolute path or a path relative to the default image folder. IMAGE_FILE_INFO = 15; - // Get information on the available log levels and the current log level + // Get information on the available log levels and the current log level (PbLogLevelInfo) LOG_LEVEL_INFO = 16; - // Get the names of the available network interfaces. Only lists interfaces that are up. + // Get the names of the available network interfaces (PbNetworkInterfacesInfo) + // Only lists interfaces that are up. NETWORK_INTERFACES_INFO = 17; - // Get the mapping of extensions to device types + // Get the mapping of extensions to device types (PbMappingInfo) MAPPING_INFO = 18; - // Get the list of reserved device IDs + // Get the list of reserved device IDs (PbReservedIdsInfo) RESERVED_IDS_INFO = 19; // Set the default folder for image files. This folder must be located in /home. @@ -109,25 +112,28 @@ enum PbOperation { // Parameters: // "ids": A comma-separated list of IDs to reserve, or an empty string in order not to reserve any ID. RESERVE_IDS = 22; + + // Shut down the rascsi process + SHUT_DOWN = 23; // Create an image file. The image file must not yet exist. // Parameters: // "file": The filename, relative to the default image folder. It must not contain a slash. // "size": The file size in bytes, must be a multiple of 512 // "read_only": Optional, "true" (case-insensitive) in order to create a read-only file - CREATE_IMAGE = 23; + CREATE_IMAGE = 24; // Delete an image file. // Parameters: // "file": The filename, relative to the default image folder. It must not contain a slash. - DELETE_IMAGE = 24; + DELETE_IMAGE = 25; // Rename an image file. // Parameters: // "from": The old filename, relative to the default image folder. It must not contain a slash. // "to": The new filename, relative to the default image folder. It must not contain a slash. // The new filename must not yet exist. - RENAME_IMAGE = 25; + RENAME_IMAGE = 26; // Copy an image file. // Parameters: @@ -135,17 +141,17 @@ enum PbOperation { // "to": The destination filename, relative to the default image folder. It must not contain a slash. // "read_only": Optional, "true" (case-insensitive) in order to create a read-only file // The destination filename must not yet exist. - COPY_IMAGE = 26; + COPY_IMAGE = 27; // Write-protect an image file. // Parameters: // "file": The filename, relative to the default image folder. It must not contain a slash. - PROTECT_IMAGE = 27; + PROTECT_IMAGE = 28; // Make an image file writable. // Parameters: // "file": The filename, relative to the default image folder. It must not contain a slash. - UNPROTECT_IMAGE = 28; + UNPROTECT_IMAGE = 29; } // The supported file extensions mapped to their respective device types @@ -305,7 +311,7 @@ message PbResult { PbVersionInfo version_info = 4; // The result of a LOG_LEVEL_INFO command PbLogLevelInfo log_level_info = 5; - // The result of a DEVICES_INFO command + // The result of a DEVICES_INFO, ATTACH or DETACH command PbDevicesInfo devices_info = 6; // The result of a DEVICE_TYPES_INFO command PbDeviceTypesInfo device_types_info = 7; diff --git a/src/raspberrypi/protobuf_response_helper.cpp b/src/raspberrypi/rascsi_response.cpp similarity index 70% rename from src/raspberrypi/protobuf_response_helper.cpp rename to src/raspberrypi/rascsi_response.cpp index 646d0126..e16e7520 100644 --- a/src/raspberrypi/protobuf_response_helper.cpp +++ b/src/raspberrypi/rascsi_response.cpp @@ -14,14 +14,17 @@ #include "protobuf_util.h" #include "rascsi_version.h" #include "rascsi_interface.pb.h" -#include "protobuf_response_helper.h" +#include "rascsi_image.h" +#include "rascsi_response.h" #include using namespace rascsi_interface; +using namespace protobuf_util; -ProtobufResponseHandler::ProtobufResponseHandler() +RascsiResponse::RascsiResponse(DeviceFactory *device_factory, const RascsiImage *rascsi_image) { - device_factory = DeviceFactory::instance(); + this->device_factory = device_factory; + this->rascsi_image = rascsi_image; log_levels.push_back("trace"); log_levels.push_back("debug"); @@ -32,13 +35,7 @@ ProtobufResponseHandler::ProtobufResponseHandler() log_levels.push_back("off"); } -ProtobufResponseHandler& ProtobufResponseHandler::instance() -{ - static ProtobufResponseHandler instance; - return instance; -} - -PbDeviceProperties *ProtobufResponseHandler::GetDeviceProperties(const Device *device) +PbDeviceProperties *RascsiResponse::GetDeviceProperties(const Device *device) { PbDeviceProperties *properties = new PbDeviceProperties(); @@ -55,29 +52,29 @@ PbDeviceProperties *ProtobufResponseHandler::GetDeviceProperties(const Device *d PbDeviceType_Parse(device->GetType(), &t); if (device->SupportsParams()) { - for (const auto& param : device_factory.GetDefaultParams(t)) { + for (const auto& param : device_factory->GetDefaultParams(t)) { auto& map = *properties->mutable_default_params(); map[param.first] = param.second; } } - for (const auto& block_size : device_factory.GetSectorSizes(t)) { + for (const auto& block_size : device_factory->GetSectorSizes(t)) { properties->add_block_sizes(block_size); } return properties; } -void ProtobufResponseHandler::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) +void RascsiResponse::GetDeviceTypeProperties(PbDeviceTypesInfo& device_types_info, PbDeviceType type) { PbDeviceTypeProperties *type_properties = device_types_info.add_properties(); type_properties->set_type(type); - Device *device = device_factory.CreateDevice(type, ""); + Device *device = device_factory->CreateDevice(type, ""); type_properties->set_allocated_properties(GetDeviceProperties(device)); delete device; } -void ProtobufResponseHandler::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) +void RascsiResponse::GetAllDeviceTypeProperties(PbDeviceTypesInfo& device_types_info) { GetDeviceTypeProperties(device_types_info, SAHD); GetDeviceTypeProperties(device_types_info, SCHD); @@ -88,7 +85,7 @@ void ProtobufResponseHandler::GetAllDeviceTypeProperties(PbDeviceTypesInfo& devi GetDeviceTypeProperties(device_types_info, SCDP); } -void ProtobufResponseHandler::GetDevice(const Device *device, PbDevice *pb_device, const string& image_folder) +void RascsiResponse::GetDevice(const Device *device, PbDevice *pb_device) { pb_device->set_id(device->GetId()); pb_device->set_unit(device->GetLun()); @@ -126,18 +123,18 @@ void ProtobufResponseHandler::GetDevice(const Device *device, PbDevice *pb_devic Filepath filepath; file_support->GetPath(filepath); PbImageFile *image_file = new PbImageFile(); - GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath(), image_folder); + GetImageFile(image_file, device->IsRemovable() && !device->IsReady() ? "" : filepath.GetPath()); pb_device->set_allocated_file(image_file); } } -bool ProtobufResponseHandler::GetImageFile(PbImageFile *image_file, const string& filename, const string& image_folder) +bool RascsiResponse::GetImageFile(PbImageFile *image_file, const string& filename) { if (!filename.empty()) { image_file->set_name(filename); - image_file->set_type(device_factory.GetTypeForFile(filename)); + image_file->set_type(device_factory->GetTypeForFile(filename)); - string f = filename[0] == '/' ? filename : image_folder + "/" + filename; + string f = filename[0] == '/' ? filename : rascsi_image->GetDefaultImageFolder() + "/" + filename; image_file->set_read_only(access(f.c_str(), W_OK)); @@ -151,34 +148,35 @@ bool ProtobufResponseHandler::GetImageFile(PbImageFile *image_file, const string return false; } -PbImageFilesInfo *ProtobufResponseHandler::GetAvailableImages(PbResult& result, const string& image_folder) +PbImageFilesInfo *RascsiResponse::GetAvailableImages(PbResult& result) { PbImageFilesInfo *image_files_info = new PbImageFilesInfo(); - image_files_info->set_default_image_folder(image_folder); + 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(image_folder.c_str()); + 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 = image_folder + "/" + dir->d_name; + 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, image_folder.c_str()); + 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, image_folder.c_str()); + 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, image_folder)) { - GetImageFile(image_files_info->add_image_files(), dir->d_name, image_folder); + if (GetImageFile(image_file, dir->d_name)) { + GetImageFile(image_files_info->add_image_files(), dir->d_name); } delete image_file; } @@ -192,16 +190,16 @@ PbImageFilesInfo *ProtobufResponseHandler::GetAvailableImages(PbResult& result, return image_files_info; } -void ProtobufResponseHandler::GetAvailableImages(PbResult& result, PbServerInfo& server_info, const string& image_folder) +void RascsiResponse::GetAvailableImages(PbResult& result, PbServerInfo& server_info) { - PbImageFilesInfo *image_files_info = GetAvailableImages(result, image_folder); - image_files_info->set_default_image_folder(image_folder); + PbImageFilesInfo *image_files_info = GetAvailableImages(result); + image_files_info->set_default_image_folder(rascsi_image->GetDefaultImageFolder()); server_info.set_allocated_image_files_info(image_files_info); result.set_status(true); } -PbReservedIdsInfo *ProtobufResponseHandler::GetReservedIds(PbResult& result, const set& ids) +PbReservedIdsInfo *RascsiResponse::GetReservedIds(PbResult& result, const set& ids) { PbReservedIdsInfo *reserved_ids_info = new PbReservedIdsInfo(); for (int id : ids) { @@ -213,19 +211,19 @@ PbReservedIdsInfo *ProtobufResponseHandler::GetReservedIds(PbResult& result, con return reserved_ids_info; } -void ProtobufResponseHandler::GetDevices(PbServerInfo& server_info, const vector& devices, const string& image_folder) +void RascsiResponse::GetDevices(PbServerInfo& server_info, const vector& devices) { for (const Device *device : devices) { // Skip if unit does not exist or is not assigned if (device) { PbDevice *pb_device = server_info.mutable_devices_info()->add_devices(); - GetDevice(device, pb_device, image_folder); + GetDevice(device, pb_device); } } } -void ProtobufResponseHandler::GetDevicesInfo(PbResult& result, const PbCommand& command, const vector& devices, - const string& image_folder, int unit_count) +void RascsiResponse::GetDevicesInfo(PbResult& result, const PbCommand& command, const vector& devices, + int unit_count) { set id_sets; if (!command.devices_size()) { @@ -255,13 +253,13 @@ void ProtobufResponseHandler::GetDevicesInfo(PbResult& result, const PbCommand& for (const auto& id_set : id_sets) { const Device *device = devices[id_set.first * unit_count + id_set.second]; - GetDevice(device, devices_info->add_devices(), image_folder); + GetDevice(device, devices_info->add_devices()); } result.set_status(true); } -PbDeviceTypesInfo *ProtobufResponseHandler::GetDeviceTypesInfo(PbResult& result, const PbCommand& command) +PbDeviceTypesInfo *RascsiResponse::GetDeviceTypesInfo(PbResult& result, const PbCommand& command) { PbDeviceTypesInfo *device_types_info = new PbDeviceTypesInfo(); @@ -272,18 +270,18 @@ PbDeviceTypesInfo *ProtobufResponseHandler::GetDeviceTypesInfo(PbResult& result, return device_types_info; } -PbServerInfo *ProtobufResponseHandler::GetServerInfo(PbResult& result, const vector& devices, const set& reserved_ids, - const string& image_folder, const string& current_log_level) +PbServerInfo *RascsiResponse::GetServerInfo(PbResult& result, const vector& devices, const set& reserved_ids, + const string& current_log_level) { 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, image_folder); + GetAvailableImages(result, *server_info); server_info->set_allocated_network_interfaces_info(GetNetworkInterfacesInfo(result)); server_info->set_allocated_mapping_info(GetMappingInfo(result)); - GetDevices(*server_info, devices, image_folder); + GetDevices(*server_info, devices); server_info->set_allocated_reserved_ids_info(GetReservedIds(result, reserved_ids)); result.set_status(true); @@ -291,7 +289,7 @@ PbServerInfo *ProtobufResponseHandler::GetServerInfo(PbResult& result, const vec return server_info; } -PbVersionInfo *ProtobufResponseHandler::GetVersionInfo(PbResult& result) +PbVersionInfo *RascsiResponse::GetVersionInfo(PbResult& result) { PbVersionInfo *version_info = new PbVersionInfo(); @@ -304,7 +302,7 @@ PbVersionInfo *ProtobufResponseHandler::GetVersionInfo(PbResult& result) return version_info; } -PbLogLevelInfo *ProtobufResponseHandler::GetLogLevelInfo(PbResult& result, const string& current_log_level) +PbLogLevelInfo *RascsiResponse::GetLogLevelInfo(PbResult& result, const string& current_log_level) { PbLogLevelInfo *log_level_info = new PbLogLevelInfo(); @@ -319,11 +317,11 @@ PbLogLevelInfo *ProtobufResponseHandler::GetLogLevelInfo(PbResult& result, const return log_level_info; } -PbNetworkInterfacesInfo *ProtobufResponseHandler::GetNetworkInterfacesInfo(PbResult& result) +PbNetworkInterfacesInfo *RascsiResponse::GetNetworkInterfacesInfo(PbResult& result) { PbNetworkInterfacesInfo *network_interfaces_info = new PbNetworkInterfacesInfo(); - for (const auto& network_interface : device_factory.GetNetworkInterfaces()) { + for (const auto& network_interface : device_factory->GetNetworkInterfaces()) { network_interfaces_info->add_name(network_interface); } @@ -332,11 +330,11 @@ PbNetworkInterfacesInfo *ProtobufResponseHandler::GetNetworkInterfacesInfo(PbRes return network_interfaces_info; } -PbMappingInfo *ProtobufResponseHandler::GetMappingInfo(PbResult& result) +PbMappingInfo *RascsiResponse::GetMappingInfo(PbResult& result) { PbMappingInfo *mapping_info = new PbMappingInfo(); - for (const auto& mapping : device_factory.GetExtensionMapping()) { + for (const auto& mapping : device_factory->GetExtensionMapping()) { (*mapping_info->mutable_mapping())[mapping.first] = mapping.second; } diff --git a/src/raspberrypi/protobuf_response_helper.h b/src/raspberrypi/rascsi_response.h similarity index 63% rename from src/raspberrypi/protobuf_response_helper.h rename to src/raspberrypi/rascsi_response.h index 42fccf47..12f4ea44 100644 --- a/src/raspberrypi/protobuf_response_helper.h +++ b/src/raspberrypi/rascsi_response.h @@ -5,8 +5,6 @@ // // Copyright (C) 2021 Uwe Seimet // -// A singleton that creates responses for protobuf interface requests -// //--------------------------------------------------------------------------- #pragma once @@ -19,38 +17,39 @@ using namespace std; using namespace rascsi_interface; +class DeviceFactory; +class RascsiImage; class Device; -class ProtobufResponseHandler +class RascsiResponse { public: - ProtobufResponseHandler(); - ~ProtobufResponseHandler() {}; + RascsiResponse(DeviceFactory *, const RascsiImage *); + ~RascsiResponse() {}; - static ProtobufResponseHandler& instance(); - - bool GetImageFile(PbImageFile *, const string&, const string&); - PbImageFilesInfo *GetAvailableImages(PbResult&, const string&); + bool GetImageFile(PbImageFile *, const string&); + PbImageFilesInfo *GetAvailableImages(PbResult&); PbReservedIdsInfo *GetReservedIds(PbResult&, const set&); - void GetDevices(PbServerInfo&, const vector&, const string&); - void GetDevicesInfo(PbResult&, const PbCommand&, const vector&, const string&, int); + 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&, const string&); + PbServerInfo *GetServerInfo(PbResult&, const vector&, const set&, const string&); PbNetworkInterfacesInfo *GetNetworkInterfacesInfo(PbResult&); PbMappingInfo *GetMappingInfo(PbResult&); PbLogLevelInfo *GetLogLevelInfo(PbResult&, const string&); private: - DeviceFactory device_factory; + DeviceFactory *device_factory; + const RascsiImage *rascsi_image; vector log_levels; PbDeviceProperties *GetDeviceProperties(const Device *); - void GetDevice(const Device *, PbDevice *, const string&); + void GetDevice(const Device *, PbDevice *); void GetAllDeviceTypeProperties(PbDeviceTypesInfo&); void GetDeviceTypeProperties(PbDeviceTypesInfo&, PbDeviceType); - void GetAvailableImages(PbResult& result, PbServerInfo&, const string&); + void GetAvailableImages(PbResult& result, PbServerInfo&); }; diff --git a/src/raspberrypi/rasctl.cpp b/src/raspberrypi/rasctl.cpp index 31ad3f76..b778e0b3 100644 --- a/src/raspberrypi/rasctl.cpp +++ b/src/raspberrypi/rasctl.cpp @@ -23,6 +23,8 @@ using namespace std; using namespace rascsi_interface; +using namespace ras_util; +using namespace protobuf_util; PbOperation ParseOperation(const char *optarg) { @@ -99,7 +101,7 @@ int main(int argc, char* argv[]) cerr << "Usage: " << argv[0] << " -i ID [-u UNIT] [-c CMD] [-C FILE] [-t TYPE] [-b BLOCK_SIZE] [-n NAME] [-f FILE|PARAM] "; cerr << "[-F IMAGE_FOLDER] [-L LOG_LEVEL] [-h HOST] [-p PORT] [-r RESERVED_IDS] "; cerr << "[-C FILENAME:FILESIZE] [-d FILENAME] [-w FILENAME] [-R CURRENT_NAME:NEW_NAME] [-x CURRENT_NAME:NEW_NAME] "; - cerr << "[-e] [-E FILENAME] [-D] [-I] [-l] [-L] [-m] [-O] [-s] [-v] [-V] [-y]" << endl; + cerr << "[-e] [-E FILENAME] [-D] [-I] [-l] [-L] [-m] [-O] [-s] [-v] [-V] [-y] [-X]" << endl; cerr << " where ID := {0-7}" << endl; cerr << " UNIT := {0-31}, default is 0" << endl; cerr << " CMD := {attach|detach|insert|eject|protect|unprotect|show}" << endl; @@ -135,7 +137,7 @@ int main(int argc, char* argv[]) opterr = 1; int opt; - while ((opt = getopt(argc, argv, "elmsvDINOTVa:b:c:d:f:h:i:n:p:r:t:u:x:C:E:F:L:R:")) != -1) { + while ((opt = getopt(argc, argv, "elmsvDINOTVXa:b:c:d:f:h:i:n:p:r:t:u:x:C:E:F:L:R:")) != -1) { switch (opt) { case 'i': { int id; @@ -310,6 +312,10 @@ int main(int argc, char* argv[]) case 'T': command.set_operation(DEVICE_TYPES_INFO); break; + + case 'X': + command.set_operation(SHUT_DOWN); + break; } } diff --git a/src/raspberrypi/rasctl_commands.cpp b/src/raspberrypi/rasctl_commands.cpp index 6b3e197e..43440e03 100644 --- a/src/raspberrypi/rasctl_commands.cpp +++ b/src/raspberrypi/rasctl_commands.cpp @@ -23,6 +23,7 @@ using namespace std; using namespace rascsi_interface; +using namespace protobuf_util; RasctlCommands::RasctlCommands(PbCommand& command, const string& hostname, int port) { diff --git a/src/raspberrypi/rasctl_display.cpp b/src/raspberrypi/rasctl_display.cpp index 41a938c9..d9e6036c 100644 --- a/src/raspberrypi/rasctl_display.cpp +++ b/src/raspberrypi/rasctl_display.cpp @@ -15,6 +15,7 @@ using namespace std; using namespace rascsi_interface; +using namespace ras_util; void RasctlDisplay::DisplayDevices(const PbDevicesInfo& devices_info) { diff --git a/src/raspberrypi/rasutil.cpp b/src/raspberrypi/rasutil.cpp index e1cbc09d..f464aeba 100644 --- a/src/raspberrypi/rasutil.cpp +++ b/src/raspberrypi/rasutil.cpp @@ -15,7 +15,7 @@ using namespace std; using namespace rascsi_interface; -bool GetAsInt(const string& value, int& result) +bool ras_util::GetAsInt(const string& value, int& result) { if (value.find_first_not_of("0123456789") != string::npos) { return false; @@ -34,7 +34,7 @@ bool GetAsInt(const string& value, int& result) return true; } -string ListDevices(const list& pb_devices) +string ras_util::ListDevices(const list& pb_devices) { if (pb_devices.empty()) { return "No images currently attached."; diff --git a/src/raspberrypi/rasutil.h b/src/raspberrypi/rasutil.h index 4d9f2788..dd5d52a1 100644 --- a/src/raspberrypi/rasutil.h +++ b/src/raspberrypi/rasutil.h @@ -15,5 +15,8 @@ #include #include "rascsi_interface.pb.h" -bool GetAsInt(const std::string&, int&); -std::string ListDevices(const std::list&); +namespace ras_util +{ + bool GetAsInt(const std::string&, int&); + std::string ListDevices(const std::list&); +}