mirror of
https://github.com/akuker/RASCSI.git
synced 2024-06-01 19:41:48 +00:00
Merge branch 'develop' into feature_bpi4
This commit is contained in:
commit
b5d1b0ea3d
|
@ -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)
|
||||
|
|
|
@ -21,10 +21,13 @@ WORKDIR /home/pi/RASCSI
|
|||
USER pi
|
||||
COPY --chown=pi:pi . .
|
||||
|
||||
# Standalone RaSCSI web UI
|
||||
RUN ./easyinstall.sh --run_choice=11 --skip-token
|
||||
# Install standalone RaSCSI web UI
|
||||
RUN ./easyinstall.sh --run_choice=11
|
||||
|
||||
# Wired network bridge
|
||||
# Enable web UI authentication
|
||||
RUN ./easyinstall.sh --run_choice=13
|
||||
|
||||
# Setup wired network bridge
|
||||
RUN ./easyinstall.sh --run_choice=6 --headless
|
||||
|
||||
USER root
|
||||
|
|
|
@ -21,7 +21,7 @@ COPY --chown=pi:pi . .
|
|||
RUN patch -p0 < docker/rascsi/cfilesystem.patch
|
||||
|
||||
# Install RaSCSI standalone
|
||||
RUN ./easyinstall.sh --run_choice=10 --cores=`nproc` --skip-token
|
||||
RUN ./easyinstall.sh --run_choice=10 --cores=`nproc`
|
||||
|
||||
USER root
|
||||
WORKDIR /home/pi
|
||||
|
|
137
easyinstall.sh
137
easyinstall.sh
|
@ -157,8 +157,18 @@ function installRaScsi() {
|
|||
cleanupOutdatedManPage "scsimon.1"
|
||||
cleanupOutdatedManPage "rasdump.1"
|
||||
cleanupOutdatedManPage "sasidump.1"
|
||||
|
||||
# install
|
||||
sudo make install CONNECT_TYPE="${CONNECT_TYPE:-FULLSPEC}" </dev/null
|
||||
|
||||
# update launch parameters
|
||||
if [[ -f $SECRET_FILE ]]; then
|
||||
sudo sed -i "\@^ExecStart.*@ s@@& -F $VIRTUAL_DRIVER_PATH -P $SECRET_FILE@" "$SYSTEMD_PATH/rascsi.service"
|
||||
echo "Secret token file $SECRET_FILE detected. Using it to enable back-end authentication."
|
||||
else
|
||||
sudo sed -i "\@^ExecStart.*@ s@@& -F $VIRTUAL_DRIVER_PATH@" "$SYSTEMD_PATH/rascsi.service"
|
||||
fi
|
||||
echo "Configured rascsi.service to use $VIRTUAL_DRIVER_PATH as default image dir."
|
||||
}
|
||||
|
||||
function preparePythonCommon() {
|
||||
|
@ -281,7 +291,7 @@ function backupRaScsiService() {
|
|||
fi
|
||||
}
|
||||
|
||||
# Offers the choice of enabling token-based authentication for RaSCSI
|
||||
# Offers the choice of enabling token-based authentication for RaSCSI, or disables it if enabled
|
||||
function configureTokenAuth() {
|
||||
if [[ -f "$HOME/.rascsi_secret" ]]; then
|
||||
sudo rm "$HOME/.rascsi_secret"
|
||||
|
@ -290,46 +300,34 @@ function configureTokenAuth() {
|
|||
|
||||
if [[ -f $SECRET_FILE ]]; then
|
||||
sudo rm "$SECRET_FILE"
|
||||
echo "Removed RaSCSI token file"
|
||||
fi
|
||||
|
||||
if [[ $SKIP_TOKEN ]]; then
|
||||
echo "Skipping RaSCSI token setup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -z $TOKEN ]]; then
|
||||
echo ""
|
||||
echo "Do you want to protect your RaSCSI installation with a password? [y/N]"
|
||||
echo "RaSCSI token file $SECRET_FILE already exists. Do you want to disable authentication? (y/N)"
|
||||
read REPLY
|
||||
|
||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
return 0
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
sudo sed -i 's@-P '"$SECRET_FILE"'@@' "$SYSTEMD_PATH/rascsi.service"
|
||||
return
|
||||
fi
|
||||
|
||||
echo -n "Enter the password that you want to use: "
|
||||
read -r TOKEN
|
||||
fi
|
||||
|
||||
echo -n "Enter the token password for protecting RaSCSI: "
|
||||
read -r TOKEN
|
||||
|
||||
echo "$TOKEN" > "$SECRET_FILE"
|
||||
|
||||
# Make the secret file owned and only readable by root
|
||||
sudo chown root:root "$SECRET_FILE"
|
||||
sudo chmod 600 "$SECRET_FILE"
|
||||
|
||||
sudo sed -i "s@^ExecStart.*@& -P $SECRET_FILE@" "$SYSTEMD_PATH/rascsi.service"
|
||||
|
||||
echo ""
|
||||
echo "Configured RaSCSI to use $SECRET_FILE for authentication. This file is readable by root only."
|
||||
echo "Make note of your password: you will need it to use rasctl and other RaSCSI clients."
|
||||
echo "If you have RaSCSI clients installed, please re-run the installation scripts, or update the systemd config manually."
|
||||
}
|
||||
|
||||
# Modifies and installs the rascsi service
|
||||
# Enables and starts the rascsi service
|
||||
function enableRaScsiService() {
|
||||
if [ ! -z "$TOKEN" ]; then
|
||||
sudo sed -i "s@^ExecStart.*@& -F $VIRTUAL_DRIVER_PATH -P $SECRET_FILE@" "$SYSTEMD_PATH/rascsi.service"
|
||||
else
|
||||
sudo sed -i "s@^ExecStart.*@& -F $VIRTUAL_DRIVER_PATH@" "$SYSTEMD_PATH/rascsi.service"
|
||||
fi
|
||||
echo "Configured rascsi.service to use $VIRTUAL_DRIVER_PATH as default image dir."
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl restart rsyslog
|
||||
sudo systemctl enable rascsi # optional - start rascsi at boot
|
||||
|
@ -339,10 +337,16 @@ function enableRaScsiService() {
|
|||
|
||||
# Modifies and installs the rascsi-web service
|
||||
function installWebInterfaceService() {
|
||||
if [[ -f "$SECRET_FILE" && -z "$TOKEN" ]] ; then
|
||||
echo ""
|
||||
echo "Secret token file $SECRET_FILE detected. You must enter the password, or press Ctrl+C to cancel installation."
|
||||
read -r TOKEN
|
||||
fi
|
||||
|
||||
echo "Installing the rascsi-web.service configuration..."
|
||||
sudo cp -f "$WEB_INSTALL_PATH/service-infra/rascsi-web.service" "$SYSTEMD_PATH/rascsi-web.service"
|
||||
sudo sed -i /^ExecStart=/d "$SYSTEMD_PATH/rascsi-web.service"
|
||||
echo "$TOKEN"
|
||||
|
||||
if [ ! -z "$TOKEN" ]; then
|
||||
sudo sed -i "8 i ExecStart=$WEB_INSTALL_PATH/start.sh --password=$TOKEN" "$SYSTEMD_PATH/rascsi-web.service"
|
||||
# Make the service file readable by root only, to protect the token string
|
||||
|
@ -935,6 +939,12 @@ function installMacproxy {
|
|||
|
||||
# updates configuration files and installs packages needed for the OLED screen script
|
||||
function installRaScsiScreen() {
|
||||
if [[ -f "$SECRET_FILE" && -z "$TOKEN" ]] ; then
|
||||
echo ""
|
||||
echo "Secret token file $SECRET_FILE detected. You must enter the password, or press Ctrl+C to cancel installation."
|
||||
read -r TOKEN
|
||||
fi
|
||||
|
||||
echo "IMPORTANT: This configuration requires a OLED screen to be installed onto your RaSCSI board."
|
||||
echo "See wiki for more information: https://github.com/akuker/RASCSI/wiki/OLED-Status-Display-(Optional)"
|
||||
echo ""
|
||||
|
@ -965,16 +975,6 @@ function installRaScsiScreen() {
|
|||
SCREEN_HEIGHT="32"
|
||||
fi
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo ""
|
||||
echo "Did you protect your RaSCSI installation with a token password? [y/N]"
|
||||
read -r REPLY
|
||||
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
|
||||
echo -n "Enter the password that you configured with RaSCSI at the time of installation: "
|
||||
read -r TOKEN
|
||||
fi
|
||||
fi
|
||||
|
||||
stopRaScsiScreen
|
||||
disableRaScsiCtrlBoardService
|
||||
updateRaScsiGit
|
||||
|
@ -1027,6 +1027,12 @@ function installRaScsiScreen() {
|
|||
|
||||
# updates configuration files and installs packages needed for the CtrlBoard script
|
||||
function installRaScsiCtrlBoard() {
|
||||
if [[ -f "$SECRET_FILE" && -z "$TOKEN" ]] ; then
|
||||
echo ""
|
||||
echo "Secret token file $SECRET_FILE detected. You must enter the password, or press Ctrl+C to cancel installation."
|
||||
read -r TOKEN
|
||||
fi
|
||||
|
||||
echo "IMPORTANT: This configuration requires a RaSCSI Control Board connected to your RaSCSI board."
|
||||
echo "See wiki for more information: https://github.com/akuker/RASCSI/wiki/RaSCSI-Control-Board"
|
||||
echo ""
|
||||
|
@ -1043,16 +1049,6 @@ function installRaScsiCtrlBoard() {
|
|||
ROTATION="180"
|
||||
fi
|
||||
|
||||
if [ -z "$TOKEN" ]; then
|
||||
echo ""
|
||||
echo "Did you protect your RaSCSI installation with a token password? [y/N]"
|
||||
read -r REPLY
|
||||
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
|
||||
echo -n "Enter the password that you configured with RaSCSI at the time of installation: "
|
||||
read -r TOKEN
|
||||
fi
|
||||
fi
|
||||
|
||||
stopRaScsiCtrlBoard
|
||||
updateRaScsiGit
|
||||
|
||||
|
@ -1149,6 +1145,13 @@ function enableWebInterfaceAuth {
|
|||
|
||||
if [ $(getent group "$AUTH_GROUP") ]; then
|
||||
echo "The '$AUTH_GROUP' group already exists."
|
||||
echo "Do you want to disable Web Interface authentication? (y/N)"
|
||||
read -r REPLY
|
||||
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
|
||||
sudo groupdel "$AUTH_GROUP"
|
||||
echo "The '$AUTH_GROUP' group has been deleted."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
echo "Creating the '$AUTH_GROUP' group."
|
||||
sudo groupadd "$AUTH_GROUP"
|
||||
|
@ -1176,7 +1179,6 @@ function runChoice() {
|
|||
sudoCheck
|
||||
createImagesDir
|
||||
createCfgDir
|
||||
configureTokenAuth
|
||||
stopOldWebInterface
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
|
@ -1197,7 +1199,6 @@ function runChoice() {
|
|||
cachePipPackages
|
||||
installRaScsiWebInterface
|
||||
installWebInterfaceService
|
||||
enableWebInterfaceAuth
|
||||
showRaScsiScreenStatus
|
||||
showRaScsiCtrlBoardStatus
|
||||
showRaScsiStatus
|
||||
|
@ -1218,7 +1219,6 @@ function runChoice() {
|
|||
sudoCheck
|
||||
createImagesDir
|
||||
createCfgDir
|
||||
configureTokenAuth
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
stopRaScsiScreen
|
||||
|
@ -1307,7 +1307,6 @@ function runChoice() {
|
|||
echo "- Install manpages to /usr/local/man"
|
||||
sudoCheck
|
||||
createImagesDir
|
||||
configureTokenAuth
|
||||
updateRaScsiGit
|
||||
installPackagesStandalone
|
||||
stopRaScsi
|
||||
|
@ -1323,21 +1322,36 @@ function runChoice() {
|
|||
echo "- Add and modify systemd services"
|
||||
echo "- Modify and enable Apache2 and Nginx web service"
|
||||
echo "- Create directories and change permissions"
|
||||
echo "- Modify user groups and permissions"
|
||||
echo "- Create a self-signed certificate in /etc/ssl"
|
||||
sudoCheck
|
||||
createCfgDir
|
||||
configureTokenAuth
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
preparePythonCommon
|
||||
cachePipPackages
|
||||
installRaScsiWebInterface
|
||||
enableWebInterfaceAuth
|
||||
echo "Configuring RaSCSI Web Interface stand-alone - Complete!"
|
||||
echo "Launch the Web Interface with the 'start.sh' script. To use a custom port for the web server: 'start.sh --web-port=8081"
|
||||
;;
|
||||
12)
|
||||
echo "Enabling or disabling RaSCSI back-end authentication"
|
||||
echo "This script will make the following changes to your system:"
|
||||
echo "- Modify user groups and permissions"
|
||||
sudoCheck
|
||||
stopRaScsi
|
||||
configureTokenAuth
|
||||
enableRaScsiService
|
||||
echo "Enabling or disabling RaSCSI back-end authentication - Complete!"
|
||||
;;
|
||||
13)
|
||||
echo "Enabling or disabling Web Interface authentication"
|
||||
echo "This script will make the following changes to your system:"
|
||||
echo "- Modify user groups and permissions"
|
||||
sudoCheck
|
||||
enableWebInterfaceAuth
|
||||
echo "Enabling or disabling Web Interface authentication - Complete!"
|
||||
;;
|
||||
14)
|
||||
echo "Installing / Updating RaSCSI Control Board UI"
|
||||
echo "This script will make the following changes to your system:"
|
||||
echo "- Install additional packages with apt-get"
|
||||
|
@ -1350,7 +1364,7 @@ function runChoice() {
|
|||
showRaScsiCtrlBoardStatus
|
||||
echo "Installing / Updating RaSCSI Control Board UI - Complete!"
|
||||
;;
|
||||
13)
|
||||
15)
|
||||
shareImagesWithNetatalk
|
||||
echo "Configuring AppleShare File Server - Complete!"
|
||||
;;
|
||||
|
@ -1367,7 +1381,7 @@ function runChoice() {
|
|||
function readChoice() {
|
||||
choice=-1
|
||||
|
||||
until [ $choice -ge "0" ] && [ $choice -le "13" ]; do
|
||||
until [ $choice -ge "0" ] && [ $choice -le "15" ]; do
|
||||
echo -n "Enter your choice (0-13) or CTRL-C to exit: "
|
||||
read -r choice
|
||||
done
|
||||
|
@ -1396,9 +1410,11 @@ function showMenu() {
|
|||
echo "ADVANCED OPTIONS"
|
||||
echo " 10) compile and install RaSCSI stand-alone"
|
||||
echo " 11) configure the RaSCSI Web Interface stand-alone"
|
||||
echo " 12) enable or disable RaSCSI back-end authentication"
|
||||
echo " 13) enable or disable RaSCSI Web Interface authentication"
|
||||
echo "EXPERIMENTAL FEATURES"
|
||||
echo " 12) install or update RaSCSI Control Board UI (requires hardware)"
|
||||
echo " 13) share the images dir over AppleShare (requires Netatalk)"
|
||||
echo " 14) install or update RaSCSI Control Board UI (requires hardware)"
|
||||
echo " 15) share the images dir over AppleShare (requires Netatalk)"
|
||||
}
|
||||
|
||||
# parse arguments passed to the script
|
||||
|
@ -1414,8 +1430,8 @@ while [ "$1" != "" ]; do
|
|||
CONNECT_TYPE=$VALUE
|
||||
;;
|
||||
-r | --run_choice)
|
||||
if ! [[ $VALUE =~ ^[1-9][0-9]?$ && $VALUE -ge 1 && $VALUE -le 12 ]]; then
|
||||
echo "ERROR: The run choice parameter must have a numeric value between 1 and 12"
|
||||
if ! [[ $VALUE =~ ^[1-9][0-9]?$ && $VALUE -ge 1 && $VALUE -le 15 ]]; then
|
||||
echo "ERROR: The run choice parameter must have a numeric value between 1 and 15"
|
||||
exit 1
|
||||
fi
|
||||
RUN_CHOICE=$VALUE
|
||||
|
@ -1434,9 +1450,6 @@ while [ "$1" != "" ]; do
|
|||
fi
|
||||
TOKEN=$VALUE
|
||||
;;
|
||||
-s | --skip-token)
|
||||
SKIP_TOKEN=1
|
||||
;;
|
||||
-h | --headless)
|
||||
HEADLESS=1
|
||||
;;
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
Module for methods reading from and writing to the file system
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
from os import path, walk
|
||||
from functools import lru_cache
|
||||
from pathlib import PurePath, Path
|
||||
from zipfile import ZipFile, is_zipfile
|
||||
from time import time
|
||||
from subprocess import run, CalledProcessError
|
||||
from json import dump, load
|
||||
from shutil import copyfile
|
||||
from urllib.parse import quote
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import requests
|
||||
|
||||
|
@ -54,14 +54,14 @@ class FileCmds:
|
|||
index 0 is (str) file name and index 1 is (int) size in bytes
|
||||
"""
|
||||
files_list = []
|
||||
for path, _dirs, files in os.walk(dir_path):
|
||||
for file_path, _dirs, files in walk(dir_path):
|
||||
# Only list selected file types
|
||||
files = [f for f in files if f.lower().endswith(file_types)]
|
||||
files_list.extend(
|
||||
[
|
||||
(
|
||||
file,
|
||||
os.path.getsize(os.path.join(path, file))
|
||||
path.getsize(path.join(file_path, file))
|
||||
)
|
||||
for file in files
|
||||
]
|
||||
|
@ -75,7 +75,7 @@ class FileCmds:
|
|||
Returns a (list) of (str) files_list
|
||||
"""
|
||||
files_list = []
|
||||
for _root, _dirs, files in os.walk(CFG_DIR):
|
||||
for _root, _dirs, files in walk(CFG_DIR):
|
||||
for file in files:
|
||||
if file.endswith("." + CONFIG_FILE_SUFFIX):
|
||||
files_list.append(file)
|
||||
|
@ -375,63 +375,61 @@ class FileCmds:
|
|||
server_info = self.ractl.get_server_info()
|
||||
|
||||
file_name = PurePath(url).name
|
||||
tmp_ts = int(time())
|
||||
tmp_dir = "/tmp/" + str(tmp_ts) + "/"
|
||||
os.mkdir(tmp_dir)
|
||||
tmp_full_path = tmp_dir + file_name
|
||||
iso_filename = f"{server_info['image_dir']}/{file_name}.iso"
|
||||
iso_filename = Path(server_info['image_dir']) / f"{file_name}.iso"
|
||||
|
||||
req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_dir, file_name)
|
||||
with TemporaryDirectory() as tmp_dir:
|
||||
req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_dir, file_name)
|
||||
logging.info("Downloaded %s to %s", file_name, tmp_dir)
|
||||
if not req_proc["status"]:
|
||||
return {"status": False, "msg": req_proc["msg"]}
|
||||
|
||||
if not req_proc["status"]:
|
||||
return {"status": False, "msg": req_proc["msg"]}
|
||||
|
||||
if is_zipfile(tmp_full_path):
|
||||
if "XtraStuf.mac" in str(ZipFile(tmp_full_path).namelist()):
|
||||
logging.info("MacZip file format detected. Will not unzip to retain resource fork.")
|
||||
else:
|
||||
logging.info(
|
||||
"%s is a zipfile! Will attempt to unzip and store the resulting files.",
|
||||
tmp_full_path,
|
||||
)
|
||||
unzip_proc = asyncio.run(self.run_async("unzip", [
|
||||
"-d",
|
||||
tmp_dir,
|
||||
"-n",
|
||||
tmp_full_path,
|
||||
]))
|
||||
if not unzip_proc["returncode"]:
|
||||
tmp_full_path = Path(tmp_dir) / file_name
|
||||
if is_zipfile(tmp_full_path):
|
||||
if "XtraStuf.mac" in str(ZipFile(str(tmp_full_path)).namelist()):
|
||||
logging.info("MacZip file format detected. Will not unzip to retain resource fork.")
|
||||
else:
|
||||
logging.info(
|
||||
"%s was successfully unzipped. Deleting the zipfile.",
|
||||
"%s is a zipfile! Will attempt to unzip and store the resulting files.",
|
||||
tmp_full_path,
|
||||
)
|
||||
self.delete_file(Path(tmp_full_path))
|
||||
unzip_proc = asyncio.run(self.run_async("unzip", [
|
||||
"-d",
|
||||
str(tmp_dir),
|
||||
"-n",
|
||||
str(tmp_full_path),
|
||||
]))
|
||||
if not unzip_proc["returncode"]:
|
||||
logging.info(
|
||||
"%s was successfully unzipped. Deleting the zipfile.",
|
||||
tmp_full_path,
|
||||
)
|
||||
tmp_full_path.unlink(True)
|
||||
|
||||
try:
|
||||
run(
|
||||
[
|
||||
"genisoimage",
|
||||
*iso_args,
|
||||
"-o",
|
||||
iso_filename,
|
||||
tmp_dir,
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
except CalledProcessError as error:
|
||||
logging.warning(SHELL_ERROR, " ".join(error.cmd), error.stderr.decode("utf-8"))
|
||||
return {"status": False, "msg": error.stderr.decode("utf-8")}
|
||||
try:
|
||||
run(
|
||||
[
|
||||
"genisoimage",
|
||||
*iso_args,
|
||||
"-o",
|
||||
str(iso_filename),
|
||||
tmp_dir,
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
except CalledProcessError as error:
|
||||
logging.warning(SHELL_ERROR, " ".join(error.cmd), error.stderr.decode("utf-8"))
|
||||
return {"status": False, "msg": error.stderr.decode("utf-8")}
|
||||
|
||||
parameters = {
|
||||
"value": " ".join(iso_args)
|
||||
}
|
||||
return {
|
||||
"status": True,
|
||||
"return_code": ReturnCodes.DOWNLOADFILETOISO_SUCCESS,
|
||||
"parameters": parameters,
|
||||
"file_name": iso_filename,
|
||||
}
|
||||
parameters = {
|
||||
"value": " ".join(iso_args)
|
||||
}
|
||||
return {
|
||||
"status": True,
|
||||
"return_code": ReturnCodes.DOWNLOADFILETOISO_SUCCESS,
|
||||
"parameters": parameters,
|
||||
"file_name": iso_filename.name,
|
||||
}
|
||||
|
||||
# noinspection PyMethodMayBeStatic
|
||||
def download_to_dir(self, url, save_dir, file_name):
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
bjoern==3.1.0
|
||||
click==7.1.2
|
||||
Flask==2.0.1
|
||||
itsdangerous==2.0.1
|
||||
Jinja2==3.0.1
|
||||
MarkupSafe==2.0.1
|
||||
protobuf==3.20.1
|
||||
requests==2.26.0
|
||||
bjoern==3.2.2
|
||||
Flask==2.2.2
|
||||
Jinja2==3.1.2
|
||||
protobuf==3.20.2
|
||||
requests==2.28.1
|
||||
simplepam==0.1.5
|
||||
flask_babel==2.0.0
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
<p>
|
||||
<form action="/config/save" method="post">
|
||||
<label for="config_save_name">{{ _("File name") }}</label>
|
||||
<input name="name" id="config_save_name" placeholder="default" size="20">
|
||||
<input name="name" id="config_save_name" value="default" size="20">
|
||||
.{{ CONFIG_FILE_SUFFIX }}
|
||||
<input type="submit" value="{{ _("Save") }}">
|
||||
</form>
|
||||
|
@ -522,22 +522,22 @@
|
|||
<input name="url" id="iso_url" required="" type="url">
|
||||
<label for="iso_type">{{ _("Type:") }}</label>
|
||||
<select name="type" id="iso_type">
|
||||
<option value="-hfs">
|
||||
<option value="HFS">
|
||||
HFS
|
||||
</option>
|
||||
<option value="-iso-level 1">
|
||||
<option value="ISO-9660 Level 1">
|
||||
ISO-9660 Level 1
|
||||
</option>
|
||||
<option value="-iso-level 2">
|
||||
<option value="ISO-9660 Level 2">
|
||||
ISO-9660 Level 2
|
||||
</option>
|
||||
<option value="-iso-level 3">
|
||||
<option value="ISO-9660 Level 3">
|
||||
ISO-9660 Level 3
|
||||
</option>
|
||||
<option value="-J">
|
||||
<option value="Joliet">
|
||||
Joliet
|
||||
</option>
|
||||
<option value="-r">
|
||||
<option value="Rock Ridge">
|
||||
Rock Ridge
|
||||
</option>
|
||||
</select>
|
||||
|
|
|
@ -54,6 +54,7 @@ from web_utils import (
|
|||
get_properties_by_drive_name,
|
||||
auth_active,
|
||||
is_bridge_configured,
|
||||
is_safe_path,
|
||||
upload_with_dropzonejs,
|
||||
)
|
||||
from settings import (
|
||||
|
@ -336,7 +337,7 @@ def drive_create():
|
|||
Creates the image and properties file pair
|
||||
"""
|
||||
drive_name = request.form.get("drive_name")
|
||||
file_name = request.form.get("file_name")
|
||||
file_name = Path(request.form.get("file_name")).name
|
||||
|
||||
properties = get_properties_by_drive_name(
|
||||
APP.config["RASCSI_DRIVE_PROPERTIES"],
|
||||
|
@ -360,11 +361,9 @@ def drive_create():
|
|||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(message=[
|
||||
(_("Image file created: %(file_name)s", file_name=full_file_name), "success"),
|
||||
(process["msg"], "success"),
|
||||
])
|
||||
return response(message=
|
||||
_("Image file with properties created: %(file_name)s", file_name=full_file_name)
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/drive/cdrom", methods=["POST"])
|
||||
|
@ -374,7 +373,7 @@ def drive_cdrom():
|
|||
Creates a properties file for a CD-ROM image
|
||||
"""
|
||||
drive_name = request.form.get("drive_name")
|
||||
file_name = request.form.get("file_name")
|
||||
file_name = Path(request.form.get("file_name")).name
|
||||
|
||||
# Creating the drive properties file
|
||||
file_name = f"{file_name}.{PROPERTIES_SUFFIX}"
|
||||
|
@ -396,8 +395,7 @@ def config_save():
|
|||
"""
|
||||
Saves a config file to disk
|
||||
"""
|
||||
file_name = request.form.get("name") or "default"
|
||||
file_name = f"{file_name}.{CONFIG_FILE_SUFFIX}"
|
||||
file_name = Path(request.form.get("name") + f".{CONFIG_FILE_SUFFIX}").name
|
||||
|
||||
process = file_cmd.write_config(file_name)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
|
@ -413,7 +411,7 @@ def config_load():
|
|||
"""
|
||||
Loads a config file from disk
|
||||
"""
|
||||
file_name = request.form.get("name")
|
||||
file_name = Path(request.form.get("name")).name
|
||||
|
||||
if "load" in request.form:
|
||||
process = file_cmd.read_config(file_name)
|
||||
|
@ -440,18 +438,18 @@ def show_diskinfo():
|
|||
"""
|
||||
Displays disk image info
|
||||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
|
||||
file_name = Path(request.form.get("file_name"))
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
returncode, diskinfo = sys_cmd.get_diskinfo(
|
||||
server_info["image_dir"] +
|
||||
"/" +
|
||||
file_name
|
||||
Path(server_info["image_dir"]) / file_name
|
||||
)
|
||||
if returncode == 0:
|
||||
return response(
|
||||
template="diskinfo.html",
|
||||
file_name=file_name,
|
||||
file_name=str(file_name),
|
||||
diskinfo=diskinfo,
|
||||
)
|
||||
|
||||
|
@ -580,17 +578,10 @@ def attach_device():
|
|||
if param:
|
||||
params.update({item.replace(PARAM_PREFIX, ""): param})
|
||||
|
||||
error_url = "https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link"
|
||||
error_msg = _("Please follow the instructions at %(url)s", url=error_url)
|
||||
|
||||
if "interface" in params.keys():
|
||||
bridge_status = is_bridge_configured(params["interface"])
|
||||
if not bridge_status["status"]:
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(bridge_status["msg"], "error"),
|
||||
(error_msg, "error")
|
||||
])
|
||||
return response(error=True, message=bridge_status["msg"])
|
||||
|
||||
kwargs = {
|
||||
"unit": int(unit),
|
||||
|
@ -642,8 +633,8 @@ def attach_image():
|
|||
|
||||
# Attempt to load the device properties file:
|
||||
# same file name with PROPERTIES_SUFFIX appended
|
||||
drive_properties = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
|
||||
if Path(drive_properties).is_file():
|
||||
drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
|
||||
if drive_properties.is_file():
|
||||
process = file_cmd.read_drive_properties(drive_properties)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if not process["status"]:
|
||||
|
@ -658,32 +649,26 @@ def attach_image():
|
|||
process = ractl_cmd.attach_device(scsi_id, **kwargs)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
response_messages = [(_(
|
||||
"Attached %(file_name)s as %(device_type)s to "
|
||||
"SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name,
|
||||
device_type=get_device_name(device_type),
|
||||
id_number=scsi_id,
|
||||
unit_number=unit,
|
||||
), "success")]
|
||||
|
||||
if int(file_size) % int(expected_block_size):
|
||||
response_messages.append((_(
|
||||
"The image file size %(file_size)s bytes is not a multiple of "
|
||||
"%(block_size)s. RaSCSI will ignore the trailing data. "
|
||||
logging.warning(
|
||||
"The image file size %s bytes is not a multiple of %s. "
|
||||
"RaSCSI will ignore the trailing data. "
|
||||
"The image may be corrupted, so proceed with caution.",
|
||||
file_size=file_size,
|
||||
block_size=expected_block_size,
|
||||
), "warning"))
|
||||
file_size,
|
||||
expected_block_size,
|
||||
)
|
||||
return response(
|
||||
message=_(
|
||||
"Attached %(file_name)s as %(device_type)s to "
|
||||
"SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name,
|
||||
device_type=get_device_name(device_type),
|
||||
id_number=scsi_id,
|
||||
unit_number=unit,
|
||||
)
|
||||
)
|
||||
|
||||
return response(message=response_messages)
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to attach %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
file_name=file_name, id_number=scsi_id, unit_number=unit), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
||||
@APP.route("/scsi/detach_all", methods=["POST"])
|
||||
|
@ -712,12 +697,7 @@ def detach():
|
|||
return response(message=_("Detached SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to detach SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
||||
@APP.route("/scsi/eject", methods=["POST"])
|
||||
|
@ -734,12 +714,7 @@ def eject():
|
|||
return response(message=_("Ejected SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit))
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to eject SCSI ID %(id_number)s LUN %(unit_number)s",
|
||||
id_number=scsi_id, unit_number=unit), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
||||
@APP.route("/scsi/info", methods=["POST"])
|
||||
|
@ -772,11 +747,7 @@ def reserve_id():
|
|||
RESERVATIONS[int(scsi_id)] = memo
|
||||
return response(message=_("Reserved SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
||||
@APP.route("/scsi/release", methods=["POST"])
|
||||
|
@ -793,11 +764,7 @@ def release_id():
|
|||
RESERVATIONS[int(scsi_id)] = ""
|
||||
return response(message=_("Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id))
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
||||
@APP.route("/pi/reboot", methods=["POST"])
|
||||
|
@ -824,24 +791,40 @@ def shutdown():
|
|||
@login_required
|
||||
def download_to_iso():
|
||||
"""
|
||||
Downloads a remote file and creates a CD-ROM image formatted with HFS that contains the file
|
||||
Downloads a file and creates a CD-ROM image with the specified file system and the file
|
||||
"""
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
url = request.form.get("url")
|
||||
iso_args = request.form.get("type").split()
|
||||
response_messages = []
|
||||
iso_type = request.form.get("type")
|
||||
|
||||
if iso_type == "HFS":
|
||||
iso_args = ["-hfs"]
|
||||
elif iso_type == "ISO-9660 Level 1":
|
||||
iso_args = ["-iso-level", "1"]
|
||||
elif iso_type == "ISO-9660 Level 2":
|
||||
iso_args = ["-iso-level", "2"]
|
||||
elif iso_type == "ISO-9660 Level 3":
|
||||
iso_args = ["-iso-level", "3"]
|
||||
elif iso_type == "Joliet":
|
||||
iso_args = ["-J"]
|
||||
elif iso_type == "Rock Ridge":
|
||||
iso_args = ["-r"]
|
||||
else:
|
||||
return response(
|
||||
error=True,
|
||||
message=_("%(iso_type)s is not a valid CD-ROM format.", iso_type=iso_type)
|
||||
)
|
||||
|
||||
process = file_cmd.download_file_to_iso(url, *iso_args)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if not process["status"]:
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to create CD-ROM image from %(url)s", url=url), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
|
||||
response_messages.append((process["msg"], "success"))
|
||||
response_messages.append((_("Saved image as: %(file_name)s", file_name=process['file_name']), "success"))
|
||||
return response(
|
||||
error=True,
|
||||
message=_(
|
||||
"The following error occurred when creating the CD-ROM image: %(error)s",
|
||||
error=process["msg"],
|
||||
),
|
||||
)
|
||||
|
||||
process_attach = ractl_cmd.attach_device(
|
||||
scsi_id,
|
||||
|
@ -850,14 +833,26 @@ def download_to_iso():
|
|||
)
|
||||
process_attach = ReturnCodeMapper.add_msg(process_attach)
|
||||
if process_attach["status"]:
|
||||
response_messages.append((_("Attached to SCSI ID %(id_number)s", id_number=scsi_id), "success"))
|
||||
return response(message=response_messages)
|
||||
return response(
|
||||
message=_(
|
||||
"CD-ROM image %(file_name)s with type %(iso_type)s was created "
|
||||
"and attached to SCSI ID %(id_number)s",
|
||||
file_name=process["file_name"],
|
||||
iso_type=iso_type,
|
||||
id_number=scsi_id,
|
||||
),
|
||||
)
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to attach image to SCSI ID %(id_number)s. Try attaching it manually.", id_number=scsi_id), "error"),
|
||||
(process_attach["msg"], "error"),
|
||||
])
|
||||
return response(
|
||||
error=True,
|
||||
message=_(
|
||||
"CD-ROM image %(file_name)s with type %(iso_type)s was created "
|
||||
"but could not be attached: %(error)s",
|
||||
file_name=process["file_name"],
|
||||
iso_type=iso_type,
|
||||
error=process_attach["msg"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/download_url", methods=["POST"])
|
||||
|
@ -878,11 +873,13 @@ def download_file():
|
|||
if process["status"]:
|
||||
return response(message=process["msg"])
|
||||
|
||||
# TODO: Refactor the return messages into one string
|
||||
return response(error=True, message=[
|
||||
(_("Failed to download file from %(url)s", url=url), "error"),
|
||||
(process["msg"], "error"),
|
||||
])
|
||||
return response(
|
||||
error=True,
|
||||
message=_(
|
||||
"The following error occurred when downloading: %(error)s",
|
||||
error=process["msg"],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/upload", methods=["POST"])
|
||||
|
@ -911,13 +908,16 @@ def create_file():
|
|||
"""
|
||||
Creates an empty image file in the images dir
|
||||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
file_name = Path(request.form.get("file_name"))
|
||||
size = (int(request.form.get("size")) * 1024 * 1024)
|
||||
file_type = request.form.get("type")
|
||||
drive_name = request.form.get("drive_name")
|
||||
|
||||
full_file_name = file_name + "." + file_type
|
||||
process = file_cmd.create_new_image(file_name, file_type, size)
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
full_file_name = f"{file_name}.{file_type}"
|
||||
process = file_cmd.create_new_image(str(file_name), file_type, size)
|
||||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
|
@ -933,15 +933,20 @@ def create_file():
|
|||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
return response(
|
||||
status_code=201,
|
||||
message=_(
|
||||
"Image file with properties created: %(file_name)s",
|
||||
file_name=full_file_name,
|
||||
),
|
||||
image=full_file_name,
|
||||
)
|
||||
|
||||
return response(
|
||||
status_code=201,
|
||||
# TODO: Refactor the return messages into one string
|
||||
message=[
|
||||
(_("Image file created: %(file_name)s", file_name=full_file_name), "success"),
|
||||
(process["msg"], "success"),
|
||||
],
|
||||
message=_("Image file created: %(file_name)s", file_name=full_file_name),
|
||||
image=full_file_name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/download", methods=["POST"])
|
||||
|
@ -950,9 +955,12 @@ def download():
|
|||
"""
|
||||
Downloads a file from the Pi to the local computer
|
||||
"""
|
||||
file_name = request.form.get("file")
|
||||
file_name = Path(request.form.get("file"))
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
server_info = ractl_cmd.get_server_info()
|
||||
return send_from_directory(server_info["image_dir"], file_name, as_attachment=True)
|
||||
return send_from_directory(server_info["image_dir"], str(file_name), as_attachment=True)
|
||||
|
||||
|
||||
@APP.route("/files/delete", methods=["POST"])
|
||||
|
@ -961,26 +969,35 @@ def delete():
|
|||
"""
|
||||
Deletes a specified file in the images dir
|
||||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
|
||||
process = file_cmd.delete_image(file_name)
|
||||
file_name = Path(request.form.get("file_name"))
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
process = file_cmd.delete_image(str(file_name))
|
||||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
response_messages = [
|
||||
(_("Image file deleted: %(file_name)s", file_name=file_name), "success")]
|
||||
|
||||
# Delete the drive properties file, if it exists
|
||||
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
|
||||
if prop_file_path.is_file():
|
||||
process = file_cmd.delete_file(prop_file_path)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
response_messages.append((process["msg"], "success"))
|
||||
return response(
|
||||
message=_(
|
||||
"Image file with properties deleted: %(file_name)s",
|
||||
file_name=str(file_name),
|
||||
),
|
||||
)
|
||||
else:
|
||||
response_messages.append((process["msg"], "error"))
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
return response(message=response_messages)
|
||||
return response(
|
||||
message=_(
|
||||
"Image file deleted: %(file_name)s",
|
||||
file_name=str(file_name),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/rename", methods=["POST"])
|
||||
|
@ -989,16 +1006,18 @@ def rename():
|
|||
"""
|
||||
Renames a specified file in the images dir
|
||||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
new_file_name = request.form.get("new_file_name")
|
||||
|
||||
process = file_cmd.rename_image(file_name, new_file_name)
|
||||
file_name = Path(request.form.get("file_name"))
|
||||
new_file_name = Path(request.form.get("new_file_name"))
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
safe_path = is_safe_path(new_file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
process = file_cmd.rename_image(str(file_name), str(new_file_name))
|
||||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
response_messages = [
|
||||
(_("Image file renamed to: %(file_name)s", file_name=new_file_name), "success")]
|
||||
|
||||
# Rename the drive properties file, if it exists
|
||||
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
|
||||
new_prop_file_path = Path(CFG_DIR) / f"{new_file_name}.{PROPERTIES_SUFFIX}"
|
||||
|
@ -1006,11 +1025,21 @@ def rename():
|
|||
process = file_cmd.rename_file(prop_file_path, new_prop_file_path)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
response_messages.append((process["msg"], "success"))
|
||||
return response(
|
||||
message=_(
|
||||
"Image file with properties renamed to: %(file_name)s",
|
||||
file_name=str(new_file_name),
|
||||
),
|
||||
)
|
||||
else:
|
||||
response_messages.append((process["msg"], "error"))
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
return response(message=response_messages)
|
||||
return response(
|
||||
message=_(
|
||||
"Image file renamed to: %(file_name)s",
|
||||
file_name=str(new_file_name),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/copy", methods=["POST"])
|
||||
|
@ -1019,16 +1048,18 @@ def copy():
|
|||
"""
|
||||
Creates a copy of a specified file in the images dir
|
||||
"""
|
||||
file_name = request.form.get("file_name")
|
||||
new_file_name = request.form.get("copy_file_name")
|
||||
|
||||
process = file_cmd.copy_image(file_name, new_file_name)
|
||||
file_name = Path(request.form.get("file_name"))
|
||||
new_file_name = Path(request.form.get("copy_file_name"))
|
||||
safe_path = is_safe_path(file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
safe_path = is_safe_path(new_file_name)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
process = file_cmd.copy_image(str(file_name), str(new_file_name))
|
||||
if not process["status"]:
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
response_messages = [
|
||||
(_("Copy of image file saved as: %(file_name)s", file_name=new_file_name), "success")]
|
||||
|
||||
# Create a copy of the drive properties file, if it exists
|
||||
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
|
||||
new_prop_file_path = Path(CFG_DIR) / f"{new_file_name}.{PROPERTIES_SUFFIX}"
|
||||
|
@ -1036,11 +1067,21 @@ def copy():
|
|||
process = file_cmd.copy_file(prop_file_path, new_prop_file_path)
|
||||
process = ReturnCodeMapper.add_msg(process)
|
||||
if process["status"]:
|
||||
response_messages.append((process["msg"], "success"))
|
||||
return response(
|
||||
message=_(
|
||||
"Copy of image file with properties saved as: %(file_name)s",
|
||||
file_name=str(new_file_name),
|
||||
),
|
||||
)
|
||||
else:
|
||||
response_messages.append((process["msg"], "error"))
|
||||
return response(error=True, message=process["msg"])
|
||||
|
||||
return response(message=response_messages)
|
||||
return response(
|
||||
message=_(
|
||||
"Copy of image file saved as: %(file_name)s",
|
||||
file_name=str(new_file_name),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@APP.route("/files/extract_image", methods=["POST"])
|
||||
|
@ -1049,31 +1090,34 @@ def extract_image():
|
|||
"""
|
||||
Extracts all or a subset of files in the specified archive
|
||||
"""
|
||||
archive_file = request.form.get("archive_file")
|
||||
archive_file = Path(request.form.get("archive_file"))
|
||||
archive_members_raw = request.form.get("archive_members") or None
|
||||
archive_members = archive_members_raw.split("|") if archive_members_raw else None
|
||||
|
||||
safe_path = is_safe_path(archive_file)
|
||||
if not safe_path["status"]:
|
||||
return response(error=True, message=safe_path["msg"])
|
||||
extract_result = file_cmd.extract_image(
|
||||
archive_file,
|
||||
str(archive_file),
|
||||
archive_members
|
||||
)
|
||||
|
||||
if extract_result["return_code"] == ReturnCodes.EXTRACTIMAGE_SUCCESS:
|
||||
response_messages = [(ReturnCodeMapper.add_msg(extract_result).get("msg"), "success")]
|
||||
|
||||
for properties_file in extract_result["properties_files_moved"]:
|
||||
if properties_file["status"]:
|
||||
response_messages.append((_("Properties file %(file)s moved to %(directory)s",
|
||||
file=properties_file['name'],
|
||||
directory=CFG_DIR
|
||||
), "success"))
|
||||
logging.info(
|
||||
"Properties file %s moved to %s",
|
||||
properties_file["name"],
|
||||
CFG_DIR,
|
||||
)
|
||||
else:
|
||||
response_messages.append((_("Failed to move properties file %(file)s to %(directory)s",
|
||||
file=properties_file['name'],
|
||||
directory=CFG_DIR
|
||||
), "error"))
|
||||
logging.warning(
|
||||
"Failed to move properties file %s to %s",
|
||||
properties_file["name"],
|
||||
CFG_DIR,
|
||||
)
|
||||
|
||||
return response(message=response_messages)
|
||||
return response(message=ReturnCodeMapper.add_msg(extract_result).get("msg"))
|
||||
|
||||
return response(error=True, message=ReturnCodeMapper.add_msg(extract_result).get("msg"))
|
||||
|
||||
|
|
|
@ -240,29 +240,43 @@ def is_bridge_configured(interface):
|
|||
Takes (str) interface of a network device being attached.
|
||||
Returns a (dict) with (bool) status and (str) msg
|
||||
"""
|
||||
# TODO: Reduce the nesting of these checks, and streamline how the results are notified
|
||||
status = True
|
||||
return_msg = ""
|
||||
PATH_SYSCTL = "/etc/sysctl.conf"
|
||||
PATH_IPTV4 = "/etc/iptables/rules.v4"
|
||||
PATH_DHCPCD = "/etc/dhcpcd.conf"
|
||||
PATH_BRIDGE = "/etc/network/interfaces.d/rascsi_bridge"
|
||||
return_msg = _("Configure the network bridge for %(interface)s first: ", interface=interface)
|
||||
to_configure = []
|
||||
sys_cmd = SysCmds()
|
||||
if interface.startswith("wlan"):
|
||||
if not sys_cmd.introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"):
|
||||
status = False
|
||||
return_msg = _("Configure IPv4 forwarding before using a wireless network device.")
|
||||
elif not Path("/etc/iptables/rules.v4").is_file():
|
||||
status = False
|
||||
return_msg = _("Configure NAT before using a wireless network device.")
|
||||
if not sys_cmd.introspect_file(PATH_SYSCTL, r"^net\.ipv4\.ip_forward=1$"):
|
||||
to_configure.append("IPv4 forwarding")
|
||||
if not Path(PATH_IPTV4).is_file():
|
||||
to_configure.append("NAT")
|
||||
else:
|
||||
if not sys_cmd.introspect_file(
|
||||
"/etc/dhcpcd.conf",
|
||||
r"^denyinterfaces " + interface + r"$",
|
||||
):
|
||||
status = False
|
||||
return_msg = _("Configure the network bridge before using a wired network device.")
|
||||
elif not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
|
||||
status = False
|
||||
return_msg = _("Configure the network bridge before using a wired network device.")
|
||||
if not sys_cmd.introspect_file(PATH_DHCPCD, r"^denyinterfaces " + interface + r"$"):
|
||||
to_configure.append(PATH_DHCPCD)
|
||||
if not Path(PATH_BRIDGE).is_file():
|
||||
to_configure.append(PATH_BRIDGE)
|
||||
|
||||
return {"status": status, "msg": return_msg + f" ({interface})"}
|
||||
if to_configure:
|
||||
return {"status": False, "msg": return_msg + ", ".join(to_configure)}
|
||||
|
||||
return {"status": True, "msg": ""}
|
||||
|
||||
|
||||
def is_safe_path(file_name):
|
||||
"""
|
||||
Takes (Path) file_name with the path to a file on the file system
|
||||
Returns True if the path is safe
|
||||
Returns False if the path is either absolute, or tries to traverse the file system
|
||||
"""
|
||||
if file_name.is_absolute() or ".." in str(file_name):
|
||||
return {
|
||||
"status": False,
|
||||
"msg": _("%(file_name)s is not a valid path", file_name=file_name),
|
||||
}
|
||||
|
||||
return {"status": True, "msg": ""}
|
||||
|
||||
|
||||
def upload_with_dropzonejs(image_dir):
|
||||
|
|
|
@ -20,7 +20,6 @@ def test_create_file(http_client, list_files, delete_file):
|
|||
"file_name": file_prefix,
|
||||
"type": "hds",
|
||||
"size": 1,
|
||||
"drive_name": "DEC RZ22",
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -36,6 +35,36 @@ def test_create_file(http_client, list_files, delete_file):
|
|||
delete_file(file_name)
|
||||
|
||||
|
||||
# route("/files/create", methods=["POST"])
|
||||
def test_create_file_with_properties(http_client, list_files, delete_file):
|
||||
file_prefix = str(uuid.uuid4())
|
||||
file_name = f"{file_prefix}.hds"
|
||||
|
||||
response = http_client.post(
|
||||
"/files/create",
|
||||
data={
|
||||
"file_name": file_prefix,
|
||||
"type": "hds",
|
||||
"size": 1,
|
||||
"drive_name": "DEC RZ22",
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["data"]["image"] == file_name
|
||||
assert (
|
||||
response_data["messages"][0]["message"]
|
||||
== f"Image file with properties created: {file_name}"
|
||||
)
|
||||
assert file_name in list_files()
|
||||
|
||||
# Cleanup
|
||||
delete_file(file_name)
|
||||
|
||||
|
||||
# route("/files/rename", methods=["POST"])
|
||||
def test_rename_file(http_client, create_test_image, list_files, delete_file):
|
||||
original_file = create_test_image(auto_delete=False)
|
||||
|
@ -258,6 +287,7 @@ def test_download_url_to_iso(
|
|||
|
||||
http_path = f"/images/{test_file_name}"
|
||||
url = httpserver.url_for(http_path)
|
||||
ISO_TYPE = "ISO-9660 Level 1"
|
||||
|
||||
with open("tests/assets/test_image.hds", mode="rb") as file:
|
||||
test_file_data = file.read()
|
||||
|
@ -271,7 +301,7 @@ def test_download_url_to_iso(
|
|||
"/files/download_to_iso",
|
||||
data={
|
||||
"scsi_id": SCSI_ID,
|
||||
"type": "-hfs",
|
||||
"type": ISO_TYPE,
|
||||
"url": url,
|
||||
},
|
||||
)
|
||||
|
@ -283,10 +313,11 @@ def test_download_url_to_iso(
|
|||
assert iso_file_name in list_files()
|
||||
assert iso_file_name in list_attached_images()
|
||||
|
||||
m = response_data["messages"]
|
||||
assert m[0]["message"] == 'Created CD-ROM ISO image with arguments "-hfs"'
|
||||
assert m[1]["message"] == f"Saved image as: {env['images_dir']}/{iso_file_name}"
|
||||
assert m[2]["message"] == f"Attached to SCSI ID {SCSI_ID}"
|
||||
assert (
|
||||
response_data["messages"][0]["message"]
|
||||
== f"CD-ROM image {iso_file_name} with type {ISO_TYPE} was created "
|
||||
f"and attached to SCSI ID {SCSI_ID}"
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
|
|
|
@ -83,7 +83,10 @@ def test_create_image_with_properties_file(http_client, delete_file):
|
|||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Image file created: {file_name}"
|
||||
assert (
|
||||
response_data["messages"][0]["message"]
|
||||
== f"Image file with properties created: {file_name}"
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
delete_file(file_name)
|
||||
|
|
|
@ -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++
|
||||
AR = $(CROSS_COMPILE)ar
|
||||
RANLIB = $(CROSS_COMPILE)ranlib
|
||||
|
@ -32,6 +28,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 -I../BPI-WiringPi2/wiringPi
|
||||
|
||||
|
@ -196,7 +194,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) $(LIB_BPI_WIRINGPI) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) -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;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ SCSI NEC "Genuine" Hard Disk]
|
||||
// [ SCSI NEC Compatible Hard Disk]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -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 compatible?
|
||||
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];
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ SCSI NEC "Genuine" Hard Disk]
|
||||
// [ SCSI NEC Compatible Hard Disk]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
@ -19,31 +19,39 @@
|
|||
#include "scsihd.h"
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SCSI hard disk (PC-9801-55 NEC genuine / Anex86 / T98Next)
|
||||
// SCSI hard disk (PC-9801-55 NEC compatible / 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;
|
||||
};
|
|
@ -43,28 +43,28 @@
|
|||
//---------------------------------------------------------------------------
|
||||
// SCSI Bus timings taken from:
|
||||
// https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-05.html
|
||||
const static int SCSI_DELAY_ARBITRATION_DELAY_NS = 2400;
|
||||
const static int SCSI_DELAY_ASSERTION_PERIOD_NS = 90;
|
||||
const static int SCSI_DELAY_BUS_CLEAR_DELAY_NS = 800;
|
||||
const static int SCSI_DELAY_BUS_FREE_DELAY_NS = 800;
|
||||
const static int SCSI_DELAY_BUS_SET_DELAY_NS = 1800;
|
||||
const static int SCSI_DELAY_BUS_SETTLE_DELAY_NS = 400;
|
||||
const static int SCSI_DELAY_CABLE_SKEW_DELAY_NS = 10;
|
||||
const static int SCSI_DELAY_DATA_RELEASE_DELAY_NS = 400;
|
||||
const static int SCSI_DELAY_DESKEW_DELAY_NS = 45;
|
||||
const static int SCSI_DELAY_DISCONNECTION_DELAY_US = 200;
|
||||
const static int SCSI_DELAY_HOLD_TIME_NS = 45;
|
||||
const static int SCSI_DELAY_NEGATION_PERIOD_NS = 90;
|
||||
const static int SCSI_DELAY_POWER_ON_TO_SELECTION_TIME_S = 10; // (recommended)
|
||||
const static int SCSI_DELAY_RESET_TO_SELECTION_TIME_US = 250 * 1000; // (recommended)
|
||||
const static int SCSI_DELAY_RESET_HOLD_TIME_US = 25;
|
||||
const static int SCSI_DELAY_SELECTION_ABORT_TIME_US = 200;
|
||||
const static int SCSI_DELAY_SELECTION_TIMEOUT_DELAY_NS = 250 * 1000; // (recommended)
|
||||
const static int SCSI_DELAY_FAST_ASSERTION_PERIOD_NS = 30;
|
||||
const static int SCSI_DELAY_FAST_CABLE_SKEW_DELAY_NS = 5;
|
||||
const static int SCSI_DELAY_FAST_DESKEW_DELAY_NS = 20;
|
||||
const static int SCSI_DELAY_FAST_HOLD_TIME_NS = 10;
|
||||
const static int SCSI_DELAY_FAST_NEGATION_PERIOD_NS = 30;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_ARBITRATION_DELAY_NS = 2400;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_ASSERTION_PERIOD_NS = 90;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_BUS_CLEAR_DELAY_NS = 800;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_BUS_FREE_DELAY_NS = 800;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_BUS_SET_DELAY_NS = 1800;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_BUS_SETTLE_DELAY_NS = 400;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_CABLE_SKEW_DELAY_NS = 10;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_DATA_RELEASE_DELAY_NS = 400;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_DESKEW_DELAY_NS = 45;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_DISCONNECTION_DELAY_US = 200;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_HOLD_TIME_NS = 45;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_NEGATION_PERIOD_NS = 90;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_POWER_ON_TO_SELECTION_TIME_S = 10; // (recommended)
|
||||
[[maybe_unused]] const static int SCSI_DELAY_RESET_TO_SELECTION_TIME_US = 250 * 1000; // (recommended)
|
||||
[[maybe_unused]] const static int SCSI_DELAY_RESET_HOLD_TIME_US = 25;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_SELECTION_ABORT_TIME_US = 200;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_SELECTION_TIMEOUT_DELAY_NS = 250 * 1000; // (recommended)
|
||||
[[maybe_unused]] const static int SCSI_DELAY_FAST_ASSERTION_PERIOD_NS = 30;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_FAST_CABLE_SKEW_DELAY_NS = 5;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_FAST_DESKEW_DELAY_NS = 20;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_FAST_HOLD_TIME_NS = 10;
|
||||
[[maybe_unused]] const static int SCSI_DELAY_FAST_NEGATION_PERIOD_NS = 30;
|
||||
|
||||
// The DaynaPort SCSI Link do a short delay in the middle of transfering
|
||||
// a packet. This is the number of uS that will be delayed between the
|
||||
|
|
|
@ -118,4 +118,3 @@ uint32_t GPIOBUS_Allwinner::Acquire()
|
|||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
#pragma GCC diagnostic pop
|
||||
|
|
|
@ -35,6 +35,9 @@ class SBC_Version
|
|||
sbc_bananapi_m4,
|
||||
};
|
||||
|
||||
SBC_Version() = delete;
|
||||
~SBC_Version() = delete;
|
||||
|
||||
static void Init();
|
||||
|
||||
static sbc_version_type GetSbcVersion();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,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>
|
||||
|
@ -86,7 +85,8 @@ void Banner(int argc, char* argv[])
|
|||
cout << " hd1 : SCSI-1 HD image (Non-removable generic SCSI-1 HD image)\n";
|
||||
cout << " hds : SCSI HD image (Non-removable generic SCSI HD image)\n";
|
||||
cout << " hdr : SCSI HD image (Removable generic HD image)\n";
|
||||
cout << " hdn : SCSI HD image (NEC GENUINE)\n";
|
||||
cout << " hda : SCSI HD image (Apple compatible image)\n";
|
||||
cout << " hdn : SCSI HD image (NEC compatible image)\n";
|
||||
cout << " hdi : SCSI HD image (Anex86 HD image)\n";
|
||||
cout << " nhd : SCSI HD image (T98Next HD image)\n";
|
||||
cout << " mos : SCSI MO image (MO image)\n";
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#include "hal/gpiobus.h"
|
||||
#include "hal/gpiobus_factory.h"
|
||||
#include "rascsi_version.h"
|
||||
#include "spdlog/spdlog.h"
|
||||
#include <sys/time.h>
|
||||
#include <climits>
|
||||
#include <csignal>
|
||||
|
|
|
@ -14,65 +14,100 @@
|
|||
|
||||
using namespace scsi_defs;
|
||||
|
||||
TEST(AbstractControllerTest, AllocateCmd)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
EXPECT_EQ(16, controller.GetCmd().size());
|
||||
controller.AllocateCmd(1234);
|
||||
EXPECT_EQ(1234, controller.GetCmd().size());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, AllocateBuffer)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
controller.AllocateBuffer(1);
|
||||
EXPECT_LE(1, controller.GetBuffer().size());
|
||||
controller.AllocateBuffer(10000);
|
||||
EXPECT_LE(10000, controller.GetBuffer().size());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, Reset)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
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());
|
||||
controller.SetPhase(BUS::phase_t::status);
|
||||
EXPECT_EQ(BUS::phase_t::status, controller.GetPhase());
|
||||
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(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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(make_shared<MockBus>(), 0);
|
||||
|
||||
EXPECT_EQ(32, controller.GetMaxLuns());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, Status)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
controller.SetStatus(status::RESERVATION_CONFLICT);
|
||||
EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, ProcessPhase)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
controller.SetPhase(BUS::phase_t::selection);
|
||||
EXPECT_CALL(controller, Selection()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::selection);
|
||||
EXPECT_CALL(controller, Selection());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::busfree);
|
||||
EXPECT_CALL(controller, BusFree()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::busfree);
|
||||
EXPECT_CALL(controller, BusFree());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::datain);
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::datain);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::dataout);
|
||||
EXPECT_CALL(controller, DataOut()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::dataout);
|
||||
EXPECT_CALL(controller, DataOut());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::command);
|
||||
EXPECT_CALL(controller, Command()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::command);
|
||||
EXPECT_CALL(controller, Command());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::status);
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::status);
|
||||
EXPECT_CALL(controller, Status());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::msgin);
|
||||
EXPECT_CALL(controller, MsgIn()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::msgin);
|
||||
EXPECT_CALL(controller, MsgIn());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::msgout);
|
||||
EXPECT_CALL(controller, MsgOut()).Times(1);
|
||||
controller.ProcessPhase();
|
||||
controller.SetPhase(BUS::phase_t::msgout);
|
||||
EXPECT_CALL(controller, MsgOut());
|
||||
controller.ProcessPhase();
|
||||
|
||||
controller.SetPhase(BUS::phase_t::reselection);
|
||||
EXPECT_THROW(controller.ProcessPhase(), scsi_exception);
|
||||
|
@ -86,23 +121,24 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle)
|
|||
const int ID = 1;
|
||||
const int LUN = 4;
|
||||
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
auto device1 = make_shared<MockPrimaryDevice>(LUN);
|
||||
auto device2 = make_shared<MockPrimaryDevice>(32);
|
||||
auto device3 = make_shared<MockPrimaryDevice>(-1);
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
|
||||
EXPECT_EQ(0, controller.GetLunCount());
|
||||
EXPECT_EQ(ID, controller.GetTargetId());
|
||||
EXPECT_TRUE(controller.AddDevice(device1));
|
||||
EXPECT_FALSE(controller.AddDevice(device2));
|
||||
EXPECT_FALSE(controller.AddDevice(device3));
|
||||
EXPECT_TRUE(controller.GetLunCount() > 0);
|
||||
EXPECT_TRUE(controller.HasDeviceForLun(LUN));
|
||||
EXPECT_FALSE(controller.HasDeviceForLun(0));
|
||||
EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN));
|
||||
EXPECT_EQ(nullptr, controller.GetDeviceForLun(0));
|
||||
EXPECT_TRUE(controller.DeleteDevice(device1));
|
||||
EXPECT_EQ(0, controller.GetLunCount());
|
||||
auto device1 = make_shared<MockPrimaryDevice>(LUN);
|
||||
auto device2 = make_shared<MockPrimaryDevice>(32);
|
||||
auto device3 = make_shared<MockPrimaryDevice>(-1);
|
||||
|
||||
EXPECT_EQ(0, controller.GetLunCount());
|
||||
EXPECT_EQ(ID, controller.GetTargetId());
|
||||
EXPECT_TRUE(controller.AddDevice(device1));
|
||||
EXPECT_FALSE(controller.AddDevice(device2));
|
||||
EXPECT_FALSE(controller.AddDevice(device3));
|
||||
EXPECT_TRUE(controller.GetLunCount() > 0);
|
||||
EXPECT_TRUE(controller.HasDeviceForLun(LUN));
|
||||
EXPECT_FALSE(controller.HasDeviceForLun(0));
|
||||
EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN));
|
||||
EXPECT_EQ(nullptr, controller.GetDeviceForLun(0));
|
||||
EXPECT_TRUE(controller.RemoveDevice(device1));
|
||||
EXPECT_EQ(0, controller.GetLunCount());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, ExtractInitiatorId)
|
||||
|
@ -111,7 +147,7 @@ TEST(AbstractControllerTest, ExtractInitiatorId)
|
|||
const int INITIATOR_ID = 7;
|
||||
const int UNKNOWN_INITIATOR_ID = -1;
|
||||
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
|
||||
EXPECT_EQ(INITIATOR_ID, controller.ExtractInitiatorId((1 << INITIATOR_ID) | (1 << ID)));
|
||||
EXPECT_EQ(UNKNOWN_INITIATOR_ID, controller.ExtractInitiatorId(1 << ID));
|
||||
|
@ -119,9 +155,9 @@ TEST(AbstractControllerTest, ExtractInitiatorId)
|
|||
|
||||
TEST(AbstractControllerTest, GetOpcode)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
vector<int> &cmd = controller.InitCmd(1);
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
cmd[0] = 0x12;
|
||||
EXPECT_EQ(0x12, (int)controller.GetOpcode());
|
||||
|
@ -131,17 +167,28 @@ TEST(AbstractControllerTest, GetLun)
|
|||
{
|
||||
const int LUN = 3;
|
||||
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
EXPECT_FALSE(controller.HasValidLength());
|
||||
|
||||
controller.SetLength(1);
|
||||
EXPECT_EQ(1, controller.GetLength());
|
||||
EXPECT_TRUE(controller.HasValidLength());
|
||||
}
|
||||
|
||||
TEST(AbstractControllerTest, UpdateOffsetAndLength)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
EXPECT_FALSE(controller.HasValidLength());
|
||||
|
||||
|
@ -151,7 +198,7 @@ TEST(AbstractControllerTest, Length)
|
|||
|
||||
TEST(AbstractControllerTest, Offset)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
|
||||
controller.ResetOffset();
|
||||
EXPECT_EQ(0, controller.GetOffset());
|
||||
|
|
|
@ -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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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_ptr);
|
||||
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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
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());
|
||||
|
|
|
@ -8,28 +8,32 @@
|
|||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "devices/disk.h"
|
||||
#include "mocks.h"
|
||||
#include "devices/scsi_command_util.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "scsi.h"
|
||||
|
||||
using namespace scsi_defs;
|
||||
using namespace scsi_command_util;
|
||||
|
||||
TEST(DiskTest, Dispatch)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
const unordered_set<uint32_t> sector_sizes;
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
const unordered_set<uint32_t> sector_sizes;
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
disk->MediumChanged();
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception);
|
||||
EXPECT_FALSE(disk->Dispatch(scsi_command::eCmdIcd));
|
||||
|
||||
disk->SetRemovable(true);
|
||||
disk->MediumChanged();
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception);
|
||||
}
|
||||
|
||||
TEST(DiskTest, Rezero)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
|
@ -38,27 +42,27 @@ TEST(DiskTest, Rezero)
|
|||
|
||||
disk->SetReady(true);
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_CALL(controller, Status());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(DiskTest, FormatUnit)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
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_TRUE(disk->Dispatch(scsi_command::eCmdFormat));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_CALL(controller, Status());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdFormat));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
|
||||
cmd[1] = 0x10;
|
||||
cmd[4] = 1;
|
||||
|
@ -67,8 +71,8 @@ TEST(DiskTest, FormatUnit)
|
|||
|
||||
TEST(DiskTest, ReassignBlocks)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
|
@ -77,55 +81,69 @@ TEST(DiskTest, ReassignBlocks)
|
|||
|
||||
disk->SetReady(true);
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReassign));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
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(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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";
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_exception)
|
||||
<< "SEEK(6) 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";
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception)
|
||||
<< "SEEK(10) must fail because drive is not ready";
|
||||
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(make_shared<MockBus>(), 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)
|
||||
cmd[5] = 1;
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
// Block count
|
||||
cmd[5] = 1;
|
||||
EXPECT_CALL(controller, Status());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(DiskTest, ReadCapacity)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
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)";
|
||||
|
@ -147,204 +165,560 @@ TEST(DiskTest, ReadCapacity)
|
|||
<< "READ CAPACITY(16) must fail because the medium has no capacity";
|
||||
cmd[1] = 0x00;
|
||||
|
||||
disk->SetBlockCount(0x12345678);
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
|
||||
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0x56, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0x77, controller.GetBuffer()[3]);
|
||||
disk->SetBlockCount(0x12345678);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
|
||||
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0x56, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0x77, controller.GetBuffer()[3]);
|
||||
|
||||
disk->SetBlockCount(0x1234567887654321);
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[3]);
|
||||
disk->SetBlockCount(0x1234567887654321);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0xff, controller.GetBuffer()[3]);
|
||||
|
||||
disk->SetSectorSizeInBytes(1024);
|
||||
// READ CAPACITY(16), not READ LONG(16)
|
||||
cmd[1] = 0x10;
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
|
||||
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0x56, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0x78, controller.GetBuffer()[3]);
|
||||
EXPECT_EQ(0x87, controller.GetBuffer()[4]);
|
||||
EXPECT_EQ(0x65, controller.GetBuffer()[5]);
|
||||
EXPECT_EQ(0x43, controller.GetBuffer()[6]);
|
||||
EXPECT_EQ(0x20, controller.GetBuffer()[7]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[8]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[9]);
|
||||
EXPECT_EQ(0x04, controller.GetBuffer()[10]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[11]);
|
||||
disk->SetSectorSizeInBytes(1024);
|
||||
// READ CAPACITY(16), not READ LONG(16)
|
||||
cmd[1] = 0x10;
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
|
||||
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
|
||||
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
|
||||
EXPECT_EQ(0x56, controller.GetBuffer()[2]);
|
||||
EXPECT_EQ(0x78, controller.GetBuffer()[3]);
|
||||
EXPECT_EQ(0x87, controller.GetBuffer()[4]);
|
||||
EXPECT_EQ(0x65, controller.GetBuffer()[5]);
|
||||
EXPECT_EQ(0x43, controller.GetBuffer()[6]);
|
||||
EXPECT_EQ(0x20, controller.GetBuffer()[7]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[8]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[9]);
|
||||
EXPECT_EQ(0x04, controller.GetBuffer()[10]);
|
||||
EXPECT_EQ(0x00, controller.GetBuffer()[11]);
|
||||
}
|
||||
|
||||
TEST(DiskTest, ReadWriteLong)
|
||||
TEST(DiskTest, Read6)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10));
|
||||
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;
|
||||
|
||||
// READ LONG(16), not READ CAPACITY(16)
|
||||
cmd[1] = 0x11;
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
cmd[1] = 0x00;
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
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";
|
||||
// Further testing requires filesystem access
|
||||
}
|
||||
|
||||
TEST(DiskTest, Reserve)
|
||||
TEST(DiskTest, Read10)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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());
|
||||
|
||||
// Further testing requires filesystem access
|
||||
}
|
||||
|
||||
TEST(DiskTest, Read16)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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";
|
||||
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";
|
||||
}
|
||||
|
||||
TEST(DiskTest, ReadLong16)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
// READ LONG(16), not READ CAPACITY(16)
|
||||
cmd[1] = 0x11;
|
||||
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[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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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;
|
||||
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, StartStopUnit)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
|
||||
TEST(DiskTest, Release)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
// 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());
|
||||
|
||||
controller.AddDevice(disk);
|
||||
// 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());
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRelease6));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
disk->SetReady(false);
|
||||
EXPECT_CALL(*disk, FlushCache).Times(0);
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception);
|
||||
|
||||
TEST(DiskTest, SendDiagnostic)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
disk->SetReady(true);
|
||||
disk->SetLocked(true);
|
||||
EXPECT_CALL(*disk, FlushCache).Times(0);
|
||||
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception);
|
||||
|
||||
controller.AddDevice(disk);
|
||||
// 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());
|
||||
|
||||
vector<int> &cmd = controller.InitCmd(6);
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSendDiag));
|
||||
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;
|
||||
|
||||
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";
|
||||
// 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)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
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_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_FALSE(disk->IsLocked());
|
||||
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_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_TRUE(disk->IsLocked());
|
||||
cmd[4] = 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
controller.AddDevice(disk);
|
||||
|
||||
EXPECT_CALL(*disk, FlushCache()).Times(1);
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache10));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
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_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_CALL(*disk, FlushCache());
|
||||
EXPECT_CALL(controller, Status());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(DiskTest, ReadDefectData)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto disk = make_shared<MockDisk>();
|
||||
|
||||
controller.AddDevice(disk);
|
||||
|
||||
controller.InitCmd(10);
|
||||
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(DiskTest, SectorSize)
|
||||
|
@ -410,46 +784,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,46 +14,50 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
TEST(HostServicesTest, Dispatch)
|
||||
{
|
||||
TestDispatch(SCHS);
|
||||
}
|
||||
|
||||
TEST(HostServicesTest, TestUnitReady)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 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)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 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());
|
||||
|
||||
|
@ -64,10 +68,10 @@ TEST(HostServicesTest, StartStopUnit)
|
|||
|
||||
TEST(HostServicesTest, ModeSense6)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 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]);
|
||||
|
@ -101,10 +105,10 @@ TEST(HostServicesTest, ModeSense6)
|
|||
|
||||
TEST(HostServicesTest, ModeSense10)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto services = CreateDevice(SCHS, controller);
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 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]);
|
||||
|
@ -138,12 +142,11 @@ TEST(HostServicesTest, ModeSense10)
|
|||
|
||||
TEST(HostServicesTest, SetUpModePages)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
MockHostServices services(0, controller_manager);
|
||||
map<int, vector<byte>> mode_pages;
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
MockHostServices services(0, controller_manager);
|
||||
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"));
|
||||
}
|
|
@ -11,15 +11,18 @@
|
|||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include "test_shared.h"
|
||||
#include "bus.h"
|
||||
#include "controllers/scsi_controller.h"
|
||||
#include "devices/host_services.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "devices/scsicd.h"
|
||||
#include "devices/storage_device.h"
|
||||
#include "devices/disk.h"
|
||||
#include "devices/scsihd.h"
|
||||
#include "devices/scsihd_nec.h"
|
||||
#include "devices/scsimo.h"
|
||||
#include "rascsi/command_context.h"
|
||||
#include "test_shared.h"
|
||||
#include "rascsi/rascsi_executor.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
|
@ -85,36 +88,48 @@ class MockAbstractController : public AbstractController // NOSONAR Having many
|
|||
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, 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, Offset);
|
||||
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
|
||||
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
|
||||
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
|
||||
FRIEND_TEST(PrimaryDeviceTest, ReportLuns);
|
||||
FRIEND_TEST(PrimaryDeviceTest, UnknownCommand);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSense6);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSense10);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSelect6);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSelect10);
|
||||
FRIEND_TEST(DiskTest, Dispatch);
|
||||
FRIEND_TEST(DiskTest, Rezero);
|
||||
FRIEND_TEST(DiskTest, FormatUnit);
|
||||
FRIEND_TEST(DiskTest, ReassignBlocks);
|
||||
FRIEND_TEST(DiskTest, Seek);
|
||||
FRIEND_TEST(DiskTest, ReadCapacity);
|
||||
FRIEND_TEST(DiskTest, ReadWriteLong);
|
||||
FRIEND_TEST(DiskTest, Reserve);
|
||||
FRIEND_TEST(DiskTest, Release);
|
||||
FRIEND_TEST(DiskTest, SendDiagnostic);
|
||||
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
|
||||
FRIEND_TEST(DiskTest, SynchronizeCache);
|
||||
FRIEND_TEST(DiskTest, ReadDefectData);
|
||||
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, 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);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSense10);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSelect6);
|
||||
FRIEND_TEST(ModePageDeviceTest, ModeSelect10);
|
||||
FRIEND_TEST(DiskTest, Dispatch);
|
||||
FRIEND_TEST(DiskTest, Rezero);
|
||||
FRIEND_TEST(DiskTest, FormatUnit);
|
||||
FRIEND_TEST(DiskTest, ReassignBlocks);
|
||||
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, 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);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(BUS::phase_t, Process, (int), (override));
|
||||
|
@ -132,22 +147,42 @@ class MockAbstractController : public AbstractController // NOSONAR Having many
|
|||
MOCK_METHOD(void, SetByteTransfer, (bool), (override));
|
||||
MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override));
|
||||
|
||||
explicit MockAbstractController(shared_ptr<MockBus> bus, int target_id) : AbstractController(bus, target_id, 32)
|
||||
{
|
||||
AllocateBuffer(512);
|
||||
}
|
||||
~MockAbstractController() override = default;
|
||||
MOCK_METHOD(BUS::phase_t, Process, (int), (override));
|
||||
MOCK_METHOD(int, GetEffectiveLun, (), (const override));
|
||||
MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override));
|
||||
MOCK_METHOD(int, GetInitiatorId, (), (const override));
|
||||
MOCK_METHOD(void, Status, (), ());
|
||||
MOCK_METHOD(void, DataIn, (), ());
|
||||
MOCK_METHOD(void, DataOut, (), ());
|
||||
MOCK_METHOD(void, BusFree, (), ());
|
||||
MOCK_METHOD(void, Selection, (), ());
|
||||
MOCK_METHOD(void, Command, (), ());
|
||||
MOCK_METHOD(void, MsgIn, (), ());
|
||||
MOCK_METHOD(void, MsgOut, (), ());
|
||||
MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override));
|
||||
|
||||
vector<int> &InitCmd(int size) // NOSONAR Hides function on purpose
|
||||
{
|
||||
return AbstractController::InitCmd(size);
|
||||
}
|
||||
explicit MockAbstractController(shared_ptr<MockBus> bus, int target_id) : AbstractController(bus, target_id, 32) {
|
||||
AllocateBuffer(512);
|
||||
}
|
||||
~MockAbstractController() override = default;
|
||||
|
||||
// Permit access to all tests without the need for numerous FRIEND_TEST
|
||||
vector<int>& GetCmd() { return AbstractController::GetCmd(); } //NOSONAR Hides function on purpose
|
||||
};
|
||||
|
||||
class MockScsiController : public ScsiController
|
||||
{
|
||||
FRIEND_TEST(ScsiControllerTest, RequestSense);
|
||||
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
|
||||
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);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(void, Reset, (), ());
|
||||
|
@ -156,46 +191,62 @@ class MockScsiController : public ScsiController
|
|||
MOCK_METHOD(void, DataOut, (), ());
|
||||
MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override));
|
||||
|
||||
explicit MockScsiController(shared_ptr<BUS> bus, int target_id) : ScsiController(bus, target_id) {}
|
||||
~MockScsiController() override = default;
|
||||
MOCK_METHOD(void, Reset, (), ());
|
||||
MOCK_METHOD(void, Status, (), ());
|
||||
MOCK_METHOD(void, Execute, (), ());
|
||||
|
||||
using ScsiController::ScsiController;
|
||||
explicit MockScsiController(shared_ptr<MockBus> bus, int target_id) : ScsiController(bus, target_id) {}
|
||||
MockScsiController(shared_ptr<MockBus> bus) : ScsiController(bus, 0) {}
|
||||
~MockScsiController() override = default;
|
||||
|
||||
};
|
||||
|
||||
class MockDevice : public Device
|
||||
{
|
||||
FRIEND_TEST(DeviceTest, Params);
|
||||
FRIEND_TEST(DeviceTest, StatusCode);
|
||||
FRIEND_TEST(DeviceTest, Reset);
|
||||
FRIEND_TEST(DeviceTest, Start);
|
||||
FRIEND_TEST(DeviceTest, Stop);
|
||||
FRIEND_TEST(DeviceTest, Eject);
|
||||
FRIEND_TEST(DeviceTest, Properties);
|
||||
FRIEND_TEST(DeviceTest, Params);
|
||||
FRIEND_TEST(DeviceTest, StatusCode);
|
||||
FRIEND_TEST(DeviceTest, Reset);
|
||||
FRIEND_TEST(DeviceTest, Start);
|
||||
FRIEND_TEST(DeviceTest, Stop);
|
||||
FRIEND_TEST(DeviceTest, Eject);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(int, GetId, (), (const));
|
||||
|
||||
explicit MockDevice(int lun) : Device("test", lun) {}
|
||||
~MockDevice() override = default;
|
||||
MOCK_METHOD(int, GetId, (), (const));
|
||||
|
||||
explicit MockDevice(int lun) : Device(UNDEFINED, lun) {}
|
||||
explicit MockDevice(PbDeviceType type) : Device(type, 0) {}
|
||||
~MockDevice() override = default;
|
||||
};
|
||||
|
||||
class MockPrimaryDevice : public PrimaryDevice
|
||||
{
|
||||
FRIEND_TEST(PrimaryDeviceTest, PhaseChange);
|
||||
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
|
||||
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
|
||||
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
|
||||
FRIEND_TEST(ScsiControllerTest, RequestSense);
|
||||
FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice);
|
||||
FRIEND_TEST(PrimaryDeviceTest, PhaseChange);
|
||||
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
|
||||
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
|
||||
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
|
||||
FRIEND_TEST(PrimaryDeviceTest, GetSetSendDelay);
|
||||
FRIEND_TEST(ScsiControllerTest, RequestSense);
|
||||
FRIEND_TEST(RascsiExecutorTest, ValidateOperationAgainstDevice);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(void, Reset, (), ());
|
||||
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
|
||||
|
||||
explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {}
|
||||
~MockPrimaryDevice() override = default;
|
||||
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
|
||||
|
||||
explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {}
|
||||
~MockPrimaryDevice() override = default;
|
||||
};
|
||||
|
||||
class MockModePageDevice : public ModePageDevice
|
||||
{
|
||||
FRIEND_TEST(ModePageDeviceTest, AddModePages);
|
||||
FRIEND_TEST(ModePageDeviceTest, SupportsSaveParameters);
|
||||
FRIEND_TEST(ModePageDeviceTest, AddModePages);
|
||||
FRIEND_TEST(ModePageDeviceTest, AddVendorPage);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
|
||||
|
@ -205,67 +256,135 @@ class MockModePageDevice : public ModePageDevice
|
|||
explicit MockModePageDevice() : ModePageDevice("test", 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);
|
||||
pages[page] = buf;
|
||||
}
|
||||
}
|
||||
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(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, Rezero);
|
||||
FRIEND_TEST(DiskTest, FormatUnit);
|
||||
FRIEND_TEST(DiskTest, ReassignBlocks);
|
||||
FRIEND_TEST(DiskTest, Seek);
|
||||
FRIEND_TEST(DiskTest, ReadCapacity);
|
||||
FRIEND_TEST(DiskTest, ReadWriteLong);
|
||||
FRIEND_TEST(DiskTest, ReserveRelease);
|
||||
FRIEND_TEST(DiskTest, SendDiagnostic);
|
||||
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
|
||||
FRIEND_TEST(DiskTest, SynchronizeCache);
|
||||
FRIEND_TEST(DiskTest, ReadDefectData);
|
||||
FRIEND_TEST(DiskTest, SectorSize);
|
||||
FRIEND_TEST(DiskTest, BlockCount);
|
||||
FRIEND_TEST(DiskTest, GetIdsForReservedFile);
|
||||
FRIEND_TEST(DiskTest, Dispatch);
|
||||
FRIEND_TEST(DiskTest, Rezero);
|
||||
FRIEND_TEST(DiskTest, FormatUnit);
|
||||
FRIEND_TEST(DiskTest, ReassignBlocks);
|
||||
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, 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);
|
||||
|
||||
public:
|
||||
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
|
||||
MOCK_METHOD(void, FlushCache, (), (override));
|
||||
|
||||
MockDisk() : Disk("test", 0) {}
|
||||
~MockDisk() override = default;
|
||||
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
|
||||
MOCK_METHOD(void, FlushCache, (), (override));
|
||||
MOCK_METHOD(void, Open, (), (override));
|
||||
|
||||
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, SetUpModePages);
|
||||
FRIEND_TEST(RascsiExecutorTest, SetSectorSize);
|
||||
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;
|
||||
};
|
||||
|
||||
class MockSCSIHD_NEC : public SCSIHD_NEC // NOSONAR Ignore inheritance hierarchy depth in unit tests
|
||||
{
|
||||
FRIEND_TEST(ScsiHdNecTest, SetUpModePages);
|
||||
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, 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, SetUpModePages);
|
||||
FRIEND_TEST(ScsiMoTest, SupportsSaveParameters);
|
||||
FRIEND_TEST(ScsiMoTest, SetUpModePages);
|
||||
FRIEND_TEST(ScsiMoTest, TestAddVendorPage);
|
||||
FRIEND_TEST(ScsiMoTest, ModeSelect);
|
||||
|
||||
using SCSIMO::SCSIMO;
|
||||
};
|
||||
|
@ -286,3 +405,13 @@ class MockCommandContext : public CommandContext
|
|||
}
|
||||
~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,46 +13,92 @@
|
|||
|
||||
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;
|
||||
// Page 0
|
||||
cdb[2] = 0x00;
|
||||
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, 255), scsi_exception)
|
||||
<< "Data were returned for non-existing mode page 0";
|
||||
|
||||
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";
|
||||
// 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";
|
||||
|
||||
cdb[2] = 0x00;
|
||||
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_exception)
|
||||
<< "Data were returned for non-existing mode page 0";
|
||||
// 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(make_shared<MockBus>(), 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)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<NiceMock<MockModePageDevice>>();
|
||||
auto device = make_shared<NiceMock<MockModePageDevice>>();
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
controller.InitCmd(6);
|
||||
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense6));
|
||||
}
|
||||
|
||||
TEST(ModePageDeviceTest, ModeSense10)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<NiceMock<MockModePageDevice>>();
|
||||
auto device = make_shared<NiceMock<MockModePageDevice>>();
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
controller.InitCmd(10);
|
||||
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense10));
|
||||
}
|
||||
|
||||
|
@ -62,19 +108,22 @@ 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)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockModePageDevice>();
|
||||
auto device = make_shared<MockModePageDevice>();
|
||||
|
||||
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;
|
||||
|
@ -85,13 +134,13 @@ TEST(ModePageDeviceTest, ModeSelect6)
|
|||
TEST(ModePageDeviceTest, ModeSelect10)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockModePageDevice>();
|
||||
auto device = make_shared<MockModePageDevice>();
|
||||
|
||||
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;
|
||||
|
|
|
@ -19,8 +19,8 @@ TEST(PrimaryDeviceTest, GetId)
|
|||
{
|
||||
const int ID = 5;
|
||||
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), ID);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
EXPECT_EQ(-1, device->GetId()) << "Device ID cannot be known without assignment to a controller";
|
||||
|
||||
|
@ -30,25 +30,110 @@ TEST(PrimaryDeviceTest, GetId)
|
|||
|
||||
TEST(PrimaryDeviceTest, PhaseChange)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
device->EnterStatusPhase();
|
||||
EXPECT_CALL(controller, Status());
|
||||
device->EnterStatusPhase();
|
||||
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
device->EnterDataInPhase();
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
device->EnterDataInPhase();
|
||||
|
||||
EXPECT_CALL(controller, DataOut()).Times(1);
|
||||
device->EnterDataOutPhase();
|
||||
EXPECT_CALL(controller, DataOut());
|
||||
device->EnterDataOutPhase();
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, Reset)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 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(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
|
@ -76,91 +161,119 @@ TEST(PrimaryDeviceTest, TestUnitReady)
|
|||
EXPECT_CALL(controller, DataIn()).Times(0);
|
||||
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception);
|
||||
|
||||
device->SetReady(true);
|
||||
EXPECT_CALL(controller, Status()).Times(1);
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
device->SetReady(true);
|
||||
EXPECT_CALL(controller, Status());
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, Inquiry)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
auto controller = make_shared<NiceMock<MockAbstractController>>(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
device->SetController(&controller);
|
||||
controller->AddDevice(device);
|
||||
|
||||
vector<int> &cmd = controller.InitCmd(6);
|
||||
// ALLOCATION LENGTH
|
||||
cmd[4] = 255;
|
||||
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_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
|
||||
EXPECT_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported";
|
||||
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
|
||||
return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
|
||||
});
|
||||
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";
|
||||
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_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_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";
|
||||
|
||||
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_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";
|
||||
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
|
||||
return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true);
|
||||
});
|
||||
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";
|
||||
|
||||
cmd[1] = 0x01;
|
||||
EXPECT_CALL(controller, DataIn()).Times(0);
|
||||
EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "EVPD bit is not supported";
|
||||
cmd[1] = 0x01;
|
||||
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_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "PAGE CODE field is not supported";
|
||||
cmd[2] = 0x01;
|
||||
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_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";
|
||||
cmd[1] = 0x00;
|
||||
cmd[2] = 0x00;
|
||||
// ALLOCATION LENGTH
|
||||
cmd[4] = 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";
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, RequestSense)
|
||||
{
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
NiceMock<MockAbstractController> controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
vector<int> &cmd = controller.InitCmd(6);
|
||||
// ALLOCATION LENGTH
|
||||
cmd[4] = 255;
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
// ALLOCATION LENGTH
|
||||
cmd[4] = 255;
|
||||
|
||||
device->SetReady(false);
|
||||
EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_exception);
|
||||
|
||||
device->SetReady(true);
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
device->SetReady(true);
|
||||
EXPECT_CALL(controller, DataIn());
|
||||
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
|
||||
EXPECT_EQ(status::GOOD, controller.GetStatus());
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, SendDiagnostic)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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)
|
||||
|
@ -168,42 +281,42 @@ TEST(PrimaryDeviceTest, ReportLuns)
|
|||
const int LUN1 = 1;
|
||||
const int LUN2 = 4;
|
||||
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device1 = make_shared<MockPrimaryDevice>(LUN1);
|
||||
auto device2 = make_shared<MockPrimaryDevice>(LUN2);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device1 = make_shared<MockPrimaryDevice>(LUN1);
|
||||
auto device2 = make_shared<MockPrimaryDevice>(LUN2);
|
||||
|
||||
controller.AddDevice(device1);
|
||||
EXPECT_TRUE(controller.HasDeviceForLun(LUN1));
|
||||
controller.AddDevice(device2);
|
||||
EXPECT_TRUE(controller.HasDeviceForLun(LUN2));
|
||||
|
||||
vector<int> &cmd = controller.InitCmd(10);
|
||||
// ALLOCATION LENGTH
|
||||
cmd[9] = 255;
|
||||
vector<int>& cmd = controller.GetCmd();
|
||||
// ALLOCATION LENGTH
|
||||
cmd[9] = 255;
|
||||
|
||||
EXPECT_CALL(controller, DataIn()).Times(1);
|
||||
EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns));
|
||||
const vector<BYTE> &buffer = controller.GetBuffer();
|
||||
EXPECT_EQ(0x00, buffer[0]) << "Wrong data length";
|
||||
EXPECT_EQ(0x00, buffer[1]) << "Wrong data length";
|
||||
EXPECT_EQ(0x00, buffer[2]) << "Wrong data length";
|
||||
EXPECT_EQ(0x10, buffer[3]) << "Wrong data length";
|
||||
EXPECT_EQ(0x00, buffer[8]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[9]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[10]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[11]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[12]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[13]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[14]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(LUN1, buffer[15]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[16]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[17]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[18]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[19]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[20]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[21]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[22]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(LUN2, buffer[23]) << "Wrong LUN2 number";
|
||||
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";
|
||||
EXPECT_EQ(0x00, buffer[1]) << "Wrong data length";
|
||||
EXPECT_EQ(0x00, buffer[2]) << "Wrong data length";
|
||||
EXPECT_EQ(0x10, buffer[3]) << "Wrong data length";
|
||||
EXPECT_EQ(0x00, buffer[8]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[9]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[10]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[11]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[12]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[13]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[14]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(LUN1, buffer[15]) << "Wrong LUN1 number";
|
||||
EXPECT_EQ(0x00, buffer[16]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[17]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[18]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[19]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[20]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[21]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(0x00, buffer[22]) << "Wrong LUN2 number";
|
||||
EXPECT_EQ(LUN2, buffer[23]) << "Wrong LUN2 number";
|
||||
|
||||
cmd[2] = 0x01;
|
||||
EXPECT_THROW(device1->Dispatch(scsi_command::eCmdReportLuns), scsi_exception)
|
||||
|
@ -212,27 +325,39 @@ TEST(PrimaryDeviceTest, ReportLuns)
|
|||
|
||||
TEST(PrimaryDeviceTest, UnknownCommand)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
MockAbstractController controller(make_shared<MockBus>(), 0);
|
||||
auto device = make_shared<MockPrimaryDevice>(0);
|
||||
|
||||
controller.AddDevice(device);
|
||||
|
||||
EXPECT_FALSE(device->Dispatch((scsi_command)0xFF));
|
||||
}
|
||||
|
||||
TEST(PrimaryDeviceTest, Dispatch)
|
||||
{
|
||||
MockAbstractController controller(make_shared<MockBus>(), 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)
|
||||
|
@ -240,7 +365,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,54 +7,221 @@
|
|||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
shared_ptr<MockBus> bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
MockAbstractController controller(bus_ptr, ID);
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
shared_ptr<MockBus> bus_ptr;
|
||||
DeviceFactory device_factory;
|
||||
MockAbstractController controller(bus_ptr, 0);
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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)
|
||||
{
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
||||
EXPECT_TRUE(executor.SetLogLevel("trace"));
|
||||
|
@ -62,19 +229,17 @@ 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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -101,26 +266,61 @@ 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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -147,7 +347,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,35 +355,41 @@ 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;
|
||||
const int LUN2 = 1;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
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,21 +397,22 @@ 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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
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,11 +421,10 @@ TEST(RascsiExecutorTest, DetachAll)
|
|||
EXPECT_TRUE(controller_manager.GetAllDevices().empty());
|
||||
}
|
||||
|
||||
TEST(RascsiExecutorTest, ShutDown)
|
||||
TEST_F(RascsiExecutorTest, ShutDown)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -231,11 +437,10 @@ TEST(RascsiExecutorTest, ShutDown)
|
|||
EXPECT_FALSE(executor.ShutDown(context, "reboot"));
|
||||
}
|
||||
|
||||
TEST(RascsiExecutorTest, SetReservedIds)
|
||||
TEST_F(RascsiExecutorTest, SetReservedIds)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -266,28 +471,23 @@ 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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
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,11 +495,10 @@ TEST(RascsiExecutorTest, ValidateImageFile)
|
|||
EXPECT_TRUE(full_path.empty());
|
||||
}
|
||||
|
||||
TEST(RascsiExecutorTest, ValidateLunSetup)
|
||||
TEST_F(RascsiExecutorTest, ValidateLunSetup)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -314,38 +513,36 @@ 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;
|
||||
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -360,32 +557,30 @@ TEST(RascsiExecutorTest, CreateDevice)
|
|||
EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, ""));
|
||||
}
|
||||
|
||||
TEST(RascsiExecutorTest, SetSectorSize)
|
||||
TEST_F(RascsiExecutorTest, SetSectorSize)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -393,54 +588,53 @@ 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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
@ -454,11 +648,10 @@ TEST(RascsiExecutorTest, ValidateIdAndLun)
|
|||
EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31));
|
||||
}
|
||||
|
||||
TEST(RascsiExecutorTest, SetProductData)
|
||||
TEST_F(RascsiExecutorTest, SetProductData)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
DeviceFactory device_factory;
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
RascsiImage rascsi_image;
|
||||
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
|
||||
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
|
||||
|
|
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)
|
|||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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)
|
||||
{
|
||||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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,25 @@ 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);
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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 +88,152 @@ TEST(RascsiResponseTest, GetReservedIds)
|
|||
auto bus_ptr = make_shared<MockBus>();
|
||||
ControllerManager controller_manager(bus_ptr);
|
||||
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;
|
||||
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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)
|
||||
{
|
||||
ControllerManager controller_manager(make_shared<MockBus>());
|
||||
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);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user