Resolved issues 772, 827, 909; added numerous unit tests; code cleanup (#915)

* Resolved issues 772, 827, 909

* Added numerous unit tests

* Code cleanup

* Improved type safety by using PbDeviceType instead of string

* Do not flush cache on failed STOP UNIT

* Error message and error handling updates

* Removed duplicate code

* Use map for mapping shift counts

* Reject read/write access if the drive has 0 sectors

* Updated logging configuration for tests
This commit is contained in:
Uwe Seimet 2022-10-23 21:51:39 +02:00 committed by GitHub
parent 198c10f70a
commit f3553c5480
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 4211 additions and 2041 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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'.

View File

@ -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)

View File

@ -1,15 +1,11 @@
.DEFAULT_GOAL: all
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10)
## Optional build flags:
## CROSS_COMPILE : Specify which compiler toolchain to use.
## To cross compile set this accordingly, e.g. to:
## arm-linux-gnueabihf-
CROSS_COMPILE =
CC = $(CROSS_COMPILE)gcc
CXX = $(CROSS_COMPILE)g++
## DEBUG=1 : A Debug build includes the debugger symbols
@ -30,6 +26,8 @@ ifeq ("$(shell uname -s)","Linux")
CXXFLAGS += -Wno-psabi
endif
# Depending on the GCC version the compilation flags differ
GCCVERSION10 := $(shell expr `$(CXX) -dumpversion` \>= 10)
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
@ -186,7 +184,7 @@ lcov: test
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
$(SRC_SHARED): $(SRC_PROTOBUF)
$(SRC_SHARED) $(SRC_RASCSI_CORE) $(SRC_RASCTL_CORE): $(OBJ_PROTOBUF)
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs

View File

@ -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;
}
}

View File

@ -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

View File

@ -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; }
};

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 = {};

View File

@ -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;
}
}

View File

@ -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; }

View File

@ -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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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;
}

View File

@ -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;

View File

@ -20,5 +20,4 @@ public:
virtual ~ScsiMmcCommands() = default;
virtual void ReadToc() = 0;
virtual void GetEventStatusNotification() = 0;
};

View File

@ -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);
}

View File

@ -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;
};

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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 {

View File

@ -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);

View File

@ -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;
}

View File

@ -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;

View File

@ -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)

View File

@ -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>&);

View File

@ -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()

View File

@ -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;
};

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -23,42 +23,23 @@ using namespace scsi_command_util;
const unordered_set<uint32_t> SCSIHD_NEC::sector_sizes = { 512 };
//---------------------------------------------------------------------------
//
// Extract words that are supposed to be little endian
//
//---------------------------------------------------------------------------
static inline int getWordLE(const BYTE *b)
{
return ((int)b[1] << 8) | (int)b[0];
}
//---------------------------------------------------------------------------
//
// Extract longwords assumed to be little endian
//
//---------------------------------------------------------------------------
static inline uint32_t getDwordLE(const BYTE *b)
{
return ((uint32_t)(b[3]) << 24) | ((uint32_t)(b[2]) << 16) | ((uint32_t)(b[1]) << 8) | b[0];
}
void SCSIHD_NEC::Open(const Filepath& path)
void SCSIHD_NEC::Open()
{
assert(!IsReady());
// Open as read-only
Filepath path;
path.SetPath(GetFilename().c_str());
Fileio fio;
if (!fio.Open(path, Fileio::OpenMode::ReadOnly)) {
throw file_not_found_exception("Can't open hard disk file");
throw file_not_found_exception("Can't open hard disk file '" + GetFilename() + '"');
}
// Get file size
off_t size = fio.GetFileSize();
off_t size = GetFileSize();
// NEC root sector
array<BYTE, 512> root_sector;
if (size >= (off_t)root_sector.size() && !fio.Read(root_sector.data(), root_sector.size())) {
if (size < (off_t)root_sector.size() || !fio.Read(root_sector.data(), root_sector.size())) {
fio.Close();
throw io_exception("Can't read NEC hard disk file root sector");
}
@ -67,46 +48,8 @@ void SCSIHD_NEC::Open(const Filepath& path)
// Effective size must be a multiple of 512
size = (size / 512) * 512;
int image_size = 0;
int sector_size = 0;
// Determine parameters by extension
// PC-9801-55 NEC genuine?
if (const char *ext = path.GetFileExt(); !strcasecmp(ext, ".hdn")) {
// Assuming sector size 512, number of sectors 25, number of heads 8 as default settings
image_offset = 0;
image_size = (int)size;
sector_size = 512;
sectors = 25;
heads = 8;
cylinders = (int)(size >> 9);
cylinders >>= 3;
cylinders /= 25;
}
// Anex86 HD image?
else if (!strcasecmp(ext, ".hdi")) {
image_offset = getDwordLE(&root_sector[8]);
image_size = getDwordLE(&root_sector[12]);
sector_size = getDwordLE(&root_sector[16]);
sectors = getDwordLE(&root_sector[20]);
heads = getDwordLE(&root_sector[24]);
cylinders = getDwordLE(&root_sector[28]);
}
// T98Next HD image?
else if (!strcasecmp(ext, ".nhd")) {
if (!memcmp(root_sector.data(), "T98HDDIMAGE.R0\0", 15)) {
image_offset = getDwordLE(&root_sector[0x110]);
cylinders = getDwordLE(&root_sector[0x114]);
heads = getWordLE(&root_sector[0x118]);
sectors = getWordLE(&root_sector[0x11a]);
sector_size = getWordLE(&root_sector[0x11c]);
image_size = (int)((off_t)cylinders * heads * sectors * sector_size);
}
else {
throw io_exception("Invalid NEC image file format");
}
}
const auto [image_size, sector_size] = SetParameters(path.GetFileExt(), root_sector, (int)size);
if (sector_size == 0) {
throw io_exception("Invalid NEC drive sector size");
@ -129,7 +72,57 @@ void SCSIHD_NEC::Open(const Filepath& path)
SetBlockCount(image_size >> GetSectorSizeShiftCount());
FinalizeSetup(path, size, image_offset);
FinalizeSetup(size, image_offset);
}
pair<int, int> SCSIHD_NEC::SetParameters(const string& extension, const array<BYTE, 512>& root_sector, int size)
{
string ext = extension;
transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
int image_size;
int sector_size;
// PC-9801-55 NEC genuine?
if (ext == ".hdn") {
// Assuming sector size 512, number of sectors 25, number of heads 8 as default settings
image_offset = 0;
image_size = size;
sector_size = 512;
sectors = 25;
heads = 8;
cylinders = size >> 9;
cylinders >>= 3;
cylinders /= 25;
}
// Anex86 HD image?
else if (ext == ".hdi") {
image_offset = GetInt32LittleEndian(&root_sector[8]);
image_size = GetInt32LittleEndian(&root_sector[12]);
sector_size = GetInt32LittleEndian(&root_sector[16]);
sectors = GetInt32LittleEndian(&root_sector[20]);
heads = GetInt32LittleEndian(&root_sector[24]);
cylinders = GetInt32LittleEndian(&root_sector[28]);
}
// T98Next HD image?
else if (ext == ".nhd") {
if (!memcmp(root_sector.data(), "T98HDDIMAGE.R0\0", 15)) {
image_offset = GetInt32LittleEndian(&root_sector[0x110]);
cylinders = GetInt32LittleEndian(&root_sector[0x114]);
heads = GetInt16LittleEndian(&root_sector[0x118]);
sectors = GetInt16LittleEndian(&root_sector[0x11a]);
sector_size = GetInt16LittleEndian(&root_sector[0x11c]);
image_size = (int)((off_t)cylinders * heads * sectors * sector_size);
}
else {
throw io_exception("Invalid NEC image file format");
}
}
else {
throw io_exception("Invalid NEC image file extension");
}
return make_pair(image_size, sector_size);
}
vector<byte> SCSIHD_NEC::InquiryInternal() const
@ -137,15 +130,6 @@ vector<byte> SCSIHD_NEC::InquiryInternal() const
return HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, false);
}
void SCSIHD_NEC::AddErrorPage(map<int, vector<byte>>& pages, bool) const
{
vector<byte> buf(8);
// The retry count is 0, and the limit time uses the default value inside the device.
pages[1] = buf;
}
void SCSIHD_NEC::AddFormatPage(map<int, vector<byte>>& pages, bool changeable) const
{
vector<byte> buf(24);
@ -196,3 +180,13 @@ void SCSIHD_NEC::AddDrivePage(map<int, vector<byte>>& pages, bool changeable) co
pages[4] = buf;
}
int SCSIHD_NEC::GetInt16LittleEndian(const BYTE *buf)
{
return ((int)buf[1] << 8) | buf[0];
}
int SCSIHD_NEC::GetInt32LittleEndian(const BYTE *buf)
{
return ((int)buf[3] << 24) | ((int)buf[2] << 16) | ((int)buf[1] << 8) | buf[0];
}

View File

@ -19,6 +19,8 @@
#include "scsihd.h"
#include <unordered_set>
#include <map>
#include <array>
#include <vector>
using namespace std;
@ -27,23 +29,29 @@ using namespace std;
// SCSI hard disk (PC-9801-55 NEC genuine / Anex86 / T98Next)
//
//===========================================================================
class SCSIHD_NEC : public SCSIHD
class SCSIHD_NEC : public SCSIHD //NOSONAR The inheritance hierarchy depth is acceptable in this case
{
public:
explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {}
~SCSIHD_NEC() override = default;
void Open(const Filepath&) override;
void Open() override;
protected:
vector<byte> InquiryInternal() const override;
void AddErrorPage(map<int, vector<byte>>&, bool) const override;
void AddFormatPage(map<int, vector<byte>>&, bool) const override;
void AddDrivePage(map<int, vector<byte>>&, bool) const override;
private:
pair<int, int> SetParameters(const string&, const array<BYTE, 512>&, int);
static int GetInt16LittleEndian(const BYTE *);
static int GetInt32LittleEndian(const BYTE *);
static const unordered_set<uint32_t> sector_sizes;
// Image file offset (NEC only)

View File

@ -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;

View File

@ -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:

View 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 + "'");
}

View 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;
};

View File

@ -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__)

View File

@ -10,7 +10,6 @@
#include "sm_reports.h"
#include "log.h"
#include "spdlog/spdlog.h"
#include "string.h"
#include <iostream>
#include <fstream>

View File

@ -11,7 +11,6 @@
#include "os.h"
#include "log.h"
#include "spdlog/spdlog.h"
#include <sstream>
#include <iostream>
#include <fstream>

View File

@ -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");
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -28,7 +28,6 @@
#include "rascsi/rascsi_image.h"
#include "rascsi/rascsi_service.h"
#include "rasutil.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include <netinet/in.h>
#include <csignal>

View File

@ -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);

View File

@ -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;

View File

@ -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>);

View File

@ -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();

View File

@ -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;

View File

@ -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();

View File

@ -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;

View File

@ -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
}

View File

@ -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;

View File

@ -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; }
};

View File

@ -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).

View File

@ -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;
}

View File

@ -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)

View File

@ -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;

View File

@ -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
}

View File

@ -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);
}

View File

@ -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 {

View File

@ -12,7 +12,6 @@
#include "log.h"
#include "hal/gpiobus.h"
#include "rascsi_version.h"
#include "spdlog/spdlog.h"
#include <sys/time.h>
#include <climits>
#include <csignal>

View File

@ -14,6 +14,25 @@
using namespace scsi_defs;
TEST(AbstractControllerTest, AllocateCmd)
{
MockAbstractController controller(0);
EXPECT_EQ(16, controller.GetCmd().size());
controller.AllocateCmd(1234);
EXPECT_EQ(1234, controller.GetCmd().size());
}
TEST(AbstractControllerTest, AllocateBuffer)
{
MockAbstractController controller(0);
controller.AllocateBuffer(1);
EXPECT_LE(1, controller.GetBuffer().size());
controller.AllocateBuffer(10000);
EXPECT_LE(10000, controller.GetBuffer().size());
}
TEST(AbstractControllerTest, Reset)
{
MockAbstractController controller(0);
@ -23,19 +42,35 @@ TEST(AbstractControllerTest, Reset)
controller.SetPhase(BUS::phase_t::status);
EXPECT_EQ(BUS::phase_t::status, controller.GetPhase());
EXPECT_CALL(*device, Reset()).Times(1);
controller.Reset();
EXPECT_TRUE(controller.IsBusFree());
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_EQ(0, controller.GetLength());
}
TEST(AbstractControllerTest, SetGetStatus)
TEST(AbstractControllerTest, ByteTransfer)
{
MockAbstractController controller(0);
controller.SetStatus(status::BUSY);
EXPECT_EQ(status::BUSY, controller.GetStatus());
controller.SetByteTransfer(false);
EXPECT_FALSE(controller.IsByteTransfer());
controller.SetByteTransfer(true);
EXPECT_TRUE(controller.IsByteTransfer());
}
TEST(AbstractControllerTest, GetMaxLuns)
{
MockAbstractController controller(0);
EXPECT_EQ(32, controller.GetMaxLuns());
}
TEST(AbstractControllerTest, Status)
{
MockAbstractController controller(0);
controller.SetStatus(status::RESERVATION_CONFLICT);
EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus());
}
TEST(AbstractControllerTest, ProcessPhase)
@ -43,35 +78,35 @@ TEST(AbstractControllerTest, ProcessPhase)
MockAbstractController controller(0);
controller.SetPhase(BUS::phase_t::selection);
EXPECT_CALL(controller, Selection()).Times(1);
EXPECT_CALL(controller, Selection());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::busfree);
EXPECT_CALL(controller, BusFree()).Times(1);
EXPECT_CALL(controller, BusFree());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::datain);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::dataout);
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::command);
EXPECT_CALL(controller, Command()).Times(1);
EXPECT_CALL(controller, Command());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::status);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::msgin);
EXPECT_CALL(controller, MsgIn()).Times(1);
EXPECT_CALL(controller, MsgIn());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::msgout);
EXPECT_CALL(controller, MsgOut()).Times(1);
EXPECT_CALL(controller, MsgOut());
controller.ProcessPhase();
controller.SetPhase(BUS::phase_t::reselection);
@ -101,7 +136,7 @@ TEST(AbstractControllerTest, DeviceLunLifeCycle)
EXPECT_FALSE(controller.HasDeviceForLun(0));
EXPECT_NE(nullptr, controller.GetDeviceForLun(LUN));
EXPECT_EQ(nullptr, controller.GetDeviceForLun(0));
EXPECT_TRUE(controller.DeleteDevice(device1));
EXPECT_TRUE(controller.RemoveDevice(device1));
EXPECT_EQ(0, controller.GetLunCount());
}
@ -121,7 +156,7 @@ TEST(AbstractControllerTest, GetOpcode)
{
MockAbstractController controller(0);
vector<int>& cmd = controller.InitCmd(1);
vector<int>& cmd = controller.GetCmd();
cmd[0] = 0x12;
EXPECT_EQ(0x12, (int)controller.GetOpcode());
@ -133,13 +168,24 @@ TEST(AbstractControllerTest, GetLun)
MockAbstractController controller(0);
vector<int>& cmd = controller.InitCmd(2);
vector<int>& cmd = controller.GetCmd();
cmd[1] = LUN << 5;
EXPECT_EQ(LUN, controller.GetLun());
}
TEST(AbstractControllerTest, Length)
TEST(AbstractControllerTest, SetLength)
{
MockAbstractController controller(0);
EXPECT_FALSE(controller.HasValidLength());
controller.SetLength(1);
EXPECT_EQ(1, controller.GetLength());
EXPECT_TRUE(controller.HasValidLength());
}
TEST(AbstractControllerTest, UpdateOffsetAndLength)
{
MockAbstractController controller(0);

View File

@ -7,7 +7,7 @@
//
//---------------------------------------------------------------------------
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "rascsi/command_context.h"

View File

@ -14,17 +14,17 @@
TEST(ControllerManagerTest, LifeCycle)
{
const int ID = 4;
const int LUN1 = 2;
const int LUN1 = 0;
const int LUN2 = 3;
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, -1, "services");
auto device = device_factory.CreateDevice(controller_manager, SCHS, -1, "");
EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device));
device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services");
device = device_factory.CreateDevice(controller_manager, SCHS, LUN1, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device));
auto controller = controller_manager.FindController(ID);
EXPECT_NE(nullptr, controller);
@ -35,7 +35,7 @@ TEST(ControllerManagerTest, LifeCycle)
EXPECT_NE(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1));
EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(0, 0));
device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services");
device = device_factory.CreateDevice(controller_manager, SCHS, LUN2, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device));
controller = controller_manager.FindController(ID);
EXPECT_TRUE(controller_manager.DeleteController(controller));
@ -46,6 +46,25 @@ TEST(ControllerManagerTest, LifeCycle)
EXPECT_EQ(nullptr, controller_manager.GetDeviceByIdAndLun(ID, LUN1));
}
TEST(ControllerManagerTest, AttachToScsiController)
{
const int ID = 4;
const int LUN1 = 3;
const int LUN2 = 0;
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
auto device1 = device_factory.CreateDevice(controller_manager, SCHS, LUN1, "");
EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device1)) << "LUN 0 is missing";
auto device2 = device_factory.CreateDevice(controller_manager, SCLP, LUN2, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2));
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1));
EXPECT_FALSE(controller_manager.AttachToScsiController(ID, device1));
}
TEST(ControllerManagerTest, ResetAllControllers)
{
const int ID = 2;
@ -54,12 +73,12 @@ TEST(ControllerManagerTest, ResetAllControllers)
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device));
auto controller = controller_manager.FindController(ID);
EXPECT_NE(nullptr, controller);
controller->SetStatus(status::BUSY);
controller->SetStatus(status::RESERVATION_CONFLICT);
controller_manager.ResetAllControllers();
EXPECT_EQ(status::GOOD, controller->GetStatus());
}

