//--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Copyright (C) 2022-2023 Uwe Seimet // //--------------------------------------------------------------------------- #include "mocks.h" #include "shared/protobuf_util.h" #include "shared/piscsi_exceptions.h" #include "controllers/controller_manager.h" #include "devices/device_factory.h" #include "generated/piscsi_interface.pb.h" #include "piscsi/command_context.h" #include "piscsi/piscsi_response.h" #include "piscsi/piscsi_executor.h" #include using namespace filesystem; using namespace piscsi_interface; using namespace protobuf_util; TEST(PiscsiExecutorTest, ProcessDeviceCmd) { const int ID = 3; const int LUN = 0; auto bus = make_shared(); ControllerManager controller_manager; MockAbstractController controller(bus, ID); auto executor = make_shared(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); definition.set_id(8); definition.set_unit(32); EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID and LUN must fail"; definition.set_unit(LUN); EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid ID must fail"; definition.set_id(ID); definition.set_unit(32); EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Invalid LUN must fail"; definition.set_unit(LUN); EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, true)) << "Unknown operation must fail"; command.set_operation(ATTACH); CommandContext context_attach(command, "", ""); EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "Operation for unknown device type must fail"; auto device1 = make_shared(LUN); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); definition.set_type(SCHS); command.set_operation(INSERT); CommandContext context_insert1(command, "", ""); EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert1, definition, true)) << "Operation unsupported by device must fail"; controller_manager.DeleteAllControllers(); definition.set_type(SCRM); auto device2 = make_shared(LUN); device2->SetRemovable(true); device2->SetProtectable(true); device2->SetReady(true); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); EXPECT_FALSE(executor->ProcessDeviceCmd(context_attach, definition, true)) << "ID and LUN already exist"; command.set_operation(START); CommandContext context_start(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_start, definition, false)); command.set_operation(PROTECT); CommandContext context_protect(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_protect, definition, false)); command.set_operation(UNPROTECT); CommandContext context_unprotect(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_unprotect, definition, false)); command.set_operation(STOP); CommandContext context_stop(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_stop, definition, false)); command.set_operation(EJECT); CommandContext context_eject(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_eject, definition, false)); command.set_operation(INSERT); SetParam(definition, "file", "filename"); CommandContext context_insert2(command, "", ""); EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, true)) << "Non-existing file"; EXPECT_FALSE(executor->ProcessDeviceCmd(context_insert2, definition, false)) << "Non-existing file"; command.set_operation(DETACH); CommandContext context_detach(command, "", ""); EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, true)); EXPECT_TRUE(executor->ProcessDeviceCmd(context_detach, definition, false)); } TEST(PiscsiExecutorTest, ProcessCmd) { auto bus = make_shared(); ControllerManager controller_manager; MockAbstractController controller(bus, 0); auto executor = make_shared(*bus, controller_manager); PbCommand command_detach_all; command_detach_all.set_operation(DETACH_ALL); CommandContext context_detach_all(command_detach_all, "", ""); EXPECT_TRUE(executor->ProcessCmd(context_detach_all)); PbCommand command_reserve_ids1; command_reserve_ids1.set_operation(RESERVE_IDS); SetParam(command_reserve_ids1, "ids", "2,3"); CommandContext context_reserve_ids1(command_reserve_ids1, "", ""); EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids1)); const unordered_set ids = executor->GetReservedIds(); EXPECT_EQ(2, ids.size()); EXPECT_TRUE(ids.contains(2)); EXPECT_TRUE(ids.contains(3)); PbCommand command_reserve_ids2; command_reserve_ids2.set_operation(RESERVE_IDS); CommandContext context_reserve_ids2(command_reserve_ids2, "", ""); EXPECT_TRUE(executor->ProcessCmd(context_reserve_ids2)); EXPECT_TRUE(executor->GetReservedIds().empty()); PbCommand command_reserve_ids3; command_reserve_ids3.set_operation(RESERVE_IDS); SetParam(command_reserve_ids3, "ids", "-1"); CommandContext context_reserve_ids3(command_reserve_ids3, "", ""); EXPECT_FALSE(executor->ProcessCmd(context_reserve_ids3)); EXPECT_TRUE(executor->GetReservedIds().empty()); PbCommand command_no_operation; command_no_operation.set_operation(NO_OPERATION); CommandContext context_no_operation(command_no_operation, "", ""); EXPECT_TRUE(executor->ProcessCmd(context_no_operation)); PbCommand command_attach1; command_attach1.set_operation(ATTACH); auto device1 = command_attach1.add_devices(); device1->set_type(SCHS); device1->set_id(-1); CommandContext context_attach1(command_attach1, "", ""); EXPECT_FALSE(executor->ProcessCmd(context_attach1)); PbCommand command_attach2; command_attach2.set_operation(ATTACH); auto device2 = command_attach2.add_devices(); device2->set_type(SCHS); device2->set_id(0); device2->set_unit(1); CommandContext context_attach2(command_attach2, "", ""); EXPECT_FALSE(executor->ProcessCmd(context_attach2)) << "LUN 0 is missing"; } TEST(PiscsiExecutorTest, Attach) { const int ID = 3; const int LUN = 0; DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); definition.set_unit(32); EXPECT_FALSE(executor.Attach(context, definition, false)); auto device = device_factory.CreateDevice(SCHD, LUN, ""); definition.set_id(ID); definition.set_unit(LUN); executor.SetReservedIds(to_string(ID)); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Reserved ID not rejected"; executor.SetReservedIds(""); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Unknown device type not rejected"; definition.set_type(PbDeviceType::SCHS); EXPECT_TRUE(executor.Attach(context, definition, false)); controller_manager.DeleteAllControllers(); definition.set_type(PbDeviceType::SCHD); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; definition.set_revision("invalid revision"); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid revision not rejected"; definition.set_revision("1234"); definition.set_block_size(1); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid sector size not rejected"; definition.set_block_size(512); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without image file not rejected"; SetParam(definition, "file", "/non_existing_file"); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected"; path filename = CreateTempFile(1); SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); SetParam(definition, "file", filename.string()); bool result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); controller_manager.DeleteAllControllers(); filename = CreateTempFile(513); SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); definition.set_type(PbDeviceType::SCCD); definition.set_unit(LUN + 1); filename = CreateTempFile(2048); SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); definition.set_type(PbDeviceType::SCMO); definition.set_unit(LUN + 2); SetParam(definition, "read_only", "true"); filename = CreateTempFile(4096); SetParam(definition, "file", filename.string()); result = executor.Attach(context, definition, false); remove(filename); EXPECT_TRUE(result); controller_manager.DeleteAllControllers(); } TEST(PiscsiExecutorTest, Insert) { DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; auto [controller, device] = CreateDevice(SCHD); PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; CommandContext context(command, "", ""); device->SetRemoved(false); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Medium is not removed"; device->SetRemoved(true); definition.set_vendor("v"); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; definition.set_vendor(""); definition.set_product("p"); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; definition.set_product(""); definition.set_revision("r"); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Product data must not be set"; definition.set_revision(""); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Filename is missing"; SetParam(definition, "file", "filename"); EXPECT_TRUE(executor.Insert(context, definition, device, true)) << "Dry-run must not fail"; EXPECT_FALSE(executor.Insert(context, definition, device, false)); definition.set_block_size(1); EXPECT_FALSE(executor.Insert(context, definition, device, false)); definition.set_block_size(0); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation must fail"; SetParam(definition, "file", "/non_existing_file"); EXPECT_FALSE(executor.Insert(context, definition, device, false)); path filename = CreateTempFile(1); SetParam(definition, "file", filename.string()); EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Too small image file not rejected"; remove(filename); filename = CreateTempFile(512); SetParam(definition, "file", filename.string()); const bool result = executor.Insert(context, definition, device, false); remove(filename); EXPECT_TRUE(result); } TEST(PiscsiExecutorTest, Detach) { const int ID = 3; const int LUN1 = 0; const int LUN2 = 1; DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); auto device1 = device_factory.CreateDevice(SCHS, LUN1, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device1)); auto device2 = device_factory.CreateDevice(SCHS, LUN2, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device2)); auto d1 = controller_manager.GetDeviceForIdAndLun(ID, LUN1); EXPECT_FALSE(executor.Detach(context, *d1, false)) << "LUNs > 0 have to be detached first"; auto d2 = controller_manager.GetDeviceForIdAndLun(ID, LUN2); EXPECT_TRUE(executor.Detach(context, *d2, false)); EXPECT_TRUE(executor.Detach(context, *d1, false)); EXPECT_TRUE(controller_manager.GetAllDevices().empty()); EXPECT_FALSE(executor.Detach(context, *d1, false)); } TEST(PiscsiExecutorTest, DetachAll) { const int ID = 4; DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); auto device = device_factory.CreateDevice(SCHS, 0, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); EXPECT_TRUE(controller_manager.HasController(ID)); EXPECT_FALSE(controller_manager.GetAllDevices().empty()); executor.DetachAll(); EXPECT_EQ(nullptr, controller_manager.FindController(ID)); EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } TEST(PiscsiExecutorTest, SetReservedIds) { DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); string error = executor.SetReservedIds("xyz"); EXPECT_FALSE(error.empty()); EXPECT_TRUE(executor.GetReservedIds().empty()); error = executor.SetReservedIds("8"); EXPECT_FALSE(error.empty()); EXPECT_TRUE(executor.GetReservedIds().empty()); error = executor.SetReservedIds("-1"); EXPECT_FALSE(error.empty()); EXPECT_TRUE(executor.GetReservedIds().empty()); error = executor.SetReservedIds(""); EXPECT_TRUE(error.empty()); EXPECT_TRUE(executor.GetReservedIds().empty()); error = executor.SetReservedIds("7,1,2,3,5"); EXPECT_TRUE(error.empty()); unordered_set reserved_ids = executor.GetReservedIds(); EXPECT_EQ(5, reserved_ids.size()); EXPECT_TRUE(reserved_ids.contains(1)); EXPECT_TRUE(reserved_ids.contains(2)); EXPECT_TRUE(reserved_ids.contains(3)); EXPECT_TRUE(reserved_ids.contains(5)); EXPECT_TRUE(reserved_ids.contains(7)); auto device = device_factory.CreateDevice(SCHS, 0, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, 5, device)); error = executor.SetReservedIds("5"); EXPECT_FALSE(error.empty()); } TEST(PiscsiExecutorTest, ValidateImageFile) { DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); auto device = dynamic_pointer_cast(device_factory.CreateDevice(SCHD, 0, "test")); EXPECT_TRUE(executor.ValidateImageFile(context, *device, "")); EXPECT_FALSE(executor.ValidateImageFile(context, *device, "/non_existing_file")); } TEST(PiscsiExecutorTest, PrintCommand) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbDeviceDefinition definition; PbCommand command; string s = executor.PrintCommand(command, definition); EXPECT_NE(s.find("operation="), string::npos); EXPECT_EQ(s.find("key1=value1"), string::npos); EXPECT_EQ(s.find("key2=value2"), string::npos); SetParam(command, "key1", "value1"); s = executor.PrintCommand(command, definition); EXPECT_NE(s.find("operation="), string::npos); EXPECT_NE(s.find("key1=value1"), string::npos); SetParam(command, "key2", "value2"); s = executor.PrintCommand(command, definition); EXPECT_NE(s.find("operation="), string::npos); EXPECT_NE(s.find("key1=value1"), string::npos); EXPECT_NE(s.find("key2=value2"), string::npos); } TEST(PiscsiExecutorTest, EnsureLun0) { DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; auto device1 = command.add_devices(); device1->set_unit(0); string error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); device1->set_unit(1); error = executor.EnsureLun0(command); EXPECT_FALSE(error.empty()); auto device2 = device_factory.CreateDevice(SCHS, 0, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, 0, device2)); error = executor.EnsureLun0(command); EXPECT_TRUE(error.empty()); } TEST(PiscsiExecutorTest, VerifyExistingIdAndLun) { const int ID = 1; const int LUN1 = 0; const int LUN2 = 3; DeviceFactory device_factory; auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); auto device = device_factory.CreateDevice(SCHS, LUN1, ""); EXPECT_TRUE(controller_manager.AttachToController(*bus, ID, device)); EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); } TEST(PiscsiExecutorTest, CreateDevice) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); EXPECT_EQ(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "")); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" EXPECT_EQ(nullptr, executor.CreateDevice(context, SAHD, 0, "")); #pragma GCC diagnostic pop EXPECT_NE(nullptr, executor.CreateDevice(context, UNDEFINED, 0, "services")); EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); } TEST(PiscsiExecutorTest, SetSectorSize) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); unordered_set sizes; auto hd = make_shared(0, sizes, false); EXPECT_FALSE(executor.SetSectorSize(context, hd, 512)); sizes.insert(512); hd = make_shared(0, sizes, false); EXPECT_TRUE(executor.SetSectorSize(context, hd, 0)); EXPECT_FALSE(executor.SetSectorSize(context, hd, 1)); EXPECT_TRUE(executor.SetSectorSize(context, hd, 512)); } TEST(PiscsiExecutorTest, ValidateOperationAgainstDevice) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); auto device = make_shared(0); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, ATTACH)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, DETACH)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, START)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, STOP)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, INSERT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, EJECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, PROTECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); device->SetStoppable(true); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, START)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, STOP)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, INSERT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, EJECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, PROTECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); device->SetRemovable(true); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, START)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, STOP)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, INSERT)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, EJECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, PROTECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); device->SetProtectable(true); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, START)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, STOP)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, INSERT)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, EJECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, PROTECT)); EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); device->SetReady(true); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, START)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, STOP)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, INSERT)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, EJECT)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, PROTECT)); EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, *device, UNPROTECT)); } TEST(PiscsiExecutorTest, ValidateIdAndLun) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); EXPECT_FALSE(executor.ValidateIdAndLun(context, -1, 0)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 8, 0)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 7, -1)); EXPECT_FALSE(executor.ValidateIdAndLun(context, 7, 32)); EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 0)); EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); } TEST(PiscsiExecutorTest, SetProductData) { auto bus = make_shared(); ControllerManager controller_manager; PiscsiExecutor executor(*bus, controller_manager); PbCommand command; CommandContext context(command, "", ""); PbDeviceDefinition definition; auto device = make_shared(0); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_vendor("123456789"); EXPECT_FALSE(executor.SetProductData(context, definition, *device)); definition.set_vendor("1"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_vendor("12345678"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_product("12345678901234567"); EXPECT_FALSE(executor.SetProductData(context, definition, *device)); definition.set_product("1"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_product("1234567890123456"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_revision("12345"); EXPECT_FALSE(executor.SetProductData(context, definition, *device)); definition.set_revision("1"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); definition.set_revision("1234"); EXPECT_TRUE(executor.SetProductData(context, definition, *device)); }