diff --git a/doc/rascsi.1 b/doc/rascsi.1 index de8d2430..102a5496 100644 --- a/doc/rascsi.1 +++ b/doc/rascsi.1 @@ -53,7 +53,7 @@ The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 5 The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'. .TP .BR \-L\fI " " \fILOG_LEVEL -The rascsi log level (trace, debug, info, warn, err, critical, off). The default log level is 'info'. +The rascsi log level (trace, debug, info, warn, err, off). The default log level is 'info'. .TP .BR \-P\fI " " \fIACCESS_TOKEN_FILE Enable authentication and read the access token from the specified file. The access token file must be owned by root and must be readable by root only. diff --git a/doc/rascsi_man_page.txt b/doc/rascsi_man_page.txt index 48275194..aace3a4b 100644 --- a/doc/rascsi_man_page.txt +++ b/doc/rascsi_man_page.txt @@ -1,92 +1,120 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is rascsi.1. Re-run 'make docs' after updating - - -rascsi(1) General Commands Manual rascsi(1) +!! ------ The native file is rascsi.1. Re-run 'make docs' after updating\n\n +rascsi(1) General Commands Manual rascsi(1) NAME rascsi - Emulates SCSI devices using the Raspberry Pi GPIO pins SYNOPSIS - rascsi [-F FOLDER] [-L LOG_LEVEL] [-P ACCESS_TOKEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r - RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]... + rascsi [-F FOLDER] [-L LOG_LEVEL] [-P ACCESS_TOKEN_FILE] [-R + SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p PORT] [-r RE‐ + SERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] + FILE]... DESCRIPTION rascsi emulates SCSI devices using the Raspberry Pi GPIO pins. - In the arguments to RaSCSI, one or more SCSI (-IDn[:u]) devices can be specified. The number (n) after the ID or HD iden‐ - tifier specifies the ID number for that device. The optional number (u) specifies the LUN (logical unit) for that device. - The default LUN is 0. For SCSI: The ID is limited from 0-7. However, typically SCSI ID 7 is reserved for the "initiator" - (the host computer). The LUN is limited from 0-31. + In the arguments to RaSCSI, one or more SCSI (-IDn[:u]) devices can be + specified. The number (n) after the ID or HD identifier specifies the + ID number for that device. The optional number (u) specifies the LUN + (logical unit) for that device. The default LUN is 0. For SCSI: The ID + is limited from 0-7. However, typically SCSI ID 7 is reserved for the + "initiator" (the host computer). The LUN is limited from 0-31. - RaSCSI will determine the type of device based upon the file extension of the FILE argument. + RaSCSI will determine the type of device based upon the file extension + of the FILE argument. hd1: SCSI Hard Disk image (generic, non-removable, SCSI-1) hds: SCSI Hard Disk image (generic, non-removable) hdr: SCSI Hard Disk image (generic, removable) - hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 computers) - hdi: SCSI Hard Disk image (Anex86 proprietary - only used with PC-98 computers) - nhd: SCSI Hard Disk image (T98Next proprietary - only used with PC-98 computers) - hda: SCSI Hard Disk image (Apple compatible - typically used with Macintosh computers) - mos: SCSI Magneto-Optical image (generic - typically used with NeXT, X68000, etc.) + hdn: SCSI Hard Disk image (NEC compatible - only used with PC-98 + computers) + hdi: SCSI Hard Disk image (Anex86 proprietary - only used with + PC-98 computers) + nhd: SCSI Hard Disk image (T98Next proprietary - only used with + PC-98 computers) + hda: SCSI Hard Disk image (Apple compatible - typically used with + Macintosh computers) + mos: SCSI Magneto-Optical image (generic - typically used with + NeXT, X68000, etc.) iso: SCSI CD-ROM or DVD-ROM image (ISO 9660 image) - For example, if you want to specify an Apple-compatible HD image on ID 0, you can use the following command: + For example, if you want to specify an Apple-compatible HD image on ID + 0, you can use the following command: sudo rascsi -ID0 /path/to/drive/hdimage.hda - Once RaSCSI starts, it will open a socket (default port is 6868) to allow external management commands. If another process - is using the rascsi port, RaSCSI will terminate, since it is likely another instance of RaSCSI. Once RaSCSI has initial‐ - ized, the rasctl utility can be used to send commands. + Once RaSCSI starts, it will open a socket (default port is 6868) to al‐ + low external management commands. If another process is using the + rascsi port, RaSCSI will terminate, since it is likely another instance + of RaSCSI. Once RaSCSI has initialized, the rasctl utility can be used + to send commands. - To quit RaSCSI, press Control + C. If it is running in the background, you can kill it using an INT signal. + To quit RaSCSI, press Control + C. If it is running in the background, + you can kill it using an INT signal. OPTIONS -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + Default size is 512 bytes. -F FOLDER - The default folder for image files. For files in this folder no absolute path needs to be specified. The initial de‐ - fault folder is '~/images'. + The default folder for image files. For files in this folder no + absolute path needs to be specified. The initial default folder + is '~/images'. -L LOG_LEVEL - The rascsi log level (trace, debug, info, warn, err, critical, off). The default log level is 'info'. + The rascsi log level (trace, debug, info, warn, err, off). The + default log level is 'info'. -P ACCESS_TOKEN_FILE - Enable authentication and read the access token from the specified file. The access token file must be owned by root - and must be readable by root only. + Enable authentication and read the access token from the speci‐ + fied file. The access token file must be owned by root and must + be readable by root only. -R SCAN_DEPTH - Scan for image files recursively, up to a depth of SCAN_DEPTH. Depth 0 means to ignore any folders within the de‐ - fault image filder. Be careful when using this option with many sub-folders in the default image folder. The default - depth is 1. + Scan for image files recursively, up to a depth of SCAN_DEPTH. + Depth 0 means to ignore any folders within the default image + filder. Be careful when using this option with many sub-folders + in the default image folder. The default depth is 1. -h Show a help page. -n VENDOR:PRODUCT:REVISION - Set the vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name - components must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with - blanks to the maxium length is automatically applied. Once set the name of a device cannot be changed. + Set the vendor, product and revision for the device, to be re‐ + turned with the INQUIRY data. A complete set of name components + must be provided. VENDOR may have up to 8, PRODUCT up to 16, RE‐ + VISION up to 4 characters. Padding with blanks to the maxium + length is automatically applied. Once set the name of a device + cannot be changed. -p PORT The rascsi server port, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. -p TYPE The optional - case-insensitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, SCLP, SCHS). If no type is specified for de‐ - vices that support an image file, rascsi tries to derive the type from the file extension. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. -p TYPE The optional case-insen‐ + sitive device type (SAHD, SCHD, SCRM, SCCD, SCMO, SCBR, SCDP, + SCLP, SCHS). If no type is specified for devices that support an + image file, rascsi tries to derive the type from the file exten‐ + sion. -v Display the rascsi version. -z LOCALE - Overrides the default locale for client-faces error messages. The client can override the locale. + Overrides the default locale for client-faces error messages. + The client can override the locale. -IDn[:u] FILE - n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0. + n is the SCSI ID number (0-7). u (0-31) is the optional LUN + (logical unit). The default LUN is 0. - FILE is the name of the image file to use for the SCSI device. For devices that do not support an image file (SCBR, - SCDP, SCLP, SCHS) the filename may have a special meaning or a dummy name can be provided. For SCBR and SCDP it is - an optioinal prioritized list of network interfaces, an optional IP address and netmask, e.g. "inter‐ - faces=eth0,eth1,wlan0:inet=10.10.20.1/24". For SCLP it is the print command to be used and a reservation timeout in - seconds, e.g. "cmd=lp -oraw %f:timeout=60". + FILE is the name of the image file to use for the SCSI device. + For devices that do not support an image file (SCBR, SCDP, SCLP, + SCHS) the filename may have a special meaning or a dummy name + can be provided. For SCBR and SCDP it is an optioinal priori‐ + tized list of network interfaces, an optional IP address and + netmask, e.g. "interfaces=eth0,eth1,wlan0:inet=10.10.20.1/24". + For SCLP it is the print command to be used and a reservation + timeout in seconds, e.g. "cmd=lp -oraw %f:timeout=60". FILE is the name of the image file to use for the SCSI device. @@ -94,22 +122,26 @@ EXAMPLES Launch RaSCSI with no emulated drives attached: rascsi - Launch RaSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2 + Launch RaSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID + 2 rascsi -ID0 /path/to/harddrive.hda -ID2 /path/to/cdimage.iso - Launch RaSCSI with a removable SCSI drive image as ID 0 and the raw device file /dev/hdb (e.g. a USB stick) and a DaynaPort - network adapter as ID 6: + Launch RaSCSI with a removable SCSI drive image as ID 0 and the raw de‐ + vice file /dev/hdb (e.g. a USB stick) and a DaynaPort network adapter + as ID 6: rascsi -ID0 -t scrm /dev/hdb -ID6 -t scdp daynaport To create an empty, 100MiB HD image, use the following command: dd if=/dev/zero of=/path/to/newimage.hda bs=512 count=204800 - In case the fallocate command is available a much faster alternative to the dd command is: + In case the fallocate command is available a much faster alternative to + the dd command is: fallocate -l 104857600 /path/to/newimage.hda SEE ALSO rasctl(1), scsimon(1), rasdump(1) - Full documentation is available at: + Full documentation is available at: + - rascsi(1) + rascsi(1) diff --git a/doc/rasctl.1 b/doc/rasctl.1 index a644934a..a75f232d 100644 --- a/doc/rasctl.1 +++ b/doc/rasctl.1 @@ -61,7 +61,7 @@ Set the default image folder. Gets the list of reserved device IDs. .TP .BR \-L\fI " "\fILOG_LEVEL -Set the rascsi log level (trace, debug, info, warn, err, critical, off). +Set the rascsi log level (trace, debug, info, warn, err, off). .TP .BR \-h\fI " " \fIHOST The rascsi host to connect to, default is 'localhost'. diff --git a/doc/rasctl_man_page.txt b/doc/rasctl_man_page.txt index ae2d31c9..d7ed524e 100644 --- a/doc/rasctl_man_page.txt +++ b/doc/rasctl_man_page.txt @@ -1,31 +1,32 @@ !! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!! -!! ------ The native file is rasctl.1. Re-run 'make docs' after updating - - -rascsi(1) General Commands Manual rascsi(1) +!! ------ The native file is rasctl.1. Re-run 'make docs' after updating\n\n +rascsi(1) General Commands Manual rascsi(1) NAME rasctl - Sends management commands to the rascsi process SYNOPSIS - rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -T | -V | -X | [-C FILENAME:FILESIZE] [-E FILENAME] [-F IM‐ - AGE_FOLDER] [-R CURRENT_NAME:NEW_NAME] [-c CMD] [-f FILE|PARAM] [-g LOG_LEVEL] [-h HOST] [-i ID [-n NAME] [-p PORT] [-r RE‐ - SERVED_IDS] [-t TYPE] [-u UNIT] [-x CURRENT_NAME:NEW_NAME] [-z LOCALE] + rasctl -e | -l | -m | -o | -s | -v | -D | -I | -L | -O | -P | -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] [-z LOCALE] DESCRIPTION - rasctl sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the de‐ - vices. + rasctl sends commands to the rascsi process to make configuration ad‐ + justments at runtime or to check the status of the devices. Either the -i or -l option should be specified at one time. Not both. You do NOT need root privileges to use rasctl. - Note: The command and type arguments are case insensitive. Only the first letter of the command/type is evaluated by the - tool. + Note: The command and type arguments are case insensitive. Only the + first letter of the command/type is evaluated by the tool. OPTIONS -C FILENAME:FILESIZE - Create an image file in the default image folder with the specified name and size in bytes. + Create an image file in the default image folder with the speci‐ + fied name and size in bytes. -D Detach all devices. @@ -38,22 +39,27 @@ OPTIONS -I Gets the list of reserved device IDs. -L LOG_LEVEL - Set the rascsi log level (trace, debug, info, warn, err, critical, off). + Set the rascsi log level (trace, debug, info, warn, err, off). -h HOST The rascsi host to connect to, default is 'localhost'. -e List all images files in the default image folder. - -N Lists all available network interfaces provided that they are up. + -N Lists all available network interfaces provided that they are + up. - -O Display the available rascsi server log levels and the current log level. + -O Display the available rascsi server log levels and the current + log level. - -P Prompt for the access token in case rascsi requires authentication. + -P Prompt for the access token in case rascsi requires authentica‐ + tion. - -l List all of the devices that are currently being emulated by RaSCSI, as well as their current status. + -l List all of the devices that are currently being emulated by + RaSCSI, as well as their current status. - -m List all file extensions recognized by RaSCSI and the device types they map to. + -m List all file extensions recognized by RaSCSI and the device + types they map to. -o Display operation meta data information. @@ -64,9 +70,11 @@ OPTIONS The rascsi port to connect to, default is 6868. -r RESERVED_IDS - Comma-separated list of IDs to reserve. Pass an empty list in order to not reserve anything. + Comma-separated list of IDs to reserve. Pass an empty list in + order to not reserve anything. - -s Display server-side settings like available images or supported device types. + -s Display server-side settings like available images or supported + device types. -T Display all device types and their properties. @@ -92,23 +100,28 @@ OPTIONS d(etach): Detach disk i(nsert): Insert media (removable media devices only) e(ject): Eject media (removable media devices only) - p(rotect): Write protect the medium (not for CD-ROMs, which are always read-only) - u(nprotect): Remove write protection from the medium (not for CD-ROMs, which are always read-only) + p(rotect): Write protect the medium (not for CD-ROMs, which + are always read-only) + u(nprotect): Remove write protection from the medium (not for + CD-ROMs, which are always read-only) s(how): Display device information eject, protect and unprotect are idempotent. -b BLOCK_SIZE - The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes. + The optional block size, either 512, 1024, 2048 or 4096 bytes. + The default size is 512 bytes. -f FILE|PARAM - Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the rascsi(1) man - page for permitted file types. + Device-specific: Either a path to a disk image file, or a param‐ + eter for a non-disk device. See the rascsi(1) man page for per‐ + mitted file types. -t TYPE - Specifies the device type. This type overrides the type derived from the file extension of the specified image. See - the rascsi(1) man page for the available device types. For some types there are shortcuts (only the first letter is - required): + Specifies the device type. This type overrides the type derived + from the file extension of the specified image. See the + rascsi(1) man page for the available device types. For some + types there are shortcuts (only the first letter is required): hd: SCSI hard disk drive rm: SCSI removable media drive cd: CD-ROM @@ -119,13 +132,17 @@ OPTIONS services: Host services device -n VENDOR:PRODUCT:REVISION - The vendor, product and revision for the device, to be returned with the INQUIRY data. A complete set of name compo‐ - nents must be provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up to 4 characters. Padding with blanks - to the maxium length is automatically applied. Once set the name of a device cannot be changed. + The vendor, product and revision for the device, to be returned + with the INQUIRY data. A complete set of name components must be + provided. VENDOR may have up to 8, PRODUCT up to 16, REVISION up + to 4 characters. Padding with blanks to the maxium length is au‐ + tomatically applied. Once set the name of a device cannot be + changed. -u UNIT - Unit number (0-31). This will default to 0. This option is only used when there are multiple SCSI devices on a - shared SCSI controller. (This is not common) + Unit number (0-31). This will default to 0. This option is only + used when there are multiple SCSI devices on a shared SCSI con‐ + troller. (This is not common) EXAMPLES Show a listing of all of the SCSI devices and their current status. @@ -138,13 +155,14 @@ EXAMPLES | 0 | 1 | SCHD | /home/pi/harddisk.hda +----+-----+------+------------------------------------- - Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM‐ - AGE0.HDS". + Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with + the contents of the file system image "HDIIMAGE0.HDS". rasctl -i 0 -f HDIIMAGE0.HDS SEE ALSO rascsi(1), scsimon(1), rasdump(1) - Full documentation is available at: + Full documentation is available at: + - rascsi(1) + rascsi(1) diff --git a/src/raspberrypi/Makefile b/src/raspberrypi/Makefile index 5a09b4aa..9a7f25a4 100644 --- a/src/raspberrypi/Makefile +++ b/src/raspberrypi/Makefile @@ -1,15 +1,11 @@ .DEFAULT_GOAL: all -# Depending on the GCC version the compilation flags differ -GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10) - ## Optional build flags: ## CROSS_COMPILE : Specify which compiler toolchain to use. ## To cross compile set this accordingly, e.g. to: ## arm-linux-gnueabihf- CROSS_COMPILE = -CC = $(CROSS_COMPILE)gcc CXX = $(CROSS_COMPILE)g++ ## DEBUG=1 : A Debug build includes the debugger symbols @@ -30,6 +26,8 @@ ifeq ("$(shell uname -s)","Linux") CXXFLAGS += -Wno-psabi endif +# Depending on the GCC version the compilation flags differ +GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10) CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP @@ -186,7 +184,7 @@ lcov: test docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt -$(SRC_SHARED): $(SRC_PROTOBUF) +$(SRC_SHARED) $(SRC_RASCSI_CORE) $(SRC_RASCTL_CORE): $(OBJ_PROTOBUF) $(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR) $(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs diff --git a/src/raspberrypi/command_util.cpp b/src/raspberrypi/command_util.cpp deleted file mode 100644 index 716b03c8..00000000 --- a/src/raspberrypi/command_util.cpp +++ /dev/null @@ -1,87 +0,0 @@ -//--------------------------------------------------------------------------- -// -// SCSI Target Emulator RaSCSI Reloaded -// for Raspberry Pi -// -// Copyright (C) 2021-2022 Uwe Seimet -// -//--------------------------------------------------------------------------- - -#include "log.h" -#include "rascsi_interface.pb.h" -#include "protobuf_serializer.h" -#include "command_util.h" -#include - -using namespace std; -using namespace rascsi_interface; - -#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ ) - -static const char COMPONENT_SEPARATOR = ':'; -static const char KEY_VALUE_SEPARATOR = '='; - -void command_util::ParseParameters(PbDeviceDefinition& device, const string& params) -{ - if (params.empty()) { - return; - } - - // Old style parameters, for backwards compatibility only. - // Only one of these parameters will be used by rascsi, depending on the device type. - if (params.find(KEY_VALUE_SEPARATOR) == string::npos) { - AddParam(device, "file", params); - if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") { - AddParam(device, "interfaces", params); - } - - return; - } - - stringstream ss(params); - string p; - while (getline(ss, p, COMPONENT_SEPARATOR)) { - if (!p.empty()) { - size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); - if (separator_pos != string::npos) { - AddParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); - } - } - } -} - -string command_util::GetParam(const PbCommand& command, const string& key) -{ - const auto& it = command.params().find(key); - return it != command.params().end() ? it->second : ""; -} - -string command_util::GetParam(const PbDeviceDefinition& device, const string& key) -{ - const auto& it = device.params().find(key); - return it != device.params().end() ? it->second : ""; -} - -void command_util::AddParam(PbCommand& command, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *command.mutable_params(); - map[key] = value; - } -} - -void command_util::AddParam(PbDevice& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} - -void command_util::AddParam(PbDeviceDefinition& device, const string& key, string_view value) -{ - if (!key.empty() && !value.empty()) { - auto& map = *device.mutable_params(); - map[key] = value; - } -} diff --git a/src/raspberrypi/controllers/abstract_controller.cpp b/src/raspberrypi/controllers/abstract_controller.cpp index a9d08b13..d33dc65a 100644 --- a/src/raspberrypi/controllers/abstract_controller.cpp +++ b/src/raspberrypi/controllers/abstract_controller.cpp @@ -11,6 +11,13 @@ #include "devices/primary_device.h" #include "abstract_controller.h" +void AbstractController::AllocateCmd(size_t size) +{ + if (size > ctrl.cmd.size()) { + ctrl.cmd.resize(size); + } +} + void AbstractController::AllocateBuffer(size_t size) { if (size > ctrl.buffer.size()) { @@ -18,6 +25,15 @@ void AbstractController::AllocateBuffer(size_t size) } } +void AbstractController::SetByteTransfer(bool b) +{ + is_byte_transfer = b; + + if (!is_byte_transfer) { + bytes_to_transfer = 0; + } +} + unordered_set> AbstractController::GetDevices() const { unordered_set> devices; @@ -88,7 +104,7 @@ void AbstractController::ProcessPhase() default: LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase())) - throw scsi_exception(); + throw scsi_exception(sense_key::ABORTED_COMMAND); break; } } @@ -105,9 +121,16 @@ bool AbstractController::AddDevice(shared_ptr device) return true; } -bool AbstractController::DeleteDevice(const shared_ptr device) +bool AbstractController::RemoveDevice(const shared_ptr device) { - return luns.erase(device->GetLun()) == 1; + const size_t count = luns.erase(device->GetLun()); + assert (count == 1); + + if (count == 1) { + device->SetController(nullptr); + } + + return count == 1; } bool AbstractController::HasDeviceForLun(int lun) const diff --git a/src/raspberrypi/controllers/abstract_controller.h b/src/raspberrypi/controllers/abstract_controller.h index 0007662d..0c0c3809 100644 --- a/src/raspberrypi/controllers/abstract_controller.h +++ b/src/raspberrypi/controllers/abstract_controller.h @@ -41,7 +41,9 @@ public: }; using ctrl_t = struct _ctrl_t { - vector cmd; // Command data, dynamically allocated per received command + // Command data, dynamically resized if required + vector cmd = vector(16); + scsi_defs::status status; // Status data int message; // Message data @@ -60,7 +62,6 @@ public: scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0; virtual void Reset(); virtual int GetInitiatorId() const = 0; - virtual void SetByteTransfer(bool) = 0; // Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback virtual int GetEffectiveLun() const = 0; @@ -74,7 +75,7 @@ public: unordered_set> GetDevices() const; shared_ptr GetDeviceForLun(int) const; bool AddDevice(shared_ptr); - bool DeleteDevice(const shared_ptr); + bool RemoveDevice(const shared_ptr); bool HasDeviceForLun(int) const; int ExtractInitiatorId(int) const; @@ -84,18 +85,23 @@ public: void SetStatus(scsi_defs::status s) { ctrl.status = s; } uint32_t GetLength() const { return ctrl.length; } + bool IsByteTransfer() const { return is_byte_transfer; } + void SetByteTransfer(bool); + protected: - scsi_defs::scsi_command GetOpcode() const { return (scsi_defs::scsi_command)ctrl.cmd[0]; } + scsi_defs::scsi_command GetOpcode() const { return static_cast(ctrl.cmd[0]); } int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; } void ProcessPhase(); - vector& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; } + vector& GetCmd() { return ctrl.cmd; } + void AllocateCmd(size_t); - bool HasValidLength() const { return ctrl.length != 0; } + bool HasValidLength() const { return ctrl.length != 0; } int GetOffset() const { return ctrl.offset; } void ResetOffset() { ctrl.offset = 0; } + void SetLength(uint32_t l) { ctrl.length = l; } void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; } private: @@ -106,7 +112,9 @@ private: int max_luns; - ctrl_t ctrl = {}; + bool is_byte_transfer = false; + uint32_t bytes_to_transfer = 0; + ctrl_t ctrl = {}; ctrl_t* GetCtrl() { return &ctrl; } }; diff --git a/src/raspberrypi/controllers/controller_manager.cpp b/src/raspberrypi/controllers/controller_manager.cpp index ce93a4da..424e4ba6 100644 --- a/src/raspberrypi/controllers/controller_manager.cpp +++ b/src/raspberrypi/controllers/controller_manager.cpp @@ -17,18 +17,22 @@ using namespace std; bool ControllerManager::AttachToScsiController(int id, shared_ptr device) { auto controller = FindController(id); - if (controller == nullptr) { + if (controller != nullptr) { + return controller->AddDevice(device); + } + + // If there is no LUN yet the first LUN must be LUN 0 + if (device->GetLun() == 0) { controller = make_shared(bus, id); + if (controller->AddDevice(device)) { controllers[id] = controller; return true; } - - return false; } - return controller->AddDevice(device); + return false; } bool ControllerManager::DeleteController(shared_ptr controller) @@ -79,7 +83,7 @@ void ControllerManager::ResetAllControllers() const shared_ptr ControllerManager::GetDeviceByIdAndLun(int id, int lun) const { - if (const auto controller = FindController(id); controller != nullptr) { + if (const auto& controller = FindController(id); controller != nullptr) { return controller->GetDeviceForLun(lun); } diff --git a/src/raspberrypi/controllers/scsi_controller.cpp b/src/raspberrypi/controllers/scsi_controller.cpp index c5aa7cf8..483e72e9 100644 --- a/src/raspberrypi/controllers/scsi_controller.cpp +++ b/src/raspberrypi/controllers/scsi_controller.cpp @@ -45,8 +45,7 @@ void ScsiController::Reset() scsi.msc = 0; scsi.msb = {}; - is_byte_transfer = false; - bytes_to_transfer = 0; + SetByteTransfer(false); } BUS::phase_t ScsiController::Process(int id) @@ -81,8 +80,6 @@ BUS::phase_t ScsiController::Process(int id) } catch(const scsi_exception&) { // Any exception should have been handled during the phase processing - assert(false); - LOGERROR("%s Unhandled SCSI error, resetting controller and bus and entering bus free phase", __PRETTY_FUNCTION__) Reset(); @@ -116,15 +113,14 @@ void ScsiController::BusFree() identified_lun = -1; - is_byte_transfer = false; - bytes_to_transfer = 0; + SetByteTransfer(false); // When the bus is free RaSCSI or the Pi may be shut down. // This code has to be executed in the bus free phase and thus has to be located in the controller. switch(shutdown_mode) { case rascsi_shutdown_mode::STOP_RASCSI: LOGINFO("RaSCSI shutdown requested") - exit(0); + exit(EXIT_SUCCESS); break; case rascsi_shutdown_mode::STOP_PI: @@ -209,7 +205,7 @@ void ScsiController::Command() return; } - InitCmd(command_byte_count); + AllocateCmd(command_byte_count); // Command data transfer stringstream s; @@ -219,7 +215,7 @@ void ScsiController::Command() } LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str()) - ctrl.length = 0; + SetLength(0); Execute(); } @@ -250,7 +246,13 @@ void ScsiController::Execute() // Use LUN 0 for INQUIRY and REQUEST SENSE because LUN0 is assumed to be always available. // INQUIRY and REQUEST SENSE have a special LUN handling of their own, required by the SCSI standard. else { - assert(HasDeviceForLun(0)); + if (!HasDeviceForLun(0)) { + LOGERROR("No LUN 0 for device %d", GetTargetId()) + + GetBuffer().data()[0] = 0x7f; + + return; + } lun = 0; } @@ -263,17 +265,22 @@ void ScsiController::Execute() device->SetStatusCode(0); } - try { - if (!device->Dispatch(GetOpcode())) { - LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode()) - - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); - } + if (!device->CheckReservation(initiator_id, GetOpcode(), ctrl.cmd[4] & 0x01)) { + Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); } - catch(const scsi_exception& e) { //NOSONAR This exception is handled properly - Error(e.get_sense_key(), e.get_asc(), e.get_status()); + else { + try { + if (!device->Dispatch(GetOpcode())) { + LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode()) - // Fall through + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); + } + } + catch(const scsi_exception& e) { //NOSONAR This exception is handled properly + Error(e.get_sense_key(), e.get_asc()); + + // Fall through + } } // SCSI-2 p.104 4.4.3 Incorrect logical unit handling @@ -307,7 +314,7 @@ void ScsiController::Status() // Data transfer is 1 byte x 1 block ResetOffset(); - ctrl.length = 1; + SetLength(1); ctrl.blocks = 1; GetBuffer()[0] = (BYTE)GetStatus(); @@ -328,14 +335,10 @@ void ScsiController::MsgIn() bus.SetCD(true); bus.SetIO(true); - // length, blocks are already set - assert(HasValidLength()); - assert(ctrl.blocks > 0); ResetOffset(); return; } - LOGTRACE("%s Transitioning to Send()", __PRETTY_FUNCTION__) Send(); } @@ -361,7 +364,7 @@ void ScsiController::MsgOut() // Data transfer is 1 byte x 1 block ResetOffset(); - ctrl.length = 1; + SetLength(1); ctrl.blocks = 1; return; @@ -392,8 +395,6 @@ void ScsiController::DataIn() bus.SetCD(false); bus.SetIO(true); - // length, blocks are already set - assert(ctrl.blocks > 0); ResetOffset(); return; @@ -453,7 +454,16 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status) int lun = GetEffectiveLun(); if (!HasDeviceForLun(lun) || asc == asc::INVALID_LUN) { - assert(HasDeviceForLun(0)); + if (!HasDeviceForLun(0)) { + LOGERROR("No LUN 0 for device %d", GetTargetId()) + + SetStatus(status); + ctrl.message = 0x00; + + Status(); + + return; + } lun = 0; } @@ -549,7 +559,7 @@ void ScsiController::Send() // status phase case BUS::phase_t::status: // Message in phase - ctrl.length = 1; + SetLength(1); ctrl.blocks = 1; GetBuffer()[0] = (BYTE)ctrl.message; MsgIn(); @@ -563,7 +573,7 @@ void ScsiController::Send() void ScsiController::Receive() { - if (is_byte_transfer) { + if (IsByteTransfer()) { ReceiveBytes(); return; } @@ -755,20 +765,15 @@ bool ScsiController::XferOut(bool cont) { assert(IsDataOut()); - if (!is_byte_transfer) { + if (!IsByteTransfer()) { return XferOutBlockOriented(cont); } - is_byte_transfer = false; + const uint32_t length = bytes_to_transfer; + SetByteTransfer(false); - if (auto device = GetDeviceForLun(GetEffectiveLun()); - device != nullptr && GetOpcode() == scsi_command::eCmdWrite6) { - return device->WriteByteSequence(GetBuffer(), bytes_to_transfer); - } - - LOGWARN("%s Received unexpected command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode()) - - return false; + auto device = GetDeviceForLun(GetEffectiveLun()); + return device != nullptr ? device->WriteByteSequence(GetBuffer(), length) : false; } void ScsiController::DataOutNonBlockOriented() @@ -791,7 +796,7 @@ void ScsiController::DataOutNonBlockOriented() // TODO Try to get rid of this cast if (auto device = dynamic_pointer_cast(GetDeviceForLun(GetEffectiveLun())); device != nullptr) { - device->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset()); + device->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset()); } else { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); @@ -833,7 +838,7 @@ bool ScsiController::XferIn(vector& buf) case scsi_command::eCmdRead16: // Read from disk try { - ctrl.length = (dynamic_pointer_cast(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next); + SetLength(dynamic_pointer_cast(GetDeviceForLun(lun))->Read(ctrl.cmd, buf, ctrl.next)); } catch(const scsi_exception&) { // If there is an error, go to the status phase @@ -873,10 +878,10 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdModeSelect6: case scsi_command::eCmdModeSelect10: try { - disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset()); + disk->ModeSelect(GetOpcode(), ctrl.cmd, GetBuffer(), GetOffset()); } catch(const scsi_exception& e) { - Error(e.get_sense_key(), e.get_asc(), e.get_status()); + Error(e.get_sense_key(), e.get_asc()); return false; } break; @@ -888,7 +893,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) case scsi_command::eCmdVerify10: case scsi_command::eCmdVerify16: { - // Special case Write function for brige + // Special case Write function for bridge // TODO This class must not know about SCSIBR if (auto bridge = dynamic_pointer_cast(disk); bridge) { if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) { @@ -914,7 +919,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) disk->Write(ctrl.cmd, GetBuffer(), ctrl.next - 1); } catch(const scsi_exception& e) { - Error(e.get_sense_key(), e.get_asc(), e.get_status()); + Error(e.get_sense_key(), e.get_asc()); // Write failed return false; @@ -928,7 +933,7 @@ bool ScsiController::XferOutBlockOriented(bool cont) // Check the next block try { - ctrl.length = disk->WriteCheck(ctrl.next - 1); + SetLength(disk->WriteCheck(ctrl.next - 1)); } catch(const scsi_exception&) { // Cannot write @@ -981,6 +986,9 @@ void ScsiController::ParseMessage() if (message_type == 0x0C) { LOGTRACE("Received BUS DEVICE RESET message") scsi.syncoffset = 0; + if (auto device = GetDeviceForLun(identified_lun); device != nullptr) { + device->DiscardReservation(); + } BusFree(); return; } @@ -995,7 +1003,7 @@ void ScsiController::ParseMessage() // Check only when synchronous transfer is possible if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) { - ctrl.length = 1; + SetLength(1); ctrl.blocks = 1; GetBuffer()[0] = 0x07; MsgIn(); @@ -1013,7 +1021,7 @@ void ScsiController::ParseMessage() } // STDR response message generation - ctrl.length = 5; + SetLength(5); ctrl.blocks = 1; GetBuffer()[0] = 0x01; GetBuffer()[1] = 0x03; @@ -1035,7 +1043,7 @@ void ScsiController::ProcessMessage() if (bus.GetATN()) { // Data transfer is 1 byte x 1 block ResetOffset(); - ctrl.length = 1; + SetLength(1); ctrl.blocks = 1; return; } diff --git a/src/raspberrypi/controllers/scsi_controller.h b/src/raspberrypi/controllers/scsi_controller.h index 52fdd5ce..5044d647 100644 --- a/src/raspberrypi/controllers/scsi_controller.h +++ b/src/raspberrypi/controllers/scsi_controller.h @@ -67,12 +67,23 @@ public: scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override; int GetInitiatorId() const override { return initiator_id; } - void SetByteTransfer(bool b) override { is_byte_transfer = b; } + // Phases + void BusFree() override; + void Selection() override; + void Command() override; + void MsgIn() override; + void MsgOut() override; void Status() override; void DataIn() override; void DataOut() override; + // TODO Make non-virtual private as soon as SysTimer calls do not segfault anymore on a regular PC, + // e.g. by using ifdef __arm__. Currently the unit tests require this method to be public. + virtual void Execute(); + + void ScheduleShutdown(rascsi_shutdown_mode mode) override { shutdown_mode = mode; } + private: // Execution start time @@ -84,16 +95,6 @@ private: // The LUN from the IDENTIFY message int identified_lun = -1; - bool is_byte_transfer = false; - uint32_t bytes_to_transfer = 0; - - // Phases - void BusFree() override; - void Selection() override; - void Command() override; - void MsgIn() override; - void MsgOut() override; - // Data transfer void Send(); bool XferMsg(int); @@ -102,7 +103,6 @@ private: bool XferOutBlockOriented(bool); void ReceiveBytes(); - void Execute(); void DataOutNonBlockOriented(); void Receive(); @@ -110,8 +110,6 @@ private: void ParseMessage(); void ProcessMessage(); - void ScheduleShutdown(rascsi_shutdown_mode mode) override { shutdown_mode = mode; } - void Sleep(); scsi_t scsi = {}; diff --git a/src/raspberrypi/devices/device.cpp b/src/raspberrypi/devices/device.cpp index 83862157..7a68e06a 100644 --- a/src/raspberrypi/devices/device.cpp +++ b/src/raspberrypi/devices/device.cpp @@ -16,10 +16,8 @@ using namespace std; -Device::Device(const string& type, int lun) : type(type), lun(lun) +Device::Device(PbDeviceType type, int lun) : type(type), lun(lun) { - assert(type.length() == 4); - ostringstream os; os << setw(2) << setfill('0') << rascsi_major_version << setw(2) << setfill('0') << rascsi_minor_version; revision = os.str(); @@ -34,7 +32,7 @@ void Device::Reset() void Device::SetProtected(bool b) { - if (!read_only) { + if (protectable && !read_only) { write_protected = b; } } diff --git a/src/raspberrypi/devices/device.h b/src/raspberrypi/devices/device.h index edda7904..5a8fd383 100644 --- a/src/raspberrypi/devices/device.h +++ b/src/raspberrypi/devices/device.h @@ -9,16 +9,18 @@ #pragma once +#include "rascsi_interface.pb.h" #include #include using namespace std; +using namespace rascsi_interface; class Device //NOSONAR The number of fields and methods is justified, the complexity is low { const string DEFAULT_VENDOR = "RaSCSI"; - string type; + PbDeviceType type; bool ready = false; bool reset = false; @@ -42,9 +44,12 @@ class Device //NOSONAR The number of fields and methods is justified, the comple bool lockable = false; bool locked = false; - // Device can be created with parameters + // A device can be created with parameters bool supports_params = false; + // A device can support an image file + bool supports_file = false; + // Immutable LUN int lun; @@ -74,18 +79,25 @@ protected: bool IsAttn() const { return attn; } void SetAttn(bool b) { attn = b; } + void SetRemovable(bool b) { removable = b; } + void SetStoppable(bool b) { stoppable = b; } + void SetStopped(bool b) { stopped = b; } + void SetLockable(bool b) { lockable = b; } + void SetLocked(bool b) { locked = b; } + int GetStatusCode() const { return status_code; } string GetParam(const string&) const; void SetParams(const unordered_map&); - Device(const string&, int); + Device(PbDeviceType, int); public: virtual ~Device() = default; - const string& GetType() const { return type; } + PbDeviceType GetType() const { return type; } + const char *GetTypeString() const { return PbDeviceType_Name(type).c_str(); } bool IsReady() const { return ready; } virtual void Reset(); @@ -96,20 +108,13 @@ public: void SetProtected(bool); bool IsReadOnly() const { return read_only; } void SetReadOnly(bool b) { read_only = b; } - bool IsStoppable() const { return stoppable; } - void SetStoppable(bool b) { stoppable = b; } bool IsStopped() const { return stopped; } - void SetStopped(bool b) { stopped = b; } bool IsRemovable() const { return removable; } - void SetRemovable(bool b) { removable = b; } bool IsRemoved() const { return removed; } void SetRemoved(bool b) { removed = b; } - bool IsLockable() const { return lockable; } - void SetLockable(bool b) { lockable = b; } bool IsLocked() const { return locked; } - void SetLocked(bool b) { locked = b; } virtual int GetId() const = 0; int GetLun() const { return lun; } @@ -123,8 +128,9 @@ public: string GetPaddedName() const; bool SupportsParams() const { return supports_params; } - virtual bool SupportsFile() const { return !supports_params; } + bool SupportsFile() const { return supports_file; } void SupportsParams(bool b) { supports_params = b; } + void SupportsFile(bool b) { supports_file = b; } unordered_map GetParams() const { return params; } void SetDefaultParams(const unordered_map& p) { default_params = p; } diff --git a/src/raspberrypi/devices/device_factory.cpp b/src/raspberrypi/devices/device_factory.cpp index 5a3ab52a..759cbb91 100644 --- a/src/raspberrypi/devices/device_factory.cpp +++ b/src/raspberrypi/devices/device_factory.cpp @@ -16,6 +16,7 @@ #include "scsi_daynaport.h" #include "rascsi_exceptions.h" #include "host_services.h" +#include "rasutil.h" #include "device_factory.h" #include #include @@ -24,6 +25,7 @@ using namespace std; using namespace rascsi_interface; +using namespace ras_util; DeviceFactory::DeviceFactory() { @@ -45,7 +47,6 @@ DeviceFactory::DeviceFactory() default_params[SCDP]["interface"] = network_interfaces; default_params[SCDP]["inet"] = DEFAULT_IP; default_params[SCLP]["cmd"] = "lp -oraw %f"; - default_params[SCLP]["timeout"] = "30"; extension_mapping["hd1"] = SCHD; extension_mapping["hds"] = SCHD; @@ -63,20 +64,9 @@ DeviceFactory::DeviceFactory() device_mapping["services"] = SCHS; } -string DeviceFactory::GetExtension(const string& filename) const -{ - string ext; - if (const size_t separator = filename.rfind('.'); separator != string::npos) { - ext = filename.substr(separator + 1); - } - std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); - - return ext; -} - PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const { - if (const auto& it = extension_mapping.find(GetExtension(filename)); it != extension_mapping.end()) { + if (const auto& it = extension_mapping.find(GetExtensionLowerCase(filename)); it != extension_mapping.end()) { return it->second; } @@ -87,7 +77,6 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const return UNDEFINED; } -// ID -1 is used by rascsi to create a temporary device shared_ptr DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type, int lun, const string& filename) { @@ -102,7 +91,7 @@ shared_ptr DeviceFactory::CreateDevice(const ControllerManager& c shared_ptr device; switch (type) { case SCHD: { - if (const string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { + if (const string ext = GetExtensionLowerCase(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") { device = make_shared(lun); } else { device = make_shared(lun, sector_sizes[SCHD], false, @@ -114,35 +103,21 @@ shared_ptr DeviceFactory::CreateDevice(const ControllerManager& c device->SetProduct("FIREBALL"); } } - device->SetProtectable(true); - device->SetStoppable(true); break; } case SCRM: device = make_shared(lun, sector_sizes[SCRM], true); - device->SetProtectable(true); - device->SetStoppable(true); - device->SetRemovable(true); - device->SetLockable(true); device->SetProduct("SCSI HD (REM.)"); break; case SCMO: device = make_shared(lun, sector_sizes[SCMO]); - device->SetProtectable(true); - device->SetStoppable(true); - device->SetRemovable(true); - device->SetLockable(true); device->SetProduct("SCSI MO"); break; case SCCD: device = make_shared(lun, sector_sizes[SCCD]); - device->SetReadOnly(true); - device->SetStoppable(true); - device->SetRemovable(true); - device->SetLockable(true); device->SetProduct("SCSI CD-ROM"); break; @@ -150,7 +125,6 @@ shared_ptr DeviceFactory::CreateDevice(const ControllerManager& c device = make_shared(lun); // Since this is an emulation for a specific driver the product name has to be set accordingly device->SetProduct("RASCSI BRIDGE"); - device->SupportsParams(true); device->SetDefaultParams(default_params[SCBR]); break; @@ -160,7 +134,6 @@ shared_ptr DeviceFactory::CreateDevice(const ControllerManager& c device->SetVendor("Dayna"); device->SetProduct("SCSI/Link"); device->SetRevision("1.4a"); - device->SupportsParams(true); device->SetDefaultParams(default_params[SCDP]); break; @@ -174,7 +147,6 @@ shared_ptr DeviceFactory::CreateDevice(const ControllerManager& c case SCLP: device = make_shared(lun); device->SetProduct("SCSI PRINTER"); - device->SupportsParams(true); device->SetDefaultParams(default_params[SCLP]); break; @@ -191,14 +163,6 @@ const unordered_set& DeviceFactory::GetSectorSizes(PbDeviceType type) return it != sector_sizes.end() ? it->second : empty_set; } -const unordered_set& DeviceFactory::GetSectorSizes(const string& type) const -{ - PbDeviceType t = UNDEFINED; - PbDeviceType_Parse(type, &t); - - return GetSectorSizes(t); -} - const unordered_map& DeviceFactory::GetDefaultParams(PbDeviceType type) const { const auto& it = default_params.find(type); diff --git a/src/raspberrypi/devices/device_factory.h b/src/raspberrypi/devices/device_factory.h index f5e06ca8..0ed0829a 100644 --- a/src/raspberrypi/devices/device_factory.h +++ b/src/raspberrypi/devices/device_factory.h @@ -35,15 +35,12 @@ public: shared_ptr CreateDevice(const ControllerManager&, PbDeviceType, int, const string&); PbDeviceType GetTypeForFile(const string&) const; const unordered_set& GetSectorSizes(PbDeviceType type) const; - const unordered_set& GetSectorSizes(const string&) const; const unordered_map& GetDefaultParams(PbDeviceType type) const; list GetNetworkInterfaces() const; const unordered_map& GetExtensionMapping() const { return extension_mapping; } private: - string GetExtension(const string&) const; - unordered_map> sector_sizes; unordered_map> default_params; diff --git a/src/raspberrypi/devices/disk.cpp b/src/raspberrypi/devices/disk.cpp index ad2f1660..823a4ccb 100644 --- a/src/raspberrypi/devices/disk.cpp +++ b/src/raspberrypi/devices/disk.cpp @@ -23,20 +23,19 @@ using namespace scsi_defs; using namespace scsi_command_util; -unordered_map Disk::reserved_files; +const unordered_map Disk::shift_counts = { { 512, 9 }, { 1024, 10 }, { 2048, 11 }, { 4096, 12 } }; -Disk::Disk(const string& type, int lun) : ModePageDevice(type, lun) +Disk::Disk(PbDeviceType type, int lun) : StorageDevice(type, lun) { - dispatcher.Add(scsi_command::eCmdRezero, "Rezero", &Disk::Rezero); + // REZERO implementation is identical with Seek + dispatcher.Add(scsi_command::eCmdRezero, "Rezero", &Disk::Seek); dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit); - dispatcher.Add(scsi_command::eCmdReassign, "ReassignBlocks", &Disk::ReassignBlocks); + // REASSIGN BLOCKS implementation is identical with Seek + dispatcher.Add(scsi_command::eCmdReassign, "ReassignBlocks", &Disk::Seek); dispatcher.Add(scsi_command::eCmdRead6, "Read6", &Disk::Read6); dispatcher.Add(scsi_command::eCmdWrite6, "Write6", &Disk::Write6); dispatcher.Add(scsi_command::eCmdSeek6, "Seek6", &Disk::Seek6); - dispatcher.Add(scsi_command::eCmdReserve6, "Reserve6", &Disk::Reserve); - dispatcher.Add(scsi_command::eCmdRelease6, "Release6", &Disk::Release); dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &Disk::StartStopUnit); - dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &Disk::SendDiagnostic); dispatcher.Add(scsi_command::eCmdRemoval, "PreventAllowMediumRemoval", &Disk::PreventAllowMediumRemoval); dispatcher.Add(scsi_command::eCmdReadCapacity10, "ReadCapacity10", &Disk::ReadCapacity10); dispatcher.Add(scsi_command::eCmdRead10, "Read10", &Disk::Read10); @@ -49,8 +48,6 @@ Disk::Disk(const string& type, int lun) : ModePageDevice(type, lun) dispatcher.Add(scsi_command::eCmdSynchronizeCache10, "SynchronizeCache10", &Disk::SynchronizeCache); dispatcher.Add(scsi_command::eCmdSynchronizeCache16, "SynchronizeCache16", &Disk::SynchronizeCache); dispatcher.Add(scsi_command::eCmdReadDefectData10, "ReadDefectData10", &Disk::ReadDefectData10); - dispatcher.Add(scsi_command::eCmdReserve10, "Reserve10", &Disk::Reserve); - dispatcher.Add(scsi_command::eCmdRelease10, "Release10", &Disk::Release); dispatcher.Add(scsi_command::eCmdRead16, "Read16", &Disk::Read16); dispatcher.Add(scsi_command::eCmdWrite16, "Write16", &Disk::Write16); dispatcher.Add(scsi_command::eCmdVerify16, "Verify16", &Disk::Verify16); @@ -68,10 +65,10 @@ Disk::~Disk() bool Disk::Dispatch(scsi_command cmd) { // Media changes must be reported on the next access, i.e. not only for TEST UNIT READY - if (is_medium_changed) { + if (IsMediumChanged()) { assert(IsRemovable()); - is_medium_changed = false; + SetMediumChanged(false); throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE); } @@ -80,46 +77,19 @@ bool Disk::Dispatch(scsi_command cmd) return dispatcher.Dispatch(this, cmd) ? true : super::Dispatch(cmd); } -//--------------------------------------------------------------------------- -// -// Open -// * Call as a post-process after successful opening in a derived class -// -//--------------------------------------------------------------------------- -void Disk::Open(const Filepath& path) +void Disk::SetUpCache(off_t image_offset, bool raw) { - if (blocks == 0) { - throw io_exception("Disk has 0 blocks"); - } - - SetReady(true); - - // Can read/write open - if (Fileio fio; fio.Open(path, Fileio::OpenMode::ReadWrite)) { - // Write permission - fio.Close(); - } else { - // Permanently write-protected - SetReadOnly(true); - SetProtectable(false); - SetProtected(false); - } - - SetStopped(false); - SetRemoved(false); - SetLocked(false); -} - -void Disk::SetUpCache(const Filepath& path, off_t image_offset, bool raw) -{ - assert(cache == nullptr); - cache = make_unique(path, size_shift_count, (uint32_t)blocks, image_offset); + Filepath path; + path.SetPath(GetFilename().c_str()); + cache = make_unique(path, size_shift_count, (uint32_t)GetBlockCount(), image_offset); cache->SetRawMode(raw); } -void Disk::ResizeCache(const Filepath& path, bool raw) +void Disk::ResizeCache(const string& filename, bool raw) { - cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)blocks)); + Filepath path; + path.SetPath(filename.c_str()); + cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)GetBlockCount())); cache->SetRawMode(raw); } @@ -130,11 +100,6 @@ void Disk::FlushCache() } } -void Disk::Rezero() -{ - Seek(); -} - void Disk::FormatUnit() { CheckReady(); @@ -147,11 +112,6 @@ void Disk::FormatUnit() EnterStatusPhase(); } -void Disk::ReassignBlocks() -{ - Seek(); -} - void Disk::Read(access_mode mode) { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, mode)) { @@ -168,21 +128,6 @@ void Disk::Read(access_mode mode) } } -void Disk::Read6() -{ - Read(RW6); -} - -void Disk::Read10() -{ - Read(RW10); -} - -void Disk::Read16() -{ - Read(RW16); -} - void Disk::ReadWriteLong10() { // Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard @@ -222,21 +167,6 @@ void Disk::Write(access_mode mode) } } -void Disk::Write6() -{ - Write(RW6); -} - -void Disk::Write10() -{ - Write(RW10); -} - -void Disk::Write16() -{ - Write(RW16); -} - void Disk::Verify(access_mode mode) { if (uint64_t start; CheckAndGetStartAndCount(start, ctrl->blocks, mode)) { @@ -259,16 +189,6 @@ void Disk::Verify(access_mode mode) } } -void Disk::Verify10() -{ - Verify(RW10); -} - -void Disk::Verify16() -{ - Verify(RW16); -} - void Disk::StartStopUnit() { const bool start = ctrl->cmd[4] & 0x01; @@ -284,8 +204,6 @@ void Disk::StartStopUnit() } if (!start) { - FlushCache(); - // Look at the eject bit and eject if necessary if (load) { if (IsLocked()) { @@ -295,24 +213,12 @@ void Disk::StartStopUnit() // Eject if (!Eject(false)) { - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED); } } - } - - EnterStatusPhase(); -} - -void Disk::SendDiagnostic() -{ - // Do not support PF bit - if (ctrl->cmd[1] & 0x10) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } - - // Do not support parameter list - if ((ctrl->cmd[3] != 0) || (ctrl->cmd[4] != 0)) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + else { + FlushCache(); + } } EnterStatusPhase(); @@ -349,16 +255,6 @@ void Disk::ReadDefectData10() EnterDataInPhase(); } -void Disk::MediumChanged() -{ - if (IsRemovable()) { - is_medium_changed = true; - } - else { - LOGERROR("Medium change requested for non-removable medium") - } -} - bool Disk::Eject(bool force) { const bool status = super::Eject(force); @@ -395,24 +291,15 @@ int Disk::ModeSense6(const vector& cdb, vector& buf) const // Only if ready if (IsReady()) { // Short LBA mode parameter block descriptor (number of blocks and block length) - SetInt32(buf, 4, (uint32_t)blocks); + SetInt32(buf, 4, (uint32_t)GetBlockCount()); SetInt32(buf, 8, GetSectorSizeInBytes()); } size = 12; } - size += super::AddModePages(cdb, buf, size, length - size); - if (size > 255) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } + size = AddModePages(cdb, buf, size, length, 255); - // Do not return more than ALLOCATION LENGTH bytes - if (size > length) { - size = length; - } - - // Final setting of mode data length buf[0] = (BYTE)size; return size; @@ -434,7 +321,7 @@ int Disk::ModeSense10(const vector& cdb, vector& buf) const // Add block descriptor if DBD is 0, only if ready if ((cdb[1] & 0x08) == 0 && IsReady()) { - uint64_t disk_blocks = blocks; + uint64_t disk_blocks = GetBlockCount(); uint32_t disk_size = GetSectorSizeInBytes(); // Check LLBAA for short or long block descriptor @@ -463,17 +350,8 @@ int Disk::ModeSense10(const vector& cdb, vector& buf) const } } - size += super::AddModePages(cdb, buf, size, length - size); - if (size > 65535) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } + size = AddModePages(cdb, buf, size, length, 65535); - // Do not return more than ALLOCATION LENGTH bytes - if (size > length) { - size = length; - } - - // Final setting of mode data length SetInt16(buf, 0, size); return size; @@ -491,7 +369,7 @@ void Disk::SetUpModePages(map>& pages, int page, bool changeab AddFormatPage(pages, changeable); } - // Page code 4 (drive parameter) + // Page code 4 (rigid drive page) if (page == 0x04 || page == 0x3f) { AddDrivePage(pages, changeable); } @@ -567,7 +445,7 @@ void Disk::AddDrivePage(map>& pages, bool changeable) const if (IsReady()) { // Set the number of cylinders (total number of blocks // divided by 25 sectors/track and 8 heads) - uint64_t cylinders = blocks; + uint64_t cylinders = GetBlockCount(); cylinders >>= 3; cylinders /= 25; SetInt32(buf, 0x01, (uint32_t)cylinders); @@ -607,12 +485,6 @@ void Disk::AddCachePage(map>& pages, bool changeable) const pages[8] = buf; } -void Disk::AddVendorPage(map>&, int, bool) const -{ - // Nothing to add by default -} - -// TODO Read more than one block in a single call. Currently blocked by the the track-oriented cache int Disk::Read(const vector&, vector& buf, uint64_t block) { LOGTRACE("%s", __PRETTY_FUNCTION__) @@ -620,7 +492,7 @@ int Disk::Read(const vector&, vector& buf, uint64_t block) CheckReady(); // Error if the total number of blocks is exceeded - if (block >= blocks) { + if (block >= GetBlockCount()) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } @@ -638,7 +510,7 @@ int Disk::WriteCheck(uint64_t block) CheckReady(); // Error if the total number of blocks is exceeded - if (block >= blocks) { + if (block >= GetBlockCount()) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } @@ -651,7 +523,6 @@ int Disk::WriteCheck(uint64_t block) return 1 << size_shift_count; } -// TODO Write more than one block in a single call. Currently blocked by the track-oriented cache void Disk::Write(const vector&, const vector& buf, uint64_t block) { LOGTRACE("%s", __PRETTY_FUNCTION__) @@ -662,7 +533,7 @@ void Disk::Write(const vector&, const vector& buf, uint64_t block) } // Error if the total number of blocks is exceeded - if (block >= blocks) { + if (block >= GetBlockCount()) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); } @@ -706,14 +577,14 @@ void Disk::ReadCapacity10() { CheckReady(); - if (blocks == 0) { + if (GetBlockCount() == 0) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); } vector& buf = controller->GetBuffer(); // Create end of logical block address (blocks-1) - uint64_t capacity = blocks - 1; + uint64_t capacity = GetBlockCount() - 1; // If the capacity exceeds 32 bit, -1 must be returned and the client has to use READ CAPACITY(16) if (capacity > 4294967295) { @@ -734,14 +605,14 @@ void Disk::ReadCapacity16() { CheckReady(); - if (blocks == 0) { + if (GetBlockCount() == 0) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT); } vector& buf = controller->GetBuffer(); // Create end of logical block address (blocks-1) - SetInt64(buf, 0, blocks - 1); + SetInt64(buf, 0, GetBlockCount() - 1); // Create block length (1 << size) SetInt32(buf, 8, 1 << size_shift_count); @@ -775,26 +646,6 @@ void Disk::ReadCapacity16_ReadLong16() } } -//--------------------------------------------------------------------------- -// -// RESERVE/RELEASE(6/10) -// -// The reserve/release commands are only used in multi-initiator -// environments. RaSCSI doesn't support this use case. However, some old -// versions of Solaris will issue the reserve/release commands. We will -// just respond with an OK status. -// -//--------------------------------------------------------------------------- -void Disk::Reserve() -{ - EnterStatusPhase(); -} - -void Disk::Release() -{ - EnterStatusPhase(); -} - //--------------------------------------------------------------------------- // // Check/Get start sector and sector count for a READ/WRITE or READ/WRITE LONG operation @@ -805,8 +656,8 @@ void Disk::ValidateBlockAddress(access_mode mode) const { const uint64_t block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2); - if (block > blocks) { - LOGTRACE("%s", ("Capacity of " + to_string(blocks) + " block(s) exceeded: Trying to access block " + if (block > GetBlockCount()) { + LOGTRACE("%s", ("Capacity of " + to_string(GetBlockCount()) + " block(s) exceeded: Trying to access block " + to_string(block)).c_str()) throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); } @@ -839,7 +690,7 @@ bool Disk::CheckAndGetStartAndCount(uint64_t& start, uint32_t& count, access_mod LOGTRACE("%s READ/WRITE/VERIFY/SEEK command record=$%08X blocks=%d", __PRETTY_FUNCTION__, (uint32_t)start, count) // Check capacity - if (uint64_t capacity = blocks; start > capacity || start + count > capacity) { + if (uint64_t capacity = GetBlockCount(); !capacity || start > capacity || start + count > capacity) { LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block " + to_string(start) + ", block count " + to_string(count)).c_str()) throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE); @@ -853,6 +704,12 @@ bool Disk::CheckAndGetStartAndCount(uint64_t& start, uint32_t& count, access_mod return true; } +uint32_t Disk::CalculateShiftCount(uint32_t size_in_bytes) +{ + const auto& it = shift_counts.find(size_in_bytes); + return it != shift_counts.end() ? it->second : 0; +} + uint32_t Disk::GetSectorSizeInBytes() const { return size_shift_count ? 1 << size_shift_count : 0; @@ -861,32 +718,13 @@ uint32_t Disk::GetSectorSizeInBytes() const void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes) { DeviceFactory device_factory; - if (unordered_set sizes = device_factory.GetSectorSizes(GetType()); + if (const auto& sizes = device_factory.GetSectorSizes(GetType()); !sizes.empty() && sizes.find(size_in_bytes) == sizes.end()) { - throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes"); + throw io_exception("Invalid sector size of " + to_string(size_in_bytes) + " byte(s)"); } - switch (size_in_bytes) { - case 512: - size_shift_count = 9; - break; - - case 1024: - size_shift_count = 10; - break; - - case 2048: - size_shift_count = 11; - break; - - case 4096: - size_shift_count = 12; - break; - - default: - throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes"); - break; - } + size_shift_count = CalculateShiftCount(size_in_bytes); + assert(size_shift_count); } uint32_t Disk::GetConfiguredSectorSize() const @@ -905,43 +743,3 @@ bool Disk::SetConfiguredSectorSize(const DeviceFactory& device_factory, uint32_t return true; } - -void Disk::ReserveFile(const Filepath& path, int id, int lun) const -{ - reserved_files[path.GetPath()] = make_pair(id, lun); -} - -void Disk::UnreserveFile() const -{ - reserved_files.erase(diskpath.GetPath()); -} - -bool Disk::GetIdsForReservedFile(const Filepath& path, int& id, int& lun) -{ - if (const auto& it = reserved_files.find(path.GetPath()); it != reserved_files.end()) { - id = it->second.first; - lun = it->second.second; - - return true; - } - - return false; -} - -void Disk::UnreserveAll() -{ - reserved_files.clear(); -} - -bool Disk::FileExists(const Filepath& filepath) -{ - try { - // Disk::Open closes the file in case it exists - Open(filepath); - } - catch(const file_not_found_exception&) { - return false; - } - - return true; -} diff --git a/src/raspberrypi/devices/disk.h b/src/raspberrypi/devices/disk.h index 3fe55b0f..c03ee685 100644 --- a/src/raspberrypi/devices/disk.h +++ b/src/raspberrypi/devices/disk.h @@ -19,17 +19,15 @@ #include "device_factory.h" #include "disk_track.h" #include "disk_cache.h" -#include "filepath.h" #include "interfaces/scsi_block_commands.h" -#include "mode_page_device.h" +#include "storage_device.h" #include #include +#include using namespace std; -using id_set = pair; - -class Disk : public ModePageDevice, private ScsiBlockCommands +class Disk : public StorageDevice, private ScsiBlockCommands { enum access_mode { RW6, RW10, RW16, SEEK6, SEEK10 }; @@ -44,24 +42,13 @@ class Disk : public ModePageDevice, private ScsiBlockCommands // Sector size shift count (9=512, 10=1024, 11=2048, 12=4096) uint32_t size_shift_count = 0; - // Total number of sectors - uint64_t blocks = 0; - - bool is_medium_changed = false; - - Filepath diskpath; - - // The list of image files in use and the IDs and LUNs using these files - static unordered_map reserved_files; - public: - Disk(const string&, int); + Disk(PbDeviceType, int); ~Disk() override; bool Dispatch(scsi_command) override; - void MediumChanged(); bool Eject(bool) override; // Command helpers @@ -73,48 +60,30 @@ public: uint32_t GetSectorSizeInBytes() const; bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); } bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t); - uint64_t GetBlockCount() const { return blocks; } void FlushCache() override; - virtual void Open(const Filepath&); - void GetPath(Filepath& path) const { path = diskpath; } - - void ReserveFile(const Filepath&, int, int) const; - void UnreserveFile() const; - static void UnreserveAll(); - bool FileExists(const Filepath&); - - static unordered_map GetReservedFiles() { return reserved_files; } - static void SetReservedFiles(const unordered_map& files_in_use) { reserved_files = files_in_use; } - static bool GetIdsForReservedFile(const Filepath&, int&, int&); - private: - using super = ModePageDevice; + using super = StorageDevice; // Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm) void StartStopUnit(); - void SendDiagnostic(); void PreventAllowMediumRemoval(); void SynchronizeCache(); void ReadDefectData10(); - virtual void Read6(); - void Read10() override; - void Read16() override; - virtual void Write6(); - void Write10() override; - void Write16() override; - void Verify10(); - void Verify16(); + virtual void Read6() { Read(RW6); } + void Read10() override { Read(RW10); } + void Read16() override { Read(RW16); } + virtual void Write6() { Write(RW6); } + void Write10() override { Write(RW10); } + void Write16() override { Write(RW16); } + void Verify10() { Verify(RW10); } + void Verify16() { Verify(RW16); } void Seek(); void Seek10(); void ReadCapacity10() override; void ReadCapacity16() override; - void Reserve(); - void Release(); - void Rezero(); void FormatUnit() override; - void ReassignBlocks(); void Seek6(); void Read(access_mode); void Write(access_mode); @@ -129,23 +98,24 @@ private: int ModeSense6(const vector&, vector&) const override; int ModeSense10(const vector&, vector&) const override; + static const unordered_map shift_counts; + protected: - void SetUpCache(const Filepath&, off_t, bool = false); - void ResizeCache(const Filepath&, bool); + void SetUpCache(off_t, bool = false); + void ResizeCache(const string&, bool); void SetUpModePages(map>&, int, bool) const override; - virtual void AddErrorPage(map>&, bool) const; + void AddErrorPage(map>&, bool) const; virtual void AddFormatPage(map>&, bool) const; virtual void AddDrivePage(map>&, bool) const; void AddCachePage(map>&, bool) const; - virtual void AddVendorPage(map>&, int, bool) const; + unordered_set GetSectorSizes() const; void SetSectorSizes(const unordered_set& sizes) { sector_sizes = sizes; } void SetSectorSizeInBytes(uint32_t); uint32_t GetSectorSizeShiftCount() const { return size_shift_count; } void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; } uint32_t GetConfiguredSectorSize() const; - void SetBlockCount(uint64_t b) { blocks = b; } - void SetPath(const Filepath& path) { diskpath = path; } + static uint32_t CalculateShiftCount(uint32_t); }; diff --git a/src/raspberrypi/devices/host_services.cpp b/src/raspberrypi/devices/host_services.cpp index 95f12a41..9cdd8c84 100644 --- a/src/raspberrypi/devices/host_services.cpp +++ b/src/raspberrypi/devices/host_services.cpp @@ -32,13 +32,12 @@ using namespace scsi_defs; using namespace scsi_command_util; HostServices::HostServices(int lun, const ControllerManager& manager) - : ModePageDevice("SCHS", lun), controller_manager(manager) + : ModePageDevice(SCHS, lun), controller_manager(manager) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &HostServices::TestUnitReady); dispatcher.Add(scsi_command::eCmdStartStop, "StartStopUnit", &HostServices::StartStopUnit); SetReady(true); - SetReset(false); } bool HostServices::Dispatch(scsi_command cmd) @@ -96,18 +95,8 @@ int HostServices::ModeSense6(const vector& cdb, vector& buf) const const auto length = (int)min(buf.size(), (size_t)cdb[4]); fill_n(buf.begin(), length, 0); - // Basic Information - int size = 4; - - size += super::AddModePages(cdb, buf, size, length - size); - if (size > 255) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } - - // Do not return more than ALLOCATION LENGTH bytes - if (size > length) { - size = length; - } + // 4 bytes basic information + int size = AddModePages(cdb, buf, 4, length, 255); buf[0] = (BYTE)size; @@ -124,18 +113,8 @@ int HostServices::ModeSense10(const vector& cdb, vector& buf) const const auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7)); fill_n(buf.begin(), length, 0); - // Basic Information - int size = 8; - - size += super::AddModePages(cdb, buf, size, length - size); - if (size > 65535) { - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } - - // Do not return more than ALLOCATION LENGTH bytes - if (size > length) { - size = length; - } + // 8 bytes basic information + int size = AddModePages(cdb, buf, 8, length, 65535); SetInt16(buf, 0, size); @@ -151,6 +130,8 @@ void HostServices::SetUpModePages(map>& pages, int page, bool void HostServices::AddRealtimeClockPage(map>& pages, bool changeable) const { + vector buf(10); + if (!changeable) { time_t t = time(nullptr); tm localtime; @@ -167,8 +148,8 @@ void HostServices::AddRealtimeClockPage(map>& pages, bool chan // Ignore leap second for simplicity datetime.second = (uint8_t)(localtime.tm_sec < 60 ? localtime.tm_sec : 59); - vector buf(10); memcpy(&buf[2], &datetime, sizeof(datetime)); - pages[32] = buf; } + + pages[32] = buf; } diff --git a/src/raspberrypi/devices/host_services.h b/src/raspberrypi/devices/host_services.h index f34a04a9..55c756a5 100644 --- a/src/raspberrypi/devices/host_services.h +++ b/src/raspberrypi/devices/host_services.h @@ -30,8 +30,6 @@ public: vector InquiryInternal() const override; void TestUnitReady() override; - bool SupportsFile() const override { return false; } - protected: void SetUpModePages(map>&, int, bool) const override; diff --git a/src/raspberrypi/devices/interfaces/scsi_mmc_commands.h b/src/raspberrypi/devices/interfaces/scsi_mmc_commands.h index a524b0df..d8f161e3 100644 --- a/src/raspberrypi/devices/interfaces/scsi_mmc_commands.h +++ b/src/raspberrypi/devices/interfaces/scsi_mmc_commands.h @@ -20,5 +20,4 @@ public: virtual ~ScsiMmcCommands() = default; virtual void ReadToc() = 0; - virtual void GetEventStatusNotification() = 0; }; diff --git a/src/raspberrypi/devices/mode_page_device.cpp b/src/raspberrypi/devices/mode_page_device.cpp index 0bcf0636..08f58bc6 100644 --- a/src/raspberrypi/devices/mode_page_device.cpp +++ b/src/raspberrypi/devices/mode_page_device.cpp @@ -20,7 +20,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -ModePageDevice::ModePageDevice(const string& type, int lun) : PrimaryDevice(type, lun) +ModePageDevice::ModePageDevice(PbDeviceType type, int lun) : PrimaryDevice(type, lun) { dispatcher.Add(scsi_command::eCmdModeSense6, "ModeSense6", &ModePageDevice::ModeSense6); dispatcher.Add(scsi_command::eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10); @@ -34,10 +34,11 @@ bool ModePageDevice::Dispatch(scsi_command cmd) return dispatcher.Dispatch(this, cmd) ? true : super::Dispatch(cmd); } -int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int offset, int max_length) const +int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int offset, int length, int max_size) const { + int max_length = length - offset; if (max_length < 0) { - return 0; + return length; } const bool changeable = (cdb[2] & 0xc0) == 0x40; @@ -87,11 +88,15 @@ int ModePageDevice::AddModePages(const vector& cdb, vector& buf, int result[off + 1] = (byte)(page0.size() - 2); } - // Do not return more than the requested number of bytes - size_t size = min((size_t)max_length, result.size()); + if ((int)result.size() > max_size) { + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + } + + auto size = (int)min((size_t)max_length, result.size()); memcpy(&buf.data()[offset], result.data(), size); - return (int)size; + // Do not return more than the requested number of bytes + return size + offset < length ? size + offset : length; } void ModePageDevice::ModeSense6() @@ -108,46 +113,32 @@ void ModePageDevice::ModeSense10() EnterDataInPhase(); } -void ModePageDevice::ModeSelect(const vector&, const vector&, int) const +void ModePageDevice::ModeSelect(scsi_command, const vector&, const vector&, int) const { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); } void ModePageDevice::ModeSelect6() { - ctrl->length = ModeSelectCheck6(); + ctrl->length = SaveParametersCheck(ctrl->cmd[4]); EnterDataOutPhase(); } void ModePageDevice::ModeSelect10() { - ctrl->length = ModeSelectCheck10(); + const size_t length = min(controller->GetBuffer().size(), (size_t)GetInt16(ctrl->cmd, 7)); + + ctrl->length = SaveParametersCheck((int)length); EnterDataOutPhase(); } -int ModePageDevice::ModeSelectCheck(int length) const +int ModePageDevice::SaveParametersCheck(int length) const { - // Error if save parameters are set for other types than SCHD, SCRM or SCMO - // TODO The assumption above is not correct, and this code should be located elsewhere - if (GetType() != "SCHD" && GetType() != "SCRM" && GetType() != "SCMO" && (ctrl->cmd[1] & 0x01)) { + if (!SupportsSaveParameters() && (ctrl->cmd[1] & 0x01)) { throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } return length; } - -int ModePageDevice::ModeSelectCheck6() const -{ - // Receive the data specified by the parameter length - return ModeSelectCheck(ctrl->cmd[4]); -} - -int ModePageDevice::ModeSelectCheck10() const -{ - // Receive the data specified by the parameter length - size_t length = min(controller->GetBuffer().size(), (size_t)GetInt16(ctrl->cmd, 7)); - - return ModeSelectCheck((int)length); -} diff --git a/src/raspberrypi/devices/mode_page_device.h b/src/raspberrypi/devices/mode_page_device.h index c1c1d4fe..454f4d60 100644 --- a/src/raspberrypi/devices/mode_page_device.h +++ b/src/raspberrypi/devices/mode_page_device.h @@ -18,17 +18,22 @@ class ModePageDevice: public PrimaryDevice { public: - ModePageDevice(const string&, int); - ~ModePageDevice()override = default; + ModePageDevice(PbDeviceType, int); + ~ModePageDevice() override = default; bool Dispatch(scsi_command) override; - virtual void ModeSelect(const vector&, const vector&, int) const; + virtual void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const; protected: - int AddModePages(const vector&, vector&, int, int) const; + bool SupportsSaveParameters() const { return supports_save_parameters; } + void SupportsSaveParameters(bool b) { supports_save_parameters = b; } + int AddModePages(const vector&, vector&, int, int, int) const; virtual void SetUpModePages(map>&, int, bool) const = 0; + virtual void AddVendorPage(map>&, int, bool) const { + // Nothing to add by default + } private: @@ -36,6 +41,8 @@ private: Dispatcher dispatcher; + bool supports_save_parameters = false; + virtual int ModeSense6(const vector&, vector&) const = 0; virtual int ModeSense10(const vector&, vector&) const = 0; @@ -44,7 +51,5 @@ private: void ModeSelect6(); void ModeSelect10(); - int ModeSelectCheck(int) const; - int ModeSelectCheck6() const; - int ModeSelectCheck10() const; + int SaveParametersCheck(int) const; }; diff --git a/src/raspberrypi/devices/primary_device.cpp b/src/raspberrypi/devices/primary_device.cpp index 0ecbf95a..d7c03366 100644 --- a/src/raspberrypi/devices/primary_device.cpp +++ b/src/raspberrypi/devices/primary_device.cpp @@ -17,15 +17,18 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -PrimaryDevice::PrimaryDevice(const string& type, int lun) : Device(type, lun) +PrimaryDevice::PrimaryDevice(PbDeviceType type, int lun) : Device(type, lun) { // Mandatory SCSI primary commands dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady); dispatcher.Add(scsi_command::eCmdInquiry, "Inquiry", &PrimaryDevice::Inquiry); dispatcher.Add(scsi_command::eCmdReportLuns, "ReportLuns", &PrimaryDevice::ReportLuns); - // Optional commands used by all RaSCSI devices + // Optional commands supported by all RaSCSI devices dispatcher.Add(scsi_command::eCmdRequestSense, "RequestSense", &PrimaryDevice::RequestSense); + dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &PrimaryDevice::ReserveUnit); + dispatcher.Add(scsi_command::eCmdRelease6, "ReleaseUnit", &PrimaryDevice::ReleaseUnit); + dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &PrimaryDevice::SendDiagnostic); } bool PrimaryDevice::Dispatch(scsi_command cmd) @@ -33,6 +36,13 @@ bool PrimaryDevice::Dispatch(scsi_command cmd) return dispatcher.Dispatch(this, cmd); } +void PrimaryDevice::Reset() +{ + DiscardReservation(); + + Device::Reset(); +} + int PrimaryDevice::GetId() const { if (controller == nullptr) { @@ -137,6 +147,21 @@ void PrimaryDevice::RequestSense() EnterDataInPhase(); } +void PrimaryDevice::SendDiagnostic() +{ + // Do not support PF bit + if (ctrl->cmd[1] & 0x10) { + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + } + + // Do not support parameter list + if ((ctrl->cmd[3] != 0) || (ctrl->cmd[4] != 0)) { + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + } + + EnterStatusPhase(); +} + void PrimaryDevice::CheckReady() { // Not ready if reset @@ -216,3 +241,61 @@ bool PrimaryDevice::WriteByteSequence(vector&, uint32_t) return false; } + +void PrimaryDevice::ReserveUnit() +{ + reserving_initiator = controller->GetInitiatorId(); + + if (reserving_initiator != -1) { + LOGTRACE("Reserved device ID %d, LUN %d for initiator ID %d", GetId(), GetLun(), reserving_initiator) + } + else { + LOGTRACE("Reserved device ID %d, LUN %d for unknown initiator", GetId(), GetLun()) + } + + EnterStatusPhase(); +} + +void PrimaryDevice::ReleaseUnit() +{ + if (reserving_initiator != -1) { + LOGTRACE("Released device ID %d, LUN %d reserved by initiator ID %d", GetId(), GetLun(), reserving_initiator) + } + else { + LOGTRACE("Released device ID %d, LUN %d reserved by unknown initiator", GetId(), GetLun()) + } + + DiscardReservation(); + + EnterStatusPhase(); +} + +bool PrimaryDevice::CheckReservation(int initiator_id, scsi_command cmd, bool prevent_removal) const +{ + if (reserving_initiator == NOT_RESERVED || reserving_initiator == initiator_id) { + return true; + } + + // A reservation is valid for all commands except those excluded below + if (cmd == scsi_command::eCmdInquiry || cmd == scsi_command::eCmdRequestSense || cmd == scsi_command::eCmdRelease6) { + return true; + } + // PREVENT ALLOW MEDIUM REMOVAL is permitted if the prevent bit is 0 + if (cmd == scsi_command::eCmdRemoval && !prevent_removal) { + return true; + } + + if (initiator_id != -1) { + LOGTRACE("Initiator ID %d tries to access reserved device ID %d, LUN %d", initiator_id, GetId(), GetLun()) + } + else { + LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun()) + } + + return false; +} + +void PrimaryDevice::DiscardReservation() +{ + reserving_initiator = NOT_RESERVED; +} diff --git a/src/raspberrypi/devices/primary_device.h b/src/raspberrypi/devices/primary_device.h index 574c9046..cbb84ae1 100644 --- a/src/raspberrypi/devices/primary_device.h +++ b/src/raspberrypi/devices/primary_device.h @@ -21,7 +21,7 @@ class PrimaryDevice: private ScsiPrimaryCommands, public Device { public: - PrimaryDevice(const string&, int); + PrimaryDevice(PbDeviceType, int); ~PrimaryDevice() override = default; virtual bool Dispatch(scsi_command); @@ -30,10 +30,15 @@ public: void SetController(AbstractController *); virtual bool WriteByteSequence(vector&, uint32_t); - virtual int GetSendDelay() const { return BUS::SEND_NO_DELAY; } - // Override for device specific initializations, to be called after all device properties have been set - virtual bool Init(const unordered_map&) { return true; }; + int GetSendDelay() const { return send_delay; } + + bool CheckReservation(int, scsi_command, bool) const; + void DiscardReservation(); + + // Override for device specific initializations + virtual bool Init(const unordered_map&) { return false; }; + void Reset() override; virtual void FlushCache() { // Devices with a cache have to implement this method @@ -45,6 +50,12 @@ protected: virtual vector InquiryInternal() const = 0; void CheckReady(); + void SetSendDelay(int s) { send_delay = s; } + + virtual void SendDiagnostic(); + virtual void ReserveUnit(); + virtual void ReleaseUnit(); + void EnterStatusPhase() { controller->Status(); } void EnterDataInPhase() { controller->DataIn(); } void EnterDataOutPhase() { controller->DataOut(); } @@ -54,6 +65,8 @@ protected: private: + static const int NOT_RESERVED = -2; + void TestUnitReady() override; void RequestSense() override; void ReportLuns() override; @@ -62,4 +75,8 @@ private: vector HandleRequestSense() const; Dispatcher dispatcher; + + int send_delay = BUS::SEND_NO_DELAY; + + int reserving_initiator = NOT_RESERVED; }; diff --git a/src/raspberrypi/devices/scsi_command_util.cpp b/src/raspberrypi/devices/scsi_command_util.cpp index 4f1788a1..5e09617e 100644 --- a/src/raspberrypi/devices/scsi_command_util.cpp +++ b/src/raspberrypi/devices/scsi_command_util.cpp @@ -13,8 +13,10 @@ using namespace scsi_defs; -void scsi_command_util::ModeSelect(const vector& cdb, const vector& buf, int length, int sector_size) +void scsi_command_util::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length, + int sector_size) { + assert(cmd == scsi_command::eCmdModeSelect6 || cmd == scsi_command::eCmdModeSelect10); assert(length >= 0); // PF @@ -25,7 +27,7 @@ void scsi_command_util::ModeSelect(const vector& cdb, const vector& b // Skip block descriptors int offset; - if ((scsi_command)cdb[0] == scsi_command::eCmdModeSelect10) { + if (cmd == scsi_command::eCmdModeSelect10) { offset = 8 + GetInt16(buf, 6); } else { diff --git a/src/raspberrypi/devices/scsi_command_util.h b/src/raspberrypi/devices/scsi_command_util.h index 5f95a8d2..2d44230f 100644 --- a/src/raspberrypi/devices/scsi_command_util.h +++ b/src/raspberrypi/devices/scsi_command_util.h @@ -11,6 +11,7 @@ #pragma once +#include "scsi.h" #include #include @@ -18,7 +19,7 @@ using namespace std; namespace scsi_command_util { - void ModeSelect(const vector&, const vector&, int, int); + void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int, int); void EnrichFormatPage(map>&, bool, int); void AddAppleVendorModePage(map>&, bool); diff --git a/src/raspberrypi/devices/scsi_daynaport.cpp b/src/raspberrypi/devices/scsi_daynaport.cpp index 8b3eed54..3f06465f 100644 --- a/src/raspberrypi/devices/scsi_daynaport.cpp +++ b/src/raspberrypi/devices/scsi_daynaport.cpp @@ -33,7 +33,7 @@ using namespace scsi_defs; using namespace scsi_command_util; // TODO Disk must not be the superclass -SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun) +SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk(SCDP, lun) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady); dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6); @@ -42,6 +42,16 @@ SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun) dispatcher.Add(scsi_command::eCmdSetIfaceMode, "SetIfaceMode", &SCSIDaynaPort::SetInterfaceMode); dispatcher.Add(scsi_command::eCmdSetMcastAddr, "SetMcastAddr", &SCSIDaynaPort::SetMcastAddr); dispatcher.Add(scsi_command::eCmdEnableInterface, "EnableInterface", &SCSIDaynaPort::EnableInterface); + + // The Daynaport needs to have a delay after the size/flags field of the read response. + // In the MacOS driver, it looks like the driver is doing two "READ" system calls. + SetSendDelay(DAYNAPORT_READ_HEADER_SZ); + + SupportsParams(true); + // TODO Remove as soon as SCDP is not a subclass of Disk anymore + SetStoppable(false); + // TODO Remove as soon as SCDP is not a subclass of Disk anymore + SupportsFile(false); } bool SCSIDaynaPort::Dispatch(scsi_command cmd) @@ -79,8 +89,10 @@ bool SCSIDaynaPort::Init(const unordered_map& params) return true; } -void SCSIDaynaPort::Open(const Filepath& path) +void SCSIDaynaPort::Open() { + Filepath path; + path.SetPath(GetFilename().c_str()); m_tap.OpenDump(path); } @@ -433,12 +445,12 @@ void SCSIDaynaPort::SetInterfaceMode() case CMD_SCSILINK_ENABLE: case CMD_SCSILINK_SET: LOGWARN("%s Unsupported SetInterface command received: %02X", __PRETTY_FUNCTION__, ctrl->cmd[5]) - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); break; default: LOGWARN("%s Unknown SetInterface command received: %02X", __PRETTY_FUNCTION__, ctrl->cmd[5]) - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE); break; } } @@ -449,7 +461,7 @@ void SCSIDaynaPort::SetMcastAddr() if (ctrl->length == 0) { LOGWARN("%s Not supported SetMcastAddr Command %02X", __PRETTY_FUNCTION__, ctrl->cmd[2]) - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } EnterDataOutPhase(); @@ -473,7 +485,7 @@ void SCSIDaynaPort::EnableInterface() if (!m_tap.Enable()) { LOGWARN("Unable to enable the DaynaPort Interface") - throw scsi_exception(); + throw scsi_exception(sense_key::ABORTED_COMMAND); } m_tap.Flush(); @@ -484,7 +496,7 @@ void SCSIDaynaPort::EnableInterface() if (!m_tap.Disable()) { LOGWARN("Unable to disable the DaynaPort Interface") - throw scsi_exception(); + throw scsi_exception(sense_key::ABORTED_COMMAND); } LOGINFO("The DaynaPort interface has been DISABLED") @@ -492,11 +504,3 @@ void SCSIDaynaPort::EnableInterface() EnterStatusPhase(); } - -int SCSIDaynaPort::GetSendDelay() const -{ - // The Daynaport needs to have a delay after the size/flags field - // of the read response. In the MacOS driver, it looks like the - // driver is doing two "READ" system calls. - return DAYNAPORT_READ_HEADER_SZ; -} diff --git a/src/raspberrypi/devices/scsi_daynaport.h b/src/raspberrypi/devices/scsi_daynaport.h index 9ca3a9af..11157a42 100644 --- a/src/raspberrypi/devices/scsi_daynaport.h +++ b/src/raspberrypi/devices/scsi_daynaport.h @@ -49,7 +49,7 @@ public: ~SCSIDaynaPort() override = default; bool Init(const unordered_map&) override; - void Open(const Filepath& path) override; + void Open() override; // Commands vector InquiryInternal() const override; @@ -66,7 +66,6 @@ public: void SetInterfaceMode(); void SetMcastAddr(); void EnableInterface(); - int GetSendDelay() const override; bool Dispatch(scsi_command) override; diff --git a/src/raspberrypi/devices/scsi_host_bridge.cpp b/src/raspberrypi/devices/scsi_host_bridge.cpp index 6d5b4e16..3a1e7e88 100644 --- a/src/raspberrypi/devices/scsi_host_bridge.cpp +++ b/src/raspberrypi/devices/scsi_host_bridge.cpp @@ -27,7 +27,7 @@ using namespace std; using namespace scsi_defs; using namespace scsi_command_util; -SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun) +SCSIBR::SCSIBR(int lun) : Disk(SCBR, lun) { // Create host file system fs.Reset(); @@ -35,6 +35,12 @@ SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun) dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIBR::TestUnitReady); dispatcher.Add(scsi_command::eCmdRead6, "GetMessage10", &SCSIBR::GetMessage10); dispatcher.Add(scsi_command::eCmdWrite6, "SendMessage10", &SCSIBR::SendMessage10); + + SupportsParams(true); + // TODO Remove as soon as SCBR is not a subclass of Disk anymore + SetStoppable(false); + // TODO Remove as soon as SCBR is not a subclass of Disk anymore + SupportsFile(false); } bool SCSIBR::Init(const unordered_map& params) @@ -257,7 +263,7 @@ void SCSIBR::GetMessage10() ctrl->length = GetMessage10(ctrl->cmd, controller->GetBuffer()); if (ctrl->length <= 0) { - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Set next block @@ -278,7 +284,7 @@ void SCSIBR::SendMessage10() { ctrl->length = GetInt24(ctrl->cmd, 6); if (ctrl->length <= 0) { - throw scsi_exception(); + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } // Ensure a sufficient buffer size (because it is not a transfer for each block) diff --git a/src/raspberrypi/devices/scsi_host_bridge.h b/src/raspberrypi/devices/scsi_host_bridge.h index 9505acb1..8bbcda7d 100644 --- a/src/raspberrypi/devices/scsi_host_bridge.h +++ b/src/raspberrypi/devices/scsi_host_bridge.h @@ -38,6 +38,9 @@ public: bool Init(const unordered_map&) override; bool Dispatch(scsi_command) override; + // TODO Remove as soon as SCSIBR is not a subclass of Disk anymore + void Open() override { super::ValidateFile(GetFilename()); } + // Commands vector InquiryInternal() const override; int GetMessage10(const vector&, vector&); diff --git a/src/raspberrypi/devices/scsi_printer.cpp b/src/raspberrypi/devices/scsi_printer.cpp index a16820bd..dad93733 100644 --- a/src/raspberrypi/devices/scsi_printer.cpp +++ b/src/raspberrypi/devices/scsi_printer.cpp @@ -12,18 +12,10 @@ // // How to print: // -// 1. The client reserves the printer device with RESERVE UNIT (optional step, mandatory for -// a multi-initiator environment). -// 2. The client sends the data to be printed with one or several PRINT commands. Due to -// https://github.com/akuker/RASCSI/issues/669 the maximum transfer size per PRINT command is -// limited to 4096 bytes. -// 3. The client triggers printing with SYNCHRONIZE BUFFER. Each SYNCHRONIZE BUFFER results in +// 1. The client sends the data to be printed with one or several PRINT commands. The maximum +// transfer size per PRINT command is currently limited to 4096 bytes. +// 2. The client triggers printing with SYNCHRONIZE BUFFER. Each SYNCHRONIZE BUFFER results in // the print command for this printer (see below) to be called for the data not yet printed. -// 4. The client releases the printer with RELEASE UNIT (optional step, mandatory for a -// multi-initiator environment). -// -// A client usually does not know whether it is running in a multi-initiator environment. This is why -// always using a reservation is recommended. // // The command to be used for printing can be set with the "cmd" property when attaching the device. // By default the data to be printed are sent to the printer unmodified, using "lp -oraw %f". This @@ -32,37 +24,34 @@ // applies any conversions on the file to be printed (%f) before passing it to the printing service. // 'enscript' is an example for a conversion tool. // By attaching different devices/LUNs multiple printers (i.e. different print commands) are possible. -// Note that the print command is not executed by root but with the permissions of the lp user. // // With STOP PRINT printing can be cancelled before SYNCHRONIZE BUFFER was sent. // -// SEND DIAGNOSTIC currently returns no data. -// #include #include "rascsi_exceptions.h" #include "scsi_command_util.h" -#include "../rasutil.h" #include "dispatcher.h" #include "scsi_printer.h" using namespace std; using namespace scsi_defs; -using namespace ras_util; using namespace scsi_command_util; -SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice("SCLP", lun) +SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice(SCLP, lun) { dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady); + dispatcher.Add(scsi_command::eCmdPrint, "Print", &SCSIPrinter::Print); + dispatcher.Add(scsi_command::eCmdSynchronizeBuffer, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer); + dispatcher.Add(scsi_command::eCmdStopPrint, "StopPrint", &SCSIPrinter::StopPrint); + + // Required also in this class in order to fulfill the ScsiPrinterCommands interface contract dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit); dispatcher.Add(scsi_command::eCmdRelease6, "ReleaseUnit", &SCSIPrinter::ReleaseUnit); - dispatcher.Add(scsi_command::eCmdWrite6, "Print", &SCSIPrinter::Print); - dispatcher.Add(scsi_command::eCmdSynchronizeBuffer, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer); dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &SCSIPrinter::SendDiagnostic); - dispatcher.Add(scsi_command::eCmdStartStop, "StopPrint", &SCSIPrinter::StopPrint); + SupportsParams(true); SetReady(true); - SetReset(false); } SCSIPrinter::~SCSIPrinter() @@ -79,11 +68,6 @@ bool SCSIPrinter::Init(const unordered_map& params) return false; } - if (!GetAsInt(GetParam("timeout"), timeout) || timeout <= 0) { - LOGERROR("Reservation timeout value must be > 0") - return false; - } - return true; } @@ -95,8 +79,7 @@ bool SCSIPrinter::Dispatch(scsi_command cmd) void SCSIPrinter::TestUnitReady() { - CheckReservation(); - + // The printer is always ready EnterStatusPhase(); } @@ -105,49 +88,8 @@ vector SCSIPrinter::InquiryInternal() const return HandleInquiry(device_type::PRINTER, scsi_level::SCSI_2, false); } -void SCSIPrinter::ReserveUnit() -{ - // The printer is released after a configurable time in order to prevent deadlocks caused by broken clients - if (reservation_time + timeout < time(nullptr)) { - DiscardReservation(); - } - - CheckReservation(); - - reserving_initiator = controller->GetInitiatorId(); - - if (reserving_initiator != -1) { - LOGTRACE("Reserved device ID %d, LUN %d for initiator ID %d", GetId(), GetLun(), reserving_initiator) - } - else { - LOGTRACE("Reserved device ID %d, LUN %d for unknown initiator", GetId(), GetLun()) - } - - Cleanup(); - - EnterStatusPhase(); -} - -void SCSIPrinter::ReleaseUnit() -{ - CheckReservation(); - - if (reserving_initiator != -1) { - LOGTRACE("Released device ID %d, LUN %d reserved by initiator ID %d", GetId(), GetLun(), reserving_initiator) - } - else { - LOGTRACE("Released device ID %d, LUN %d reserved by unknown initiator", GetId(), GetLun()) - } - - DiscardReservation(); - - EnterStatusPhase(); -} - void SCSIPrinter::Print() { - CheckReservation(); - const uint32_t length = GetInt24(ctrl->cmd, 2); LOGTRACE("Receiving %d byte(s) to be printed", length) @@ -167,14 +109,11 @@ void SCSIPrinter::Print() void SCSIPrinter::SynchronizeBuffer() { - CheckReservation(); - if (fd == -1) { - throw scsi_exception(); - } + LOGWARN("Missing printer output file") - // Make the file readable for the lp user - fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); //NOSONAR Granting permissions to "others" is required here + throw scsi_exception(sense_key::ABORTED_COMMAND); + } struct stat st; fstat(fd, &st); @@ -186,7 +125,6 @@ void SCSIPrinter::SynchronizeBuffer() const size_t file_position = cmd.find("%f"); assert(file_position != string::npos); cmd.replace(file_position, 2, filename); - cmd = "sudo -u lp " + cmd; LOGTRACE("%s", string("Printing file with size of " + to_string(st.st_size) +" byte(s)").c_str()) @@ -197,7 +135,7 @@ void SCSIPrinter::SynchronizeBuffer() unlink(filename); - throw scsi_exception(); + throw scsi_exception(sense_key::ABORTED_COMMAND); } unlink(filename); @@ -205,15 +143,9 @@ void SCSIPrinter::SynchronizeBuffer() EnterStatusPhase(); } -void SCSIPrinter::SendDiagnostic() -{ - // Both command implemntations are identical - TestUnitReady(); -} - void SCSIPrinter::StopPrint() { - // Both command implemntations are identical + // Command implementations are identical TestUnitReady(); } @@ -232,34 +164,7 @@ bool SCSIPrinter::WriteByteSequence(vector& buf, uint32_t length) LOGTRACE("Appending %d byte(s) to printer output file '%s'", length, filename) - const auto num_written = (uint32_t)write(fd, buf.data(), length); - - return num_written == length; -} - -void SCSIPrinter::CheckReservation() -{ - if (reserving_initiator == NOT_RESERVED || reserving_initiator == controller->GetInitiatorId()) { - reservation_time = time(nullptr); - return; - } - - if (controller->GetInitiatorId() != -1) { - LOGTRACE("Initiator ID %d tries to access reserved device ID %d, LUN %d", controller->GetInitiatorId(), GetId(), GetLun()) - } - else { - LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun()) - } - - throw scsi_exception(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, - status::RESERVATION_CONFLICT); -} - -void SCSIPrinter::DiscardReservation() -{ - Cleanup(); - - reserving_initiator = NOT_RESERVED; + return (uint32_t)write(fd, buf.data(), length) == length; } void SCSIPrinter::Cleanup() diff --git a/src/raspberrypi/devices/scsi_printer.h b/src/raspberrypi/devices/scsi_printer.h index e24d09e5..1972df7d 100644 --- a/src/raspberrypi/devices/scsi_printer.h +++ b/src/raspberrypi/devices/scsi_printer.h @@ -30,20 +30,18 @@ public: bool Dispatch(scsi_command) override; bool Init(const unordered_map&) override; + void Cleanup(); vector InquiryInternal() const override; void TestUnitReady() override; - void ReserveUnit() override; - void ReleaseUnit() override; + void ReserveUnit() override { PrimaryDevice::ReserveUnit(); } + void ReleaseUnit() override { PrimaryDevice::ReleaseUnit(); } + void SendDiagnostic() override { PrimaryDevice::SendDiagnostic(); } void Print() override; void SynchronizeBuffer(); - void SendDiagnostic() override; void StopPrint(); bool WriteByteSequence(vector&, uint32_t) override; - void CheckReservation(); - void DiscardReservation(); - void Cleanup(); private: @@ -53,9 +51,4 @@ private: char filename[TMP_FILENAME_LENGTH + 1]; //NOSONAR mkstemp() requires a modifiable string int fd = -1; - - int reserving_initiator = NOT_RESERVED; - - time_t reservation_time = 0; - int timeout = 0; }; diff --git a/src/raspberrypi/devices/scsicd.cpp b/src/raspberrypi/devices/scsicd.cpp index c922eb9f..aa793a18 100644 --- a/src/raspberrypi/devices/scsicd.cpp +++ b/src/raspberrypi/devices/scsicd.cpp @@ -24,12 +24,15 @@ using namespace scsi_defs; using namespace scsi_command_util; -SCSICD::SCSICD(int lun, const unordered_set& sector_sizes) : Disk("SCCD", lun) +SCSICD::SCSICD(int lun, const unordered_set& sector_sizes) : Disk(SCCD, lun) { SetSectorSizes(sector_sizes); dispatcher.Add(scsi_command::eCmdReadToc, "ReadToc", &SCSICD::ReadToc); - dispatcher.Add(scsi_command::eCmdGetEventStatusNotification, "GetEventStatusNotification", &SCSICD::GetEventStatusNotification); + + SetReadOnly(true); + SetRemovable(true); + SetLockable(true); } bool SCSICD::Dispatch(scsi_command cmd) @@ -38,7 +41,7 @@ bool SCSICD::Dispatch(scsi_command cmd) return dispatcher.Dispatch(this, cmd) ? true : super::Dispatch(cmd); } -void SCSICD::Open(const Filepath& path) +void SCSICD::Open() { assert(!IsReady()); @@ -48,9 +51,11 @@ void SCSICD::Open(const Filepath& path) ClearTrack(); // Open as read-only + Filepath path; + path.SetPath(GetFilename().c_str()); Fileio fio; if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { - throw file_not_found_exception("Can't open CD-ROM file"); + throw file_not_found_exception("Can't open CD-ROM file '" + GetFilename() + "'"); } // Default sector size is 2048 bytes @@ -62,10 +67,9 @@ void SCSICD::Open(const Filepath& path) fio.Close(); // Open physical CD - OpenPhysical(path); + OpenPhysical(); } else { - // Get file size - if (fio.GetFileSize() < 4) { + if (GetFileSize() < 4) { fio.Close(); throw io_exception("CD-ROM file size must be at least 4 bytes"); } @@ -78,21 +82,21 @@ void SCSICD::Open(const Filepath& path) // If it starts with FILE, consider it as a CUE sheet if (!strcasecmp(file.data(), "FILE")) { - // Open as CUE - OpenCue(path); + throw io_exception("Opening CUE CD-ROM files is not supported"); } else { - // Open as ISO - OpenIso(path); + OpenIso(); } } // Successful opening assert(GetBlockCount() > 0); - super::Open(path); - SetPath(path); + super::ValidateFile(GetFilename()); - SetUpCache(path, 0, rawfile); + SetUpCache(0, rawfile); + + SetReadOnly(true); + SetProtectable(false); // Attention if ready if (IsReady()) { @@ -100,21 +104,16 @@ void SCSICD::Open(const Filepath& path) } } -void SCSICD::OpenCue(const Filepath&) const -{ - throw io_exception("Opening CUE CD-ROM files is not supported"); -} - -void SCSICD::OpenIso(const Filepath& path) +void SCSICD::OpenIso() { // Open as read-only Fileio fio; - if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { + if (!fio.Open(GetFilename().c_str(), Fileio::OpenMode::ReadOnly)) { throw io_exception("Can't open ISO CD-ROM file"); } // Get file size - const off_t size = fio.GetFileSize(); + const off_t size = GetFileSize(); if (size < 0x800) { fio.Close(); throw io_exception("ISO CD-ROM file size must be at least 2048 bytes"); @@ -166,43 +165,41 @@ void SCSICD::OpenIso(const Filepath& path) SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount())); } - // Create only one data track - assert(!tracks.size()); - auto track = make_unique(); - track->Init(1, 0, (int)GetBlockCount() - 1); - track->SetPath(false, path); - tracks.push_back(move(track)); - dataindex = 0; + CreateDataTrack(); } -void SCSICD::OpenPhysical(const Filepath& path) +void SCSICD::OpenPhysical() { // Open as read-only Fileio fio; - if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { - throw io_exception("Can't open CD-ROM file"); + if (!fio.Open(GetFilename().c_str(), Fileio::OpenMode::ReadOnly)) { + throw file_not_found_exception("Can't open CD-ROM file '" + GetFilename() + "'"); } + fio.Close(); // Get size - off_t size = fio.GetFileSize(); + off_t size = GetFileSize(); if (size < 0x800) { - fio.Close(); throw io_exception("CD-ROM file size must be at least 2048 bytes"); } - // Close - fio.Close(); - // Effective size must be a multiple of 512 size = (size / 512) * 512; // Set the number of blocks SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount())); + CreateDataTrack(); +} + +void SCSICD::CreateDataTrack() +{ // Create only one data track assert(!tracks.size()); auto track = make_unique(); track->Init(1, 0, (int)GetBlockCount() - 1); + Filepath path; + path.SetPath(GetFilename().c_str()); track->SetPath(false, path); tracks.push_back(move(track)); dataindex = 0; @@ -295,7 +292,7 @@ int SCSICD::Read(const vector& cdb, vector& buf, uint64_t block) tracks[index]->GetPath(path); // Re-assign disk cache (no need to save) - ResizeCache(path, rawfile); + ResizeCache(path.GetPath(), rawfile); // Reset data index dataindex = index; @@ -341,24 +338,24 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) // AA if not found or internal error if (!tracks[index]) { - if (cdb[6] == 0xaa) { - // Returns the final LBA+1 because it is AA - buf[0] = 0x00; - buf[1] = 0x0a; - buf[2] = (BYTE)tracks[0]->GetTrackNo(); - buf[3] = (BYTE)last; - buf[6] = 0xaa; - uint32_t lba = tracks[tracks.size() - 1]->GetLast() + 1; - if (msf) { - LBAtoMSF(lba, &buf[8]); - } else { - SetInt16(buf, 10, lba); - } - return length; + if (cdb[6] != 0xaa) { + throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); } - // Otherwise, error - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); + // Returns the final LBA+1 because it is AA + buf[0] = 0x00; + buf[1] = 0x0a; + buf[2] = (BYTE)tracks[0]->GetTrackNo(); + buf[3] = (BYTE)last; + buf[6] = 0xaa; + const uint32_t lba = tracks[tracks.size() - 1]->GetLast() + 1; + if (msf) { + LBAtoMSF(lba, &buf[8]); + } else { + SetInt16(buf, 10, lba); + } + + return length; } } @@ -403,17 +400,6 @@ int SCSICD::ReadTocInternal(const vector& cdb, vector& buf) return length; } -void SCSICD::GetEventStatusNotification() -{ - if (!(ctrl->cmd[1] & 0x01)) { - // Asynchronous notification is optional and not supported by rascsi - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); - } - - LOGTRACE("Received request for event polling, which is currently not supported") - throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB); -} - //--------------------------------------------------------------------------- // // LBA→MSF Conversion diff --git a/src/raspberrypi/devices/scsicd.h b/src/raspberrypi/devices/scsicd.h index aec65323..4aa92a6e 100644 --- a/src/raspberrypi/devices/scsicd.h +++ b/src/raspberrypi/devices/scsicd.h @@ -14,7 +14,6 @@ #pragma once -#include "filepath.h" #include "cd_track.h" #include "disk.h" #include "interfaces/scsi_mmc_commands.h" @@ -28,7 +27,7 @@ public: bool Dispatch(scsi_command) override; - void Open(const Filepath& path) override; + void Open() override; // Commands vector InquiryInternal() const override; @@ -50,13 +49,12 @@ private: void AddCDROMPage(map>&, bool) const; void AddCDDAPage(map>&, bool) const; - // Open - void OpenCue(const Filepath& path) const; - void OpenIso(const Filepath& path); - void OpenPhysical(const Filepath& path); + void OpenIso(); + void OpenPhysical(); + + void CreateDataTrack(); void ReadToc() override; - void GetEventStatusNotification() override; void LBAtoMSF(uint32_t, BYTE *) const; // LBA→MSF conversion diff --git a/src/raspberrypi/devices/scsihd.cpp b/src/raspberrypi/devices/scsihd.cpp index bc71c966..ba830ac4 100644 --- a/src/raspberrypi/devices/scsihd.cpp +++ b/src/raspberrypi/devices/scsihd.cpp @@ -18,78 +18,75 @@ #include "fileio.h" #include "rascsi_exceptions.h" #include "scsi_command_util.h" -#include using namespace scsi_command_util; SCSIHD::SCSIHD(int lun, const unordered_set& sector_sizes, bool removable, scsi_defs::scsi_level level) - : Disk(removable ? "SCRM" : "SCHD", lun), scsi_level(level) + : Disk(removable ? SCRM : SCHD, lun), scsi_level(level) { SetSectorSizes(sector_sizes); + + SetProtectable(true); + SetRemovable(removable); + SetLockable(removable); + + SupportsSaveParameters(true); } -void SCSIHD::FinalizeSetup(const Filepath &path, off_t size, off_t image_offset) +string SCSIHD::GetProductData() const { - // 2TB is the current maximum + uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); + string unit; + + // 10 GiB and more + if (capacity >= 1'099'511'627'776) { + capacity /= 1'099'511'627'776; + unit = "GiB"; + } + // 1 MiB and more + else if (capacity >= 1'048'576) { + capacity /= 1'048'576; + unit = "MiB"; + } + else { + capacity /= 1024; + unit = "KiB"; + } + + return DEFAULT_PRODUCT + " " + to_string(capacity) + " " + unit; +} + +void SCSIHD::FinalizeSetup(off_t size, off_t image_offset) +{ + // Effective size must be a multiple of the sector size + size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); + + // 2 TiB is the current maximum if (size > 2LL * 1024 * 1024 * 1024 * 1024) { - throw io_exception("File size must not exceed 2 TiB"); + throw io_exception("Drive capacity cannot exceed 2 TiB"); } // For non-removable media drives set the default product name based on the drive capacity if (!IsRemovable()) { - uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes(); - string unit; - // 10 GiB and more - if (capacity >= 1'099'511'627'776) { - capacity /= 1'099'511'627'776; - unit = "GiB"; - } - // 1 MiB and more - else if (capacity >= 1'048'576) { - capacity /= 1'048'576; - unit = "MiB"; - } - else { - capacity /= 1024; - unit = "KiB"; - } - stringstream product; - product << DEFAULT_PRODUCT << " " << capacity << " " << unit; - SetProduct(product.str(), false); + SetProduct(GetProductData(), false); } - SetReadOnly(false); - SetProtectable(true); - SetProtected(false); + super::ValidateFile(GetFilename()); - super::Open(path); - SetPath(path); - - SetUpCache(path, image_offset); + SetUpCache(image_offset); } -void SCSIHD::Open(const Filepath& path) +void SCSIHD::Open() { assert(!IsReady()); - // Open as read-only - Fileio fio; - if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { - throw file_not_found_exception("Can't open SCSI hard disk file"); - } - - // Get file size - off_t size = fio.GetFileSize(); - fio.Close(); + off_t size = GetFileSize(); // Sector size (default 512 bytes) and number of blocks SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512); SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount())); - // Effective size must be a multiple of the sector size - size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes(); - - FinalizeSetup(path, size); + FinalizeSetup(size); } vector SCSIHD::InquiryInternal() const @@ -97,9 +94,9 @@ vector SCSIHD::InquiryInternal() const return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable()); } -void SCSIHD::ModeSelect(const vector& cdb, const vector& buf, int length) const +void SCSIHD::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const { - scsi_command_util::ModeSelect(cdb, buf, length, 1 << GetSectorSizeShiftCount()); + scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); } void SCSIHD::AddFormatPage(map>& pages, bool changeable) const diff --git a/src/raspberrypi/devices/scsihd.h b/src/raspberrypi/devices/scsihd.h index 9cae8d87..f93279bb 100644 --- a/src/raspberrypi/devices/scsihd.h +++ b/src/raspberrypi/devices/scsihd.h @@ -17,30 +17,32 @@ #pragma once #include "disk.h" -#include "filepath.h" +#include class SCSIHD : public Disk { - static constexpr const char *DEFAULT_PRODUCT = "SCSI HD"; + const string DEFAULT_PRODUCT = "SCSI HD"; public: SCSIHD(int, const unordered_set&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2); ~SCSIHD() override = default; - void FinalizeSetup(const Filepath&, off_t, off_t = 0); + void FinalizeSetup(off_t, off_t = 0); - void Open(const Filepath&) override; + void Open() override; // Commands vector InquiryInternal() const override; - void ModeSelect(const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; void AddFormatPage(map>&, bool) const override; void AddVendorPage(map>&, int, bool) const override; private: + string GetProductData() const; + using super = Disk; scsi_defs::scsi_level scsi_level; diff --git a/src/raspberrypi/devices/scsihd_nec.cpp b/src/raspberrypi/devices/scsihd_nec.cpp index 1a2ddb95..9e6d0bcb 100644 --- a/src/raspberrypi/devices/scsihd_nec.cpp +++ b/src/raspberrypi/devices/scsihd_nec.cpp @@ -23,42 +23,23 @@ using namespace scsi_command_util; const unordered_set SCSIHD_NEC::sector_sizes = { 512 }; -//--------------------------------------------------------------------------- -// -// Extract words that are supposed to be little endian -// -//--------------------------------------------------------------------------- -static inline int getWordLE(const BYTE *b) -{ - return ((int)b[1] << 8) | (int)b[0]; -} - -//--------------------------------------------------------------------------- -// -// Extract longwords assumed to be little endian -// -//--------------------------------------------------------------------------- -static inline uint32_t getDwordLE(const BYTE *b) -{ - return ((uint32_t)(b[3]) << 24) | ((uint32_t)(b[2]) << 16) | ((uint32_t)(b[1]) << 8) | b[0]; -} - -void SCSIHD_NEC::Open(const Filepath& path) +void SCSIHD_NEC::Open() { assert(!IsReady()); // Open as read-only + Filepath path; + path.SetPath(GetFilename().c_str()); Fileio fio; if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { - throw file_not_found_exception("Can't open hard disk file"); + throw file_not_found_exception("Can't open hard disk file '" + GetFilename() + '"'); } - // Get file size - off_t size = fio.GetFileSize(); + off_t size = GetFileSize(); // NEC root sector array root_sector; - if (size >= (off_t)root_sector.size() && !fio.Read(root_sector.data(), root_sector.size())) { + if (size < (off_t)root_sector.size() || !fio.Read(root_sector.data(), root_sector.size())) { fio.Close(); throw io_exception("Can't read NEC hard disk file root sector"); } @@ -67,46 +48,8 @@ void SCSIHD_NEC::Open(const Filepath& path) // Effective size must be a multiple of 512 size = (size / 512) * 512; - int image_size = 0; - int sector_size = 0; - // Determine parameters by extension - - // PC-9801-55 NEC genuine? - if (const char *ext = path.GetFileExt(); !strcasecmp(ext, ".hdn")) { - // Assuming sector size 512, number of sectors 25, number of heads 8 as default settings - image_offset = 0; - image_size = (int)size; - sector_size = 512; - sectors = 25; - heads = 8; - cylinders = (int)(size >> 9); - cylinders >>= 3; - cylinders /= 25; - } - // Anex86 HD image? - else if (!strcasecmp(ext, ".hdi")) { - image_offset = getDwordLE(&root_sector[8]); - image_size = getDwordLE(&root_sector[12]); - sector_size = getDwordLE(&root_sector[16]); - sectors = getDwordLE(&root_sector[20]); - heads = getDwordLE(&root_sector[24]); - cylinders = getDwordLE(&root_sector[28]); - } - // T98Next HD image? - else if (!strcasecmp(ext, ".nhd")) { - if (!memcmp(root_sector.data(), "T98HDDIMAGE.R0\0", 15)) { - image_offset = getDwordLE(&root_sector[0x110]); - cylinders = getDwordLE(&root_sector[0x114]); - heads = getWordLE(&root_sector[0x118]); - sectors = getWordLE(&root_sector[0x11a]); - sector_size = getWordLE(&root_sector[0x11c]); - image_size = (int)((off_t)cylinders * heads * sectors * sector_size); - } - else { - throw io_exception("Invalid NEC image file format"); - } - } + const auto [image_size, sector_size] = SetParameters(path.GetFileExt(), root_sector, (int)size); if (sector_size == 0) { throw io_exception("Invalid NEC drive sector size"); @@ -129,7 +72,57 @@ void SCSIHD_NEC::Open(const Filepath& path) SetBlockCount(image_size >> GetSectorSizeShiftCount()); - FinalizeSetup(path, size, image_offset); + FinalizeSetup(size, image_offset); +} + +pair SCSIHD_NEC::SetParameters(const string& extension, const array& root_sector, int size) +{ + string ext = extension; + transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + int image_size; + int sector_size; + + // PC-9801-55 NEC genuine? + if (ext == ".hdn") { + // Assuming sector size 512, number of sectors 25, number of heads 8 as default settings + image_offset = 0; + image_size = size; + sector_size = 512; + sectors = 25; + heads = 8; + cylinders = size >> 9; + cylinders >>= 3; + cylinders /= 25; + } + // Anex86 HD image? + else if (ext == ".hdi") { + image_offset = GetInt32LittleEndian(&root_sector[8]); + image_size = GetInt32LittleEndian(&root_sector[12]); + sector_size = GetInt32LittleEndian(&root_sector[16]); + sectors = GetInt32LittleEndian(&root_sector[20]); + heads = GetInt32LittleEndian(&root_sector[24]); + cylinders = GetInt32LittleEndian(&root_sector[28]); + } + // T98Next HD image? + else if (ext == ".nhd") { + if (!memcmp(root_sector.data(), "T98HDDIMAGE.R0\0", 15)) { + image_offset = GetInt32LittleEndian(&root_sector[0x110]); + cylinders = GetInt32LittleEndian(&root_sector[0x114]); + heads = GetInt16LittleEndian(&root_sector[0x118]); + sectors = GetInt16LittleEndian(&root_sector[0x11a]); + sector_size = GetInt16LittleEndian(&root_sector[0x11c]); + image_size = (int)((off_t)cylinders * heads * sectors * sector_size); + } + else { + throw io_exception("Invalid NEC image file format"); + } + } + else { + throw io_exception("Invalid NEC image file extension"); + } + + return make_pair(image_size, sector_size); } vector SCSIHD_NEC::InquiryInternal() const @@ -137,15 +130,6 @@ vector SCSIHD_NEC::InquiryInternal() const return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false); } -void SCSIHD_NEC::AddErrorPage(map>& pages, bool) const -{ - vector buf(8); - - // The retry count is 0, and the limit time uses the default value inside the device. - - pages[1] = buf; -} - void SCSIHD_NEC::AddFormatPage(map>& pages, bool changeable) const { vector buf(24); @@ -196,3 +180,13 @@ void SCSIHD_NEC::AddDrivePage(map>& pages, bool changeable) co pages[4] = buf; } + +int SCSIHD_NEC::GetInt16LittleEndian(const BYTE *buf) +{ + return ((int)buf[1] << 8) | buf[0]; +} + +int SCSIHD_NEC::GetInt32LittleEndian(const BYTE *buf) +{ + return ((int)buf[3] << 24) | ((int)buf[2] << 16) | ((int)buf[1] << 8) | buf[0]; +} diff --git a/src/raspberrypi/devices/scsihd_nec.h b/src/raspberrypi/devices/scsihd_nec.h index 16f1a529..934d9b53 100644 --- a/src/raspberrypi/devices/scsihd_nec.h +++ b/src/raspberrypi/devices/scsihd_nec.h @@ -19,6 +19,8 @@ #include "scsihd.h" #include #include +#include +#include using namespace std; @@ -27,23 +29,29 @@ using namespace std; // SCSI hard disk (PC-9801-55 NEC genuine / Anex86 / T98Next) // //=========================================================================== -class SCSIHD_NEC : public SCSIHD +class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is acceptable in this case { public: explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {} ~SCSIHD_NEC() override = default; - void Open(const Filepath&) override; + void Open() override; + +protected: vector InquiryInternal() const override; - void AddErrorPage(map>&, bool) const override; void AddFormatPage(map>&, bool) const override; void AddDrivePage(map>&, bool) const override; private: + pair SetParameters(const string&, const array&, int); + + static int GetInt16LittleEndian(const BYTE *); + static int GetInt32LittleEndian(const BYTE *); + static const unordered_set sector_sizes; // Image file offset (NEC only) diff --git a/src/raspberrypi/devices/scsimo.cpp b/src/raspberrypi/devices/scsimo.cpp index 5626fd1b..b2680ea6 100644 --- a/src/raspberrypi/devices/scsimo.cpp +++ b/src/raspberrypi/devices/scsimo.cpp @@ -19,7 +19,7 @@ using namespace scsi_command_util; -SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk("SCMO", lun) +SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk(SCMO, lun) { SetSectorSizes(sector_sizes); @@ -31,23 +31,25 @@ SCSIMO::SCSIMO(int lun, const unordered_set& sector_sizes) : Disk("SCM geometries[512 * 1041500] = make_pair(512, 1041500); // 640 MB, 20248 bytes per sector, 310352 sectors geometries[2048 * 310352] = make_pair(2048, 310352); + + SetProtectable(true); + SetRemovable(true); + SetLockable(true); + + SupportsSaveParameters(true); } -void SCSIMO::Open(const Filepath& path) +void SCSIMO::Open() { assert(!IsReady()); - // Open as read-only - Fileio fio; + off_t size = GetFileSize(); - if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) { - throw file_not_found_exception("Can't open MO file"); + // 2 TiB is the current maximum + if (size > 2LL * 1024 * 1024 * 1024 * 1024) { + throw io_exception("Drive capacity cannot exceed 2 TiB"); } - // Get file size - off_t size = fio.GetFileSize(); - fio.Close(); - // For some capacities there are hard-coded, well-defined sector sizes and block counts if (!SetGeometryForCapacity(size)) { // Sector size (default 512 bytes) and number of blocks @@ -55,14 +57,9 @@ void SCSIMO::Open(const Filepath& path) SetBlockCount(size >> GetSectorSizeShiftCount()); } - SetReadOnly(false); - SetProtectable(true); - SetProtected(false); + super::ValidateFile(GetFilename()); - super::Open(path); - SetPath(path); - - SetUpCache(path, 0); + SetUpCache(0); // Attention if ready if (IsReady()) { @@ -100,19 +97,35 @@ void SCSIMO::AddOptionPage(map>& pages, bool) const // Do not report update blocks } -void SCSIMO::ModeSelect(const vector& cdb, const vector& buf, int length) const +void SCSIMO::ModeSelect(scsi_command cmd, const vector& cdb, const vector& buf, int length) const { - scsi_command_util::ModeSelect(cdb, buf, length, 1 << GetSectorSizeShiftCount()); + scsi_command_util::ModeSelect(cmd, cdb, buf, length, 1 << GetSectorSizeShiftCount()); } -//--------------------------------------------------------------------------- // -// Vendor Unique Format Page 20h (MO) +// Mode page code 20h - Vendor Unique Format Page +// Format mode XXh type 0 +// Information: http://h20628.www2.hp.com/km-ext/kmcsdirect/emr_na-lpg28560-1.pdf + +// Offset description +// 02h format mode +// 03h type of format (0) +// 04~07h size of user band (total sectors?) +// 08~09h size of spare band (spare sectors?) +// 0A~0Bh number of bands +// +// Actual value of each 3.5 inches optical medium (grabbed by Fujitsu M2513EL) +// +// 128M 230M 540M 640M +// --------------------------------------------------- +// Size of user band 3CBFAh 6CF75h FE45Ch 4BC50h +// Size of spare band 0400h 0401h 08CAh 08C4h +// Number of bands 0001h 000Ah 0012h 000Bh +// +// Further information: http://r2089.blog36.fc2.com/blog-entry-177.html // -//--------------------------------------------------------------------------- void SCSIMO::AddVendorPage(map>& pages, int page, bool changeable) const { - // Page code 20h if (page != 0x20 && page != 0x3f) { return; } @@ -126,29 +139,6 @@ void SCSIMO::AddVendorPage(map>& pages, int page, bool changea return; } - /* - mode page code 20h - Vendor Unique Format Page - format mode XXh type 0 - information: http://h20628.www2.hp.com/km-ext/kmcsdirect/emr_na-lpg28560-1.pdf - - offset description - 02h format mode - 03h type of format (0) - 04~07h size of user band (total sectors?) - 08~09h size of spare band (spare sectors?) - 0A~0Bh number of bands - - actual value of each 3.5inches optical medium (grabbed by Fujitsu M2513EL) - - 128M 230M 540M 640M - --------------------------------------------------- - size of user band 3CBFAh 6CF75h FE45Ch 4BC50h - size of spare band 0400h 0401h 08CAh 08C4h - number of bands 0001h 000Ah 0012h 000Bh - - further information: http://r2089.blog36.fc2.com/blog-entry-177.html - */ - if (IsReady()) { unsigned spare = 0; unsigned bands = 0; diff --git a/src/raspberrypi/devices/scsimo.h b/src/raspberrypi/devices/scsimo.h index 02237665..4dbe4656 100644 --- a/src/raspberrypi/devices/scsimo.h +++ b/src/raspberrypi/devices/scsimo.h @@ -15,7 +15,6 @@ #pragma once #include "disk.h" -#include "filepath.h" using Geometry = pair; @@ -26,10 +25,10 @@ public: SCSIMO(int, const unordered_set&); ~SCSIMO() override = default; - void Open(const Filepath&) override; + void Open() override; vector InquiryInternal() const override; - void ModeSelect(const vector&, const vector&, int) const override; + void ModeSelect(scsi_defs::scsi_command, const vector&, const vector&, int) const override; protected: diff --git a/src/raspberrypi/devices/storage_device.cpp b/src/raspberrypi/devices/storage_device.cpp new file mode 100644 index 00000000..bcb4c31e --- /dev/null +++ b/src/raspberrypi/devices/storage_device.cpp @@ -0,0 +1,103 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "rascsi_exceptions.h" +#include "storage_device.h" +#include +#include + +using namespace std; +using namespace filesystem; + +unordered_map StorageDevice::reserved_files; + +StorageDevice::StorageDevice(PbDeviceType type, int lun) : ModePageDevice(type, lun) +{ + SupportsFile(true); + SetStoppable(true); +} + +void StorageDevice::ValidateFile(const string& file) +{ + if (blocks == 0) { + throw io_exception(string(GetTypeString()) + " device has 0 blocks"); + } + + // TODO Check for duplicate handling of these properties (-> rascsi_executor.cpp) + if (access(file.c_str(), W_OK)) { + // Permanently write-protected + SetReadOnly(true); + SetProtectable(false); + SetProtected(false); + } + + SetStopped(false); + SetRemoved(false); + SetLocked(false); + SetReady(true); +} + +void StorageDevice::MediumChanged() +{ + assert(IsRemovable()); + + medium_changed = true; +} + +void StorageDevice::ReserveFile(const string& file, int id, int lun) const +{ + assert(!file.empty()); + assert(reserved_files.find(file) == reserved_files.end()); + + reserved_files[file] = make_pair(id, lun); +} + +void StorageDevice::UnreserveFile() +{ + reserved_files.erase(filename); + + filename = ""; +} + +bool StorageDevice::GetIdsForReservedFile(const string& file, int& id, int& lun) +{ + if (const auto& it = reserved_files.find(file); it != reserved_files.end()) { + id = it->second.first; + lun = it->second.second; + + return true; + } + + return false; +} + +void StorageDevice::UnreserveAll() +{ + reserved_files.clear(); +} + +bool StorageDevice::FileExists(const string& file) +{ + return exists(file); +} + +bool StorageDevice::IsReadOnlyFile() const +{ + return access(filename.c_str(), W_OK); +} + +off_t StorageDevice::GetFileSize() const +{ + // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handled more than 2 GiB + if (struct stat st; !stat(filename.c_str(), &st)) { + return st.st_size; + } + + throw io_exception("Can't get size of '" + filename + "'"); +} diff --git a/src/raspberrypi/devices/storage_device.h b/src/raspberrypi/devices/storage_device.h new file mode 100644 index 00000000..caea0b3a --- /dev/null +++ b/src/raspberrypi/devices/storage_device.h @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// The base class for all mass storage devices with image file support +// +//--------------------------------------------------------------------------- + +#pragma once + +#include "mode_page_device.h" +#include +#include + +using namespace std; + +using id_set = pair; + +class StorageDevice : public ModePageDevice +{ +public: + + StorageDevice(PbDeviceType, int); + ~StorageDevice() override = default; + + virtual void Open() = 0; + + void ValidateFile(const string&); + string GetFilename() const { return filename; } + void SetFilename(string_view f) { filename = f; } + + void MediumChanged(); + + uint64_t GetBlockCount() const { return blocks; } + + void ReserveFile(const string&, int, int) const; + void UnreserveFile(); + static void UnreserveAll(); + + static bool FileExists(const string&); + bool IsReadOnlyFile() const; + + static unordered_map GetReservedFiles() { return reserved_files; } + static void SetReservedFiles(const unordered_map& r) { reserved_files = r; } + static bool GetIdsForReservedFile(const string&, int&, int&); + +protected: + + bool IsMediumChanged() const { return medium_changed; } + void SetMediumChanged(bool b) { medium_changed = b; } + + void SetBlockCount(uint64_t b) { blocks = b; } + + off_t GetFileSize() const; + +private: + + // Total number of blocks + uint64_t blocks = 0; + + string filename; + + bool medium_changed = false; + + // The list of image files in use and the IDs and LUNs using these files + static unordered_map reserved_files; +}; diff --git a/src/raspberrypi/log.h b/src/raspberrypi/log.h index 00069147..90ec26d2 100644 --- a/src/raspberrypi/log.h +++ b/src/raspberrypi/log.h @@ -29,4 +29,3 @@ static const int LOGBUF_SIZE = 512; #define LOGINFO(...) SPDLOGWRAPPER(spdlog::level::info, __VA_ARGS__) #define LOGWARN(...) SPDLOGWRAPPER(spdlog::level::warn, __VA_ARGS__) #define LOGERROR(...) SPDLOGWRAPPER(spdlog::level::err, __VA_ARGS__) -#define LOGCRITICAL(...) SPDLOGWRAPPER(spdlog::level::critical, __VA_ARGS__) diff --git a/src/raspberrypi/monitor/sm_json_report.cpp b/src/raspberrypi/monitor/sm_json_report.cpp index 4525b87a..2389edd9 100644 --- a/src/raspberrypi/monitor/sm_json_report.cpp +++ b/src/raspberrypi/monitor/sm_json_report.cpp @@ -10,7 +10,6 @@ #include "sm_reports.h" #include "log.h" -#include "spdlog/spdlog.h" #include "string.h" #include #include diff --git a/src/raspberrypi/monitor/sm_vcd_report.cpp b/src/raspberrypi/monitor/sm_vcd_report.cpp index 21bba1fe..566c306a 100644 --- a/src/raspberrypi/monitor/sm_vcd_report.cpp +++ b/src/raspberrypi/monitor/sm_vcd_report.cpp @@ -11,7 +11,6 @@ #include "os.h" #include "log.h" -#include "spdlog/spdlog.h" #include #include #include diff --git a/src/raspberrypi/protobuf_serializer.cpp b/src/raspberrypi/protobuf_serializer.cpp index 2f4bed4a..e2e7e649 100644 --- a/src/raspberrypi/protobuf_serializer.cpp +++ b/src/raspberrypi/protobuf_serializer.cpp @@ -47,9 +47,9 @@ void ProtobufSerializer::DeserializeMessage(int fd, google::protobuf::Message& m throw io_exception("Invalid protobuf message header"); } - const size_t size = ((int)header_buf[3] << 24) + ((int)header_buf[2] << 16) + const int size = ((int)header_buf[3] << 24) + ((int)header_buf[2] << 16) + ((int)header_buf[1] << 8) + (int)header_buf[0]; - if (size <= 0) { + if (size < 0) { throw io_exception("Invalid protobuf message header"); } diff --git a/src/raspberrypi/protobuf_util.cpp b/src/raspberrypi/protobuf_util.cpp index 9b8545e2..1aa534d1 100644 --- a/src/raspberrypi/protobuf_util.cpp +++ b/src/raspberrypi/protobuf_util.cpp @@ -30,9 +30,9 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa // Old style parameters, for backwards compatibility only. // Only one of these parameters will be used by rascsi, depending on the device type. if (params.find(KEY_VALUE_SEPARATOR) == string::npos) { - AddParam(device, "file", params); + SetParam(device, "file", params); if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") { - AddParam(device, "interfaces", params); + SetParam(device, "interfaces", params); } return; @@ -44,7 +44,7 @@ void protobuf_util::ParseParameters(PbDeviceDefinition& device, const string& pa if (!p.empty()) { const size_t separator_pos = p.find(KEY_VALUE_SEPARATOR); if (separator_pos != string::npos) { - AddParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); + SetParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1)); } } } @@ -62,7 +62,7 @@ string protobuf_util::GetParam(const PbDeviceDefinition& device, const string& k return it != device.params().end() ? it->second : ""; } -void protobuf_util::AddParam(PbCommand& command, const string& key, string_view value) +void protobuf_util::SetParam(PbCommand& command, const string& key, string_view value) { if (!key.empty() && !value.empty()) { auto& map = *command.mutable_params(); @@ -70,7 +70,7 @@ void protobuf_util::AddParam(PbCommand& command, const string& key, string_view } } -void protobuf_util::AddParam(PbDevice& device, const string& key, string_view value) +void protobuf_util::SetParam(PbDevice& device, const string& key, string_view value) { if (!key.empty() && !value.empty()) { auto& map = *device.mutable_params(); @@ -78,7 +78,7 @@ void protobuf_util::AddParam(PbDevice& device, const string& key, string_view va } } -void protobuf_util::AddParam(PbDeviceDefinition& device, const string& key, string_view value) +void protobuf_util::SetParam(PbDeviceDefinition& device, const string& key, string_view value) { if (!key.empty() && !value.empty()) { auto& map = *device.mutable_params(); @@ -98,6 +98,6 @@ void protobuf_util::SetPatternParams(PbCommand& command, string_view patterns) file_pattern = patterns; } - AddParam(command, "folder_pattern", folder_pattern); - AddParam(command, "file_pattern", file_pattern); + SetParam(command, "folder_pattern", folder_pattern); + SetParam(command, "file_pattern", file_pattern); } diff --git a/src/raspberrypi/protobuf_util.h b/src/raspberrypi/protobuf_util.h index 4dafe484..04cd79ff 100644 --- a/src/raspberrypi/protobuf_util.h +++ b/src/raspberrypi/protobuf_util.h @@ -23,8 +23,8 @@ namespace protobuf_util void ParseParameters(PbDeviceDefinition&, const string&); string GetParam(const PbCommand&, const string&); string GetParam(const PbDeviceDefinition&, const string&); - void AddParam(PbCommand&, const string&, string_view); - void AddParam(PbDevice&, const string&, string_view); - void AddParam(PbDeviceDefinition&, const string&, string_view); + void SetParam(PbCommand&, const string&, string_view); + void SetParam(PbDevice&, const string&, string_view); + void SetParam(PbDeviceDefinition&, const string&, string_view); void SetPatternParams(PbCommand&, string_view); } diff --git a/src/raspberrypi/rascsi.cpp b/src/raspberrypi/rascsi.cpp index 116b431e..ff42f19b 100644 --- a/src/raspberrypi/rascsi.cpp +++ b/src/raspberrypi/rascsi.cpp @@ -28,7 +28,6 @@ #include "rascsi/rascsi_image.h" #include "rascsi/rascsi_service.h" #include "rasutil.h" -#include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" #include #include diff --git a/src/raspberrypi/rascsi/localizer.cpp b/src/raspberrypi/rascsi/localizer.cpp index cb4bf3d9..31a0e02b 100644 --- a/src/raspberrypi/rascsi/localizer.cpp +++ b/src/raspberrypi/rascsi/localizer.cpp @@ -195,13 +195,16 @@ string Localizer::Localize(LocalizationKey key, const string& locale, const stri } } - if (it == localized_messages.end()) { - return "Missing default localization for enum value " + to_string((int)key); - } + assert (it != localized_messages.end()); auto messages = it->second; - string message = messages[key]; + const auto& m = messages.find(key); + if (m == messages.end()) { + return "Missing localization for enum value " + to_string((int)key); + } + + string message = m->second; message = regex_replace(message, regex("%1"), arg1); message = regex_replace(message, regex("%2"), arg2); message = regex_replace(message, regex("%3"), arg3); diff --git a/src/raspberrypi/rascsi/rascsi_executor.cpp b/src/raspberrypi/rascsi/rascsi_executor.cpp index 93fe7f30..67702b73 100644 --- a/src/raspberrypi/rascsi/rascsi_executor.cpp +++ b/src/raspberrypi/rascsi/rascsi_executor.cpp @@ -21,7 +21,6 @@ #include "protobuf_util.h" #include "command_context.h" #include "rasutil.h" -#include "spdlog/spdlog.h" #include "rascsi_executor.h" #include @@ -29,7 +28,7 @@ using namespace spdlog; using namespace protobuf_util; using namespace ras_util; -bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, +bool RascsiExecutor::ProcessDeviceCmd(const CommandContext& context, const PbDeviceDefinition& pb_device, const PbCommand& command, bool dryRun) { PrintCommand(command, pb_device, dryRun); @@ -50,7 +49,7 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbDeviceDef auto device = controller_manager.GetDeviceByIdAndLun(id, lun); - if (!ValidationOperationAgainstDevice(context, device, operation)) { + if (!ValidateOperationAgainstDevice(context, device, operation)) { return false; } @@ -131,24 +130,24 @@ bool RascsiExecutor::ProcessCmd(const CommandContext& context, const PbCommand& } // Remember the list of reserved files, than run the dry run - const auto& reserved_files = Disk::GetReservedFiles(); + const auto& reserved_files = StorageDevice::GetReservedFiles(); for (const auto& device : command.devices()) { - if (!ProcessCmd(context, device, command, true)) { + if (!ProcessDeviceCmd(context, device, command, true)) { // Dry run failed, restore the file list - Disk::SetReservedFiles(reserved_files); + StorageDevice::SetReservedFiles(reserved_files); return false; } } // Restore the list of reserved files before proceeding - Disk::SetReservedFiles(reserved_files); + StorageDevice::SetReservedFiles(reserved_files); if (const string result = ValidateLunSetup(command); !result.empty()) { return context.ReturnStatus(false, result); } for (const auto& device : command.devices()) { - if (!ProcessCmd(context, device, command, false)) { + if (!ProcessDeviceCmd(context, device, command, false)) { return false; } } @@ -183,9 +182,6 @@ bool RascsiExecutor::SetLogLevel(const string& log_level) const else if (log_level == "err") { set_level(level::err); } - else if (log_level == "critical") { - set_level(level::critical); - } else if (log_level == "off") { set_level(level::off); } @@ -203,10 +199,10 @@ bool RascsiExecutor::SetLogLevel(const string& log_level) const bool RascsiExecutor::Start(shared_ptr device, bool dryRun) const { if (!dryRun) { - LOGINFO("Start requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + LOGINFO("Start requested for %s ID %d, unit %d", device->GetTypeString(), device->GetId(), device->GetLun()) if (!device->Start()) { - LOGWARN("Starting %s ID %d, unit %d failed", device->GetType().c_str(), device->GetId(), device->GetLun()) + LOGWARN("Starting %s ID %d, unit %d failed", device->GetTypeString(), device->GetId(), device->GetLun()) } } @@ -216,9 +212,8 @@ bool RascsiExecutor::Start(shared_ptr device, bool dryRun) const bool RascsiExecutor::Stop(shared_ptr device, bool dryRun) const { if (!dryRun) { - LOGINFO("Stop requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + LOGINFO("Stop requested for %s ID %d, unit %d", device->GetTypeString(), device->GetId(), device->GetLun()) - // STOP is idempotent device->Stop(); } @@ -228,10 +223,10 @@ bool RascsiExecutor::Stop(shared_ptr device, bool dryRun) const bool RascsiExecutor::Eject(shared_ptr device, bool dryRun) const { if (!dryRun) { - LOGINFO("Eject requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), device->GetLun()) + LOGINFO("Eject requested for %s ID %d, unit %d", device->GetTypeString(), device->GetId(), device->GetLun()) if (!device->Eject(true)) { - LOGWARN("Ejecting %s ID %d, unit %d failed", device->GetType().c_str(), device->GetId(), device->GetLun()) + LOGWARN("Ejecting %s ID %d, unit %d failed", device->GetTypeString(), device->GetId(), device->GetLun()) } } @@ -241,10 +236,9 @@ bool RascsiExecutor::Eject(shared_ptr device, bool dryRun) const bool RascsiExecutor::Protect(shared_ptr device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write protection requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), + LOGINFO("Write protection requested for %s ID %d, unit %d", device->GetTypeString(), device->GetId(), device->GetLun()) - // PROTECT is idempotent device->SetProtected(true); } @@ -254,10 +248,9 @@ bool RascsiExecutor::Protect(shared_ptr device, bool dryRun) cons bool RascsiExecutor::Unprotect(shared_ptr device, bool dryRun) const { if (!dryRun) { - LOGINFO("Write unprotection requested for %s ID %d, unit %d", device->GetType().c_str(), device->GetId(), + LOGINFO("Write unprotection requested for %s ID %d, unit %d", device->GetTypeString(), device->GetId(), device->GetLun()) - // UNPROTECT is idempotent device->SetProtected(false); } @@ -289,15 +282,15 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return false; } - // If no filename was provided the medium is considered removed - auto disk = dynamic_pointer_cast(device); - device->SetRemoved(disk != nullptr ? filename.empty() : false); + // If no filename was provided the medium is considered not inserted + auto storage_device = dynamic_pointer_cast(device); + device->SetRemoved(storage_device != nullptr ? filename.empty() : false); if (!SetProductData(context, pb_device, device)) { return false; } - if (!SetSectorSize(context, PbDeviceType_Name(type), device, pb_device.block_size())) { + if (!SetSectorSize(context, device, pb_device.block_size())) { return false; } @@ -308,7 +301,7 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME, PbDeviceType_Name(type)); } - if (!ValidateImageFile(context, device, filename, full_path)) { + if (!ValidateImageFile(context, storage_device, filename, full_path)) { return false; } } @@ -319,29 +312,31 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit device->SetProtected(pb_device.protected_()); } - // Stop the dry run here, before permanently modifying something - if (dryRun) { - return true; - } - unordered_map params = { pb_device.params().begin(), pb_device.params().end() }; if (!device->SupportsFile()) { + // Clients like rasctl might have sent both "file" and "interfaces" params.erase("file"); } - if (!device->Init(params)) { + if (device->SupportsParams() && !device->Init(params)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_INITIALIZATION, PbDeviceType_Name(type), to_string(id), to_string(lun)); } + // Remove SupportsFile as soon as Daynaport and bridge do not inherit from Disk anymore + if (storage_device != nullptr && storage_device->SupportsFile()) { + storage_device->ReserveFile(full_path, id, lun); + } + + // Stop the dry run here, before actually attaching + if (dryRun) { + return true; + } + if (!controller_manager.AttachToScsiController(id, device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_SCSI_CONTROLLER); } - Filepath filepath; - filepath.SetPath(full_path.c_str()); - disk->ReserveFile(filepath, id, lun); - string msg = "Attached "; if (device->IsReadOnly()) { msg += "read-only "; @@ -349,7 +344,7 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit else if (device->IsProtectable() && device->IsProtected()) { msg += "protected "; } - msg += device->GetType() + " device, ID " + to_string(id) + ", unit " + to_string(lun); + msg += string(device->GetTypeString()) + " device, ID " + to_string(id) + ", unit " + to_string(lun); LOGINFO("%s", msg.c_str()) return true; @@ -358,7 +353,12 @@ bool RascsiExecutor::Attach(const CommandContext& context, const PbDeviceDefinit bool RascsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinition& pb_device, shared_ptr device, bool dryRun) const { - if (!device->IsRemoved()) { + auto storage_device = dynamic_pointer_cast(device); + if (storage_device == nullptr) { + return false; + } + + if (!storage_device->IsRemoved()) { return context.ReturnLocalizedError(LocalizationKey::ERROR_EJECT_REQUIRED); } @@ -371,35 +371,26 @@ bool RascsiExecutor::Insert(const CommandContext& context, const PbDeviceDefinit return context.ReturnLocalizedError(LocalizationKey::ERROR_MISSING_FILENAME); } + // Stop the dry run here, before modifying the device if (dryRun) { return true; } LOGINFO("Insert %sfile '%s' requested into %s ID %d, unit %d", pb_device.protected_() ? "protected " : "", - filename.c_str(), device->GetType().c_str(), pb_device.id(), pb_device.unit()) + filename.c_str(), storage_device->GetTypeString(), pb_device.id(), pb_device.unit()) - if (!SetSectorSize(context, device->GetType(), device, pb_device.block_size())) { + if (!SetSectorSize(context, storage_device, pb_device.block_size())) { return false; } string full_path; - if (!ValidateImageFile(context, device, filename, full_path)) { + if (!ValidateImageFile(context, storage_device, filename, full_path)) { return false; } - Filepath filepath; - filepath.SetPath(full_path.c_str()); - dynamic_pointer_cast(device)->ReserveFile(filepath, device->GetId(), device->GetLun()); - - // Only non read-only devices support protect/unprotect. - // This operation must not be executed before Open() because Open() overrides some settings. - if (device->IsProtectable() && !device->IsReadOnly()) { - device->SetProtected(pb_device.protected_()); - } - - if (auto disk = dynamic_pointer_cast(device); disk != nullptr) { - disk->MediumChanged(); - } + storage_device->SetProtected(pb_device.protected_()); + storage_device->ReserveFile(full_path, storage_device->GetId(), storage_device->GetLun()); + storage_device->MediumChanged(); return true; } @@ -417,7 +408,7 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptrDeleteDevice(device)) { + if (!controller->RemoveDevice(device)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH); } @@ -426,12 +417,12 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr(device); disk != nullptr) { - disk->UnreserveFile(); + if (auto storage_device = dynamic_pointer_cast(device); storage_device != nullptr) { + storage_device->UnreserveFile(); } - LOGINFO("%s", ("Detached " + device->GetType() + " device with ID " + to_string(device->GetId()) - + ", unit " + to_string(device->GetLun())).c_str()) + LOGINFO("%s", ("Detached " + string(device->GetTypeString()) + " device with ID " + + to_string(device->GetId()) + ", unit " + to_string(device->GetLun())).c_str()) } return true; @@ -440,7 +431,7 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr reserved; + set reserved; for (const string& id_to_reserve : ids_to_reserve) { int res_id; if (!GetAsInt(id_to_reserve, res_id) || res_id > 7) { @@ -524,12 +515,12 @@ string RascsiExecutor::SetReservedIds(string_view ids) reserved.insert(res_id); } - reserved_ids = reserved; + reserved_ids = { reserved.begin(), reserved.end() }; if (!reserved_ids.empty()) { string s; bool isFirst = true; - for (auto const& reserved_id : reserved_ids) { + for (auto const& reserved_id : reserved) { if (!isFirst) { s += ", "; } @@ -546,50 +537,51 @@ string RascsiExecutor::SetReservedIds(string_view ids) return ""; } -bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr device, +bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr storage_device, const string& filename, string& full_path) const { - if (!device->SupportsFile()) { - return true; - } - - auto disk = dynamic_pointer_cast(device); - if (disk == nullptr || filename.empty()) { + if (filename.empty()) { return true; } int id; int lun; - Filepath filepath; - filepath.SetPath(filename.c_str()); - - if (Disk::GetIdsForReservedFile(filepath, id, lun)) { + if (StorageDevice::GetIdsForReservedFile(filename, id, lun)) { return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, to_string(id), to_string(lun)); } - string initial_filename = filepath.GetPath(); + string effective_filename = filename; - try { - if (!disk->FileExists(filepath)) { - // If the file does not exist search for it in the default image folder - filepath.SetPath((rascsi_image.GetDefaultFolder() + "/" + filename).c_str()); + if (!StorageDevice::FileExists(filename)) { + // If the file does not exist search for it in the default image folder + effective_filename = rascsi_image.GetDefaultFolder() + "/" + filename; - if (Disk::GetIdsForReservedFile(filepath, id, lun)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, - to_string(id), to_string(lun)); - } + if (StorageDevice::GetIdsForReservedFile(effective_filename, id, lun)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_IMAGE_IN_USE, filename, + to_string(id), to_string(lun)); + } - if (!disk->FileExists(filepath)) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, initial_filename); - } + if (!StorageDevice::FileExists(effective_filename)) { + return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, effective_filename); } } - catch(const io_exception& e) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_FILE_OPEN, initial_filename); + + storage_device->SetFilename(effective_filename); + + if (storage_device->IsReadOnlyFile()) { + // Permanently write-protected + storage_device->SetReadOnly(true); + storage_device->SetProtectable(false); + } + else { + storage_device->SetReadOnly(false); + storage_device->SetProtectable(true); } - full_path = filepath.GetPath(); + storage_device->Open(); + + full_path = effective_filename; return true; } @@ -689,8 +681,7 @@ shared_ptr RascsiExecutor::CreateDevice(const CommandContext& con return device; } -bool RascsiExecutor::SetSectorSize(const CommandContext& context, const string& type, - shared_ptr device, int block_size) const +bool RascsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr device, int block_size) const { if (block_size) { auto disk = dynamic_pointer_cast(device); @@ -700,32 +691,31 @@ bool RascsiExecutor::SetSectorSize(const CommandContext& context, const string& } } else { - return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, type); + return context.ReturnLocalizedError(LocalizationKey::ERROR_BLOCK_SIZE_NOT_CONFIGURABLE, + device->GetTypeString()); } } return true; } -bool RascsiExecutor::ValidationOperationAgainstDevice(const CommandContext& context, +bool RascsiExecutor::ValidateOperationAgainstDevice(const CommandContext& context, const shared_ptr device, const PbOperation& operation) { - const string& type = device->GetType(); - if ((operation == START || operation == STOP) && !device->IsStoppable()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, type); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_STOPPABLE, device->GetTypeString()); } if ((operation == INSERT || operation == EJECT) && !device->IsRemovable()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, type); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_REMOVABLE, device->GetTypeString()); } if ((operation == PROTECT || operation == UNPROTECT) && !device->IsProtectable()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, type); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_PROTECTABLE, device->GetTypeString()); } if ((operation == PROTECT || operation == UNPROTECT) && !device->IsReady()) { - return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, type); + return context.ReturnLocalizedError(LocalizationKey::ERROR_OPERATION_DENIED_READY, device->GetTypeString()); } return true; diff --git a/src/raspberrypi/rascsi/rascsi_executor.h b/src/raspberrypi/rascsi/rascsi_executor.h index 02b40a6a..36dce4fd 100644 --- a/src/raspberrypi/rascsi/rascsi_executor.h +++ b/src/raspberrypi/rascsi/rascsi_executor.h @@ -42,7 +42,7 @@ public: unordered_set GetReservedIds() const { return reserved_ids; } - bool ProcessCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); + bool ProcessDeviceCmd(const CommandContext&, const PbDeviceDefinition&, const PbCommand&, bool); bool ProcessCmd(const CommandContext&, const PbCommand&); bool SetLogLevel(const string&) const; bool Start(shared_ptr, bool) const; @@ -56,14 +56,14 @@ public: void DetachAll(); bool ShutDown(const CommandContext&, const string&); string SetReservedIds(string_view); - bool ValidateImageFile(const CommandContext&, shared_ptr, const string&, string&) const; + bool ValidateImageFile(const CommandContext&, shared_ptr, const string&, string&) const; void PrintCommand(const PbCommand&, const PbDeviceDefinition&, bool) const; string ValidateLunSetup(const PbCommand&) const; bool VerifyExistingIdAndLun(const CommandContext&, int, int) const; shared_ptr CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const; - bool SetSectorSize(const CommandContext&, const string& type, shared_ptr, int) const; + bool SetSectorSize(const CommandContext&, shared_ptr, int) const; - static bool ValidationOperationAgainstDevice(const CommandContext&, const shared_ptr, + static bool ValidateOperationAgainstDevice(const CommandContext&, const shared_ptr, const PbOperation&); static bool ValidateIdAndLun(const CommandContext&, int, int); static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, shared_ptr); diff --git a/src/raspberrypi/rascsi/rascsi_image.cpp b/src/raspberrypi/rascsi/rascsi_image.cpp index 6ae5a9b8..3e066880 100644 --- a/src/raspberrypi/rascsi/rascsi_image.cpp +++ b/src/raspberrypi/rascsi/rascsi_image.cpp @@ -10,8 +10,6 @@ #include #include #include "log.h" -#include "filepath.h" -#include "spdlog/spdlog.h" #include "devices/disk.h" #include "protobuf_util.h" #include "command_context.h" @@ -24,7 +22,6 @@ #endif using namespace std; -using namespace spdlog; using namespace rascsi_interface; using namespace protobuf_util; @@ -89,20 +86,6 @@ string RascsiImage::SetDefaultFolder(const string& f) return ""; } -bool RascsiImage::IsValidSrcFilename(const string& filename) const -{ - // 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) const -{ - // Destination file must not yet exist - struct stat st; - return stat(filename.c_str(), &st); -} - bool RascsiImage::CreateImage(const CommandContext& context, const PbCommand& command) const { const string filename = GetParam(command, "file"); @@ -190,12 +173,9 @@ bool RascsiImage::DeleteImage(const CommandContext& context, const PbCommand& co const string full_filename = GetFullName(filename); int id; - int unit; - Filepath filepath; - filepath.SetPath(full_filename.c_str()); - if (Disk::GetIdsForReservedFile(filepath, id, unit)) { + if (int lun; StorageDevice::GetIdsForReservedFile(full_filename, id, lun)) { return context.ReturnStatus(false, "Can't delete image file '" + full_filename + - "', it is currently being used by device ID " + to_string(id) + ", unit " + to_string(unit)); + "', it is currently being used by device ID " + to_string(id) + ", unit " + to_string(lun)); } if (remove(full_filename.c_str())) { @@ -386,6 +366,20 @@ bool RascsiImage::ValidateParams(const CommandContext& context, const PbCommand& return true; } +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); +} + string RascsiImage::GetHomeDir() { int uid = getuid(); diff --git a/src/raspberrypi/rascsi/rascsi_image.h b/src/raspberrypi/rascsi/rascsi_image.h index ca035248..deb4b80a 100644 --- a/src/raspberrypi/rascsi/rascsi_image.h +++ b/src/raspberrypi/rascsi/rascsi_image.h @@ -38,10 +38,10 @@ private: bool CheckDepth(string_view) const; string GetFullName(const string& filename) const { return default_folder + "/" + filename; } bool CreateImageFolder(const CommandContext&, const string&) const; - bool IsValidSrcFilename(const string&) const; - bool IsValidDstFilename(const string&) const; bool ValidateParams(const CommandContext&, const PbCommand&, const string&, string&, string&) const; + static bool IsValidSrcFilename(const string&); + static bool IsValidDstFilename(const string&); static string GetHomeDir(); string default_folder; diff --git a/src/raspberrypi/rascsi/rascsi_response.cpp b/src/raspberrypi/rascsi/rascsi_response.cpp index 9d477888..3e4388be 100644 --- a/src/raspberrypi/rascsi/rascsi_response.cpp +++ b/src/raspberrypi/rascsi/rascsi_response.cpp @@ -31,17 +31,14 @@ unique_ptr RascsiResponse::GetDeviceProperties(const Device& properties->set_supports_file(device.SupportsFile()); properties->set_supports_params(device.SupportsParams()); - PbDeviceType t = UNDEFINED; - PbDeviceType_Parse(device.GetType(), &t); - if (device.SupportsParams()) { - for (const auto& [key, value] : device_factory.GetDefaultParams(t)) { + for (const auto& [key, value] : device_factory.GetDefaultParams(device.GetType())) { auto& map = *properties->mutable_default_params(); map[key] = value; } } - for (const auto& block_size : device_factory.GetSectorSizes(t)) { + for (const auto& block_size : device_factory.GetSectorSizes(device.GetType())) { properties->add_block_sizes(block_size); } @@ -75,10 +72,7 @@ void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const pb_device.set_vendor(device.GetVendor()); pb_device.set_product(device.GetProduct()); pb_device.set_revision(device.GetRevision()); - - PbDeviceType type = UNDEFINED; - PbDeviceType_Parse(device.GetType(), &type); - pb_device.set_type(type); + pb_device.set_type(device.GetType()); pb_device.set_allocated_properties(GetDeviceProperties(device).release()); @@ -91,7 +85,7 @@ void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const if (device.SupportsParams()) { //NOSONAR The allocated memory is managed by protobuf for (const auto& [key, value] : device.GetParams()) { - AddParam(pb_device, key, value); + SetParam(pb_device, key, value); } } @@ -100,12 +94,10 @@ void RascsiResponse::GetDevice(const Device& device, PbDevice& pb_device, const pb_device.set_block_count(device.IsRemoved() ? 0: disk->GetBlockCount()); } - const auto disk = dynamic_cast(&device); - if (disk != nullptr) { - Filepath filepath; - disk->GetPath(filepath); + const auto storage_device = dynamic_cast(&device); + if (storage_device != nullptr) { auto image_file = make_unique().release(); - GetImageFile(*image_file, default_folder, device.IsRemovable() && !device.IsReady() ? "" : filepath.GetPath()); + GetImageFile(*image_file, default_folder, device.IsReady() ? storage_device->GetFilename() : ""); pb_device.set_allocated_file(image_file); } } //NOSONAR The allocated memory is managed by protobuf @@ -120,6 +112,7 @@ bool RascsiResponse::GetImageFile(PbImageFile& image_file, const string& default image_file.set_read_only(access(f.c_str(), W_OK)); + // filesystem::file_size cannot be used here because gcc < 10.3.0 cannot handled more than 2 GiB if (struct stat st; !stat(f.c_str(), &st) && !S_ISDIR(st.st_mode)) { image_file.set_size(st.st_size); return true; @@ -355,7 +348,6 @@ unique_ptr RascsiResponse::GetOperationInfo(PbResult& result, i AddOperationParameter(*operation, "interface", "Comma-separated prioritized network interface list").release(); AddOperationParameter(*operation, "inet", "IP address and netmask of the network bridge").release(); AddOperationParameter(*operation, "cmd", "Print command for the printer device").release(); - AddOperationParameter(*operation, "timeout", "Reservation timeout for the printer device in seconds").release(); operation.release(); CreateOperation(*operation_info, DETACH, "Detach device, device-specific parameters are required").release(); diff --git a/src/raspberrypi/rascsi/rascsi_response.h b/src/raspberrypi/rascsi/rascsi_response.h index b8cf56c2..3bf7fc10 100644 --- a/src/raspberrypi/rascsi/rascsi_response.h +++ b/src/raspberrypi/rascsi/rascsi_response.h @@ -51,7 +51,7 @@ private: int max_luns; - const list log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" }; + const list log_levels = { "trace", "debug", "info", "warn", "err", "off" }; unique_ptr GetDeviceProperties(const Device&) const; void GetDevice(const Device&, PbDevice&, const string&) const; diff --git a/src/raspberrypi/rascsi/rascsi_service.cpp b/src/raspberrypi/rascsi/rascsi_service.cpp index c85c8494..60d2c55e 100644 --- a/src/raspberrypi/rascsi/rascsi_service.cpp +++ b/src/raspberrypi/rascsi/rascsi_service.cpp @@ -30,6 +30,10 @@ void RascsiService::Cleanup() const bool RascsiService::Init(const callback& cb, int port) { + if (port <= 0 || port > 65535) { + return false; + } + // Create socket for monitor sockaddr_in server = {}; service_socket = socket(PF_INET, SOCK_STREAM, 0); @@ -95,7 +99,7 @@ void RascsiService::Execute() const } } catch(const io_exception& e) { - LOGWARN("%s", e.get_msg().c_str()) + LOGWARN("%s", e.what()) // Fall through } diff --git a/src/raspberrypi/rascsi/rascsi_service.h b/src/raspberrypi/rascsi/rascsi_service.h index cdbad5b8..1e7747f6 100644 --- a/src/raspberrypi/rascsi/rascsi_service.h +++ b/src/raspberrypi/rascsi/rascsi_service.h @@ -16,6 +16,7 @@ class CommandContext; using namespace std; +using namespace rascsi_interface; class RascsiService { @@ -40,6 +41,8 @@ public: bool IsRunning() const { return running; } void SetRunning(bool b) const { running = b; } +private: + void Execute() const; PbCommand ReadCommand(CommandContext&) const; diff --git a/src/raspberrypi/rascsi_exceptions.h b/src/raspberrypi/rascsi_exceptions.h index d3ddbd03..519419ca 100644 --- a/src/raspberrypi/rascsi_exceptions.h +++ b/src/raspberrypi/rascsi_exceptions.h @@ -10,19 +10,13 @@ #pragma once #include "scsi.h" -#include -#include +#include -class io_exception : public std::exception +using namespace std; + +class io_exception : public runtime_error { - string msg; - -public: - - explicit io_exception(const string& msg) : msg(msg) {} - ~io_exception() override = default; - - const string& get_msg() const { return msg; } + using runtime_error::runtime_error; }; class file_not_found_exception : public io_exception @@ -30,21 +24,17 @@ class file_not_found_exception : public io_exception using io_exception::io_exception; }; -class scsi_exception : public std::exception +class scsi_exception : public exception { scsi_defs::sense_key sense_key; scsi_defs::asc asc; - scsi_defs::status status; public: - scsi_exception(scsi_defs::sense_key sense_key = scsi_defs::sense_key::ABORTED_COMMAND, - scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION, - scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) - : sense_key(sense_key), asc(asc), status(status) {} + scsi_exception(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION) + : sense_key(sense_key), asc(asc) {} ~scsi_exception() override = default; scsi_defs::sense_key get_sense_key() const { return sense_key; } scsi_defs::asc get_asc() const { return asc; } - scsi_defs::status get_status() const { return status; } }; diff --git a/src/raspberrypi/rascsi_interface.proto b/src/raspberrypi/rascsi_interface.proto index e0c41a68..36a979dd 100644 --- a/src/raspberrypi/rascsi_interface.proto +++ b/src/raspberrypi/rascsi_interface.proto @@ -1,3 +1,12 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2021-2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + // // Each rascsi message sent to the rascsi server is preceded by the magic string "RASCSI". // A message starts with a little endian 32 bit header which contains the protobuf message size. @@ -44,7 +53,6 @@ enum PbOperation { // "interface": A prioritized comma-separated list of interfaces to create a network bridge for // "inet": The IP address and netmask for the network bridge // "cmd": The command to be used for printing, with "%f" as file placeholder - // "timeout": The timeout for printer reservations in seconds ATTACH = 1; // Detach a device and return the new device list (PbDevicesInfo) @@ -76,8 +84,9 @@ enum PbOperation { // Gets the server information (PbServerInfo). Calling this operation should be avoided because it // may return a lot of data. More specific other operations should be used instead. - // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned - // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned + // Parameters: + // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned + // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned SERVER_INFO = 10; // Get rascsi server version (PbVersionInfo) @@ -92,8 +101,8 @@ enum PbOperation { // Get information on available image files in the default image folder (PbImageFilesInfo) // Parameters: - // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned - // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned + // "folder_pattern": Optional filter, only folder names containing the case-insensitive pattern are returned + // "file_pattern": Optional filter, only filenames containing the case-insensitive pattern are returned DEFAULT_IMAGE_FILES_INFO = 14; // Get information on an image file (not necessarily in the default image folder). diff --git a/src/raspberrypi/rasctl.cpp b/src/raspberrypi/rasctl.cpp index b81ac86d..18ccba91 100644 --- a/src/raspberrypi/rasctl.cpp +++ b/src/raspberrypi/rasctl.cpp @@ -13,13 +13,13 @@ #include "rascsi_version.h" #include "protobuf_util.h" #include "rasutil.h" +#include "rascsi_exceptions.h" #include "rascsi_interface.pb.h" #include "rasctl/rasctl_parser.h" #include "rasctl/rasctl_commands.h" #include #include #include -#include // Separator for the INQUIRY name components and for compound parameters static const char COMPONENT_SEPARATOR = ':'; @@ -50,7 +50,7 @@ void Banner(int argc, char* argv[]) cout << " HOST := rascsi host to connect to, default is 'localhost'\n"; cout << " PORT := rascsi port to connect to, default is 6868\n"; cout << " RESERVED_IDS := comma-separated list of IDs to reserve\n"; - cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|critical|off}, default is 'info'\n"; + cout << " LOG_LEVEL := log level {trace|debug|info|warn|err|off}, default is 'info'\n"; cout << " If CMD is 'attach' or 'insert' the FILE parameter is required.\n"; cout << "Usage: " << argv[0] << " -l\n"; cout << " Print device list.\n" << flush; @@ -67,7 +67,6 @@ int main(int argc, char* argv[]) RasctlParser parser; PbCommand command; - list devices; PbDeviceDefinition* device = command.add_devices(); device->set_id(-1); const char *hostname = "localhost"; @@ -279,7 +278,7 @@ int main(int argc, char* argv[]) case 'X': command.set_operation(SHUT_DOWN); - AddParam(command, "mode", "rascsi"); + SetParam(command, "mode", "rascsi"); break; case 'z': @@ -296,19 +295,33 @@ int main(int argc, char* argv[]) exit(EXIT_FAILURE); } - // Listing devices is a special case (rasctl backwards compatibility) - if (list) { - PbCommand command_list; - command_list.set_operation(DEVICES_INFO); - RasctlCommands rasctl_commands(command_list, hostname, port, token, locale); - rasctl_commands.CommandDevicesInfo(); - exit(EXIT_SUCCESS); + SetParam(command, "token", token); + SetParam(command, "locale", locale); + + RasctlCommands rasctl_commands(command, hostname, port); + + bool status; + try { + // Listing devices is a special case (rasctl backwards compatibility) + if (list) { + command.clear_devices(); + command.set_operation(DEVICES_INFO); + + status = rasctl_commands.CommandDevicesInfo(); + } + else { + ParseParameters(*device, param); + + status = rasctl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); + } + } + catch(const io_exception& e) { + cerr << "Error: " << e.what() << endl; + + status = false; + + // Fall through } - ParseParameters(*device, param); - - RasctlCommands rasctl_commands(command, hostname, port, token, locale); - rasctl_commands.Execute(log_level, default_folder, reserved_ids, image_params, filename); - - exit(EXIT_SUCCESS); + return status ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/src/raspberrypi/rasctl/rasctl_commands.cpp b/src/raspberrypi/rasctl/rasctl_commands.cpp index 46278b09..0e927304 100644 --- a/src/raspberrypi/rasctl/rasctl_commands.cpp +++ b/src/raspberrypi/rasctl/rasctl_commands.cpp @@ -8,11 +8,8 @@ //--------------------------------------------------------------------------- #include "rascsi_exceptions.h" -#include "protobuf_serializer.h" #include "protobuf_util.h" -#include "rasutil.h" #include "rasctl_commands.h" -#include "rascsi_interface.pb.h" #include #include #include @@ -25,236 +22,198 @@ using namespace protobuf_util; // Separator for the INQUIRY name components static const char COMPONENT_SEPARATOR = ':'; -RasctlCommands::RasctlCommands(const PbCommand& command, const string& hostname, int port, const string& token, - const string& locale) - : command(command), hostname(hostname), port(port), token(token), locale(locale) -{ -} - -void RasctlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids, +bool RasctlCommands::Execute(const string& log_level, const string& default_folder, const string& reserved_ids, const string& image_params, const string& filename) { switch(command.operation()) { case LOG_LEVEL: - CommandLogLevel(log_level); - break; + return CommandLogLevel(log_level); case DEFAULT_FOLDER: - CommandDefaultImageFolder(default_folder); - break; + return CommandDefaultImageFolder(default_folder); case RESERVE_IDS: - CommandReserveIds(reserved_ids); - break; + return CommandReserveIds(reserved_ids); case CREATE_IMAGE: - CommandCreateImage(image_params); - break; + return CommandCreateImage(image_params); case DELETE_IMAGE: - CommandDeleteImage(image_params); - break; + return CommandDeleteImage(image_params); case RENAME_IMAGE: - CommandRenameImage(image_params); - break; + return CommandRenameImage(image_params); case COPY_IMAGE: - CommandCopyImage(image_params); - break; + return CommandCopyImage(image_params); case DEVICES_INFO: - CommandDeviceInfo(); - break; + return CommandDeviceInfo(); case DEVICE_TYPES_INFO: - CommandDeviceTypesInfo(); - break; + return CommandDeviceTypesInfo(); case VERSION_INFO: - CommandVersionInfo(); - break; + return CommandVersionInfo(); case SERVER_INFO: - CommandServerInfo(); - break; + return CommandServerInfo(); case DEFAULT_IMAGE_FILES_INFO: - CommandDefaultImageFilesInfo(); - break; + return CommandDefaultImageFilesInfo(); case IMAGE_FILE_INFO: - CommandImageFileInfo(filename); - break; + return CommandImageFileInfo(filename); case NETWORK_INTERFACES_INFO: - CommandNetworkInterfacesInfo(); - break; + return CommandNetworkInterfacesInfo(); case LOG_LEVEL_INFO: - CommandLogLevelInfo(); - break; + return CommandLogLevelInfo(); case RESERVED_IDS_INFO: - CommandReservedIdsInfo(); - break; + return CommandReservedIdsInfo(); case MAPPING_INFO: - CommandMappingInfo(); - break; + return CommandMappingInfo(); case OPERATION_INFO: - CommandOperationInfo(); - break; + return CommandOperationInfo(); default: - SendCommand(); - break; + return SendCommand(); } + + return false; } -void RasctlCommands::SendCommand() +bool RasctlCommands::SendCommand() { - AddParam(command, "token", token); - AddParam(command, "locale", locale); + sockaddr_in server_addr = {}; + if (!ResolveHostName(hostname, &server_addr)) { + throw io_exception("Can't resolve hostname '" + hostname + "'"); + } - int fd = -1; - try { - fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd == -1) { - throw io_exception("Can't create socket: " + string(strerror(errno))); - } + const int fd = socket(AF_INET, SOCK_STREAM, 0); + if (fd == -1) { + throw io_exception("Can't create socket: " + string(strerror(errno))); + } - sockaddr_in server_addr = {}; - if (!ResolveHostName(hostname, &server_addr)) { - throw io_exception("Can't resolve hostname '" + hostname + "'"); - } + server_addr.sin_port = htons(uint16_t(port)); + if (connect(fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) { + close(fd); - server_addr.sin_port = htons(uint16_t(port)); - if (connect(fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0) { - throw io_exception("Can't connect to rascsi on host '" + hostname + "', port " + to_string(port) - + ": " + strerror(errno)); - } + throw io_exception("Can't connect to rascsi on host '" + hostname + "', port " + to_string(port) + + ": " + strerror(errno)); + } - if (write(fd, "RASCSI", 6) != 6) { - throw io_exception("Can't write magic"); - } + if (write(fd, "RASCSI", 6) != 6) { + close(fd); - serializer.SerializeMessage(fd, command); - } - catch(const io_exception& e) { - cerr << "Error: " << e.get_msg() << endl; + throw io_exception("Can't write magic"); + } - if (fd != -1) { - close(fd); - } - - exit(fd == -1 ? ENOTCONN : EXIT_FAILURE); - } - - // Receive result - try { - serializer.DeserializeMessage(fd, result); - - if (!result.status()) { - throw io_exception(result.msg()); - } - } - catch(const io_exception& e) { - close(fd); - - cerr << "Error: " << e.get_msg() << endl; - - exit(EXIT_FAILURE); - } + serializer.SerializeMessage(fd, command); + serializer.DeserializeMessage(fd, result); close(fd); + if (!result.status()) { + throw io_exception(result.msg()); + } + if (!result.msg().empty()) { cout << result.msg() << endl; } + + return true; } -void RasctlCommands::CommandDevicesInfo() +bool RasctlCommands::CommandDevicesInfo() { SendCommand(); cout << rasctl_display.DisplayDevicesInfo(result.devices_info()) << flush; + + return true; } -void RasctlCommands::CommandLogLevel(const string& log_level) +bool RasctlCommands::CommandLogLevel(const string& log_level) { - AddParam(command, "level", log_level); + SetParam(command, "level", log_level); - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandReserveIds(const string& reserved_ids) +bool RasctlCommands::CommandReserveIds(const string& reserved_ids) { - AddParam(command, "ids", reserved_ids); + SetParam(command, "ids", reserved_ids); - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandCreateImage(const string& image_params) +bool RasctlCommands::CommandCreateImage(const string& image_params) { if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - AddParam(command, "file", string_view(image_params).substr(0, separator_pos)); - AddParam(command, "size", string_view(image_params).substr(separator_pos + 1)); + SetParam(command, "file", string_view(image_params).substr(0, separator_pos)); + SetParam(command, "size", string_view(image_params).substr(separator_pos + 1)); } else { cerr << "Error: Invalid file descriptor '" << image_params << "', format is NAME:SIZE" << endl; - exit(EXIT_FAILURE); + + return false; } - AddParam(command, "read_only", "false"); + SetParam(command, "read_only", "false"); - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandDeleteImage(const string& filename) +bool RasctlCommands::CommandDeleteImage(const string& filename) { - AddParam(command, "file", filename); + SetParam(command, "file", filename); - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandRenameImage(const string& image_params) +bool RasctlCommands::CommandRenameImage(const string& image_params) { if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - AddParam(command, "from", string_view(image_params).substr(0, separator_pos)); - AddParam(command, "to", string_view(image_params).substr(separator_pos + 1)); + SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); + SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); } else { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; - exit(EXIT_FAILURE); + + return false; } - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandCopyImage(const string& image_params) +bool RasctlCommands::CommandCopyImage(const string& image_params) { if (const size_t separator_pos = image_params.find(COMPONENT_SEPARATOR); separator_pos != string::npos) { - AddParam(command, "from", string_view(image_params).substr(0, separator_pos)); - AddParam(command, "to", string_view(image_params).substr(separator_pos + 1)); + SetParam(command, "from", string_view(image_params).substr(0, separator_pos)); + SetParam(command, "to", string_view(image_params).substr(separator_pos + 1)); } else { cerr << "Error: Invalid file descriptor '" << image_params << "', format is CURRENT_NAME:NEW_NAME" << endl; - exit(EXIT_FAILURE); + + return false; } - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandDefaultImageFolder(const string& folder) +bool RasctlCommands::CommandDefaultImageFolder(const string& folder) { - AddParam(command, "folder", folder); + SetParam(command, "folder", folder); - SendCommand(); + return SendCommand(); } -void RasctlCommands::CommandDeviceInfo() +bool RasctlCommands::CommandDeviceInfo() { SendCommand(); @@ -263,23 +222,29 @@ void RasctlCommands::CommandDeviceInfo() } cout << flush; + + return true; } -void RasctlCommands::CommandDeviceTypesInfo() +bool RasctlCommands::CommandDeviceTypesInfo() { SendCommand(); cout << rasctl_display.DisplayDeviceTypesInfo(result.device_types_info()) << flush; + + return true; } -void RasctlCommands::CommandVersionInfo() +bool RasctlCommands::CommandVersionInfo() { SendCommand(); cout << rasctl_display.DisplayVersionInfo(result.version_info()) << flush; + + return true; } -void RasctlCommands::CommandServerInfo() +bool RasctlCommands::CommandServerInfo() { SendCommand(); @@ -306,57 +271,73 @@ void RasctlCommands::CommandServerInfo() } cout << flush; + + return true; } -void RasctlCommands::CommandDefaultImageFilesInfo() +bool RasctlCommands::CommandDefaultImageFilesInfo() { SendCommand(); cout << rasctl_display.DisplayImageFilesInfo(result.image_files_info()) << flush; + + return true; } -void RasctlCommands::CommandImageFileInfo(const string& filename) +bool RasctlCommands::CommandImageFileInfo(const string& filename) { - AddParam(command, "file", filename); + SetParam(command, "file", filename); SendCommand(); cout << rasctl_display.DisplayImageFile(result.image_file_info()) << flush; + + return true; } -void RasctlCommands::CommandNetworkInterfacesInfo() +bool RasctlCommands::CommandNetworkInterfacesInfo() { SendCommand(); cout << rasctl_display.DisplayNetworkInterfaces(result.network_interfaces_info()) << flush; + + return true; } -void RasctlCommands::CommandLogLevelInfo() +bool RasctlCommands::CommandLogLevelInfo() { SendCommand(); cout << rasctl_display.DisplayLogLevelInfo(result.log_level_info()) << flush; + + return true; } -void RasctlCommands::CommandReservedIdsInfo() +bool RasctlCommands::CommandReservedIdsInfo() { SendCommand(); cout << rasctl_display.DisplayReservedIdsInfo(result.reserved_ids_info()) << flush; + + return true; } -void RasctlCommands::CommandMappingInfo() +bool RasctlCommands::CommandMappingInfo() { SendCommand(); cout << rasctl_display.DisplayMappingInfo(result.mapping_info()) << flush; + + return true; } -void RasctlCommands::CommandOperationInfo() +bool RasctlCommands::CommandOperationInfo() { SendCommand(); cout << rasctl_display.DisplayOperationInfo(result.operation_info()) << flush; + + return true; } bool RasctlCommands::ResolveHostName(const string& host, sockaddr_in *addr) diff --git a/src/raspberrypi/rasctl/rasctl_commands.h b/src/raspberrypi/rasctl/rasctl_commands.h index 2764b540..ec18f36b 100644 --- a/src/raspberrypi/rasctl/rasctl_commands.h +++ b/src/raspberrypi/rasctl/rasctl_commands.h @@ -12,52 +12,52 @@ #include "protobuf_serializer.h" #include "rascsi_interface.pb.h" #include "rasctl_display.h" -#include #include using namespace rascsi_interface; +struct sockaddr_in; + class RasctlCommands { public: - RasctlCommands(const PbCommand&, const string&, int, const string&, const string&); + RasctlCommands(PbCommand& command, const string& hostname, int port) + : command(command), hostname(hostname), port(port) {} ~RasctlCommands() = default; - void Execute(const string&, const string&, const string&, const string&, const string&); + bool Execute(const string&, const string&, const string&, const string&, const string&); - void CommandDevicesInfo(); + bool CommandDevicesInfo(); private: - void CommandLogLevel(const string&); - void CommandReserveIds(const string&); - void CommandCreateImage(const string&); - void CommandDeleteImage(const string&); - void CommandRenameImage(const string&); - void CommandCopyImage(const string&); - void CommandDefaultImageFolder(const string&); - void CommandDeviceInfo(); - void CommandDeviceTypesInfo(); - void CommandVersionInfo(); - void CommandServerInfo(); - void CommandDefaultImageFilesInfo(); - void CommandImageFileInfo(const string&); - void CommandNetworkInterfacesInfo(); - void CommandLogLevelInfo(); - void CommandReservedIdsInfo(); - void CommandMappingInfo(); - void CommandOperationInfo(); - void SendCommand(); + bool CommandLogLevel(const string&); + bool CommandReserveIds(const string&); + bool CommandCreateImage(const string&); + bool CommandDeleteImage(const string&); + bool CommandRenameImage(const string&); + bool CommandCopyImage(const string&); + bool CommandDefaultImageFolder(const string&); + bool CommandDeviceInfo(); + bool CommandDeviceTypesInfo(); + bool CommandVersionInfo(); + bool CommandServerInfo(); + bool CommandDefaultImageFilesInfo(); + bool CommandImageFileInfo(const string&); + bool CommandNetworkInterfacesInfo(); + bool CommandLogLevelInfo(); + bool CommandReservedIdsInfo(); + bool CommandMappingInfo(); + bool CommandOperationInfo(); + bool SendCommand(); static bool ResolveHostName(const string&, sockaddr_in *); ProtobufSerializer serializer; - PbCommand command; + PbCommand& command; string hostname; int port; - string token; - string locale; PbResult result; diff --git a/src/raspberrypi/rasutil.cpp b/src/raspberrypi/rasutil.cpp index a6f19799..1c07e835 100644 --- a/src/raspberrypi/rasutil.cpp +++ b/src/raspberrypi/rasutil.cpp @@ -87,7 +87,7 @@ string ras_util::ListDevices(const list& pb_devices) } s << "| " << device.id() << " | " << device.unit() << " | " << PbDeviceType_Name(device.type()) << " | " - << (filename.empty() ? "NO MEDIA" : filename) + << (filename.empty() ? "NO MEDIUM" : filename) << (!device.status().removed() && (device.properties().read_only() || device.status().protected_()) ? " (READ-ONLY)" : "") << '\n'; } @@ -97,21 +97,33 @@ string ras_util::ListDevices(const list& pb_devices) return s.str(); } +string ras_util::GetExtensionLowerCase(const string& filename) +{ + string ext; + if (const size_t separator = filename.rfind('.'); separator != string::npos) { + ext = filename.substr(separator + 1); + } + std::transform(ext.begin(), ext.end(), ext.begin(), [](unsigned char c){ return std::tolower(c); }); + + return ext; +} + // Pin the thread to a specific CPU +// TODO Check whether just using a single CPU really makes sense void ras_util::FixCpu(int cpu) { #ifdef __linux__ // Get the number of CPUs - cpu_set_t cpuset; - CPU_ZERO(&cpuset); - sched_getaffinity(0, sizeof(cpu_set_t), &cpuset); - int cpus = CPU_COUNT(&cpuset); + cpu_set_t mask; + CPU_ZERO(&mask); + sched_getaffinity(0, sizeof(cpu_set_t), &mask); + const int cpus = CPU_COUNT(&mask); // Set the thread affinity if (cpu < cpus) { - CPU_ZERO(&cpuset); - CPU_SET(cpu, &cpuset); - sched_setaffinity(0, sizeof(cpu_set_t), &cpuset); + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + sched_setaffinity(0, sizeof(cpu_set_t), &mask); } #endif } diff --git a/src/raspberrypi/rasutil.h b/src/raspberrypi/rasutil.h index 2fff46de..9bf69c9e 100644 --- a/src/raspberrypi/rasutil.h +++ b/src/raspberrypi/rasutil.h @@ -23,5 +23,7 @@ namespace ras_util string Banner(const string&); string ListDevices(const list&); + string GetExtensionLowerCase(const string&); + void FixCpu(int); } diff --git a/src/raspberrypi/scsi.h b/src/raspberrypi/scsi.h index e44e9825..a6e18717 100644 --- a/src/raspberrypi/scsi.h +++ b/src/raspberrypi/scsi.h @@ -44,6 +44,7 @@ namespace scsi_defs { // DaynaPort specific command eCmdRetrieveStats = 0x09, eCmdWrite6 = 0x0A, + eCmdPrint = 0x0A, eCmdSeek6 = 0x0B, // DaynaPort specific command eCmdSetIfaceMode = 0x0C, @@ -58,9 +59,10 @@ namespace scsi_defs { eCmdRelease6 = 0x17, eCmdModeSense6 = 0x1A, eCmdStartStop = 0x1B, + eCmdStopPrint = 0x1B, eCmdSendDiag = 0x1D, eCmdRemoval = 0x1E, - // ICD specific command + // ICD specific command, evaluated by the controller eCmdIcd = 0x1F, eCmdReadCapacity10 = 0x25, eCmdRead10 = 0x28, @@ -74,8 +76,6 @@ namespace scsi_defs { eCmdReadToc = 0x43, eCmdGetEventStatusNotification = 0x4A, eCmdModeSelect10 = 0x55, - eCmdReserve10 = 0x56, - eCmdRelease10 = 0x57, eCmdModeSense10 = 0x5A, eCmdRead16 = 0x88, eCmdWrite16 = 0x8A, @@ -89,31 +89,17 @@ namespace scsi_defs { enum class status : int { GOOD = 0x00, CHECK_CONDITION = 0x02, - CONDITION_MET = 0x04, - BUSY = 0x08, - INTERMEDIATE = 0x10, - INTERMEDIATE_CONDITION_MET = 0x14, - RESERVATION_CONFLICT = 0x18, - COMMAND_TERMINATED = 0x22, - QUEUE_FULL = 0x28 + RESERVATION_CONFLICT = 0x18 }; enum class sense_key : int { NO_SENSE = 0x00, - RECOVERED_ERROR = 0x01, NOT_READY = 0x02, MEDIUM_ERROR = 0x03, - HARDWARE_ERROR = 0x04, ILLEGAL_REQUEST = 0x05, UNIT_ATTENTION = 0x06, DATA_PROTECT = 0x07, - BLANK_CHECK = 0x08, - VENDOR_SPECIFIC = 0x09, - COPY_ABORTED = 0x0a, - ABORTED_COMMAND = 0x0b, - VOLUME_OVERFLOW = 0x0d, - MISCOMPARE = 0x0e, - COMPLETED = 0x0f + ABORTED_COMMAND = 0x0b }; enum class asc : int { diff --git a/src/raspberrypi/scsimon.cpp b/src/raspberrypi/scsimon.cpp index c5b55c95..d24f1436 100644 --- a/src/raspberrypi/scsimon.cpp +++ b/src/raspberrypi/scsimon.cpp @@ -12,7 +12,6 @@ #include "log.h" #include "hal/gpiobus.h" #include "rascsi_version.h" -#include "spdlog/spdlog.h" #include #include #include diff --git a/src/raspberrypi/test/abstract_controller_test.cpp b/src/raspberrypi/test/abstract_controller_test.cpp index 9618db63..8218c30f 100644 --- a/src/raspberrypi/test/abstract_controller_test.cpp +++ b/src/raspberrypi/test/abstract_controller_test.cpp @@ -14,6 +14,25 @@ using namespace scsi_defs; +TEST(AbstractControllerTest, AllocateCmd) +{ + MockAbstractController controller(0); + + EXPECT_EQ(16, controller.GetCmd().size()); + controller.AllocateCmd(1234); + EXPECT_EQ(1234, controller.GetCmd().size()); +} + +TEST(AbstractControllerTest, AllocateBuffer) +{ + MockAbstractController controller(0); + + controller.AllocateBuffer(1); + EXPECT_LE(1, controller.GetBuffer().size()); + controller.AllocateBuffer(10000); + EXPECT_LE(10000, controller.GetBuffer().size()); +} + TEST(AbstractControllerTest, Reset) { MockAbstractController controller(0); @@ -23,19 +42,35 @@ TEST(AbstractControllerTest, Reset) controller.SetPhase(BUS::phase_t::status); EXPECT_EQ(BUS::phase_t::status, controller.GetPhase()); - EXPECT_CALL(*device, Reset()).Times(1); controller.Reset(); EXPECT_TRUE(controller.IsBusFree()); EXPECT_EQ(status::GOOD, controller.GetStatus()); EXPECT_EQ(0, controller.GetLength()); } -TEST(AbstractControllerTest, SetGetStatus) +TEST(AbstractControllerTest, ByteTransfer) { MockAbstractController controller(0); - controller.SetStatus(status::BUSY); - EXPECT_EQ(status::BUSY, controller.GetStatus()); + controller.SetByteTransfer(false); + EXPECT_FALSE(controller.IsByteTransfer()); + controller.SetByteTransfer(true); + EXPECT_TRUE(controller.IsByteTransfer()); +} + +TEST(AbstractControllerTest, GetMaxLuns) +{ + MockAbstractController controller(0); + + EXPECT_EQ(32, controller.GetMaxLuns()); +} + +TEST(AbstractControllerTest, Status) +{ + MockAbstractController controller(0); + + controller.SetStatus(status::RESERVATION_CONFLICT); + EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); } TEST(AbstractControllerTest, ProcessPhase) @@ -43,35 +78,35 @@ TEST(AbstractControllerTest, ProcessPhase) MockAbstractController controller(0); controller.SetPhase(BUS::phase_t::selection); - EXPECT_CALL(controller, Selection()).Times(1); + EXPECT_CALL(controller, Selection()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::busfree); - EXPECT_CALL(controller, BusFree()).Times(1); + EXPECT_CALL(controller, BusFree()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::datain); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::dataout); - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::command); - EXPECT_CALL(controller, Command()).Times(1); + EXPECT_CALL(controller, Command()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::status); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::msgin); - EXPECT_CALL(controller, MsgIn()).Times(1); + EXPECT_CALL(controller, MsgIn()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::msgout); - EXPECT_CALL(controller, MsgOut()).Times(1); + EXPECT_CALL(controller, MsgOut()); controller.ProcessPhase(); controller.SetPhase(BUS::phase_t::reselection); @@ -101,7 +136,7 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle) EXPECT_FALSE(controller.HasDeviceForLun(0)); EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN)); EXPECT_EQ(nullptr, controller.GetDeviceForLun(0)); - EXPECT_TRUE(controller.DeleteDevice(device1)); + EXPECT_TRUE(controller.RemoveDevice(device1)); EXPECT_EQ(0, controller.GetLunCount()); } @@ -121,7 +156,7 @@ TEST(AbstractControllerTest, GetOpcode) { MockAbstractController controller(0); - vector& cmd = controller.InitCmd(1); + vector& cmd = controller.GetCmd(); cmd[0] = 0x12; EXPECT_EQ(0x12, (int)controller.GetOpcode()); @@ -133,13 +168,24 @@ TEST(AbstractControllerTest, GetLun) MockAbstractController controller(0); - vector& cmd = controller.InitCmd(2); + vector& cmd = controller.GetCmd(); cmd[1] = LUN << 5; EXPECT_EQ(LUN, controller.GetLun()); } -TEST(AbstractControllerTest, Length) +TEST(AbstractControllerTest, SetLength) +{ + MockAbstractController controller(0); + + EXPECT_FALSE(controller.HasValidLength()); + + controller.SetLength(1); + EXPECT_EQ(1, controller.GetLength()); + EXPECT_TRUE(controller.HasValidLength()); +} + +TEST(AbstractControllerTest, UpdateOffsetAndLength) { MockAbstractController controller(0); diff --git a/src/raspberrypi/test/command_context_test.cpp b/src/raspberrypi/test/command_context_test.cpp index edfa0576..ec798481 100644 --- a/src/raspberrypi/test/command_context_test.cpp +++ b/src/raspberrypi/test/command_context_test.cpp @@ -7,7 +7,7 @@ // //--------------------------------------------------------------------------- -#include +#include #include "rascsi/command_context.h" diff --git a/src/raspberrypi/test/controller_manager_test.cpp b/src/raspberrypi/test/controller_manager_test.cpp index 4a8eebc3..e64b31a5 100644 --- a/src/raspberrypi/test/controller_manager_test.cpp +++ b/src/raspberrypi/test/controller_manager_test.cpp @@ -14,17 +14,17 @@ TEST(ControllerManagerTest, LifeCycle) { const int ID = 4; - const int LUN1 = 2; + const int LUN1 = 0; const int LUN2 = 3; MockBus bus; ControllerManager controller_manager(bus); DeviceFactory device_factory; - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, -1, "services"); + auto device = device_factory.CreateDevice(controller_manager, SCHS, -1, ""); EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device)); - device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); + device = device_factory.CreateDevice(controller_manager, SCHS, LUN1, ""); EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device)); auto controller = controller_manager.FindController(ID); EXPECT_NE(nullptr, controller); @@ -35,7 +35,7 @@ TEST(ControllerManagerTest, LifeCycle) EXPECT_NE(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1)); EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(0, 0)); - device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services"); + device = device_factory.CreateDevice(controller_manager, SCHS, LUN2, ""); EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device)); controller = controller_manager.FindController(ID); EXPECT_TRUE(controller_manager.DeleteController(controller)); @@ -46,6 +46,25 @@ TEST(ControllerManagerTest, LifeCycle) EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1)); } +TEST(ControllerManagerTest, AttachToScsiController) +{ + const int ID = 4; + const int LUN1 = 3; + const int LUN2 = 0; + + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + + auto device1 = device_factory.CreateDevice(controller_manager, SCHS, LUN1, ""); + EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device1)) << "LUN 0 is missing"; + + auto device2 = device_factory.CreateDevice(controller_manager, SCLP, LUN2, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2)); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1)); + EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device1)); +} + TEST(ControllerManagerTest, ResetAllControllers) { const int ID = 2; @@ -54,12 +73,12 @@ TEST(ControllerManagerTest, ResetAllControllers) ControllerManager controller_manager(bus); DeviceFactory device_factory; - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); + auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, ""); EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device)); auto controller = controller_manager.FindController(ID); EXPECT_NE(nullptr, controller); - controller->SetStatus(status::BUSY); + controller->SetStatus(status::RESERVATION_CONFLICT); controller_manager.ResetAllControllers(); EXPECT_EQ(status::GOOD, controller->GetStatus()); } diff --git a/src/raspberrypi/test/device_factory_test.cpp b/src/raspberrypi/test/device_factory_test.cpp index b45b2d5c..877b64c1 100644 --- a/src/raspberrypi/test/device_factory_test.cpp +++ b/src/raspberrypi/test/device_factory_test.cpp @@ -43,8 +43,6 @@ TEST(DeviceFactoryTest, GetSectorSizes) DeviceFactory device_factory; unordered_set sector_sizes; - sector_sizes = device_factory.GetSectorSizes("SCHD"); - EXPECT_EQ(4, sector_sizes.size()); sector_sizes = device_factory.GetSectorSizes(SCHD); EXPECT_EQ(4, sector_sizes.size()); @@ -53,8 +51,6 @@ TEST(DeviceFactoryTest, GetSectorSizes) EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); - sector_sizes = device_factory.GetSectorSizes("SCRM"); - EXPECT_EQ(4, sector_sizes.size()); sector_sizes = device_factory.GetSectorSizes(SCRM); EXPECT_EQ(4, sector_sizes.size()); @@ -63,8 +59,6 @@ TEST(DeviceFactoryTest, GetSectorSizes) EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); - sector_sizes = device_factory.GetSectorSizes("SCMO"); - EXPECT_EQ(4, sector_sizes.size()); sector_sizes = device_factory.GetSectorSizes(SCMO); EXPECT_EQ(4, sector_sizes.size()); @@ -73,8 +67,6 @@ TEST(DeviceFactoryTest, GetSectorSizes) EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end()); EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end()); - sector_sizes = device_factory.GetSectorSizes("SCCD"); - EXPECT_EQ(2, sector_sizes.size()); sector_sizes = device_factory.GetSectorSizes(SCCD); EXPECT_EQ(2, sector_sizes.size()); @@ -125,7 +117,7 @@ TEST(DeviceFactoryTest, GetDefaultParams) EXPECT_EQ(2, params.size()); params = device_factory.GetDefaultParams(SCLP); - EXPECT_EQ(2, params.size()); + EXPECT_EQ(1, params.size()); } TEST(DeviceFactoryTest, UnknownDeviceType) @@ -152,7 +144,7 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hda"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHD", device->GetType()); + EXPECT_EQ(SCHD, device->GetType()); EXPECT_TRUE(device->SupportsFile()); EXPECT_FALSE(device->SupportsParams()); EXPECT_TRUE(device->IsProtectable()); @@ -172,18 +164,18 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults) device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hds"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHD", device->GetType()); + EXPECT_EQ(SCHD, device->GetType()); device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hdi"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHD", device->GetType()); + EXPECT_EQ(SCHD, device->GetType()); device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.nhd"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHD", device->GetType()); + EXPECT_EQ(SCHD, device->GetType()); } -void TestRemovableDrive(const string& type, const string& filename, const string& product) +void TestRemovableDrive(PbDeviceType type, const string& filename, const string& product) { MockBus bus; DeviceFactory device_factory; @@ -213,12 +205,12 @@ void TestRemovableDrive(const string& type, const string& filename, const string TEST(DeviceFactoryTest, SCRM_Device_Defaults) { - TestRemovableDrive("SCRM", "test.hdr", "SCSI HD (REM.)"); + TestRemovableDrive(SCRM, "test.hdr", "SCSI HD (REM.)"); } TEST(DeviceFactoryTest, SCMO_Device_Defaults) { - TestRemovableDrive("SCMO", "test.mos", "SCSI MO"); + TestRemovableDrive(SCMO, "test.mos", "SCSI MO"); } TEST(DeviceFactoryTest, SCCD_Device_Defaults) @@ -229,7 +221,7 @@ TEST(DeviceFactoryTest, SCCD_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.iso"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCCD", device->GetType()); + EXPECT_EQ(SCCD, device->GetType()); EXPECT_TRUE(device->SupportsFile()); EXPECT_FALSE(device->SupportsParams()); EXPECT_FALSE(device->IsProtectable()); @@ -256,7 +248,7 @@ TEST(DeviceFactoryTest, SCBR_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "bridge"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCBR", device->GetType()); + EXPECT_EQ(SCBR, device->GetType()); EXPECT_FALSE(device->SupportsFile()); EXPECT_TRUE(device->SupportsParams()); EXPECT_FALSE(device->IsProtectable()); @@ -283,7 +275,7 @@ TEST(DeviceFactoryTest, SCDP_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "daynaport"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCDP", device->GetType()); + EXPECT_EQ(SCDP, device->GetType()); EXPECT_FALSE(device->SupportsFile()); EXPECT_TRUE(device->SupportsParams()); EXPECT_FALSE(device->IsProtectable()); @@ -309,7 +301,7 @@ TEST(DeviceFactoryTest, SCHS_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCHS", device->GetType()); + EXPECT_EQ(SCHS, device->GetType()); EXPECT_FALSE(device->SupportsFile()); EXPECT_FALSE(device->SupportsParams()); EXPECT_FALSE(device->IsProtectable()); @@ -336,7 +328,7 @@ TEST(DeviceFactoryTest, SCLP_Device_Defaults) auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "printer"); EXPECT_NE(nullptr, device); - EXPECT_EQ("SCLP", device->GetType()); + EXPECT_EQ(SCLP, device->GetType()); EXPECT_FALSE(device->SupportsFile()); EXPECT_TRUE(device->SupportsParams()); EXPECT_FALSE(device->IsProtectable()); diff --git a/src/raspberrypi/test/device_test.cpp b/src/raspberrypi/test/device_test.cpp index ab0d7c20..5bb121dd 100644 --- a/src/raspberrypi/test/device_test.cpp +++ b/src/raspberrypi/test/device_test.cpp @@ -17,51 +17,131 @@ TEST(DeviceTest, Properties) MockDevice device(LUN); - EXPECT_FALSE(device.IsProtectable()); - device.SetProtectable(true); - EXPECT_TRUE(device.IsProtectable()); + EXPECT_FALSE(device.IsReady()) << "Wrong default value"; + device.SetReady(true); + EXPECT_TRUE(device.IsReady()); + device.SetReady(false); + EXPECT_FALSE(device.IsReady()); - EXPECT_FALSE(device.IsProtected()); - device.SetProtected(true); - EXPECT_TRUE(device.IsProtected()); + EXPECT_FALSE(device.IsReset()) << "Wrong default value"; + device.SetReset(true); + EXPECT_TRUE(device.IsReset()); + device.SetReset(false); + EXPECT_FALSE(device.IsReset()); - EXPECT_FALSE(device.IsReadOnly()); + EXPECT_FALSE(device.IsAttn()) << "Wrong default value"; + device.SetAttn(true); + EXPECT_TRUE(device.IsAttn()); + device.SetAttn(false); + EXPECT_FALSE(device.IsAttn()); + + EXPECT_FALSE(device.IsReadOnly()) << "Wrong default value"; device.SetReadOnly(true); EXPECT_TRUE(device.IsReadOnly()); + device.SetReadOnly(false); + EXPECT_FALSE(device.IsReadOnly()); - EXPECT_FALSE(device.IsStoppable()); + EXPECT_FALSE(device.IsProtectable()) << "Wrong default value"; + device.SetProtectable(true); + EXPECT_TRUE(device.IsProtectable()); + device.SetProtectable(false); + EXPECT_FALSE(device.IsProtectable()); + + EXPECT_FALSE(device.IsProtected()) << "Wrong default value"; + device.SetProtected(true); + EXPECT_FALSE(device.IsProtected()); + device.SetProtectable(true); + device.SetProtected(true); + EXPECT_TRUE(device.IsProtected()); + device.SetProtected(false); + EXPECT_FALSE(device.IsProtected()); + + device.SetProtectable(false); + device.SetReadOnly(true); + device.SetProtected(true); + EXPECT_FALSE(device.IsProtected()) << "Read-only or not protectable devices cannot be protected"; + device.SetReadOnly(false); + device.SetProtected(true); + EXPECT_FALSE(device.IsProtected()) << "Read-only or not protectable devices cannot be protected"; + + EXPECT_FALSE(device.IsStoppable()) << "Wrong default value"; device.SetStoppable(true); EXPECT_TRUE(device.IsStoppable()); + device.SetStoppable(false); + EXPECT_FALSE(device.IsStoppable()); - EXPECT_FALSE(device.IsStopped()); + EXPECT_FALSE(device.IsStopped()) << "Wrong default value"; device.SetStopped(true); EXPECT_TRUE(device.IsStopped()); + device.SetStopped(false); + EXPECT_FALSE(device.IsStopped()); - EXPECT_FALSE(device.IsRemovable()); + EXPECT_FALSE(device.IsRemovable()) << "Wrong default value"; device.SetRemovable(true); EXPECT_TRUE(device.IsRemovable()); + device.SetRemovable(false); + EXPECT_FALSE(device.IsRemovable()); - EXPECT_FALSE(device.IsRemoved()); + EXPECT_FALSE(device.IsRemoved()) << "Wrong default value"; device.SetRemoved(true); EXPECT_TRUE(device.IsRemoved()); + device.SetRemoved(false); + EXPECT_FALSE(device.IsRemoved()); - EXPECT_FALSE(device.IsLockable()); + EXPECT_FALSE(device.IsLockable()) << "Wrong default value"; device.SetLockable(true); EXPECT_TRUE(device.IsLockable()); + device.SetLockable(false); + EXPECT_FALSE(device.IsLockable()); - EXPECT_FALSE(device.IsLocked()); + EXPECT_FALSE(device.IsLocked()) << "Wrong default value"; device.SetLocked(true); EXPECT_TRUE(device.IsLocked()); + device.SetLocked(false); + EXPECT_FALSE(device.IsLocked()); - EXPECT_FALSE(device.SupportsParams()); - EXPECT_TRUE(device.SupportsFile()); + EXPECT_FALSE(device.SupportsParams()) << "Wrong default value"; device.SupportsParams(true); EXPECT_TRUE(device.SupportsParams()); + device.SupportsParams(false); + EXPECT_FALSE(device.SupportsParams()); + + EXPECT_FALSE(device.SupportsFile()) << "Wrong default value"; + device.SupportsFile(true); + EXPECT_TRUE(device.SupportsFile()); + device.SupportsFile(false); EXPECT_FALSE(device.SupportsFile()); EXPECT_EQ(LUN, device.GetLun()); } +TEST(DeviceTest, GetTypeString) +{ + MockDevice schd(SCHD); + EXPECT_STREQ("SCHD", schd.GetTypeString()); + + MockDevice scrm(SCRM); + EXPECT_STREQ("SCRM", scrm.GetTypeString()); + + MockDevice scmo(SCMO); + EXPECT_STREQ("SCMO", scmo.GetTypeString()); + + MockDevice sccd(SCCD); + EXPECT_STREQ("SCCD", sccd.GetTypeString()); + + MockDevice schs(SCHS); + EXPECT_STREQ("SCHS", schs.GetTypeString()); + + MockDevice scbr(SCBR); + EXPECT_STREQ("SCBR", scbr.GetTypeString()); + + MockDevice scdp(SCDP); + EXPECT_STREQ("SCDP", scdp.GetTypeString()); + + MockDevice sclp(SCLP); + EXPECT_STREQ("SCLP", sclp.GetTypeString()); +} + TEST(DeviceTest, Vendor) { MockDevice device(0); @@ -81,7 +161,7 @@ TEST(DeviceTest, Product) device.SetProduct("1234567890123456"); EXPECT_EQ("1234567890123456", device.GetProduct()); device.SetProduct("xyz", false); - EXPECT_EQ("1234567890123456", device.GetProduct()) << "Changing vital product data is not SCSI complient"; + EXPECT_EQ("1234567890123456", device.GetProduct()) << "Changing vital product data is not SCSI compliant"; } TEST(DeviceTest, Revision) @@ -179,11 +259,16 @@ TEST(DeviceTest, Eject) device.SetReady(false); device.SetRemovable(false); EXPECT_FALSE(device.Eject(false)); + device.SetReady(true); EXPECT_FALSE(device.Eject(false)); + device.SetRemovable(true); device.SetLocked(true); EXPECT_FALSE(device.Eject(false)); + EXPECT_TRUE(device.Eject(true)); + + device.SetReady(true); device.SetLocked(false); EXPECT_TRUE(device.Eject(false)); EXPECT_FALSE(device.IsReady()); diff --git a/src/raspberrypi/test/disk_test.cpp b/src/raspberrypi/test/disk_test.cpp index 92c04d16..c22ec752 100644 --- a/src/raspberrypi/test/disk_test.cpp +++ b/src/raspberrypi/test/disk_test.cpp @@ -10,9 +10,11 @@ #include "mocks.h" #include "scsi.h" #include "devices/disk.h" +#include "devices/scsi_command_util.h" #include "rascsi_exceptions.h" using namespace scsi_defs; +using namespace scsi_command_util; TEST(DiskTest, Dispatch) { @@ -22,6 +24,9 @@ TEST(DiskTest, Dispatch) controller.AddDevice(disk); + EXPECT_FALSE(disk->Dispatch(scsi_command::eCmdIcd)); + + disk->SetRemovable(true); disk->MediumChanged(); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception); } @@ -38,7 +43,7 @@ TEST(DiskTest, Rezero) disk->SetReady(true); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } @@ -50,13 +55,13 @@ TEST(DiskTest, FormatUnit) controller.AddDevice(disk); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdFormat), scsi_exception); disk->SetReady(true); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdFormat)); EXPECT_EQ(status::GOOD, controller.GetStatus()); @@ -77,43 +82,57 @@ TEST(DiskTest, ReassignBlocks) disk->SetReady(true); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReassign)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } -TEST(DiskTest, Seek) +TEST(DiskTest, Seek6) { MockAbstractController controller(0); auto disk = make_shared(); controller.AddDevice(disk); - vector& cmd = controller.InitCmd(10); + vector& cmd = controller.GetCmd(); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_exception) << "SEEK(6) must fail for a medium with 0 blocks"; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception) - << "SEEK(10) must fail for a medium with 0 blocks"; disk->SetBlockCount(1); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_exception) << "SEEK(6) must fail because drive is not ready"; + + disk->SetReady(true); + + // Block count + cmd[4] = 1; + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); +} + +TEST(DiskTest, Seek10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception) + << "SEEK(10) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception) << "SEEK(10) must fail because drive is not ready"; disk->SetReady(true); - // Block count for SEEK(6) - cmd[4] = 1; - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6)); - EXPECT_EQ(status::GOOD, controller.GetStatus()); - cmd[4] = 0; - - // Block count for SEEK(10) + // Block count cmd[5] = 1; - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } @@ -125,7 +144,7 @@ TEST(DiskTest, ReadCapacity) controller.AddDevice(disk); - vector& cmd = controller.InitCmd(16); + vector& cmd = controller.GetCmd(); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception) << "Neithed READ CAPACITY(16) nor READ LONG(16)"; @@ -148,7 +167,7 @@ TEST(DiskTest, ReadCapacity) cmd[1] = 0x00; disk->SetBlockCount(0x12345678); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10)); EXPECT_EQ(0x12, controller.GetBuffer()[0]); EXPECT_EQ(0x34, controller.GetBuffer()[1]); @@ -156,7 +175,7 @@ TEST(DiskTest, ReadCapacity) EXPECT_EQ(0x77, controller.GetBuffer()[3]); disk->SetBlockCount(0x1234567887654321); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10)); EXPECT_EQ(0xff, controller.GetBuffer()[0]); EXPECT_EQ(0xff, controller.GetBuffer()[1]); @@ -166,7 +185,7 @@ TEST(DiskTest, ReadCapacity) disk->SetSectorSizeInBytes(1024); // READ CAPACITY(16), not READ LONG(16) cmd[1] = 0x10; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); EXPECT_EQ(0x12, controller.GetBuffer()[0]); EXPECT_EQ(0x34, controller.GetBuffer()[1]); @@ -182,111 +201,282 @@ TEST(DiskTest, ReadCapacity) EXPECT_EQ(0x00, controller.GetBuffer()[11]); } -TEST(DiskTest, ReadWriteLong) +TEST(DiskTest, Read6) { MockAbstractController controller(0); auto disk = make_shared(); controller.AddDevice(disk); - vector& cmd = controller.InitCmd(16); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead6), scsi_exception) + << "READ(6) must fail for a medium with 0 blocks"; - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10)); + // Further testing requires filesystem access +} + +TEST(DiskTest, Read10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead10), scsi_exception) + << "READ(10) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRead10)); EXPECT_EQ(status::GOOD, controller.GetStatus()); - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10)); + + // Further testing requires filesystem access +} + +TEST(DiskTest, Read16) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead16), scsi_exception) + << "READ(16) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRead16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + // Further testing requires filesystem access +} + +TEST(DiskTest, Write6) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite6), scsi_exception) + << "WRIte(6) must fail for a medium with 0 blocks"; + + // Further testing requires filesystem access +} + +TEST(DiskTest, Write10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite10), scsi_exception) + << "WRITE(10) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + // Further testing requires filesystem access +} + +TEST(DiskTest, Write16) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite16), scsi_exception) + << "WRITE(16) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + // Further testing requires filesystem access +} + +TEST(DiskTest, Verify10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdVerify10), scsi_exception) + << "VERIFY(10) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + // Further testing requires filesystem access +} + +TEST(DiskTest, Verify16) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdVerify16), scsi_exception) + << "VERIFY(16) must fail for a medium with 0 blocks"; + + disk->SetBlockCount(1); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite16)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + // Further testing requires filesystem access +} + +TEST(DiskTest, ReadLong10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10)); EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[2] = 1; EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_exception) << "READ LONG(10) must fail because the capacity is exceeded"; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception) - << "WRITE LONG(10) must fail because the capacity is exceeded"; - // READ LONG(16), not READ CAPACITY(16) - cmd[1] = 0x11; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception) - << "READ LONG(16) must fail because the capacity is exceeded"; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception) - << "WRITE LONG(16) must fail because the capacity is exceeded"; cmd[2] = 0; cmd[7] = 1; EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_exception) << "READ LONG(10) must fail because it currently only supports 0 bytes transfer length"; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception) - << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; - cmd[7] = 0; +} + +TEST(DiskTest, ReadLong16) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); // READ LONG(16), not READ CAPACITY(16) cmd[1] = 0x11; - EXPECT_CALL(controller, Status()).Times(1); + cmd[2] = 1; + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception) + << "READ LONG(16) must fail because the capacity is exceeded"; + cmd[2] = 0; + + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16)); EXPECT_EQ(status::GOOD, controller.GetStatus()); - cmd[1] = 0x00; - EXPECT_CALL(controller, Status()).Times(1); + + cmd[13] = 1; + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception) + << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; +} + +TEST(DiskTest, WriteLong10) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + cmd[2] = 1; + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception) + << "WRITE LONG(10) must fail because the capacity is exceeded"; + cmd[2] = 0; + + cmd[7] = 1; + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception) + << "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length"; +} + +TEST(DiskTest, WriteLong16) +{ + MockAbstractController controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + cmd[2] = 1; + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception) + << "WRITE LONG(16) must fail because the capacity is exceeded"; + cmd[2] = 0; + + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong16)); EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[13] = 1; - // READ LONG(16), not READ CAPACITY(16) - cmd[1] = 0x11; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception) - << "READ LONG(16) must fail because it currently only supports 0 bytes transfer length"; - cmd[1] = 0x00; EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception) << "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length"; } -TEST(DiskTest, Reserve) +TEST(DiskTest, StartStopUnit) { MockAbstractController controller(0); auto disk = make_shared(); + disk->SetRemovable(true); controller.AddDevice(disk); - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReserve6)); + vector& cmd = controller.GetCmd(); + + // Stop/Unload + disk->SetReady(true); + EXPECT_CALL(controller, Status()); + EXPECT_CALL(*disk, FlushCache); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop)); EXPECT_EQ(status::GOOD, controller.GetStatus()); -} + EXPECT_TRUE(disk->IsStopped()); -TEST(DiskTest, Release) -{ - MockAbstractController controller(0); - auto disk = make_shared(); - - controller.AddDevice(disk); - - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRelease6)); - EXPECT_EQ(status::GOOD, controller.GetStatus()); -} - -TEST(DiskTest, SendDiagnostic) -{ - MockAbstractController controller(0); - auto disk = make_shared(); - - controller.AddDevice(disk); - - vector& cmd = controller.InitCmd(6); - - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSendDiag)); + // Stop/Load + cmd[4] = 0x02; + disk->SetReady(true); + disk->SetLocked(false); + EXPECT_CALL(controller, Status()); + EXPECT_CALL(*disk, FlushCache); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop)); EXPECT_EQ(status::GOOD, controller.GetStatus()); - cmd[1] = 0x10; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) - << "SEND DIAGNOSTIC must fail because PF bit is not supported"; - cmd[1] = 0; + disk->SetReady(false); + EXPECT_CALL(*disk, FlushCache).Times(0); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception); - cmd[3] = 1; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) - << "SEND DIAGNOSTIC must fail because parameter list is not supported"; - cmd[3] = 0; - cmd[4] = 1; - EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) - << "SEND DIAGNOSTIC must fail because parameter list is not supported"; + disk->SetReady(true); + disk->SetLocked(true); + EXPECT_CALL(*disk, FlushCache).Times(0); + EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception); + + // Start/Unload + cmd[4] = 0x01; + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_FALSE(disk->IsStopped()); + + // Start/Load + cmd[4] = 0x03; + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(DiskTest, PreventAllowMediumRemoval) @@ -296,25 +486,212 @@ TEST(DiskTest, PreventAllowMediumRemoval) controller.AddDevice(disk); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRemoval), scsi_exception) << "REMOVAL must fail because drive is not ready"; disk->SetReady(true); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval)); EXPECT_EQ(status::GOOD, controller.GetStatus()); EXPECT_FALSE(disk->IsLocked()); cmd[4] = 1; - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval)); EXPECT_EQ(status::GOOD, controller.GetStatus()); EXPECT_TRUE(disk->IsLocked()); } +TEST(DiskTest, Eject) +{ + MockDisk disk; + + disk.SetReady(false); + disk.SetRemovable(false); + disk.SetLocked(false); + EXPECT_CALL(disk, FlushCache).Times(0); + EXPECT_FALSE(disk.Eject(false)); + + disk.SetRemovable(true); + EXPECT_CALL(disk, FlushCache).Times(0); + EXPECT_FALSE(disk.Eject(false)); + + disk.SetReady(true); + disk.SetLocked(true); + EXPECT_CALL(disk, FlushCache).Times(0); + EXPECT_FALSE(disk.Eject(false)); + + disk.SetReady(true); + disk.SetLocked(false); + EXPECT_CALL(disk, FlushCache); + EXPECT_TRUE(disk.Eject(false)); + + disk.SetReady(true); + EXPECT_CALL(disk, FlushCache); + EXPECT_TRUE(disk.Eject(true)); +} + +void DiskTest_ValidateFormatPage(AbstractController& controller, int offset) +{ + const auto& buf = controller.GetBuffer(); + EXPECT_EQ(0x08, buf[offset + 3]) << "Wrong number of trackes in one zone"; + EXPECT_EQ(25, GetInt16(buf, offset + 10)) << "Wrong number of sectors per track"; + EXPECT_EQ(1024, GetInt16(buf, offset + 12)) << "Wrong number of bytes per sector"; + EXPECT_EQ(1, GetInt16(buf, offset + 14)) << "Wrong interleave"; + EXPECT_EQ(11, GetInt16(buf, offset + 16)) << "Wrong track skew factor"; + EXPECT_EQ(20, GetInt16(buf, offset + 18)) << "Wrong cylinder skew factor"; + EXPECT_FALSE(buf[offset + 20] & 0x20) << "Wrong removable flag"; + EXPECT_TRUE(buf[offset + 20] & 0x40) << "Wrong hard-sectored flag"; +} + +void DiskTest_ValidateDrivePage(AbstractController& controller, int offset) +{ + const auto& buf = controller.GetBuffer(); + EXPECT_EQ(0x17, buf[offset + 2]); + EXPECT_EQ(0x4d3b, GetInt16(buf, offset + 3)); + EXPECT_EQ(8, buf[offset + 5]) << "Wrong number of heads"; + EXPECT_EQ(7200, GetInt16(buf, offset + 20)) << "Wrong medium rotation rate"; +} + +void DiskTest_ValidateCachePage(AbstractController& controller, int offset) +{ + const auto& buf = controller.GetBuffer(); + EXPECT_EQ(0xffff, GetInt16(buf, offset + 4)) << "Wrong pre-fetch transfer length"; + EXPECT_EQ(0xffff, GetInt16(buf, offset + 8)) << "Wrong maximum pre-fetch"; + EXPECT_EQ(0xffff, GetInt16(buf, offset + 10)) << "Wrong maximum pre-fetch ceiling"; +} + +TEST(DiskTest, ModeSense6) +{ + NiceMock controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + // Drive must be ready on order to return all data + disk->SetReady(true); + + cmd[2] = 0x3f; + // ALLOCATION LENGTH + cmd[4] = 255; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + EXPECT_EQ(0x08, controller.GetBuffer()[3]) << "Wrong block descriptor length"; + + // No block descriptor + cmd[1] = 0x08; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + EXPECT_EQ(0x00, controller.GetBuffer()[2]) << "Wrong device-specific parameter"; + + disk->SetReadOnly(false); + disk->SetProtectable(true); + disk->SetProtected(true); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + const auto& buf = controller.GetBuffer(); + EXPECT_EQ(0x80, buf[2]) << "Wrong device-specific parameter"; + + // Return block descriptor + cmd[1] = 0x00; + + // Format page + cmd[2] = 3; + disk->SetSectorSizeInBytes(1024); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + DiskTest_ValidateFormatPage(controller, 12); + + // Rigid disk drive page + cmd[2] = 4; + disk->SetBlockCount(0x12345678); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + DiskTest_ValidateDrivePage(controller, 12); + + // Cache page + cmd[2] = 8; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6)); + DiskTest_ValidateCachePage(controller, 12); +} + +TEST(DiskTest, ModeSense10) +{ + NiceMock controller(0); + auto disk = make_shared(); + + controller.AddDevice(disk); + + vector& cmd = controller.GetCmd(); + + // Drive must be ready on order to return all data + disk->SetReady(true); + + cmd[2] = 0x3f; + // ALLOCATION LENGTH + cmd[8] = 255; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + EXPECT_EQ(0x08, controller.GetBuffer()[7]) << "Wrong block descriptor length"; + + // No block descriptor + cmd[1] = 0x08; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + auto& buf = controller.GetBuffer(); + EXPECT_EQ(0x00, controller.GetBuffer()[3]) << "Wrong device-specific parameter"; + + disk->SetReadOnly(false); + disk->SetProtectable(true); + disk->SetProtected(true); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + buf = controller.GetBuffer(); + EXPECT_EQ(0x80, buf[3]) << "Wrong device-specific parameter"; + + // Return short block descriptor + cmd[1] = 0x00; + disk->SetBlockCount(0x1234); + disk->SetSectorSizeInBytes(1024); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + buf = controller.GetBuffer(); + EXPECT_EQ(0x00, buf[4]) << "Wrong LONGLBA field"; + EXPECT_EQ(0x08, buf[7]) << "Wrong block descriptor length"; + EXPECT_EQ(0x00, GetInt16(buf, 8)); + EXPECT_EQ(0x1234, GetInt16(buf, 10)); + EXPECT_EQ(0x00, GetInt16(buf, 12)); + EXPECT_EQ(1024, GetInt16(buf, 14)); + + // Return long block descriptor + cmd[1] = 0x10; + disk->SetBlockCount((uint64_t)0xffffffff + 1); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + buf = controller.GetBuffer(); + EXPECT_EQ(0x01, buf[4]) << "Wrong LONGLBA field"; + EXPECT_EQ(0x10, buf[7]) << "Wrong block descriptor length"; + EXPECT_EQ(0x00, GetInt16(buf, 8)); + EXPECT_EQ(0x01, GetInt16(buf, 10)); + EXPECT_EQ(0x00, GetInt16(buf, 12)); + EXPECT_EQ(0x00, GetInt16(buf, 14)); + EXPECT_EQ(0x00, GetInt16(buf, 20)); + EXPECT_EQ(1024, GetInt16(buf, 22)); + cmd[1] = 0x00; + + // Format page + cmd[2] = 3; + disk->SetSectorSizeInBytes(1024); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + DiskTest_ValidateFormatPage(controller, 16); + + // Rigid disk drive page + cmd[2] = 4; + disk->SetBlockCount(0x12345678); + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + DiskTest_ValidateDrivePage(controller, 16); + + // Cache page + cmd[2] = 8; + EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10)); + DiskTest_ValidateCachePage(controller, 16); +} + TEST(DiskTest, SynchronizeCache) { MockAbstractController controller(0); @@ -322,13 +699,13 @@ TEST(DiskTest, SynchronizeCache) controller.AddDevice(disk); - EXPECT_CALL(*disk, FlushCache()).Times(1); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(*disk, FlushCache()); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache10)); EXPECT_EQ(status::GOOD, controller.GetStatus()); - EXPECT_CALL(*disk, FlushCache()).Times(1); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(*disk, FlushCache()); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } @@ -340,9 +717,7 @@ TEST(DiskTest, ReadDefectData) controller.AddDevice(disk); - controller.InitCmd(10); - - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } @@ -410,46 +785,3 @@ TEST(DiskTest, BlockCount) disk.SetBlockCount(0x1234567887654321); EXPECT_EQ(0x1234567887654321, disk.GetBlockCount()); } - -TEST(DiskTest, GetIdsForReservedFile) -{ - const int ID = 1; - const int LUN = 2; - - Filepath path; - path.SetPath("path"); - MockDisk disk; - - disk.SetPath(path); - Filepath result; - disk.GetPath(result); - EXPECT_STREQ("path", result.GetPath()); - - int id; - int lun; - EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun)); - - disk.ReserveFile(path, ID, LUN); - EXPECT_TRUE(Disk::GetIdsForReservedFile(path, id, lun)); - EXPECT_EQ(ID, id); - EXPECT_EQ(LUN, lun); - - disk.UnreserveFile(); - EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun)); -} - -TEST(DiskTest, UnreserveAll) -{ - const int ID = 2; - const int LUN = 31; - - Filepath path; - path.SetPath("path"); - MockDisk disk; - - disk.ReserveFile(path, ID, LUN); - Disk::UnreserveAll(); - int id; - int lun; - EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun)); -} diff --git a/src/raspberrypi/test/gpiobus_test.cpp b/src/raspberrypi/test/gpiobus_test.cpp new file mode 100644 index 00000000..7a48c9c0 --- /dev/null +++ b/src/raspberrypi/test/gpiobus_test.cpp @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include + +#include "hal/gpiobus.h" +#include + +TEST(GpioBusTest, GetCommandByteCount) +{ + unordered_set opcodes; + + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x88)); + opcodes.insert(0x88); + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x8a)); + opcodes.insert(0x8a); + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x8f)); + opcodes.insert(0x8f); + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x91)); + opcodes.insert(0x91); + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x9e)); + opcodes.insert(0x9e); + EXPECT_EQ(16, GPIOBUS::GetCommandByteCount(0x9f)); + opcodes.insert(0x9f); + EXPECT_EQ(12, GPIOBUS::GetCommandByteCount(0xa0)); + opcodes.insert(0xa0); + + // TODO Opcode 0x05 must be removed from gpiobus.cpp + EXPECT_EQ(10, GPIOBUS::GetCommandByteCount(0x05)); + opcodes.insert(0x05); + + for (int i = 0x20; i <= 0x7d; i++) { + EXPECT_EQ(10, GPIOBUS::GetCommandByteCount(i)); + opcodes.insert(i); + } + + for (int i = 0; i < 256; i++) { + if (opcodes.find(i) == opcodes.end()) { + EXPECT_EQ(6, GPIOBUS::GetCommandByteCount(i)); + } + } +} diff --git a/src/raspberrypi/test/host_services_test.cpp b/src/raspberrypi/test/host_services_test.cpp index b086d841..f7cd802f 100644 --- a/src/raspberrypi/test/host_services_test.cpp +++ b/src/raspberrypi/test/host_services_test.cpp @@ -14,20 +14,24 @@ using namespace std; +TEST(HostServicesTest, Dispatch) +{ + TestDispatch(SCHS); +} + TEST(HostServicesTest, TestUnitReady) { NiceMock controller(0); auto services = CreateDevice(SCHS, controller); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdTestUnitReady)) << "TEST UNIT READY must never fail"; EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(HostServicesTest, Inquiry) { - TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, scsi_level::SCSI_2, - "RaSCSI Host Services ", 0x1f, false); + TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, "RaSCSI Host Services ", 0x1f, false); } TEST(HostServicesTest, StartStopUnit) @@ -35,25 +39,25 @@ TEST(HostServicesTest, StartStopUnit) NiceMock controller(0); auto services = CreateDevice(SCHS, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // STOP - EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI)).Times(1); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI)); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop)); EXPECT_EQ(status::GOOD, controller.GetStatus()); // LOAD cmd[4] = 0x02; - EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI)).Times(1); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI)); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop)); EXPECT_EQ(status::GOOD, controller.GetStatus()); // UNLOAD cmd[4] = 0x03; - EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI)).Times(1); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI)); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop)); EXPECT_EQ(status::GOOD, controller.GetStatus()); @@ -67,7 +71,7 @@ TEST(HostServicesTest, ModeSense6) NiceMock controller(0); auto services = CreateDevice(SCHS, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); EXPECT_THROW(services->Dispatch(scsi_command::eCmdModeSense6), scsi_exception) << "Unsupported mode page was returned"; @@ -79,7 +83,7 @@ TEST(HostServicesTest, ModeSense6) cmd[1] = 0x08; // ALLOCATION LENGTH cmd[4] = 255; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense6)); vector& buffer = controller.GetBuffer(); // Major version 1 @@ -93,7 +97,7 @@ TEST(HostServicesTest, ModeSense6) // ALLOCATION LENGTH cmd[4] = 2; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense6)); buffer = controller.GetBuffer(); EXPECT_EQ(0x02, buffer[0]); @@ -104,7 +108,7 @@ TEST(HostServicesTest, ModeSense10) NiceMock controller(0); auto services = CreateDevice(SCHS, controller); - vector& cmd = controller.InitCmd(10); + vector& cmd = controller.GetCmd(); EXPECT_THROW(services->Dispatch(scsi_command::eCmdModeSense10), scsi_exception) << "Unsupported mode page was returned"; @@ -116,7 +120,7 @@ TEST(HostServicesTest, ModeSense10) cmd[1] = 0x08; // ALLOCATION LENGTH cmd[8] = 255; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense10)); vector& buffer = controller.GetBuffer(); // Major version 1 @@ -130,7 +134,7 @@ TEST(HostServicesTest, ModeSense10) // ALLOCATION LENGTH cmd[8] = 2; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense10)); buffer = controller.GetBuffer(); EXPECT_EQ(0x02, buffer[1]); @@ -141,9 +145,9 @@ TEST(HostServicesTest, SetUpModePages) MockBus bus; ControllerManager controller_manager(bus); MockHostServices services(0, controller_manager); - map> mode_pages; + map> pages; - services.SetUpModePages(mode_pages, 0x3f, false); - EXPECT_EQ(1, mode_pages.size()) << "Unexpected number of mode pages"; - EXPECT_EQ(10, mode_pages[32].size()); + services.SetUpModePages(pages, 0x3f, false); + EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages"; + EXPECT_EQ(10, pages[32].size()); } diff --git a/src/raspberrypi/test/localizer_test.cpp b/src/raspberrypi/test/localizer_test.cpp new file mode 100644 index 00000000..0c1be769 --- /dev/null +++ b/src/raspberrypi/test/localizer_test.cpp @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include + +#include "rascsi/localizer.h" + +TEST(Localizer, Localize) +{ + Localizer localizer; + + string message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, ""); + EXPECT_FALSE(message.empty()); + EXPECT_EQ(string::npos, message.find("enum value")); + + message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, "de_DE"); + EXPECT_FALSE(message.empty()); + EXPECT_EQ(string::npos, message.find("enum value")); + + message = localizer.Localize(LocalizationKey::ERROR_AUTHENTICATION, "en"); + EXPECT_FALSE(message.empty()); + EXPECT_EQ(string::npos, message.find("enum value")); + + message = localizer.Localize((LocalizationKey)1234, ""); + EXPECT_FALSE(message.empty()); + EXPECT_NE(string::npos, message.find("enum value")); +} diff --git a/src/raspberrypi/test/mocks.h b/src/raspberrypi/test/mocks.h index 3c7ee865..fa6198ac 100644 --- a/src/raspberrypi/test/mocks.h +++ b/src/raspberrypi/test/mocks.h @@ -12,14 +12,18 @@ #include #include "test_shared.h" +#include "bus.h" #include "controllers/scsi_controller.h" #include "devices/primary_device.h" +#include "devices/storage_device.h" +#include "devices/disk.h" #include "devices/scsihd.h" #include "devices/scsihd_nec.h" #include "devices/scsicd.h" #include "devices/scsimo.h" #include "devices/host_services.h" #include "rascsi/command_context.h" +#include "rascsi/rascsi_executor.h" using namespace testing; @@ -87,17 +91,20 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f friend void TestInquiry(rascsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, scsi_defs::scsi_level, const std::string&, int, bool); + FRIEND_TEST(AbstractControllerTest, AllocateCmd); FRIEND_TEST(AbstractControllerTest, Reset); FRIEND_TEST(AbstractControllerTest, ProcessPhase); FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle); FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId); FRIEND_TEST(AbstractControllerTest, GetOpcode); FRIEND_TEST(AbstractControllerTest, GetLun); - FRIEND_TEST(AbstractControllerTest, Length); + FRIEND_TEST(AbstractControllerTest, SetLength); + FRIEND_TEST(AbstractControllerTest, UpdateOffsetAndLength); FRIEND_TEST(AbstractControllerTest, Offset); FRIEND_TEST(PrimaryDeviceTest, Inquiry); FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); FRIEND_TEST(PrimaryDeviceTest, RequestSense); + FRIEND_TEST(PrimaryDeviceTest, SendDiagnostic); FRIEND_TEST(PrimaryDeviceTest, ReportLuns); FRIEND_TEST(PrimaryDeviceTest, UnknownCommand); FRIEND_TEST(ModePageDeviceTest, ModeSense6); @@ -108,12 +115,21 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f FRIEND_TEST(DiskTest, Rezero); FRIEND_TEST(DiskTest, FormatUnit); FRIEND_TEST(DiskTest, ReassignBlocks); - FRIEND_TEST(DiskTest, Seek); + FRIEND_TEST(DiskTest, Seek6); + FRIEND_TEST(DiskTest, Seek10); + FRIEND_TEST(DiskTest, Read6); + FRIEND_TEST(DiskTest, Read10); + FRIEND_TEST(DiskTest, Read16); + FRIEND_TEST(DiskTest, Write6); + FRIEND_TEST(DiskTest, Write10); + FRIEND_TEST(DiskTest, Write16); + FRIEND_TEST(DiskTest, Verify10); + FRIEND_TEST(DiskTest, Verify16); FRIEND_TEST(DiskTest, ReadCapacity); - FRIEND_TEST(DiskTest, ReadWriteLong); - FRIEND_TEST(DiskTest, Reserve); - FRIEND_TEST(DiskTest, Release); - FRIEND_TEST(DiskTest, SendDiagnostic); + FRIEND_TEST(DiskTest, ReadLong10); + FRIEND_TEST(DiskTest, ReadLong16); + FRIEND_TEST(DiskTest, WriteLong10); + FRIEND_TEST(DiskTest, WriteLong16); FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); @@ -132,7 +148,6 @@ public: MOCK_METHOD(void, Command, (), ()); MOCK_METHOD(void, MsgIn, (), ()); MOCK_METHOD(void, MsgOut, (), ()); - MOCK_METHOD(void, SetByteTransfer, (bool), (override)); MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override)); explicit MockAbstractController(int target_id) : AbstractController(bus, target_id, 32) { @@ -140,13 +155,23 @@ public: } ~MockAbstractController() override = default; - vector& InitCmd(int size) { return AbstractController::InitCmd(size); } //NOSONAR Hides function on purpose + // Permit access to all tests without the need for numerous FRIEND_TEST + vector& GetCmd() { return AbstractController::GetCmd(); } //NOSONAR Hides function on purpose MockBus bus; }; class MockScsiController : public ScsiController { + FRIEND_TEST(ScsiControllerTest, Process); + FRIEND_TEST(ScsiControllerTest, BusFree); + FRIEND_TEST(ScsiControllerTest, Selection); + FRIEND_TEST(ScsiControllerTest, Command); + FRIEND_TEST(ScsiControllerTest, MsgIn); + FRIEND_TEST(ScsiControllerTest, MsgOut); + FRIEND_TEST(ScsiControllerTest, DataIn); + FRIEND_TEST(ScsiControllerTest, DataOut); + FRIEND_TEST(ScsiControllerTest, Error); FRIEND_TEST(ScsiControllerTest, RequestSense); FRIEND_TEST(PrimaryDeviceTest, RequestSense); @@ -154,18 +179,19 @@ public: MOCK_METHOD(void, Reset, (), ()); MOCK_METHOD(void, Status, (), ()); - MOCK_METHOD(void, DataIn, (), ()); - MOCK_METHOD(void, DataOut, (), ()); - MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override)); + MOCK_METHOD(void, Execute, (), ()); + using ScsiController::ScsiController; explicit MockScsiController(int target_id) : ScsiController(bus, target_id) {} + MockScsiController() : ScsiController(bus, 0) {} ~MockScsiController() override = default; - MockBus bus; + NiceMock bus; }; class MockDevice : public Device { + FRIEND_TEST(DeviceTest, Properties); FRIEND_TEST(DeviceTest, Params); FRIEND_TEST(DeviceTest, StatusCode); FRIEND_TEST(DeviceTest, Reset); @@ -177,7 +203,8 @@ public: MOCK_METHOD(int, GetId, (), (const)); - explicit MockDevice(int lun) : Device("test", lun) {} + explicit MockDevice(int lun) : Device(UNDEFINED, lun) {} + explicit MockDevice(PbDeviceType type) : Device(type, 0) {} ~MockDevice() override = default; }; @@ -187,21 +214,23 @@ class MockPrimaryDevice : public PrimaryDevice FRIEND_TEST(PrimaryDeviceTest, TestUnitReady); FRIEND_TEST(PrimaryDeviceTest, RequestSense); FRIEND_TEST(PrimaryDeviceTest, Inquiry); + FRIEND_TEST(PrimaryDeviceTest, GetSetSendDelay); FRIEND_TEST(ScsiControllerTest, RequestSense); - FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice); + FRIEND_TEST(RascsiExecutorTest, ValidateOperationAgainstDevice); public: - MOCK_METHOD(void, Reset, (), ()); MOCK_METHOD(vector, InquiryInternal, (), (const)); - explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {} + explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {} ~MockPrimaryDevice() override = default; }; class MockModePageDevice : public ModePageDevice { + FRIEND_TEST(ModePageDeviceTest, SupportsSaveParameters); FRIEND_TEST(ModePageDeviceTest, AddModePages); + FRIEND_TEST(ModePageDeviceTest, AddVendorPage); public: @@ -209,49 +238,105 @@ public: MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); - explicit MockModePageDevice() : ModePageDevice("test", 0) {} + MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {} ~MockModePageDevice() override = default; void SetUpModePages(map>& pages, int page, bool) const override { // Return dummy data for other pages than page 0 if (page) { - vector buf(255); + vector buf(32); pages[page] = buf; } } }; +class MockPage0ModePageDevice : public MockModePageDevice +{ + FRIEND_TEST(ModePageDeviceTest, Page0); + +public: + + using MockModePageDevice::MockModePageDevice; + + void SetUpModePages(map>& pages, int, bool) const override { + // Return dummy data for pages 0 and 1 + vector buf(32); + pages[0] = buf; + pages[1] = buf; + } +}; + +class MockStorageDevice : public StorageDevice +{ + FRIEND_TEST(StorageDeviceTest, ValidateFile); + FRIEND_TEST(StorageDeviceTest, MediumChanged); + FRIEND_TEST(StorageDeviceTest, GetIdsForReservedFile); + FRIEND_TEST(StorageDeviceTest, FileExists); + FRIEND_TEST(StorageDeviceTest, GetFileSize); + +public: + + MOCK_METHOD(vector, InquiryInternal, (), (const)); + MOCK_METHOD(void, Open, (), (override)); + MOCK_METHOD(int, ModeSense6, (const vector&, vector&), (const override)); + MOCK_METHOD(int, ModeSense10, (const vector&, vector&), (const override)); + MOCK_METHOD(void, SetUpModePages, ((map>&), int, bool), (const override)); + + MockStorageDevice() : StorageDevice(UNDEFINED, 0) {} + ~MockStorageDevice() override = default; +}; + class MockDisk : public Disk { + FRIEND_TEST(DiskTest, Dispatch); FRIEND_TEST(DiskTest, Rezero); FRIEND_TEST(DiskTest, FormatUnit); FRIEND_TEST(DiskTest, ReassignBlocks); - FRIEND_TEST(DiskTest, Seek); + FRIEND_TEST(DiskTest, Seek6); + FRIEND_TEST(DiskTest, Seek10); + FRIEND_TEST(DiskTest, Read6); + FRIEND_TEST(DiskTest, Read10); + FRIEND_TEST(DiskTest, Read16); + FRIEND_TEST(DiskTest, Write6); + FRIEND_TEST(DiskTest, Write10); + FRIEND_TEST(DiskTest, Write16); + FRIEND_TEST(DiskTest, Verify10); + FRIEND_TEST(DiskTest, Verify16); FRIEND_TEST(DiskTest, ReadCapacity); - FRIEND_TEST(DiskTest, ReadWriteLong); + FRIEND_TEST(DiskTest, ReadLong10); + FRIEND_TEST(DiskTest, ReadLong16); + FRIEND_TEST(DiskTest, WriteLong10); + FRIEND_TEST(DiskTest, WriteLong16); FRIEND_TEST(DiskTest, ReserveRelease); FRIEND_TEST(DiskTest, SendDiagnostic); + FRIEND_TEST(DiskTest, StartStopUnit); FRIEND_TEST(DiskTest, PreventAllowMediumRemoval); + FRIEND_TEST(DiskTest, Eject); + FRIEND_TEST(DiskTest, ModeSense6); + FRIEND_TEST(DiskTest, ModeSense10); FRIEND_TEST(DiskTest, SynchronizeCache); FRIEND_TEST(DiskTest, ReadDefectData); FRIEND_TEST(DiskTest, SectorSize); FRIEND_TEST(DiskTest, BlockCount); - FRIEND_TEST(DiskTest, GetIdsForReservedFile); public: MOCK_METHOD(vector, InquiryInternal, (), (const)); MOCK_METHOD(void, FlushCache, (), (override)); + MOCK_METHOD(void, Open, (), (override)); - MockDisk() : Disk("test", 0) {} + MockDisk() : Disk(SCHD, 0) {} ~MockDisk() override = default; }; -class MockSCSIHD : public SCSIHD +class MockSCSIHD : public SCSIHD //NOSONAR Ignore inheritance hierarchy depth in unit tests { FRIEND_TEST(DiskTest, ConfiguredSectorSize); + FRIEND_TEST(ScsiHdTest, SupportsSaveParameters); + FRIEND_TEST(ScsiHdTest, FinalizeSetup); FRIEND_TEST(ScsiHdTest, SetUpModePages); FRIEND_TEST(RascsiExecutorTest, SetSectorSize); + FRIEND_TEST(ScsiHdTest, ModeSelect); using SCSIHD::SCSIHD; }; @@ -259,20 +344,27 @@ class MockSCSIHD : public SCSIHD class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests { FRIEND_TEST(ScsiHdNecTest, SetUpModePages); + FRIEND_TEST(ScsiHdNecTest, TestAddFormatPage); + FRIEND_TEST(ScsiHdNecTest, TestAddDrivePage); + FRIEND_TEST(RascsiExecutorTest, ProcessDeviceCmd); using SCSIHD_NEC::SCSIHD_NEC; }; -class MockSCSICD : public SCSICD +class MockSCSICD : public SCSICD //NOSONAR Ignore inheritance hierarchy depth in unit tests { FRIEND_TEST(ScsiCdTest, SetUpModePages); + FRIEND_TEST(ScsiCdTest, ReadToc); using SCSICD::SCSICD; }; -class MockSCSIMO : public SCSIMO +class MockSCSIMO : public SCSIMO //NOSONAR Ignore inheritance hierarchy depth in unit tests { + FRIEND_TEST(ScsiMoTest, SupportsSaveParameters); FRIEND_TEST(ScsiMoTest, SetUpModePages); + FRIEND_TEST(ScsiMoTest, TestAddVendorPage); + FRIEND_TEST(ScsiMoTest, ModeSelect); using SCSIMO::SCSIMO; }; @@ -293,3 +385,13 @@ public: } ~MockCommandContext() = default; }; + +class MockRascsiExecutor : public RascsiExecutor +{ +public: + + MOCK_METHOD(bool, Start, (shared_ptr, bool), (const)); + MOCK_METHOD(bool, Stop, (shared_ptr, bool), (const)); + + using RascsiExecutor::RascsiExecutor; +}; diff --git a/src/raspberrypi/test/mode_page_device_test.cpp b/src/raspberrypi/test/mode_page_device_test.cpp index 2748a1e4..1e7e5810 100644 --- a/src/raspberrypi/test/mode_page_device_test.cpp +++ b/src/raspberrypi/test/mode_page_device_test.cpp @@ -13,21 +13,71 @@ using namespace std; +TEST(ModePageDeviceTest, SupportsSaveParameters) +{ + MockModePageDevice device; + + EXPECT_FALSE(device.SupportsSaveParameters()) << "Wrong default value"; + device.SupportsSaveParameters(true); + EXPECT_TRUE(device.SupportsSaveParameters()); + device.SupportsSaveParameters(false); + EXPECT_FALSE(device.SupportsSaveParameters()); +} + TEST(ModePageDeviceTest, AddModePages) { vector cdb(6); vector buf(512); MockModePageDevice device; - cdb[2] = 0x3f; - - EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, -1)) << "Negative maximum length must be rejected"; - EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length 0 must be rejected"; - EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length 1 must be rejected"; - + // Page 0 cdb[2] = 0x00; - EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_exception) + EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, 255), scsi_exception) << "Data were returned for non-existing mode page 0"; + + // All pages, non changeable + cdb[2] = 0x3f; + EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); + EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); + EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, -1), scsi_exception) << "Maximum size was ignored"; + + // All pages, changeable + cdb[2]= 0x7f; + EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); + EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255)); + EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, -1), scsi_exception) << "Maximum size was ignored"; +} + +TEST(ModePageDeviceTest, Page0) +{ + vector cdb(6); + vector buf(512); + MockPage0ModePageDevice device; + + cdb[2] = 0x3f; + EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255)); + EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1, 255)); +} + +TEST(ModePageDeviceTest, AddVendorPage) +{ + map> pages; + MockModePageDevice device; + + device.AddVendorPage(pages, 0x3f, false); + EXPECT_TRUE(pages.empty()) << "There must not be any default vendor page"; + device.AddVendorPage(pages, 0x3f, true); + EXPECT_TRUE(pages.empty()) << "There must not be any default vendor page"; +} + +TEST(ModePageDeviceTest, Dispatch) +{ + MockAbstractController controller(0); + auto device = make_shared>(); + + controller.AddDevice(device); + + EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class"; } TEST(ModePageDeviceTest, ModeSense6) @@ -37,9 +87,7 @@ TEST(ModePageDeviceTest, ModeSense6) controller.AddDevice(device); - controller.InitCmd(6); - - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense6)); } @@ -50,9 +98,7 @@ TEST(ModePageDeviceTest, ModeSense10) controller.AddDevice(device); - controller.InitCmd(10); - - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense10)); } @@ -62,7 +108,10 @@ TEST(ModePageDeviceTest, ModeSelect) vector cmd; vector buf; - EXPECT_THROW(device.ModeSelect(cmd, buf, 0), scsi_exception) << "Unexpected MODE SELECT default implementation"; + EXPECT_THROW(device.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 0), scsi_exception) + << "Unexpected MODE SELECT(6) default implementation"; + EXPECT_THROW(device.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 0), scsi_exception) + << "Unexpected MODE SELECT(10) default implementation"; } TEST(ModePageDeviceTest, ModeSelect6) @@ -72,9 +121,9 @@ TEST(ModePageDeviceTest, ModeSelect6) controller.AddDevice(device); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSelect6)); cmd[1] = 0x01; @@ -89,9 +138,9 @@ TEST(ModePageDeviceTest, ModeSelect10) controller.AddDevice(device); - vector& cmd = controller.InitCmd(10); + vector& cmd = controller.GetCmd(); - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSelect10)); cmd[1] = 0x01; diff --git a/src/raspberrypi/test/primary_device_test.cpp b/src/raspberrypi/test/primary_device_test.cpp index a700ce99..ff7bf73f 100644 --- a/src/raspberrypi/test/primary_device_test.cpp +++ b/src/raspberrypi/test/primary_device_test.cpp @@ -35,16 +35,101 @@ TEST(PrimaryDeviceTest, PhaseChange) controller.AddDevice(device); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); device->EnterStatusPhase(); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); device->EnterDataInPhase(); - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); device->EnterDataOutPhase(); } +TEST(PrimaryDeviceTest, Reset) +{ + NiceMock controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for initiator ID 1"; + device->Reset(); + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved anymore for initiator ID 1"; +} + +TEST(PrimaryDeviceTest, CheckReservation) +{ + NiceMock controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved for initiator ID 0"; + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved for initiator ID 0"; + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for initiator ID 1"; + EXPECT_FALSE(device->CheckReservation(-1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for unknown initiator"; + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdInquiry, false)) + << "Device must not be reserved for INQUIRY"; + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRequestSense, false)) + << "Device must not be reserved for REQUEST SENSE"; + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRelease6, false)) + << "Device must not be reserved for RELEASE (6)"; + + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRemoval, false)) + << "Device must not be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit not set"; + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdRemoval, true)) + << "Device must be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit set"; +} + +TEST(PrimaryDeviceTest, ReserveReleaseUnit) +{ + NiceMock controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for initiator ID 1"; + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRelease6)); + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved anymore for initiator ID 1"; + + ON_CALL(controller, GetInitiatorId).WillByDefault(Return(-1)); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for unknown initiator"; + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRelease6)); + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved anymore for unknown initiator"; +} + +TEST(PrimaryDeviceTest, DiscardReservation) +{ + NiceMock controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6)); + EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must be reserved for initiator ID 1"; + device->DiscardReservation(); + EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false)) + << "Device must not be reserved anymore for initiator ID 1"; +} + TEST(PrimaryDeviceTest, TestUnitReady) { MockAbstractController controller(0); @@ -77,70 +162,71 @@ TEST(PrimaryDeviceTest, TestUnitReady) EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception); device->SetReady(true); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(PrimaryDeviceTest, Inquiry) { - NiceMock controller(0); + auto controller = make_shared>(0); auto device = make_shared(0); - device->SetController(&controller); + controller->AddDevice(device); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller->GetCmd(); // ALLOCATION LENGTH cmd[4] = 255; ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false); }); - EXPECT_CALL(*device, InquiryInternal()).Times(1); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(*device, InquiryInternal()); + EXPECT_CALL(*controller, DataIn()); + ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(1)); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); - EXPECT_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported"; + EXPECT_EQ(0x7f, controller->GetBuffer()[0]) << "Invalid LUN was not reported"; + ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(0)); - EXPECT_TRUE(controller.AddDevice(device)); - EXPECT_FALSE(controller.AddDevice(device)) << "Duplicate LUN was not rejected"; - EXPECT_CALL(*device, InquiryInternal()).Times(1); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_FALSE(controller->AddDevice(make_shared(0))) << "Duplicate LUN was not rejected"; + EXPECT_CALL(*device, InquiryInternal()); + EXPECT_CALL(*controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); - EXPECT_EQ(device_type::PROCESSOR, (device_type)controller.GetBuffer()[0]); - EXPECT_EQ(0x00, controller.GetBuffer()[1]) << "Device was not reported as non-removable"; - EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level"; - EXPECT_EQ(0x1f, controller.GetBuffer()[4]) << "Wrong additional data size"; + EXPECT_EQ(device_type::PROCESSOR, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable"; + EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() { return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true); }); - EXPECT_CALL(*device, InquiryInternal()).Times(1); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(*device, InquiryInternal()); + EXPECT_CALL(*controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); - EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller.GetBuffer()[0]); - EXPECT_EQ(0x80, controller.GetBuffer()[1]) << "Device was not reported as removable"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level"; - EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level"; - EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size"; + EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller->GetBuffer()[0]); + EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable"; + EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level"; + EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level"; + EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; cmd[1] = 0x01; - EXPECT_CALL(controller, DataIn()).Times(0); + EXPECT_CALL(*controller, DataIn()).Times(0); EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "EVPD bit is not supported"; cmd[2] = 0x01; - EXPECT_CALL(controller, DataIn()).Times(0); + EXPECT_CALL(*controller, DataIn()).Times(0); EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "PAGE CODE field is not supported"; cmd[1] = 0x00; cmd[2] = 0x00; // ALLOCATION LENGTH cmd[4] = 1; - EXPECT_CALL(*device, InquiryInternal()).Times(1); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(*device, InquiryInternal()); + EXPECT_CALL(*controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); - EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size"; - EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling"; + EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size"; + EXPECT_EQ(1, controller->GetLength()) << "Wrong ALLOCATION LENGTH handling"; } TEST(PrimaryDeviceTest, RequestSense) @@ -150,7 +236,7 @@ TEST(PrimaryDeviceTest, RequestSense) controller.AddDevice(device); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // ALLOCATION LENGTH cmd[4] = 255; @@ -158,11 +244,38 @@ TEST(PrimaryDeviceTest, RequestSense) EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_exception); device->SetReady(true); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } +TEST(PrimaryDeviceTest, SendDiagnostic) +{ + MockAbstractController controller(0); + auto device = make_shared(0); + + controller.AddDevice(device); + + vector& cmd = controller.GetCmd(); + + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(device->Dispatch(scsi_command::eCmdSendDiag)); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + cmd[1] = 0x10; + EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) + << "SEND DIAGNOSTIC must fail because PF bit is not supported"; + cmd[1] = 0; + + cmd[3] = 1; + EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) + << "SEND DIAGNOSTIC must fail because parameter list is not supported"; + cmd[3] = 0; + cmd[4] = 1; + EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception) + << "SEND DIAGNOSTIC must fail because parameter list is not supported"; +} + TEST(PrimaryDeviceTest, ReportLuns) { const int LUN1 = 1; @@ -177,11 +290,11 @@ TEST(PrimaryDeviceTest, ReportLuns) controller.AddDevice(device2); EXPECT_TRUE(controller.HasDeviceForLun(LUN2)); - vector& cmd = controller.InitCmd(10); + vector& cmd = controller.GetCmd(); // ALLOCATION LENGTH cmd[9] = 255; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns)); const vector& buffer = controller.GetBuffer(); EXPECT_EQ(0x00, buffer[0]) << "Wrong data length"; @@ -219,19 +332,31 @@ TEST(PrimaryDeviceTest, UnknownCommand) EXPECT_FALSE(device->Dispatch((scsi_command)0xFF)); } +TEST(PrimaryDeviceTest, Dispatch) +{ + MockAbstractController controller(0); + auto device = make_shared>(0); + + controller.AddDevice(device); + + EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class"; +} + TEST(PrimaryDeviceTest, WriteByteSequence) { vector data; MockPrimaryDevice device(0); - EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device must not support writing byte sequences"; + EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device does not support writing byte sequences"; } -TEST(PrimaryDeviceTest, GetSendDelay) +TEST(PrimaryDeviceTest, GetSetSendDelay) { MockPrimaryDevice device(0); - EXPECT_EQ(-1, device.GetSendDelay()); + EXPECT_EQ(-1, device.GetSendDelay()) << "Wrong delay default value"; + device.SetSendDelay(1234); + EXPECT_EQ(1234, device.GetSendDelay()); } TEST(PrimaryDeviceTest, Init) @@ -239,7 +364,7 @@ TEST(PrimaryDeviceTest, Init) unordered_map params; MockPrimaryDevice device(0); - EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail"; + EXPECT_FALSE(device.Init(params)) << "Initialization of primary device must fail"; } TEST(PrimaryDeviceTest, FlushCache) diff --git a/src/raspberrypi/test/protobuf_serializer_test.cpp b/src/raspberrypi/test/protobuf_serializer_test.cpp index 7af80ac4..d8028155 100644 --- a/src/raspberrypi/test/protobuf_serializer_test.cpp +++ b/src/raspberrypi/test/protobuf_serializer_test.cpp @@ -19,12 +19,7 @@ TEST(ProtobufSerializerTest, SerializeMessage) PbResult result; ProtobufSerializer serializer; - int fd = open("/dev/zero", O_RDONLY); - EXPECT_NE(-1, fd); - EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Writing the message header must fail"; - close(fd); - - fd = open("/dev/null", O_WRONLY); + const int fd = open("/dev/null", O_WRONLY); EXPECT_NE(-1, fd); serializer.SerializeMessage(fd, result); EXPECT_THROW(serializer.SerializeMessage(-1, result), io_exception) << "Writing a message must fail"; @@ -42,10 +37,50 @@ TEST(ProtobufSerializerTest, DeserializeMessage) EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading the message header must fail"; close(fd); - fd = open("/dev/zero", O_RDONLY); + string filename; + fd = OpenTempFile(filename); EXPECT_NE(-1, fd); - EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Reading a message must fail"; + // Data size -1 + buf = { byte{0xff}, byte{0xff}, byte{0xff}, byte{0xff} }; + EXPECT_EQ(buf.size(), write(fd, buf.data(), buf.size())); close(fd); + fd = open(filename.c_str(), O_RDONLY); + EXPECT_NE(-1, fd); + EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Invalid header was not rejected"; + unlink(filename.c_str()); + + fd = OpenTempFile(filename); + EXPECT_NE(-1, fd); + // Data size 2 + buf = { byte{0x02}, byte{0x00}, byte{0x00}, byte{0x00} }; + EXPECT_EQ(buf.size(), write(fd, buf.data(), buf.size())); + close(fd); + fd = open(filename.c_str(), O_RDONLY); + EXPECT_NE(-1, fd); + EXPECT_THROW(serializer.DeserializeMessage(fd, result), io_exception) << "Invalid data were not rejected"; + unlink(filename.c_str()); +} + +TEST(ProtobufSerializerTest, SerializeDeserializeMessage) +{ + PbResult result; + result.set_status(true); + ProtobufSerializer serializer; + + string filename; + int fd = OpenTempFile(filename); + EXPECT_NE(-1, fd); + serializer.SerializeMessage(fd, result); + close(fd); + + result.set_status(false); + fd = open(filename.c_str(), O_RDONLY); + EXPECT_NE(-1, fd); + serializer.DeserializeMessage(fd, result); + close(fd); + unlink(filename.c_str()); + + EXPECT_TRUE(result.status()); } TEST(ProtobufSerializerTest, ReadBytes) diff --git a/src/raspberrypi/test/protobuf_util_test.cpp b/src/raspberrypi/test/protobuf_util_test.cpp index a5bbb8f3..60eb139f 100644 --- a/src/raspberrypi/test/protobuf_util_test.cpp +++ b/src/raspberrypi/test/protobuf_util_test.cpp @@ -25,17 +25,17 @@ void TestSpecialDevice(const string& name) TEST(CommandUtil, AddGetParam) { PbCommand command; - AddParam(command, "key", "value"); + SetParam(command, "key", "value"); EXPECT_EQ("value", GetParam(command, "key")); EXPECT_EQ("", GetParam(command, "xyz")); PbDeviceDefinition definition; - AddParam(definition, "key", "value"); + SetParam(definition, "key", "value"); EXPECT_EQ("value", GetParam(definition, "key")); EXPECT_EQ("", GetParam(definition, "xyz")); PbDevice device; - AddParam(device, "key", "value"); + SetParam(device, "key", "value"); const auto& it = device.params().find("key"); EXPECT_EQ("value", it->second); } diff --git a/src/raspberrypi/test/rascsi_exceptions_test.cpp b/src/raspberrypi/test/rascsi_exceptions_test.cpp index 89935341..9b5d1f91 100644 --- a/src/raspberrypi/test/rascsi_exceptions_test.cpp +++ b/src/raspberrypi/test/rascsi_exceptions_test.cpp @@ -7,7 +7,8 @@ // //--------------------------------------------------------------------------- -#include "mocks.h" +#include + #include "rascsi_exceptions.h" using namespace scsi_defs; @@ -18,7 +19,7 @@ TEST(RascsiExceptionsTest, IoException) throw io_exception("msg"); } catch(const io_exception& e) { - EXPECT_EQ("msg", e.get_msg()); + EXPECT_STREQ("msg", e.what()); } } @@ -28,28 +29,18 @@ TEST(RascsiExceptionsTest, FileNotFoundException) throw file_not_found_exception("msg"); } catch(const file_not_found_exception& e) { - EXPECT_EQ("msg", e.get_msg()); + EXPECT_STREQ("msg", e.what()); } } TEST(RascsiExceptionsTest, ScsiErrorException) { - try { - throw scsi_exception(); - } - catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::ABORTED_COMMAND, e.get_sense_key()); - EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); - EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); - } - try { throw scsi_exception(sense_key::UNIT_ATTENTION); } catch(const scsi_exception& e) { EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); EXPECT_EQ(asc::NO_ADDITIONAL_SENSE_INFORMATION, e.get_asc()); - EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); } try { @@ -58,15 +49,5 @@ TEST(RascsiExceptionsTest, ScsiErrorException) catch(const scsi_exception& e) { EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); - EXPECT_EQ(status::CHECK_CONDITION, e.get_status()); - } - - try { - throw scsi_exception(sense_key::UNIT_ATTENTION, asc::LBA_OUT_OF_RANGE, status::BUSY); - } - catch(const scsi_exception& e) { - EXPECT_EQ(sense_key::UNIT_ATTENTION, e.get_sense_key()); - EXPECT_EQ(asc::LBA_OUT_OF_RANGE, e.get_asc()); - EXPECT_EQ(status::BUSY, e.get_status()); } } diff --git a/src/raspberrypi/test/rascsi_executor_test.cpp b/src/raspberrypi/test/rascsi_executor_test.cpp index 70b306d0..810bd1e2 100644 --- a/src/raspberrypi/test/rascsi_executor_test.cpp +++ b/src/raspberrypi/test/rascsi_executor_test.cpp @@ -7,48 +7,216 @@ // //--------------------------------------------------------------------------- +#include "spdlog/spdlog.h" #include "mocks.h" +#include "rascsi_exceptions.h" #include "protobuf_util.h" #include "controllers/controller_manager.h" #include "devices/device_factory.h" +#include "rascsi_interface.pb.h" #include "rascsi/command_context.h" #include "rascsi/rascsi_response.h" #include "rascsi/rascsi_image.h" #include "rascsi/rascsi_executor.h" +#include using namespace rascsi_interface; using namespace protobuf_util; -TEST(RascsiExecutorTest, ProcessCmd) +const extern bool enable_logging; + +// This test fixture is required in order to reset the log level changed by the log level tests +class RascsiExecutorTest : public Test +{ + void TearDown() override { + spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); + } +}; + +TEST_F(RascsiExecutorTest, ProcessDeviceCmd) { const int ID = 3; const int LUN = 0; MockBus bus; DeviceFactory device_factory; + MockAbstractController controller(ID); ControllerManager controller_manager(bus); RascsiImage rascsi_image; RascsiResponse rascsi_response(device_factory, controller_manager, 32); - RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); + auto executor = make_shared(rascsi_response, rascsi_image, device_factory, controller_manager); PbDeviceDefinition definition; PbCommand command; MockCommandContext context; definition.set_id(8); definition.set_unit(32); - EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid ID must fail"; - - definition.set_id(ID); - EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid LUN must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID and LUN must fail"; definition.set_unit(LUN); - EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Unknown operation must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID must fail"; + + definition.set_id(ID); + definition.set_unit(32); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid LUN must fail"; + + definition.set_unit(LUN); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Unknown operation must fail"; command.set_operation(ATTACH); - EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Operation for unknown device must fail"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation for unknown device type must fail"; + + auto device1 = make_shared(LUN); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1)); + + definition.set_type(SCHS); + command.set_operation(INSERT); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, 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.AttachToScsiController(ID, device2)); + + command.set_operation(ATTACH); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "ID and LUN already exist"; + + command.set_operation(START); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(PROTECT); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(UNPROTECT); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(STOP); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(EJECT); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(INSERT); + SetParam(definition, "file", "filename"); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Non-existing file"; + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, false)) << "Non-existing file"; + + command.set_operation(DETACH); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2)); + + command.set_operation(CHECK_AUTHENTICATION); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + command.set_operation(NO_OPERATION); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true)); + EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false)); + + // The operations below are not related to a device + + command.set_operation(DETACH_ALL); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(RESERVE_IDS); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(CREATE_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(DELETE_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(RENAME_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(COPY_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(PROTECT_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); + + command.set_operation(UNPROTECT_IMAGE); + EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)); } -TEST(RascsiExecutorTest, SetLogLevel) +TEST_F(RascsiExecutorTest, ProcessCmd) +{ + MockBus bus; + DeviceFactory device_factory; + MockAbstractController controller(0); + ControllerManager controller_manager(bus); + RascsiImage rascsi_image; + RascsiResponse rascsi_response(device_factory, controller_manager, 32); + auto executor = make_shared(rascsi_response, rascsi_image, device_factory, controller_manager); + PbCommand command1; + PbCommand command2; + MockCommandContext context; + + command1.set_operation(DETACH_ALL); + EXPECT_TRUE(executor->ProcessCmd(context, command1)); + + command1.set_operation(RESERVE_IDS); + SetParam(command1, "ids", "2,3"); + EXPECT_TRUE(executor->ProcessCmd(context, command1)); + const unordered_set ids = executor->GetReservedIds(); + EXPECT_EQ(2, ids.size()); + EXPECT_NE(ids.end(), ids.find(2)); + EXPECT_NE(ids.end(), ids.find(3)); + command2.set_operation(RESERVE_IDS); + EXPECT_TRUE(executor->ProcessCmd(context, command2)); + EXPECT_TRUE(executor->GetReservedIds().empty()); + + SetParam(command2, "ids", "-1"); + EXPECT_FALSE(executor->ProcessCmd(context, command2)); + EXPECT_TRUE(executor->GetReservedIds().empty()); + + command1.set_operation(NO_OPERATION); + EXPECT_TRUE(executor->ProcessCmd(context, command1)); + + command1.set_operation(ATTACH); + auto device = command1.add_devices(); + device->set_type(SCHS); + device->set_id(-1); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + device->set_id(0); + device->set_unit(1); + EXPECT_FALSE(executor->ProcessCmd(context, command1)) << "LUN 0 is missing"; + device->set_unit(0); + EXPECT_TRUE(executor->ProcessCmd(context, command1)); + + // The operations below must fail because of missing parameters. + // The respective functionality is tested in rascsi_image_test.cpp. + + command1.set_operation(CREATE_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + + command1.set_operation(DELETE_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + + command1.set_operation(RENAME_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + + command1.set_operation(COPY_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + + command1.set_operation(PROTECT_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); + + command1.set_operation(UNPROTECT_IMAGE); + EXPECT_FALSE(executor->ProcessCmd(context, command1)); +} + +TEST_F(RascsiExecutorTest, SetLogLevel) { MockBus bus; DeviceFactory device_factory; @@ -62,12 +230,11 @@ TEST(RascsiExecutorTest, SetLogLevel) EXPECT_TRUE(executor.SetLogLevel("info")); EXPECT_TRUE(executor.SetLogLevel("warn")); EXPECT_TRUE(executor.SetLogLevel("err")); - EXPECT_TRUE(executor.SetLogLevel("critical")); EXPECT_TRUE(executor.SetLogLevel("off")); EXPECT_FALSE(executor.SetLogLevel("xyz")); } -TEST(RascsiExecutorTest, Attach) +TEST_F(RascsiExecutorTest, Attach) { const int ID = 3; const int LUN = 0; @@ -101,22 +268,58 @@ TEST(RascsiExecutorTest, Attach) definition.set_type(PbDeviceType::SCHD); EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected"; - definition.set_block_size(1); - EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid sector size not rejeced"; + 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(1024); + 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"; - AddParam(definition, "file", "/non_existing_file"); - EXPECT_FALSE(executor.Attach(context, definition, false)); + SetParam(definition, "file", "/non_existing_file"); + EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected"; - AddParam(definition, "file", "/dev/zero"); - EXPECT_FALSE(executor.Attach(context, definition, false)) << "Empty image file not rejected"; + string filename = CreateTempFile(1); + SetParam(definition, "file", filename); + EXPECT_THROW(executor.Attach(context, definition, false), io_exception) << "Too small image file not rejected"; + unlink(filename.c_str()); - // Further testing is not possible without a filesystem + filename = CreateTempFile(512); + SetParam(definition, "file", filename); + bool result = executor.Attach(context, definition, false); + unlink(filename.c_str()); + EXPECT_TRUE(result); + controller_manager.DeleteAllControllers(); + + filename = CreateTempFile(513); + SetParam(definition, "file", filename); + result = executor.Attach(context, definition, false); + unlink(filename.c_str()); + EXPECT_TRUE(result); + + definition.set_type(PbDeviceType::SCCD); + definition.set_unit(LUN + 1); + filename = CreateTempFile(2048); + SetParam(definition, "file", filename); + result = executor.Attach(context, definition, false); + unlink(filename.c_str()); + 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); + result = executor.Attach(context, definition, false); + unlink(filename.c_str()); + EXPECT_TRUE(result); + + controller_manager.DeleteAllControllers(); } -TEST(RascsiExecutorTest, Insert) +TEST_F(RascsiExecutorTest, Insert) { MockBus bus; DeviceFactory device_factory; @@ -147,7 +350,7 @@ TEST(RascsiExecutorTest, Insert) EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Filename is missing"; - AddParam(definition, "file", "filename"); + 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)); @@ -155,18 +358,25 @@ TEST(RascsiExecutorTest, Insert) EXPECT_FALSE(executor.Insert(context, definition, device, false)); definition.set_block_size(0); - EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation has to fail"; + EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation must fail"; - AddParam(definition, "file", "/non_existing_file"); + SetParam(definition, "file", "/non_existing_file"); EXPECT_FALSE(executor.Insert(context, definition, device, false)); - AddParam(definition, "file", "/dev/zero"); - EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "File has 0 bytes"; + string filename = CreateTempFile(1); + SetParam(definition, "file", filename); + EXPECT_THROW(executor.Insert(context, definition, device, false), io_exception) + << "Too small image file not rejected"; + unlink(filename.c_str()); - // Further testing is not possible without a filesystem + filename = CreateTempFile(512); + SetParam(definition, "file", filename); + const bool result = executor.Insert(context, definition, device, false); + unlink(filename.c_str()); + EXPECT_TRUE(result); } -TEST(RascsiExecutorTest, Detach) +TEST_F(RascsiExecutorTest, Detach) { const int ID = 3; const int LUN1 = 0; @@ -180,10 +390,10 @@ TEST(RascsiExecutorTest, Detach) RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); MockCommandContext context; - auto device1 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); - controller_manager.AttachToScsiController(ID, device1); - auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services"); - controller_manager.AttachToScsiController(ID, device2); + auto device1 = device_factory.CreateDevice(controller_manager, SCHS, LUN1, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1)); + auto device2 = device_factory.CreateDevice(controller_manager, SCHS, LUN2, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2)); auto d1 = controller_manager.GetDeviceByIdAndLun(ID, LUN1); EXPECT_FALSE(executor.Detach(context, d1, false)) << "LUNs > 0 have to be detached first"; @@ -191,9 +401,11 @@ TEST(RascsiExecutorTest, Detach) 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(RascsiExecutorTest, DetachAll) +TEST_F(RascsiExecutorTest, DetachAll) { const int ID = 4; @@ -204,8 +416,8 @@ TEST(RascsiExecutorTest, DetachAll) RascsiResponse rascsi_response(device_factory, controller_manager, 32); RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager); - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); - controller_manager.AttachToScsiController(ID, device); + auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device)); EXPECT_NE(nullptr, controller_manager.FindController(ID)); EXPECT_FALSE(controller_manager.GetAllDevices().empty()); @@ -214,7 +426,7 @@ TEST(RascsiExecutorTest, DetachAll) EXPECT_TRUE(controller_manager.GetAllDevices().empty()); } -TEST(RascsiExecutorTest, ShutDown) +TEST_F(RascsiExecutorTest, ShutDown) { MockBus bus; DeviceFactory device_factory; @@ -231,7 +443,7 @@ TEST(RascsiExecutorTest, ShutDown) EXPECT_FALSE(executor.ShutDown(context, "reboot")); } -TEST(RascsiExecutorTest, SetReservedIds) +TEST_F(RascsiExecutorTest, SetReservedIds) { MockBus bus; DeviceFactory device_factory; @@ -266,13 +478,13 @@ TEST(RascsiExecutorTest, SetReservedIds) EXPECT_NE(reserved_ids.end(), reserved_ids.find(5)); EXPECT_NE(reserved_ids.end(), reserved_ids.find(7)); - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); - controller_manager.AttachToScsiController(5, device); + auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(5, device)); error = executor.SetReservedIds("5"); EXPECT_FALSE(error.empty()); } -TEST(RascsiExecutorTest, ValidateImageFile) +TEST_F(RascsiExecutorTest, ValidateImageFile) { MockBus bus; DeviceFactory device_factory; @@ -283,11 +495,7 @@ TEST(RascsiExecutorTest, ValidateImageFile) MockCommandContext context; string full_path; - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); - EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path)); - EXPECT_TRUE(full_path.empty()); - - device = device_factory.CreateDevice(controller_manager, SCHD, 0, "test"); + auto device = dynamic_pointer_cast(device_factory.CreateDevice(controller_manager, SCHD, 0, "test")); EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path)); EXPECT_TRUE(full_path.empty()); @@ -295,7 +503,7 @@ TEST(RascsiExecutorTest, ValidateImageFile) EXPECT_TRUE(full_path.empty()); } -TEST(RascsiExecutorTest, ValidateLunSetup) +TEST_F(RascsiExecutorTest, ValidateLunSetup) { MockBus bus; DeviceFactory device_factory; @@ -314,16 +522,16 @@ TEST(RascsiExecutorTest, ValidateLunSetup) error = executor.ValidateLunSetup(command); EXPECT_FALSE(error.empty()); - auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services"); - controller_manager.AttachToScsiController(0, device2); + auto device2 = device_factory.CreateDevice(controller_manager, SCHS, 0, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(0, device2)); error = executor.ValidateLunSetup(command); EXPECT_TRUE(error.empty()); } -TEST(RascsiExecutorTest, VerifyExistingIdAndLun) +TEST_F(RascsiExecutorTest, VerifyExistingIdAndLun) { const int ID = 1; - const int LUN1 = 2; + const int LUN1 = 0; const int LUN2 = 3; MockBus bus; @@ -335,13 +543,13 @@ TEST(RascsiExecutorTest, VerifyExistingIdAndLun) MockCommandContext context; EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); - auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services"); - controller_manager.AttachToScsiController(ID, device); + auto device = device_factory.CreateDevice(controller_manager, SCHS, LUN1, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device)); EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1)); EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2)); } -TEST(RascsiExecutorTest, CreateDevice) +TEST_F(RascsiExecutorTest, CreateDevice) { MockBus bus; DeviceFactory device_factory; @@ -360,7 +568,7 @@ TEST(RascsiExecutorTest, CreateDevice) EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, "")); } -TEST(RascsiExecutorTest, SetSectorSize) +TEST_F(RascsiExecutorTest, SetSectorSize) { MockBus bus; DeviceFactory device_factory; @@ -371,17 +579,17 @@ TEST(RascsiExecutorTest, SetSectorSize) MockCommandContext context; unordered_set sizes; - auto disk = make_shared(0, sizes, false); - EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 512)); + auto hd = make_shared(0, sizes, false); + EXPECT_FALSE(executor.SetSectorSize(context, hd, 512)); sizes.insert(512); - disk = make_shared(0, sizes, false); - EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 0)); - EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 1)); - EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 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(RascsiExecutorTest, ValidationOperationAgainstDevice) +TEST_F(RascsiExecutorTest, ValidateOperationAgainstDevice) { MockBus bus; DeviceFactory device_factory; @@ -393,50 +601,50 @@ TEST(RascsiExecutorTest, ValidationOperationAgainstDevice) auto device = make_shared(0); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, ATTACH)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, DETACH)); + EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, ATTACH)); + EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, DETACH)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, START)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, STOP)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + 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.ValidationOperationAgainstDevice(context, device, START)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + 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.ValidationOperationAgainstDevice(context, device, START)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + 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.ValidationOperationAgainstDevice(context, device, START)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); - EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + 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.ValidationOperationAgainstDevice(context, device, START)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, PROTECT)); - EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT)); + 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(RascsiExecutorTest, ValidateIdAndLun) +TEST_F(RascsiExecutorTest, ValidateIdAndLun) { MockBus bus; DeviceFactory device_factory; @@ -454,7 +662,7 @@ TEST(RascsiExecutorTest, ValidateIdAndLun) EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31)); } -TEST(RascsiExecutorTest, SetProductData) +TEST_F(RascsiExecutorTest, SetProductData) { MockBus bus; DeviceFactory device_factory; diff --git a/src/raspberrypi/test/rascsi_image_test.cpp b/src/raspberrypi/test/rascsi_image_test.cpp new file mode 100644 index 00000000..ac14ca4c --- /dev/null +++ b/src/raspberrypi/test/rascsi_image_test.cpp @@ -0,0 +1,129 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "protobuf_util.h" +#include "rascsi_interface.pb.h" +#include "rascsi/rascsi_image.h" + +using namespace rascsi_interface; +using namespace protobuf_util; + +TEST(RascsiImageTest, SetGetDepth) +{ + RascsiImage image; + + image.SetDepth(1); + EXPECT_EQ(1, image.GetDepth()); +} + +TEST(RascsiImageTest, SetGetDefaultFolder) +{ + RascsiImage image; + + EXPECT_NE(string::npos, image.GetDefaultFolder().find("/images")); + + EXPECT_TRUE(!image.SetDefaultFolder("").empty()); + EXPECT_TRUE(!image.SetDefaultFolder("/not_in_home").empty()); +} + +TEST(RascsiImageTest, CreateImage) +{ + MockCommandContext context; + PbCommand command; + RascsiImage image; + + EXPECT_FALSE(image.CreateImage(context, command)) << "Filename must be reported as missing"; + + SetParam(command, "file", "/a/b/c/filename"); + EXPECT_FALSE(image.CreateImage(context, command)) << "Depth must be reported as invalid"; + + SetParam(command, "file", "filename"); + SetParam(command, "size", "-1"); + EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + + SetParam(command, "size", "1"); + EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + + SetParam(command, "size", "513"); + EXPECT_FALSE(image.CreateImage(context, command)) << "Size must be reported as invalid"; + + // Further tests would modify the filesystem +} + +TEST(RascsiImageTest, DeleteImage) +{ + MockCommandContext context; + PbCommand command; + RascsiImage image; + + EXPECT_FALSE(image.DeleteImage(context, command)) << "Filename must be reported as missing"; + + SetParam(command, "file", "/a/b/c/filename"); + EXPECT_FALSE(image.DeleteImage(context, command)) << "Depth must be reported as invalid"; + + // Further testing would modify the filesystem +} + +TEST(RascsiImageTest, RenameImage) +{ + MockCommandContext context; + PbCommand command; + RascsiImage image; + + EXPECT_FALSE(image.RenameImage(context, command)) << "Filenames must be reported as missing"; + + SetParam(command, "to", "/a/b/c/filename_to"); + EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid"; + + SetParam(command, "to", "filename_to"); + EXPECT_FALSE(image.RenameImage(context, command)) << "Source filename must be reported as missing"; + + SetParam(command, "from", "/a/b/c/filename_from"); + EXPECT_FALSE(image.RenameImage(context, command)) << "Depth must be reported as invalid"; + + // Further testing would modify the filesystem +} + +TEST(RascsiImageTest, CopyImage) +{ + MockCommandContext context; + PbCommand command; + RascsiImage image; + + EXPECT_FALSE(image.CopyImage(context, command)) << "Filenames must be reported as missing"; + + SetParam(command, "to", "/a/b/c/filename_to"); + EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid"; + + SetParam(command, "to", "filename_to"); + EXPECT_FALSE(image.CopyImage(context, command)) << "Source filename must be reported as missing"; + + SetParam(command, "from", "/a/b/c/filename_from"); + EXPECT_FALSE(image.CopyImage(context, command)) << "Depth must be reported as invalid"; + + // Further testing would modify the filesystem +} + +TEST(RascsiImageTest, SetImagePermissions) +{ + MockCommandContext context; + PbCommand command; + RascsiImage image; + + EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Filename must be reported as missing"; + + SetParam(command, "file", "/a/b/c/filename"); + EXPECT_FALSE(image.SetImagePermissions(context, command)) << "Depth must be reported as invalid"; + + SetParam(command, "file", "filename"); + EXPECT_FALSE(image.CopyImage(context, command)) << "Source file must be reported as missing"; + + // Further testing would modify the filesystem +} diff --git a/src/raspberrypi/test/rascsi_response_test.cpp b/src/raspberrypi/test/rascsi_response_test.cpp index 81b345a4..130aff53 100644 --- a/src/raspberrypi/test/rascsi_response_test.cpp +++ b/src/raspberrypi/test/rascsi_response_test.cpp @@ -10,6 +10,7 @@ #include "mocks.h" #include "controllers/controller_manager.h" #include "devices/device_factory.h" +#include "rascsi_version.h" #include "rascsi_interface.pb.h" #include "rascsi/rascsi_response.h" @@ -20,28 +21,28 @@ TEST(RascsiResponseTest, Operation_Count) MockBus bus; ControllerManager controller_manager(bus); DeviceFactory device_factory; - RascsiResponse rascsi_response(device_factory, controller_manager, 32); - PbResult pb_operation_info_result; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; - const auto operation_info = rascsi_response.GetOperationInfo(pb_operation_info_result, 0); - EXPECT_EQ(PbOperation_ARRAYSIZE - 1, operation_info->operations_size()); + const auto info = response.GetOperationInfo(result, 0); + EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info->operations_size()); } -void TestNonDiskDevice(const string& name, int default_param_count) +void TestNonDiskDevice(PbDeviceType type, int default_param_count) { MockBus bus; ControllerManager controller_manager(bus); DeviceFactory device_factory; - RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiResponse response(device_factory, controller_manager, 32); - auto d = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, name); - controller_manager.AttachToScsiController(0, d); + auto d = device_factory.CreateDevice(controller_manager, type, 0, ""); + EXPECT_TRUE(controller_manager.AttachToScsiController(0, d)); - PbServerInfo server_info; - rascsi_response.GetDevices(server_info, "image_folder"); + PbServerInfo info; + response.GetDevices(info, "image_folder"); - EXPECT_EQ(1, server_info.devices_info().devices().size()); - const auto& device = server_info.devices_info().devices()[0]; + EXPECT_EQ(1, info.devices_info().devices().size()); + const auto& device = info.devices_info().devices()[0]; EXPECT_FALSE(device.properties().read_only()); EXPECT_FALSE(device.properties().protectable()); EXPECT_FALSE(device.properties().stoppable()); @@ -61,14 +62,26 @@ void TestNonDiskDevice(const string& name, int default_param_count) } } -TEST(RascsiResponseTest, GetDevice_Printer) +TEST(RascsiResponseTest, GetDevices) { - TestNonDiskDevice("printer", 2); + TestNonDiskDevice(SCHS, 0); + TestNonDiskDevice(SCLP, 1); } -TEST(RascsiResponseTest, GetDevice_HostServices) +TEST(RascsiResponseTest, GetImageFile) { - TestNonDiskDevice("services", 0); + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbImageFile image_file; + + EXPECT_FALSE(response.GetImageFile(image_file, "default_folder", "")); + + // Even though the call fails (non-existing file) some properties must be set + EXPECT_FALSE(response.GetImageFile(image_file, "default_folder", "filename.hds")); + EXPECT_EQ("filename.hds", image_file.name()); + EXPECT_EQ(SCHD, image_file.type()); } TEST(RascsiResponseTest, GetReservedIds) @@ -76,17 +89,159 @@ TEST(RascsiResponseTest, GetReservedIds) MockBus bus; ControllerManager controller_manager(bus); DeviceFactory device_factory; - RascsiResponse rascsi_response(device_factory, controller_manager, 32); + RascsiResponse response(device_factory, controller_manager, 32); unordered_set ids; PbResult result; - const auto& reserved_ids_info1 = rascsi_response.GetReservedIds(result, ids); + const auto& info1 = response.GetReservedIds(result, ids); EXPECT_TRUE(result.status()); - EXPECT_TRUE(reserved_ids_info1->ids().empty()); + EXPECT_TRUE(info1->ids().empty()); ids.insert(3); - const auto& reserved_ids_info2 = rascsi_response.GetReservedIds(result, ids); + const auto& info2 = response.GetReservedIds(result, ids); EXPECT_TRUE(result.status()); - EXPECT_EQ(1, reserved_ids_info2->ids().size()); - EXPECT_EQ(3, reserved_ids_info2->ids()[0]); + EXPECT_EQ(1, info2->ids().size()); + EXPECT_EQ(3, info2->ids()[0]); +} + +TEST(RascsiResponseTest, GetDevicesInfo) +{ + const int ID = 2; + const int LUN1 = 0; + const int LUN2 = 5; + const int LUN3 = 6; + + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbCommand command; + PbResult result; + + response.GetDevicesInfo(result, command, ""); + EXPECT_TRUE(result.status()); + EXPECT_TRUE(result.devices_info().devices().empty()); + + auto device1 = make_shared(LUN1, controller_manager); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1)); + + response.GetDevicesInfo(result, command, ""); + EXPECT_TRUE(result.status()); + auto& devices1 = result.devices_info().devices(); + EXPECT_EQ(1, devices1.size()); + EXPECT_EQ(SCHS, devices1[0].type()); + EXPECT_EQ(ID, devices1[0].id()); + EXPECT_EQ(LUN1, devices1[0].unit()); + + auto device2 = make_shared(LUN2); + EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2)); + + response.GetDevicesInfo(result, command, ""); + EXPECT_TRUE(result.status()); + auto& devices2 = result.devices_info().devices(); + EXPECT_EQ(2, devices2.size()) << "Data for all devices must be returned"; + + auto requested_device = command.add_devices(); + requested_device->set_id(ID); + requested_device->set_unit(LUN1); + response.GetDevicesInfo(result, command, ""); + EXPECT_TRUE(result.status()); + auto& devices3 = result.devices_info().devices(); + EXPECT_EQ(1, devices3.size()) << "Only data for the specified ID and LUN must be returned"; + EXPECT_EQ(SCHS, devices3[0].type()); + EXPECT_EQ(ID, devices3[0].id()); + EXPECT_EQ(LUN1, devices3[0].unit()); + + requested_device->set_id(ID); + requested_device->set_unit(LUN3); + response.GetDevicesInfo(result, command, ""); + EXPECT_FALSE(result.status()) << "Only data for the specified ID and LUN must be returned"; +} + +TEST(RascsiResponseTest, GetDeviceTypesInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; + + const auto& info = response.GetDeviceTypesInfo(result); + EXPECT_TRUE(result.status()); + EXPECT_EQ(8, info->properties().size()); +} + +TEST(RascsiResponseTest, GetServerInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + const unordered_set ids = { 1, 3 }; + PbResult result; + + const auto& info = response.GetServerInfo(result, ids, "log_level", "default_folder", "", "", 1234); + EXPECT_TRUE(result.status()); + EXPECT_EQ(rascsi_major_version, info->version_info().major_version()); + EXPECT_EQ(rascsi_minor_version, info->version_info().minor_version()); + EXPECT_EQ(rascsi_patch_version, info->version_info().patch_version()); + EXPECT_EQ("log_level", info->log_level_info().current_log_level()); + EXPECT_EQ("default_folder", info->image_files_info().default_image_folder()); + EXPECT_EQ(1234, info->image_files_info().depth()); + EXPECT_EQ(2, info->reserved_ids_info().ids().size()); +} + +TEST(RascsiResponseTest, GetVersionInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; + + const auto& info = response.GetVersionInfo(result); + EXPECT_TRUE(result.status()); + EXPECT_EQ(rascsi_major_version, info->major_version()); + EXPECT_EQ(rascsi_minor_version, info->minor_version()); + EXPECT_EQ(rascsi_patch_version, info->patch_version()); +} + +TEST(RascsiResponseTest, GetLogLevelInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; + + const auto& info = response.GetLogLevelInfo(result, "level"); + EXPECT_TRUE(result.status()); + EXPECT_EQ("level", info->current_log_level()); + EXPECT_EQ(6, info->log_levels().size()); +} + +TEST(RascsiResponseTest, GetNetworkInterfacesInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; + + const auto& info = response.GetNetworkInterfacesInfo(result); + EXPECT_TRUE(result.status()); + EXPECT_FALSE(info->name().empty()); +} + +TEST(RascsiResponseTest, GetMappingInfo) +{ + MockBus bus; + ControllerManager controller_manager(bus); + DeviceFactory device_factory; + RascsiResponse response(device_factory, controller_manager, 32); + PbResult result; + + const auto& info = response.GetMappingInfo(result); + EXPECT_TRUE(result.status()); + EXPECT_EQ(9, info->mapping().size()); } diff --git a/src/raspberrypi/test/rascsi_service_test.cpp b/src/raspberrypi/test/rascsi_service_test.cpp new file mode 100644 index 00000000..5fe2b416 --- /dev/null +++ b/src/raspberrypi/test/rascsi_service_test.cpp @@ -0,0 +1,26 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// These tests only test up the point where a network connection is required. +// +//--------------------------------------------------------------------------- + +#include + +#include "rascsi/rascsi_service.h" + +TEST(RascsiServiceTest, LifeCycle) +{ + RascsiService service; + + EXPECT_TRUE(service.Init(nullptr, 65535)); + EXPECT_FALSE(service.Init(nullptr, 65536)); + EXPECT_FALSE(service.Init(nullptr, 0)); + EXPECT_FALSE(service.Init(nullptr, -1)); + + service.Cleanup(); +} diff --git a/src/raspberrypi/test/rasctl_commands_test.cpp b/src/raspberrypi/test/rasctl_commands_test.cpp new file mode 100644 index 00000000..43aef037 --- /dev/null +++ b/src/raspberrypi/test/rasctl_commands_test.cpp @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +// These tests only test up the point where a network connection is required. +// +//--------------------------------------------------------------------------- + +#include + +#include "protobuf_util.h" +#include "rascsi_exceptions.h" +#include "rascsi_interface.pb.h" +#include "rasctl/rasctl_commands.h" + +using namespace testing; +using namespace rascsi_interface; +using namespace protobuf_util; + +TEST(RasctlCommandsTest, Execute) +{ + PbCommand command; + RasctlCommands commands(command, "localhost", 0); + + command.set_operation(LOG_LEVEL); + EXPECT_THROW(commands.Execute("log_level", "", "", "", ""), io_exception); + EXPECT_EQ("log_level", GetParam(command, "level")); + + command.set_operation(DEFAULT_FOLDER); + EXPECT_THROW(commands.Execute("", "default_folder", "", "", ""), io_exception); + EXPECT_EQ("default_folder", GetParam(command, "folder")); + + command.set_operation(RESERVE_IDS); + EXPECT_THROW(commands.Execute("", "", "reserved_ids", "", ""), io_exception); + EXPECT_EQ("reserved_ids", GetParam(command, "ids")); + + command.set_operation(CREATE_IMAGE); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); + EXPECT_THROW(commands.Execute("", "", "", "filename:0", ""), io_exception); + EXPECT_EQ("false", GetParam(command, "read_only")); + + command.set_operation(DELETE_IMAGE); + EXPECT_THROW(commands.Execute("", "", "", "filename1", ""), io_exception); + EXPECT_EQ("filename1", GetParam(command, "file")); + + command.set_operation(RENAME_IMAGE); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); + EXPECT_THROW(commands.Execute("", "", "", "from1:to1", ""), io_exception); + EXPECT_EQ("from1", GetParam(command, "from")); + EXPECT_EQ("to1", GetParam(command, "to")); + + command.set_operation(COPY_IMAGE); + EXPECT_FALSE(commands.Execute("", "", "", "", "")); + EXPECT_THROW(commands.Execute("", "", "", "from2:to2", ""), io_exception); + EXPECT_EQ("from2", GetParam(command, "from")); + EXPECT_EQ("to2", GetParam(command, "to")); + + command.set_operation(DEVICES_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(DEVICE_TYPES_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(VERSION_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(SERVER_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(DEFAULT_IMAGE_FILES_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(IMAGE_FILE_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", "filename2"), io_exception); + EXPECT_EQ("filename2", GetParam(command, "file")); + + command.set_operation(NETWORK_INTERFACES_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(LOG_LEVEL_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(RESERVED_IDS_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(MAPPING_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(OPERATION_INFO); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); + + command.set_operation(NO_OPERATION); + EXPECT_THROW(commands.Execute("", "", "", "", ""), io_exception); +} + +TEST(RasctlCommandsTest, CommandDevicesInfo) +{ + PbCommand command; + + RasctlCommands commands1(command, "/invalid_host_name", 0); + EXPECT_THROW(commands1.CommandDevicesInfo(), io_exception); + + RasctlCommands commands2(command, "localhost", 0); + EXPECT_THROW(commands2.CommandDevicesInfo(), io_exception); +} diff --git a/src/raspberrypi/test/rasctl_display_test.cpp b/src/raspberrypi/test/rasctl_display_test.cpp index ba222443..b564fb09 100644 --- a/src/raspberrypi/test/rasctl_display_test.cpp +++ b/src/raspberrypi/test/rasctl_display_test.cpp @@ -272,7 +272,7 @@ TEST(RasctlDisplayTest, DisplayOperationInfo) EXPECT_FALSE(s.empty()); EXPECT_NE(string::npos, s.find(PbOperation_Name(NO_OPERATION))); - meta_data.set_server_side_name("name"); + meta_data.set_server_side_name("server_side_name"); meta_data.set_description("description"); (*info.mutable_operations())[0] = meta_data; s = display.DisplayOperationInfo(info); @@ -285,4 +285,10 @@ TEST(RasctlDisplayTest, DisplayOperationInfo) EXPECT_NE(string::npos, s.find("description3")); EXPECT_NE(string::npos, s.find("permitted_value3_1")); EXPECT_NE(string::npos, s.find("permitted_value3_2")); + EXPECT_EQ(string::npos, s.find("server_side_name")); + + (*info.mutable_operations())[1234] = meta_data; + s = display.DisplayOperationInfo(info); + EXPECT_FALSE(s.empty()); + EXPECT_NE(string::npos, s.find("server_side_name")); } diff --git a/src/raspberrypi/test/rasutil_test.cpp b/src/raspberrypi/test/rasutil_test.cpp index 3f7d7286..a038fbb9 100644 --- a/src/raspberrypi/test/rasutil_test.cpp +++ b/src/raspberrypi/test/rasutil_test.cpp @@ -7,9 +7,16 @@ // //--------------------------------------------------------------------------- -#include "mocks.h" -#include "rasutil.h" +#include +#include "rascsi_interface.pb.h" +#include "rasutil.h" +#ifdef __linux__ +#include +#endif + +using namespace std; +using namespace rascsi_interface; using namespace ras_util; TEST(RasUtilTest, GetAsInt) @@ -55,3 +62,42 @@ TEST(RasUtilTest, ListDevices) EXPECT_NE(string::npos, device_list.find("Host Services")); EXPECT_NE(string::npos, device_list.find("SCSI Printer")); } + +TEST(RasUtilTest, GetExtensionLowerCase) +{ + EXPECT_EQ("", GetExtensionLowerCase("")); + EXPECT_EQ("", GetExtensionLowerCase(".")); + EXPECT_EQ("ext", GetExtensionLowerCase("file.ext")); + EXPECT_EQ("ext", GetExtensionLowerCase("FILE.EXT")); + EXPECT_EQ("ext", GetExtensionLowerCase(".ext")); + EXPECT_EQ("ext_long", GetExtensionLowerCase(".ext_long")); + EXPECT_EQ("ext", GetExtensionLowerCase(".XYZ.EXT")); +} + +#ifdef __linux__ +TEST(RasUtilTest, FixCpu) +{ + cpu_set_t mask; + + FixCpu(0); + CPU_ZERO(&mask); + const int cpu = sched_getaffinity(0, sizeof(cpu_set_t), &mask); + const int cpus = CPU_COUNT(&mask); + + FixCpu(cpus); + CPU_ZERO(&mask); + EXPECT_EQ(cpu, sched_getaffinity(0, sizeof(cpu_set_t), &mask)); + + FixCpu(cpus - 1); + CPU_ZERO(&mask); + EXPECT_EQ(cpus - 1, sched_getaffinity(0, sizeof(cpu_set_t), &mask)); + FixCpu(0); + + // Reset affinity + CPU_ZERO(&mask); + CPU_SET(cpu, &mask); + sched_setaffinity(0, sizeof(cpu_set_t), &mask); + + EXPECT_EQ(0, cpu); +} +#endif diff --git a/src/raspberrypi/test/scsi_command_util_test.cpp b/src/raspberrypi/test/scsi_command_util_test.cpp index bfe65c5a..f251ad59 100644 --- a/src/raspberrypi/test/scsi_command_util_test.cpp +++ b/src/raspberrypi/test/scsi_command_util_test.cpp @@ -8,6 +8,7 @@ //--------------------------------------------------------------------------- #include "mocks.h" +#include "scsi.h" #include "rascsi_exceptions.h" #include "devices/scsi_command_util.h" @@ -22,10 +23,9 @@ TEST(ScsiCommandUtilTest, ModeSelect6) // PF (vendor-specific parameter format) cdb[1] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 0), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0), scsi_exception) << "Vendor-specific parameters are not supported"; - cdb[0] = (int)scsi_command::eCmdModeSelect6; cdb[0] = 0x15; // PF (standard parameter format) cdb[1] = 0x10; @@ -33,25 +33,25 @@ TEST(ScsiCommandUtilTest, ModeSelect6) buf[9] = 0x00; buf[10] = 0x02; buf[11] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 256), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256), scsi_exception) << "Requested sector size does not match current sector size"; // Page 0 buf[12] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512), scsi_exception) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[12] = 0x03; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512), scsi_exception) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[24] = 0x02; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH - 1, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512), scsi_exception) << "Not enough command parameters"; - ModeSelect(cdb, buf, LENGTH, 512); + ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512); } TEST(ScsiCommandUtilTest, ModeSelect10) @@ -63,35 +63,34 @@ TEST(ScsiCommandUtilTest, ModeSelect10) // PF (vendor-specific parameter format) cdb[1] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 0), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0), scsi_exception) << "Vendor-specific parameters are not supported"; - cdb[0] = (int)scsi_command::eCmdModeSelect10; // PF (standard parameter format) cdb[1] = 0x10; // Request 512 bytes per sector buf[13] = 0x00; buf[14] = 0x02; buf[15] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 256), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256), scsi_exception) << "Requested sector size does not match current sector size"; // Page 0 buf[16] = 0x00; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512), scsi_exception) << "Unsupported page 0 was not rejected"; // Page 3 (Format Device Page) buf[16] = 0x03; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512), scsi_exception) << "Requested sector size does not match current sector size"; // Match the requested to the current sector size buf[28] = 0x02; - EXPECT_THROW(ModeSelect(cdb, buf, LENGTH - 1, 512), scsi_exception) + EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512), scsi_exception) << "Not enough command parameters"; - ModeSelect(cdb, buf, LENGTH, 512); + ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512); } TEST(ScsiCommandUtilTest, EnrichFormatPage) diff --git a/src/raspberrypi/test/scsi_controller_test.cpp b/src/raspberrypi/test/scsi_controller_test.cpp index c915c063..e797d182 100644 --- a/src/raspberrypi/test/scsi_controller_test.cpp +++ b/src/raspberrypi/test/scsi_controller_test.cpp @@ -9,33 +9,290 @@ #include "mocks.h" #include "scsi.h" +#include "rascsi_exceptions.h" #include "controllers/scsi_controller.h" using namespace scsi_defs; -TEST(ScsiControllerTest, GetMaxLuns) +TEST(ScsiControllerTest, GetInitiatorId) { - MockScsiController controller(0); + const int ID = 2; - EXPECT_EQ(32, controller.GetMaxLuns()); + MockScsiController controller; + + controller.Process(ID); + EXPECT_EQ(ID, controller.GetInitiatorId()); + controller.Process(-1); + EXPECT_EQ(-1, controller.GetInitiatorId()); +} + +TEST(ScsiControllerTest, Process) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + controller.SetPhase(BUS::phase_t::reserved); + ON_CALL(bus, GetRST).WillByDefault(Return(true)); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST); + EXPECT_CALL(bus, Reset); + EXPECT_CALL(controller, Reset); + EXPECT_EQ(BUS::phase_t::reserved, controller.Process(0)); + + controller.SetPhase(BUS::phase_t::busfree); + ON_CALL(bus, GetRST).WillByDefault(Return(false)); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST); + EXPECT_EQ(BUS::phase_t::busfree, controller.Process(0)); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST); + EXPECT_CALL(bus, Reset); + EXPECT_CALL(controller, Reset); + EXPECT_EQ(BUS::phase_t::busfree, controller.Process(0)); +} + +TEST(ScsiControllerTest, BusFree) +{ + MockScsiController controller; + + controller.SetPhase(BUS::phase_t::busfree); + controller.BusFree(); + EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase()); + + controller.SetStatus(status::CHECK_CONDITION); + controller.SetPhase(BUS::phase_t::reserved); + controller.BusFree(); + EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase()); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + + controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::NONE); + controller.SetPhase(BUS::phase_t::reserved); + controller.BusFree(); + + controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI); + controller.SetPhase(BUS::phase_t::reserved); + controller.BusFree(); + + controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI); + controller.SetPhase(BUS::phase_t::reserved); + controller.BusFree(); + + controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI); + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_EXIT(controller.BusFree(), ExitedWithCode(EXIT_SUCCESS), ""); +} + +TEST(ScsiControllerTest, Selection) +{ + NiceMock bus; + NiceMock controller(bus, 0); + + controller.SetPhase(BUS::phase_t::selection); + ON_CALL(bus, GetSEL).WillByDefault(Return(true)); + ON_CALL(bus, GetBSY).WillByDefault(Return(true)); + EXPECT_CALL(bus, GetATN).Times(0); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase()); + + ON_CALL(bus, GetSEL).WillByDefault(Return(true)); + ON_CALL(bus, GetBSY).WillByDefault(Return(false)); + EXPECT_CALL(bus, GetATN).Times(0); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase()); + + ON_CALL(bus, GetSEL).WillByDefault(Return(false)); + ON_CALL(bus, GetBSY).WillByDefault(Return(false)); + EXPECT_CALL(bus, GetATN).Times(0); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase()); + + ON_CALL(bus, GetSEL).WillByDefault(Return(false)); + ON_CALL(bus, GetBSY).WillByDefault(Return(true)); + ON_CALL(bus, GetATN).WillByDefault(Return(false)); + EXPECT_CALL(bus, GetATN); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::command, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::selection); + ON_CALL(bus, GetSEL).WillByDefault(Return(false)); + ON_CALL(bus, GetBSY).WillByDefault(Return(true)); + ON_CALL(bus, GetATN).WillByDefault(Return(true)); + EXPECT_CALL(bus, GetATN); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::msgout, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::reserved); + ON_CALL(bus, GetDAT).WillByDefault(Return(0)); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()); + + ON_CALL(bus, GetDAT).WillByDefault(Return(1)); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()) << "There is no device that can be selected"; + + auto device = make_shared(0); + controller.AddDevice(device); + EXPECT_CALL(bus, SetBSY(true)); + controller.Selection(); + EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase()); +} + +TEST(ScsiControllerTest, Command) +{ + NiceMock bus; + NiceMock controller(bus, 0); + + controller.SetPhase(BUS::phase_t::command); + controller.Command(); + EXPECT_EQ(BUS::phase_t::command, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, SetMSG(false)); + EXPECT_CALL(bus, SetCD(true)); + EXPECT_CALL(bus, SetIO(false)); + controller.Command(); + EXPECT_EQ(BUS::phase_t::command, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::reserved); + ON_CALL(bus, CommandHandShake).WillByDefault(Return(6)); + EXPECT_CALL(bus, SetMSG(false)); + EXPECT_CALL(bus, SetCD(true)); + EXPECT_CALL(bus, SetIO(false)); + EXPECT_CALL(controller, Execute); + controller.Command(); + EXPECT_EQ(BUS::phase_t::command, controller.GetPhase()); +} + +TEST(ScsiControllerTest, MsgIn) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, SetMSG(true)); + EXPECT_CALL(bus, SetCD(true)); + EXPECT_CALL(bus, SetIO(true)); + controller.MsgIn(); + EXPECT_EQ(BUS::phase_t::msgin, controller.GetPhase()); + EXPECT_FALSE(controller.HasValidLength()); + EXPECT_EQ(0, controller.GetOffset()); +} + +TEST(ScsiControllerTest, MsgOut) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, SetMSG(true)); + EXPECT_CALL(bus, SetCD(true)); + EXPECT_CALL(bus, SetIO(false)); + controller.MsgOut(); + EXPECT_EQ(BUS::phase_t::msgout, controller.GetPhase()); + EXPECT_EQ(1, controller.GetLength()); + EXPECT_EQ(0, controller.GetOffset()); +} + +TEST(ScsiControllerTest, DataIn) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + controller.SetPhase(BUS::phase_t::reserved); + controller.SetLength(0); + EXPECT_CALL(controller, Status); + controller.DataIn(); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()); + + controller.SetLength(1); + EXPECT_CALL(bus, SetMSG(false)); + EXPECT_CALL(bus, SetCD(false)); + EXPECT_CALL(bus, SetIO(true)); + controller.DataIn(); + EXPECT_EQ(BUS::phase_t::datain, controller.GetPhase()); + EXPECT_EQ(0, controller.GetOffset()); +} + +TEST(ScsiControllerTest, DataOut) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + controller.SetPhase(BUS::phase_t::reserved); + controller.SetLength(0); + EXPECT_CALL(controller, Status); + controller.DataOut(); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()); + + controller.SetLength(1); + EXPECT_CALL(bus, SetMSG(false)); + EXPECT_CALL(bus, SetCD(false)); + EXPECT_CALL(bus, SetIO(false)); + controller.DataOut(); + EXPECT_EQ(BUS::phase_t::dataout, controller.GetPhase()); + EXPECT_EQ(0, controller.GetOffset()); +} + +TEST(ScsiControllerTest, Error) +{ + NiceMock bus; + MockScsiController controller(bus, 0); + + ON_CALL(bus, GetRST).WillByDefault(Return(true)); + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST()); + EXPECT_CALL(bus, Reset); + EXPECT_CALL(controller, Reset); + controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + EXPECT_EQ(status::GOOD, controller.GetStatus()); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()); + + ON_CALL(bus, GetRST).WillByDefault(Return(false)); + controller.SetPhase(BUS::phase_t::status); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST()); + EXPECT_CALL(bus, Reset).Times(0); + EXPECT_CALL(controller, Reset).Times(0); + controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::msgin); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST()); + EXPECT_CALL(bus, Reset).Times(0); + EXPECT_CALL(controller, Reset).Times(0); + controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase()); + + controller.SetPhase(BUS::phase_t::reserved); + EXPECT_CALL(bus, Acquire); + EXPECT_CALL(bus, GetRST()); + EXPECT_CALL(bus, Reset).Times(0); + EXPECT_CALL(controller, Reset).Times(0); + EXPECT_CALL(controller, Status); + controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT); + EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus()); + EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()); } TEST(ScsiControllerTest, RequestSense) { - MockScsiController controller(0); + MockScsiController controller; auto device = make_shared(0); controller.AddDevice(device); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // ALLOCATION LENGTH cmd[4] = 255; // Non-existing LUN cmd[1] = 0x20; device->SetReady(true); - EXPECT_CALL(controller, Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN, status::CHECK_CONDITION)).Times(1); - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, Status); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense)); EXPECT_EQ(status::GOOD, controller.GetStatus()) << "Illegal CHECK CONDITION for non-exsting LUN"; } diff --git a/src/raspberrypi/test/scsi_daynaport_test.cpp b/src/raspberrypi/test/scsi_daynaport_test.cpp index 8155e32a..4a4de736 100644 --- a/src/raspberrypi/test/scsi_daynaport_test.cpp +++ b/src/raspberrypi/test/scsi_daynaport_test.cpp @@ -13,8 +13,7 @@ TEST(ScsiDaynaportTest, Inquiry) { - TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, scsi_level::SCSI_2, - "Dayna SCSI/Link 1.4a", 0x20, false); + TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, "Dayna SCSI/Link 1.4a", 0x20, false); } TEST(ScsiDaynaportTest, Dispatch) @@ -22,6 +21,9 @@ TEST(ScsiDaynaportTest, Dispatch) NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); + EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class"; + + // TODO Remove tests below as soon as Daynaport does not inherit from Disk anymore EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdModeSense6)) << "Non-DaynaPort commands inherited from Disk must not be supported"; EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdModeSelect6)) @@ -37,21 +39,88 @@ TEST(ScsiDaynaportTest, TestUnitReady) NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdTestUnitReady)) << "TEST UNIT READY must never fail"; EXPECT_EQ(status::GOOD, controller.GetStatus()); } +TEST(ScsiDaynaportTest, Read) +{ + vector buf(0); + NiceMock controller(0); + auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, controller)); + + vector& cmd = controller.GetCmd(); + + // ALLOCATION LENGTH + cmd[4] = 1; + EXPECT_EQ(0, daynaport->Read(cmd, buf, 0)) << "Trying to read the root sector must fail"; +} + +TEST(ScsiDaynaportTest, WriteCheck) +{ + vector buf(0); + NiceMock controller(0); + auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, controller)); + + EXPECT_THROW(daynaport->WriteCheck(0), scsi_exception); +} + +TEST(ScsiDaynaportTest, WriteBytes) +{ + vector buf(0); + NiceMock controller(0); + auto daynaport = dynamic_pointer_cast(CreateDevice(SCDP, controller)); + + vector& cmd = controller.GetCmd(); + + // Unknown data format + cmd[5] = 0xff; + EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0)); +} + +TEST(ScsiDaynaportTest, Read6) +{ + NiceMock controller(0); + auto daynaport = CreateDevice(SCDP, controller); + + vector& cmd = controller.GetCmd(); + + cmd[5] = 0xff; + EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdRead6), scsi_exception) << "Invalid data format"; +} + +TEST(ScsiDaynaportTest, Write6) +{ + NiceMock controller(0); + auto daynaport = CreateDevice(SCDP, controller); + + vector& cmd = controller.GetCmd(); + + cmd[5] = 0x00; + EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length"; + + cmd[3] = -1; + cmd[4] = -8; + cmd[5] = 0x80; + EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length"; + + cmd[3] = 0; + cmd[4] = 0; + cmd[5] = 0xff; + EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length"; +} + TEST(ScsiDaynaportTest, TestRetrieveStats) { NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // ALLOCATION LENGTH cmd[4] = 255; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdRetrieveStats)); } @@ -60,19 +129,19 @@ TEST(ScsiDaynaportTest, SetInterfaceMode) NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // Unknown interface command EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode), scsi_exception); // Not implemented, do nothing cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMODE; - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode)); EXPECT_EQ(status::GOOD, controller.GetStatus()); cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMAC; - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode)); // Not implemented @@ -93,12 +162,12 @@ TEST(ScsiDaynaportTest, SetMcastAddr) NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdSetMcastAddr), scsi_exception) << "Length of 0 is not supported"; cmd[4] = 1; - EXPECT_CALL(controller, DataOut()).Times(1); + EXPECT_CALL(controller, DataOut()); EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetMcastAddr)); } @@ -107,7 +176,7 @@ TEST(ScsiDaynaportTest, EnableInterface) NiceMock controller(0); auto daynaport = CreateDevice(SCDP, controller); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // Enable EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdEnableInterface), scsi_exception); diff --git a/src/raspberrypi/test/scsi_host_bridge_test.cpp b/src/raspberrypi/test/scsi_host_bridge_test.cpp index 58304737..7a820fea 100644 --- a/src/raspberrypi/test/scsi_host_bridge_test.cpp +++ b/src/raspberrypi/test/scsi_host_bridge_test.cpp @@ -11,6 +11,10 @@ TEST(ScsiHostBridgeTest, Inquiry) { - TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI RASCSI BRIDGE ", 0x27, false); + TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, "RaSCSI RASCSI BRIDGE ", 0x27, false); +} + +TEST(ScsiHostBridgeTest, Dispatch) +{ + TestDispatch(SCBR); } diff --git a/src/raspberrypi/test/scsi_printer_test.cpp b/src/raspberrypi/test/scsi_printer_test.cpp index 2a960bd7..6005bd85 100644 --- a/src/raspberrypi/test/scsi_printer_test.cpp +++ b/src/raspberrypi/test/scsi_printer_test.cpp @@ -14,20 +14,39 @@ using namespace std; +TEST(ScsiPrinterTest, Init) +{ + NiceMock controller(0); + auto printer = CreateDevice(SCLP, controller); + + unordered_map params; + EXPECT_TRUE(printer->Init(params)); + + params["cmd"] = "missing_filename_specifier"; + EXPECT_FALSE(printer->Init(params)); + + params["cmd"] = "%f"; + EXPECT_TRUE(printer->Init(params)); +} + +TEST(ScsiPrinterTest, Dispatch) +{ + TestDispatch(SCLP); +} + TEST(ScsiPrinterTest, TestUnitReady) { NiceMock controller(0); auto printer = CreateDevice(SCLP, controller); - EXPECT_CALL(controller, Status()).Times(1); + EXPECT_CALL(controller, Status()); EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdTestUnitReady)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } TEST(ScsiPrinterTest, Inquiry) { - TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI SCSI PRINTER ", 0x1f, false); + TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, "RaSCSI SCSI PRINTER ", 0x1f, false); } TEST(ScsiPrinterTest, ReserveUnit) @@ -60,13 +79,37 @@ TEST(ScsiPrinterTest, SendDiagnostic) EXPECT_EQ(status::GOOD, controller.GetStatus()); } +TEST(ScsiPrinterTest, Print) +{ + NiceMock controller(0); + auto printer = CreateDevice(SCLP, controller); + + vector& cmd = controller.GetCmd(); + + EXPECT_CALL(controller, DataOut()); + EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdPrint)); + + cmd[3] = 0xff; + cmd[4] = 0xff; + EXPECT_THROW(printer->Dispatch(scsi_command::eCmdPrint), scsi_exception) << "Buffer overflow was not reported"; +} TEST(ScsiPrinterTest, StopPrint) { NiceMock controller(0); auto printer = CreateDevice(SCLP, controller); - EXPECT_CALL(controller, Status()).Times(1); - EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdStartStop)); + EXPECT_CALL(controller, Status()); + EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdStopPrint)); EXPECT_EQ(status::GOOD, controller.GetStatus()); } + +TEST(ScsiPrinterTest, WriteByteSequence) +{ + NiceMock controller(0); + auto printer = dynamic_pointer_cast(CreateDevice(SCLP, controller)); + + vector buf(1); + EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size())); + printer->Cleanup(); +} diff --git a/src/raspberrypi/test/scsicd_test.cpp b/src/raspberrypi/test/scsicd_test.cpp index d1326ec7..6528564b 100644 --- a/src/raspberrypi/test/scsicd_test.cpp +++ b/src/raspberrypi/test/scsicd_test.cpp @@ -8,26 +8,44 @@ //--------------------------------------------------------------------------- #include "mocks.h" +#include "rascsi_exceptions.h" TEST(ScsiCdTest, Inquiry) { - TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI SCSI CD-ROM ", 0x1f, true); + TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, "RaSCSI SCSI CD-ROM ", 0x1f, true); +} + +TEST(ScsiCdTest, Dispatch) +{ + TestDispatch(SCCD); } TEST(ScsiCdTest, SetUpModePages) { - map> mode_pages; + map> pages; const unordered_set sector_sizes; MockSCSICD cd(0, sector_sizes); - cd.SetUpModePages(mode_pages, 0x3f, false); - EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages"; - EXPECT_EQ(12, mode_pages[1].size()); - EXPECT_EQ(24, mode_pages[3].size()); - EXPECT_EQ(24, mode_pages[4].size()); - EXPECT_EQ(12, mode_pages[8].size()); - EXPECT_EQ(8, mode_pages[13].size()); - EXPECT_EQ(16, mode_pages[14].size()); - EXPECT_EQ(30, mode_pages[48].size()); + cd.SetUpModePages(pages, 0x3f, false); + EXPECT_EQ(7, pages.size()) << "Unexpected number of mode pages"; + EXPECT_EQ(12, pages[1].size()); + EXPECT_EQ(24, pages[3].size()); + EXPECT_EQ(24, pages[4].size()); + EXPECT_EQ(12, pages[8].size()); + EXPECT_EQ(8, pages[13].size()); + EXPECT_EQ(16, pages[14].size()); + EXPECT_EQ(30, pages[48].size()); +} + +TEST(ScsiCdTest, ReadToc) +{ + MockAbstractController controller(0); + const unordered_set sector_sizes; + auto cd = make_shared(0, sector_sizes); + + controller.AddDevice(cd); + + EXPECT_THROW(cd->Dispatch(scsi_command::eCmdReadToc), scsi_exception) << "Drive is not ready"; + + // Further testing requires filesystem access } diff --git a/src/raspberrypi/test/scsihd_nec_test.cpp b/src/raspberrypi/test/scsihd_nec_test.cpp index 412bd326..f312300f 100644 --- a/src/raspberrypi/test/scsihd_nec_test.cpp +++ b/src/raspberrypi/test/scsihd_nec_test.cpp @@ -15,21 +15,52 @@ using namespace std; TEST(ScsiHdNecTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI ", 0x1f, false); + TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "RaSCSI ", 0x1f, false, ".hdn"); } TEST(ScsiHdNecTest, SetUpModePages) { - map> mode_pages; + map> pages; MockSCSIHD_NEC hd(0); - hd.SetUpModePages(mode_pages, 0x3f, false); - EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; - EXPECT_EQ(8, mode_pages[1].size()); - EXPECT_EQ(24, mode_pages[3].size()); - EXPECT_EQ(20, mode_pages[4].size()); - EXPECT_EQ(12, mode_pages[8].size()); - EXPECT_EQ(30, mode_pages[48].size()); + hd.SetUpModePages(pages, 0x3f, false); + EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages"; + EXPECT_EQ(12, pages[1].size()); + EXPECT_EQ(24, pages[3].size()); + EXPECT_EQ(20, pages[4].size()); + EXPECT_EQ(12, pages[8].size()); + EXPECT_EQ(30, pages[48].size()); } +TEST(ScsiHdNecTest, TestAddFormatPage) +{ + map> pages; + MockSCSIHD_NEC hd(0); + + hd.SetBlockCount(0x1234); + hd.SetReady(true); + hd.SetUpModePages(pages, 0x03, false); + EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages"; + vector& page_3 = pages[3]; + EXPECT_EQ(0x80, (int)page_3[0] & 0x80); + EXPECT_EQ(0, (int)page_3[20]); + + hd.SetRemovable(true); + hd.SetUpModePages(pages, 0x03, false); + page_3 = pages[3]; + EXPECT_EQ(0x20, (int)page_3[20]); + + hd.SetUpModePages(pages, 0x03, true); + EXPECT_EQ(0xffff, GetInt16(page_3, 12)); +} + +TEST(ScsiHdNecTest, TestAddDrivePage) +{ + map> pages; + MockSCSIHD_NEC hd(0); + + hd.SetBlockCount(0x1234); + hd.SetReady(true); + hd.SetUpModePages(pages, 0x04, false); + EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages"; +} diff --git a/src/raspberrypi/test/scsihd_test.cpp b/src/raspberrypi/test/scsihd_test.cpp index 9568c5cf..02096d9f 100644 --- a/src/raspberrypi/test/scsihd_test.cpp +++ b/src/raspberrypi/test/scsihd_test.cpp @@ -11,31 +11,81 @@ #include "rascsi_exceptions.h" #include "devices/scsihd.h" -TEST(ScsiHdTest, FinalizeSetup) -{ - MockSCSIHD_NEC disk(0); - Filepath filepath; - - EXPECT_THROW(disk.FinalizeSetup(filepath, 2LL * 1024 * 1024 * 1024 * 1024 + 1, 0), io_exception); -} - TEST(ScsiHdTest, Inquiry) { - TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI ", 0x1f, false); + TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "RaSCSI ", 0x1f, false); + TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "RaSCSI ", 0x1f, false, ".hd1"); +} + +TEST(ScsiHdTest, SupportsSaveParameters) +{ + map> pages; + const unordered_set sector_sizes; + MockSCSIHD hd(0, sector_sizes, false); + + EXPECT_TRUE(hd.SupportsSaveParameters()); +} + +TEST(ScsiHdTest, FinalizeSetup) +{ + map> pages; + const unordered_set sector_sizes; + MockSCSIHD hd(0, sector_sizes, false); + + hd.SetSectorSizeInBytes(1024); + EXPECT_THROW(hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes(), 0), io_exception) + << "Unsupported drive capacity"; + + EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks"; + + hd.SetBlockCount(1); + hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024); + hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes() - 1); } TEST(ScsiHdTest, SetUpModePages) { - map> mode_pages; + map> pages; const unordered_set sector_sizes; MockSCSIHD hd(0, sector_sizes, false); - hd.SetUpModePages(mode_pages, 0x3f, false); - EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages"; - EXPECT_EQ(12, mode_pages[1].size()); - EXPECT_EQ(24, mode_pages[3].size()); - EXPECT_EQ(24, mode_pages[4].size()); - EXPECT_EQ(12, mode_pages[8].size()); - EXPECT_EQ(30, mode_pages[48].size()); + hd.SetReady(false); + hd.SetUpModePages(pages, 0x3f, false); + EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages"; + EXPECT_EQ(12, pages[1].size()); + EXPECT_EQ(24, pages[3].size()); + EXPECT_EQ(24, pages[4].size()); + EXPECT_EQ(12, pages[8].size()); + EXPECT_EQ(30, pages[48].size()); +} + +TEST(ScsiHdTest, ModeSelect) +{ + const unordered_set sector_sizes = { 512 }; + MockSCSIHD hd(0, sector_sizes, false); + vector cmd(10); + vector buf(255); + + hd.SetSectorSizeInBytes(512); + + // PF + cmd[1] = 0x10; + // Page 3 (Device Format Page) + buf[4] = 0x03; + // 512 bytes per sector + buf[16] = 0x02; + EXPECT_NO_THROW(hd.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) is supported"; + buf[4] = 0; + buf[16] = 0; + + // Page 3 (Device Format Page) + buf[8] = 0x03; + // 512 bytes per sector + buf[20] = 0x02; + EXPECT_NO_THROW(hd.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 255)) << "MODE SELECT(10) is supported"; +} + +TEST(ScsiHdTest, Dispatch) +{ + TestDispatch(SCHD); } diff --git a/src/raspberrypi/test/scsimo_test.cpp b/src/raspberrypi/test/scsimo_test.cpp index cd2c4a25..42d56874 100644 --- a/src/raspberrypi/test/scsimo_test.cpp +++ b/src/raspberrypi/test/scsimo_test.cpp @@ -11,22 +11,136 @@ TEST(ScsiMoTest, Inquiry) { - TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, scsi_level::SCSI_2, - "RaSCSI SCSI MO ", 0x1f, true); + TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "RaSCSI SCSI MO ", 0x1f, true); +} + +TEST(ScsiMoTest, SupportsSaveParameters) +{ + map> pages; + const unordered_set sector_sizes; + MockSCSIMO mo(0, sector_sizes); + + EXPECT_TRUE(mo.SupportsSaveParameters()); } TEST(ScsiMoTest, SetUpModePages) { - map> mode_pages; + map> pages; const unordered_set sector_sizes; - MockSCSIMO device(0, sector_sizes); + MockSCSIMO mo(0, sector_sizes); - device.SetUpModePages(mode_pages, 0x3f, false); - EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages"; - EXPECT_EQ(12, mode_pages[1].size()); - EXPECT_EQ(24, mode_pages[3].size()); - EXPECT_EQ(24, mode_pages[4].size()); - EXPECT_EQ(4, mode_pages[6].size()); - EXPECT_EQ(12, mode_pages[8].size()); - EXPECT_EQ(12, mode_pages[32].size()); + mo.SetReady(false); + mo.SetUpModePages(pages, 0x3f, false); + EXPECT_EQ(6, pages.size()) << "Unexpected number of mode pages"; + EXPECT_EQ(12, pages[1].size()); + EXPECT_EQ(24, pages[3].size()); + EXPECT_EQ(24, pages[4].size()); + EXPECT_EQ(4, pages[6].size()); + EXPECT_EQ(12, pages[8].size()); + EXPECT_EQ(12, pages[32].size()); } + +TEST(ScsiMoTest, TestAddVendorPage) +{ + map> pages; + const unordered_set sector_sizes; + MockSCSIMO mo(0, sector_sizes); + + mo.SetReady(true); + mo.SetUpModePages(pages, 0x21, false); + EXPECT_TRUE(pages.empty()) << "Unsupported vendor-specific page was returned"; + + mo.SetBlockCount(0x12345678); + mo.SetUpModePages(pages, 0x20, false); + EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages"; + vector& page_32 = pages[32]; + EXPECT_EQ(12, page_32.size()); + EXPECT_EQ(0, (int)page_32[2]) << "Wrong format mode"; + EXPECT_EQ(0, (int)page_32[3]) << "Wrong format type"; + EXPECT_EQ(0x12345678, GetInt32(page_32, 4)) << "Wrong number of blocks"; + EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(0, GetInt16(page_32, 10)); + + mo.SetSectorSizeInBytes(512); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(0, GetInt16(page_32, 10)); + + mo.SetBlockCount(248826); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(1024, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(1, GetInt16(page_32, 10)); + + mo.SetBlockCount(446325); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(1025, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(10, GetInt16(page_32, 10)); + + mo.SetBlockCount(1041500); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(2250, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(18, GetInt16(page_32, 10)); + + mo.SetSectorSizeInBytes(2048); + mo.SetBlockCount(0x12345678); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(0, GetInt16(page_32, 10)); + + mo.SetBlockCount(310352); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(2244, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(11, GetInt16(page_32, 10)); + + mo.SetBlockCount(605846); + mo.SetUpModePages(pages, 0x20, false); + page_32 = pages[32]; + EXPECT_EQ(4437, GetInt16(page_32, 8)) << "Wrong number of spare blocks"; + EXPECT_EQ(18, GetInt16(page_32, 10)); + + // Changeable page + mo.SetUpModePages(pages, 0x20, true); + EXPECT_EQ(0, (int)page_32[2]); + EXPECT_EQ(0, (int)page_32[3]); + EXPECT_EQ(0, GetInt32(page_32, 4)); + EXPECT_EQ(0, GetInt16(page_32, 8)); + EXPECT_EQ(0, GetInt16(page_32, 10)); +} + +TEST(ScsiMoTest, ModeSelect) +{ + const unordered_set sector_sizes = { 1024, 2048 }; + MockSCSIMO mo(0, sector_sizes); + vector cmd(10); + vector buf(255); + + mo.SetSectorSizeInBytes(2048); + + // PF + cmd[1] = 0x10; + // Page 3 (Device Format Page) + buf[4] = 0x03; + // 2048 bytes per sector + buf[16] = 0x08; + EXPECT_NO_THROW(mo.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) is supported"; + buf[4] = 0; + buf[16] = 0; + + // Page 3 (Device Format Page) + buf[8] = 0x03; + // 2048 bytes per sector + buf[20] = 0x08; + EXPECT_NO_THROW(mo.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 255)) << "MODE SELECT(10) is supported"; +} + +TEST(ScsiMoTest, Dispatch) +{ + TestDispatch(SCMO); +} + diff --git a/src/raspberrypi/test/storage_device_test.cpp b/src/raspberrypi/test/storage_device_test.cpp new file mode 100644 index 00000000..d8626db2 --- /dev/null +++ b/src/raspberrypi/test/storage_device_test.cpp @@ -0,0 +1,150 @@ +//--------------------------------------------------------------------------- +// +// SCSI Target Emulator RaSCSI Reloaded +// for Raspberry Pi +// +// Copyright (C) 2022 Uwe Seimet +// +//--------------------------------------------------------------------------- + +#include "mocks.h" +#include "rascsi_exceptions.h" +#include "devices/storage_device.h" +#include + +TEST(StorageDeviceTest, Filename) +{ + MockStorageDevice device; + + device.SetFilename("filename"); + EXPECT_EQ("filename", device.GetFilename()); +} + +TEST(StorageDeviceTest, ValidateFile) +{ + MockStorageDevice device; + + device.SetBlockCount(0); + EXPECT_THROW(device.ValidateFile("/non_existing_file"), io_exception); + + device.SetReadOnly(false); + device.SetProtectable(true); + device.SetBlockCount(1); + device.ValidateFile("/non_existing_file"); + EXPECT_TRUE(device.IsReadOnly()); + EXPECT_FALSE(device.IsProtectable()); + EXPECT_FALSE(device.IsStopped()); + EXPECT_FALSE(device.IsRemoved()); + EXPECT_FALSE(device.IsLocked()); + + device.SetReadOnly(false); + device.SetProtectable(true); + device.ValidateFile("/dev/null"); + EXPECT_FALSE(device.IsReadOnly()); + EXPECT_TRUE(device.IsProtectable()); + EXPECT_FALSE(device.IsStopped()); + EXPECT_FALSE(device.IsRemoved()); + EXPECT_FALSE(device.IsLocked()); +} + +TEST(StorageDeviceTest, MediumChanged) +{ + MockStorageDevice device; + + device.SetMediumChanged(true); + EXPECT_TRUE(device.IsMediumChanged()); + + device.SetMediumChanged(false); + EXPECT_FALSE(device.IsMediumChanged()); +} + +TEST(StorageDeviceTest, GetIdsForReservedFile) +{ + const int ID = 1; + const int LUN = 2; + + MockStorageDevice device; + device.SetFilename("filename"); + + int id; + int lun; + EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun)); + + device.ReserveFile("filename", ID, LUN); + EXPECT_TRUE(StorageDevice::GetIdsForReservedFile("filename", id, lun)); + EXPECT_EQ(ID, id); + EXPECT_EQ(LUN, lun); + + device.UnreserveFile(); + EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun)); +} + +TEST(StorageDeviceTest, UnreserveAll) +{ + MockStorageDevice device; + device.ReserveFile("filename", 2, 31); + + StorageDevice::UnreserveAll(); + int id; + int lun; + EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun)); +} + +TEST(StorageDeviceTest, GetSetReservedFiles) +{ + const int ID = 1; + const int LUN = 16; + + MockStorageDevice device; + device.ReserveFile("filename", ID, LUN); + + const unordered_map reserved_files = StorageDevice::GetReservedFiles(); + EXPECT_EQ(1, reserved_files.size()); + EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); + + StorageDevice::SetReservedFiles(reserved_files); + EXPECT_EQ(1, reserved_files.size()); + EXPECT_NE(reserved_files.end(), reserved_files.find("filename")); +} + +TEST(StorageDeviceTest, FileExists) +{ + EXPECT_FALSE(StorageDevice::FileExists("/non_existing_file")); + EXPECT_TRUE(StorageDevice::FileExists("/dev/null")); +} + +TEST(StorageDeviceTest, IsReadOnlyFile) +{ + MockStorageDevice device; + + device.SetFilename("/dev/null"); + EXPECT_FALSE(device.IsReadOnlyFile()); + + device.SetFilename("/dev/mem"); + EXPECT_TRUE(device.IsReadOnlyFile()); +} + +TEST(StorageDeviceTest, GetFileSize) +{ + MockStorageDevice device; + + const string filename = CreateTempFile(512); + device.SetFilename(filename); + const off_t size = device.GetFileSize(); + unlink(filename.c_str()); + EXPECT_EQ(512, size); + + device.SetFilename("/non_existing_file"); + EXPECT_THROW(device.GetFileSize(), io_exception); +} + +TEST(StorageDeviceTest, Dispatch) +{ + MockAbstractController controller(0); + auto device = make_shared>(); + + controller.AddDevice(device); + + EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class"; +} + diff --git a/src/raspberrypi/test/test_setup.cpp b/src/raspberrypi/test/test_setup.cpp index da528f76..3ed7cfaf 100644 --- a/src/raspberrypi/test/test_setup.cpp +++ b/src/raspberrypi/test/test_setup.cpp @@ -11,22 +11,25 @@ #include "spdlog/spdlog.h" +// Also used by the RascsiExecutor tests +bool enable_logging; //NOSONAR Must be global in order to be shared with the tests + class Environment final : public ::testing::Environment { - spdlog::level::level_enum log_level; - public: - explicit Environment(spdlog::level::level_enum level) : log_level(level) {} + Environment() = default; ~Environment() override = default; - void SetUp() override { spdlog::set_level(log_level); } + void SetUp() override { spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); } }; int main(int argc, char *[]) { // If any argument is provided the log level is set to trace - testing::AddGlobalTestEnvironment(new Environment(argc > 1 ? spdlog::level::trace : spdlog::level::off)); + enable_logging = argc > 1; + + testing::AddGlobalTestEnvironment(new Environment()); testing::InitGoogleTest(); diff --git a/src/raspberrypi/test/test_shared.cpp b/src/raspberrypi/test/test_shared.cpp index 594f9426..65babd0d 100644 --- a/src/raspberrypi/test/test_shared.cpp +++ b/src/raspberrypi/test/test_shared.cpp @@ -11,42 +11,42 @@ #include "controllers/controller_manager.h" #include "rascsi_version.h" #include "test_shared.h" +#include +#include #include using namespace std; -shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller) +shared_ptr CreateDevice(PbDeviceType type, MockAbstractController& controller, const string& extension) { DeviceFactory device_factory; auto bus = make_shared(); auto controller_manager = make_shared(*bus); - auto device = device_factory.CreateDevice(*controller_manager, type, 0, ""); + auto device = device_factory.CreateDevice(*controller_manager, type, 0, extension); controller.AddDevice(device); - controller.InitCmd(16); - return device; } -void TestInquiry(PbDeviceType type, device_type t, scsi_level l, scsi_level r, const string& ident, - int additional_length, bool removable) +void TestInquiry(PbDeviceType type, device_type t, scsi_level l, const string& ident, int additional_length, + bool removable, const string& extension) { NiceMock controller(0); - auto device = CreateDevice(type, controller); + auto device = CreateDevice(type, controller, extension); - vector& cmd = controller.InitCmd(6); + vector& cmd = controller.GetCmd(); // ALLOCATION LENGTH cmd[4] = 255; - EXPECT_CALL(controller, DataIn()).Times(1); + EXPECT_CALL(controller, DataIn()); EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry)); const vector& buffer = controller.GetBuffer(); EXPECT_EQ((int)t, buffer[0]); EXPECT_EQ(removable ? 0x80: 0x00, buffer[1]); EXPECT_EQ((int)l, buffer[2]); - EXPECT_EQ((int)r, buffer[3]); + EXPECT_EQ((int)l > (int)scsi_level::SCSI_2 ? (int)scsi_level::SCSI_2 : (int)l, buffer[3]); EXPECT_EQ(additional_length, buffer[4]); string product_data; if (ident.size() == 24) { @@ -59,3 +59,53 @@ void TestInquiry(PbDeviceType type, device_type t, scsi_level l, scsi_level r, c } EXPECT_TRUE(!memcmp(product_data.c_str(), &buffer[8], 28)); } + +void TestDispatch(PbDeviceType type) +{ + NiceMock controller(0); + auto device = CreateDevice(type, controller); + + EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class"; +} + +int OpenTempFile(string& file) +{ + char filename[] = "/tmp/rascsi_test-XXXXXX"; //NOSONAR mkstemp() requires a modifiable string + + const int fd = mkstemp(filename); + EXPECT_NE(-1, fd) << "Couldn't create temporary file '" << filename << "'"; + + file = filename; + + return fd; +} + +string CreateTempFile(int size) +{ + char filename[] = "/tmp/rascsi_test-XXXXXX"; //NOSONAR mkstemp() requires a modifiable string + vector data(size); + + const int fd = mkstemp(filename); + + const size_t count = write(fd, data.data(), data.size()); + close(fd); + EXPECT_EQ(count, data.size()) << "Couldn't create temporary file '" << string(filename) << "'"; + + return filename; +} + +int GetInt16(const vector& buf, int offset) +{ + assert(buf.size() > (size_t)offset + 1); + + return ((int)buf[offset] << 8) | (int)buf[offset + 1]; +} + +uint32_t GetInt32(const vector& buf, int offset) +{ + assert(buf.size() > (size_t)offset + 3); + + return ((uint32_t)buf[offset] << 24) | ((uint32_t)buf[offset + 1] << 16) | + ((uint32_t)buf[offset + 2] << 8) | (uint32_t)buf[offset + 3]; +} + diff --git a/src/raspberrypi/test/test_shared.h b/src/raspberrypi/test/test_shared.h index 6e2f5ed2..90c839a5 100644 --- a/src/raspberrypi/test/test_shared.h +++ b/src/raspberrypi/test/test_shared.h @@ -15,11 +15,21 @@ #include using namespace std; +using namespace rascsi_interface; class PrimaryDevice; class MockAbstractController; -shared_ptr CreateDevice(rascsi_interface::PbDeviceType, MockAbstractController&); +shared_ptr CreateDevice(PbDeviceType, MockAbstractController&, const string& = ""); + +void TestInquiry(PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, const string&, + int, bool, const string& = ""); + +void TestDispatch(PbDeviceType); + +int OpenTempFile(string&); +string CreateTempFile(int); + +int GetInt16(const vector&, int); +uint32_t GetInt32(const vector&, int); -void TestInquiry(rascsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level, scsi_defs::scsi_level, - const string&, int, bool);