View File

@ -43,8 +43,6 @@ TEST(DeviceFactoryTest, GetSectorSizes)
DeviceFactory device_factory;
unordered_set<uint32_t> sector_sizes;
sector_sizes = device_factory.GetSectorSizes("SCHD");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCHD);
EXPECT_EQ(4, sector_sizes.size());
@ -53,8 +51,6 @@ TEST(DeviceFactoryTest, GetSectorSizes)
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCRM");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCRM);
EXPECT_EQ(4, sector_sizes.size());
@ -63,8 +59,6 @@ TEST(DeviceFactoryTest, GetSectorSizes)
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCMO");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCMO);
EXPECT_EQ(4, sector_sizes.size());
@ -73,8 +67,6 @@ TEST(DeviceFactoryTest, GetSectorSizes)
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCCD");
EXPECT_EQ(2, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCCD);
EXPECT_EQ(2, sector_sizes.size());
@ -125,7 +117,7 @@ TEST(DeviceFactoryTest, GetDefaultParams)
EXPECT_EQ(2, params.size());
params = device_factory.GetDefaultParams(SCLP);
EXPECT_EQ(2, params.size());
EXPECT_EQ(1, params.size());
}
TEST(DeviceFactoryTest, UnknownDeviceType)
@ -152,7 +144,7 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hda");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
EXPECT_EQ(SCHD, device->GetType());
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
@ -172,18 +164,18 @@ TEST(DeviceFactoryTest, SCHD_Device_Defaults)
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hds");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
EXPECT_EQ(SCHD, device->GetType());
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.hdi");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
EXPECT_EQ(SCHD, device->GetType());
device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.nhd");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHD", device->GetType());
EXPECT_EQ(SCHD, device->GetType());
}
void TestRemovableDrive(const string& type, const string& filename, const string& product)
void TestRemovableDrive(PbDeviceType type, const string& filename, const string& product)
{
MockBus bus;
DeviceFactory device_factory;
@ -213,12 +205,12 @@ void TestRemovableDrive(const string& type, const string& filename, const string
TEST(DeviceFactoryTest, SCRM_Device_Defaults)
{
TestRemovableDrive("SCRM", "test.hdr", "SCSI HD (REM.)");
TestRemovableDrive(SCRM, "test.hdr", "SCSI HD (REM.)");
}
TEST(DeviceFactoryTest, SCMO_Device_Defaults)
{
TestRemovableDrive("SCMO", "test.mos", "SCSI MO");
TestRemovableDrive(SCMO, "test.mos", "SCSI MO");
}
TEST(DeviceFactoryTest, SCCD_Device_Defaults)
@ -229,7 +221,7 @@ TEST(DeviceFactoryTest, SCCD_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "test.iso");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCCD", device->GetType());
EXPECT_EQ(SCCD, device->GetType());
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
@ -256,7 +248,7 @@ TEST(DeviceFactoryTest, SCBR_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "bridge");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCBR", device->GetType());
EXPECT_EQ(SCBR, device->GetType());
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
@ -283,7 +275,7 @@ TEST(DeviceFactoryTest, SCDP_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "daynaport");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCDP", device->GetType());
EXPECT_EQ(SCDP, device->GetType());
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
@ -309,7 +301,7 @@ TEST(DeviceFactoryTest, SCHS_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCHS", device->GetType());
EXPECT_EQ(SCHS, device->GetType());
EXPECT_FALSE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
@ -336,7 +328,7 @@ TEST(DeviceFactoryTest, SCLP_Device_Defaults)
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "printer");
EXPECT_NE(nullptr, device);
EXPECT_EQ("SCLP", device->GetType());
EXPECT_EQ(SCLP, device->GetType());
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());

View File

@ -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());

View File

@ -10,9 +10,11 @@
#include "mocks.h"
#include "scsi.h"
#include "devices/disk.h"
#include "devices/scsi_command_util.h"
#include "rascsi_exceptions.h"
using namespace scsi_defs;
using namespace scsi_command_util;
TEST(DiskTest, Dispatch)
{
@ -22,6 +24,9 @@ TEST(DiskTest, Dispatch)
controller.AddDevice(disk);
EXPECT_FALSE(disk->Dispatch(scsi_command::eCmdIcd));
disk->SetRemovable(true);
disk->MediumChanged();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception);
}
@ -38,7 +43,7 @@ TEST(DiskTest, Rezero)
disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRezero));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
@ -50,13 +55,13 @@ TEST(DiskTest, FormatUnit)
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdFormat), scsi_exception);
disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdFormat));
EXPECT_EQ(status::GOOD, controller.GetStatus());
@ -77,43 +82,57 @@ TEST(DiskTest, ReassignBlocks)
disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReassign));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(DiskTest, Seek)
TEST(DiskTest, Seek6)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(10);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_exception)
<< "SEEK(6) must fail for a medium with 0 blocks";
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception)
<< "SEEK(10) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek6), scsi_exception)
<< "SEEK(6) must fail because drive is not ready";
disk->SetReady(true);
// Block count
cmd[4] = 1;
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(DiskTest, Seek10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception)
<< "SEEK(10) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSeek10), scsi_exception)
<< "SEEK(10) must fail because drive is not ready";
disk->SetReady(true);
// Block count for SEEK(6)
cmd[4] = 1;
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek6));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[4] = 0;
// Block count for SEEK(10)
// Block count
cmd[5] = 1;
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSeek10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
@ -125,7 +144,7 @@ TEST(DiskTest, ReadCapacity)
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(16);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception)
<< "Neithed READ CAPACITY(16) nor READ LONG(16)";
@ -148,7 +167,7 @@ TEST(DiskTest, ReadCapacity)
cmd[1] = 0x00;
disk->SetBlockCount(0x12345678);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
@ -156,7 +175,7 @@ TEST(DiskTest, ReadCapacity)
EXPECT_EQ(0x77, controller.GetBuffer()[3]);
disk->SetBlockCount(0x1234567887654321);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity10));
EXPECT_EQ(0xff, controller.GetBuffer()[0]);
EXPECT_EQ(0xff, controller.GetBuffer()[1]);
@ -166,7 +185,7 @@ TEST(DiskTest, ReadCapacity)
disk->SetSectorSizeInBytes(1024);
// READ CAPACITY(16), not READ LONG(16)
cmd[1] = 0x10;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
EXPECT_EQ(0x12, controller.GetBuffer()[0]);
EXPECT_EQ(0x34, controller.GetBuffer()[1]);
@ -182,111 +201,282 @@ TEST(DiskTest, ReadCapacity)
EXPECT_EQ(0x00, controller.GetBuffer()[11]);
}
TEST(DiskTest, ReadWriteLong)
TEST(DiskTest, Read6)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(16);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead6), scsi_exception)
<< "READ(6) must fail for a medium with 0 blocks";
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10));
// Further testing requires filesystem access
}
TEST(DiskTest, Read10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead10), scsi_exception)
<< "READ(10) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRead10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10));
// Further testing requires filesystem access
}
TEST(DiskTest, Read16)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRead16), scsi_exception)
<< "READ(16) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRead16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// Further testing requires filesystem access
}
TEST(DiskTest, Write6)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite6), scsi_exception)
<< "WRIte(6) must fail for a medium with 0 blocks";
// Further testing requires filesystem access
}
TEST(DiskTest, Write10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite10), scsi_exception)
<< "WRITE(10) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// Further testing requires filesystem access
}
TEST(DiskTest, Write16)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWrite16), scsi_exception)
<< "WRITE(16) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// Further testing requires filesystem access
}
TEST(DiskTest, Verify10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdVerify10), scsi_exception)
<< "VERIFY(10) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// Further testing requires filesystem access
}
TEST(DiskTest, Verify16)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdVerify16), scsi_exception)
<< "VERIFY(16) must fail for a medium with 0 blocks";
disk->SetBlockCount(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWrite16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// Further testing requires filesystem access
}
TEST(DiskTest, ReadLong10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadLong10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[2] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_exception)
<< "READ LONG(10) must fail because the capacity is exceeded";
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception)
<< "WRITE LONG(10) must fail because the capacity is exceeded";
// READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception)
<< "READ LONG(16) must fail because the capacity is exceeded";
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception)
<< "WRITE LONG(16) must fail because the capacity is exceeded";
cmd[2] = 0;
cmd[7] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadLong10), scsi_exception)
<< "READ LONG(10) must fail because it currently only supports 0 bytes transfer length";
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception)
<< "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length";
cmd[7] = 0;
}
TEST(DiskTest, ReadLong16)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
// READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11;
EXPECT_CALL(controller, Status()).Times(1);
cmd[2] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception)
<< "READ LONG(16) must fail because the capacity is exceeded";
cmd[2] = 0;
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x00;
EXPECT_CALL(controller, Status()).Times(1);
cmd[13] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception)
<< "READ LONG(16) must fail because it currently only supports 0 bytes transfer length";
}
TEST(DiskTest, WriteLong10)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[2] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception)
<< "WRITE LONG(10) must fail because the capacity is exceeded";
cmd[2] = 0;
cmd[7] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong10), scsi_exception)
<< "WRITE LONG(10) must fail because it currently only supports 0 bytes transfer length";
}
TEST(DiskTest, WriteLong16)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
cmd[2] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception)
<< "WRITE LONG(16) must fail because the capacity is exceeded";
cmd[2] = 0;
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdWriteLong16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[13] = 1;
// READ LONG(16), not READ CAPACITY(16)
cmd[1] = 0x11;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdReadCapacity16_ReadLong16), scsi_exception)
<< "READ LONG(16) must fail because it currently only supports 0 bytes transfer length";
cmd[1] = 0x00;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdWriteLong16), scsi_exception)
<< "WRITE LONG(16) must fail because it currently only supports 0 bytes transfer length";
}
TEST(DiskTest, Reserve)
TEST(DiskTest, StartStopUnit)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
disk->SetRemovable(true);
controller.AddDevice(disk);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReserve6));
vector<int>& cmd = controller.GetCmd();
// Stop/Unload
disk->SetReady(true);
EXPECT_CALL(controller, Status());
EXPECT_CALL(*disk, FlushCache);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
EXPECT_TRUE(disk->IsStopped());
TEST(DiskTest, Release)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRelease6));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(DiskTest, SendDiagnostic)
{
MockAbstractController controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSendDiag));
// Stop/Load
cmd[4] = 0x02;
disk->SetReady(true);
disk->SetLocked(false);
EXPECT_CALL(controller, Status());
EXPECT_CALL(*disk, FlushCache);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x10;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because PF bit is not supported";
cmd[1] = 0;
disk->SetReady(false);
EXPECT_CALL(*disk, FlushCache).Times(0);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception);
cmd[3] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because parameter list is not supported";
cmd[3] = 0;
cmd[4] = 1;
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because parameter list is not supported";
disk->SetReady(true);
disk->SetLocked(true);
EXPECT_CALL(*disk, FlushCache).Times(0);
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdStartStop), scsi_exception);
// Start/Unload
cmd[4] = 0x01;
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_FALSE(disk->IsStopped());
// Start/Load
cmd[4] = 0x03;
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(DiskTest, PreventAllowMediumRemoval)
@ -296,25 +486,212 @@ TEST(DiskTest, PreventAllowMediumRemoval)
controller.AddDevice(disk);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(disk->Dispatch(scsi_command::eCmdRemoval), scsi_exception)
<< "REMOVAL must fail because drive is not ready";
disk->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_FALSE(disk->IsLocked());
cmd[4] = 1;
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdRemoval));
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_TRUE(disk->IsLocked());
}
TEST(DiskTest, Eject)
{
MockDisk disk;
disk.SetReady(false);
disk.SetRemovable(false);
disk.SetLocked(false);
EXPECT_CALL(disk, FlushCache).Times(0);
EXPECT_FALSE(disk.Eject(false));
disk.SetRemovable(true);
EXPECT_CALL(disk, FlushCache).Times(0);
EXPECT_FALSE(disk.Eject(false));
disk.SetReady(true);
disk.SetLocked(true);
EXPECT_CALL(disk, FlushCache).Times(0);
EXPECT_FALSE(disk.Eject(false));
disk.SetReady(true);
disk.SetLocked(false);
EXPECT_CALL(disk, FlushCache);
EXPECT_TRUE(disk.Eject(false));
disk.SetReady(true);
EXPECT_CALL(disk, FlushCache);
EXPECT_TRUE(disk.Eject(true));
}
void DiskTest_ValidateFormatPage(AbstractController& controller, int offset)
{
const auto& buf = controller.GetBuffer();
EXPECT_EQ(0x08, buf[offset + 3]) << "Wrong number of trackes in one zone";
EXPECT_EQ(25, GetInt16(buf, offset + 10)) << "Wrong number of sectors per track";
EXPECT_EQ(1024, GetInt16(buf, offset + 12)) << "Wrong number of bytes per sector";
EXPECT_EQ(1, GetInt16(buf, offset + 14)) << "Wrong interleave";
EXPECT_EQ(11, GetInt16(buf, offset + 16)) << "Wrong track skew factor";
EXPECT_EQ(20, GetInt16(buf, offset + 18)) << "Wrong cylinder skew factor";
EXPECT_FALSE(buf[offset + 20] & 0x20) << "Wrong removable flag";
EXPECT_TRUE(buf[offset + 20] & 0x40) << "Wrong hard-sectored flag";
}
void DiskTest_ValidateDrivePage(AbstractController& controller, int offset)
{
const auto& buf = controller.GetBuffer();
EXPECT_EQ(0x17, buf[offset + 2]);
EXPECT_EQ(0x4d3b, GetInt16(buf, offset + 3));
EXPECT_EQ(8, buf[offset + 5]) << "Wrong number of heads";
EXPECT_EQ(7200, GetInt16(buf, offset + 20)) << "Wrong medium rotation rate";
}
void DiskTest_ValidateCachePage(AbstractController& controller, int offset)
{
const auto& buf = controller.GetBuffer();
EXPECT_EQ(0xffff, GetInt16(buf, offset + 4)) << "Wrong pre-fetch transfer length";
EXPECT_EQ(0xffff, GetInt16(buf, offset + 8)) << "Wrong maximum pre-fetch";
EXPECT_EQ(0xffff, GetInt16(buf, offset + 10)) << "Wrong maximum pre-fetch ceiling";
}
TEST(DiskTest, ModeSense6)
{
NiceMock<MockAbstractController> controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
// Drive must be ready on order to return all data
disk->SetReady(true);
cmd[2] = 0x3f;
// ALLOCATION LENGTH
cmd[4] = 255;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
EXPECT_EQ(0x08, controller.GetBuffer()[3]) << "Wrong block descriptor length";
// No block descriptor
cmd[1] = 0x08;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
EXPECT_EQ(0x00, controller.GetBuffer()[2]) << "Wrong device-specific parameter";
disk->SetReadOnly(false);
disk->SetProtectable(true);
disk->SetProtected(true);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
const auto& buf = controller.GetBuffer();
EXPECT_EQ(0x80, buf[2]) << "Wrong device-specific parameter";
// Return block descriptor
cmd[1] = 0x00;
// Format page
cmd[2] = 3;
disk->SetSectorSizeInBytes(1024);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
DiskTest_ValidateFormatPage(controller, 12);
// Rigid disk drive page
cmd[2] = 4;
disk->SetBlockCount(0x12345678);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
DiskTest_ValidateDrivePage(controller, 12);
// Cache page
cmd[2] = 8;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense6));
DiskTest_ValidateCachePage(controller, 12);
}
TEST(DiskTest, ModeSense10)
{
NiceMock<MockAbstractController> controller(0);
auto disk = make_shared<MockDisk>();
controller.AddDevice(disk);
vector<int>& cmd = controller.GetCmd();
// Drive must be ready on order to return all data
disk->SetReady(true);
cmd[2] = 0x3f;
// ALLOCATION LENGTH
cmd[8] = 255;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
EXPECT_EQ(0x08, controller.GetBuffer()[7]) << "Wrong block descriptor length";
// No block descriptor
cmd[1] = 0x08;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
auto& buf = controller.GetBuffer();
EXPECT_EQ(0x00, controller.GetBuffer()[3]) << "Wrong device-specific parameter";
disk->SetReadOnly(false);
disk->SetProtectable(true);
disk->SetProtected(true);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
buf = controller.GetBuffer();
EXPECT_EQ(0x80, buf[3]) << "Wrong device-specific parameter";
// Return short block descriptor
cmd[1] = 0x00;
disk->SetBlockCount(0x1234);
disk->SetSectorSizeInBytes(1024);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
buf = controller.GetBuffer();
EXPECT_EQ(0x00, buf[4]) << "Wrong LONGLBA field";
EXPECT_EQ(0x08, buf[7]) << "Wrong block descriptor length";
EXPECT_EQ(0x00, GetInt16(buf, 8));
EXPECT_EQ(0x1234, GetInt16(buf, 10));
EXPECT_EQ(0x00, GetInt16(buf, 12));
EXPECT_EQ(1024, GetInt16(buf, 14));
// Return long block descriptor
cmd[1] = 0x10;
disk->SetBlockCount((uint64_t)0xffffffff + 1);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
buf = controller.GetBuffer();
EXPECT_EQ(0x01, buf[4]) << "Wrong LONGLBA field";
EXPECT_EQ(0x10, buf[7]) << "Wrong block descriptor length";
EXPECT_EQ(0x00, GetInt16(buf, 8));
EXPECT_EQ(0x01, GetInt16(buf, 10));
EXPECT_EQ(0x00, GetInt16(buf, 12));
EXPECT_EQ(0x00, GetInt16(buf, 14));
EXPECT_EQ(0x00, GetInt16(buf, 20));
EXPECT_EQ(1024, GetInt16(buf, 22));
cmd[1] = 0x00;
// Format page
cmd[2] = 3;
disk->SetSectorSizeInBytes(1024);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
DiskTest_ValidateFormatPage(controller, 16);
// Rigid disk drive page
cmd[2] = 4;
disk->SetBlockCount(0x12345678);
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
DiskTest_ValidateDrivePage(controller, 16);
// Cache page
cmd[2] = 8;
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdModeSense10));
DiskTest_ValidateCachePage(controller, 16);
}
TEST(DiskTest, SynchronizeCache)
{
MockAbstractController controller(0);
@ -322,13 +699,13 @@ TEST(DiskTest, SynchronizeCache)
controller.AddDevice(disk);
EXPECT_CALL(*disk, FlushCache()).Times(1);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(*disk, FlushCache());
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_CALL(*disk, FlushCache()).Times(1);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(*disk, FlushCache());
EXPECT_CALL(controller, Status());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdSynchronizeCache16));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
@ -340,9 +717,7 @@ TEST(DiskTest, ReadDefectData)
controller.AddDevice(disk);
controller.InitCmd(10);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(disk->Dispatch(scsi_command::eCmdReadDefectData10));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
@ -410,46 +785,3 @@ TEST(DiskTest, BlockCount)
disk.SetBlockCount(0x1234567887654321);
EXPECT_EQ(0x1234567887654321, disk.GetBlockCount());
}
TEST(DiskTest, GetIdsForReservedFile)
{
const int ID = 1;
const int LUN = 2;
Filepath path;
path.SetPath("path");
MockDisk disk;
disk.SetPath(path);
Filepath result;
disk.GetPath(result);
EXPECT_STREQ("path", result.GetPath());
int id;
int lun;
EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun));
disk.ReserveFile(path, ID, LUN);
EXPECT_TRUE(Disk::GetIdsForReservedFile(path, id, lun));
EXPECT_EQ(ID, id);
EXPECT_EQ(LUN, lun);
disk.UnreserveFile();
EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun));
}
TEST(DiskTest, UnreserveAll)
{
const int ID = 2;
const int LUN = 31;
Filepath path;
path.SetPath("path");
MockDisk disk;
disk.ReserveFile(path, ID, LUN);
Disk::UnreserveAll();
int id;
int lun;
EXPECT_FALSE(Disk::GetIdsForReservedFile(path, id, lun));
}

View 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));
}
}
}

View File

@ -14,20 +14,24 @@
using namespace std;
TEST(HostServicesTest, Dispatch)
{
TestDispatch(SCHS);
}
TEST(HostServicesTest, TestUnitReady)
{
NiceMock<MockAbstractController> controller(0);
auto services = CreateDevice(SCHS, controller);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdTestUnitReady)) << "TEST UNIT READY must never fail";
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(HostServicesTest, Inquiry)
{
TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, scsi_level::SCSI_2,
"RaSCSI Host Services ", 0x1f, false);
TestInquiry(SCHS, device_type::PROCESSOR, scsi_level::SPC_3, "RaSCSI Host Services ", 0x1f, false);
}
TEST(HostServicesTest, StartStopUnit)
@ -35,25 +39,25 @@ TEST(HostServicesTest, StartStopUnit)
NiceMock<MockAbstractController> controller(0);
auto services = CreateDevice(SCHS, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// STOP
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI)).Times(1);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI));
EXPECT_CALL(controller, Status());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// LOAD
cmd[4] = 0x02;
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI)).Times(1);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI));
EXPECT_CALL(controller, Status());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
// UNLOAD
cmd[4] = 0x03;
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI)).Times(1);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI));
EXPECT_CALL(controller, Status());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdStartStop));
EXPECT_EQ(status::GOOD, controller.GetStatus());
@ -67,7 +71,7 @@ TEST(HostServicesTest, ModeSense6)
NiceMock<MockAbstractController> controller(0);
auto services = CreateDevice(SCHS, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(services->Dispatch(scsi_command::eCmdModeSense6), scsi_exception)
<< "Unsupported mode page was returned";
@ -79,7 +83,7 @@ TEST(HostServicesTest, ModeSense6)
cmd[1] = 0x08;
// ALLOCATION LENGTH
cmd[4] = 255;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense6));
vector<BYTE>& buffer = controller.GetBuffer();
// Major version 1
@ -93,7 +97,7 @@ TEST(HostServicesTest, ModeSense6)
// ALLOCATION LENGTH
cmd[4] = 2;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense6));
buffer = controller.GetBuffer();
EXPECT_EQ(0x02, buffer[0]);
@ -104,7 +108,7 @@ TEST(HostServicesTest, ModeSense10)
NiceMock<MockAbstractController> controller(0);
auto services = CreateDevice(SCHS, controller);
vector<int>& cmd = controller.InitCmd(10);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(services->Dispatch(scsi_command::eCmdModeSense10), scsi_exception)
<< "Unsupported mode page was returned";
@ -116,7 +120,7 @@ TEST(HostServicesTest, ModeSense10)
cmd[1] = 0x08;
// ALLOCATION LENGTH
cmd[8] = 255;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense10));
vector<BYTE>& buffer = controller.GetBuffer();
// Major version 1
@ -130,7 +134,7 @@ TEST(HostServicesTest, ModeSense10)
// ALLOCATION LENGTH
cmd[8] = 2;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(services->Dispatch(scsi_command::eCmdModeSense10));
buffer = controller.GetBuffer();
EXPECT_EQ(0x02, buffer[1]);
@ -141,9 +145,9 @@ TEST(HostServicesTest, SetUpModePages)
MockBus bus;
ControllerManager controller_manager(bus);
MockHostServices services(0, controller_manager);
map<int, vector<byte>> mode_pages;
map<int, vector<byte>> pages;
services.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(1, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(10, mode_pages[32].size());
services.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(10, pages[32].size());
}

View 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"));
}

View File

@ -12,14 +12,18 @@
#include <gmock/gmock.h>
#include "test_shared.h"
#include "bus.h"
#include "controllers/scsi_controller.h"
#include "devices/primary_device.h"
#include "devices/storage_device.h"
#include "devices/disk.h"
#include "devices/scsihd.h"
#include "devices/scsihd_nec.h"
#include "devices/scsicd.h"
#include "devices/scsimo.h"
#include "devices/host_services.h"
#include "rascsi/command_context.h"
#include "rascsi/rascsi_executor.h"
using namespace testing;
@ -87,17 +91,20 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f
friend void TestInquiry(rascsi_interface::PbDeviceType, scsi_defs::device_type, scsi_defs::scsi_level,
scsi_defs::scsi_level, const std::string&, int, bool);
FRIEND_TEST(AbstractControllerTest, AllocateCmd);
FRIEND_TEST(AbstractControllerTest, Reset);
FRIEND_TEST(AbstractControllerTest, ProcessPhase);
FRIEND_TEST(AbstractControllerTest, DeviceLunLifeCycle);
FRIEND_TEST(AbstractControllerTest, ExtractInitiatorId);
FRIEND_TEST(AbstractControllerTest, GetOpcode);
FRIEND_TEST(AbstractControllerTest, GetLun);
FRIEND_TEST(AbstractControllerTest, Length);
FRIEND_TEST(AbstractControllerTest, SetLength);
FRIEND_TEST(AbstractControllerTest, UpdateOffsetAndLength);
FRIEND_TEST(AbstractControllerTest, Offset);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, SendDiagnostic);
FRIEND_TEST(PrimaryDeviceTest, ReportLuns);
FRIEND_TEST(PrimaryDeviceTest, UnknownCommand);
FRIEND_TEST(ModePageDeviceTest, ModeSense6);
@ -108,12 +115,21 @@ class MockAbstractController : public AbstractController //NOSONAR Having many f
FRIEND_TEST(DiskTest, Rezero);
FRIEND_TEST(DiskTest, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks);
FRIEND_TEST(DiskTest, Seek);
FRIEND_TEST(DiskTest, Seek6);
FRIEND_TEST(DiskTest, Seek10);
FRIEND_TEST(DiskTest, Read6);
FRIEND_TEST(DiskTest, Read10);
FRIEND_TEST(DiskTest, Read16);
FRIEND_TEST(DiskTest, Write6);
FRIEND_TEST(DiskTest, Write10);
FRIEND_TEST(DiskTest, Write16);
FRIEND_TEST(DiskTest, Verify10);
FRIEND_TEST(DiskTest, Verify16);
FRIEND_TEST(DiskTest, ReadCapacity);
FRIEND_TEST(DiskTest, ReadWriteLong);
FRIEND_TEST(DiskTest, Reserve);
FRIEND_TEST(DiskTest, Release);
FRIEND_TEST(DiskTest, SendDiagnostic);
FRIEND_TEST(DiskTest, ReadLong10);
FRIEND_TEST(DiskTest, ReadLong16);
FRIEND_TEST(DiskTest, WriteLong10);
FRIEND_TEST(DiskTest, WriteLong16);
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
FRIEND_TEST(DiskTest, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData);
@ -132,7 +148,6 @@ public:
MOCK_METHOD(void, Command, (), ());
MOCK_METHOD(void, MsgIn, (), ());
MOCK_METHOD(void, MsgOut, (), ());
MOCK_METHOD(void, SetByteTransfer, (bool), (override));
MOCK_METHOD(void, ScheduleShutdown, (rascsi_shutdown_mode), (override));
explicit MockAbstractController(int target_id) : AbstractController(bus, target_id, 32) {
@ -140,13 +155,23 @@ public:
}
~MockAbstractController() override = default;
vector<int>& InitCmd(int size) { return AbstractController::InitCmd(size); } //NOSONAR Hides function on purpose
// Permit access to all tests without the need for numerous FRIEND_TEST
vector<int>& GetCmd() { return AbstractController::GetCmd(); } //NOSONAR Hides function on purpose
MockBus bus;
};
class MockScsiController : public ScsiController
{
FRIEND_TEST(ScsiControllerTest, Process);
FRIEND_TEST(ScsiControllerTest, BusFree);
FRIEND_TEST(ScsiControllerTest, Selection);
FRIEND_TEST(ScsiControllerTest, Command);
FRIEND_TEST(ScsiControllerTest, MsgIn);
FRIEND_TEST(ScsiControllerTest, MsgOut);
FRIEND_TEST(ScsiControllerTest, DataIn);
FRIEND_TEST(ScsiControllerTest, DataOut);
FRIEND_TEST(ScsiControllerTest, Error);
FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
@ -154,18 +179,19 @@ public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(void, Status, (), ());
MOCK_METHOD(void, DataIn, (), ());
MOCK_METHOD(void, DataOut, (), ());
MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override));
MOCK_METHOD(void, Execute, (), ());
using ScsiController::ScsiController;
explicit MockScsiController(int target_id) : ScsiController(bus, target_id) {}
MockScsiController() : ScsiController(bus, 0) {}
~MockScsiController() override = default;
MockBus bus;
NiceMock<MockBus> bus;
};
class MockDevice : public Device
{
FRIEND_TEST(DeviceTest, Properties);
FRIEND_TEST(DeviceTest, Params);
FRIEND_TEST(DeviceTest, StatusCode);
FRIEND_TEST(DeviceTest, Reset);
@ -177,7 +203,8 @@ public:
MOCK_METHOD(int, GetId, (), (const));
explicit MockDevice(int lun) : Device("test", lun) {}
explicit MockDevice(int lun) : Device(UNDEFINED, lun) {}
explicit MockDevice(PbDeviceType type) : Device(type, 0) {}
~MockDevice() override = default;
};
@ -187,21 +214,23 @@ class MockPrimaryDevice : public PrimaryDevice
FRIEND_TEST(PrimaryDeviceTest, TestUnitReady);
FRIEND_TEST(PrimaryDeviceTest, RequestSense);
FRIEND_TEST(PrimaryDeviceTest, Inquiry);
FRIEND_TEST(PrimaryDeviceTest, GetSetSendDelay);
FRIEND_TEST(ScsiControllerTest, RequestSense);
FRIEND_TEST(RascsiExecutorTest, ValidationOperationAgainstDevice);
FRIEND_TEST(RascsiExecutorTest, ValidateOperationAgainstDevice);
public:
MOCK_METHOD(void, Reset, (), ());
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
explicit MockPrimaryDevice(int lun) : PrimaryDevice("test", lun) {}
explicit MockPrimaryDevice(int lun) : PrimaryDevice(UNDEFINED, lun) {}
~MockPrimaryDevice() override = default;
};
class MockModePageDevice : public ModePageDevice
{
FRIEND_TEST(ModePageDeviceTest, SupportsSaveParameters);
FRIEND_TEST(ModePageDeviceTest, AddModePages);
FRIEND_TEST(ModePageDeviceTest, AddVendorPage);
public:
@ -209,49 +238,105 @@ public:
MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&), (const override));
MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&), (const override));
explicit MockModePageDevice() : ModePageDevice("test", 0) {}
MockModePageDevice() : ModePageDevice(UNDEFINED, 0) {}
~MockModePageDevice() override = default;
void SetUpModePages(map<int, vector<byte>>& pages, int page, bool) const override {
// Return dummy data for other pages than page 0
if (page) {
vector<byte> buf(255);
vector<byte> buf(32);
pages[page] = buf;
}
}
};
class MockPage0ModePageDevice : public MockModePageDevice
{
FRIEND_TEST(ModePageDeviceTest, Page0);
public:
using MockModePageDevice::MockModePageDevice;
void SetUpModePages(map<int, vector<byte>>& pages, int, bool) const override {
// Return dummy data for pages 0 and 1
vector<byte> buf(32);
pages[0] = buf;
pages[1] = buf;
}
};
class MockStorageDevice : public StorageDevice
{
FRIEND_TEST(StorageDeviceTest, ValidateFile);
FRIEND_TEST(StorageDeviceTest, MediumChanged);
FRIEND_TEST(StorageDeviceTest, GetIdsForReservedFile);
FRIEND_TEST(StorageDeviceTest, FileExists);
FRIEND_TEST(StorageDeviceTest, GetFileSize);
public:
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MOCK_METHOD(void, Open, (), (override));
MOCK_METHOD(int, ModeSense6, (const vector<int>&, vector<BYTE>&), (const override));
MOCK_METHOD(int, ModeSense10, (const vector<int>&, vector<BYTE>&), (const override));
MOCK_METHOD(void, SetUpModePages, ((map<int, vector<byte>>&), int, bool), (const override));
MockStorageDevice() : StorageDevice(UNDEFINED, 0) {}
~MockStorageDevice() override = default;
};
class MockDisk : public Disk
{
FRIEND_TEST(DiskTest, Dispatch);
FRIEND_TEST(DiskTest, Rezero);
FRIEND_TEST(DiskTest, FormatUnit);
FRIEND_TEST(DiskTest, ReassignBlocks);
FRIEND_TEST(DiskTest, Seek);
FRIEND_TEST(DiskTest, Seek6);
FRIEND_TEST(DiskTest, Seek10);
FRIEND_TEST(DiskTest, Read6);
FRIEND_TEST(DiskTest, Read10);
FRIEND_TEST(DiskTest, Read16);
FRIEND_TEST(DiskTest, Write6);
FRIEND_TEST(DiskTest, Write10);
FRIEND_TEST(DiskTest, Write16);
FRIEND_TEST(DiskTest, Verify10);
FRIEND_TEST(DiskTest, Verify16);
FRIEND_TEST(DiskTest, ReadCapacity);
FRIEND_TEST(DiskTest, ReadWriteLong);
FRIEND_TEST(DiskTest, ReadLong10);
FRIEND_TEST(DiskTest, ReadLong16);
FRIEND_TEST(DiskTest, WriteLong10);
FRIEND_TEST(DiskTest, WriteLong16);
FRIEND_TEST(DiskTest, ReserveRelease);
FRIEND_TEST(DiskTest, SendDiagnostic);
FRIEND_TEST(DiskTest, StartStopUnit);
FRIEND_TEST(DiskTest, PreventAllowMediumRemoval);
FRIEND_TEST(DiskTest, Eject);
FRIEND_TEST(DiskTest, ModeSense6);
FRIEND_TEST(DiskTest, ModeSense10);
FRIEND_TEST(DiskTest, SynchronizeCache);
FRIEND_TEST(DiskTest, ReadDefectData);
FRIEND_TEST(DiskTest, SectorSize);
FRIEND_TEST(DiskTest, BlockCount);
FRIEND_TEST(DiskTest, GetIdsForReservedFile);
public:
MOCK_METHOD(vector<byte>, InquiryInternal, (), (const));
MOCK_METHOD(void, FlushCache, (), (override));
MOCK_METHOD(void, Open, (), (override));
MockDisk() : Disk("test", 0) {}
MockDisk() : Disk(SCHD, 0) {}
~MockDisk() override = default;
};
class MockSCSIHD : public SCSIHD
class MockSCSIHD : public SCSIHD //NOSONAR Ignore inheritance hierarchy depth in unit tests
{
FRIEND_TEST(DiskTest, ConfiguredSectorSize);
FRIEND_TEST(ScsiHdTest, SupportsSaveParameters);
FRIEND_TEST(ScsiHdTest, FinalizeSetup);
FRIEND_TEST(ScsiHdTest, SetUpModePages);
FRIEND_TEST(RascsiExecutorTest, SetSectorSize);
FRIEND_TEST(ScsiHdTest, ModeSelect);
using SCSIHD::SCSIHD;
};
@ -259,20 +344,27 @@ class MockSCSIHD : public SCSIHD
class MockSCSIHD_NEC : public SCSIHD_NEC //NOSONAR Ignore inheritance hierarchy depth in unit tests
{
FRIEND_TEST(ScsiHdNecTest, SetUpModePages);
FRIEND_TEST(ScsiHdNecTest, TestAddFormatPage);
FRIEND_TEST(ScsiHdNecTest, TestAddDrivePage);
FRIEND_TEST(RascsiExecutorTest, ProcessDeviceCmd);
using SCSIHD_NEC::SCSIHD_NEC;
};
class MockSCSICD : public SCSICD
class MockSCSICD : public SCSICD //NOSONAR Ignore inheritance hierarchy depth in unit tests
{
FRIEND_TEST(ScsiCdTest, SetUpModePages);
FRIEND_TEST(ScsiCdTest, ReadToc);
using SCSICD::SCSICD;
};
class MockSCSIMO : public SCSIMO
class MockSCSIMO : public SCSIMO //NOSONAR Ignore inheritance hierarchy depth in unit tests
{
FRIEND_TEST(ScsiMoTest, SupportsSaveParameters);
FRIEND_TEST(ScsiMoTest, SetUpModePages);
FRIEND_TEST(ScsiMoTest, TestAddVendorPage);
FRIEND_TEST(ScsiMoTest, ModeSelect);
using SCSIMO::SCSIMO;
};
@ -293,3 +385,13 @@ public:
}
~MockCommandContext() = default;
};
class MockRascsiExecutor : public RascsiExecutor
{
public:
MOCK_METHOD(bool, Start, (shared_ptr<PrimaryDevice>, bool), (const));
MOCK_METHOD(bool, Stop, (shared_ptr<PrimaryDevice>, bool), (const));
using RascsiExecutor::RascsiExecutor;
};

