diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d7744b9..a9cc25bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build and test on code change +name: Build and test after code change on: workflow_call: diff --git a/cpp/base/property_handler.cpp b/cpp/base/property_handler.cpp index e89c3d23..9acbf61b 100644 --- a/cpp/base/property_handler.cpp +++ b/cpp/base/property_handler.cpp @@ -15,7 +15,7 @@ using namespace std; using namespace s2p_util; -void PropertyHandler::Init(const string &f, const property_map &cmd_properties) +void PropertyHandler::Init(const string &filenames, const property_map &cmd_properties) { // Clearing the property cache helps with testing because Init() can be called for different files property_cache.clear(); @@ -26,18 +26,33 @@ void PropertyHandler::Init(const string &f, const property_map &cmd_properties) property_cache[PropertyHandler::SCAN_DEPTH] = "1"; property_cache[PropertyHandler::TOKEN_FILE] = ""; - const string filename = f.empty() ? GetHomeDir() + "/" + DEFAULT_PROPERTIES_FILE : f; + if (filenames.empty()) { + ParsePropertyFile(GetHomeDir() + "/" + DEFAULT_PROPERTY_FILE, true); + } + else { + for (const auto &filename : Split(filenames, ',')) { + ParsePropertyFile(filename, false); + } + } + // Merge properties from property files and from the command line + for (const auto& [k, v] : cmd_properties) { + property_cache[k] = v; + } +} + +void PropertyHandler::ParsePropertyFile(const string &filename, bool default_file) +{ ifstream property_file(filename); - // Only report an error if an explicitly specified file is missing - if (property_file.fail() && !f.empty()) { - throw parser_exception(fmt::format("No properties file '{}'", filename)); + if (property_file.fail() && !default_file) { + // Only report an error if an explicitly specified file is missing + throw parser_exception(fmt::format("No property file '{}'", filename)); } string property; while (getline(property_file, property)) { if (property_file.fail()) { - throw parser_exception(fmt::format("Error reading from properties file '{}'", filename)); + throw parser_exception(fmt::format("Error reading from property file '{}'", filename)); } if (!property.empty() && !property.starts_with("#")) { @@ -49,11 +64,6 @@ void PropertyHandler::Init(const string &f, const property_map &cmd_properties) property_cache[kv[0]] = kv[1]; } } - - // Merge properties from property file and from command line - for (const auto& [k, v] : cmd_properties) { - property_cache[k] = v; - } } string PropertyHandler::GetProperty(const string &key) const diff --git a/cpp/base/property_handler.h b/cpp/base/property_handler.h index 6ce7a2a9..e22942de 100644 --- a/cpp/base/property_handler.h +++ b/cpp/base/property_handler.h @@ -26,7 +26,7 @@ class PropertyHandler inline static const string LOG_LEVEL = "log_level"; inline static const string MODE_PAGE = "mode_page"; inline static const string PORT = "port"; - inline static const string PROPERTY_FILE = "property_file"; + inline static const string PROPERTY_FILES = "property_files"; inline static const string RESERVED_IDS = "reserved_ids"; inline static const string SASI = "sasi"; inline static const string SCAN_DEPTH = "scan_depth"; @@ -44,6 +44,7 @@ class PropertyHandler { return property_cache; } + void ParsePropertyFile(const string&, bool); string GetProperty(const string&) const; map> GetCustomModePages(const string&, const string&) const; @@ -60,5 +61,5 @@ class PropertyHandler { '9', 9 }, { 'a', 10 }, { 'b', 11 }, { 'c', 12 }, { 'd', 13 }, { 'e', 14 }, { 'f', 15 } }; - inline static const string DEFAULT_PROPERTIES_FILE = ".config/s2p.properties"; + inline static const string DEFAULT_PROPERTY_FILE = ".config/s2p.properties"; }; diff --git a/cpp/controllers/controller_factory.cpp b/cpp/controllers/controller_factory.cpp index c2a4293b..d123b039 100644 --- a/cpp/controllers/controller_factory.cpp +++ b/cpp/controllers/controller_factory.cpp @@ -58,8 +58,10 @@ bool ControllerFactory::DeleteController(const AbstractController &controller) return controllers.erase(controller.GetTargetId()) == 1; } -void ControllerFactory::DeleteAllControllers() +bool ControllerFactory::DeleteAllControllers() { + bool has_controller = false; + unordered_set> values; ranges::transform(controllers, inserter(values, values.begin()), [](const auto &controller) { return controller.second; @@ -67,9 +69,12 @@ void ControllerFactory::DeleteAllControllers() for (const auto &controller : values) { DeleteController(*controller); + has_controller = true; } assert(controllers.empty()); + + return has_controller; } AbstractController::shutdown_mode ControllerFactory::ProcessOnController(int id_data) const diff --git a/cpp/controllers/controller_factory.h b/cpp/controllers/controller_factory.h index d0efd292..53bf50df 100644 --- a/cpp/controllers/controller_factory.h +++ b/cpp/controllers/controller_factory.h @@ -32,7 +32,7 @@ class ControllerFactory bool AttachToController(Bus&, int, shared_ptr); bool DeleteController(const AbstractController&); - void DeleteAllControllers(); + bool DeleteAllControllers(); AbstractController::shutdown_mode ProcessOnController(int) const; shared_ptr FindController(int) const; bool HasController(int) const; diff --git a/cpp/devices/mode_page_util.cpp b/cpp/devices/mode_page_util.cpp index 3552d110..3c921586 100644 --- a/cpp/devices/mode_page_util.cpp +++ b/cpp/devices/mode_page_util.cpp @@ -120,14 +120,12 @@ void mode_page_util::EnrichFormatPage(map> &pages, bool change void mode_page_util::AddAppleVendorModePage(map> &pages, bool changeable) { - // Page code 48 (30h) - Apple Vendor Mode Page - // Needed for SCCD for stock Apple driver support - // Needed for SCHD for stock Apple HD SC Setup - pages[48] = vector(30); + // Needed for SCCD for stock Apple driver support and stock Apple HD SC Setup + pages[48] = vector(24); // No changeable area if (!changeable) { constexpr const char APPLE_DATA[] = "APPLE COMPUTER, INC "; - memcpy(&pages[48].data()[2], APPLE_DATA, sizeof(APPLE_DATA)); + memcpy(&pages[48][2], APPLE_DATA, sizeof(APPLE_DATA) - 1); } } diff --git a/cpp/s2p/s2p.cpp b/cpp/s2p/s2p.cpp index 0076ebbc..f43786f3 100644 --- a/cpp/s2p/s2p.cpp +++ b/cpp/s2p/s2p.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -14,5 +14,5 @@ int main(int argc, char *argv[]) { vector args(argv, argv + argc); - return S2p().run(args); + return S2p().Run(args); } diff --git a/cpp/s2p/s2p_core.cpp b/cpp/s2p/s2p_core.cpp index ae7a2db4..48f90213 100644 --- a/cpp/s2p/s2p_core.cpp +++ b/cpp/s2p/s2p_core.cpp @@ -129,7 +129,7 @@ bool S2p::HandleDeviceListChange(const CommandContext &context, PbOperation oper return true; } -int S2p::run(span args, bool in_process) +int S2p::Run(span args, bool in_process) { GOOGLE_PROTOBUF_VERIFY_VERSION; @@ -145,7 +145,7 @@ int S2p::run(span args, bool in_process) int port; try { const auto &properties = s2p_parser.ParseArguments(args, is_sasi); - property_handler.Init(properties.at(PropertyHandler::PROPERTY_FILE), properties); + property_handler.Init(properties.at(PropertyHandler::PROPERTY_FILES), properties); if (const string &log_level = property_handler.GetProperty(PropertyHandler::LOG_LEVEL); !CommandDispatcher::SetLogLevel(log_level)) { @@ -324,7 +324,7 @@ void S2p::CreateDevices() #ifdef BUILD_SCHS // Ensure that all host services have a dispatcher for (auto d : controller_factory->GetAllDevices()) { - if (auto host_services = dynamic_pointer_cast(d); host_services != nullptr) { + if (auto host_services = dynamic_pointer_cast(d); host_services) { host_services->SetDispatcher(dispatcher); } } diff --git a/cpp/s2p/s2p_core.h b/cpp/s2p/s2p_core.h index b19767c4..5a4fe459 100644 --- a/cpp/s2p/s2p_core.h +++ b/cpp/s2p/s2p_core.h @@ -27,7 +27,7 @@ class S2p public: - int run(span, bool = false); + int Run(span, bool = false); private: diff --git a/cpp/s2p/s2p_parser.cpp b/cpp/s2p/s2p_parser.cpp index 80458444..1e22f36d 100644 --- a/cpp/s2p/s2p_parser.cpp +++ b/cpp/s2p/s2p_parser.cpp @@ -9,9 +9,11 @@ #include #include "shared/shared_exceptions.h" #include "controllers/controller_factory.h" +#include "shared/s2p_util.h" #include "s2p/s2p_parser.h" using namespace std; +using namespace s2p_util; void S2pParser::Banner(span args, bool usage) const { @@ -37,27 +39,28 @@ void S2pParser::Banner(span args, bool usage) const exit(EXIT_SUCCESS); } else { - cout << s2p_util::Banner("(Target Emulation)") << flush; + cout << s2p_util::Banner("(Device Emulation)") << flush; } } -property_map S2pParser::ParseArguments(span initial_args, bool &is_sasi) +property_map S2pParser::ParseArguments(span initial_args, bool &has_sasi) { vector args = ConvertLegacyOptions(initial_args); - string id_and_lun; + string id_lun; string type; string product_data; string block_size; + bool blue_scsi_mode = false; bool has_scsi = false; property_map properties; - properties[PropertyHandler::PROPERTY_FILE] = ""; + properties[PropertyHandler::PROPERTY_FILES] = ""; optind = 1; opterr = 0; int opt; - while ((opt = getopt(static_cast(args.size()), args.data(), "-h:-i:b:c:n:p:r:t:z:C:F:L:P:R:v")) != -1) { + while ((opt = getopt(static_cast(args.size()), args.data(), "-h:-i:b:c:n:p:r:t:z:C:F:L:P:R:vB")) != -1) { if (const auto &property = OPTIONS_TO_PROPERTIES.find(opt); property != OPTIONS_TO_PROPERTIES.end()) { properties[property->second] = optarg; continue; @@ -70,13 +73,14 @@ property_map S2pParser::ParseArguments(span initial_args, bool &is_sasi) continue; case 'h': - is_sasi = true; - id_and_lun = optarg; + id_lun = optarg; + has_sasi = true; + type = "sahd"; continue; case 'i': + id_lun = optarg; has_scsi = true; - id_and_lun = optarg; continue; case 'n': @@ -84,7 +88,11 @@ property_map S2pParser::ParseArguments(span initial_args, bool &is_sasi) continue; case 't': - type = optarg; + ranges::transform(string(optarg), back_inserter(type), ::tolower); + continue; + + case 'B': + blue_scsi_mode = true; continue; case 1: @@ -101,41 +109,97 @@ property_map S2pParser::ParseArguments(span initial_args, bool &is_sasi) break; } - const string params = optarg; + if ((has_scsi && type == "sahd") || (has_sasi && (type.empty() || type != "sahd"))) { + throw parser_exception("SCSI and SASI devices cannot be mixed"); + } - if (is_sasi) { - if (!type.empty() && type != "sahd") { - has_scsi = true; - } + string device_key; + if (!id_lun.empty()) { + device_key = fmt::format("device.{}.", id_lun); + } - type = "sahd"; + const string ¶ms = optarg; + if (blue_scsi_mode && !params.empty()) { + device_key = ParseBlueScsiFilename(properties, device_key, params, has_sasi); } - const string device_key = fmt::format("device.{}.", id_and_lun); if (!block_size.empty()) { properties[device_key + "block_size"] = block_size; } if (!type.empty()) { properties[device_key + "type"] = type; } - if (!params.empty()) { - properties[device_key + "params"] = params; - } if (!product_data.empty()) { properties[device_key + "product_data"] = product_data; } + if (!params.empty()) { + properties[device_key + "params"] = params; + } - id_and_lun = ""; + id_lun = ""; type = ""; product_data = ""; block_size = ""; } - if (has_scsi && is_sasi) { - throw parser_exception("SCSI and SASI devices cannot be mixed"); + return properties; +} + +string S2pParser::ParseBlueScsiFilename(property_map &properties, const string &d, const string &filename, bool is_sasi) +{ + const auto index = filename.find("."); + const string &specifier = index == string::npos ? filename : filename.substr(0, index); + const auto &components = Split(specifier, '_'); + + const string &type_id_lun = components[0]; + if (type_id_lun.size() < 3) { + throw parser_exception(fmt::format("Invalid BlueSCSI filename format: '{}'", specifier)); } - return properties; + // An explicit ID/LUN on the command line overrides the BlueSCSI ID/LUN + string device_key = d; + if (d.empty()) { + const char id = type_id_lun[2]; + string lun = ""; + if (type_id_lun.size() > 3) { + lun = ParseNumber(type_id_lun.substr(3)); + } + lun = !lun.empty() && lun != "0" ? ":" + lun : ""; + device_key = fmt::format("device.{0}{1}.", id, lun); + } + + const string &type = type_id_lun.substr(0, 2); + const auto &t = BLUE_SCSI_TO_S2P_TYPES.find(type); + if (t == BLUE_SCSI_TO_S2P_TYPES.end()) { + throw parser_exception(fmt::format("Invalid BlueSCSI device type: '{}'", type)); + } + if (t->second.empty()) { + throw parser_exception(fmt::format("Unsupported BlueSCSI device type: '{}'", type)); + } + if (t->second != "schd") { + properties[device_key + "type"] = t->second; + } + else { + properties[device_key + "type"] = is_sasi ? "sahd" : "schd"; + } + + string block_size = "512"; + if (components.size() > 1) { + if (const string b = ParseNumber(components[1]); !b.empty()) { + block_size = b; + } + // When there is no block_size number after the "_" separator the string is the product data + else { + properties[device_key + "product_data"] = components[1]; + } + } + properties[device_key + "block_size"] = block_size; + + if (components.size() > 2) { + properties[device_key + "product_data"] = components[2]; + } + + return device_key; } vector S2pParser::ConvertLegacyOptions(const span &initial_args) @@ -145,7 +209,7 @@ vector S2pParser::ConvertLegacyOptions(const span &initial_args) // -hd|-HD -> -h // -idn:u|-hdn:u -> -i|-h n:u vector args; - for (const string arg : initial_args) { + for (const string &arg : initial_args) { int start_of_ids = -1; for (int i = 0; i < static_cast(arg.length()); i++) { if (isdigit(arg[i])) { @@ -178,3 +242,18 @@ vector S2pParser::ConvertLegacyOptions(const span &initial_args) return args; } + +string S2pParser::ParseNumber(const string &s) +{ + string result; + size_t i = 0; + while (s.size() > i) { + if (!isdigit(s[i])) { + break; + } + + result += s[i++]; + } + + return result; +} diff --git a/cpp/s2p/s2p_parser.h b/cpp/s2p/s2p_parser.h index 653fbc70..6e7d2ec5 100644 --- a/cpp/s2p/s2p_parser.h +++ b/cpp/s2p/s2p_parser.h @@ -11,10 +11,10 @@ #include #include #include "generated/s2p_interface.pb.h" +#include "shared/s2p_util.h" #include "base/property_handler.h" using namespace std; -using namespace filesystem; using namespace s2p_interface; class S2pParser @@ -27,16 +27,27 @@ class S2pParser private: + static string ParseBlueScsiFilename(property_map&, const string&, const string&, bool); static vector ConvertLegacyOptions(const span&); + static string ParseNumber(const string&); inline static const unordered_map OPTIONS_TO_PROPERTIES = { { 'p', PropertyHandler::PORT }, { 'r', PropertyHandler::RESERVED_IDS }, { 'z', PropertyHandler::LOCALE }, - { 'C', PropertyHandler::PROPERTY_FILE }, + { 'C', PropertyHandler::PROPERTY_FILES }, { 'F', PropertyHandler::IMAGE_FOLDER }, { 'L', PropertyHandler::LOG_LEVEL }, { 'P', PropertyHandler::TOKEN_FILE }, { 'R', PropertyHandler::SCAN_DEPTH } }; + + inline static const unordered_map> BLUE_SCSI_TO_S2P_TYPES = { + { "CD", "sccd" }, + { "FD", "schd" }, + { "HD", "schd" }, + { "MO", "scmo" }, + { "RE", "scrm" }, + { "TP", "" } + }; }; diff --git a/cpp/s2pctl/s2pctl.cpp b/cpp/s2pctl/s2pctl.cpp index b5f2d253..22dd07be 100644 --- a/cpp/s2pctl/s2pctl.cpp +++ b/cpp/s2pctl/s2pctl.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -14,5 +14,5 @@ int main(int argc, char *argv[]) { const vector args(argv, argv + argc); - return ScsiCtl().run(args); + return ScsiCtl().Run(args); } diff --git a/cpp/s2pctl/s2pctl_core.h b/cpp/s2pctl/s2pctl_core.h index f99352a1..0b75b9cb 100644 --- a/cpp/s2pctl/s2pctl_core.h +++ b/cpp/s2pctl/s2pctl_core.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -21,7 +21,7 @@ class ScsiCtl ScsiCtl() = default; ~ScsiCtl() = default; - int run(const vector&) const; + int Run(const vector&) const; private: diff --git a/cpp/s2pctl/sp2ctl_core.cpp b/cpp/s2pctl/sp2ctl_core.cpp index bae1c121..aa562d3f 100644 --- a/cpp/s2pctl/sp2ctl_core.cpp +++ b/cpp/s2pctl/sp2ctl_core.cpp @@ -59,7 +59,7 @@ void ScsiCtl::Banner(const vector &args) const } } -int ScsiCtl::run(const vector &args) const +int ScsiCtl::Run(const vector &args) const { GOOGLE_PROTOBUF_VERIFY_VERSION; diff --git a/cpp/s2pdump/s2pdump.cpp b/cpp/s2pdump/s2pdump.cpp index 44ca2711..2a750822 100644 --- a/cpp/s2pdump/s2pdump.cpp +++ b/cpp/s2pdump/s2pdump.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -14,5 +14,5 @@ int main(int argc, char *argv[]) { vector args(argv, argv + argc); - return S2pDump().run(args); + return S2pDump().Run(args); } diff --git a/cpp/s2pdump/s2pdump_core.cpp b/cpp/s2pdump/s2pdump_core.cpp index 3664e9a6..ac799d6f 100644 --- a/cpp/s2pdump/s2pdump_core.cpp +++ b/cpp/s2pdump/s2pdump_core.cpp @@ -218,7 +218,7 @@ void S2pDump::ParseArguments(span args) buffer = vector(buffer_size); } -int S2pDump::run(span args, bool in_process) +int S2pDump::Run(span args, bool in_process) { to_stdout = !isatty(STDOUT_FILENO); @@ -267,8 +267,6 @@ int S2pDump::run(span args, bool in_process) return EXIT_FAILURE; } - scsi_executor->SetTarget(target_id, target_lun); - if (run_bus_scan) { ScanBus(); } @@ -320,6 +318,7 @@ void S2pDump::ScanBus() for (const auto lun : luns) { target_lun = lun; + DisplayInquiry(false); } } @@ -330,8 +329,9 @@ bool S2pDump::DisplayInquiry(bool check_type) cout << DIVIDER << "\nChecking " << (sasi ? "SASI" : "SCSI") << " target ID:LUN " << target_id << ":" << target_lun << "\n" << flush; - vector buf(36); + scsi_executor->SetTarget(target_id, target_lun); + vector buf(36); if (!scsi_executor->Inquiry(buf, sasi)) { return false; } diff --git a/cpp/s2pdump/s2pdump_core.h b/cpp/s2pdump/s2pdump_core.h index 86918f6e..9b1b58b0 100644 --- a/cpp/s2pdump/s2pdump_core.h +++ b/cpp/s2pdump/s2pdump_core.h @@ -20,7 +20,7 @@ class S2pDump public: - int run(span, bool = false); + int Run(span, bool = false); struct scsi_device_info { diff --git a/cpp/s2pexec/s2pexec.cpp b/cpp/s2pexec/s2pexec.cpp index 861624ed..7a9f4457 100644 --- a/cpp/s2pexec/s2pexec.cpp +++ b/cpp/s2pexec/s2pexec.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -14,5 +14,5 @@ int main(int argc, char *argv[]) { vector args(argv, argv + argc); - return ScsiExec().run(args); + return ScsiExec().Run(args); } diff --git a/cpp/s2pexec/s2pexec_core.cpp b/cpp/s2pexec/s2pexec_core.cpp index 4919b3e3..b3817999 100644 --- a/cpp/s2pexec/s2pexec_core.cpp +++ b/cpp/s2pexec/s2pexec_core.cpp @@ -168,7 +168,7 @@ void ScsiExec::ParseArguments(span args) } } -int ScsiExec::run(span args, bool in_process) +int ScsiExec::Run(span args, bool in_process) { if (!Banner(args)) { return EXIT_SUCCESS; @@ -212,12 +212,20 @@ int ScsiExec::run(span args, bool in_process) return status ? EXIT_SUCCESS : EXIT_FAILURE; } + const int result = GenerateOutput(input_format, input_filename, output_format, output_filename); + + CleanUp(); + + return result; +} + +int ScsiExec::GenerateOutput(S2pDumpExecutor::protobuf_format input_format, const string &input_filename, + S2pDumpExecutor::protobuf_format output_format, const string &output_filename) +{ PbResult result; if (string error = scsi_executor->Execute(input_filename, input_format, result); !error.empty()) { cerr << "Error: " << error << endl; - CleanUp(); - return EXIT_FAILURE; } @@ -226,8 +234,6 @@ int ScsiExec::run(span args, bool in_process) (void)MessageToJsonString(result, &json); cout << json << '\n'; - CleanUp(); - return EXIT_SUCCESS; } @@ -272,8 +278,6 @@ int ScsiExec::run(span args, bool in_process) break; } - CleanUp(); - return EXIT_SUCCESS; } diff --git a/cpp/s2pexec/s2pexec_core.h b/cpp/s2pexec/s2pexec_core.h index 10c2395a..2af4c5a4 100644 --- a/cpp/s2pexec/s2pexec_core.h +++ b/cpp/s2pexec/s2pexec_core.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -23,13 +23,15 @@ class ScsiExec ScsiExec() = default; ~ScsiExec() = default; - int run(span, bool = false); + int Run(span, bool = false); private: bool Banner(span) const; bool Init(bool); void ParseArguments(span); + int GenerateOutput(S2pDumpExecutor::protobuf_format, const string&, S2pDumpExecutor::protobuf_format, + const string&); bool SetLogLevel() const; diff --git a/cpp/shared_command/command_executor.cpp b/cpp/shared_command/command_executor.cpp index e6aacabb..f63c62dd 100644 --- a/cpp/shared_command/command_executor.cpp +++ b/cpp/shared_command/command_executor.cpp @@ -365,9 +365,9 @@ bool CommandExecutor::Detach(const CommandContext &context, PrimaryDevice &devic void CommandExecutor::DetachAll() const { - controller_factory->DeleteAllControllers(); - - spdlog::info("Detached all devices"); + if (controller_factory->DeleteAllControllers()) { + spdlog::info("Detached all devices"); + } } string CommandExecutor::SetReservedIds(string_view ids) diff --git a/cpp/shared_command/command_executor.h b/cpp/shared_command/command_executor.h index 774604a8..cb37fed0 100644 --- a/cpp/shared_command/command_executor.h +++ b/cpp/shared_command/command_executor.h @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2021-2023 Uwe Seimet +// Copyright (C) 2021-2024 Uwe Seimet // //--------------------------------------------------------------------------- diff --git a/cpp/test/in_process_test.cpp b/cpp/test/in_process_test.cpp index fe4a77bd..35bea801 100644 --- a/cpp/test/in_process_test.cpp +++ b/cpp/test/in_process_test.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2023 Uwe Seimet +// Copyright (C) 2023-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -60,14 +60,14 @@ int main(int argc, char *argv[]) auto target_thread = thread([&target_args]() { #endif auto s2p = make_unique(); - s2p->run(target_args, true); + s2p->Run(target_args, true); }); // Give s2p time to initialize sleep(1); auto s2pdump = make_unique(); - s2pdump->run(initiator_args, true); + s2pdump->Run(initiator_args, true); exit(EXIT_SUCCESS); } diff --git a/cpp/test/property_handler_test.cpp b/cpp/test/property_handler_test.cpp index 686eacee..2098ec45 100644 --- a/cpp/test/property_handler_test.cpp +++ b/cpp/test/property_handler_test.cpp @@ -13,14 +13,26 @@ using namespace testing; -PropertyHandler SetUpProperties(const string &properties, const property_map &cmd_properties = { }) +PropertyHandler SetUpProperties(const string &properties1, const string &properties2 = "", + const property_map &cmd_properties = { }) { auto &property_handler = PropertyHandler::Instance(); - auto [fd, filename] = OpenTempFile(); - write(fd, properties.data(), properties.size()); - close(fd); - property_handler.Init(filename, cmd_properties); - remove(filename); + string filenames; + auto [fd1, filename1] = OpenTempFile(); + filenames = filename1; + write(fd1, properties1.data(), properties1.size()); + close(fd1); + if (!properties2.empty()) { + auto [fd2, filename2] = OpenTempFile(); + filenames += ","; + filenames += filename2; + write(fd2, properties2.data(), properties2.size()); + close(fd2); + } + property_handler.Init(filenames, cmd_properties); + for (const string &filename : s2p_util::Split(filenames, ',')) { + remove(filename); + } return property_handler; } @@ -32,6 +44,9 @@ TEST(PropertyHandlerTest, Init) key2=value2 )"; const string &properties2 = + R"(key3=value3 +)"; + const string &properties3 = R"(key )"; @@ -41,11 +56,12 @@ key2=value2 property_map cmd_properties; cmd_properties["key1"] = "value2"; - property_handler = SetUpProperties(properties1, cmd_properties); + property_handler = SetUpProperties(properties1, properties2, cmd_properties); EXPECT_EQ("value2", property_handler.GetProperty("key1")); EXPECT_EQ("value2", property_handler.GetProperty("key2")); + EXPECT_EQ("value3", property_handler.GetProperty("key3")); - EXPECT_THROW(SetUpProperties(properties2), parser_exception); + EXPECT_THROW(SetUpProperties(properties3), parser_exception); } TEST(PropertyHandlerTest, GetProperty) @@ -99,9 +115,9 @@ mode_page.1._:PRODUCT2= auto property_handler = SetUpProperties(properties1); auto mode_pages = property_handler.GetCustomModePages("VENDOR", "PRODUCT"); - EXPECT_EQ(3, mode_pages.size()); + EXPECT_EQ(3UL, mode_pages.size()); auto value = mode_pages.at(0); - EXPECT_EQ(6, value.size()); + EXPECT_EQ(6UL, value.size()); EXPECT_EQ(byte { 0x00 }, value[0]); EXPECT_EQ(byte { 0x10 }, value[1]); EXPECT_EQ(byte { 0x02 }, value[2]); @@ -109,7 +125,7 @@ mode_page.1._:PRODUCT2= EXPECT_EQ(byte { 0x04 }, value[4]); EXPECT_EQ(byte { 0xff }, value[5]); value = mode_pages.at(2); - EXPECT_EQ(3, value.size()); + EXPECT_EQ(3UL, value.size()); EXPECT_EQ(byte { 0x02 }, value[0]); EXPECT_EQ(byte { 0x01 }, value[1]); EXPECT_EQ(byte { 0xb0 }, value[2]); @@ -118,9 +134,9 @@ mode_page.1._:PRODUCT2= property_handler = SetUpProperties(properties_savable); mode_pages = property_handler.GetCustomModePages("VENDOR", "PRODUCT"); - EXPECT_EQ(1, mode_pages.size()); + EXPECT_EQ(1UL, mode_pages.size()); value = mode_pages.at(1); - EXPECT_EQ(4, value.size()); + EXPECT_EQ(4UL, value.size()); EXPECT_EQ(byte { 0x81 }, value[0]); EXPECT_EQ(byte { 0x02 }, value[1]); EXPECT_EQ(byte { 0xef }, value[2]); diff --git a/cpp/test/s2p_parser_test.cpp b/cpp/test/s2p_parser_test.cpp index 72ea42c6..4cac6fd4 100644 --- a/cpp/test/s2p_parser_test.cpp +++ b/cpp/test/s2p_parser_test.cpp @@ -10,6 +10,8 @@ #include "shared/shared_exceptions.h" #include "s2p/s2p_parser.h" +// getopt() on BSD differs from Linux, so these tests cannot pass on BSD +#if !defined __FreeBSD__ && !defined __NetBSD__ void SetUpArgs(vector &args, const char *arg1, const char *arg2, const char *arg3 = nullptr, const char *arg4 = nullptr) { @@ -25,97 +27,229 @@ void SetUpArgs(vector &args, const char *arg1, const char *arg2, const ch } } -TEST(S2pParserTest, ParseArguments) +TEST(S2pParserTest, ParseArguments_SCSI2Pi) { - bool is_sasi; + bool is_sasi = false; S2pParser parser; vector args; args.emplace_back(strdup("arg0")); auto properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(1, properties.size()); - EXPECT_TRUE(properties.at(PropertyHandler::PROPERTY_FILE).empty()); + EXPECT_EQ(1UL, properties.size()); + EXPECT_TRUE(properties[PropertyHandler::PROPERTY_FILES].empty()); EXPECT_FALSE(is_sasi); SetUpArgs(args, "-p", "1"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("1", properties.at(PropertyHandler::PORT)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("1", properties[PropertyHandler::PORT]); SetUpArgs(args, "-r", "ids"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("ids", properties.at(PropertyHandler::RESERVED_IDS)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("ids", properties[PropertyHandler::RESERVED_IDS]); SetUpArgs(args, "-z", "locale"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("locale", properties.at(PropertyHandler::LOCALE)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("locale", properties[PropertyHandler::LOCALE]); SetUpArgs(args, "-C", "property_file"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(1, properties.size()); - EXPECT_EQ("property_file", properties.at(PropertyHandler::PROPERTY_FILE)); + EXPECT_EQ(1UL, properties.size()); + EXPECT_EQ("property_file", properties[PropertyHandler::PROPERTY_FILES]); SetUpArgs(args, "-F", "image_folder"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("image_folder", properties.at(PropertyHandler::IMAGE_FOLDER)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("image_folder", properties[PropertyHandler::IMAGE_FOLDER]); SetUpArgs(args, "-L", "log_level"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("log_level", properties.at(PropertyHandler::LOG_LEVEL)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("log_level", properties[PropertyHandler::LOG_LEVEL]); SetUpArgs(args, "-P", "token_file"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("token_file", properties.at(PropertyHandler::TOKEN_FILE)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("token_file", properties[PropertyHandler::TOKEN_FILE]); SetUpArgs(args, "-R", "scan_depth"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(2, properties.size()); - EXPECT_EQ("scan_depth", properties.at(PropertyHandler::SCAN_DEPTH)); + EXPECT_EQ(2UL, properties.size()); + EXPECT_EQ("scan_depth", properties[PropertyHandler::SCAN_DEPTH]); SetUpArgs(args, "-H0", "-h1"); - is_sasi = false; parser.ParseArguments(args, is_sasi); EXPECT_TRUE(is_sasi); SetUpArgs(args, "-HD0", "-hd0:1"); - is_sasi = false; parser.ParseArguments(args, is_sasi); EXPECT_TRUE(is_sasi); + is_sasi = false; - SetUpArgs(args, "-i0", "-h1"); + SetUpArgs(args, "-i0", "test1.hds", "-h1", "test2.hds"); + EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); is_sasi = false; + + SetUpArgs(args, "-i0", "-t", "sahd", "test.hds"); EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); + is_sasi = false; SetUpArgs(args, "-i0", "-b", "4096", "test.hds"); - is_sasi = false; properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(3, properties.size()); - EXPECT_EQ("4096", properties.at("device.0.block_size")); - EXPECT_EQ("test.hds", properties.at("device.0.params")); + EXPECT_EQ(3UL, properties.size()); + EXPECT_EQ("4096", properties["device.0.block_size"]); + EXPECT_EQ("test.hds", properties["device.0.params"]); - SetUpArgs(args, "-i1:2", "-t", "schd", "test"); - is_sasi = false; + SetUpArgs(args, "-i1:2", "-t", "SCHD", "test.hds"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(3, properties.size()); - EXPECT_EQ("schd", properties.at("device.1:2.type")); - EXPECT_EQ("test", properties.at("device.1:2.params")); + EXPECT_EQ(3UL, properties.size()); + EXPECT_EQ("schd", properties["device.1:2.type"]); + EXPECT_EQ("test.hds", properties["device.1:2.params"]); - SetUpArgs(args, "-h2", "test"); + SetUpArgs(args, "-h2", "test.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(3UL, properties.size()); + EXPECT_EQ("sahd", properties["device.2.type"]); + EXPECT_EQ("test.hds", properties["device.2.params"]); + EXPECT_TRUE(is_sasi); is_sasi = false; + + SetUpArgs(args, "-i1", "-n", "a:b:c", "test.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(3UL, properties.size()); + EXPECT_EQ("a:b:c", properties["device.1.product_data"]); + EXPECT_EQ("test.hds", properties["device.1.params"]); +} + +TEST(S2pParserTest, ParseArguments_BlueSCSI) +{ + bool is_sasi = false; + S2pParser parser; + + vector args; + + SetUpArgs(args, "-B", "HD2.hds"); + auto properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("512", properties["device.2.block_size"]); + EXPECT_EQ("HD2.hds", properties["device.2.params"]); + EXPECT_FALSE(is_sasi); + + SetUpArgs(args, "-B", "HD21.hds"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(3, properties.size()); - EXPECT_EQ("sahd", properties.at("device.2.type")); - EXPECT_EQ("test", properties.at("device.2.params")); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("schd", properties["device.2:1.type"]); + EXPECT_EQ("512", properties["device.2:1.block_size"]); + EXPECT_EQ("HD21.hds", properties["device.2:1.params"]); + EXPECT_FALSE(is_sasi); - SetUpArgs(args, "-i1", "-n", "a:b:c", "test"); + SetUpArgs(args, "-B", "HD20.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("512", properties["device.2.block_size"]); + EXPECT_EQ("HD20.hds", properties["device.2.params"]); + EXPECT_FALSE(is_sasi); + + SetUpArgs(args, "-i", "5", "-B", "FD2.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("schd", properties["device.5.type"]); + EXPECT_EQ("512", properties["device.5.block_size"]); + EXPECT_EQ("FD2.hds", properties["device.5.params"]); + EXPECT_FALSE(is_sasi); + + SetUpArgs(args, "-h", "5", "-B", "HD2.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("sahd", properties["device.5.type"]); + EXPECT_EQ("512", properties["device.5.block_size"]); + EXPECT_EQ("HD2.hds", properties["device.5.params"]); + EXPECT_TRUE(is_sasi); is_sasi = false; + + SetUpArgs(args, "-B", "CD13.iso"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("sccd", properties["device.1:3.type"]); + EXPECT_EQ("512", properties["device.1:3.block_size"]); + EXPECT_EQ("CD13.iso", properties["device.1:3.params"]); + + SetUpArgs(args, "-B", "MO731.mos"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("scmo", properties["device.7:31.type"]); + EXPECT_EQ("512", properties["device.7:31.block_size"]); + EXPECT_EQ("MO731.mos", properties["device.7:31.params"]); + + SetUpArgs(args, "-B", "RE731_2048.mos"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("scrm", properties["device.7:31.type"]); + EXPECT_EQ("2048", properties["device.7:31.block_size"]); + EXPECT_EQ("RE731_2048.mos", properties["device.7:31.params"]); + + SetUpArgs(args, "-b" "512", "-B", "RE731_2048.mos"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("scrm", properties["device.7:31.type"]); + EXPECT_EQ("512", properties["device.7:31.block_size"]) << "Explicit sector size provided"; + EXPECT_EQ("RE731_2048.mos", properties["device.7:31.params"]); + + SetUpArgs(args, "-B", "HD2_vendor:product:revision.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(5UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("512", properties["device.2.block_size"]); + EXPECT_EQ("vendor:product:revision", properties["device.2.product_data"]); + EXPECT_EQ("HD2_vendor:product:revision.hds", properties["device.2.params"]); + + SetUpArgs(args, "-B", "-n", "v:p:r", "HD2_vendor:product:revision.hds"); properties = parser.ParseArguments(args, is_sasi); - EXPECT_EQ(3, properties.size()); - EXPECT_EQ("a:b:c", properties.at("device.1.product_data")); - EXPECT_EQ("test", properties.at("device.1.params")); + EXPECT_EQ(5UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("512", properties["device.2.block_size"]); + EXPECT_EQ("v:p:r", properties["device.2.product_data"]) << "Explicit product data provided"; + EXPECT_EQ("HD2_vendor:product:revision.hds", properties["device.2.params"]); + + SetUpArgs(args, "-B", "HD2vendor:product:revision.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(4UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("512", properties["device.2.block_size"]); + EXPECT_EQ("HD2vendor:product:revision.hds", properties["device.2.params"]); + + SetUpArgs(args, "-B", "HD2_4096_vendor:product:revision.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(5UL, properties.size()); + EXPECT_EQ("schd", properties["device.2.type"]); + EXPECT_EQ("4096", properties["device.2.block_size"]); + EXPECT_EQ("vendor:product:revision", properties["device.2.product_data"]); + EXPECT_EQ("HD2_4096_vendor:product:revision.hds", properties["device.2.params"]); + + SetUpArgs(args, "-B", "HD1.hds", "-B", "RE131.hds"); + properties = parser.ParseArguments(args, is_sasi); + EXPECT_EQ(7UL, properties.size()); + EXPECT_EQ("schd", properties["device.1.type"]); + EXPECT_EQ("512", properties["device.1.block_size"]); + EXPECT_EQ("HD1.hds", properties["device.1.params"]); + EXPECT_EQ("scrm", properties["device.1:31.type"]); + EXPECT_EQ("512", properties["device.1:31.block_size"]); + EXPECT_EQ("RE131.hds", properties["device.1:31.params"]); + + SetUpArgs(args, "-B", "H1.hds"); + EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); + + SetUpArgs(args, "-B", "TP5.hds"); + EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); + + SetUpArgs(args, "-B", "XX2.hds"); + EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); + + SetUpArgs(args, "-B", "HD.hds"); + EXPECT_THROW(parser.ParseArguments(args, is_sasi), parser_exception); } +#endif diff --git a/cpp/test/scsi_cd_test.cpp b/cpp/test/scsi_cd_test.cpp index 60b473ea..10cfd8e3 100644 --- a/cpp/test/scsi_cd_test.cpp +++ b/cpp/test/scsi_cd_test.cpp @@ -51,7 +51,7 @@ void ScsiCdTest_SetUpModePages(map> &pages) EXPECT_EQ(24U, pages[12].size()); EXPECT_EQ(8U, pages[13].size()); EXPECT_EQ(16U, pages[14].size()); - EXPECT_EQ(30U, pages[48].size()); + EXPECT_EQ(24U, pages[48].size()); } TEST(ScsiCdTest, Inquiry) diff --git a/cpp/test/scsi_hd_test.cpp b/cpp/test/scsi_hd_test.cpp index be62032c..e1c4e205 100644 --- a/cpp/test/scsi_hd_test.cpp +++ b/cpp/test/scsi_hd_test.cpp @@ -55,7 +55,7 @@ void ScsiHdTest_SetUpModePages(map> &pages) EXPECT_EQ(8U, pages[10].size()); EXPECT_EQ(24U, pages[12].size()); EXPECT_EQ(25U, pages[37].size()); - EXPECT_EQ(30U, pages[48].size()); + EXPECT_EQ(24U, pages[48].size()); } TEST(ScsiHdTest, Inquiry) diff --git a/cpp/test/test_shared.cpp b/cpp/test/test_shared.cpp index ef89ced1..3644d234 100644 --- a/cpp/test/test_shared.cpp +++ b/cpp/test/test_shared.cpp @@ -2,7 +2,7 @@ // // SCSI target emulator and SCSI tools for the Raspberry Pi // -// Copyright (C) 2022-2023 Uwe Seimet +// Copyright (C) 2022-2024 Uwe Seimet // //--------------------------------------------------------------------------- @@ -43,8 +43,7 @@ string testing::TestShared::GetVersion() } void testing::TestShared::Inquiry(PbDeviceType type, device_type t, scsi_level l, const string &ident, - int additional_length, - bool removable, const string &extension) + int additional_length, bool removable, const string &extension) { auto [controller, device] = CreateDevice(type, 0, extension); diff --git a/doc/s2p.1 b/doc/s2p.1 index 685783f0..2d1ca960 100644 --- a/doc/s2p.1 +++ b/doc/s2p.1 @@ -3,7 +3,8 @@ s2p \- Emulates SCSI and SASI devices with the Raspberry Pi .SH SYNOPSIS .B s2p -[\fB\-C\fR \fIPROPERTY_FILE\fR] +[\fB\-B\fR] +[\fB\-C\fR \fIPROPERTY_FILES\fR] [\fB\-F\fR \fIFOLDER\fR] [\fB\-L\fR \fILOG_LEVEL[:ID:[LUN]]\fR] [\fB\-P\fR \fIACCESS_TOKEN_FILE\fR] @@ -44,8 +45,15 @@ To quit s2p press Control-C. If it is running in the background, you can kill it .SH OPTIONS .TP -.BR \-C\fI " " \fIPROPERTY_FILE -The optional property file with s2p configuration data. The default property file is ~/.config/s2p.properties. +.BR \-B\fI " " \fI +Enable BlueSCSI filename parsing compatibility mode for the respective device. In this mode the name of an image file is parsed based on the BlueSCSI parsing rules. +This mode is useful in order to share drive images between SCSI2Pi and BlueSCSI. +If there are SCSI2Pi command line options for the respective device they will override the settings derived from the filename. +If there is an additional text before the filename extension s2p tries to parse this text as INQUIRY product data. +See the BlueSCSI documentation for details on the BlueSCSI filename convention. +.TP +.BR \-C\fI " " \fIPROPERTY_FILES +An optional comma-separated list of property files with s2p configuration data. The default property file is ~/.config/s2p.properties. .TP .BR \-b\fI " " \fIBLOCK_SIZE The optional sector size, either 256, 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes.