mirror of
https://github.com/akuker/RASCSI.git
synced 2025-04-09 12:38:27 +00:00
Resolved issues 772, 827, 909; added numerous unit tests; code cleanup (#915)
* Resolved issues 772, 827, 909 * Added numerous unit tests * Code cleanup * Improved type safety by using PbDeviceType instead of string * Do not flush cache on failed STOP UNIT * Error message and error handling updates * Removed duplicate code * Use map for mapping shift counts * Reject read/write access if the drive has 0 sectors * Updated logging configuration for tests
This commit is contained in:
parent
198c10f70a
commit
f3553c5480
@ -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.
|
||||
|
@ -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: <https://www.github.com/akuker/RASCSI/wiki/>
|
||||
Full documentation is available at:
|
||||
<https://www.github.com/akuker/RASCSI/wiki/>
|
||||
|
||||
rascsi(1)
|
||||
rascsi(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'.
|
||||
|
@ -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: <https://www.github.com/akuker/RASCSI/wiki/>
|
||||
Full documentation is available at:
|
||||
<https://www.github.com/akuker/RASCSI/wiki/>
|
||||
|
||||
rascsi(1)
|
||||
rascsi(1)
|
||||
|
@ -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
|
||||
|
@ -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 <sstream>
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -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<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
|
||||
{
|
||||
unordered_set<shared_ptr<PrimaryDevice>> 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<PrimaryDevice> device)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AbstractController::DeleteDevice(const shared_ptr<PrimaryDevice> device)
|
||||
bool AbstractController::RemoveDevice(const shared_ptr<PrimaryDevice> 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
|
||||
|
@ -41,7 +41,9 @@ public:
|
||||
};
|
||||
|
||||
using ctrl_t = struct _ctrl_t {
|
||||
vector<int> cmd; // Command data, dynamically allocated per received command
|
||||
// Command data, dynamically resized if required
|
||||
vector<int> cmd = vector<int>(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<shared_ptr<PrimaryDevice>> GetDevices() const;
|
||||
shared_ptr<PrimaryDevice> GetDeviceForLun(int) const;
|
||||
bool AddDevice(shared_ptr<PrimaryDevice>);
|
||||
bool DeleteDevice(const shared_ptr<PrimaryDevice>);
|
||||
bool RemoveDevice(const shared_ptr<PrimaryDevice>);
|
||||
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<scsi_defs::scsi_command>(ctrl.cmd[0]); }
|
||||
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
|
||||
|
||||
void ProcessPhase();
|
||||
|
||||
vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; }
|
||||
vector<int>& 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; }
|
||||
};
|
||||
|
@ -17,18 +17,22 @@ using namespace std;
|
||||
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> 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<ScsiController>(bus, id);
|
||||
|
||||
if (controller->AddDevice(device)) {
|
||||
controllers[id] = controller;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return controller->AddDevice(device);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
|
||||
@ -79,7 +83,7 @@ void ControllerManager::ResetAllControllers() const
|
||||
|
||||
shared_ptr<PrimaryDevice> 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);
|
||||
}
|
||||
|
||||
|
@ -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<ModePageDevice>(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<BYTE>& buf)
|
||||
case scsi_command::eCmdRead16:
|
||||
// Read from disk
|
||||
try {
|
||||
ctrl.length = (dynamic_pointer_cast<Disk>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next);
|
||||
SetLength(dynamic_pointer_cast<Disk>(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<SCSIBR>(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;
|
||||
}
|
||||
|
@ -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 = {};
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -9,16 +9,18 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rascsi_interface.pb.h"
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
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<string, string>&);
|
||||
|
||||
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<string, string> GetParams() const { return params; }
|
||||
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "scsi_daynaport.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "host_services.h"
|
||||
#include "rasutil.h"
|
||||
#include "device_factory.h"
|
||||
#include <ifaddrs.h>
|
||||
#include <sys/ioctl.h>
|
||||
@ -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<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type,
|
||||
int lun, const string& filename)
|
||||
{
|
||||
@ -102,7 +91,7 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
|
||||
shared_ptr<PrimaryDevice> 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<SCSIHD_NEC>(lun);
|
||||
} else {
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes[SCHD], false,
|
||||
@ -114,35 +103,21 @@ shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
|
||||
device->SetProduct("FIREBALL");
|
||||
}
|
||||
}
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
break;
|
||||
}
|
||||
|
||||
case SCRM:
|
||||
device = make_shared<SCSIHD>(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<SCSIMO>(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<SCSICD>(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<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
|
||||
device = make_shared<SCSIBR>(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<PrimaryDevice> 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<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& c
|
||||
case SCLP:
|
||||
device = make_shared<SCSIPrinter>(lun);
|
||||
device->SetProduct("SCSI PRINTER");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCLP]);
|
||||
break;
|
||||
|
||||
@ -191,14 +163,6 @@ const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type)
|
||||
return it != sector_sizes.end() ? it->second : empty_set;
|
||||
}
|
||||
|
||||
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(const string& type) const
|
||||
{
|
||||
PbDeviceType t = UNDEFINED;
|
||||
PbDeviceType_Parse(type, &t);
|
||||
|
||||
return GetSectorSizes(t);
|
||||
}
|
||||
|
||||
const unordered_map<string, string>& DeviceFactory::GetDefaultParams(PbDeviceType type) const
|
||||
{
|
||||
const auto& it = default_params.find(type);
|
||||
|
@ -35,15 +35,12 @@ public:
|
||||
shared_ptr<PrimaryDevice> CreateDevice(const ControllerManager&, PbDeviceType, int, const string&);
|
||||
PbDeviceType GetTypeForFile(const string&) const;
|
||||
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
|
||||
const unordered_set<uint32_t>& GetSectorSizes(const string&) const;
|
||||
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) const;
|
||||
list<string> GetNetworkInterfaces() const;
|
||||
const unordered_map<string, PbDeviceType>& GetExtensionMapping() const { return extension_mapping; }
|
||||
|
||||
private:
|
||||
|
||||
string GetExtension(const string&) const;
|
||||
|
||||
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
|
||||
|
||||
unordered_map<PbDeviceType, unordered_map<string, string>> default_params;
|
||||
|
@ -23,20 +23,19 @@
|
||||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
unordered_map<string, id_set> Disk::reserved_files;
|
||||
const unordered_map<uint32_t, uint32_t> 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<DiskCache>(path, size_shift_count, (uint32_t)blocks, image_offset);
|
||||
Filepath path;
|
||||
path.SetPath(GetFilename().c_str());
|
||||
cache = make_unique<DiskCache>(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<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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<int, vector<byte>>& 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<int, vector<byte>>& 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<int, vector<byte>>& pages, bool changeable) const
|
||||
pages[8] = buf;
|
||||
}
|
||||
|
||||
void Disk::AddVendorPage(map<int, vector<byte>>&, 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<int>&, vector<BYTE>& buf, uint64_t block)
|
||||
{
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
@ -620,7 +492,7 @@ int Disk::Read(const vector<int>&, vector<BYTE>& 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<int>&, const vector<BYTE>& buf, uint64_t block)
|
||||
{
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
@ -662,7 +533,7 @@ void Disk::Write(const vector<int>&, const vector<BYTE>& 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<BYTE>& 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<BYTE>& 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<uint32_t> 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;
|
||||
}
|
||||
|
@ -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 <string>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
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<string, id_set> 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<string, id_set> GetReservedFiles() { return reserved_files; }
|
||||
static void SetReservedFiles(const unordered_map<string, id_set>& 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<int>&, vector<BYTE>&) const override;
|
||||
int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
|
||||
|
||||
static const unordered_map<uint32_t, uint32_t> 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, vector<byte>>&, int, bool) const override;
|
||||
virtual void AddErrorPage(map<int, vector<byte>>&, bool) const;
|
||||
void AddErrorPage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddFormatPage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddDrivePage(map<int, vector<byte>>&, bool) const;
|
||||
void AddCachePage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const;
|
||||
|
||||
unordered_set<uint32_t> GetSectorSizes() const;
|
||||
void SetSectorSizes(const unordered_set<uint32_t>& 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);
|
||||
};
|
||||
|
@ -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<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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<int, vector<byte>>& pages, int page, bool
|
||||
|
||||
void HostServices::AddRealtimeClockPage(map<int, vector<byte>>& pages, bool changeable) const
|
||||
{
|
||||
vector<byte> buf(10);
|
||||
|
||||
if (!changeable) {
|
||||
time_t t = time(nullptr);
|
||||
tm localtime;
|
||||
@ -167,8 +148,8 @@ void HostServices::AddRealtimeClockPage(map<int, vector<byte>>& pages, bool chan
|
||||
// Ignore leap second for simplicity
|
||||
datetime.second = (uint8_t)(localtime.tm_sec < 60 ? localtime.tm_sec : 59);
|
||||
|
||||
vector<byte> buf(10);
|
||||
memcpy(&buf[2], &datetime, sizeof(datetime));
|
||||
pages[32] = buf;
|
||||
}
|
||||
|
||||
pages[32] = buf;
|
||||
}
|
||||
|
@ -30,8 +30,6 @@ public:
|
||||
vector<byte> InquiryInternal() const override;
|
||||
void TestUnitReady() override;
|
||||
|
||||
bool SupportsFile() const override { return false; }
|
||||
|
||||
protected:
|
||||
|
||||
void SetUpModePages(map<int, vector<byte>>&, int, bool) const override;
|
||||
|
@ -20,5 +20,4 @@ public:
|
||||
virtual ~ScsiMmcCommands() = default;
|
||||
|
||||
virtual void ReadToc() = 0;
|
||||
virtual void GetEventStatusNotification() = 0;
|
||||
};
|
||||
|
@ -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<int>& cdb, vector<BYTE>& buf, int offset, int max_length) const
|
||||
int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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<int>&, const vector<BYTE>&, int) const
|
||||
void ModePageDevice::ModeSelect(scsi_command, const vector<int>&, const vector<BYTE>&, 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);
|
||||
}
|
||||
|
@ -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<int>&, const vector<BYTE>&, int) const;
|
||||
virtual void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<BYTE>&, int) const;
|
||||
|
||||
protected:
|
||||
|
||||
int AddModePages(const vector<int>&, vector<BYTE>&, int, int) const;
|
||||
bool SupportsSaveParameters() const { return supports_save_parameters; }
|
||||
void SupportsSaveParameters(bool b) { supports_save_parameters = b; }
|
||||
int AddModePages(const vector<int>&, vector<BYTE>&, int, int, int) const;
|
||||
virtual void SetUpModePages(map<int, vector<byte>>&, int, bool) const = 0;
|
||||
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const {
|
||||
// Nothing to add by default
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@ -36,6 +41,8 @@ private:
|
||||
|
||||
Dispatcher<ModePageDevice> dispatcher;
|
||||
|
||||
bool supports_save_parameters = false;
|
||||
|
||||
virtual int ModeSense6(const vector<int>&, vector<BYTE>&) const = 0;
|
||||
virtual int ModeSense10(const vector<int>&, vector<BYTE>&) 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;
|
||||
};
|
||||
|
@ -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<BYTE>&, 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;
|
||||
}
|
||||
|
@ -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<BYTE>&, 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<string, string>&) { 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<string, string>&) { return false; };
|
||||
void Reset() override;
|
||||
|
||||
virtual void FlushCache() {
|
||||
// Devices with a cache have to implement this method
|
||||
@ -45,6 +50,12 @@ protected:
|
||||
virtual vector<byte> 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<byte> HandleRequestSense() const;
|
||||
|
||||
Dispatcher<PrimaryDevice> dispatcher;
|
||||
|
||||
int send_delay = BUS::SEND_NO_DELAY;
|
||||
|
||||
int reserving_initiator = NOT_RESERVED;
|
||||
};
|
||||
|
@ -13,8 +13,10 @@
|
||||
|
||||
using namespace scsi_defs;
|
||||
|
||||
void scsi_command_util::ModeSelect(const vector<int>& cdb, const vector<BYTE>& buf, int length, int sector_size)
|
||||
void scsi_command_util::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<BYTE>& 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<int>& cdb, const vector<BYTE>& 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 {
|
||||
|
@ -11,6 +11,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scsi.h"
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
@ -18,7 +19,7 @@ using namespace std;
|
||||
|
||||
namespace scsi_command_util
|
||||
{
|
||||
void ModeSelect(const vector<int>&, const vector<BYTE>&, int, int);
|
||||
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<BYTE>&, int, int);
|
||||
void EnrichFormatPage(map<int, vector<byte>>&, bool, int);
|
||||
void AddAppleVendorModePage(map<int, vector<byte>>&, bool);
|
||||
|
||||
|
@ -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<string, string>& 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;
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ public:
|
||||
~SCSIDaynaPort() override = default;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
void Open(const Filepath& path) override;
|
||||
void Open() override;
|
||||
|
||||
// Commands
|
||||
vector<byte> InquiryInternal() const override;
|
||||
@ -66,7 +66,6 @@ public:
|
||||
void SetInterfaceMode();
|
||||
void SetMcastAddr();
|
||||
void EnableInterface();
|
||||
int GetSendDelay() const override;
|
||||
|
||||
bool Dispatch(scsi_command) override;
|
||||
|
||||
|
@ -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<string, string>& 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)
|
||||
|
@ -38,6 +38,9 @@ public:
|
||||
bool Init(const unordered_map<string, string>&) 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<byte> InquiryInternal() const override;
|
||||
int GetMessage10(const vector<int>&, vector<BYTE>&);
|
||||
|
@ -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 <sys/stat.h>
|
||||
#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<string, string>& 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<byte> 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<BYTE>& 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()
|
||||
|
@ -30,20 +30,18 @@ public:
|
||||
bool Dispatch(scsi_command) override;
|
||||
|
||||
bool Init(const unordered_map<string, string>&) override;
|
||||
void Cleanup();
|
||||
|
||||
vector<byte> 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<BYTE>&, 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;
|
||||
};
|
||||
|
@ -24,12 +24,15 @@
|
||||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCCD", lun)
|
||||
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& 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<CDTrack>();
|
||||
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<CDTrack>();
|
||||
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<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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<int>& cdb, vector<BYTE>& 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
|
||||
|
@ -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<byte> InquiryInternal() const override;
|
||||
@ -50,13 +49,12 @@ private:
|
||||
void AddCDROMPage(map<int, vector<byte>>&, bool) const;
|
||||
void AddCDDAPage(map<int, vector<byte>>&, 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
|
||||
|
||||
|
@ -18,78 +18,75 @@
|
||||
#include "fileio.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "scsi_command_util.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace scsi_command_util;
|
||||
|
||||
SCSIHD::SCSIHD(int lun, const unordered_set<uint32_t>& 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<byte> SCSIHD::InquiryInternal() const
|
||||
@ -97,9 +94,9 @@ vector<byte> SCSIHD::InquiryInternal() const
|
||||
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level, IsRemovable());
|
||||
}
|
||||
|
||||
void SCSIHD::ModeSelect(const vector<int>& cdb, const vector<BYTE>& buf, int length) const
|
||||
void SCSIHD::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<BYTE>& 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<int, vector<byte>>& pages, bool changeable) const
|
||||
|
@ -17,30 +17,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "disk.h"
|
||||
#include "filepath.h"
|
||||
#include <string>
|
||||
|
||||
class SCSIHD : public Disk
|
||||
{
|
||||
static constexpr const char *DEFAULT_PRODUCT = "SCSI HD";
|
||||
const string DEFAULT_PRODUCT = "SCSI HD";
|
||||
|
||||
public:
|
||||
|
||||
SCSIHD(int, const unordered_set<uint32_t>&, 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<byte> InquiryInternal() const override;
|
||||
void ModeSelect(const vector<int>&, const vector<BYTE>&, int) const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<BYTE>&, int) const override;
|
||||
|
||||
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
|
||||
void AddVendorPage(map<int, vector<byte>>&, int, bool) const override;
|
||||
|
||||
private:
|
||||
|
||||
string GetProductData() const;
|
||||
|
||||
using super = Disk;
|
||||
|
||||
scsi_defs::scsi_level scsi_level;
|
||||
|
@ -23,42 +23,23 @@ using namespace scsi_command_util;
|
||||
|
||||
const unordered_set<uint32_t> 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<BYTE, 512> 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<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BYTE, 512>& 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<byte> SCSIHD_NEC::InquiryInternal() const
|
||||
@ -137,15 +130,6 @@ vector<byte> SCSIHD_NEC::InquiryInternal() const
|
||||
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false);
|
||||
}
|
||||
|
||||
void SCSIHD_NEC::AddErrorPage(map<int, vector<byte>>& pages, bool) const
|
||||
{
|
||||
vector<byte> 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<int, vector<byte>>& pages, bool changeable) const
|
||||
{
|
||||
vector<byte> buf(24);
|
||||
@ -196,3 +180,13 @@ void SCSIHD_NEC::AddDrivePage(map<int, vector<byte>>& 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];
|
||||
}
|
||||
|
@ -19,6 +19,8 @@
|
||||
#include "scsihd.h"
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
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<byte> InquiryInternal() const override;
|
||||
|
||||
void AddErrorPage(map<int, vector<byte>>&, bool) const override;
|
||||
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
|
||||
void AddDrivePage(map<int, vector<byte>>&, bool) const override;
|
||||
|
||||
private:
|
||||
|
||||
pair<int, int> SetParameters(const string&, const array<BYTE, 512>&, int);
|
||||
|
||||
static int GetInt16LittleEndian(const BYTE *);
|
||||
static int GetInt32LittleEndian(const BYTE *);
|
||||
|
||||
static const unordered_set<uint32_t> sector_sizes;
|
||||
|
||||
// Image file offset (NEC only)
|
||||
|
@ -19,7 +19,7 @@
|
||||
|
||||
using namespace scsi_command_util;
|
||||
|
||||
SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCMO", lun)
|
||||
SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk(SCMO, lun)
|
||||
{
|
||||
SetSectorSizes(sector_sizes);
|
||||
|
||||
@ -31,23 +31,25 @@ SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& 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<int, vector<byte>>& pages, bool) const
|
||||
// Do not report update blocks
|
||||
}
|
||||
|
||||
void SCSIMO::ModeSelect(const vector<int>& cdb, const vector<BYTE>& buf, int length) const
|
||||
void SCSIMO::ModeSelect(scsi_command cmd, const vector<int>& cdb, const vector<BYTE>& 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<int, vector<byte>>& pages, int page, bool changeable) const
|
||||
{
|
||||
// Page code 20h
|
||||
if (page != 0x20 && page != 0x3f) {
|
||||
return;
|
||||
}
|
||||
@ -126,29 +139,6 @@ void SCSIMO::AddVendorPage(map<int, vector<byte>>& 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;
|
||||
|
@ -15,7 +15,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "disk.h"
|
||||
#include "filepath.h"
|
||||
|
||||
using Geometry = pair<uint32_t, uint32_t>;
|
||||
|
||||
@ -26,10 +25,10 @@ public:
|
||||
SCSIMO(int, const unordered_set<uint32_t>&);
|
||||
~SCSIMO() override = default;
|
||||
|
||||
void Open(const Filepath&) override;
|
||||
void Open() override;
|
||||
|
||||
vector<byte> InquiryInternal() const override;
|
||||
void ModeSelect(const vector<int>&, const vector<BYTE>&, int) const override;
|
||||
void ModeSelect(scsi_defs::scsi_command, const vector<int>&, const vector<BYTE>&, int) const override;
|
||||
|
||||
protected:
|
||||
|
||||
|
103
src/raspberrypi/devices/storage_device.cpp
Normal file
103
src/raspberrypi/devices/storage_device.cpp
Normal file
@ -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 <unistd.h>
|
||||
#include <filesystem>
|
||||
|
||||
using namespace std;
|
||||
using namespace filesystem;
|
||||
|
||||
unordered_map<string, id_set> 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 + "'");
|
||||
}
|
70
src/raspberrypi/devices/storage_device.h
Normal file
70
src/raspberrypi/devices/storage_device.h
Normal file
@ -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 <unordered_map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
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<string, id_set> GetReservedFiles() { return reserved_files; }
|
||||
static void SetReservedFiles(const unordered_map<string, id_set>& 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<string, id_set> reserved_files;
|
||||
};
|
@ -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__)
|
||||
|
@ -10,7 +10,6 @@
|
||||
|
||||
#include "sm_reports.h"
|
||||
#include "log.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "string.h"
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
#include "os.h"
|
||||
#include "log.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
@ -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");
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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 <netinet/in.h>
|
||||
#include <csignal>
|
||||
|
@ -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);
|
||||
|
@ -21,7 +21,6 @@
|
||||
#include "protobuf_util.h"
|
||||
#include "command_context.h"
|
||||
#include "rasutil.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include "rascsi_executor.h"
|
||||
#include <sstream>
|
||||
|
||||
@ -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<PrimaryDevice> 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<PrimaryDevice> device, bool dryRun) const
|
||||
bool RascsiExecutor::Stop(shared_ptr<PrimaryDevice> 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<PrimaryDevice> device, bool dryRun) const
|
||||
bool RascsiExecutor::Eject(shared_ptr<PrimaryDevice> 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<PrimaryDevice> device, bool dryRun) const
|
||||
bool RascsiExecutor::Protect(shared_ptr<PrimaryDevice> 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<PrimaryDevice> device, bool dryRun) cons
|
||||
bool RascsiExecutor::Unprotect(shared_ptr<PrimaryDevice> 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<Disk>(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<StorageDevice>(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<string, string> 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<PrimaryDevice> device, bool dryRun) const
|
||||
{
|
||||
if (!device->IsRemoved()) {
|
||||
auto storage_device = dynamic_pointer_cast<StorageDevice>(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<Disk>(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<Disk>(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_ptr<PrimaryDev
|
||||
}
|
||||
|
||||
if (!dryRun) {
|
||||
if (!controller->DeleteDevice(device)) {
|
||||
if (!controller->RemoveDevice(device)) {
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
|
||||
}
|
||||
|
||||
@ -426,12 +417,12 @@ bool RascsiExecutor::Detach(const CommandContext& context, shared_ptr<PrimaryDev
|
||||
return context.ReturnLocalizedError(LocalizationKey::ERROR_DETACH);
|
||||
}
|
||||
|
||||
if (auto disk = dynamic_pointer_cast<Disk>(device); disk != nullptr) {
|
||||
disk->UnreserveFile();
|
||||
if (auto storage_device = dynamic_pointer_cast<StorageDevice>(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<PrimaryDev
|
||||
void RascsiExecutor::DetachAll()
|
||||
{
|
||||
controller_manager.DeleteAllControllers();
|
||||
Disk::UnreserveAll();
|
||||
StorageDevice::UnreserveAll();
|
||||
|
||||
LOGINFO("Detached all devices")
|
||||
}
|
||||
@ -510,7 +501,7 @@ string RascsiExecutor::SetReservedIds(string_view ids)
|
||||
}
|
||||
}
|
||||
|
||||
unordered_set<int> reserved;
|
||||
set<int> 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<PrimaryDevice> device,
|
||||
bool RascsiExecutor::ValidateImageFile(const CommandContext& context, shared_ptr<StorageDevice> storage_device,
|
||||
const string& filename, string& full_path) const
|
||||
{
|
||||
if (!device->SupportsFile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto disk = dynamic_pointer_cast<Disk>(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<PrimaryDevice> RascsiExecutor::CreateDevice(const CommandContext& con
|
||||
return device;
|
||||
}
|
||||
|
||||
bool RascsiExecutor::SetSectorSize(const CommandContext& context, const string& type,
|
||||
shared_ptr<PrimaryDevice> device, int block_size) const
|
||||
bool RascsiExecutor::SetSectorSize(const CommandContext& context, shared_ptr<PrimaryDevice> device, int block_size) const
|
||||
{
|
||||
if (block_size) {
|
||||
auto disk = dynamic_pointer_cast<Disk>(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<PrimaryDevice> 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;
|
||||
|
@ -42,7 +42,7 @@ public:
|
||||
|
||||
unordered_set<int> 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<PrimaryDevice>, 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<PrimaryDevice>, const string&, string&) const;
|
||||
bool ValidateImageFile(const CommandContext&, shared_ptr<StorageDevice>, 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<PrimaryDevice> CreateDevice(const CommandContext&, const PbDeviceType, int, const string&) const;
|
||||
bool SetSectorSize(const CommandContext&, const string& type, shared_ptr<PrimaryDevice>, int) const;
|
||||
bool SetSectorSize(const CommandContext&, shared_ptr<PrimaryDevice>, int) const;
|
||||
|
||||
static bool ValidationOperationAgainstDevice(const CommandContext&, const shared_ptr<PrimaryDevice>,
|
||||
static bool ValidateOperationAgainstDevice(const CommandContext&, const shared_ptr<PrimaryDevice>,
|
||||
const PbOperation&);
|
||||
static bool ValidateIdAndLun(const CommandContext&, int, int);
|
||||
static bool SetProductData(const CommandContext&, const PbDeviceDefinition&, shared_ptr<PrimaryDevice>);
|
||||
|
@ -10,8 +10,6 @@
|
||||
#include <unistd.h>
|
||||
#include <pwd.h>
|
||||
#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();
|
||||
|
@ -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;
|
||||
|
@ -31,17 +31,14 @@ unique_ptr<PbDeviceProperties> 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<const Disk *>(&device);
|
||||
if (disk != nullptr) {
|
||||
Filepath filepath;
|
||||
disk->GetPath(filepath);
|
||||
const auto storage_device = dynamic_cast<const StorageDevice *>(&device);
|
||||
if (storage_device != nullptr) {
|
||||
auto image_file = make_unique<PbImageFile>().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<PbOperationInfo> 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();
|
||||
|
@ -51,7 +51,7 @@ private:
|
||||
|
||||
int max_luns;
|
||||
|
||||
const list<string> log_levels = { "trace", "debug", "info", "warn", "err", "critical", "off" };
|
||||
const list<string> log_levels = { "trace", "debug", "info", "warn", "err", "off" };
|
||||
|
||||
unique_ptr<PbDeviceProperties> GetDeviceProperties(const Device&) const;
|
||||
void GetDevice(const Device&, PbDevice&, const string&) const;
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -10,19 +10,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "scsi.h"
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
|
||||
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; }
|
||||
};
|
||||
|
@ -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).
|
||||
|
@ -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 <unistd.h>
|
||||
#include <clocale>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
|
||||
// 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<PbDeviceDefinition> 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;
|
||||
}
|
||||
|
@ -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 <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <iostream>
|
||||
@ -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)
|
||||
|
@ -12,52 +12,52 @@
|
||||
#include "protobuf_serializer.h"
|
||||
#include "rascsi_interface.pb.h"
|
||||
#include "rasctl_display.h"
|
||||
#include <netdb.h>
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
||||
|
@ -87,7 +87,7 @@ string ras_util::ListDevices(const list<PbDevice>& 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<PbDevice>& 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
|
||||
}
|
||||
|
@ -23,5 +23,7 @@ namespace ras_util
|
||||
string Banner(const string&);
|
||||
string ListDevices(const list<rascsi_interface::PbDevice>&);
|
||||
|
||||
string GetExtensionLowerCase(const string&);
|
||||
|
||||
void FixCpu(int);
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -12,7 +12,6 @@
|
||||
#include "log.h"
|
||||
#include "hal/gpiobus.h"
|
||||
#include "rascsi_version.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <sys/time.h>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
|
@ -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<int>& cmd = controller.InitCmd(1);
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
cmd[0] = 0x12;
|
||||
EXPECT_EQ(0x12, (int)controller.GetOpcode());
|
||||
@ -133,13 +168,24 @@ TEST(AbstractControllerTest, GetLun)
|
||||
|
||||
MockAbstractController controller(0);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(2);
|
||||
vector<int>& 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);
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "rascsi/command_context.h"
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -43,8 +43,6 @@ TEST(DeviceFactoryTest, GetSectorSizes)
|
||||
DeviceFactory device_factory;
|
||||
unordered_set<uint32_t> 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());
|
||||
|
@ -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());
|
||||
|
@ -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<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(10);
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<int>& cmd = controller.InitCmd(16);
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockDisk>();
|
||||
disk->SetRemovable(true);
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReserve6));
|
||||
vector<int>& 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<MockDisk>();
|
||||
|
||||
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<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& 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));
|
||||
}
|
||||
|
48
src/raspberrypi/test/gpiobus_test.cpp
Normal file
48
src/raspberrypi/test/gpiobus_test.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "hal/gpiobus.h"
|
||||
#include <unordered_set>
|
||||
|
||||
TEST(GpioBusTest, GetCommandByteCount)
|
||||
{
|
||||
unordered_set<int> 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -14,20 +14,24 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
TEST(HostServicesTest, Dispatch)
|
||||
{
|
||||
TestDispatch(SCHS);
|
||||
}
|
||||
|
||||
TEST(HostServicesTest, TestUnitReady)
|
||||
{
|
||||
NiceMock<MockAbstractController> 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<MockAbstractController> controller(0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<BYTE>& 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<MockAbstractController> controller(0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(10);
|
||||
vector<int>& 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<BYTE>& 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<int, vector<byte>> mode_pages;
|
||||
map<int, vector<byte>> 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());
|
||||
}
|
||||
|
33
src/raspberrypi/test/localizer_test.cpp
Normal file
33
src/raspberrypi/test/localizer_test.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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"));
|
||||
}
|
@ -12,14 +12,18 @@
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#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<int>& 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<int>& 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<MockBus> 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<byte>, 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<int>&, vector<BYTE>&), (const override));
|
||||
MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&), (const override));
|
||||
|
||||
explicit MockModePageDevice() : ModePageDevice("test", 0) {}
|
||||
MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {}
|
||||
~MockModePageDevice() override = default;
|
||||
|
||||
void SetUpModePages(map<int, vector<byte>>& pages, int page, bool) const override {
|
||||
// Return dummy data for other pages than page 0
|
||||
if (page) {
|
||||
vector<byte> buf(255);
|
||||
vector<byte> buf(32);
|
||||
pages[page] = buf;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class MockPage0ModePageDevice : public MockModePageDevice
|
||||
{
|
||||
FRIEND_TEST(ModePageDeviceTest, Page0);
|
||||
|
||||
public:
|
||||
|
||||
using MockModePageDevice::MockModePageDevice;
|
||||
|
||||
void SetUpModePages(map<int, vector<byte>>& pages, int, bool) const override {
|
||||
// Return dummy data for pages 0 and 1
|
||||
vector<byte> 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<byte>, InquiryInternal, (), (const));
|
||||
MOCK_METHOD(void, Open, (), (override));
|
||||
MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&), (const override));
|
||||
MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&), (const override));
|
||||
MOCK_METHOD(void, SetUpModePages, ((map<int, vector<byte>>&), 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<byte>, 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<PrimaryDevice>, bool), (const));
|
||||
MOCK_METHOD(bool, Stop, (shared_ptr<PrimaryDevice>, bool), (const));
|
||||
|
||||
using RascsiExecutor::RascsiExecutor;
|
||||
};
|
||||
|
@ -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<int> cdb(6);
|
||||
vector<BYTE> 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<int> cdb(6);
|
||||
vector<BYTE> 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<int, vector<byte>> 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<NiceMock<MockModePageDevice>>();
|
||||
|
||||
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<int> cmd;
|
||||
vector<BYTE> 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<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<int>& cmd = controller.InitCmd(10);
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
EXPECT_CALL(controller, DataOut()).Times(1);
|
||||
EXPECT_CALL(controller, DataOut());
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSelect10));
|
||||
|
||||
cmd[1] = 0x01;
|
||||
|
@ -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<MockAbstractController> controller(0);
|
||||
auto device = make_shared<MockPrimaryDevice>(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<MockAbstractController> controller(0);
|
||||
auto device = make_shared<MockPrimaryDevice>(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<MockAbstractController> controller(0);
|
||||
auto device = make_shared<MockPrimaryDevice>(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<MockAbstractController> controller(0);
|
||||
auto device = make_shared<MockPrimaryDevice>(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<MockAbstractController> controller(0);
|
||||
auto controller = make_shared<NiceMock<MockAbstractController>>(0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
device->SetController(&controller);
|
||||
controller->AddDevice(device);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockPrimaryDevice>(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<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
vector<int>& 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<int>& cmd = controller.InitCmd(10);
|
||||
vector<int>& 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<BYTE>& 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<NiceMock<MockPrimaryDevice>>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, WriteByteSequence)
|
||||
{
|
||||
vector<BYTE> 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<string, string> 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)
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -7,7 +7,8 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "mocks.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#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());
|
||||
}
|
||||
}
|
||||
|
@ -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 <unistd.h>
|
||||
|
||||
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<MockRascsiExecutor>(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<MockPrimaryDevice>(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<MockSCSIHD_NEC>(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<MockRascsiExecutor>(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<int> 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<StorageDevice>(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<uint32_t> sizes;
|
||||
auto disk = make_shared<MockSCSIHD>(0, sizes, false);
|
||||
EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 512));
|
||||
auto hd = make_shared<MockSCSIHD>(0, sizes, false);
|
||||
EXPECT_FALSE(executor.SetSectorSize(context, hd, 512));
|
||||
|
||||
sizes.insert(512);
|
||||
disk = make_shared<MockSCSIHD>(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<MockSCSIHD>(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<MockPrimaryDevice>(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;
|
||||
|
129
src/raspberrypi/test/rascsi_image_test.cpp
Normal file
129
src/raspberrypi/test/rascsi_image_test.cpp
Normal file
@ -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
|
||||
}
|
@ -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<int> 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<MockHostServices>(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<MockSCSIHD_NEC>(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<int> 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());
|
||||
}
|
||||
|
26
src/raspberrypi/test/rascsi_service_test.cpp
Normal file
26
src/raspberrypi/test/rascsi_service_test.cpp
Normal file
@ -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 <gtest/gtest.h>
|
||||
|
||||
#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();
|
||||
}
|
108
src/raspberrypi/test/rasctl_commands_test.cpp
Normal file
108
src/raspberrypi/test/rasctl_commands_test.cpp
Normal file
@ -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 <gtest/gtest.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
@ -7,9 +7,16 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "mocks.h"
|
||||
#include "rasutil.h"
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "rascsi_interface.pb.h"
|
||||
#include "rasutil.h"
|
||||
#ifdef __linux__
|
||||
#include <sched.h>
|
||||
#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
|
||||
|
@ -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)
|
||||
|
@ -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<MockBus> 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<MockBus> bus;
|
||||
NiceMock<MockScsiController> 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<MockPrimaryDevice>(0);
|
||||
controller.AddDevice(device);
|
||||
EXPECT_CALL(bus, SetBSY(true));
|
||||
controller.Selection();
|
||||
EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase());
|
||||
}
|
||||
|
||||
TEST(ScsiControllerTest, Command)
|
||||
{
|
||||
NiceMock<MockBus> bus;
|
||||
NiceMock<MockScsiController> 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<MockBus> 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<MockBus> 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<MockBus> 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<MockBus> 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<MockBus> 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<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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";
|
||||
}
|
||||
|
@ -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<MockAbstractController> 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<MockAbstractController> 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<BYTE> buf(0);
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
|
||||
|
||||
vector<int>& 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<BYTE> buf(0);
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
|
||||
|
||||
EXPECT_THROW(daynaport->WriteCheck(0), scsi_exception);
|
||||
}
|
||||
|
||||
TEST(ScsiDaynaportTest, WriteBytes)
|
||||
{
|
||||
vector<BYTE> buf(0);
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
|
||||
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
// Unknown data format
|
||||
cmd[5] = 0xff;
|
||||
EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0));
|
||||
}
|
||||
|
||||
TEST(ScsiDaynaportTest, Read6)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
cmd[5] = 0xff;
|
||||
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdRead6), scsi_exception) << "Invalid data format";
|
||||
}
|
||||
|
||||
TEST(ScsiDaynaportTest, Write6)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& 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<MockAbstractController> controller(0);
|
||||
auto daynaport = CreateDevice(SCDP, controller);
|
||||
|
||||
vector<int>& cmd = controller.InitCmd(6);
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
// Enable
|
||||
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdEnableInterface), scsi_exception);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -14,20 +14,39 @@
|
||||
|
||||
using namespace std;
|
||||
|
||||
TEST(ScsiPrinterTest, Init)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(0);
|
||||
auto printer = CreateDevice(SCLP, controller);
|
||||
|
||||
unordered_map<string, string> 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<MockAbstractController> 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<MockAbstractController> controller(0);
|
||||
auto printer = CreateDevice(SCLP, controller);
|
||||
|
||||
vector<int>& 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<MockAbstractController> 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<MockAbstractController> controller(0);
|
||||
auto printer = dynamic_pointer_cast<SCSIPrinter>(CreateDevice(SCLP, controller));
|
||||
|
||||
vector<BYTE> buf(1);
|
||||
EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size()));
|
||||
printer->Cleanup();
|
||||
}
|
||||
|
@ -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<int, vector<byte>> mode_pages;
|
||||
map<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> 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<uint32_t> sector_sizes;
|
||||
auto cd = make_shared<MockSCSICD>(0, sector_sizes);
|
||||
|
||||
controller.AddDevice(cd);
|
||||
|
||||
EXPECT_THROW(cd->Dispatch(scsi_command::eCmdReadToc), scsi_exception) << "Drive is not ready";
|
||||
|
||||
// Further testing requires filesystem access
|
||||
}
|
||||
|
@ -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<int, vector<byte>> mode_pages;
|
||||
map<int, vector<byte>> 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<int, vector<byte>> 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<byte>& 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<int, vector<byte>> 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";
|
||||
}
|
||||
|
@ -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<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> sector_sizes;
|
||||
MockSCSIHD hd(0, sector_sizes, false);
|
||||
|
||||
EXPECT_TRUE(hd.SupportsSaveParameters());
|
||||
}
|
||||
|
||||
TEST(ScsiHdTest, FinalizeSetup)
|
||||
{
|
||||
map<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> 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<int, vector<byte>> mode_pages;
|
||||
map<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> 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<uint32_t> sector_sizes = { 512 };
|
||||
MockSCSIHD hd(0, sector_sizes, false);
|
||||
vector<int> cmd(10);
|
||||
vector<BYTE> 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);
|
||||
}
|
||||
|
@ -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<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> sector_sizes;
|
||||
MockSCSIMO mo(0, sector_sizes);
|
||||
|
||||
EXPECT_TRUE(mo.SupportsSaveParameters());
|
||||
}
|
||||
|
||||
TEST(ScsiMoTest, SetUpModePages)
|
||||
{
|
||||
map<int, vector<byte>> mode_pages;
|
||||
map<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> 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<int, vector<byte>> pages;
|
||||
const unordered_set<uint32_t> 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<byte>& 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<uint32_t> sector_sizes = { 1024, 2048 };
|
||||
MockSCSIMO mo(0, sector_sizes);
|
||||
vector<int> cmd(10);
|
||||
vector<BYTE> 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);
|
||||
}
|
||||
|
||||
|
150
src/raspberrypi/test/storage_device_test.cpp
Normal file
150
src/raspberrypi/test/storage_device_test.cpp
Normal file
@ -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 <unistd.h>
|
||||
|
||||
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<string, id_set> 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<NiceMock<MockStorageDevice>>();
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user