View File

@ -13,21 +13,71 @@
using namespace std;
TEST(ModePageDeviceTest, SupportsSaveParameters)
{
MockModePageDevice device;
EXPECT_FALSE(device.SupportsSaveParameters()) << "Wrong default value";
device.SupportsSaveParameters(true);
EXPECT_TRUE(device.SupportsSaveParameters());
device.SupportsSaveParameters(false);
EXPECT_FALSE(device.SupportsSaveParameters());
}
TEST(ModePageDeviceTest, AddModePages)
{
vector<int> cdb(6);
vector<BYTE> buf(512);
MockModePageDevice device;
cdb[2] = 0x3f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, -1)) << "Negative maximum length must be rejected";
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0)) << "Allocation length 0 must be rejected";
EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1)) << "Allocation length 1 must be rejected";
// Page 0
cdb[2] = 0x00;
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12), scsi_exception)
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, 255), scsi_exception)
<< "Data were returned for non-existing mode page 0";
// All pages, non changeable
cdb[2] = 0x3f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255));
EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255));
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, -1), scsi_exception) << "Maximum size was ignored";
// All pages, changeable
cdb[2]= 0x7f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255));
EXPECT_EQ(3, device.AddModePages(cdb, buf, 0, 3, 255));
EXPECT_THROW(device.AddModePages(cdb, buf, 0, 12, -1), scsi_exception) << "Maximum size was ignored";
}
TEST(ModePageDeviceTest, Page0)
{
vector<int> cdb(6);
vector<BYTE> buf(512);
MockPage0ModePageDevice device;
cdb[2] = 0x3f;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 0, 0, 255));
EXPECT_EQ(1, device.AddModePages(cdb, buf, 0, 1, 255));
}
TEST(ModePageDeviceTest, AddVendorPage)
{
map<int, vector<byte>> pages;
MockModePageDevice device;
device.AddVendorPage(pages, 0x3f, false);
EXPECT_TRUE(pages.empty()) << "There must not be any default vendor page";
device.AddVendorPage(pages, 0x3f, true);
EXPECT_TRUE(pages.empty()) << "There must not be any default vendor page";
}
TEST(ModePageDeviceTest, Dispatch)
{
MockAbstractController controller(0);
auto device = make_shared<NiceMock<MockModePageDevice>>();
controller.AddDevice(device);
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
}
TEST(ModePageDeviceTest, ModeSense6)
@ -37,9 +87,7 @@ TEST(ModePageDeviceTest, ModeSense6)
controller.AddDevice(device);
controller.InitCmd(6);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense6));
}
@ -50,9 +98,7 @@ TEST(ModePageDeviceTest, ModeSense10)
controller.AddDevice(device);
controller.InitCmd(10);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSense10));
}
@ -62,7 +108,10 @@ TEST(ModePageDeviceTest, ModeSelect)
vector<int> cmd;
vector<BYTE> buf;
EXPECT_THROW(device.ModeSelect(cmd, buf, 0), scsi_exception) << "Unexpected MODE SELECT default implementation";
EXPECT_THROW(device.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 0), scsi_exception)
<< "Unexpected MODE SELECT(6) default implementation";
EXPECT_THROW(device.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 0), scsi_exception)
<< "Unexpected MODE SELECT(10) default implementation";
}
TEST(ModePageDeviceTest, ModeSelect6)
@ -72,9 +121,9 @@ TEST(ModePageDeviceTest, ModeSelect6)
controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSelect6));
cmd[1] = 0x01;
@ -89,9 +138,9 @@ TEST(ModePageDeviceTest, ModeSelect10)
controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(10);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdModeSelect10));
cmd[1] = 0x01;

View File

@ -35,16 +35,101 @@ TEST(PrimaryDeviceTest, PhaseChange)
controller.AddDevice(device);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
device->EnterStatusPhase();
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
device->EnterDataInPhase();
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
device->EnterDataOutPhase();
}
TEST(PrimaryDeviceTest, Reset)
{
NiceMock<MockAbstractController> controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6));
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for initiator ID 1";
device->Reset();
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved anymore for initiator ID 1";
}
TEST(PrimaryDeviceTest, CheckReservation)
{
NiceMock<MockAbstractController> controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved for initiator ID 0";
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6));
EXPECT_TRUE(device->CheckReservation(0, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved for initiator ID 0";
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for initiator ID 1";
EXPECT_FALSE(device->CheckReservation(-1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for unknown initiator";
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdInquiry, false))
<< "Device must not be reserved for INQUIRY";
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRequestSense, false))
<< "Device must not be reserved for REQUEST SENSE";
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRelease6, false))
<< "Device must not be reserved for RELEASE (6)";
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdRemoval, false))
<< "Device must not be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit not set";
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdRemoval, true))
<< "Device must be reserved for PREVENT ALLOW MEDIUM REMOVAL with prevent bit set";
}
TEST(PrimaryDeviceTest, ReserveReleaseUnit)
{
NiceMock<MockAbstractController> controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6));
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for initiator ID 1";
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRelease6));
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved anymore for initiator ID 1";
ON_CALL(controller, GetInitiatorId).WillByDefault(Return(-1));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6));
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for unknown initiator";
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRelease6));
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved anymore for unknown initiator";
}
TEST(PrimaryDeviceTest, DiscardReservation)
{
NiceMock<MockAbstractController> controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdReserve6));
EXPECT_FALSE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must be reserved for initiator ID 1";
device->DiscardReservation();
EXPECT_TRUE(device->CheckReservation(1, scsi_command::eCmdTestUnitReady, false))
<< "Device must not be reserved anymore for initiator ID 1";
}
TEST(PrimaryDeviceTest, TestUnitReady)
{
MockAbstractController controller(0);
@ -77,70 +162,71 @@ TEST(PrimaryDeviceTest, TestUnitReady)
EXPECT_THROW(device->Dispatch(scsi_command::eCmdTestUnitReady), scsi_exception);
device->SetReady(true);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdTestUnitReady));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(PrimaryDeviceTest, Inquiry)
{
NiceMock<MockAbstractController> controller(0);
auto controller = make_shared<NiceMock<MockAbstractController>>(0);
auto device = make_shared<MockPrimaryDevice>(0);
device->SetController(&controller);
controller->AddDevice(device);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller->GetCmd();
// ALLOCATION LENGTH
cmd[4] = 255;
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device->HandleInquiry(device_type::PROCESSOR, scsi_level::SPC_3, false);
});
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(*device, InquiryInternal());
EXPECT_CALL(*controller, DataIn());
ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(1));
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(0x7F, controller.GetBuffer()[0]) << "Invalid LUN was not reported";
EXPECT_EQ(0x7f, controller->GetBuffer()[0]) << "Invalid LUN was not reported";
ON_CALL(*controller, GetEffectiveLun()).WillByDefault(Return(0));
EXPECT_TRUE(controller.AddDevice(device));
EXPECT_FALSE(controller.AddDevice(device)) << "Duplicate LUN was not rejected";
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_FALSE(controller->AddDevice(make_shared<MockPrimaryDevice>(0))) << "Duplicate LUN was not rejected";
EXPECT_CALL(*device, InquiryInternal());
EXPECT_CALL(*controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(device_type::PROCESSOR, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x00, controller.GetBuffer()[1]) << "Device was not reported as non-removable";
EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1f, controller.GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(device_type::PROCESSOR, (device_type)controller->GetBuffer()[0]);
EXPECT_EQ(0x00, controller->GetBuffer()[1]) << "Device was not reported as non-removable";
EXPECT_EQ(scsi_level::SPC_3, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_2, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size";
ON_CALL(*device, InquiryInternal()).WillByDefault([&device]() {
return device->HandleInquiry(device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, true);
});
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(*device, InquiryInternal());
EXPECT_CALL(*controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller.GetBuffer()[0]);
EXPECT_EQ(0x80, controller.GetBuffer()[1]) << "Device was not reported as removable";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller.GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(device_type::DIRECT_ACCESS, (device_type)controller->GetBuffer()[0]);
EXPECT_EQ(0x80, controller->GetBuffer()[1]) << "Device was not reported as removable";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[2]) << "Wrong SCSI level";
EXPECT_EQ(scsi_level::SCSI_1_CCS, (scsi_level)controller->GetBuffer()[3]) << "Wrong response level";
EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size";
cmd[1] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_CALL(*controller, DataIn()).Times(0);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "EVPD bit is not supported";
cmd[2] = 0x01;
EXPECT_CALL(controller, DataIn()).Times(0);
EXPECT_CALL(*controller, DataIn()).Times(0);
EXPECT_THROW(device->Dispatch(scsi_command::eCmdInquiry), scsi_exception) << "PAGE CODE field is not supported";
cmd[1] = 0x00;
cmd[2] = 0x00;
// ALLOCATION LENGTH
cmd[4] = 1;
EXPECT_CALL(*device, InquiryInternal()).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(*device, InquiryInternal());
EXPECT_CALL(*controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdInquiry));
EXPECT_EQ(0x1F, controller.GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(1, controller.GetLength()) << "Wrong ALLOCATION LENGTH handling";
EXPECT_EQ(0x1f, controller->GetBuffer()[4]) << "Wrong additional data size";
EXPECT_EQ(1, controller->GetLength()) << "Wrong ALLOCATION LENGTH handling";
}
TEST(PrimaryDeviceTest, RequestSense)
@ -150,7 +236,7 @@ TEST(PrimaryDeviceTest, RequestSense)
controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// ALLOCATION LENGTH
cmd[4] = 255;
@ -158,11 +244,38 @@ TEST(PrimaryDeviceTest, RequestSense)
EXPECT_THROW(device->Dispatch(scsi_command::eCmdRequestSense), scsi_exception);
device->SetReady(true);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(PrimaryDeviceTest, SendDiagnostic)
{
MockAbstractController controller(0);
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, Status());
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdSendDiag));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[1] = 0x10;
EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because PF bit is not supported";
cmd[1] = 0;
cmd[3] = 1;
EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because parameter list is not supported";
cmd[3] = 0;
cmd[4] = 1;
EXPECT_THROW(device->Dispatch(scsi_command::eCmdSendDiag), scsi_exception)
<< "SEND DIAGNOSTIC must fail because parameter list is not supported";
}
TEST(PrimaryDeviceTest, ReportLuns)
{
const int LUN1 = 1;
@ -177,11 +290,11 @@ TEST(PrimaryDeviceTest, ReportLuns)
controller.AddDevice(device2);
EXPECT_TRUE(controller.HasDeviceForLun(LUN2));
vector<int>& cmd = controller.InitCmd(10);
vector<int>& cmd = controller.GetCmd();
// ALLOCATION LENGTH
cmd[9] = 255;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(device1->Dispatch(scsi_command::eCmdReportLuns));
const vector<BYTE>& buffer = controller.GetBuffer();
EXPECT_EQ(0x00, buffer[0]) << "Wrong data length";
@ -219,19 +332,31 @@ TEST(PrimaryDeviceTest, UnknownCommand)
EXPECT_FALSE(device->Dispatch((scsi_command)0xFF));
}
TEST(PrimaryDeviceTest, Dispatch)
{
MockAbstractController controller(0);
auto device = make_shared<NiceMock<MockPrimaryDevice>>(0);
controller.AddDevice(device);
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
}
TEST(PrimaryDeviceTest, WriteByteSequence)
{
vector<BYTE> data;
MockPrimaryDevice device(0);
EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device must not support writing byte sequences";
EXPECT_FALSE(device.WriteByteSequence(data, 0)) << "Primary device does not support writing byte sequences";
}
TEST(PrimaryDeviceTest, GetSendDelay)
TEST(PrimaryDeviceTest, GetSetSendDelay)
{
MockPrimaryDevice device(0);
EXPECT_EQ(-1, device.GetSendDelay());
EXPECT_EQ(-1, device.GetSendDelay()) << "Wrong delay default value";
device.SetSendDelay(1234);
EXPECT_EQ(1234, device.GetSendDelay());
}
TEST(PrimaryDeviceTest, Init)
@ -239,7 +364,7 @@ TEST(PrimaryDeviceTest, Init)
unordered_map<string, string> params;
MockPrimaryDevice device(0);
EXPECT_TRUE(device.Init(params)) << "Initialization of primary device must not fail";
EXPECT_FALSE(device.Init(params)) << "Initialization of primary device must fail";
}
TEST(PrimaryDeviceTest, FlushCache)

View File

@ -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)

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -7,48 +7,216 @@
//
//---------------------------------------------------------------------------
#include "spdlog/spdlog.h"
#include "mocks.h"
#include "rascsi_exceptions.h"
#include "protobuf_util.h"
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "rascsi_interface.pb.h"
#include "rascsi/command_context.h"
#include "rascsi/rascsi_response.h"
#include "rascsi/rascsi_image.h"
#include "rascsi/rascsi_executor.h"
#include <unistd.h>
using namespace rascsi_interface;
using namespace protobuf_util;
TEST(RascsiExecutorTest, ProcessCmd)
const extern bool enable_logging;
// This test fixture is required in order to reset the log level changed by the log level tests
class RascsiExecutorTest : public Test
{
void TearDown() override {
spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off);
}
};
TEST_F(RascsiExecutorTest, ProcessDeviceCmd)
{
const int ID = 3;
const int LUN = 0;
MockBus bus;
DeviceFactory device_factory;
MockAbstractController controller(ID);
ControllerManager controller_manager(bus);
RascsiImage rascsi_image;
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
auto executor = make_shared<MockRascsiExecutor>(rascsi_response, rascsi_image, device_factory, controller_manager);
PbDeviceDefinition definition;
PbCommand command;
MockCommandContext context;
definition.set_id(8);
definition.set_unit(32);
EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid ID must fail";
definition.set_id(ID);
EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Invalid LUN must fail";
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID and LUN must fail";
definition.set_unit(LUN);
EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Unknown operation must fail";
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid ID must fail";
definition.set_id(ID);
definition.set_unit(32);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Invalid LUN must fail";
definition.set_unit(LUN);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Unknown operation must fail";
command.set_operation(ATTACH);
EXPECT_FALSE(executor.ProcessCmd(context, definition, command, true)) << "Operation for unknown device must fail";
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation for unknown device type must fail";
auto device1 = make_shared<MockPrimaryDevice>(LUN);
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1));
definition.set_type(SCHS);
command.set_operation(INSERT);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Operation unsupported by device must fail";
controller_manager.DeleteAllControllers();
definition.set_type(SCRM);
auto device2 = make_shared<MockSCSIHD_NEC>(LUN);
device2->SetRemovable(true);
device2->SetProtectable(true);
device2->SetReady(true);
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2));
command.set_operation(ATTACH);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "ID and LUN already exist";
command.set_operation(START);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(PROTECT);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(UNPROTECT);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(STOP);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(EJECT);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(INSERT);
SetParam(definition, "file", "filename");
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true)) << "Non-existing file";
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, false)) << "Non-existing file";
command.set_operation(DETACH);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2));
command.set_operation(CHECK_AUTHENTICATION);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
command.set_operation(NO_OPERATION);
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, true));
EXPECT_TRUE(executor->ProcessDeviceCmd(context, definition, command, false));
// The operations below are not related to a device
command.set_operation(DETACH_ALL);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(RESERVE_IDS);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(CREATE_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(DELETE_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(RENAME_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(COPY_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(PROTECT_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
command.set_operation(UNPROTECT_IMAGE);
EXPECT_FALSE(executor->ProcessDeviceCmd(context, definition, command, true));
}
TEST(RascsiExecutorTest, SetLogLevel)
TEST_F(RascsiExecutorTest, ProcessCmd)
{
MockBus bus;
DeviceFactory device_factory;
MockAbstractController controller(0);
ControllerManager controller_manager(bus);
RascsiImage rascsi_image;
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
auto executor = make_shared<MockRascsiExecutor>(rascsi_response, rascsi_image, device_factory, controller_manager);
PbCommand command1;
PbCommand command2;
MockCommandContext context;
command1.set_operation(DETACH_ALL);
EXPECT_TRUE(executor->ProcessCmd(context, command1));
command1.set_operation(RESERVE_IDS);
SetParam(command1, "ids", "2,3");
EXPECT_TRUE(executor->ProcessCmd(context, command1));
const unordered_set<int> ids = executor->GetReservedIds();
EXPECT_EQ(2, ids.size());
EXPECT_NE(ids.end(), ids.find(2));
EXPECT_NE(ids.end(), ids.find(3));
command2.set_operation(RESERVE_IDS);
EXPECT_TRUE(executor->ProcessCmd(context, command2));
EXPECT_TRUE(executor->GetReservedIds().empty());
SetParam(command2, "ids", "-1");
EXPECT_FALSE(executor->ProcessCmd(context, command2));
EXPECT_TRUE(executor->GetReservedIds().empty());
command1.set_operation(NO_OPERATION);
EXPECT_TRUE(executor->ProcessCmd(context, command1));
command1.set_operation(ATTACH);
auto device = command1.add_devices();
device->set_type(SCHS);
device->set_id(-1);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
device->set_id(0);
device->set_unit(1);
EXPECT_FALSE(executor->ProcessCmd(context, command1)) << "LUN 0 is missing";
device->set_unit(0);
EXPECT_TRUE(executor->ProcessCmd(context, command1));
// The operations below must fail because of missing parameters.
// The respective functionality is tested in rascsi_image_test.cpp.
command1.set_operation(CREATE_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
command1.set_operation(DELETE_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
command1.set_operation(RENAME_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
command1.set_operation(COPY_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
command1.set_operation(PROTECT_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
command1.set_operation(UNPROTECT_IMAGE);
EXPECT_FALSE(executor->ProcessCmd(context, command1));
}
TEST_F(RascsiExecutorTest, SetLogLevel)
{
MockBus bus;
DeviceFactory device_factory;
@ -62,12 +230,11 @@ TEST(RascsiExecutorTest, SetLogLevel)
EXPECT_TRUE(executor.SetLogLevel("info"));
EXPECT_TRUE(executor.SetLogLevel("warn"));
EXPECT_TRUE(executor.SetLogLevel("err"));
EXPECT_TRUE(executor.SetLogLevel("critical"));
EXPECT_TRUE(executor.SetLogLevel("off"));
EXPECT_FALSE(executor.SetLogLevel("xyz"));
}
TEST(RascsiExecutorTest, Attach)
TEST_F(RascsiExecutorTest, Attach)
{
const int ID = 3;
const int LUN = 0;
@ -101,22 +268,58 @@ TEST(RascsiExecutorTest, Attach)
definition.set_type(PbDeviceType::SCHD);
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without sectors not rejected";
definition.set_block_size(1);
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid sector size not rejeced";
definition.set_revision("invalid revision");
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid revision not rejected";
definition.set_revision("1234");
definition.set_block_size(1024);
definition.set_block_size(1);
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with invalid sector size not rejected";
definition.set_block_size(512);
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive without image file not rejected";
AddParam(definition, "file", "/non_existing_file");
EXPECT_FALSE(executor.Attach(context, definition, false));
SetParam(definition, "file", "/non_existing_file");
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Drive with non-existing image file not rejected";
AddParam(definition, "file", "/dev/zero");
EXPECT_FALSE(executor.Attach(context, definition, false)) << "Empty image file not rejected";
string filename = CreateTempFile(1);
SetParam(definition, "file", filename);
EXPECT_THROW(executor.Attach(context, definition, false), io_exception) << "Too small image file not rejected";
unlink(filename.c_str());
// Further testing is not possible without a filesystem
filename = CreateTempFile(512);
SetParam(definition, "file", filename);
bool result = executor.Attach(context, definition, false);
unlink(filename.c_str());
EXPECT_TRUE(result);
controller_manager.DeleteAllControllers();
filename = CreateTempFile(513);
SetParam(definition, "file", filename);
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
EXPECT_TRUE(result);
definition.set_type(PbDeviceType::SCCD);
definition.set_unit(LUN + 1);
filename = CreateTempFile(2048);
SetParam(definition, "file", filename);
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
EXPECT_TRUE(result);
definition.set_type(PbDeviceType::SCMO);
definition.set_unit(LUN + 2);
SetParam(definition, "read_only", "true");
filename = CreateTempFile(4096);
SetParam(definition, "file", filename);
result = executor.Attach(context, definition, false);
unlink(filename.c_str());
EXPECT_TRUE(result);
controller_manager.DeleteAllControllers();
}
TEST(RascsiExecutorTest, Insert)
TEST_F(RascsiExecutorTest, Insert)
{
MockBus bus;
DeviceFactory device_factory;
@ -147,7 +350,7 @@ TEST(RascsiExecutorTest, Insert)
EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Filename is missing";
AddParam(definition, "file", "filename");
SetParam(definition, "file", "filename");
EXPECT_TRUE(executor.Insert(context, definition, device, true)) << "Dry-run must not fail";
EXPECT_FALSE(executor.Insert(context, definition, device, false));
@ -155,18 +358,25 @@ TEST(RascsiExecutorTest, Insert)
EXPECT_FALSE(executor.Insert(context, definition, device, false));
definition.set_block_size(0);
EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation has to fail";
EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "Image file validation must fail";
AddParam(definition, "file", "/non_existing_file");
SetParam(definition, "file", "/non_existing_file");
EXPECT_FALSE(executor.Insert(context, definition, device, false));
AddParam(definition, "file", "/dev/zero");
EXPECT_FALSE(executor.Insert(context, definition, device, false)) << "File has 0 bytes";
string filename = CreateTempFile(1);
SetParam(definition, "file", filename);
EXPECT_THROW(executor.Insert(context, definition, device, false), io_exception)
<< "Too small image file not rejected";
unlink(filename.c_str());
// Further testing is not possible without a filesystem
filename = CreateTempFile(512);
SetParam(definition, "file", filename);
const bool result = executor.Insert(context, definition, device, false);
unlink(filename.c_str());
EXPECT_TRUE(result);
}
TEST(RascsiExecutorTest, Detach)
TEST_F(RascsiExecutorTest, Detach)
{
const int ID = 3;
const int LUN1 = 0;
@ -180,10 +390,10 @@ TEST(RascsiExecutorTest, Detach)
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
MockCommandContext context;
auto device1 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services");
controller_manager.AttachToScsiController(ID, device1);
auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN2, "services");
controller_manager.AttachToScsiController(ID, device2);
auto device1 = device_factory.CreateDevice(controller_manager, SCHS, LUN1, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1));
auto device2 = device_factory.CreateDevice(controller_manager, SCHS, LUN2, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2));
auto d1 = controller_manager.GetDeviceByIdAndLun(ID, LUN1);
EXPECT_FALSE(executor.Detach(context, d1, false)) << "LUNs > 0 have to be detached first";
@ -191,9 +401,11 @@ TEST(RascsiExecutorTest, Detach)
EXPECT_TRUE(executor.Detach(context, d2, false));
EXPECT_TRUE(executor.Detach(context, d1, false));
EXPECT_TRUE(controller_manager.GetAllDevices().empty());
EXPECT_FALSE(executor.Detach(context, d1, false));
}
TEST(RascsiExecutorTest, DetachAll)
TEST_F(RascsiExecutorTest, DetachAll)
{
const int ID = 4;
@ -204,8 +416,8 @@ TEST(RascsiExecutorTest, DetachAll)
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
RascsiExecutor executor(rascsi_response, rascsi_image, device_factory, controller_manager);
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
controller_manager.AttachToScsiController(ID, device);
auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device));
EXPECT_NE(nullptr, controller_manager.FindController(ID));
EXPECT_FALSE(controller_manager.GetAllDevices().empty());
@ -214,7 +426,7 @@ TEST(RascsiExecutorTest, DetachAll)
EXPECT_TRUE(controller_manager.GetAllDevices().empty());
}
TEST(RascsiExecutorTest, ShutDown)
TEST_F(RascsiExecutorTest, ShutDown)
{
MockBus bus;
DeviceFactory device_factory;
@ -231,7 +443,7 @@ TEST(RascsiExecutorTest, ShutDown)
EXPECT_FALSE(executor.ShutDown(context, "reboot"));
}
TEST(RascsiExecutorTest, SetReservedIds)
TEST_F(RascsiExecutorTest, SetReservedIds)
{
MockBus bus;
DeviceFactory device_factory;
@ -266,13 +478,13 @@ TEST(RascsiExecutorTest, SetReservedIds)
EXPECT_NE(reserved_ids.end(), reserved_ids.find(5));
EXPECT_NE(reserved_ids.end(), reserved_ids.find(7));
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
controller_manager.AttachToScsiController(5, device);
auto device = device_factory.CreateDevice(controller_manager, SCHS, 0, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(5, device));
error = executor.SetReservedIds("5");
EXPECT_FALSE(error.empty());
}
TEST(RascsiExecutorTest, ValidateImageFile)
TEST_F(RascsiExecutorTest, ValidateImageFile)
{
MockBus bus;
DeviceFactory device_factory;
@ -283,11 +495,7 @@ TEST(RascsiExecutorTest, ValidateImageFile)
MockCommandContext context;
string full_path;
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path));
EXPECT_TRUE(full_path.empty());
device = device_factory.CreateDevice(controller_manager, SCHD, 0, "test");
auto device = dynamic_pointer_cast<StorageDevice>(device_factory.CreateDevice(controller_manager, SCHD, 0, "test"));
EXPECT_TRUE(executor.ValidateImageFile(context, device, "", full_path));
EXPECT_TRUE(full_path.empty());
@ -295,7 +503,7 @@ TEST(RascsiExecutorTest, ValidateImageFile)
EXPECT_TRUE(full_path.empty());
}
TEST(RascsiExecutorTest, ValidateLunSetup)
TEST_F(RascsiExecutorTest, ValidateLunSetup)
{
MockBus bus;
DeviceFactory device_factory;
@ -314,16 +522,16 @@ TEST(RascsiExecutorTest, ValidateLunSetup)
error = executor.ValidateLunSetup(command);
EXPECT_FALSE(error.empty());
auto device2 = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, "services");
controller_manager.AttachToScsiController(0, device2);
auto device2 = device_factory.CreateDevice(controller_manager, SCHS, 0, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(0, device2));
error = executor.ValidateLunSetup(command);
EXPECT_TRUE(error.empty());
}
TEST(RascsiExecutorTest, VerifyExistingIdAndLun)
TEST_F(RascsiExecutorTest, VerifyExistingIdAndLun)
{
const int ID = 1;
const int LUN1 = 2;
const int LUN1 = 0;
const int LUN2 = 3;
MockBus bus;
@ -335,13 +543,13 @@ TEST(RascsiExecutorTest, VerifyExistingIdAndLun)
MockCommandContext context;
EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN1));
auto device = device_factory.CreateDevice(controller_manager, UNDEFINED, LUN1, "services");
controller_manager.AttachToScsiController(ID, device);
auto device = device_factory.CreateDevice(controller_manager, SCHS, LUN1, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device));
EXPECT_TRUE(executor.VerifyExistingIdAndLun(context, ID, LUN1));
EXPECT_FALSE(executor.VerifyExistingIdAndLun(context, ID, LUN2));
}
TEST(RascsiExecutorTest, CreateDevice)
TEST_F(RascsiExecutorTest, CreateDevice)
{
MockBus bus;
DeviceFactory device_factory;
@ -360,7 +568,7 @@ TEST(RascsiExecutorTest, CreateDevice)
EXPECT_NE(nullptr, executor.CreateDevice(context, SCHS, 0, ""));
}
TEST(RascsiExecutorTest, SetSectorSize)
TEST_F(RascsiExecutorTest, SetSectorSize)
{
MockBus bus;
DeviceFactory device_factory;
@ -371,17 +579,17 @@ TEST(RascsiExecutorTest, SetSectorSize)
MockCommandContext context;
unordered_set<uint32_t> sizes;
auto disk = make_shared<MockSCSIHD>(0, sizes, false);
EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 512));
auto hd = make_shared<MockSCSIHD>(0, sizes, false);
EXPECT_FALSE(executor.SetSectorSize(context, hd, 512));
sizes.insert(512);
disk = make_shared<MockSCSIHD>(0, sizes, false);
EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 0));
EXPECT_FALSE(executor.SetSectorSize(context, "test", disk, 1));
EXPECT_TRUE(executor.SetSectorSize(context, "test", disk, 512));
hd = make_shared<MockSCSIHD>(0, sizes, false);
EXPECT_TRUE(executor.SetSectorSize(context, hd, 0));
EXPECT_FALSE(executor.SetSectorSize(context, hd, 1));
EXPECT_TRUE(executor.SetSectorSize(context, hd, 512));
}
TEST(RascsiExecutorTest, ValidationOperationAgainstDevice)
TEST_F(RascsiExecutorTest, ValidateOperationAgainstDevice)
{
MockBus bus;
DeviceFactory device_factory;
@ -393,50 +601,50 @@ TEST(RascsiExecutorTest, ValidationOperationAgainstDevice)
auto device = make_shared<MockPrimaryDevice>(0);
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, ATTACH));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, DETACH));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, ATTACH));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, DETACH));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, START));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, STOP));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, START));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, STOP));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, INSERT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, UNPROTECT));
device->SetStoppable(true);
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, INSERT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, STOP));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, INSERT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, UNPROTECT));
device->SetRemovable(true);
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, UNPROTECT));
device->SetProtectable(true);
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, EJECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, PROTECT));
EXPECT_FALSE(executor.ValidateOperationAgainstDevice(context, device, UNPROTECT));
device->SetReady(true);
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, EJECT));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, PROTECT));
EXPECT_TRUE(executor.ValidationOperationAgainstDevice(context, device, UNPROTECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, START));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, STOP));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, INSERT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, EJECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, PROTECT));
EXPECT_TRUE(executor.ValidateOperationAgainstDevice(context, device, UNPROTECT));
}
TEST(RascsiExecutorTest, ValidateIdAndLun)
TEST_F(RascsiExecutorTest, ValidateIdAndLun)
{
MockBus bus;
DeviceFactory device_factory;
@ -454,7 +662,7 @@ TEST(RascsiExecutorTest, ValidateIdAndLun)
EXPECT_TRUE(executor.ValidateIdAndLun(context, 7, 31));
}
TEST(RascsiExecutorTest, SetProductData)
TEST_F(RascsiExecutorTest, SetProductData)
{
MockBus bus;
DeviceFactory device_factory;

View 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
}

View File

@ -10,6 +10,7 @@
#include "mocks.h"
#include "controllers/controller_manager.h"
#include "devices/device_factory.h"
#include "rascsi_version.h"
#include "rascsi_interface.pb.h"
#include "rascsi/rascsi_response.h"
@ -20,28 +21,28 @@ TEST(RascsiResponseTest, Operation_Count)
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
PbResult pb_operation_info_result;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto operation_info = rascsi_response.GetOperationInfo(pb_operation_info_result, 0);
EXPECT_EQ(PbOperation_ARRAYSIZE - 1, operation_info->operations_size());
const auto info = response.GetOperationInfo(result, 0);
EXPECT_EQ(PbOperation_ARRAYSIZE - 1, info->operations_size());
}
void TestNonDiskDevice(const string& name, int default_param_count)
void TestNonDiskDevice(PbDeviceType type, int default_param_count)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
RascsiResponse response(device_factory, controller_manager, 32);
auto d = device_factory.CreateDevice(controller_manager, UNDEFINED, 0, name);
controller_manager.AttachToScsiController(0, d);
auto d = device_factory.CreateDevice(controller_manager, type, 0, "");
EXPECT_TRUE(controller_manager.AttachToScsiController(0, d));
PbServerInfo server_info;
rascsi_response.GetDevices(server_info, "image_folder");
PbServerInfo info;
response.GetDevices(info, "image_folder");
EXPECT_EQ(1, server_info.devices_info().devices().size());
const auto& device = server_info.devices_info().devices()[0];
EXPECT_EQ(1, info.devices_info().devices().size());
const auto& device = info.devices_info().devices()[0];
EXPECT_FALSE(device.properties().read_only());
EXPECT_FALSE(device.properties().protectable());
EXPECT_FALSE(device.properties().stoppable());
@ -61,14 +62,26 @@ void TestNonDiskDevice(const string& name, int default_param_count)
}
}
TEST(RascsiResponseTest, GetDevice_Printer)
TEST(RascsiResponseTest, GetDevices)
{
TestNonDiskDevice("printer", 2);
TestNonDiskDevice(SCHS, 0);
TestNonDiskDevice(SCLP, 1);
}
TEST(RascsiResponseTest, GetDevice_HostServices)
TEST(RascsiResponseTest, GetImageFile)
{
TestNonDiskDevice("services", 0);
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbImageFile image_file;
EXPECT_FALSE(response.GetImageFile(image_file, "default_folder", ""));
// Even though the call fails (non-existing file) some properties must be set
EXPECT_FALSE(response.GetImageFile(image_file, "default_folder", "filename.hds"));
EXPECT_EQ("filename.hds", image_file.name());
EXPECT_EQ(SCHD, image_file.type());
}
TEST(RascsiResponseTest, GetReservedIds)
@ -76,17 +89,159 @@ TEST(RascsiResponseTest, GetReservedIds)
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse rascsi_response(device_factory, controller_manager, 32);
RascsiResponse response(device_factory, controller_manager, 32);
unordered_set<int> ids;
PbResult result;
const auto& reserved_ids_info1 = rascsi_response.GetReservedIds(result, ids);
const auto& info1 = response.GetReservedIds(result, ids);
EXPECT_TRUE(result.status());
EXPECT_TRUE(reserved_ids_info1->ids().empty());
EXPECT_TRUE(info1->ids().empty());
ids.insert(3);
const auto& reserved_ids_info2 = rascsi_response.GetReservedIds(result, ids);
const auto& info2 = response.GetReservedIds(result, ids);
EXPECT_TRUE(result.status());
EXPECT_EQ(1, reserved_ids_info2->ids().size());
EXPECT_EQ(3, reserved_ids_info2->ids()[0]);
EXPECT_EQ(1, info2->ids().size());
EXPECT_EQ(3, info2->ids()[0]);
}
TEST(RascsiResponseTest, GetDevicesInfo)
{
const int ID = 2;
const int LUN1 = 0;
const int LUN2 = 5;
const int LUN3 = 6;
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbCommand command;
PbResult result;
response.GetDevicesInfo(result, command, "");
EXPECT_TRUE(result.status());
EXPECT_TRUE(result.devices_info().devices().empty());
auto device1 = make_shared<MockHostServices>(LUN1, controller_manager);
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device1));
response.GetDevicesInfo(result, command, "");
EXPECT_TRUE(result.status());
auto& devices1 = result.devices_info().devices();
EXPECT_EQ(1, devices1.size());
EXPECT_EQ(SCHS, devices1[0].type());
EXPECT_EQ(ID, devices1[0].id());
EXPECT_EQ(LUN1, devices1[0].unit());
auto device2 = make_shared<MockSCSIHD_NEC>(LUN2);
EXPECT_TRUE(controller_manager.AttachToScsiController(ID, device2));
response.GetDevicesInfo(result, command, "");
EXPECT_TRUE(result.status());
auto& devices2 = result.devices_info().devices();
EXPECT_EQ(2, devices2.size()) << "Data for all devices must be returned";
auto requested_device = command.add_devices();
requested_device->set_id(ID);
requested_device->set_unit(LUN1);
response.GetDevicesInfo(result, command, "");
EXPECT_TRUE(result.status());
auto& devices3 = result.devices_info().devices();
EXPECT_EQ(1, devices3.size()) << "Only data for the specified ID and LUN must be returned";
EXPECT_EQ(SCHS, devices3[0].type());
EXPECT_EQ(ID, devices3[0].id());
EXPECT_EQ(LUN1, devices3[0].unit());
requested_device->set_id(ID);
requested_device->set_unit(LUN3);
response.GetDevicesInfo(result, command, "");
EXPECT_FALSE(result.status()) << "Only data for the specified ID and LUN must be returned";
}
TEST(RascsiResponseTest, GetDeviceTypesInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto& info = response.GetDeviceTypesInfo(result);
EXPECT_TRUE(result.status());
EXPECT_EQ(8, info->properties().size());
}
TEST(RascsiResponseTest, GetServerInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
const unordered_set<int> ids = { 1, 3 };
PbResult result;
const auto& info = response.GetServerInfo(result, ids, "log_level", "default_folder", "", "", 1234);
EXPECT_TRUE(result.status());
EXPECT_EQ(rascsi_major_version, info->version_info().major_version());
EXPECT_EQ(rascsi_minor_version, info->version_info().minor_version());
EXPECT_EQ(rascsi_patch_version, info->version_info().patch_version());
EXPECT_EQ("log_level", info->log_level_info().current_log_level());
EXPECT_EQ("default_folder", info->image_files_info().default_image_folder());
EXPECT_EQ(1234, info->image_files_info().depth());
EXPECT_EQ(2, info->reserved_ids_info().ids().size());
}
TEST(RascsiResponseTest, GetVersionInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto& info = response.GetVersionInfo(result);
EXPECT_TRUE(result.status());
EXPECT_EQ(rascsi_major_version, info->major_version());
EXPECT_EQ(rascsi_minor_version, info->minor_version());
EXPECT_EQ(rascsi_patch_version, info->patch_version());
}
TEST(RascsiResponseTest, GetLogLevelInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto& info = response.GetLogLevelInfo(result, "level");
EXPECT_TRUE(result.status());
EXPECT_EQ("level", info->current_log_level());
EXPECT_EQ(6, info->log_levels().size());
}
TEST(RascsiResponseTest, GetNetworkInterfacesInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto& info = response.GetNetworkInterfacesInfo(result);
EXPECT_TRUE(result.status());
EXPECT_FALSE(info->name().empty());
}
TEST(RascsiResponseTest, GetMappingInfo)
{
MockBus bus;
ControllerManager controller_manager(bus);
DeviceFactory device_factory;
RascsiResponse response(device_factory, controller_manager, 32);
PbResult result;
const auto& info = response.GetMappingInfo(result);
EXPECT_TRUE(result.status());
EXPECT_EQ(9, info->mapping().size());
}

View 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();
}

View 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);
}

View File

@ -272,7 +272,7 @@ TEST(RasctlDisplayTest, DisplayOperationInfo)
EXPECT_FALSE(s.empty());
EXPECT_NE(string::npos, s.find(PbOperation_Name(NO_OPERATION)));
meta_data.set_server_side_name("name");
meta_data.set_server_side_name("server_side_name");
meta_data.set_description("description");
(*info.mutable_operations())[0] = meta_data;
s = display.DisplayOperationInfo(info);
@ -285,4 +285,10 @@ TEST(RasctlDisplayTest, DisplayOperationInfo)
EXPECT_NE(string::npos, s.find("description3"));
EXPECT_NE(string::npos, s.find("permitted_value3_1"));
EXPECT_NE(string::npos, s.find("permitted_value3_2"));
EXPECT_EQ(string::npos, s.find("server_side_name"));
(*info.mutable_operations())[1234] = meta_data;
s = display.DisplayOperationInfo(info);
EXPECT_FALSE(s.empty());
EXPECT_NE(string::npos, s.find("server_side_name"));
}

View File

@ -7,9 +7,16 @@
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include "rasutil.h"
#include <gtest/gtest.h>
#include "rascsi_interface.pb.h"
#include "rasutil.h"
#ifdef __linux__
#include <sched.h>
#endif
using namespace std;
using namespace rascsi_interface;
using namespace ras_util;
TEST(RasUtilTest, GetAsInt)
@ -55,3 +62,42 @@ TEST(RasUtilTest, ListDevices)
EXPECT_NE(string::npos, device_list.find("Host Services"));
EXPECT_NE(string::npos, device_list.find("SCSI Printer"));
}
TEST(RasUtilTest, GetExtensionLowerCase)
{
EXPECT_EQ("", GetExtensionLowerCase(""));
EXPECT_EQ("", GetExtensionLowerCase("."));
EXPECT_EQ("ext", GetExtensionLowerCase("file.ext"));
EXPECT_EQ("ext", GetExtensionLowerCase("FILE.EXT"));
EXPECT_EQ("ext", GetExtensionLowerCase(".ext"));
EXPECT_EQ("ext_long", GetExtensionLowerCase(".ext_long"));
EXPECT_EQ("ext", GetExtensionLowerCase(".XYZ.EXT"));
}
#ifdef __linux__
TEST(RasUtilTest, FixCpu)
{
cpu_set_t mask;
FixCpu(0);
CPU_ZERO(&mask);
const int cpu = sched_getaffinity(0, sizeof(cpu_set_t), &mask);
const int cpus = CPU_COUNT(&mask);
FixCpu(cpus);
CPU_ZERO(&mask);
EXPECT_EQ(cpu, sched_getaffinity(0, sizeof(cpu_set_t), &mask));
FixCpu(cpus - 1);
CPU_ZERO(&mask);
EXPECT_EQ(cpus - 1, sched_getaffinity(0, sizeof(cpu_set_t), &mask));
FixCpu(0);
// Reset affinity
CPU_ZERO(&mask);
CPU_SET(cpu, &mask);
sched_setaffinity(0, sizeof(cpu_set_t), &mask);
EXPECT_EQ(0, cpu);
}
#endif

View File

@ -8,6 +8,7 @@
//---------------------------------------------------------------------------
#include "mocks.h"
#include "scsi.h"
#include "rascsi_exceptions.h"
#include "devices/scsi_command_util.h"
@ -22,10 +23,9 @@ TEST(ScsiCommandUtilTest, ModeSelect6)
// PF (vendor-specific parameter format)
cdb[1] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 0), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 0), scsi_exception)
<< "Vendor-specific parameters are not supported";
cdb[0] = (int)scsi_command::eCmdModeSelect6;
cdb[0] = 0x15;
// PF (standard parameter format)
cdb[1] = 0x10;
@ -33,25 +33,25 @@ TEST(ScsiCommandUtilTest, ModeSelect6)
buf[9] = 0x00;
buf[10] = 0x02;
buf[11] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 256), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 256), scsi_exception)
<< "Requested sector size does not match current sector size";
// Page 0
buf[12] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512), scsi_exception)
<< "Unsupported page 0 was not rejected";
// Page 3 (Format Device Page)
buf[12] = 0x03;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512), scsi_exception)
<< "Requested sector size does not match current sector size";
// Match the requested to the current sector size
buf[24] = 0x02;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH - 1, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH - 1, 512), scsi_exception)
<< "Not enough command parameters";
ModeSelect(cdb, buf, LENGTH, 512);
ModeSelect(scsi_command::eCmdModeSelect6, cdb, buf, LENGTH, 512);
}
TEST(ScsiCommandUtilTest, ModeSelect10)
@ -63,35 +63,34 @@ TEST(ScsiCommandUtilTest, ModeSelect10)
// PF (vendor-specific parameter format)
cdb[1] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 0), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 0), scsi_exception)
<< "Vendor-specific parameters are not supported";
cdb[0] = (int)scsi_command::eCmdModeSelect10;
// PF (standard parameter format)
cdb[1] = 0x10;
// Request 512 bytes per sector
buf[13] = 0x00;
buf[14] = 0x02;
buf[15] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 256), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 256), scsi_exception)
<< "Requested sector size does not match current sector size";
// Page 0
buf[16] = 0x00;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512), scsi_exception)
<< "Unsupported page 0 was not rejected";
// Page 3 (Format Device Page)
buf[16] = 0x03;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512), scsi_exception)
<< "Requested sector size does not match current sector size";
// Match the requested to the current sector size
buf[28] = 0x02;
EXPECT_THROW(ModeSelect(cdb, buf, LENGTH - 1, 512), scsi_exception)
EXPECT_THROW(ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH - 1, 512), scsi_exception)
<< "Not enough command parameters";
ModeSelect(cdb, buf, LENGTH, 512);
ModeSelect(scsi_command::eCmdModeSelect10, cdb, buf, LENGTH, 512);
}
TEST(ScsiCommandUtilTest, EnrichFormatPage)

View File

@ -9,33 +9,290 @@
#include "mocks.h"
#include "scsi.h"
#include "rascsi_exceptions.h"
#include "controllers/scsi_controller.h"
using namespace scsi_defs;
TEST(ScsiControllerTest, GetMaxLuns)
TEST(ScsiControllerTest, GetInitiatorId)
{
MockScsiController controller(0);
const int ID = 2;
EXPECT_EQ(32, controller.GetMaxLuns());
MockScsiController controller;
controller.Process(ID);
EXPECT_EQ(ID, controller.GetInitiatorId());
controller.Process(-1);
EXPECT_EQ(-1, controller.GetInitiatorId());
}
TEST(ScsiControllerTest, Process)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
controller.SetPhase(BUS::phase_t::reserved);
ON_CALL(bus, GetRST).WillByDefault(Return(true));
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST);
EXPECT_CALL(bus, Reset);
EXPECT_CALL(controller, Reset);
EXPECT_EQ(BUS::phase_t::reserved, controller.Process(0));
controller.SetPhase(BUS::phase_t::busfree);
ON_CALL(bus, GetRST).WillByDefault(Return(false));
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST);
EXPECT_EQ(BUS::phase_t::busfree, controller.Process(0));
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST);
EXPECT_CALL(bus, Reset);
EXPECT_CALL(controller, Reset);
EXPECT_EQ(BUS::phase_t::busfree, controller.Process(0));
}
TEST(ScsiControllerTest, BusFree)
{
MockScsiController controller;
controller.SetPhase(BUS::phase_t::busfree);
controller.BusFree();
EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase());
controller.SetStatus(status::CHECK_CONDITION);
controller.SetPhase(BUS::phase_t::reserved);
controller.BusFree();
EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase());
EXPECT_EQ(status::GOOD, controller.GetStatus());
controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::NONE);
controller.SetPhase(BUS::phase_t::reserved);
controller.BusFree();
controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI);
controller.SetPhase(BUS::phase_t::reserved);
controller.BusFree();
controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI);
controller.SetPhase(BUS::phase_t::reserved);
controller.BusFree();
controller.ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI);
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_EXIT(controller.BusFree(), ExitedWithCode(EXIT_SUCCESS), "");
}
TEST(ScsiControllerTest, Selection)
{
NiceMock<MockBus> bus;
NiceMock<MockScsiController> controller(bus, 0);
controller.SetPhase(BUS::phase_t::selection);
ON_CALL(bus, GetSEL).WillByDefault(Return(true));
ON_CALL(bus, GetBSY).WillByDefault(Return(true));
EXPECT_CALL(bus, GetATN).Times(0);
controller.Selection();
EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase());
ON_CALL(bus, GetSEL).WillByDefault(Return(true));
ON_CALL(bus, GetBSY).WillByDefault(Return(false));
EXPECT_CALL(bus, GetATN).Times(0);
controller.Selection();
EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase());
ON_CALL(bus, GetSEL).WillByDefault(Return(false));
ON_CALL(bus, GetBSY).WillByDefault(Return(false));
EXPECT_CALL(bus, GetATN).Times(0);
controller.Selection();
EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase());
ON_CALL(bus, GetSEL).WillByDefault(Return(false));
ON_CALL(bus, GetBSY).WillByDefault(Return(true));
ON_CALL(bus, GetATN).WillByDefault(Return(false));
EXPECT_CALL(bus, GetATN);
controller.Selection();
EXPECT_EQ(BUS::phase_t::command, controller.GetPhase());
controller.SetPhase(BUS::phase_t::selection);
ON_CALL(bus, GetSEL).WillByDefault(Return(false));
ON_CALL(bus, GetBSY).WillByDefault(Return(true));
ON_CALL(bus, GetATN).WillByDefault(Return(true));
EXPECT_CALL(bus, GetATN);
controller.Selection();
EXPECT_EQ(BUS::phase_t::msgout, controller.GetPhase());
controller.SetPhase(BUS::phase_t::reserved);
ON_CALL(bus, GetDAT).WillByDefault(Return(0));
controller.Selection();
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase());
ON_CALL(bus, GetDAT).WillByDefault(Return(1));
controller.Selection();
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase()) << "There is no device that can be selected";
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
EXPECT_CALL(bus, SetBSY(true));
controller.Selection();
EXPECT_EQ(BUS::phase_t::selection, controller.GetPhase());
}
TEST(ScsiControllerTest, Command)
{
NiceMock<MockBus> bus;
NiceMock<MockScsiController> controller(bus, 0);
controller.SetPhase(BUS::phase_t::command);
controller.Command();
EXPECT_EQ(BUS::phase_t::command, controller.GetPhase());
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, SetMSG(false));
EXPECT_CALL(bus, SetCD(true));
EXPECT_CALL(bus, SetIO(false));
controller.Command();
EXPECT_EQ(BUS::phase_t::command, controller.GetPhase());
controller.SetPhase(BUS::phase_t::reserved);
ON_CALL(bus, CommandHandShake).WillByDefault(Return(6));
EXPECT_CALL(bus, SetMSG(false));
EXPECT_CALL(bus, SetCD(true));
EXPECT_CALL(bus, SetIO(false));
EXPECT_CALL(controller, Execute);
controller.Command();
EXPECT_EQ(BUS::phase_t::command, controller.GetPhase());
}
TEST(ScsiControllerTest, MsgIn)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, SetMSG(true));
EXPECT_CALL(bus, SetCD(true));
EXPECT_CALL(bus, SetIO(true));
controller.MsgIn();
EXPECT_EQ(BUS::phase_t::msgin, controller.GetPhase());
EXPECT_FALSE(controller.HasValidLength());
EXPECT_EQ(0, controller.GetOffset());
}
TEST(ScsiControllerTest, MsgOut)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, SetMSG(true));
EXPECT_CALL(bus, SetCD(true));
EXPECT_CALL(bus, SetIO(false));
controller.MsgOut();
EXPECT_EQ(BUS::phase_t::msgout, controller.GetPhase());
EXPECT_EQ(1, controller.GetLength());
EXPECT_EQ(0, controller.GetOffset());
}
TEST(ScsiControllerTest, DataIn)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
controller.SetPhase(BUS::phase_t::reserved);
controller.SetLength(0);
EXPECT_CALL(controller, Status);
controller.DataIn();
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase());
controller.SetLength(1);
EXPECT_CALL(bus, SetMSG(false));
EXPECT_CALL(bus, SetCD(false));
EXPECT_CALL(bus, SetIO(true));
controller.DataIn();
EXPECT_EQ(BUS::phase_t::datain, controller.GetPhase());
EXPECT_EQ(0, controller.GetOffset());
}
TEST(ScsiControllerTest, DataOut)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
controller.SetPhase(BUS::phase_t::reserved);
controller.SetLength(0);
EXPECT_CALL(controller, Status);
controller.DataOut();
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase());
controller.SetLength(1);
EXPECT_CALL(bus, SetMSG(false));
EXPECT_CALL(bus, SetCD(false));
EXPECT_CALL(bus, SetIO(false));
controller.DataOut();
EXPECT_EQ(BUS::phase_t::dataout, controller.GetPhase());
EXPECT_EQ(0, controller.GetOffset());
}
TEST(ScsiControllerTest, Error)
{
NiceMock<MockBus> bus;
MockScsiController controller(bus, 0);
ON_CALL(bus, GetRST).WillByDefault(Return(true));
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST());
EXPECT_CALL(bus, Reset);
EXPECT_CALL(controller, Reset);
controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
EXPECT_EQ(status::GOOD, controller.GetStatus());
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase());
ON_CALL(bus, GetRST).WillByDefault(Return(false));
controller.SetPhase(BUS::phase_t::status);
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST());
EXPECT_CALL(bus, Reset).Times(0);
EXPECT_CALL(controller, Reset).Times(0);
controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase());
controller.SetPhase(BUS::phase_t::msgin);
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST());
EXPECT_CALL(bus, Reset).Times(0);
EXPECT_CALL(controller, Reset).Times(0);
controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
EXPECT_EQ(BUS::phase_t::busfree, controller.GetPhase());
controller.SetPhase(BUS::phase_t::reserved);
EXPECT_CALL(bus, Acquire);
EXPECT_CALL(bus, GetRST());
EXPECT_CALL(bus, Reset).Times(0);
EXPECT_CALL(controller, Reset).Times(0);
EXPECT_CALL(controller, Status);
controller.Error(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION, status::RESERVATION_CONFLICT);
EXPECT_EQ(status::RESERVATION_CONFLICT, controller.GetStatus());
EXPECT_EQ(BUS::phase_t::reserved, controller.GetPhase());
}
TEST(ScsiControllerTest, RequestSense)
{
MockScsiController controller(0);
MockScsiController controller;
auto device = make_shared<MockPrimaryDevice>(0);
controller.AddDevice(device);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// ALLOCATION LENGTH
cmd[4] = 255;
// Non-existing LUN
cmd[1] = 0x20;
device->SetReady(true);
EXPECT_CALL(controller, Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN, status::CHECK_CONDITION)).Times(1);
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, Status);
EXPECT_TRUE(device->Dispatch(scsi_command::eCmdRequestSense));
EXPECT_EQ(status::GOOD, controller.GetStatus()) << "Illegal CHECK CONDITION for non-exsting LUN";
}

View File

@ -13,8 +13,7 @@
TEST(ScsiDaynaportTest, Inquiry)
{
TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, scsi_level::SCSI_2,
"Dayna SCSI/Link 1.4a", 0x20, false);
TestInquiry(SCDP, device_type::PROCESSOR, scsi_level::SCSI_2, "Dayna SCSI/Link 1.4a", 0x20, false);
}
TEST(ScsiDaynaportTest, Dispatch)
@ -22,6 +21,9 @@ TEST(ScsiDaynaportTest, Dispatch)
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
// TODO Remove tests below as soon as Daynaport does not inherit from Disk anymore
EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdModeSense6))
<< "Non-DaynaPort commands inherited from Disk must not be supported";
EXPECT_FALSE(daynaport->Dispatch(scsi_command::eCmdModeSelect6))
@ -37,21 +39,88 @@ TEST(ScsiDaynaportTest, TestUnitReady)
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdTestUnitReady)) << "TEST UNIT READY must never fail";
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(ScsiDaynaportTest, Read)
{
vector<BYTE> buf(0);
NiceMock<MockAbstractController> controller(0);
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
vector<int>& cmd = controller.GetCmd();
// ALLOCATION LENGTH
cmd[4] = 1;
EXPECT_EQ(0, daynaport->Read(cmd, buf, 0)) << "Trying to read the root sector must fail";
}
TEST(ScsiDaynaportTest, WriteCheck)
{
vector<BYTE> buf(0);
NiceMock<MockAbstractController> controller(0);
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
EXPECT_THROW(daynaport->WriteCheck(0), scsi_exception);
}
TEST(ScsiDaynaportTest, WriteBytes)
{
vector<BYTE> buf(0);
NiceMock<MockAbstractController> controller(0);
auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(CreateDevice(SCDP, controller));
vector<int>& cmd = controller.GetCmd();
// Unknown data format
cmd[5] = 0xff;
EXPECT_TRUE(daynaport->WriteBytes(cmd, buf, 0));
}
TEST(ScsiDaynaportTest, Read6)
{
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.GetCmd();
cmd[5] = 0xff;
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdRead6), scsi_exception) << "Invalid data format";
}
TEST(ScsiDaynaportTest, Write6)
{
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.GetCmd();
cmd[5] = 0x00;
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length";
cmd[3] = -1;
cmd[4] = -8;
cmd[5] = 0x80;
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length";
cmd[3] = 0;
cmd[4] = 0;
cmd[5] = 0xff;
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdWrite6), scsi_exception) << "Invalid transfer length";
}
TEST(ScsiDaynaportTest, TestRetrieveStats)
{
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// ALLOCATION LENGTH
cmd[4] = 255;
EXPECT_CALL(controller, DataIn()).Times(1);
EXPECT_CALL(controller, DataIn());
EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdRetrieveStats));
}
@ -60,19 +129,19 @@ TEST(ScsiDaynaportTest, SetInterfaceMode)
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// Unknown interface command
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode), scsi_exception);
// Not implemented, do nothing
cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMODE;
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode));
EXPECT_EQ(status::GOOD, controller.GetStatus());
cmd[5] = SCSIDaynaPort::CMD_SCSILINK_SETMAC;
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetIfaceMode));
// Not implemented
@ -93,12 +162,12 @@ TEST(ScsiDaynaportTest, SetMcastAddr)
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdSetMcastAddr), scsi_exception) << "Length of 0 is not supported";
cmd[4] = 1;
EXPECT_CALL(controller, DataOut()).Times(1);
EXPECT_CALL(controller, DataOut());
EXPECT_TRUE(daynaport->Dispatch(scsi_command::eCmdSetMcastAddr));
}
@ -107,7 +176,7 @@ TEST(ScsiDaynaportTest, EnableInterface)
NiceMock<MockAbstractController> controller(0);
auto daynaport = CreateDevice(SCDP, controller);
vector<int>& cmd = controller.InitCmd(6);
vector<int>& cmd = controller.GetCmd();
// Enable
EXPECT_THROW(daynaport->Dispatch(scsi_command::eCmdEnableInterface), scsi_exception);

View File

@ -11,6 +11,10 @@
TEST(ScsiHostBridgeTest, Inquiry)
{
TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI RASCSI BRIDGE ", 0x27, false);
TestInquiry(SCBR, device_type::COMMUNICATIONS, scsi_level::SCSI_2, "RaSCSI RASCSI BRIDGE ", 0x27, false);
}
TEST(ScsiHostBridgeTest, Dispatch)
{
TestDispatch(SCBR);
}

View File

@ -14,20 +14,39 @@
using namespace std;
TEST(ScsiPrinterTest, Init)
{
NiceMock<MockAbstractController> controller(0);
auto printer = CreateDevice(SCLP, controller);
unordered_map<string, string> params;
EXPECT_TRUE(printer->Init(params));
params["cmd"] = "missing_filename_specifier";
EXPECT_FALSE(printer->Init(params));
params["cmd"] = "%f";
EXPECT_TRUE(printer->Init(params));
}
TEST(ScsiPrinterTest, Dispatch)
{
TestDispatch(SCLP);
}
TEST(ScsiPrinterTest, TestUnitReady)
{
NiceMock<MockAbstractController> controller(0);
auto printer = CreateDevice(SCLP, controller);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_CALL(controller, Status());
EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdTestUnitReady));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(ScsiPrinterTest, Inquiry)
{
TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI SCSI PRINTER ", 0x1f, false);
TestInquiry(SCLP, device_type::PRINTER, scsi_level::SCSI_2, "RaSCSI SCSI PRINTER ", 0x1f, false);
}
TEST(ScsiPrinterTest, ReserveUnit)
@ -60,13 +79,37 @@ TEST(ScsiPrinterTest, SendDiagnostic)
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(ScsiPrinterTest, Print)
{
NiceMock<MockAbstractController> controller(0);
auto printer = CreateDevice(SCLP, controller);
vector<int>& cmd = controller.GetCmd();
EXPECT_CALL(controller, DataOut());
EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdPrint));
cmd[3] = 0xff;
cmd[4] = 0xff;
EXPECT_THROW(printer->Dispatch(scsi_command::eCmdPrint), scsi_exception) << "Buffer overflow was not reported";
}
TEST(ScsiPrinterTest, StopPrint)
{
NiceMock<MockAbstractController> controller(0);
auto printer = CreateDevice(SCLP, controller);
EXPECT_CALL(controller, Status()).Times(1);
EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdStartStop));
EXPECT_CALL(controller, Status());
EXPECT_TRUE(printer->Dispatch(scsi_command::eCmdStopPrint));
EXPECT_EQ(status::GOOD, controller.GetStatus());
}
TEST(ScsiPrinterTest, WriteByteSequence)
{
NiceMock<MockAbstractController> controller(0);
auto printer = dynamic_pointer_cast<SCSIPrinter>(CreateDevice(SCLP, controller));
vector<BYTE> buf(1);
EXPECT_TRUE(printer->WriteByteSequence(buf, buf.size()));
printer->Cleanup();
}

View File

@ -8,26 +8,44 @@
//---------------------------------------------------------------------------
#include "mocks.h"
#include "rascsi_exceptions.h"
TEST(ScsiCdTest, Inquiry)
{
TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI SCSI CD-ROM ", 0x1f, true);
TestInquiry(SCCD, device_type::CD_ROM, scsi_level::SCSI_2, "RaSCSI SCSI CD-ROM ", 0x1f, true);
}
TEST(ScsiCdTest, Dispatch)
{
TestDispatch(SCCD);
}
TEST(ScsiCdTest, SetUpModePages)
{
map<int, vector<byte>> mode_pages;
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSICD cd(0, sector_sizes);
cd.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(7, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(8, mode_pages[13].size());
EXPECT_EQ(16, mode_pages[14].size());
EXPECT_EQ(30, mode_pages[48].size());
cd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(7, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(8, pages[13].size());
EXPECT_EQ(16, pages[14].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiCdTest, ReadToc)
{
MockAbstractController controller(0);
const unordered_set<uint32_t> sector_sizes;
auto cd = make_shared<MockSCSICD>(0, sector_sizes);
controller.AddDevice(cd);
EXPECT_THROW(cd->Dispatch(scsi_command::eCmdReadToc), scsi_exception) << "Drive is not ready";
// Further testing requires filesystem access
}

View File

@ -15,21 +15,52 @@ using namespace std;
TEST(ScsiHdNecTest, Inquiry)
{
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI ", 0x1f, false);
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "RaSCSI ", 0x1f, false, ".hdn");
}
TEST(ScsiHdNecTest, SetUpModePages)
{
map<int, vector<byte>> mode_pages;
map<int, vector<byte>> pages;
MockSCSIHD_NEC hd(0);
hd.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(8, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(20, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(30, mode_pages[48].size());
hd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(20, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiHdNecTest, TestAddFormatPage)
{
map<int, vector<byte>> pages;
MockSCSIHD_NEC hd(0);
hd.SetBlockCount(0x1234);
hd.SetReady(true);
hd.SetUpModePages(pages, 0x03, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
vector<byte>& page_3 = pages[3];
EXPECT_EQ(0x80, (int)page_3[0] & 0x80);
EXPECT_EQ(0, (int)page_3[20]);
hd.SetRemovable(true);
hd.SetUpModePages(pages, 0x03, false);
page_3 = pages[3];
EXPECT_EQ(0x20, (int)page_3[20]);
hd.SetUpModePages(pages, 0x03, true);
EXPECT_EQ(0xffff, GetInt16(page_3, 12));
}
TEST(ScsiHdNecTest, TestAddDrivePage)
{
map<int, vector<byte>> pages;
MockSCSIHD_NEC hd(0);
hd.SetBlockCount(0x1234);
hd.SetReady(true);
hd.SetUpModePages(pages, 0x04, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
}

View File

@ -11,31 +11,81 @@
#include "rascsi_exceptions.h"
#include "devices/scsihd.h"
TEST(ScsiHdTest, FinalizeSetup)
{
MockSCSIHD_NEC disk(0);
Filepath filepath;
EXPECT_THROW(disk.FinalizeSetup(filepath, 2LL * 1024 * 1024 * 1024 * 1024 + 1, 0), io_exception);
}
TEST(ScsiHdTest, Inquiry)
{
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI ", 0x1f, false);
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_2, "RaSCSI ", 0x1f, false);
TestInquiry(SCHD, device_type::DIRECT_ACCESS, scsi_level::SCSI_1_CCS, "RaSCSI ", 0x1f, false, ".hd1");
}
TEST(ScsiHdTest, SupportsSaveParameters)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
EXPECT_TRUE(hd.SupportsSaveParameters());
}
TEST(ScsiHdTest, FinalizeSetup)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
hd.SetSectorSizeInBytes(1024);
EXPECT_THROW(hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes(), 0), io_exception)
<< "Unsupported drive capacity";
EXPECT_THROW(hd.FinalizeSetup(0), io_exception) << "Device has 0 blocks";
hd.SetBlockCount(1);
hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024);
hd.FinalizeSetup(2LL * 1024 * 1024 * 1024 * 1024 + hd.GetSectorSizeInBytes() - 1);
}
TEST(ScsiHdTest, SetUpModePages)
{
map<int, vector<byte>> mode_pages;
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIHD hd(0, sector_sizes, false);
hd.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(30, mode_pages[48].size());
hd.SetReady(false);
hd.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(5, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(30, pages[48].size());
}
TEST(ScsiHdTest, ModeSelect)
{
const unordered_set<uint32_t> sector_sizes = { 512 };
MockSCSIHD hd(0, sector_sizes, false);
vector<int> cmd(10);
vector<BYTE> buf(255);
hd.SetSectorSizeInBytes(512);
// PF
cmd[1] = 0x10;
// Page 3 (Device Format Page)
buf[4] = 0x03;
// 512 bytes per sector
buf[16] = 0x02;
EXPECT_NO_THROW(hd.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) is supported";
buf[4] = 0;
buf[16] = 0;
// Page 3 (Device Format Page)
buf[8] = 0x03;
// 512 bytes per sector
buf[20] = 0x02;
EXPECT_NO_THROW(hd.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 255)) << "MODE SELECT(10) is supported";
}
TEST(ScsiHdTest, Dispatch)
{
TestDispatch(SCHD);
}

View File

@ -11,22 +11,136 @@
TEST(ScsiMoTest, Inquiry)
{
TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, scsi_level::SCSI_2,
"RaSCSI SCSI MO ", 0x1f, true);
TestInquiry(SCMO, device_type::OPTICAL_MEMORY, scsi_level::SCSI_2, "RaSCSI SCSI MO ", 0x1f, true);
}
TEST(ScsiMoTest, SupportsSaveParameters)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIMO mo(0, sector_sizes);
EXPECT_TRUE(mo.SupportsSaveParameters());
}
TEST(ScsiMoTest, SetUpModePages)
{
map<int, vector<byte>> mode_pages;
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIMO device(0, sector_sizes);
MockSCSIMO mo(0, sector_sizes);
device.SetUpModePages(mode_pages, 0x3f, false);
EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(4, mode_pages[6].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(12, mode_pages[32].size());
mo.SetReady(false);
mo.SetUpModePages(pages, 0x3f, false);
EXPECT_EQ(6, pages.size()) << "Unexpected number of mode pages";
EXPECT_EQ(12, pages[1].size());
EXPECT_EQ(24, pages[3].size());
EXPECT_EQ(24, pages[4].size());
EXPECT_EQ(4, pages[6].size());
EXPECT_EQ(12, pages[8].size());
EXPECT_EQ(12, pages[32].size());
}
TEST(ScsiMoTest, TestAddVendorPage)
{
map<int, vector<byte>> pages;
const unordered_set<uint32_t> sector_sizes;
MockSCSIMO mo(0, sector_sizes);
mo.SetReady(true);
mo.SetUpModePages(pages, 0x21, false);
EXPECT_TRUE(pages.empty()) << "Unsupported vendor-specific page was returned";
mo.SetBlockCount(0x12345678);
mo.SetUpModePages(pages, 0x20, false);
EXPECT_EQ(1, pages.size()) << "Unexpected number of mode pages";
vector<byte>& page_32 = pages[32];
EXPECT_EQ(12, page_32.size());
EXPECT_EQ(0, (int)page_32[2]) << "Wrong format mode";
EXPECT_EQ(0, (int)page_32[3]) << "Wrong format type";
EXPECT_EQ(0x12345678, GetInt32(page_32, 4)) << "Wrong number of blocks";
EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(0, GetInt16(page_32, 10));
mo.SetSectorSizeInBytes(512);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(0, GetInt16(page_32, 10));
mo.SetBlockCount(248826);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(1024, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(1, GetInt16(page_32, 10));
mo.SetBlockCount(446325);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(1025, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(10, GetInt16(page_32, 10));
mo.SetBlockCount(1041500);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(2250, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(18, GetInt16(page_32, 10));
mo.SetSectorSizeInBytes(2048);
mo.SetBlockCount(0x12345678);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(0, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(0, GetInt16(page_32, 10));
mo.SetBlockCount(310352);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(2244, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(11, GetInt16(page_32, 10));
mo.SetBlockCount(605846);
mo.SetUpModePages(pages, 0x20, false);
page_32 = pages[32];
EXPECT_EQ(4437, GetInt16(page_32, 8)) << "Wrong number of spare blocks";
EXPECT_EQ(18, GetInt16(page_32, 10));
// Changeable page
mo.SetUpModePages(pages, 0x20, true);
EXPECT_EQ(0, (int)page_32[2]);
EXPECT_EQ(0, (int)page_32[3]);
EXPECT_EQ(0, GetInt32(page_32, 4));
EXPECT_EQ(0, GetInt16(page_32, 8));
EXPECT_EQ(0, GetInt16(page_32, 10));
}
TEST(ScsiMoTest, ModeSelect)
{
const unordered_set<uint32_t> sector_sizes = { 1024, 2048 };
MockSCSIMO mo(0, sector_sizes);
vector<int> cmd(10);
vector<BYTE> buf(255);
mo.SetSectorSizeInBytes(2048);
// PF
cmd[1] = 0x10;
// Page 3 (Device Format Page)
buf[4] = 0x03;
// 2048 bytes per sector
buf[16] = 0x08;
EXPECT_NO_THROW(mo.ModeSelect(scsi_command::eCmdModeSelect6, cmd, buf, 255)) << "MODE SELECT(6) is supported";
buf[4] = 0;
buf[16] = 0;
// Page 3 (Device Format Page)
buf[8] = 0x03;
// 2048 bytes per sector
buf[20] = 0x08;
EXPECT_NO_THROW(mo.ModeSelect(scsi_command::eCmdModeSelect10, cmd, buf, 255)) << "MODE SELECT(10) is supported";
}
TEST(ScsiMoTest, Dispatch)
{
TestDispatch(SCMO);
}

View File

@ -0,0 +1,150 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "mocks.h"
#include "rascsi_exceptions.h"
#include "devices/storage_device.h"
#include <unistd.h>
TEST(StorageDeviceTest, Filename)
{
MockStorageDevice device;
device.SetFilename("filename");
EXPECT_EQ("filename", device.GetFilename());
}
TEST(StorageDeviceTest, ValidateFile)
{
MockStorageDevice device;
device.SetBlockCount(0);
EXPECT_THROW(device.ValidateFile("/non_existing_file"), io_exception);
device.SetReadOnly(false);
device.SetProtectable(true);
device.SetBlockCount(1);
device.ValidateFile("/non_existing_file");
EXPECT_TRUE(device.IsReadOnly());
EXPECT_FALSE(device.IsProtectable());
EXPECT_FALSE(device.IsStopped());
EXPECT_FALSE(device.IsRemoved());
EXPECT_FALSE(device.IsLocked());
device.SetReadOnly(false);
device.SetProtectable(true);
device.ValidateFile("/dev/null");
EXPECT_FALSE(device.IsReadOnly());
EXPECT_TRUE(device.IsProtectable());
EXPECT_FALSE(device.IsStopped());
EXPECT_FALSE(device.IsRemoved());
EXPECT_FALSE(device.IsLocked());
}
TEST(StorageDeviceTest, MediumChanged)
{
MockStorageDevice device;
device.SetMediumChanged(true);
EXPECT_TRUE(device.IsMediumChanged());
device.SetMediumChanged(false);
EXPECT_FALSE(device.IsMediumChanged());
}
TEST(StorageDeviceTest, GetIdsForReservedFile)
{
const int ID = 1;
const int LUN = 2;
MockStorageDevice device;
device.SetFilename("filename");
int id;
int lun;
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
device.ReserveFile("filename", ID, LUN);
EXPECT_TRUE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
EXPECT_EQ(ID, id);
EXPECT_EQ(LUN, lun);
device.UnreserveFile();
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
}
TEST(StorageDeviceTest, UnreserveAll)
{
MockStorageDevice device;
device.ReserveFile("filename", 2, 31);
StorageDevice::UnreserveAll();
int id;
int lun;
EXPECT_FALSE(StorageDevice::GetIdsForReservedFile("filename", id, lun));
}
TEST(StorageDeviceTest, GetSetReservedFiles)
{
const int ID = 1;
const int LUN = 16;
MockStorageDevice device;
device.ReserveFile("filename", ID, LUN);
const unordered_map<string, id_set> reserved_files = StorageDevice::GetReservedFiles();
EXPECT_EQ(1, reserved_files.size());
EXPECT_NE(reserved_files.end(), reserved_files.find("filename"));
StorageDevice::SetReservedFiles(reserved_files);
EXPECT_EQ(1, reserved_files.size());
EXPECT_NE(reserved_files.end(), reserved_files.find("filename"));
}
TEST(StorageDeviceTest, FileExists)
{
EXPECT_FALSE(StorageDevice::FileExists("/non_existing_file"));
EXPECT_TRUE(StorageDevice::FileExists("/dev/null"));
}
TEST(StorageDeviceTest, IsReadOnlyFile)
{
MockStorageDevice device;
device.SetFilename("/dev/null");
EXPECT_FALSE(device.IsReadOnlyFile());
device.SetFilename("/dev/mem");
EXPECT_TRUE(device.IsReadOnlyFile());
}
TEST(StorageDeviceTest, GetFileSize)
{
MockStorageDevice device;
const string filename = CreateTempFile(512);
device.SetFilename(filename);
const off_t size = device.GetFileSize();
unlink(filename.c_str());
EXPECT_EQ(512, size);
device.SetFilename("/non_existing_file");
EXPECT_THROW(device.GetFileSize(), io_exception);
}
TEST(StorageDeviceTest, Dispatch)
{
MockAbstractController controller(0);
auto device = make_shared<NiceMock<MockStorageDevice>>();
controller.AddDevice(device);
EXPECT_FALSE(device->Dispatch(scsi_command::eCmdIcd)) << "Command is not supported by this class";
}

View File

@ -11,22 +11,25 @@
#include "spdlog/spdlog.h"
// Also used by the RascsiExecutor tests
bool enable_logging; //NOSONAR Must be global in order to be shared with the tests
class Environment final : public ::testing::Environment
{
spdlog::level::level_enum log_level;
public:
explicit Environment(spdlog::level::level_enum level) : log_level(level) {}
Environment() = default;
~Environment() override = default;
void SetUp() override { spdlog::set_level(log_level); }
void SetUp() override { spdlog::set_level(enable_logging ? spdlog::level::trace : spdlog::level::off); }
};
int main(int argc, char *[])
{
// If any argument is provided the log level is set to trace
testing::AddGlobalTestEnvironment(new Environment(argc > 1 ? spdlog::level::trace : spdlog::level::off));
enable_logging = argc > 1;
testing::AddGlobalTestEnvironment(new Environment());
testing::InitGoogleTest();

Some files were not shown because too many files have changed in this diff Show More