Merge branch 'develop' into feature_bpi3

This commit is contained in:
Tony Kuker 2022-10-21 20:59:39 -05:00
commit a3b91d0e1f
180 changed files with 9215 additions and 6881 deletions

View File

@ -57,5 +57,5 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="tests/*" --define sonar.python.version=3
cd $SOURCES | sonar-scanner --define sonar.host.url="${{ env.SONAR_SERVER_URL }}" --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.projectKey=akuker_RASCSI --define sonar.organization=rascsi --define sonar.cfamily.gcov.reportsPath=. --define sonar.cfamily.cache.enabled=false --define sonar.coverage.exclusions="**/test/**" --define sonar.cpd.exclusions="**test/**" --define sonar.python.version=3

View File

@ -3,13 +3,13 @@
rascsi \- Emulates SCSI devices using the Raspberry Pi GPIO pins
.SH SYNOPSIS
.B rascsi
[\fB\-F\f® \fIFOLDER\fR]
[\fB\-L\f® \fILOG_LEVEL\fR]
[\fB\-P\f® \fIACCESS_TOKEN_FILE\fR]
[\fB\-F\fR \fIFOLDER\fR]
[\fB\-L\fR \fILOG_LEVEL\fR]
[\fB\-P\fR \fIACCESS_TOKEN_FILE\fR]
[\fB\-R\fR \fISCAN_DEPTH\fR]
[\fB\-h\fR]
[\fB\-n\fR \fIVENDOR:PRODUCT:REVISION\fR]
[\fB\-p\f® \fIPORT\fR]
[\fB\-p\fR \fIPORT\fR]
[\fB\-r\fR \fIRESERVED_IDS\fR]
[\fB\-n\fR \fITYPE\fR]
[\fB\-v\fR]
@ -18,7 +18,7 @@ rascsi \- Emulates SCSI devices using the Raspberry Pi GPIO pins
[\fB\-HDn[:u]\fR \fIFILE\fR]...
.SH DESCRIPTION
.B rascsi
Emulates SCSI devices using the Raspberry Pi GPIO pins.
emulates SCSI devices using the Raspberry Pi GPIO pins.
.PP
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.
@ -28,12 +28,12 @@ RaSCSI will determine the type of device based upon the file extension of the FI
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 GENUINE)
hdi: SCSI Hard Disk image (Anex86 HD image)
nhd: SCSI Hard Disk image (T98Next HD image)
hda: SCSI Hard Disk image (APPLE GENUINE - typically used with Mac SCSI emulation)
mos: SCSI Magneto-optical image (XM6 SCSI MO image - typically only used with X68000)
iso: SCSI CD-ROM image (ISO 9660 image)
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:
sudo rascsi -ID0 /path/to/drive/hdimage.hda
@ -106,6 +106,6 @@ In case the fallocate command is available a much faster alternative to the dd c
fallocate -l 104857600 /path/to/newimage.hda
.SH SEE ALSO
rasctl(1), scsimon(1), rasdump(1), sasidump(1)
rasctl(1), scsimon(1), rasdump(1)
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>

View File

@ -1,117 +1,92 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating\n\n
rascsi(1) General Commands Manual rascsi(1)
!! ------ The native file is rascsi.1. Re-run 'make docs' after updating
rascsi(1) General Commands Manual rascsi(1)
NAME
rascsi - Emulates SCSI devices using the Raspberry Pi GPIO pins
SYNOPSIS
rascsi [-F[u00AE] FOLDER] [-L[u00AE] LOG_LEVEL] [-P[u00AE] ACCESS_TO
KEN_FILE] [-R SCAN_DEPTH] [-h] [-n VENDOR:PRODUCT:REVISION] [-p[u00AE]
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
RESERVED_IDS] [-n TYPE] [-v] [-z LOCALE] [-IDn:[u] FILE] [-HDn[:u] FILE]...
DESCRIPTION
rascsi Emulates SCSI devices using the Raspberry Pi GPIO pins.
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 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.
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.
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 GENUINE)
hdi: SCSI Hard Disk image (Anex86 HD image)
nhd: SCSI Hard Disk image (T98Next HD image)
hda: SCSI Hard Disk image (APPLE GENUINE - typically used with Mac
SCSI emulation)
mos: SCSI Magneto-optical image (XM6 SCSI MO image - typically only
used with X68000)
iso: SCSI CD-ROM image (ISO 9660 image)
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 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.
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.
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 default folder
is '~/images'.
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'.
-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, critical, off). The default log level is 'info'.
-P ACCESS_TOKEN_FILE
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.
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.
-R SCAN_DEPTH
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.
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.
-h Show a help page.
-n VENDOR:PRODUCT:REVISION
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.
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.
-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-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.
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.
-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 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. 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.
@ -119,26 +94,22 @@ 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 de
vice 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 device 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), sasidump(1)
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

@ -35,7 +35,7 @@ rasctl \- Sends management commands to the rascsi process
[\fB\-z\fR \fILOCALE\fR]
.SH DESCRIPTION
.B rasctl
Sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the devices.
sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the devices.
Either the -i or -l option should be specified at one time. Not both.
@ -174,6 +174,6 @@ Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with the cont
rasctl -i 0 -f HDIIMAGE0.HDS
.SH SEE ALSO
rascsi(1), scsimon(1), rasdump(1), sasidump(1)
rascsi(1), scsimon(1), rasdump(1)
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>

View File

@ -1,32 +1,31 @@
!! ------ THIS FILE IS AUTO_GENERATED! DO NOT MANUALLY UPDATE!!!
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating\n\n
rascsi(1) General Commands Manual rascsi(1)
!! ------ The native file is rasctl.1. Re-run 'make docs' after updating
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 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]
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]
DESCRIPTION
rasctl Sends commands to the rascsi process to make configuration ad
justments at runtime or to check the status of the devices.
rasctl sends commands to the rascsi process to make configuration adjustments at runtime or to check the status of the de
vices.
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 speci
fied name and size in bytes.
Create an image file in the default image folder with the specified name and size in bytes.
-D Detach all devices.
@ -39,28 +38,22 @@ OPTIONS
-I Gets the list of reserved device IDs.
-L LOG_LEVEL
Set the rascsi log level (trace, debug, info, warn, err, criti
cal, off).
Set the rascsi log level (trace, debug, info, warn, err, critical, 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 authentica
tion.
-P Prompt for the access token in case rascsi requires authentication.
-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.
@ -71,11 +64,9 @@ 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.
@ -101,28 +92,23 @@ 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 param
eter for a non-disk device. See the rascsi(1) man page for per
mitted file types.
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.
-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
@ -133,17 +119,13 @@ 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 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.
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.
-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 con
troller. (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 controller. (This is not common)
EXAMPLES
Show a listing of all of the SCSI devices and their current status.
@ -156,14 +138,13 @@ 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 "HDIIMAGE0.HDS".
Request the RaSCSI process to attach a disk (assumed) to SCSI ID 0 with the contents of the file system image "HDIIM
AGE0.HDS".
rasctl -i 0 -f HDIIMAGE0.HDS
SEE ALSO
rascsi(1), scsimon(1), rasdump(1), sasidump(1)
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

@ -9,7 +9,7 @@ rasdump \- SCSI disk dumping tool for RaSCSI
[\fB\-r\fR]
.SH DESCRIPTION
.B rasdump
Samples the data on physical SCSI storage media, including hard drives and MO drives, and stores it to an image file. It can also restore from a dumped file onto physical SCSI storage media. Can be connected directly, through a STANDARD RaSCSI board, or a FULLSPEC RaSCSI board.
samples the data on physical SCSI storage media, including hard drives and MO drives, and stores it to an image file. It can also restore from a dumped file onto physical SCSI storage media. Can be connected directly, through a STANDARD RaSCSI board, or a FULLSPEC RaSCSI board.
Set its own ID with the BID option. Defaults to 7 if ommitted.

View File

@ -5,7 +5,7 @@ scsimon \- Acts as a data capture tool for all traffic on the SCSI bus. Data is
.B scsimon
.SH DESCRIPTION
.B scsimon
Monitors all of the traffic on the SCSI bus, using a RaSCSI device. The data is cached in memory while the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal.
monitors all of the traffic on the SCSI bus, using a RaSCSI device. The data is cached in memory while the tool is running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue to run until the user presses CTRL-C, or the process receives a SIGINT signal.
.PP
The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was launched.
@ -22,6 +22,6 @@ Launch scsimon to capture all SCSI traffic available to the RaSCSI hardware:
scsimon
.SH SEE ALSO
rasctl(1), rascsi(1), rasdump(1), sasidump(1)
rasctl(1), rascsi(1), rasdump(1)
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>

View File

@ -2,24 +2,20 @@
!! ------ The native file is scsimon.1. Re-run 'make docs' after updating
scsimon(1) General Commands Manual scsimon(1)
scsimon(1) General Commands Manual scsimon(1)
NAME
scsimon - Acts as a data capture tool for all traffic on the SCSI bus.
Data is stored in a Value Change Dump (VCD) file.
scsimon - Acts as a data capture tool for all traffic on the SCSI bus. Data is stored in a Value Change Dump (VCD) file.
SYNOPSIS
scsimon
DESCRIPTION
scsimon Monitors all of the traffic on the SCSI bus, using a RaSCSI de
vice. The data is cached in memory while the tool is running. A circu
lar buffer is used so that only the most recent 1,000,000 transactions
are stored. The tool will continue to run until the user presses CTRL-
C, or the process receives a SIGINT signal.
scsimon monitors all of the traffic on the SCSI bus, using a RaSCSI device. The data is cached in memory while the tool is
running. A circular buffer is used so that only the most recent 1,000,000 transactions are stored. The tool will continue
to run until the user presses CTRL-C, or the process receives a SIGINT signal.
The logged data is stored in a file called "log.vcd" in the current
working directory from where scsimon was launched.
The logged data is stored in a file called "log.vcd" in the current working directory from where scsimon was launched.
Currently, scsimon doesn't accept any arguments.
@ -29,14 +25,12 @@ OPTIONS
None
EXAMPLES
Launch scsimon to capture all SCSI traffic available to the RaSCSI
hardware:
Launch scsimon to capture all SCSI traffic available to the RaSCSI hardware:
scsimon
SEE ALSO
rasctl(1), rascsi(1), rasdump(1), sasidump(1)
rasctl(1), rascsi(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/>
scsimon(1)
scsimon(1)

View File

@ -55,3 +55,17 @@ services:
"--log-level=${WEB_LOG_LEVEL:-info}",
"--dev-mode"
]
pytest:
container_name: pytest
image: rascsi:pytest
pull_policy: never
profiles:
- webui-tests
build:
context: ..
dockerfile: docker/pytest/Dockerfile
volumes:
- ../python/web:/src:delegated
working_dir: /src
entrypoint: "pytest"

5
docker/pytest/Dockerfile Normal file
View File

@ -0,0 +1,5 @@
FROM python:3.7-bullseye
ENV DOCKER=1
COPY python/web/requirements-dev.txt /requirements-dev.txt
RUN pip install -r /requirements-dev.txt

View File

@ -6,7 +6,7 @@ FROM "${OS_ARCH}/${OS_DISTRO}:${OS_VERSION}"
EXPOSE 80 443
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends sudo systemd rsyslog procps
RUN apt-get update && apt-get install -y --no-install-recommends sudo systemd rsyslog procps man-db man2html
RUN groupadd pi
RUN useradd --create-home --shell /bin/bash -g pi pi

View File

@ -1 +0,0 @@
theme: jekyll-theme-dinky

View File

@ -1,37 +0,0 @@
## Welcome to GitHub Pages
You can use the [editor on GitHub](https://github.com/akuker/RASCSI/edit/master/docs/index.md) to maintain and preview the content for your website in Markdown files.
Whenever you commit to this repository, GitHub Pages will run [Jekyll](https://jekyllrb.com/) to rebuild the pages in your site, from the content in your Markdown files.
### Markdown
Markdown is a lightweight and easy-to-use syntax for styling your writing. It includes conventions for
```markdown
Syntax highlighted code block
# Header 1
## Header 2
### Header 3
- Bulleted
- List
1. Numbered
2. List
**Bold** and _Italic_ and `Code` text
[Link](url) and ![Image](src)
```
For more details see [GitHub Flavored Markdown](https://guides.github.com/features/mastering-markdown/).
### Jekyll Themes
Your Pages site will use the layout and styles from the Jekyll theme you have selected in your [repository settings](https://github.com/akuker/RASCSI/settings). The name of this theme is saved in the Jekyll `_config.yml` configuration file.
### Support or Contact
Having trouble with Pages? Check out our [documentation](https://docs.github.com/categories/github-pages-basics/) or [contact support](https://support.github.com/contact) and well help you sort it out.

View File

@ -104,7 +104,8 @@ function installPackages() {
unzip \
unar \
disktype \
libgmock-dev
libgmock-dev \
man2html
}
# install Debian packges for RaSCSI standalone
@ -116,7 +117,8 @@ function installPackagesStandalone() {
libprotobuf-dev \
protobuf-compiler \
disktype \
libgmock-dev
libgmock-dev \
man2html
}
# cache the pip packages
@ -832,6 +834,20 @@ function installNetatalk() {
NETATALK_VERSION="2-220801"
AFP_SHARE_PATH="$HOME/afpshare"
AFP_SHARE_NAME="Pi File Server"
NETATALK_CONFIG_PATH="/etc/netatalk"
if [ -d "$NETATALK_CONFIG_PATH" ]; then
echo
echo "WARNING: Netatalk configuration dir $NETATALK_CONFIG_PATH already exists."
echo "This installation process will overwrite existing Netatalk applications and configurations."
echo "No shared files will be deleted, but you may have to manually restore your settings after the installation."
echo
echo "Do you want to proceed with the installation? [y/N]"
read -r REPLY
if ! [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
exit 0
fi
fi
echo "Downloading netatalk-$NETATALK_VERSION to $HOME"
cd $HOME || exit 1
@ -842,6 +858,39 @@ function installNetatalk() {
./debian_install.sh -j="${CORES:-1}" -n="$AFP_SHARE_NAME" -p="$AFP_SHARE_PATH" || exit 1
}
# Appends the images dir as a shared Netatalk volume
function shareImagesWithNetatalk() {
APPLEVOLUMES_PATH="/etc/netatalk/AppleVolumes.default"
if ! [ -f "$APPLEVOLUMES_PATH" ]; then
echo "Could not find $APPLEVOLUMES_PATH ... is Netatalk installed?"
exit 1
fi
if [ "$(grep -c "$VIRTUAL_DRIVER_PATH" "$APPLEVOLUMES_PATH")" -ge 1 ]; then
echo "The $VIRTUAL_DRIVER_PATH dir is already shared in $APPLEVOLUMES_PATH"
echo "Do you want to turn off the sharing? [y/N]"
read -r REPLY
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
sudo systemctl stop afpd
sudo sed -i '\,^'"$VIRTUAL_DRIVER_PATH"',d' "$APPLEVOLUMES_PATH"
echo "Sharing for $VIRTUAL_DRIVER_PATH disabled!"
sudo systemctl start afpd
exit 0
fi
exit 0
fi
sudo systemctl stop afpd
echo "Appended to AppleVolumes.default:"
echo "$VIRTUAL_DRIVER_PATH \"RaSCSI Images\"" | sudo tee -a "$APPLEVOLUMES_PATH"
sudo systemctl start afpd
echo
echo "WARNING: Do not inadvertently move or rename image files that are in use by RaSCSI."
echo "Doing so may lead to data loss."
echo
}
# Downloads, compiles, and installs Macproxy (web proxy)
function installMacproxy {
PORT=5000
@ -1301,6 +1350,10 @@ function runChoice() {
showRaScsiCtrlBoardStatus
echo "Installing / Updating RaSCSI Control Board UI - Complete!"
;;
13)
shareImagesWithNetatalk
echo "Configuring AppleShare File Server - Complete!"
;;
-h|--help|h|help)
showMenu
;;
@ -1314,8 +1367,8 @@ function runChoice() {
function readChoice() {
choice=-1
until [ $choice -ge "0" ] && [ $choice -le "12" ]; do
echo -n "Enter your choice (0-12) or CTRL-C to exit: "
until [ $choice -ge "0" ] && [ $choice -le "13" ]; do
echo -n "Enter your choice (0-13) or CTRL-C to exit: "
read -r choice
done
@ -1345,6 +1398,7 @@ function showMenu() {
echo " 11) configure the RaSCSI Web Interface stand-alone"
echo "EXPERIMENTAL FEATURES"
echo " 12) install or update RaSCSI Control Board UI (requires hardware)"
echo " 13) share the images dir over AppleShare (requires Netatalk)"
}
# parse arguments passed to the script

View File

@ -23,4 +23,7 @@ ARCHIVE_FILE_SUFFIXES = [
# The RESERVATIONS list is used to keep track of the reserved ID memos.
# Initialize with a list of 8 empty strings.
RESERVATIONS = ["" for x in range(0, 8)]
RESERVATIONS = ["" for _ in range(0, 8)]
# Standard error message for shell commands
SHELL_ERROR = "Shell command: \"%s\" led to error: %s"

View File

@ -6,22 +6,33 @@ import os
import logging
import asyncio
from functools import lru_cache
from pathlib import PurePath
from pathlib import PurePath, Path
from zipfile import ZipFile, is_zipfile
from time import time
from subprocess import run, CalledProcessError
from json import dump, load
from shutil import copyfile
from urllib.parse import quote
import requests
import rascsi_interface_pb2 as proto
from rascsi.common_settings import CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, ARCHIVE_FILE_SUFFIXES, RESERVATIONS
from rascsi.common_settings import (
CFG_DIR,
CONFIG_FILE_SUFFIX,
PROPERTIES_SUFFIX,
ARCHIVE_FILE_SUFFIXES,
RESERVATIONS,
SHELL_ERROR,
)
from rascsi.ractl_cmds import RaCtlCmds
from rascsi.return_codes import ReturnCodes
from rascsi.socket_cmds import SocketCmds
from util import unarchiver
FILE_READ_ERROR = "Unhandled exception when reading file: %s"
FILE_WRITE_ERROR = "Unhandled exception when writing to file: %s"
URL_SAFE = "/:?&"
class FileCmds:
"""
@ -94,7 +105,9 @@ class FileCmds:
for file in result.image_files_info.image_files:
# Add properties meta data for the image, if applicable
if file.name in prop_files:
process = self.read_drive_properties(f"{CFG_DIR}/{file.name}.{PROPERTIES_SUFFIX}")
process = self.read_drive_properties(
Path(CFG_DIR) / f"{file.name}.{PROPERTIES_SUFFIX}"
)
prop = process["conf"]
else:
prop = False
@ -148,7 +161,7 @@ class FileCmds:
command.params["token"] = self.token
command.params["locale"] = self.locale
command.params["file"] = file_name + "." + file_type
command.params["file"] = f"{file_name}.{file_type}"
command.params["size"] = str(size)
command.params["read_only"] = "false"
@ -216,14 +229,15 @@ class FileCmds:
# noinspection PyMethodMayBeStatic
def delete_file(self, file_path):
"""
Takes (str) file_path with the full path to the file to delete
Takes (Path) file_path for the file to delete
Returns (dict) with (bool) status and (str) msg
"""
parameters = {
"file_path": file_path
}
if os.path.exists(file_path):
os.remove(file_path)
if file_path.exists():
file_path.unlink()
return {
"status": True,
"return_code": ReturnCodes.DELETEFILE_SUCCESS,
@ -238,14 +252,16 @@ class FileCmds:
# noinspection PyMethodMayBeStatic
def rename_file(self, file_path, target_path):
"""
Takes (str) file_path and (str) target_path
Takes:
- (Path) file_path for the file to rename
- (Path) target_path for the name to rename
Returns (dict) with (bool) status and (str) msg
"""
parameters = {
"target_path": target_path
}
if os.path.exists(PurePath(target_path).parent):
os.rename(file_path, target_path)
if target_path.parent.exists:
file_path.rename(target_path)
return {
"status": True,
"return_code": ReturnCodes.RENAMEFILE_SUCCESS,
@ -260,14 +276,16 @@ class FileCmds:
# noinspection PyMethodMayBeStatic
def copy_file(self, file_path, target_path):
"""
Takes (str) file_path and (str) target_path
Takes:
- (Path) file_path for the file to copy from
- (Path) target_path for the name to copy to
Returns (dict) with (bool) status and (str) msg
"""
parameters = {
"target_path": target_path
}
if os.path.exists(PurePath(target_path).parent):
copyfile(file_path, target_path)
if target_path.parent.exists:
copyfile(str(file_path), str(target_path))
return {
"status": True,
"return_code": ReturnCodes.WRITEFILE_SUCCESS,
@ -305,21 +323,22 @@ class FileCmds:
properties_files_moved = []
if move_properties_files_to_config:
for file in extract_result["extracted"]:
if file.get("name").endswith(".properties"):
if file.get("name").endswith(f".{PROPERTIES_SUFFIX}"):
prop_path = Path(CFG_DIR) / file["name"]
if (self.rename_file(
file["absolute_path"],
f"{CFG_DIR}/{file['name']}"
Path(file["absolute_path"]),
prop_path,
)):
properties_files_moved.append({
"status": True,
"name": file["path"],
"path": f"{CFG_DIR}/{file['name']}",
"path": str(prop_path),
})
else:
properties_files_moved.append({
"status": False,
"name": file["path"],
"path": f"{CFG_DIR}/{file['name']}",
"path": str(prop_path),
})
return {
@ -362,7 +381,7 @@ class FileCmds:
tmp_full_path = tmp_dir + file_name
iso_filename = f"{server_info['image_dir']}/{file_name}.iso"
req_proc = self.download_to_dir(url, tmp_dir, file_name)
req_proc = self.download_to_dir(quote(url, safe=URL_SAFE), tmp_dir, file_name)
if not req_proc["status"]:
return {"status": False, "msg": req_proc["msg"]}
@ -386,7 +405,7 @@ class FileCmds:
"%s was successfully unzipped. Deleting the zipfile.",
tmp_full_path,
)
self.delete_file(tmp_full_path)
self.delete_file(Path(tmp_full_path))
try:
run(
@ -401,8 +420,7 @@ class FileCmds:
check=True,
)
except CalledProcessError as error:
logging.warning("Executed shell command: %s", " ".join(error.cmd))
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
logging.warning(SHELL_ERROR, " ".join(error.cmd), error.stderr.decode("utf-8"))
return {"status": False, "msg": error.stderr.decode("utf-8")}
parameters = {
@ -424,7 +442,11 @@ class FileCmds:
logging.info("Making a request to download %s", url)
try:
with requests.get(url, stream=True, headers={"User-Agent": "Mozilla/5.0"}) as req:
with requests.get(
quote(url, safe=URL_SAFE),
stream=True,
headers={"User-Agent": "Mozilla/5.0"},
) as req:
req.raise_for_status()
with open(f"{save_dir}/{file_name}", "wb") as download:
for chunk in req.iter_content(chunk_size=8192):
@ -452,9 +474,9 @@ class FileCmds:
Takes (str) file_name
Returns (dict) with (bool) status and (str) msg
"""
file_name = f"{CFG_DIR}/{file_name}"
file_path = f"{CFG_DIR}/{file_name}"
try:
with open(file_name, "w", encoding="ISO-8859-1") as json_file:
with open(file_path, "w", encoding="ISO-8859-1") as json_file:
version = self.ractl.get_server_info()["version"]
devices = self.ractl.list_devices()["device_list"]
for device in devices:
@ -485,7 +507,7 @@ class FileCmds:
indent=4
)
parameters = {
"target_path": file_name
"target_path": file_path
}
return {
"status": True,
@ -494,28 +516,21 @@ class FileCmds:
}
except (IOError, ValueError, EOFError, TypeError) as error:
logging.error(str(error))
self.delete_file(file_name)
self.delete_file(Path(file_path))
return {"status": False, "msg": str(error)}
except:
logging.error("Could not write to file: %s", file_name)
self.delete_file(file_name)
parameters = {
"file_name": file_name
}
return {
"status": False,
"return_code": ReturnCodes.WRITEFILE_COULD_NOT_WRITE,
"parameters": parameters,
}
logging.error(FILE_WRITE_ERROR, file_name)
self.delete_file(Path(file_path))
raise
def read_config(self, file_name):
"""
Takes (str) file_name
Returns (dict) with (bool) status and (str) msg
"""
file_name = f"{CFG_DIR}/{file_name}"
file_path = Path(CFG_DIR) / file_name
try:
with open(file_name, encoding="ISO-8859-1") as json_file:
with open(file_path, encoding="ISO-8859-1") as json_file:
config = load(json_file)
# If the config file format changes again in the future,
# introduce more sophisticated format detection logic here.
@ -577,15 +592,8 @@ class FileCmds:
logging.error(str(error))
return {"status": False, "msg": str(error)}
except:
logging.error("Could not read file: %s", file_name)
parameters = {
"file_name": file_name
}
return {
"status": False,
"return_code": ReturnCodes.READCONFIG_COULD_NOT_READ,
"parameters": parameters
}
logging.error(FILE_READ_ERROR, str(file_path))
raise
def write_drive_properties(self, file_name, conf):
"""
@ -593,12 +601,12 @@ class FileCmds:
Takes file name base (str) and (list of dicts) conf as arguments
Returns (dict) with (bool) status and (str) msg
"""
file_path = f"{CFG_DIR}/{file_name}"
file_path = Path(CFG_DIR) / file_name
try:
with open(file_path, "w") as json_file:
dump(conf, json_file, indent=4)
parameters = {
"target_path": file_path
"target_path": str(file_path)
}
return {
"status": True,
@ -610,29 +618,22 @@ class FileCmds:
self.delete_file(file_path)
return {"status": False, "msg": str(error)}
except:
logging.error("Could not write to file: %s", file_path)
logging.error(FILE_WRITE_ERROR, str(file_path))
self.delete_file(file_path)
parameters = {
"target_path": file_path
}
return {
"status": False,
"return_code": ReturnCodes.WRITEFILE_COULD_NOT_WRITE,
"parameters": parameters,
}
raise
# noinspection PyMethodMayBeStatic
def read_drive_properties(self, file_path):
"""
Reads drive properties from json formatted file.
Takes (str) file_path as argument.
Takes (Path) file_path as argument.
Returns (dict) with (bool) status, (str) msg, (dict) conf
"""
try:
with open(file_path) as json_file:
conf = load(json_file)
parameters = {
"file_path": file_path
"file_path": str(file_path)
}
return {
"status": True,
@ -644,15 +645,8 @@ class FileCmds:
logging.error(str(error))
return {"status": False, "msg": str(error)}
except:
logging.error("Could not read file: %s", file_path)
parameters = {
"file_path": file_path
}
return {
"status": False,
"return_codes": ReturnCodes.READDRIVEPROPS_COULD_NOT_READ,
"parameters": parameters,
}
logging.error(FILE_READ_ERROR, str(file_path))
raise
# noinspection PyMethodMayBeStatic
async def run_async(self, program, args):
@ -687,5 +681,6 @@ class FileCmds:
"""
try:
return unarchiver.inspect_archive(file_path)
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError):
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError) as error:
logging.error(str(error))
raise

View File

@ -224,16 +224,13 @@ class RaCtlCmds:
devices = proto.PbDeviceDefinition()
devices.id = int(scsi_id)
if "device_type" in kwargs.keys():
if kwargs["device_type"]:
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
if "unit" in kwargs.keys():
if kwargs["unit"]:
devices.unit = kwargs["unit"]
if "params" in kwargs.keys():
if isinstance(kwargs["params"], dict):
for param in kwargs["params"]:
devices.params[param] = kwargs["params"][param]
if kwargs.get("device_type"):
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
if kwargs.get("unit"):
devices.unit = kwargs["unit"]
if kwargs.get("params") and isinstance(kwargs["params"], dict):
for param in kwargs["params"]:
devices.params[param] = kwargs["params"][param]
# Handling the inserting of media into an attached removable type device
device_type = kwargs.get("device_type", None)
@ -260,18 +257,14 @@ class RaCtlCmds:
# Handling attaching a new device
else:
command.operation = proto.PbOperation.ATTACH
if "vendor" in kwargs.keys():
if kwargs["vendor"]:
devices.vendor = kwargs["vendor"]
if "product" in kwargs.keys():
if kwargs["product"]:
devices.product = kwargs["product"]
if "revision" in kwargs.keys():
if kwargs["revision"]:
devices.revision = kwargs["revision"]
if "block_size" in kwargs.keys():
if kwargs["block_size"]:
devices.block_size = int(kwargs["block_size"])
if kwargs.get("vendor"):
devices.vendor = kwargs["vendor"]
if kwargs.get("product"):
devices.product = kwargs["product"]
if kwargs.get("revision"):
devices.revision = kwargs["revision"]
if kwargs.get("block_size"):
devices.block_size = int(kwargs["block_size"])
command.devices.append(devices)

View File

@ -8,6 +8,7 @@ from shutil import disk_usage
from re import findall, match
from socket import socket, gethostname, AF_INET, SOCK_DGRAM
from rascsi.common_settings import SHELL_ERROR
class SysCmds:
"""
@ -32,8 +33,7 @@ class SysCmds:
.strip()
)
except subprocess.CalledProcessError as error:
logging.warning("Executed shell command: %s", " ".join(error.cmd))
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
logging.warning(SHELL_ERROR, error.cmd, error.stderr.decode("utf-8"))
ra_git_version = ""
try:
@ -47,9 +47,8 @@ class SysCmds:
.strip()
)
except subprocess.CalledProcessError as error:
logging.warning("Executed shell command: %s", " ".join(error.cmd))
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
pi_version = "Unknown"
logging.warning(SHELL_ERROR, " ".join(error.cmd), error.stderr.decode("utf-8"))
pi_version = "?"
return {"git": ra_git_version, "env": pi_version}
@ -70,8 +69,7 @@ class SysCmds:
.strip()
)
except subprocess.CalledProcessError as error:
logging.warning("Executed shell command: %s", " ".join(error.cmd))
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
logging.warning(SHELL_ERROR, error.cmd, error.stderr.decode("utf-8"))
processes = ""
matching_processes = findall(daemon, processes)
@ -93,8 +91,7 @@ class SysCmds:
.strip()
)
except subprocess.CalledProcessError as error:
logging.warning("Executed shell command: %s", " ".join(error.cmd))
logging.warning("Got error: %s", error.stderr.decode("utf-8"))
logging.warning(SHELL_ERROR, error.cmd, error.stderr.decode("utf-8"))
bridges = ""
if "rascsi_bridge" in bridges:
@ -181,3 +178,18 @@ class SysCmds:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")
@staticmethod
def get_manpage(file_path):
"""
Takes (str) file_path path to image file to generate manpage for.
Returns either the man2html output, or the stderr output.
"""
process = run(
["man2html", file_path, "-M", "/"],
capture_output=True,
)
if process.returncode == 0:
return process.returncode, process.stdout.decode("utf-8")
return process.returncode, process.stderr.decode("utf-8")

View File

@ -69,7 +69,7 @@ def extract_archive(file_path, **kwargs):
unar_result_success = r'^Successfully extracted to "(?P<destination>.+)".$'
unar_result_no_files = "No files extracted."
unar_file_extracted = \
r"^ (?P<path>.+). \(((?P<size>[0-9]+) B)?(?P<types>(dir)?(, )?(rsrc)?)\)\.\.\. (?P<status>[A-Z]+)\.$"
r"^ {2}(?P<path>.+). \(((?P<size>\d+) B)?(?P<types>(dir)?(, )?(rsrc)?)\)\.\.\. (?P<status>[A-Z]+)\.$"
lines = process["stdout"].rstrip("\n").split("\n")
@ -141,7 +141,7 @@ def extract_archive(file_path, **kwargs):
# The parent dir may not be specified as a member, so ensure it exists
target_path.parent.mkdir(parents=True, exist_ok=True)
logging.debug("Moving temp file: %s -> %s", source_path, target_path)
move(source_path, target_path)
move(str(source_path), str(target_path))
moved.append(member)
return {
@ -152,7 +152,7 @@ def extract_archive(file_path, **kwargs):
raise UnarUnexpectedOutputError(lines[-1])
def inspect_archive(file_path, **kwargs):
def inspect_archive(file_path):
"""
Calls `lsar` to inspect the contents of an archive
Takes (str) file_path

View File

@ -2,3 +2,5 @@ pytest==7.1.3
pytest-httpserver==1.0.6
black==22.8.0
flake8==5.0.4
watchdog==2.1.9
requests==2.28.1

View File

@ -1,4 +1,5 @@
<html>
<!doctype html>
<html lang="en">
<head>
<title>RaSCSI-Web is Starting</title>
<meta http-equiv="refresh" content="2">

View File

@ -434,7 +434,7 @@
{
"device_type": "SCCD",
"vendor": null,
"product": null,
"product": "SCSI CD-ROM 512",
"revision": null,
"block_size": 512,
"size": null,

View File

@ -2,19 +2,7 @@ body {
color: black;
background-color: white;
font-family: Arial, Helvetica, sans-serif;
text-decoration:none;
}
h1 {
color: white;
font-size:20px;
background-color:black;
}
h2 {
color: black;
font-size:16px;
margin: 0px;
text-decoration: none;
}
a {
@ -27,10 +15,50 @@ form {
table, tr, td {
border: 1px solid black;
border-collapse:collapse;
border-collapse: collapse;
margin: none;
}
h1 {
color: white;
font-size: 20px;
}
h2 {
color: black;
font-size: large;
font-weight: bold;
margin: 3px;
}
summary.heading {
color: black;
font-size: large;
font-weight: bold;
margin: 3px;
}
div.header {
color: white;
background-color: black;
}
div.footer {
font-family: monospace;
}
div.logged_in {
background-color: green;
}
div.logged_out {
background-color: red;
}
input.lun {
width: 36px;
}
div.flash {
margin-top: 5px;
margin-bottom: 5px;
@ -61,71 +89,76 @@ div.flash div.info {
}
td.inactive {
text-align:center;
background-color:tan;
text-align: center;
background-color: tan;
}
summary.heading {
color: black;
font-size: large;
font-weight: bold;
margin: 0px;
ul.inline_list {
list-style: none;
}
.dropzone, .dropzone * {
box-sizing: border-box;
box-sizing: border-box;
}
.dropzone {
position: relative;
position: relative;
}
.dropzone .dz-button {
position: relative;
background-color: white;
color: black;
border: 2px dashed blue;
padding: 12px 28px;
}
.dropzone .dz-preview {
position: relative;
display: inline-block;
width: 120px;
margin: .5em;
position: relative;
display: inline-block;
width: 120px;
margin: .5em;
}
.dropzone .dz-preview .dz-progress {
display: block;
height: 15px;
border: 1px solid #aaa;
display: block;
height: 15px;
border: 1px solid #aaa;
}
.dropzone .dz-preview .dz-progress .dz-upload {
display: block;
height: 100%;
width: 0;
background: green;
display: block;
height: 100%;
width: 0;
background: green;
}
.dropzone .dz-preview .dz-error-message {
color: red;
display: none;
color: red;
display: none;
}
.dropzone .dz-preview.dz-error .dz-error-message {
display: block;
display: block;
}
.dropzone .dz-preview.dz-error .dz-error-mark {
display: block;
filter: drop-shadow(0px 0px 2px red);
display: block;
filter: drop-shadow(0px 0px 2px red);
opacity: 0;
}
.dropzone .dz-preview.dz-success .dz-success-mark {
display: block;
filter: drop-shadow(0px 0px 2px green);
display: block;
filter: drop-shadow(0px 0px 2px green);
}
.dropzone .dz-preview .dz-error-mark, .dropzone .dz-preview .dz-success-mark {
position: absolute;
display: none;
left: 30px;
top: 30px;
width: 54px;
height: 58px;
left: 50%;
margin-left: -27px;
position: absolute;
display: none;
top: 30px;
width: 54px;
height: 58px;
left: 50%;
margin-left: -27px;
}

View File

@ -1,80 +1,76 @@
<!doctype html>
<html>
<html lang="{{ env["locale"] }}">
<head>
<title>{{ _("RaSCSI Reloaded Control Page") }} [{{ env["host"] }}]</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link rel="apple-touch-icon" sizes="57x57" href="/pwa/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/pwa/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/pwa/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/pwa/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/pwa/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/pwa/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/pwa/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/pwa/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/pwa/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/pwa/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/pwa/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/pwa/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/pwa/favicon-16x16.png">
<link rel="manifest" href="/pwa/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/pwa/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<link rel="apple-touch-icon" sizes="57x57" href="/pwa/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/pwa/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/pwa/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/pwa/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/pwa/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/pwa/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/pwa/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/pwa/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/pwa/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/pwa/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/pwa/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/pwa/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/pwa/favicon-16x16.png">
<link rel="manifest" href="/pwa/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/pwa/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<script type="application/javascript">
var processNotify = function(Notification) {
document.getElementById("flash").innerHTML = "<div class=\"info\">" + Notification + "{{ _(" This process may take a while, and will continue in the background if you navigate away from this page.") }}</div>";
window.scrollTo(0,0);
}
<script type="application/javascript">
var processNotify = function(Notification) {
document.getElementById("flash").innerHTML = "<div class=\"info\">" + Notification + "{{ _(" This process may take a while, and will continue in the background if you navigate away from this page.") }}</div>";
window.scrollTo(0,0);
}
var shutdownNotify = function(Notification) {
document.getElementById("flash").innerHTML = "<div class=\"info\">" + Notification + "{{ _(" The Web Interface will become unresponsive momentarily. Reload this page after the Pi has started up again.") }}</div>";
window.scrollTo(0,0);
}
</script>
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>
var shutdownNotify = function(Notification) {
document.getElementById("flash").innerHTML = "<div class=\"info\">" + Notification + "{{ _(" The Web Interface will become unresponsive momentarily. Reload this page after the Pi has started up again.") }}</div>";
window.scrollTo(0,0);
}
</script>
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.9.3/min/dropzone.min.js"></script>
</head>
<body>
<div class="content">
<div class="header">
{% if env["auth_active"] %}
{% if env["username"] %}
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }} &#8211; <a href="/logout">{{ _("Log Out") }}</a></span>
<div align="center" class="logged_in">
{{ _("Logged in as <em>%(username)s</em>", username=env["username"]) }} - <a href="/logout">{{ _("Log Out") }}</a>
</div>
{% else %}
<span style="display: inline-block; width: 100%; color: white; background-color: red; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">
<div align="center" class="logged_out">
<form method="POST" action="/login">
<div>{{ _("Log In to Use Web Interface") }}</div>
<input type="text" name="username" placeholder="{{ _("Username") }}">
<input type="password" name="password" placeholder="{{ _("Password") }}">
<label for="username">{{ _("Username") }}</label>
<input type="text" name="username" id="username">
<label for="password">{{ _("Password") }}</label>
<input type="password" name="password" id="password">
<input type="submit" value="Login">
</form>
</span>
</div>
{% endif %}
{% else %}
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">{{ _("Web Interface Authentication Disabled") }} &#8211; {{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication") }}</span>
<div align="center" class="logged_out">
{{ _("Web Interface Authentication Disabled") }} - {{ _("See <a href=\"%(url)s\" target=\"_blank\">Wiki</a> for more information", url="https://github.com/akuker/RASCSI/wiki/Web-Interface#enable-authentication") }}
</div>
{% endif %}
<table width="100%" style="background-color: black;">
<tbody>
<tr align="center">
<td>
<a href="/">
<h1>{{ _("RaSCSI Reloaded Control Page") }}</h1>
</a>
</td>
</tr>
<tr>
<td style="color: white;">
hostname: {{ env["host"] }} ip: {{ env["ip_addr"] }}
</td>
</tr>
</tbody>
</table>
<div align="center">
<a href="/">
<h1>{{ _("RaSCSI Reloaded Control Page") }}</h1>
</a>
</div>
<div>
hostname: {{ env["host"] }} ip: {{ env["ip_addr"] }}
</div>
</div>
<div class="flash" id="flash">
{% for category, message in get_flashed_messages(with_categories=true) %}
@ -88,9 +84,27 @@
<div class="content">
{% block content %}{% endblock content %}
</div>
<div class="footer">
<center><tt>{{ _("RaSCSI Reloaded version: ") }}<strong>{{ version }} <a href="https://github.com/akuker/RASCSI/commit/{{ env["running_env"]["git"] }}" target="_blank">{{ env["running_env"]["git"][:7] }}</a></strong></tt></center>
<center><tt>{{ _("Pi environment: ") }}{{ env["running_env"]["env"] }}</tt></center>
<div align="center" class="footer">
<div>
{% if env["netatalk_configured"] == 1 %}
{{ _("The AppleShare server is running. No active connections.") }}
{% endif %}
{% if env["netatalk_configured"] == 2 %}
{{ _("%(value)d active AFP connection", value=(env["netatalk_configured"] - 1)) }}
{% elif env["netatalk_configured"] > 2 %}
{{ _("%(value)d active AFP connections", value=(env["netatalk_configured"] - 1)) }}
{% endif %}
</div>
<div>
{% if env["macproxy_configured"] %}
{{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }}
{% endif %}
</div>
<div>
{{ _("RaSCSI Reloaded version: ") }}<b>{{ env["version"] }} <a href="https://github.com/akuker/RASCSI/commit/{{ env["running_env"]["git"] }}" target="_blank">{{ env["running_env"]["git"][:7] }}</a></b>
</div>
<div>
{{ _("Pi environment: ") }}{{ env["running_env"]["env"] }}
</div>
</div>
</div>
</body>

View File

@ -1,52 +1,52 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("Detailed Info for Attached Devices") }}</h3>
<h2>{{ _("Detailed Info for Attached Devices") }}</h2>
{% for device in devices %}
<p>
<table border="black" cellpadding="3">
<table border="black" cellpadding="3" summary="Detailed information for attached devices">
<tr>
<td><i>{{ _("SCSI ID") }}</i></td>
<th scope="row">{{ _("SCSI ID") }}</th>
<td>{{ device["id"] }}</td>
</tr>
<tr>
<td><i>{{ _("LUN") }}</i></td>
<th scope="row">{{ _("LUN") }}</th>
<td>{{ device["unit"] }}</td>
</tr>
<tr>
<td><i>{{ _("Type") }}</i></td>
<th scope="row">{{ _("Type") }}</th>
<td>{{ device["device_type"] }}</td>
</tr>
<tr>
<td><i>{{ _("Status") }}</i></td>
<th scope="row">{{ _("Status") }}</th>
<td>{{ device["status"] }}</td>
</tr>
<tr>
<td><i>{{ _("File") }}</i></td>
<th scope="row">{{ _("File") }}</th>
<td>{{ device["image"] }}</td>
</tr>
<tr>
<td><i>{{ _("Parameters") }}</i></td>
<th scope="row">{{ _("Parameters") }}</th>
<td>{{ device["params"] }}</td>
</tr>
<tr>
<td><i>{{ _("Vendor") }}</i></td>
<th scope="row">{{ _("Vendor") }}</th>
<td>{{ device["vendor"] }}</td>
</tr>
<tr>
<td><i>{{ _("Product") }}</i></td>
<th scope="row">{{ _("Product") }}</th>
<td>{{ device["product"] }}</td>
</tr>
<tr>
<td><i>{{ _("Revision") }}</i></td>
<th scope="row">{{ _("Revision") }}</th>
<td>{{ device["revision"] }}</td>
</tr>
<tr>
<td><i>{{ _("Block Size") }}</i></td>
<th scope="row">{{ _("Block Size") }}</th>
<td>{{ device["block_size"] }}</td>
</tr>
<tr>
<td><i>{{ _("Image Size") }}</i></td>
<th scope="row">{{ _("Image Size") }}</th>
<td>{{ device["size"] }}</td>
</tr>
</table>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("Disk Image Details: %(file_name)s", file_name=file_name) }}</h3>
<h2>{{ _("Disk Image Details: %(file_name)s", file_name=file_name) }}</h2>
<p><pre>{{ diskinfo }}</pre></p>
<p><a href="/">{{ _("Go to Home") }}</a></p>

View File

@ -5,38 +5,30 @@
<p>{{ _("These device profiles are provided as-is with no guarantee to work equally to the actual physical device they are named after. You may need to provide appropirate device drivers and/or configuration parameters for them to function properly. If you would like to see data modified, or have additional devices to add to the list, please raise an issue ticket at <a href=\"%(url)s\">GitHub</a>.", url="https://github.com/akuker/RASCSI/issues") }}</p>
<h2>{{ _("Hard Disk Drives") }}</h2>
<table cellpadding="3" border="black">
<table cellpadding="3" border="black" summary="List of hard drives">
<tbody>
<tr>
<td><b>{{ _("Name") }}</b></td>
<td><b>{{ _("Size (MiB)") }}</b></td>
<td><b>{{ _("Description") }}</b></td>
<td><b>{{ _("Ref.") }}</b></td>
<td><b>{{ _("Action") }}</b></td>
<th scope="col">{{ _("Name") }}</th>
<th scope="col">{{ _("Size (MiB)") }}</th>
<th scope="col">{{ _("Description") }}</th>
<th scope="col">{{ _("Action") }}</th>
</tr>
{% for hd in drive_properties['hd_conf']|sort(attribute='name') %}
{% for hd in env['drive_properties']['hd_conf']|sort(attribute='name') %}
<tr>
<td style="text-align:center">{{ hd.name }}</td>
<td style="text-align:center">{{ hd.size_mb }}</td>
<td style="text-align:left">{{ hd.description }}</td>
<td style="text-align:left">
{% if hd.url != "" %}
<a href="{{ hd.url }}">{{ _("Link") }}</a>
{% else %}
-
{% endif %}
</td>
<td style="text-align:left">
<td align="center">
{% if hd.url != "" %}
<a href="{{ hd.url }}">{{ hd.name }}</a>
{% else %}
{{ hd.name }}
{% endif %}
</td>
<td align="center">{{ hd.size_mb }}</td>
<td>{{ hd.description }}</td>
<td>
<form action="/drive/create" method="post">
<input type="hidden" name="vendor" value="{{ hd.vendor }}">
<input type="hidden" name="product" value="{{ hd.product }}">
<input type="hidden" name="revision" value="{{ hd.revision }}">
<input type="hidden" name="blocks" value="{{ hd.blocks }}">
<input type="hidden" name="block_size" value="{{ hd.block_size }}">
{{ _("Size:") }} <input type="number" name="size" min="512" max="274877906944" step="512" value="{{ hd.size }}">{{ _("B") }}
<input type="hidden" name="file_type" value="{{ hd.file_type }}">
<label for="file_name">{{ _("Save as:") }}</label>
<input type="text" name="file_name" value="{{ hd.secure_name }}" required />.{{ hd.file_type }}
<input type="hidden" name="drive_name" value="{{ hd.name }}">
<label for="file_name_{{ hd.name }}">{{ _("Save as:") }}</label>
<input type="text" name="file_name" id="file_name_{{ hd.name }}" value="{{ hd.secure_name }}" required />.{{ hd.file_type }}
<input type="submit" value="{{ _("Create") }}" />
</form>
</td>
@ -49,41 +41,34 @@
<h2>{{ _("CD/DVD Drives") }}</h2>
<p><em>{{ _("This will create a properties file for the given CD-ROM or DVD image. No new image file will be created.") }}</em></p>
<table cellpadding="3" border="black">
<table cellpadding="3" border="black" summary="List of CD-ROM or DVD drives">
<tbody>
<tr>
<td><b>{{ _("Name") }}</b></td>
<td><b>{{ _("Size (MiB)") }}</b></td>
<td><b>{{ _("Description") }}</b></td>
<td><b>{{ _("Ref.") }}</b></td>
<td><b>{{ _("Action") }}</b></td>
<th scope="col">{{ _("Name") }}</th>
<th scope="col">{{ _("Description") }}</th>
<th scope="col">{{ _("Action") }}</th>
</tr>
{% for cd in drive_properties['cd_conf']|sort(attribute='name') %}
{% for cd in env['drive_properties']['cd_conf']|sort(attribute='name') %}
<tr>
<td style="text-align:center">{{ cd.name }}</td>
<td style="text-align:center">{{ cd.size_mb }}</td>
<td style="text-align:left">{{ cd.description }}</td>
<td style="text-align:left">
{% if cd.url != "" %}
<a href="{{ cd.url }}">{{ _("Link") }}</a>
{% else %}
-
{% endif %}
<td align="center">
{% if cd.url != "" %}
<a href="{{ cd.url }}">{{ cd.name }}</a>
{% else %}
{{ cd.name }}
{% endif %}
</td>
<td style="text-align:left">
<td>{{ cd.description }}</td>
<td>
<form action="/drive/cdrom" method="post">
<input type="hidden" name="vendor" value="{{ cd.vendor }}">
<input type="hidden" name="product" value="{{ cd.product }}">
<input type="hidden" name="revision" value="{{ cd.revision }}">
<input type="hidden" name="block_size" value="{{ cd.block_size }}">
<label for="file_name">{{ _("Create for:") }}</label>
<select type="select" name="file_name">
<input type="hidden" name="drive_name" value="{{ cd.name }}">
<label for="file_name_{{ cd.name }}">{{ _("Create for:") }}</label>
<select type="select" name="file_name" id="file_name_{{ cd.name }}">
{% for file in files|sort(attribute='name') %}
{% if file["name"].lower().endswith(cdrom_file_suffix) %}
<option value="{{ file["name"] }}">{{ file["name"].replace(base_dir, '') }}</option>
{% endif %}
{% endfor %}
</select>
{% if file["name"].lower().endswith(env['cd_suffixes']) %}
<option value="{{ file["name"] }}">{{ file["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% endfor %}
</select>
<input type="submit" value="{{ _("Create") }}" />
</form>
</td>
@ -95,38 +80,30 @@
<hr/>
<h2>{{ _("Removable Disk Drives") }}</h2>
<table cellpadding="3" border="black">
<table cellpadding="3" border="black" summary="List of removable disk drives">
<tbody>
<tr>
<td><b>{{ _("Name") }}</b></td>
<td><b>{{ _("Size (MiB)") }}</b></td>
<td><b>{{ _("Description") }}</b></td>
<td><b>{{ _("Ref.") }}</b></td>
<td><b>{{ _("Action") }}</b></td>
<th scope="col">{{ _("Name") }}</th>
<th scope="col">{{ _("Size (MiB)") }}</th>
<th scope="col">{{ _("Description") }}</th>
<th scope="col">{{ _("Action") }}</th>
</tr>
{% for rm in drive_properties['rm_conf']|sort(attribute='name') %}
{% for rm in env['drive_properties']['rm_conf']|sort(attribute='name') %}
<tr>
<td style="text-align:center">{{ rm.name }}</td>
<td style="text-align:center">{{ rm.size_mb }}</td>
<td style="text-align:left">{{ rm.description }}</td>
<td style="text-align:left">
{% if rm.url != "" %}
<a href="{{ rm.url }}">{{ _("Link") }}</a>
{% else %}
-
{% endif %}
<td align="center">
{% if rm.url != "" %}
<a href="{{ rm.url }}">{{ rm.name }}</a>
{% else %}
{{ rm.name }}
{% endif %}
</td>
<td style="text-align:left">
<td align="center">{{ rm.size_mb }}</td>
<td>{{ rm.description }}</td>
<td>
<form action="/drive/create" method="post">
<input type="hidden" name="vendor" value="{{ rm.vendor }}">
<input type="hidden" name="product" value="{{ rm.product }}">
<input type="hidden" name="revision" value="{{ rm.revision }}">
<input type="hidden" name="blocks" value="{{ rm.blocks }}">
<input type="hidden" name="block_size" value="{{ rm.block_size }}">
{{ _("Size:") }} <input type="number" name="size" min="512" max="274877906944" step="512" value="{{ rm.size }}">{{ _("B") }}
<input type="hidden" name="file_type" value="{{ rm.file_type }}">
<label for="file_name">{{ _("Save as:") }}</label>
<input type="text" name="file_name" value="{{ rm.secure_name }}" required />.{{ rm.file_type }}
<input type="hidden" name="drive_name" value="{{ rm.name }}">
<label for="file_name_{{ rm.name }}">{{ _("Save as:") }}</label>
<input type="text" name="file_name" id="file_name_{{ rm.name }}" value="{{ rm.secure_name }}" required />.{{ rm.file_type }}
<input type="submit" value="{{ _("Create") }}" />
</form>
</td>

View File

@ -6,18 +6,19 @@
{{ _("Current RaSCSI Configuration") }}
</summary>
<ul>
<li>{{ _("Displays the currently attached devices for each available SCSI ID.") }}</li>
<li>{{ _("Save and load device configurations, stored as json files in <tt>%(config_dir)s</tt>", config_dir=CFG_DIR) }}</tt></li>
<li>{{ _("Save and load device configurations, stored as json files in <tt>%(config_dir)s</tt>", config_dir=CFG_DIR) }}</li>
<li>{{ _("To have a particular device configuration load when RaSCSI starts, save it as <em>default</em>.") }}</li>
</ul>
</details>
<p><form action="/config/load" method="post">
<select name="name" required="" width="14">
<p>
<form action="/config/load" method="post">
<label for="config_load_name">{{ _("File name") }}</label>
<select name="name" id="config_load_name" required="" width="14">
{% if config_files %}
{% for config in config_files|sort %}
<option value="{{ config }}">
{{ config.replace(".json", '') }}
{{ config }}
</option>
{% endfor %}
{% else %}
@ -28,55 +29,59 @@
</select>
<input name="load" type="submit" value="{{ _("Load") }}" onclick="return confirm('{{ _("Detach all current device and Load configuration?") }}')">
<input name="delete" type="submit" value="{{ _("Delete") }}" onclick="return confirm('{{ _("Delete configuration file?") }}')">
</form></p>
</form>
</p>
<p><form action="/config/save" method="post">
<input name="name" placeholder="default" size="20">
<p>
<form action="/config/save" method="post">
<label for="config_save_name">{{ _("File name") }}</label>
<input name="name" id="config_save_name" placeholder="default" size="20">
.{{ CONFIG_FILE_SUFFIX }}
<input type="submit" value="{{ _("Save") }}">
</form></p>
</form>
</p>
<table border="black" cellpadding="3">
<table border="black" cellpadding="3" summary="List of attached devices">
<tbody>
<tr>
<td><b>{{ _("ID") }}</b></td>
<th scope="col">{{ _("ID") }}</th>
{% if units %}
<td><b>{{ _("LUN") }}</b></td>
<th scope="col">{{ _("LUN") }}</th>
{% endif %}
<td><b>{{ _("Type") }}</b></td>
<td><b>{{ _("Status") }}</b></td>
<td><b>{{ _("File") }}</b></td>
<td><b>{{ _("Product") }}</b></td>
<td><b>{{ _("Actions") }}</b></td>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Parameters") }}</th>
<th scope="col">{{ _("Product") }}</th>
<th scope="col">{{ _("Actions") }}</th>
</tr>
{% for device in devices %}
{% for device in devices | sort(attribute='id') %}
<tr>
{% if device["id"] not in reserved_scsi_ids %}
<td style="text-align:center">{{ device.id }}</td>
<td align="center">{{ device.id }}</td>
{% if units %}
<td style="text-align:center">{{ device.unit }}</td>
<td align="center">{{ device.unit }}</td>
{% endif %}
<td style="text-align:center">{{ device.device_type }}</td>
<td style="text-align:center">{{ device.status }}</td>
<td style="text-align:left">
<td align="center">{{ device.device_name }}</td>
<td>
{% if "No Media" in device.status %}
<form action="/scsi/attach" method="post">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="unit" type="hidden" value="{{ device.unit }}">
<input name="type" type="hidden" value="{{ device.device_type }}">
<input name="file_size" type="hidden" value="{{ device.size }}">
<select type="select" name="file_name">
<label for="device_list_file_name_{{ device.id }}_{{ device.unit }}">{{ _("File name") }}</label>
<select type="select" name="file_name" id="device_list_file_name_{{ device.id }}_{{ device.unit }}">
{% for f in files|sort(attribute='name') %}
{% if device.device_type == "SCCD" %}
{% if f["name"].lower().endswith(cdrom_file_suffix) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(base_dir, '') }}</option>
{% if f["name"].lower().endswith(env['cd_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif device.device_type == "SCRM" %}
{% if f["name"].lower().endswith(removable_file_suffix) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(base_dir, '') }}</option>
{% if f["name"].lower().endswith(env['rm_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% elif device.device_type == "SCMO" %}
{% if f["name"].lower().endswith(mo_file_suffix) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(base_dir, '') }}</option>
{% if f["name"].lower().endswith(env['mo_suffixes']) %}
<option value="{{ f["name"] }}">{{ f["name"].replace(env["image_dir"], '') }}</option>
{% endif %}
{% endif %}
{% endfor %}
@ -84,16 +89,32 @@
<input type="submit" value="{{ _("Attach") }}">
</form>
{% else %}
{% if device.params %}
{% for key in device.params %}
{% if key == "interface" %}
({{device.params[key]}})
{% elif key == "timeout" %}
({{key}}:{{device.params[key]}})
{% else %}
{{device.params[key]}}
{% endif %}
{% endfor %}
{% elif device.file %}
{{ device.file }}
{% endif %}
</td>
{% if device.vendor == "RaSCSI" %}
<td style="text-align:center">{{ device.product }}</td>
{% else %}
<td style="text-align:center">{{ device.vendor }} {{ device.product }}</td>
{% endif %}
<td style="text-align:center">
{% if device.device_type != "-" %}
</td>
<td align="center">
{% if device.vendor != "RaSCSI" %}
{{ device.vendor }}
{% endif %}
{{ device.product }}
{% if device.vendor != "RaSCSI" %}
{{ device.revision }}
{% endif %}
</td>
<td align="center">
{% if device.id in scsi_ids["occupied_ids"] %}
{% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %}
<form action="/scsi/eject" method="post" onsubmit="return confirm('{{ _("Eject Disk? WARNING: On Mac OS, eject the Disk in the Finder instead!") }}')">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
@ -106,20 +127,19 @@
<input name="unit" type="hidden" value="{{ device.unit }}">
<input type="submit" value="{{ _("Detach") }}">
</form>
{% else %}
<form action="/scsi/reserve" method="post" onsubmit="var memo = prompt('{{ _("Enter a memo for this reservation") }}'); if (memo === null) event.preventDefault(); document.getElementById('memo_{{ device.id }}').value = memo;">
{% else %}
<form action="/scsi/reserve" method="post" onsubmit="var memo = prompt('{{ _("Enter a memo for this reservation") }}'); if (memo === null) event.preventDefault(); document.getElementById('memo_{{ device.id }}').value = memo;">
<input name="scsi_id" type="hidden" value="{{ device.id }}">
<input name="memo" id="memo_{{ device.id }}" type="hidden" value="">
<input type="submit" value="{{ _("Reserve") }}">
</form>
{% endif %}
{% endif %}
</td>
{% else %}
<td class="inactive">{{ device.id }}</td>
{% if units %}
<td class="inactive"></td>
{% endif %}
<td class="inactive"></td>
<td class="inactive">{{ _("Reserved ID") }}</td>
<td class="inactive">{{ RESERVATIONS[device.id] }}</td>
<td class="inactive"></td>
@ -135,20 +155,14 @@
</tbody>
</table>
<table style="border: none;" cellpadding="3">
<tr style="border: none;">
<td style="border: none;">
<form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
<input type="submit" value="{{ _("Detach All Devices") }}">
</form>
</td>
<td style="border: none;">
<form action="/scsi/info" method="post">
<input type="submit" value="{{ _("Show Device Info") }}">
</form>
</td>
</tr>
</table>
<p>
<form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
<input type="submit" value="{{ _("Detach All Devices") }}">
</form>
<form action="/scsi/info" method="post">
<input type="submit" value="{{ _("Show Device Info") }}">
</form>
</p>
<hr/>
@ -157,10 +171,9 @@
{{ _("Image File Management") }}
</summary>
<ul>
<li>{{ _("Manage image files in the active RaSCSI image directory: <tt>%(directory)s</tt> with a scan depth of %(scan_depth)s.", directory=base_dir, scan_depth=scan_depth) }}</li>
<li>{{ _("Select a valid SCSI ID and <a href=\"%(url)s\">LUN</a> to attach to. Unless you know what you're doing, always use LUN 0.", url="https://en.wikipedia.org/wiki/Logical_unit_number") }}
<li>{{ _("Manage image files in the active RaSCSI image directory: <tt>%(directory)s</tt> with a scan depth of %(scan_depth)s.", directory=env["image_dir"], scan_depth=scan_depth) }}</li>
<li>{{ _("Select a valid SCSI ID and <a href=\"%(url)s\" target=\"_blank\">LUN</a> to attach to. Unless you know what you're doing, always use LUN 0.", url="https://en.wikipedia.org/wiki/Logical_unit_number") }}
</li>
<li>{{ _("If RaSCSI was unable to detect the media type associated with the image, you get to choose the type from the dropdown.") }}</li>
<li>
{{ _("Recognized image file types:") }}
{% set comma = joiner(", ") %}
@ -174,12 +187,12 @@
</ul>
</details>
<table border="black" cellpadding="3">
<table border="black" cellpadding="3" summary="List of files in the image directory">
<tbody>
<tr style="font-weight: bold;">
<td>{{ _("File") }}</td>
<td>{{ _("Size") }}</td>
<td>{{ _("Parameters and Actions") }}</td>
<tr>
<th scope="col">{{ _("File") }}</th>
<th scope="col">{{ _("Size") }}</th>
<th scope="col">{{ _("Actions") }}</th>
</tr>
{% for file in files|sort(attribute='name') %}
<tr>
@ -189,12 +202,12 @@
<summary>
{{ file["name"] }}
</summary>
<ul style="list-style: none;">
<ul class="inline_list">
{% for key in file["prop"] %}
<li>{{ key }}: {{ file['prop'][key] }}</li>
{% endfor %}
<form action="/files/download" method="post">
<input name="file" type="hidden" value="{{ CFG_DIR }}/{{ file['name'].replace(base_dir, '') }}.{{ PROPERTIES_SUFFIX }}">
<input name="file" type="hidden" value="{{ CFG_DIR }}/{{ file['name'].replace(env['image_dir'], '') }}.{{ PROPERTIES_SUFFIX }}">
<input type="submit" value="{{ _("Properties File") }} &#8595;">
</form>
</ul>
@ -206,7 +219,7 @@
<summary>
{{ file["name"] }}
</summary>
<ul style="list-style: none;">
<ul class="inline_list">
{% for member in file["archive_contents"] %}
{% if not member["is_properties_file"] %}
<li>
@ -220,7 +233,7 @@
<input type="submit" value="{{ _("Extract") }}" onclick="processNotify('{{ _("Extracting a single file...") }}')">
</form>
</summary>
<ul style="list-style: none;">
<ul class="inline_list">
<li>{{ member["related_properties_file"] }}</li>
</ul>
</details>
@ -241,9 +254,9 @@
{% else %}
<td>{{ file["name"] }}</td>
{% endif %}
<td style="text-align:center">
<td align="center">
<form action="/files/download" method="post">
<input name="file" type="hidden" value="{{ base_dir }}/{{ file['name'] }}">
<input name="file" type="hidden" value="{{ file['name'] }}">
<input type="submit" value="{{ file['size_mb'] }} {{ _("MiB") }} &#8595;">
</form>
</td>
@ -262,23 +275,24 @@
<form action="/scsi/attach" method="post">
<input name="file_name" type="hidden" value="{{ file['name'] }}">
<input name="file_size" type="hidden" value="{{ file['size'] }}">
<label for="id">{{ _("ID") }}</label>
<select name="scsi_id">
{% for id in scsi_ids %}
<option name="id" value="{{id}}"{% if id == recommended_id %} selected{% endif %}>
<label for="image_list_scsi_id_{{ file["name"] }}">{{ _("ID") }}</label>
<select name="scsi_id" id="image_list_scsi_id_{{ file["name"] }}">
{% for id in scsi_ids["valid_ids"] %}
<option name="id" value="{{id}}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="unit">{{ _("LUN") }}</label>
<input name="unit" type="number" value="0" min="0" max="31" step="1">
<label for="image_list_unit_{{ file["name"] }}">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="image_list_unit_{{ file["name"] }}" type="number" value="0" min="0" max="31" step="1" size="3">
{% if file["detected_type"] != "UNDEFINED" %}
<input name="type" type="hidden" value="{{ file['detected_type'] }}">
{{ file['detected_type_name'] }}
{% else %}
<select name="type">
<label for="image_list_type_{{ file["name"] }}">{{ _("Type") }}</label>
<select name="type" id="image_list_type_{{ file["name"] }}">
<option selected disabled value="">
{{ _("Select media type") }}
{{ _("Unknown") }}
</option>
{% for key, value in device_types.items() %}
{% if key in DISK_DEVICE_TYPES %}
@ -326,27 +340,23 @@
{{ _("Attach Peripheral Device") }}
</summary>
<ul>
<li>{{ _("Before using a networking device, it is recommended to run easyinstall.sh from the command line to configure your Raspberry Pi.") }}
</li>
<ul>
{% if bridge_configured %}
<li>{{ _("The <tt>rascsi_bridge</tt> network bridge is active and ready to be used by an emulated network adapter!") }}</li>
{% else %}
{% else %}
<li>{{ _("Please configure the <tt>rascsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
{% endif %}
<li>{{ _("If you have a DHCP setup, choose only the interface you have configured the bridge with. You can ignore the inet field when attaching.") }}</li>
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\">Macproxy</a>.", url="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy") }}</li>
<li>{{ _("To browse the modern web, install a vintage web proxy such as <a href=\"%(url)s\" target=\"_blank\">Macproxy</a>.", url="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy") }}</li>
</li>
</ul>
<li>{{ _("Read more about <a href=\"%(url)s\">supported device types</a> on the wiki.", url="https://github.com/akuker/RASCSI/wiki/Supported-Device-Types") }}
<li>{{ _("Read more about <a href=\"%(url)s\" target=\"_blank\">supported device types</a> on the wiki.", url="https://github.com/akuker/RASCSI/wiki/Supported-Device-Types") }}
</li>
</ul>
</details>
<table border="black" cellpadding="3">
<tr style="font-weight: bold;">
<td>{{ _("Device") }}</td>
<td>{{ _("Code") }}</td>
<td>{{ _("Parameters and Actions") }}</td>
<table border="black" cellpadding="3" summary="List of peripheral devices">
<tr>
<th scope="col">{{ _("Device") }}</th>
<th scope="col">{{ _("Key") }}</th>
<th scope="col">{{ _("Parameters and Actions") }}</th>
</tr>
{% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %}
<tr>
@ -360,11 +370,11 @@
<form action="/scsi/attach_device" method="post">
<input name="type" type="hidden" value="{{ type }}">
{% for key, value in device_types[type]["params"] | dictsort %}
<label for="{{ key }}">{{ key }}:</label>
<label for="param_{{ type }}_{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="{{ key }}" type="number" value="{{ value }}">
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="interface">
<select name="param_{{ key }}" id="param_{{ type }}_{{ key }}">
{% for if in netinfo["ifs"] %}
<option value="{{ if }}">
{{ if }}
@ -372,31 +382,31 @@
{% endfor %}
</select>
{% else %}
<input name="{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
<input name="param_{{ key }}" id="param_{{ type }}_{{ key }}" type="text" size="{{ value|length }}" placeholder="{{ value }}">
{% endif %}
{% endfor %}
{% if type in REMOVABLE_DEVICE_TYPES %}
<label for="drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name">
<label for="{{ type }}_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="{{ type }}_drive_name">
<option value="">
{{ _("None") }}
</option>
{% if type == "SCCD" %}
{% for drive in drive_properties["cd_conf"] | sort(attribute='name') %}
{% for drive in env["drive_properties"]["cd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCRM" %}
{% for drive in drive_properties["rm_conf"] | sort(attribute='name') %}
{% for drive in env["drive_properties"]["rm_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
{% endif %}
{% if type == "SCMO" %}
{% for drive in drive_properties["mo_conf"] | sort(attribute='name') %}
{% for drive in env["drive_properties"]["mo_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
@ -404,16 +414,16 @@
{% endif %}
</select>
{% endif %}
<label for="scsi_id">{{ _("SCSI ID:") }}</label>
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{ id }}"{% if id == recommended_id %} selected{% endif %}>
<label for="{{ type }}_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="{{ type }}_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="unit">{{ _("LUN") }}</label>
<input name="unit" type="number" value="0" min="0" max="31" step="1">
<label for="{{ type }}_unit">{{ _("LUN") }}</label>
<input class="lun" name="unit" id="{{ type }}_unit" type="number" value="0" min="0" max="31" step="1" size="3">
<input type="submit" value="{{ _("Attach") }}">
</form>
</td>
@ -429,25 +439,20 @@
<ul>
<li>{{ _("The largest file size accepted in this form is %(max_file_size)s MiB. Use other file transfer means for larger files.", max_file_size=max_file_size) }}</li>
<li>{{ _("File uploads will progress only if you stay on this page. If you navigate away before the transfer is completed, you will end up with an incomplete file.") }}</li>
<li>{{ _("Install <a href=\"%(url)s\">Netatalk</a> to use the AFP File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
<li>{{ _("Install <a href=\"%(url)s\" target=\"_blank\">Netatalk</a> to use the AFP File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
</ul>
</details>
<table style="border: none;">
<tr style="border: none;">
<td style="border: none; vertical-align:top;">
<form name="dropper" action="/files/upload" method="post" class="dropzone dz-clickable" enctype="multipart/form-data" id="dropper">
<p>
<label for="destination">{{ _("Target directory:") }}</label>
<select name="destination">
<option value="images">{{ base_dir }}</option>
<option value="afp">{{ AFP_DIR }}</option>
</select>
</p>
</form>
</td>
</tr>
</table>
<form name="dropper" action="/files/upload" method="post" class="dropzone dz-clickable" enctype="multipart/form-data" id="dropper">
<p>
<label for="upload_destination">{{ _("Target directory:") }}</label>
<select name="destination" id="upload_destination">
<option value="images">Images - {{ env["image_dir"] }}</option>
<option value="afp">AppleShare - {{ AFP_DIR }}</option>
</select>
</p>
</form>
<script type="application/javascript">
Dropzone.options.dropper = {
paramName: 'file',
@ -484,27 +489,20 @@
{{ _("Download File from the Web") }}
</summary>
<ul>
<li>{{ _("Choose the desination directory and download a file from the Web to your Raspberry Pi.") }}</li>
<li>{{ _("Install <a href=\"%(url)s\">Netatalk</a> to use the AFP File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
<li>{{ _("Install <a href=\"%(url)s\" target=\"_blank\">Netatalk</a> to use the AFP File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/files/download_url" method="post">
<label for="destination">{{ _("Target directory:") }}</label>
<select name="destination">
<option value="images">{{ base_dir }}</option>
<option value="afp">{{ AFP_DIR }}</option>
</select>
<label for="url">{{ _("URL:") }}</label>
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File...") }}')">
</form>
</td>
</tr>
</table>
<form action="/files/download_url" method="post">
<label for="download_destination">{{ _("Target directory:") }}</label>
<select name="destination" id="download_destination">
<option value="images">Images - {{ env["image_dir"] }}</option>
<option value="afp">AppleShare - {{ AFP_DIR }}</option>
</select>
<label for="download_url">{{ _("URL:") }}</label>
<input name="url" id="download_url" required="" type="url">
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File...") }}')">
</form>
<hr/>
@ -518,46 +516,41 @@
<li>{{ _("If the downloaded file is a zip archive, we will attempt to unzip it and store the resulting files.") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<label for="scsi_id">{{ _("SCSI ID:") }}</label>
<form action="/files/download_to_iso" method="post">
<select name="scsi_id">
{% for id in scsi_ids %}
<option value="{{ id }}"{% if id == recommended_id %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<label for="url">{{ _("URL:") }}</label>
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
<label for="type">{{ _("Type:") }}</label>
<select name="type">
<option value="-hfs">
HFS
</option>
<option value="-iso-level 1">
ISO-9660 Level 1
</option>
<option value="-iso-level 2">
ISO-9660 Level 2
</option>
<option value="-iso-level 3">
ISO-9660 Level 3
</option>
<option value="-J">
Joliet
</option>
<option value="-r">
Rock Ridge
</option>
</select>
<input type="submit" value="{{ _("Download and Mount CD-ROM image") }}" onclick="processNotify('{{ _("Downloading File and generating CD-ROM image...") }}')">
</form>
</td>
</tr>
</table>
<form action="/files/download_to_iso" method="post">
<label for="iso_url">{{ _("URL:") }}</label>
<input name="url" id="iso_url" required="" type="url">
<label for="iso_type">{{ _("Type:") }}</label>
<select name="type" id="iso_type">
<option value="-hfs">
HFS
</option>
<option value="-iso-level 1">
ISO-9660 Level 1
</option>
<option value="-iso-level 2">
ISO-9660 Level 2
</option>
<option value="-iso-level 3">
ISO-9660 Level 3
</option>
<option value="-J">
Joliet
</option>
<option value="-r">
Rock Ridge
</option>
</select>
<label for="iso_scsi_id">{{ _("ID") }}</label>
<select name="scsi_id" id="iso_scsi_id">
{% for id in scsi_ids["valid_ids"] %}
<option value="{{ id }}"{% if id == scsi_ids["recommended_id"] %} selected{% endif %}>
{{ id }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Download and Mount CD-ROM image") }}" onclick="processNotify('{{ _("Downloading File and generating CD-ROM image...") }}')">
</form>
<hr/>
@ -566,46 +559,37 @@
{{ _("Create Empty Disk Image File") }}
</summary>
<ul>
<li>{{ _("The Generic Hard Disk image type is recommended for most computer platforms.") }}</li>
<li>{{ _("The Apple image type improves compatibility with Apple Macintosh computers.") }}</li>
<li>{{ _("The NEC image type improves compatibility with NEC PC-98 computers.") }}</li>
<li>{{ _("The SCSI-1 image type makes RaSCSI behave like a legacy SCSI-1 device, which may improve compatibility with very old SCSI controllers.") }}</li>
<li>{{ _("The Removable Disk image type can be used with SCSI floppy drives, SyQuest drives, Zip drives, etc.") }}</li>
<li>{{ _("Please refer to <a href=\"%(url)s\" target=\"_blank\">wiki documentation</a> to learn more about the supported image file types.", url="https://github.com/akuker/RASCSI/wiki/Supported-Device-Types#image-types") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/files/create" method="post">
<label for="file_name">{{ _("File Name:") }}</label>
<input name="file_name" placeholder="{{ _("File Name") }}" required="" type="text">
<label for="type">{{ _("Type:") }}</label>
<select name="type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="size">{{ _("Size:") }}</label>
<input name="size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<input type="submit" value="{{ _("Create") }}">
</form>
</td>
</tr>
</table>
<hr/>
<form action="/files/create" method="post">
<label for="image_create_file_name">{{ _("File Name:") }}</label>
<input name="file_name" id="image_create_file_name" required="" type="text">
<label for="image_create_type">{{ _("Type:") }}</label>
<select name="type" id="image_create_type">
{% for key, value in image_suffixes_to_create.items() %}
<option value="{{ key }}">
{{ value }} [.{{ key }}]
</option>
{% endfor %}
</select>
<label for="image_create_size">{{ _("Size:") }}</label>
<input name="size" id="image_create_size" type="number" placeholder="{{ _("MiB") }}" min="1" max="262144" required>
<label for="image_create_drive_name">{{ _("Masquerade as:") }}</label>
<select name="drive_name" id="image_create_drive_name">
<option value="">
{{ _("None") }}
</option>
{% for drive in env["drive_properties"]["hd_conf"] | sort(attribute='name') %}
<option value="{{ drive.name }}">
{{ drive.name }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Create") }}">
</form>
<details>
<summary class="heading">
{{ _("Create Named Drive") }}
</summary>
<ul>
<li>{{ _("Create pairs of images and properties files from a list of real-life drives.") }}</li>
<li>{{ _("This will make RaSCSI use certain vendor strings and block sizes that may improve compatibility with certain systems.") }}</li>
</ul>
</details>
<p><a href="/drive/list">{{ _("Create a named disk image that mimics real-life drives") }}</a></p>
<hr/>
@ -615,67 +599,49 @@
{{ _("Logging") }}
</summary>
<ul>
<li>{{ _("Fetch a certain number of lines of system logs with the given scope.") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/logs/show" method="post">
<label for="lines">{{ _("Log Lines:") }}</label>
<input name="lines" type="number" value="200" min="0" max="99999" step="100">
<label for="scope">{{ _("Scope:") }}</label>
<select name="scope">
<option value="">
{{ _("All logs") }}
</option>
<option value="rascsi">
rascsi
</option>
<option value="rascsi-web">
rascsi-web
</option>
<option value="rascsi-oled">
rascsi-oled
</option>
<option value="rascsi-ctrlboard">
rascsi-ctrlboard
</option>
</select>
<input type="submit" value="{{ _("Show Logs") }}">
</form>
</td>
</tr>
</table>
<hr/>
<details>
<summary class="heading">
{{ _("Server Log Level") }}
</summary>
<ul>
<li>{{ _("Change the log level of the RaSCSI backend process.") }}</li>
<li>{{ _("The current dropdown selection indicates the active log level.") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/logs/level" method="post">
<label for="level">{{ _("Log Level:") }}</label>
<select name="level">
{% for level in log_levels %}
<option value="{{ level }}"{% if level == current_log_level %} selected{% endif %}>
{{ level }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Set Log Level") }}">
</form>
</td>
</tr>
</table>
<div>
<form action="/logs/show" method="post">
<label for="log_lines">{{ _("Log Lines:") }}</label>
<input name="lines" id="log_lines" type="number" value="200" min="0" max="99999" step="100">
<label for="log_scope">{{ _("Scope:") }}</label>
<select name="scope" id="log_scope">
<option value="">
{{ _("All logs") }}
</option>
<option value="rascsi">
rascsi
</option>
<option value="rascsi-web">
rascsi-web
</option>
<option value="rascsi-oled">
rascsi-oled
</option>
<option value="rascsi-ctrlboard">
rascsi-ctrlboard
</option>
</select>
<input type="submit" value="{{ _("Show Logs") }}">
</form>
</div>
<div>
<form action="/logs/level" method="post">
<label for="log_level">{{ _("Log Level:") }}</label>
<select name="level" id="log_level">
{% for level in log_levels %}
<option value="{{ level }}"{% if level == current_log_level %} selected{% endif %}>
{{ level }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Set Log Level") }}">
</form>
</div>
<hr/>
@ -687,23 +653,18 @@
<li>{{ _("Change the Web Interface language.") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/language" method="post">
<label for="language">{{ _("Language:") }}</label>
<select name="locale">
{% for locale in locales %}
<option value="{{ locale.language }}">
{{ locale.language }} - {{ locale.display_name }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Change Language") }}">
</form>
</td>
</tr>
</table>
<form action="/language" method="post">
<label for="locale">{{ _("Language:") }}</label>
<select name="locale" id="locale">
{% for locale in locales %}
<option value="{{ locale.language }}">
{{ locale.language }} - {{ locale.display_name }}
</option>
{% endfor %}
</select>
<input type="submit" value="{{ _("Change Language") }}">
</form>
<hr/>
@ -712,39 +673,19 @@
{{ _("Raspberry Pi Operations") }}
</summary>
<ul>
<li>{{ _("Reboot or shut down the Raspberry Pi that RaSCSI is running on.") }}</li>
<li>{{ _("IMPORTANT: Always shut down the Pi before turning off the power. Failing to do so may lead to data loss.") }}</li>
</ul>
</details>
<table style="border: none">
<tr style="border: none">
<td style="border: none; vertical-align:top;">
<form action="/pi/reboot" method="post" onclick="if (confirm('{{ _("Reboot the Raspberry Pi?") }}')) shutdownNotify('{{ _("Rebooting the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Reboot Raspberry Pi") }}">
</form>
</td>
<td style="border: none; vertical-align:top;">
<form action="/pi/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the Raspberry Pi?") }}')) shutdownNotify('{{ _("Shutting down the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Shut Down Raspberry Pi") }}">
</form>
</td>
</tr>
</table>
<center><tt>
{% if netatalk_configured == 1 %}
{{ _("The AppleShare server is running. No active connections.") }}
{% endif %}
{% if netatalk_configured == 2 %}
{{ _("%(value)d active AFP connection", value=(netatalk_configured - 1)) }}
{% elif netatalk_configured > 2 %}
{{ _("%(value)d active AFP connections", value=(netatalk_configured - 1)) }}
{% endif %}
</center></tt>
<center><tt>
{% if macproxy_configured %}
<center><tt>{{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=env['ip_addr']) }}</tt></center>
{% endif %}
</center></tt>
<form action="/pi/reboot" method="post" onclick="if (confirm('{{ _("Reboot the Raspberry Pi?") }}')) shutdownNotify('{{ _("Rebooting the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Reboot Raspberry Pi") }}">
</form>
<form action="/pi/shutdown" method="post" onclick="if (confirm('{{ _("Shut down the Raspberry Pi?") }}')) shutdownNotify('{{ _("Shutting down the Raspberry Pi...") }}'); else event.preventDefault();">
<input type="submit" value="{{ _("Shut Down Raspberry Pi") }}">
</form>
<hr/>
<a href="/sys/manpage?app=rascsi"><p>{{ _("Read the RaSCSI Manual") }}</p></a>
{% endblock content %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %}
{% block content %}
<h3>{{ _("System Logs: %(scope)s %(lines)s lines", scope=scope, lines=lines) }}</h3>
<h2>{{ _("System Logs: %(scope)s %(lines)s lines", scope=scope, lines=lines) }}</h2>
<p><pre>{{ logs }}</pre></p>
<p><a href="/">{{ _("Go to Home") }}</a></p>

View File

@ -0,0 +1,8 @@
{% extends "base.html" %}
{% block content %}
<h2>{{ _("Manual for %(app)s", app=app) }}</h2>
{{ manpage | safe }}
<p><a href="/">{{ _("Go to Home") }}</a></p>
{% endblock content %}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,6 @@ from flask import (
flash,
url_for,
redirect,
send_file,
send_from_directory,
make_response,
session,
@ -52,11 +51,13 @@ from web_utils import (
get_device_name,
map_image_file_descriptions,
format_drive_properties,
get_properties_by_drive_name,
auth_active,
is_bridge_configured,
upload_with_dropzonejs,
)
from settings import (
WEB_DIR,
AFP_DIR,
MAX_FILE_SIZE,
DEFAULT_CONFIG,
@ -73,6 +74,7 @@ def get_env_info():
"""
Get information about the app/host environment
"""
server_info = ractl_cmd.get_server_info()
ip_addr, host = sys_cmd.get_ip_and_host()
if "username" in session:
@ -87,6 +89,15 @@ def get_env_info():
"ip_addr": ip_addr,
"host": host,
"free_disk_space": int(sys_cmd.disk_space()["free"] / 1024 / 1024),
"locale": get_locale(),
"version": server_info["version"],
"image_dir": server_info["image_dir"],
"netatalk_configured": sys_cmd.running_proc("afpd"),
"macproxy_configured": sys_cmd.running_proc("macproxy"),
"cd_suffixes": tuple(server_info["sccd"]),
"rm_suffixes": tuple(server_info["scrm"]),
"mo_suffixes": tuple(server_info["scmo"]),
"drive_properties": format_drive_properties(APP.config["RASCSI_DRIVE_PROPERTIES"]),
}
@ -139,7 +150,7 @@ def get_locale():
language = session["language"]
except KeyError:
language = ""
logging.warning("The default locale could not be detected. Falling back to English.")
logging.info("The default locale could not be detected. Falling back to English.")
if language:
return language
# Hardcoded fallback to "en" when the user agent does not send an accept-language header
@ -195,7 +206,7 @@ def index():
units += int(device["unit"])
reserved_scsi_ids = server_info["reserved_ids"]
scsi_ids, recommended_id = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids)
scsi_ids = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids)
formatted_devices = sort_and_format_devices(devices["device_list"])
image_suffixes_to_create = map_image_file_descriptions(
@ -218,49 +229,31 @@ def index():
server_info["sccd"]
)
try:
drive_properties = format_drive_properties(APP.config["RASCSI_DRIVE_PROPERTIES"])
except:
drive_properties = {
"hd_conf": [],
"cd_conf": [],
"rm_conf": [],
"mo_conf": [],
}
return response(
template="index.html",
locales=get_supported_locales(),
netinfo=ractl_cmd.get_network_info(),
bridge_configured=sys_cmd.is_bridge_setup(),
netatalk_configured=sys_cmd.running_proc("afpd"),
macproxy_configured=sys_cmd.running_proc("macproxy"),
devices=formatted_devices,
attached_images=attached_images,
files=extended_image_files,
config_files=config_files,
base_dir=server_info["image_dir"],
device_types=device_types,
scan_depth=server_info["scan_depth"],
CFG_DIR=CFG_DIR,
AFP_DIR=AFP_DIR,
scsi_ids=scsi_ids,
recommended_id=recommended_id,
attached_images=attached_images,
units=units,
reserved_scsi_ids=reserved_scsi_ids,
RESERVATIONS=RESERVATIONS,
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
version=server_info["version"],
log_levels=server_info["log_levels"],
current_log_level=server_info["current_log_level"],
netinfo=ractl_cmd.get_network_info(),
device_types=device_types,
scsi_ids=scsi_ids,
units=units,
reserved_scsi_ids=reserved_scsi_ids,
image_suffixes_to_create=image_suffixes_to_create,
valid_image_suffixes=valid_image_suffixes,
cdrom_file_suffix=tuple(server_info["sccd"]),
removable_file_suffix=tuple(server_info["scrm"]),
mo_file_suffix=tuple(server_info["scmo"]),
drive_properties=drive_properties,
max_file_size=int(int(MAX_FILE_SIZE) / 1024 / 1024),
RESERVATIONS=RESERVATIONS,
CFG_DIR=CFG_DIR,
AFP_DIR=AFP_DIR,
PROPERTIES_SUFFIX=PROPERTIES_SUFFIX,
ARCHIVE_FILE_SUFFIXES=ARCHIVE_FILE_SUFFIXES,
CONFIG_FILE_SUFFIX=CONFIG_FILE_SUFFIX,
REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(),
DISK_DEVICE_TYPES=ractl_cmd.get_disk_device_types(),
PERIPHERAL_DEVICE_TYPES=ractl_cmd.get_peripheral_device_types(),
@ -280,25 +273,10 @@ def drive_list():
"""
Sets up the data structures and kicks off the rendering of the drive list page
"""
try:
drive_properties = format_drive_properties(APP.config["RASCSI_DRIVE_PROPERTIES"])
except:
drive_properties = {
"hd_conf": [],
"cd_conf": [],
"rm_conf": [],
"mo_conf": [],
}
server_info = ractl_cmd.get_server_info()
return response(
template="drives.html",
files=file_cmd.list_images()["files"],
base_dir=server_info["image_dir"],
drive_properties=drive_properties,
version=server_info["version"],
cdrom_file_suffix=tuple(server_info["sccd"]),
)
@ -311,10 +289,9 @@ def login():
password = request.form["password"]
groups = [g.gr_name for g in getgrall() if username in g.gr_mem]
if AUTH_GROUP in groups:
if authenticate(str(username), str(password)):
session["username"] = request.form["username"]
return response(env=get_env_info())
if AUTH_GROUP in groups and authenticate(str(username), str(password)):
session["username"] = request.form["username"]
return response(env=get_env_info())
return response(error=True, status_code=401, message=_(
"You must log in with valid credentials for a user in the '%(group)s' group",
@ -358,34 +335,36 @@ def drive_create():
"""
Creates the image and properties file pair
"""
vendor = request.form.get("vendor")
product = request.form.get("product")
revision = request.form.get("revision")
block_size = request.form.get("block_size")
size = request.form.get("size")
file_type = request.form.get("file_type")
drive_name = request.form.get("drive_name")
file_name = request.form.get("file_name")
full_file_name = file_name + "." + file_type
properties = get_properties_by_drive_name(
APP.config["RASCSI_DRIVE_PROPERTIES"],
drive_name
)
# Creating the image file
process = file_cmd.create_new_image(file_name, file_type, size)
process = file_cmd.create_new_image(
file_name,
properties["file_type"],
properties["size"],
)
if not process["status"]:
return response(error=True, message=process["msg"])
# Creating the drive properties file
prop_file_name = f"{file_name}.{file_type}.{PROPERTIES_SUFFIX}"
properties = {
"vendor": vendor,
"product": product,
"revision": revision,
"block_size": block_size,
}
full_file_name = f"{file_name}.{properties['file_type']}"
prop_file_name = f"{full_file_name}.{PROPERTIES_SUFFIX}"
process = file_cmd.write_drive_properties(prop_file_name, properties)
process = ReturnCodeMapper.add_msg(process)
if not process["status"]:
return response(error=True, message=process["msg"])
return response(message=_("Image file created: %(file_name)s", file_name=full_file_name))
# TODO: Refactor the return messages into one string
return response(message=[
(_("Image file created: %(file_name)s", file_name=full_file_name), "success"),
(process["msg"], "success"),
])
@APP.route("/drive/cdrom", methods=["POST"])
@ -394,20 +373,15 @@ def drive_cdrom():
"""
Creates a properties file for a CD-ROM image
"""
vendor = request.form.get("vendor")
product = request.form.get("product")
revision = request.form.get("revision")
block_size = request.form.get("block_size")
drive_name = request.form.get("drive_name")
file_name = request.form.get("file_name")
# Creating the drive properties file
file_name = f"{file_name}.{PROPERTIES_SUFFIX}"
properties = {
"vendor": vendor,
"product": product,
"revision": revision,
"block_size": block_size,
}
properties = get_properties_by_drive_name(
APP.config["RASCSI_DRIVE_PROPERTIES"],
drive_name
)
process = file_cmd.write_drive_properties(file_name, properties)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
@ -450,7 +424,8 @@ def config_load():
return response(error=True, message=process["msg"])
if "delete" in request.form:
process = file_cmd.delete_file(f"{CFG_DIR}/{file_name}")
file_path = Path(CFG_DIR) / file_name
process = file_cmd.delete_file(file_path)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
return response(message=process["msg"])
@ -478,7 +453,6 @@ def show_diskinfo():
template="diskinfo.html",
file_name=file_name,
diskinfo=diskinfo,
version=server_info["version"],
)
return response(
@ -487,6 +461,56 @@ def show_diskinfo():
)
@APP.route("/sys/manpage", methods=["GET"])
def show_manpage():
"""
Displays manpage
"""
app_allowlist = ["rascsi", "rasctl", "rasdump", "scsimon"]
app = request.args.get("app", type = str)
if app not in app_allowlist:
return response(
error=True,
message=_("%(app)s is not a recognized RaSCSI app", app=app)
)
file_path = f"{WEB_DIR}/../../../doc/{app}.1"
html_to_strip = [
"Content-type",
"!DOCTYPE",
"<HTML>",
"<HEAD>",
"<BODY>",
"<H1>",
]
returncode, manpage = sys_cmd.get_manpage(file_path)
if returncode == 0:
formatted_manpage = ""
for line in manpage.splitlines(True):
# Make URIs compatible with the Flask webapp
if "/?1+" in line:
line = line.replace("/?1+", "manpage?app=")
# Strip out useless hyperlink
elif "man2html" in line:
line = line.replace("<A HREF=\"/\">man2html</A>", "man2html")
if not any(ele in line for ele in html_to_strip):
formatted_manpage += line
return response(
template="manpage.html",
app=app,
manpage=formatted_manpage,
)
return response(
error=True,
message=_("An error occurred when accessing man page: %(error)s", error=manpage)
)
@APP.route("/logs/show", methods=["POST"])
def show_logs():
"""
@ -497,13 +521,11 @@ def show_logs():
returncode, logs = sys_cmd.get_logs(lines, scope)
if returncode == 0:
server_info = ractl_cmd.get_server_info()
return response(
template="logs.html",
scope=scope,
lines=lines,
logs=logs,
version=server_info["version"],
)
return response(
@ -533,35 +555,40 @@ def attach_device():
"""
Attaches a peripheral device that doesn't take an image file as argument
"""
params = {}
scsi_id = request.form.get("scsi_id")
unit = request.form.get("unit")
device_type = request.form.get("type")
drive_name = request.form.get("drive_name")
if not scsi_id:
return response(error=True, message=_("No SCSI ID specified"))
# Attempt to fetch the drive properties based on drive name
drive_props = None
if drive_name:
for drive in APP.config["RASCSI_DRIVE_PROPERTIES"]:
if drive["name"] == drive_name:
drive_props = drive
break
# Collect device parameters into a dictionary
PARAM_PREFIX = "param_"
params = {}
for item in request.form:
if item == "scsi_id":
scsi_id = request.form.get(item)
elif item == "unit":
unit = request.form.get(item)
elif item == "type":
device_type = request.form.get(item)
elif item == "drive_name":
drive_name = request.form.get(item)
for drive in APP.config["RASCSI_DRIVE_PROPERTIES"]:
if drive["name"] == drive_name:
drive_props = drive
break
else:
if item.startswith(PARAM_PREFIX):
param = request.form.get(item)
if param:
params.update({item: param})
params.update({item.replace(PARAM_PREFIX, ""): param})
error_url = "https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link"
error_msg = _("Please follow the instructions at %(url)s", url=error_url)
if "interface" in params.keys():
# Note: is_bridge_configured returns False if the bridge is configured
bridge_status = is_bridge_configured(params["interface"])
if bridge_status:
if not bridge_status["status"]:
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(bridge_status, "error"),
(bridge_status["msg"], "error"),
(error_msg, "error")
])
@ -601,6 +628,11 @@ def attach_image():
unit = request.form.get("unit")
device_type = request.form.get("type")
if not scsi_id:
return response(error=True, message=_("No SCSI ID specified"))
if not file_name:
return response(error=True, message=_("No image file to insert"))
kwargs = {"unit": int(unit), "params": {"file": file_name}}
if device_type:
@ -646,6 +678,7 @@ def attach_image():
return response(message=response_messages)
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to attach %(file_name)s to SCSI ID %(id_number)s LUN %(unit_number)s",
file_name=file_name, id_number=scsi_id, unit_number=unit), "error"),
@ -679,6 +712,7 @@ def detach():
return response(message=_("Detached SCSI ID %(id_number)s LUN %(unit_number)s",
id_number=scsi_id, unit_number=unit))
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to detach SCSI ID %(id_number)s LUN %(unit_number)s",
id_number=scsi_id, unit_number=unit), "error"),
@ -700,6 +734,7 @@ def eject():
return response(message=_("Ejected SCSI ID %(id_number)s LUN %(unit_number)s",
id_number=scsi_id, unit_number=unit))
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to eject SCSI ID %(id_number)s LUN %(unit_number)s",
id_number=scsi_id, unit_number=unit), "error"),
@ -712,13 +747,11 @@ def device_info():
"""
Displays detailed info for all attached devices
"""
server_info = ractl_cmd.get_server_info()
process = ractl_cmd.list_devices()
if process["status"]:
return response(
template="deviceinfo.html",
devices=process["device_list"],
version=server_info["version"],
)
return response(error=True, message=_("No devices attached"))
@ -739,6 +772,7 @@ def reserve_id():
RESERVATIONS[int(scsi_id)] = memo
return response(message=_("Reserved SCSI ID %(id_number)s", id_number=scsi_id))
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to reserve SCSI ID %(id_number)s", id_number=scsi_id), "error"),
(process["msg"], "error"),
@ -759,6 +793,7 @@ def release_id():
RESERVATIONS[int(scsi_id)] = ""
return response(message=_("Released the reservation for SCSI ID %(id_number)s", id_number=scsi_id))
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to release the reservation for SCSI ID %(id_number)s", id_number=scsi_id), "error"),
(process["msg"], "error"),
@ -799,6 +834,7 @@ def download_to_iso():
process = file_cmd.download_file_to_iso(url, *iso_args)
process = ReturnCodeMapper.add_msg(process)
if not process["status"]:
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to create CD-ROM image from %(url)s", url=url), "error"),
(process["msg"], "error"),
@ -817,6 +853,7 @@ def download_to_iso():
response_messages.append((_("Attached to SCSI ID %(id_number)s", id_number=scsi_id), "success"))
return response(message=response_messages)
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to attach image to SCSI ID %(id_number)s. Try attaching it manually.", id_number=scsi_id), "error"),
(process_attach["msg"], "error"),
@ -841,6 +878,7 @@ def download_file():
if process["status"]:
return response(message=process["msg"])
# TODO: Refactor the return messages into one string
return response(error=True, message=[
(_("Failed to download file from %(url)s", url=url), "error"),
(process["msg"], "error"),
@ -876,17 +914,34 @@ def create_file():
file_name = request.form.get("file_name")
size = (int(request.form.get("size")) * 1024 * 1024)
file_type = request.form.get("type")
drive_name = request.form.get("drive_name")
full_file_name = file_name + "." + file_type
process = file_cmd.create_new_image(file_name, file_type, size)
if process["status"]:
return response(
status_code=201,
message=_("Image file created: %(file_name)s", file_name=full_file_name),
image=full_file_name,
)
if not process["status"]:
return response(error=True, message=process["msg"])
return response(error=True, message=process["msg"])
# Creating the drive properties file, if one is chosen
if drive_name:
properties = get_properties_by_drive_name(
APP.config["RASCSI_DRIVE_PROPERTIES"],
drive_name
)
prop_file_name = f"{full_file_name}.{PROPERTIES_SUFFIX}"
process = file_cmd.write_drive_properties(prop_file_name, properties)
process = ReturnCodeMapper.add_msg(process)
if not process["status"]:
return response(error=True, message=process["msg"])
return response(
status_code=201,
# TODO: Refactor the return messages into one string
message=[
(_("Image file created: %(file_name)s", file_name=full_file_name), "success"),
(process["msg"], "success"),
],
image=full_file_name,
)
@APP.route("/files/download", methods=["POST"])
@ -895,8 +950,9 @@ def download():
"""
Downloads a file from the Pi to the local computer
"""
image = request.form.get("file")
return send_file(image, as_attachment=True)
file_name = request.form.get("file")
server_info = ractl_cmd.get_server_info()
return send_from_directory(server_info["image_dir"], file_name, as_attachment=True)
@APP.route("/files/delete", methods=["POST"])
@ -915,8 +971,8 @@ def delete():
(_("Image file deleted: %(file_name)s", file_name=file_name), "success")]
# Delete the drive properties file, if it exists
prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
if Path(prop_file_path).is_file():
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
if prop_file_path.is_file():
process = file_cmd.delete_file(prop_file_path)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
@ -944,9 +1000,9 @@ def rename():
(_("Image file renamed to: %(file_name)s", file_name=new_file_name), "success")]
# Rename the drive properties file, if it exists
prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
new_prop_file_path = f"{CFG_DIR}/{new_file_name}.{PROPERTIES_SUFFIX}"
if Path(prop_file_path).is_file():
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
new_prop_file_path = Path(CFG_DIR) / f"{new_file_name}.{PROPERTIES_SUFFIX}"
if prop_file_path.is_file():
process = file_cmd.rename_file(prop_file_path, new_prop_file_path)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
@ -974,9 +1030,9 @@ def copy():
(_("Copy of image file saved as: %(file_name)s", file_name=new_file_name), "success")]
# Create a copy of the drive properties file, if it exists
prop_file_path = f"{CFG_DIR}/{file_name}.{PROPERTIES_SUFFIX}"
new_prop_file_path = f"{CFG_DIR}/{new_file_name}.{PROPERTIES_SUFFIX}"
if Path(prop_file_path).is_file():
prop_file_path = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}"
new_prop_file_path = Path(CFG_DIR) / f"{new_file_name}.{PROPERTIES_SUFFIX}"
if prop_file_path.is_file():
process = file_cmd.copy_file(prop_file_path, new_prop_file_path)
process = ReturnCodeMapper.add_msg(process)
if process["status"]:
@ -1112,8 +1168,10 @@ if __name__ == "__main__":
if process["status"]:
APP.config["RASCSI_DRIVE_PROPERTIES"] = process["conf"]
else:
APP.config["RASCSI_DRIVE_PROPERTIES"] = []
logging.error(process["msg"])
else:
APP.config["RASCSI_DRIVE_PROPERTIES"] = []
logging.warning("Could not read drive properties from %s", DRIVE_PROPERTIES_FILE)
logging.basicConfig(stream=sys.stdout,

View File

@ -18,6 +18,7 @@ def get_valid_scsi_ids(devices, reserved_ids):
Takes a list of (dict)s devices, and list of (int)s reserved_ids.
Returns:
- (list) of (int)s valid_ids, which are the SCSI ids that are not reserved
- (list) of (int)s occupied_ids, which are the SCSI ids were a device is attached
- (int) recommended_id, which is the id that the Web UI should default to recommend
"""
occupied_ids = []
@ -33,11 +34,15 @@ def get_valid_scsi_ids(devices, reserved_ids):
recommended_id = unoccupied_ids[-1]
else:
if occupied_ids:
recommended_id = occupied_ids.pop(0)
recommended_id = occupied_ids[0]
else:
recommended_id = 0
return valid_ids, recommended_id
return {
"valid_ids": valid_ids,
"occupied_ids": occupied_ids,
"recommended_id": recommended_id,
}
def sort_and_format_devices(devices):
@ -47,18 +52,25 @@ def sort_and_format_devices(devices):
For SCSI IDs where no device is attached, inject a (dict) with placeholder text.
"""
occupied_ids = []
formatted_devices = []
for device in devices:
occupied_ids.append(device["id"])
device["device_name"] = get_device_name(device["device_type"])
formatted_devices.append(device)
formatted_devices = devices
# Add padding devices and sort the list
for i in range(8):
if i not in occupied_ids:
formatted_devices.append({"id": i, "device_type": "-", \
"status": "-", "file": "-", "product": "-"})
# Sort list of devices by id
formatted_devices.sort(key=lambda dic: str(dic["id"]))
# Add placeholder data for non-occupied IDs
for scsi_id in range(8):
if scsi_id not in occupied_ids:
formatted_devices.append(
{
"id": scsi_id,
"unit": "-",
"device_name": "-",
"status": "-",
"file": "-",
"product": "-",
}
)
return formatted_devices
@ -91,7 +103,7 @@ def get_device_name(device_type):
if device_type == "SCBR":
return _("Host Bridge")
if device_type == "SCDP":
return _("DaynaPORT SCSI/Link")
return _("Ethernet Adapter")
if device_type == "SCLP":
return _("Printer")
if device_type == "SCHS":
@ -144,6 +156,12 @@ def format_drive_properties(drive_properties):
FORMAT_FILTER = "{:,.2f}"
for device in drive_properties:
# Add fallback device names, since other code relies on this data for display
if not device["name"]:
if device["product"]:
device["name"] = device["product"]
else:
device["name"] = "Unknown Device"
if device["device_type"] == "SCHD":
device["secure_name"] = secure_filename(device["name"])
device["size_mb"] = FORMAT_FILTER.format(device["size"] / 1024 / 1024)
@ -167,6 +185,41 @@ def format_drive_properties(drive_properties):
"mo_conf": mo_conf,
}
def get_properties_by_drive_name(drives, drive_name):
"""
Takes (list) of (dict) drives, and (str) drive_name
Returns (dict) with the collection of drive properties that matches drive_name
"""
drives.sort(key=lambda item: item.get("name"))
drive_props = None
prev_drive = {"name": ""}
for drive in drives:
# TODO: Make this check into an integration test
if "name" not in drive:
logging.warning(
"Device without a name exists in the drive properties database. This is a bug."
)
break
# TODO: Make this check into an integration test
if drive["name"] == prev_drive["name"]:
logging.warning(
"Device with duplicate name \"%s\" in drive properties database. This is a bug.",
drive["name"],
)
prev_drive = drive
if drive["name"] == drive_name:
drive_props = drive
return {
"file_type": drive_props["file_type"],
"vendor": drive_props["vendor"],
"product": drive_props["product"],
"revision": drive_props["revision"],
"block_size": drive_props["block_size"],
"size": drive_props["size"],
}
def auth_active(group):
"""
Inspects if the group defined in (str) group exists on the system.
@ -185,24 +238,31 @@ def auth_active(group):
def is_bridge_configured(interface):
"""
Takes (str) interface of a network device being attached.
Returns (bool) False if the network bridge is configured.
Returns (str) with an error message if the network bridge is not configured.
Returns a (dict) with (bool) status and (str) msg
"""
# TODO: Reduce the nesting of these checks, and streamline how the results are notified
status = True
return_msg = ""
sys_cmd = SysCmds()
if interface.startswith("wlan"):
if not sys_cmd.introspect_file("/etc/sysctl.conf", r"^net\.ipv4\.ip_forward=1$"):
return _("Configure IPv4 forwarding before using a wireless network device.")
if not Path("/etc/iptables/rules.v4").is_file():
return _("Configure NAT before using a wireless network device.")
status = False
return_msg = _("Configure IPv4 forwarding before using a wireless network device.")
elif not Path("/etc/iptables/rules.v4").is_file():
status = False
return_msg = _("Configure NAT before using a wireless network device.")
else:
if not sys_cmd.introspect_file(
"/etc/dhcpcd.conf",
r"^denyinterfaces " + interface + r"$",
):
return _("Configure the network bridge before using a wired network device.")
if not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
return _("Configure the network bridge before using a wired network device.")
return False
status = False
return_msg = _("Configure the network bridge before using a wired network device.")
elif not Path("/etc/network/interfaces.d/rascsi_bridge").is_file():
status = False
return_msg = _("Configure the network bridge before using a wired network device.")
return {"status": status, "msg": return_msg + f" ({interface})"}
def upload_with_dropzonejs(image_dir):
@ -235,18 +295,7 @@ def upload_with_dropzonejs(image_dir):
if current_chunk + 1 == total_chunks:
# Validate the resulting file size after writing the last chunk
if path.getsize(save_path) != int(request.form["dztotalfilesize"]):
log.error(
"Finished transferring %s, "
"but it has a size mismatch with the original file. "
"Got %s but we expected %s.",
file_object.filename,
path.getsize(save_path),
request.form['dztotalfilesize'],
)
log.error("File size mismatch between the original file and transferred file.")
return make_response(_("Transferred file corrupted!"), 500)
log.info("File %s has been uploaded successfully", file_object.filename)
log.debug("Chunk %s of %s for file %s completed.",
current_chunk + 1, total_chunks, file_object.filename)
return make_response(_("File upload successful!"), 200)

View File

@ -2,9 +2,6 @@ import pytest
import uuid
import warnings
CFG_DIR = "/home/pi/.config/rascsi"
IMAGES_DIR = "/home/pi/images"
AFP_DIR = "/home/pi/afpshare"
SCSI_ID = 6
FILE_SIZE_1_MIB = 1048576
STATUS_SUCCESS = "success"

View File

@ -3,6 +3,7 @@ from conftest import STATUS_SUCCESS, STATUS_ERROR
# route("/login", methods=["POST"])
def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated):
# Note: This test depends on the rascsi group existing and 'username' a member the group
response = http_client_unauthenticated.post(
"/login",
data={

View File

@ -42,8 +42,8 @@ def test_attach_image(http_client, create_test_image, detach_devices):
{
"type": "SCRM",
"drive_props": {
"vendor": "VENDOR",
"product": "PRODUCT",
"vendor": "HD VENDOR",
"product": "HD PRODUCT",
"revision": "0123",
"block_size": "512",
},
@ -54,8 +54,8 @@ def test_attach_image(http_client, create_test_image, detach_devices):
{
"type": "SCMO",
"drive_props": {
"vendor": "VENDOR",
"product": "PRODUCT",
"vendor": "MO VENDOR",
"product": "MO PRODUCT",
"revision": "0123",
"block_size": "512",
},
@ -66,20 +66,25 @@ def test_attach_image(http_client, create_test_image, detach_devices):
{
"type": "SCCD",
"drive_props": {
"vendor": "VENDOR",
"product": "PRODUCT",
"vendor": "CD VENDOR",
"product": "CD PRODUCT",
"revision": "0123",
"block_size": "512",
},
},
),
("Host Bridge", {"type": "SCBR", "interface": "eth0", "inet": "10.10.20.1/24"}),
("DaynaPORT SCSI/Link", {"type": "SCDP", "interface": "eth0", "inet": "10.10.20.1/24"}),
# TODO: Find a portable way to detect network interfaces for testing
("Host Bridge", {"type": "SCBR", "param_inet": "192.168.0.1/24"}),
# TODO: Find a portable way to detect network interfaces for testing
("Ethernet Adapter", {"type": "SCDP", "param_inet": "192.168.0.1/24"}),
("Host Services", {"type": "SCHS"}),
("Printer", {"type": "SCLP", "timeout": 30, "cmd": "lp -oraw %f"}),
("Printer", {"type": "SCLP", "param_timeout": 60, "param_cmd": "lp -fart %f"}),
],
)
def test_attach_device(http_client, detach_devices, device_name, device_config):
def test_attach_device(env, http_client, detach_devices, device_name, device_config):
if env["is_docker"] and device_name == "Host Bridge":
pytest.skip("Test not supported in Docker environment.")
device_config["scsi_id"] = SCSI_ID
device_config["unit"] = 0

View File

@ -3,7 +3,6 @@ import uuid
import os
from conftest import (
IMAGES_DIR,
SCSI_ID,
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
@ -21,6 +20,7 @@ def test_create_file(http_client, list_files, delete_file):
"file_name": file_prefix,
"type": "hds",
"size": 1,
"drive_name": "DEC RZ22",
},
)
@ -85,7 +85,7 @@ def test_copy_file(http_client, create_test_image, list_files, delete_file):
# route("/files/delete", methods=["POST"])
def test_delete_file(http_client, create_test_image, list_files):
file_name = create_test_image()
file_name = create_test_image(auto_delete=False)
response = http_client.post("/files/delete", data={"file_name": file_name})
@ -200,7 +200,7 @@ def test_upload_file(http_client, delete_file):
def test_download_file(http_client, create_test_image):
file_name = create_test_image()
response = http_client.post("/files/download", data={"file": f"{IMAGES_DIR}/{file_name}"})
response = http_client.post("/files/download", data={"file": file_name})
assert response.status_code == 200
assert response.headers["content-type"] == "application/octet-stream"
@ -209,7 +209,7 @@ def test_download_file(http_client, create_test_image):
# route("/files/download_url", methods=["POST"])
def test_download_url_to_dir(httpserver, http_client, list_files, delete_file):
def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file):
file_name = str(uuid.uuid4())
http_path = f"/images/{file_name}"
url = httpserver.url_for(http_path)
@ -235,7 +235,9 @@ def test_download_url_to_dir(httpserver, http_client, list_files, delete_file):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert file_name in list_files()
assert response_data["messages"][0]["message"] == f"{file_name} downloaded to {IMAGES_DIR}"
assert (
response_data["messages"][0]["message"] == f"{file_name} downloaded to {env['images_dir']}"
)
# Cleanup
delete_file(file_name)
@ -243,6 +245,7 @@ def test_download_url_to_dir(httpserver, http_client, list_files, delete_file):
# route("/files/download_to_iso", methods=["POST"])
def test_download_url_to_iso(
env,
httpserver,
http_client,
list_files,
@ -282,7 +285,7 @@ def test_download_url_to_iso(
m = response_data["messages"]
assert m[0]["message"] == 'Created CD-ROM ISO image with arguments "-hfs"'
assert m[1]["message"] == f"Saved image as: {IMAGES_DIR}/{iso_file_name}"
assert m[1]["message"] == f"Saved image as: {env['images_dir']}/{iso_file_name}"
assert m[2]["message"] == f"Attached to SCSI ID {SCSI_ID}"
# Cleanup

View File

@ -1,7 +1,6 @@
import uuid
from conftest import (
CFG_DIR,
FILE_SIZE_1_MIB,
STATUS_SUCCESS,
)
@ -42,20 +41,17 @@ def test_show_named_drive_presets(http_client):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert "drive_properties" in response_data["data"]
assert "files" in response_data["data"]
# route("/drive/cdrom", methods=["POST"])
def test_create_cdrom_properties_file(http_client):
def test_create_cdrom_properties_file(env, http_client):
file_name = f"{uuid.uuid4()}.iso"
response = http_client.post(
"/drive/cdrom",
data={
"vendor": "TEST_AAA",
"product": "TEST_BBB",
"revision": "1.0A",
"block_size": 2048,
"drive_name": "Sony CDU-8012",
"file_name": file_name,
},
)
@ -65,7 +61,7 @@ def test_create_cdrom_properties_file(http_client):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == (
f"File created: {CFG_DIR}/{file_name}.properties"
f"File created: {env['cfg_dir']}/{file_name}.properties"
)
@ -77,12 +73,8 @@ def test_create_image_with_properties_file(http_client, delete_file):
response = http_client.post(
"/drive/create",
data={
"vendor": "TEST_AAA",
"product": "TEST_BBB",
"revision": "1.0A",
"block_size": 512,
"drive_name": "Miniscribe M8425",
"size": FILE_SIZE_1_MIB,
"file_type": "hds",
"file_name": file_prefix,
},
)
@ -95,3 +87,12 @@ def test_create_image_with_properties_file(http_client, delete_file):
# Cleanup
delete_file(file_name)
# route("/sys/manpage", methods=["POST"])
def test_show_manpage(http_client):
response = http_client.get("/sys/manpage?app=rascsi")
response_data = response.json()
assert response.status_code == 200
assert "rascsi" in response_data["data"]["manpage"]

View File

@ -1,7 +1,7 @@
import pytest
import uuid
from conftest import CFG_DIR, STATUS_SUCCESS
from conftest import STATUS_SUCCESS
# route("/language", methods=["POST"])
@ -74,7 +74,7 @@ def test_show_logs(http_client):
# route("/config/save", methods=["POST"])
# route("/config/load", methods=["POST"])
def test_save_load_and_delete_configs(http_client):
def test_save_load_and_delete_configs(env, http_client):
config_name = str(uuid.uuid4())
config_json_file = f"{config_name}.json"
reserved_scsi_id = 0
@ -96,7 +96,7 @@ def test_save_load_and_delete_configs(http_client):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == (
f"File created: {CFG_DIR}/{config_json_file}"
f"File created: {env['cfg_dir']}/{config_json_file}"
)
assert config_json_file in http_client.get("/").json()["data"]["config_files"]
@ -126,7 +126,7 @@ def test_save_load_and_delete_configs(http_client):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == (
f"Loaded configurations from: {CFG_DIR}/{config_json_file}"
f"Loaded configurations from: {config_json_file}"
)
# Confirm the application has returned to its initial state
@ -146,7 +146,7 @@ def test_save_load_and_delete_configs(http_client):
assert response.status_code == 200
assert response_data["status"] == STATUS_SUCCESS
assert response_data["messages"][0]["message"] == (
f"File deleted: {CFG_DIR}/{config_json_file}"
f"File deleted: {env['cfg_dir']}/{config_json_file}"
)
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]

View File

@ -1,15 +1,32 @@
import pytest
import requests
import socket
import os
def pytest_addoption(parser):
parser.addoption("--base_url", action="store", default="http://localhost:8080")
parser.addoption("--httpserver_host", action="store", default="host.docker.internal")
parser.addoption("--httpserver_listen_address", action="store", default="127.0.0.1")
default_base_url = "http://rascsi_web" if os.getenv("DOCKER") else "http://localhost:8080"
parser.addoption("--home_dir", action="store", default="/home/pi")
parser.addoption("--base_url", action="store", default=default_base_url)
parser.addoption("--httpserver_host", action="store", default=socket.gethostname())
parser.addoption("--httpserver_listen_address", action="store", default="0.0.0.0")
parser.addoption("--rascsi_username", action="store", default="pi")
parser.addoption("--rascsi_password", action="store", default="rascsi")
@pytest.fixture(scope="session")
def env(pytestconfig):
home_dir = pytestconfig.getoption("home_dir")
return {
"is_docker": bool(os.getenv("DOCKER")),
"home_dir": home_dir,
"cfg_dir": f"{home_dir}/.config/rascsi",
"images_dir": f"{home_dir}/images",
"afp_dir": f"{home_dir}/afpshare",
}
@pytest.fixture(scope="session")
def httpserver_listen_address(pytestconfig):
return (pytestconfig.getoption("httpserver_listen_address"), 0)
@ -17,8 +34,7 @@ def httpserver_listen_address(pytestconfig):
@pytest.fixture(scope="function", autouse=True)
def set_httpserver_hostname(pytestconfig, httpserver):
# The HTTP requests are made by Python from within the container so we need
# httpserver.url_for to generate URLs which point to the Docker host
# We need httpserver.url_for() to generate URLs pointing to the correct host
httpserver.host = pytestconfig.getoption("httpserver_host")

View File

@ -89,45 +89,37 @@ SRC_PROTOC = \
SRC_PROTOBUF = \
rascsi_interface.pb.cpp
SRC_RASCSI_CORE = \
scsi.cpp \
filepath.cpp \
fileio.cpp \
SRC_SHARED = \
rascsi_version.cpp \
rascsi_image.cpp \
rascsi_response.cpp \
rasutil.cpp \
command_util.cpp \
socket_connector.cpp \
localizer.cpp
protobuf_util.cpp \
protobuf_serializer.cpp
SRC_RASCSI_CORE = \
bus.cpp \
filepath.cpp \
fileio.cpp
SRC_RASCSI_CORE += $(shell find ./rascsi -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp')
SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp')
SRC_RASCSI_CORE += $(SRC_PROTOBUF)
SRC_RASCSI = rascsi.cpp
SRC_SCSIMON = \
scsimon.cpp \
scsi.cpp \
bus.cpp \
rascsi_version.cpp
SRC_SCSIMON += $(shell find ./monitor -name '*.cpp')
SRC_SCSIMON += $(shell find ./hal -name '*.cpp')
SRC_RASCTL = \
rasctl.cpp\
rasctl_commands.cpp \
rasctl_display.cpp \
rascsi_version.cpp \
rasutil.cpp \
command_util.cpp \
socket_connector.cpp \
localizer.cpp
SRC_RASCTL += $(SRC_PROTOBUF)
SRC_RASCTL_CORE = $(shell find ./rasctl -name '*.cpp')
SRC_RASCTL = rasctl.cpp
SRC_RASDUMP = \
rasdump.cpp \
scsi.cpp \
bus.cpp \
filepath.cpp \
fileio.cpp \
rascsi_version.cpp
@ -140,18 +132,21 @@ SRC_GPIO_TEST = \
gpiotest.cpp
SRC_GPIO_TEST += $(shell find ./hal -name '*.cpp')
vpath %.h ./ ./controllers ./devices ./monitor ./hal ../BPI-WiringPi2/wiringPi/
vpath %.cpp ./ ./controllers ./devices ./monitor ./test ./hal
vpath %.h ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl ../BPI-WiringPi2/wiringPi/
vpath %.cpp ./ ./controllers ./devices ./monitor ./test ./hal ./rascsi ./rasctl
vpath %.o ./$(OBJDIR)
vpath ./$(BINDIR)
OBJ_RASCSI_CORE := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI_CORE:%.cpp=%.o)))
OBJ_RASCSI := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI:%.cpp=%.o)))
OBJ_RASCTL_CORE := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL_CORE:%.cpp=%.o)))
OBJ_RASCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL:%.cpp=%.o)))
OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o)))
OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o)))
OBJ_RASCSI_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI_TEST:%.cpp=%.o)))
OBJ_SHARED := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SHARED:%.cpp=%.o)))
OBJ_PROTOBUF := $(addprefix $(OBJDIR)/,$(notdir $(SRC_PROTOBUF:%.cpp=%.o)))
OBJ_GPIO_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_GPIO_TEST:%.cpp=%.o)))
GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h
@ -160,7 +155,7 @@ LIB_BPI_WIRINGPI := $(OBJDIR)/libwiringPi.a
# The following will include all of the auto-generated dependency files (*.d)
# if they exist. This will trigger a rebuild of a source file if a header changes
ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_SCSIMON) $(OBJ_RASCSI_TEST))
ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_RASCSI_CORE) $(OBJ_RASCTL_CORE) $(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_SCSIMON) $(OBJ_RASCSI_TEST))
-include $(ALL_DEPS)
$(OBJDIR) $(BINDIR):
@ -201,11 +196,13 @@ lcov: test
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(LIB_BPI_WIRINGPI) -lpthread -lpcap -lprotobuf -lstdc++fs
$(SRC_SHARED): $(SRC_PROTOBUF)
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lpthread -lprotobuf
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) -lpthread -lpcap -lprotobuf -lstdc++fs
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL_CORE) $(OBJ_RASCTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL_CORE) $(OBJ_RASCTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lprotobuf
$(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASDUMP) $(LIB_BPI_WIRINGPI) -lpthread
@ -213,14 +210,13 @@ $(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_SCSIMON) $(LIB_BPI_WIRINGPI) -lpthread
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI_TEST) $(LIB_BPI_WIRINGPI) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCTL_CORE) $(OBJ_RASCSI_TEST) $(OBJ_RASCTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCTL_CORE) $(OBJ_RASCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) $(LIB_BPI_WIRINGPI) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
$(BINDIR)/$(GPIO_TEST): $(OBJ_GPIO_TEST) $(LIB_BPI_WIRINGPI) | $(BINDIR)
echo $(OBJ_GPIO_TEST)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_GPIO_TEST) $(LIB_BPI_WIRINGPI) -lpthread
# Phony rules for building individual utilities
.PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SCSIMON) $(GPIO_TEST)
$(RASCSI) : $(BINDIR)/$(RASCSI)

View File

@ -1,15 +1,14 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
//
// [ SCSI Common Functionality ]
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "scsi.h"
#include "bus.h"
using namespace std;
@ -31,9 +30,9 @@ BUS::phase_t BUS::GetPhase()
}
// Get target phase from bus signal line
DWORD mci = GetMSG() ? 0x04 : 0x00;
mci |= GetCD() ? 0x02 : 0x00;
mci |= GetIO() ? 0x01 : 0x00;
int mci = GetMSG() ? 0b100 : 0b000;
mci |= GetCD() ? 0b010 : 0b000;
mci |= GetIO() ? 0b001 : 0b000;
return GetPhase(mci);
}
@ -42,13 +41,9 @@ BUS::phase_t BUS::GetPhase()
// Determine Phase String phase enum
//
//---------------------------------------------------------------------------
const char* BUS::GetPhaseStrRaw(phase_t current_phase){
if(current_phase <= phase_t::reserved) {
return phase_str_table[(int)current_phase];
}
else {
return "INVALID";
}
const char* BUS::GetPhaseStrRaw(phase_t current_phase) {
const auto& it = phase_str_mapping.find(current_phase);
return it != phase_str_mapping.end() ? it->second : "INVALID";
}
//---------------------------------------------------------------------------
@ -70,24 +65,21 @@ const array<BUS::phase_t, 8> BUS::phase_table = {
phase_t::msgin // | 1 | 1 | 1 |
};
//---------------------------------------------------------------------------
//
// Phase Table
// This MUST be kept in sync with the phase_t enum type!
// Phase string to phase mapping
//
//---------------------------------------------------------------------------
const array<const char*, 12> BUS::phase_str_table = {
"busfree",
"arbitration",
"selection",
"reselection",
"command",
"execute",
"datain",
"dataout",
"status",
"msgin",
"msgout",
"reserved"
const unordered_map<BUS::phase_t, const char*> BUS::phase_str_mapping = {
{ phase_t::busfree, "busfree" },
{ phase_t::arbitration, "arbitration" },
{ phase_t::selection, "selection" },
{ phase_t::reselection, "reselection" },
{ phase_t::command, "command" },
{ phase_t::datain, "datain" },
{ phase_t::dataout, "dataout" },
{ phase_t::status, "status" },
{ phase_t::msgin, "msgin" },
{ phase_t::msgout, "msgout" },
{ phase_t::reserved, "reserved" }
};

113
src/raspberrypi/bus.h Normal file
View File

@ -0,0 +1,113 @@
//---------------------------------------------------------------------------
//
// X68000 EMULATOR "XM6"
//
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
// Copyright (C) 2014-2020 GIMONS
//
//---------------------------------------------------------------------------
#pragma once
#include "os.h"
#include "scsi.h"
#include <array>
#include <unordered_map>
using namespace std;
class BUS
{
public:
// Operation modes definition
enum class mode_e {
TARGET = 0,
INITIATOR = 1,
MONITOR = 2,
};
// Phase definitions
enum class phase_t : int {
busfree,
arbitration,
selection,
reselection,
command,
datain,
dataout,
status,
msgin,
msgout,
reserved
};
BUS() = default;
virtual ~BUS() = default;
// Basic Functions
virtual bool Init(mode_e mode) = 0;
virtual void Reset() = 0;
virtual void Cleanup() = 0;
phase_t GetPhase();
static phase_t GetPhase(int mci)
{
return phase_table[mci];
}
// Get the string phase name, based upon the raw data
static const char* GetPhaseStrRaw(phase_t current_phase);
// Extract as specific pin field from a raw data capture
static inline uint32_t GetPinRaw(uint32_t raw_data, uint32_t pin_num)
{
return ((raw_data >> pin_num) & 1);
}
virtual bool GetBSY() const = 0;
virtual void SetBSY(bool ast) = 0;
virtual bool GetSEL() const = 0;
virtual void SetSEL(bool ast) = 0;
virtual bool GetATN() const = 0;
virtual void SetATN(bool ast) = 0;
virtual bool GetACK() const = 0;
virtual void SetACK(bool ast) = 0;
virtual bool GetRST() const = 0;
virtual void SetRST(bool ast) = 0;
virtual bool GetMSG() const = 0;
virtual void SetMSG(bool ast) = 0;
virtual bool GetCD() const = 0;
virtual void SetCD(bool ast) = 0;
virtual bool GetIO() = 0;
virtual void SetIO(bool ast) = 0;
virtual bool GetREQ() const = 0;
virtual void SetREQ(bool ast) = 0;
virtual BYTE GetDAT() = 0;
virtual void SetDAT(BYTE dat) = 0;
virtual bool GetDP() const = 0; // Get parity signal
virtual uint32_t Acquire() = 0;
virtual int CommandHandShake(BYTE *buf) = 0;
virtual int ReceiveHandShake(BYTE *buf, int count) = 0;
virtual int SendHandShake(BYTE *buf, int count, int delay_after_bytes) = 0;
virtual bool GetSignal(int pin) const = 0;
// Get SCSI input signal value
virtual void SetSignal(int pin, bool ast) = 0;
// Set SCSI output signal value
static const int SEND_NO_DELAY = -1;
// Passed into SendHandShake when we don't want to delay
private:
static const array<phase_t, 8> phase_table;
static const unordered_map<phase_t, const char *> phase_str_mapping;
};

View File

@ -1,27 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include <string>
class SocketConnector;
class Localizer;
struct CommandContext
{
CommandContext(const SocketConnector& c, const Localizer& l, int f, const std::string& s)
: connector(c), localizer(l), fd(f), locale(s) {}
~CommandContext() = default;
const SocketConnector& connector;
const Localizer& localizer;
int fd;
std::string locale;
};

View File

@ -9,8 +9,7 @@
#include "log.h"
#include "rascsi_interface.pb.h"
#include "localizer.h"
#include "socket_connector.h"
#include "protobuf_serializer.h"
#include "command_util.h"
#include <sstream>
@ -86,51 +85,3 @@ void command_util::AddParam(PbDeviceDefinition& device, const string& key, strin
map[key] = value;
}
}
bool command_util::ReturnLocalizedError(const CommandContext& context, LocalizationKey key,
const string& arg1, const string& arg2, const string& arg3)
{
return ReturnLocalizedError(context, key, NO_ERROR_CODE, arg1, arg2, arg3);
}
bool command_util::ReturnLocalizedError(const CommandContext& context, LocalizationKey key,
PbErrorCode error_code, const string& arg1, const string& arg2, const string& arg3)
{
// For the logfile always use English
LOGERROR("%s", context.localizer.Localize(key, "en", arg1, arg2, arg3).c_str())
return ReturnStatus(context, false, context.localizer.Localize(key, context.locale, arg1, arg2, arg3), error_code,
false);
}
bool command_util::ReturnStatus(const CommandContext& context, bool status, const string& msg,
PbErrorCode error_code, bool log)
{
// Do not log twice if logging has already been done in the localized error handling above
if (log && !status && !msg.empty()) {
LOGERROR("%s", msg.c_str())
}
if (context.fd == -1) {
if (!msg.empty()) {
if (status) {
FPRT(stderr, "Error: ");
FPRT(stderr, "%s", msg.c_str());
FPRT(stderr, "\n");
}
else {
FPRT(stdout, "%s", msg.c_str());
FPRT(stderr, "\n");
}
}
}
else {
PbResult result;
result.set_status(status);
result.set_error_code(error_code);
result.set_msg(msg);
context.connector.SerializeMessage(context.fd, result);
}
return status;
}

View File

@ -20,6 +20,6 @@
#define USE_SEL_EVENT_ENABLE // Check SEL signal by event
// This avoids an indefinite loop with warnings if there is no RaSCSI hardware
// and thus helps with running rasctl and unit test on x86 hardware.
#if defined(__x86_64__) || defined(__X86__) || !defined(__linux)
#if defined(__x86_64__) || defined(__X86__) || !defined(__linux__)
#undef USE_SEL_EVENT_ENABLE
#endif

View File

@ -7,8 +7,9 @@
//
//---------------------------------------------------------------------------
#include "abstract_controller.h"
#include "rascsi_exceptions.h"
#include "devices/primary_device.h"
#include "abstract_controller.h"
void AbstractController::AllocateBuffer(size_t size)
{
@ -17,7 +18,18 @@ void AbstractController::AllocateBuffer(size_t size)
}
}
PrimaryDevice *AbstractController::GetDeviceForLun(int lun) const {
unordered_set<shared_ptr<PrimaryDevice>> AbstractController::GetDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, lun] : luns) {
devices.insert(lun);
}
return devices;
}
shared_ptr<PrimaryDevice> AbstractController::GetDeviceForLun(int lun) const {
const auto& it = luns.find(lun);
return it == luns.end() ? nullptr : it->second;
}
@ -26,7 +38,7 @@ void AbstractController::Reset()
{
SetPhase(BUS::phase_t::busfree);
ctrl.status = 0x00;
ctrl.status = status::GOOD;
ctrl.message = 0x00;
ctrl.blocks = 0;
ctrl.next = 0;
@ -75,18 +87,15 @@ void AbstractController::ProcessPhase()
break;
default:
assert(false);
LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase()))
throw scsi_exception();
break;
}
}
bool AbstractController::AddDevice(PrimaryDevice *device)
bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
{
if (device->GetLun() >= GetMaxLuns()) {
return false;
}
if (HasDeviceForLun(device->GetLun())) {
if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
return false;
}
@ -96,7 +105,7 @@ bool AbstractController::AddDevice(PrimaryDevice *device)
return true;
}
bool AbstractController::DeleteDevice(const PrimaryDevice *device)
bool AbstractController::DeleteDevice(const shared_ptr<PrimaryDevice> device)
{
return luns.erase(device->GetLun()) == 1;
}

View File

@ -12,23 +12,24 @@
#pragma once
#include "scsi.h"
#include "bus.h"
#include "phase_handler.h"
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include <memory>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class PrimaryDevice;
class AbstractController
class AbstractController : public PhaseHandler
{
friend class PrimaryDevice;
friend class ScsiController;
BUS::phase_t phase = BUS::phase_t::busfree;
// Logical units of this device controller mapped to their LUN numbers
unordered_map<int, PrimaryDevice *> luns;
// Logical units of this controller mapped to their LUN numbers
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
public:
@ -41,7 +42,7 @@ public:
using ctrl_t = struct _ctrl_t {
vector<int> cmd; // Command data, dynamically allocated per received command
uint32_t status; // Status data
scsi_defs::status status; // Status data
int message; // Message data
// Transfer
@ -52,21 +53,8 @@ public:
uint32_t length; // Transfer remaining length
};
AbstractController(shared_ptr<BUS> bus, int target_id, int luns) : target_id(target_id), bus(bus), max_luns(luns) {}
virtual ~AbstractController() = default;
AbstractController(AbstractController&) = delete;
AbstractController& operator=(const AbstractController&) = delete;
virtual void BusFree() = 0;
virtual void Selection() = 0;
virtual void Command() = 0;
virtual void Status() = 0;
virtual void DataIn() = 0;
virtual void DataOut() = 0;
virtual void MsgIn() = 0;
virtual void MsgOut() = 0;
virtual BUS::phase_t Process(int) = 0;
AbstractController(BUS& bus, int target_id, int max_luns) : target_id(target_id), bus(bus), max_luns(max_luns) {}
~AbstractController() override = default;
virtual void Error(scsi_defs::sense_key, scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
scsi_defs::status = scsi_defs::status::CHECK_CONDITION) = 0;
@ -81,19 +69,19 @@ public:
int GetTargetId() const { return target_id; }
int GetMaxLuns() const { return max_luns; }
bool HasLuns() const { return !luns.empty(); }
int GetLunCount() const { return (int)luns.size(); }
PrimaryDevice *GetDeviceForLun(int) const;
bool AddDevice(PrimaryDevice *);
bool DeleteDevice(const PrimaryDevice *);
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 HasDeviceForLun(int) const;
int ExtractInitiatorId(int id_data) const;
int ExtractInitiatorId(int) const;
void AllocateBuffer(size_t);
vector<BYTE>& GetBuffer() { return ctrl.buffer; }
size_t GetBufferSize() const { return ctrl.buffer.size(); }
uint32_t GetStatus() const { return ctrl.status; }
void SetStatus(uint32_t s) { ctrl.status = s; }
scsi_defs::status GetStatus() const { return ctrl.status; }
void SetStatus(scsi_defs::status s) { ctrl.status = s; }
uint32_t GetLength() const { return ctrl.length; }
protected:
@ -106,25 +94,15 @@ protected:
vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; }
bool HasValidLength() const { return ctrl.length != 0; }
int GetOffset() const { return ctrl.offset; }
void ResetOffset() { ctrl.offset = 0; }
void UpdateOffsetAndLength() { ctrl.offset += ctrl.length; ctrl.length = 0; }
BUS::phase_t GetPhase() const { return phase; }
void SetPhase(BUS::phase_t p) { phase = p; }
bool IsSelection() const { return phase == BUS::phase_t::selection; }
bool IsBusFree() const { return phase == BUS::phase_t::busfree; }
bool IsCommand() const { return phase == BUS::phase_t::command; }
bool IsStatus() const { return phase == BUS::phase_t::status; }
bool IsDataIn() const { return phase == BUS::phase_t::datain; }
bool IsDataOut() const { return phase == BUS::phase_t::dataout; }
bool IsMsgIn() const { return phase == BUS::phase_t::msgin; }
bool IsMsgOut() const { return phase == BUS::phase_t::msgout; }
private:
int target_id;
shared_ptr<BUS> bus;
BUS& bus;
int max_luns;

View File

@ -9,23 +9,33 @@
#include "devices/device_factory.h"
#include "devices/primary_device.h"
#include "devices/file_support.h"
#include "scsi_controller.h"
#include "controller_manager.h"
using namespace std;
bool ControllerManager::CreateScsiController(shared_ptr<BUS> bus, PrimaryDevice *device)
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
{
shared_ptr<AbstractController> controller = FindController(device->GetId());
auto controller = FindController(id);
if (controller == nullptr) {
controller = make_shared<ScsiController>(bus, device->GetId());
controllers[device->GetId()] = controller;
controller = make_shared<ScsiController>(bus, id);
if (controller->AddDevice(device)) {
controllers[id] = controller;
return true;
}
return false;
}
return controller->AddDevice(device);
}
bool ControllerManager::DeleteController(shared_ptr<AbstractController> controller)
{
return controllers.erase(controller->GetTargetId()) == 1;
}
shared_ptr<AbstractController> ControllerManager::IdentifyController(int data) const
{
for (const auto& [id, controller] : controllers) {
@ -43,6 +53,18 @@ shared_ptr<AbstractController> ControllerManager::FindController(int target_id)
return it == controllers.end() ? nullptr : it->second;
}
unordered_set<shared_ptr<PrimaryDevice>> ControllerManager::GetAllDevices() const
{
unordered_set<shared_ptr<PrimaryDevice>> devices;
for (const auto& [id, controller] : controllers) {
const auto& d = controller->GetDevices();
devices.insert(d.begin(), d.end());
}
return devices;
}
void ControllerManager::DeleteAllControllers()
{
controllers.clear();
@ -55,7 +77,7 @@ void ControllerManager::ResetAllControllers() const
}
}
PrimaryDevice *ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
{
if (const auto controller = FindController(id); controller != nullptr) {
return controller->GetDeviceForLun(lun);

View File

@ -12,9 +12,10 @@
#pragma once
#include <unordered_map>
#include <unordered_set>
#include <memory>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class BUS;
class AbstractController;
@ -22,23 +23,24 @@ class PrimaryDevice;
class ControllerManager
{
BUS& bus;
unordered_map<int, shared_ptr<AbstractController>> controllers;
public:
ControllerManager() = default;
explicit ControllerManager(BUS& bus) : bus(bus) {}
~ControllerManager() = default;
ControllerManager(ControllerManager&) = delete;
ControllerManager& operator=(const ControllerManager&) = delete;
// Maximum number of controller devices
static const int DEVICE_MAX = 8;
bool CreateScsiController(shared_ptr<BUS>, PrimaryDevice *);
bool AttachToScsiController(int, shared_ptr<PrimaryDevice>);
bool DeleteController(shared_ptr<AbstractController>);
shared_ptr<AbstractController> IdentifyController(int) const;
shared_ptr<AbstractController> FindController(int) const;
unordered_set<shared_ptr<PrimaryDevice>> GetAllDevices() const;
void DeleteAllControllers();
void ResetAllControllers() const;
PrimaryDevice *GetDeviceByIdAndLun(int, int) const;
unordered_map<int, shared_ptr<AbstractController>> controllers;
shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
};

View File

@ -0,0 +1,46 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#pragma once
#include "scsi.h"
class PhaseHandler
{
BUS::phase_t phase = BUS::phase_t::busfree;
public:
PhaseHandler() = default;
virtual ~PhaseHandler() = default;
virtual void BusFree() = 0;
virtual void Selection() = 0;
virtual void Command() = 0;
virtual void Status() = 0;
virtual void DataIn() = 0;
virtual void DataOut() = 0;
virtual void MsgIn() = 0;
virtual void MsgOut() = 0;
virtual BUS::phase_t Process(int) = 0;
protected:
BUS::phase_t GetPhase() const { return phase; }
void SetPhase(BUS::phase_t p) { phase = p; }
bool IsSelection() const { return phase == BUS::phase_t::selection; }
bool IsBusFree() const { return phase == BUS::phase_t::busfree; }
bool IsCommand() const { return phase == BUS::phase_t::command; }
bool IsStatus() const { return phase == BUS::phase_t::status; }
bool IsDataIn() const { return phase == BUS::phase_t::datain; }
bool IsDataOut() const { return phase == BUS::phase_t::dataout; }
bool IsMsgIn() const { return phase == BUS::phase_t::msgin; }
bool IsMsgOut() const { return phase == BUS::phase_t::msgout; }
};

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -21,13 +21,13 @@
#include "scsi_controller.h"
#include <sstream>
#include <iomanip>
#ifdef __linux
#ifdef __linux__
#include <linux/if_tun.h>
#endif
using namespace scsi_defs;
ScsiController::ScsiController(shared_ptr<BUS> bus, int target_id) : AbstractController(bus, target_id, LUN_MAX)
ScsiController::ScsiController(BUS& bus, int target_id) : AbstractController(bus, target_id, LUN_MAX)
{
// The initial buffer size will default to either the default buffer size OR
// the size of an Ethernet message, whichever is larger.
@ -52,17 +52,17 @@ void ScsiController::Reset()
BUS::phase_t ScsiController::Process(int id)
{
// Get bus information
bus->Acquire();
bus.Acquire();
// Check to see if the reset signal was asserted
if (bus->GetRST()) {
if (bus.GetRST()) {
LOGWARN("RESET signal received!")
// Reset the controller
Reset();
// Reset the bus
bus->Reset();
bus.Reset();
return GetPhase();
}
@ -79,14 +79,14 @@ BUS::phase_t ScsiController::Process(int id)
try {
ProcessPhase();
}
catch(const scsi_error_exception&) {
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();
bus->Reset();
bus.Reset();
BusFree();
}
@ -101,14 +101,14 @@ void ScsiController::BusFree()
SetPhase(BUS::phase_t::busfree);
bus->SetREQ(false);
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(false);
bus->SetBSY(false);
bus.SetREQ(false);
bus.SetMSG(false);
bus.SetCD(false);
bus.SetIO(false);
bus.SetBSY(false);
// Initialize status and message
SetStatus(0);
SetStatus(status::GOOD);
ctrl.message = 0x00;
// Initialize ATN message reception status
@ -120,21 +120,21 @@ void ScsiController::BusFree()
bytes_to_transfer = 0;
// When the bus is free RaSCSI or the Pi may be shut down.
// TODO Try to find a better place for this code without breaking encapsulation
// This code has to be executed in the bus free phase and thus has to be located in the controller.
switch(shutdown_mode) {
case AbstractController::rascsi_shutdown_mode::STOP_RASCSI:
case rascsi_shutdown_mode::STOP_RASCSI:
LOGINFO("RaSCSI shutdown requested")
exit(0);
break;
case AbstractController::rascsi_shutdown_mode::STOP_PI:
case rascsi_shutdown_mode::STOP_PI:
LOGINFO("Raspberry Pi shutdown requested")
if (system("init 0") == -1) {
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno))
}
break;
case AbstractController::rascsi_shutdown_mode::RESTART_PI:
case rascsi_shutdown_mode::RESTART_PI:
LOGINFO("Raspberry Pi restart requested")
if (system("init 6") == -1) {
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno))
@ -149,7 +149,7 @@ void ScsiController::BusFree()
}
// Move to selection phase
if (bus->GetSEL() && !bus->GetBSY()) {
if (bus.GetSEL() && !bus.GetBSY()) {
Selection();
}
}
@ -158,12 +158,12 @@ void ScsiController::Selection()
{
if (!IsSelection()) {
// A different device controller was selected
if (int id = 1 << GetTargetId(); ((int)bus->GetDAT() & id) == 0) {
if (int id = 1 << GetTargetId(); ((int)bus.GetDAT() & id) == 0) {
return;
}
// Abort if there is no LUN for this controller
if (!HasLuns()) {
if (!GetLunCount()) {
return;
}
@ -172,14 +172,14 @@ void ScsiController::Selection()
SetPhase(BUS::phase_t::selection);
// Raise BSY and respond
bus->SetBSY(true);
bus.SetBSY(true);
return;
}
// Selection completed
if (!bus->GetSEL() && bus->GetBSY()) {
if (!bus.GetSEL() && bus.GetBSY()) {
// Message out phase if ATN=1, otherwise command phase
if (bus->GetATN()) {
if (bus.GetATN()) {
MsgOut();
} else {
Command();
@ -194,12 +194,12 @@ void ScsiController::Command()
SetPhase(BUS::phase_t::command);
bus->SetMSG(false);
bus->SetCD(true);
bus->SetIO(false);
bus.SetMSG(false);
bus.SetCD(true);
bus.SetIO(false);
int actual_count = bus->CommandHandShake(GetBuffer().data());
int command_byte_count = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
const int actual_count = bus.CommandHandShake(GetBuffer().data());
const int command_byte_count = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
// If not able to receive all, move to the status phase
if (actual_count != command_byte_count) {
@ -229,8 +229,6 @@ void ScsiController::Execute()
{
LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (int)GetOpcode())
SetPhase(BUS::phase_t::execute);
// Initialization for data transfer
ResetOffset();
ctrl.blocks = 1;
@ -238,7 +236,7 @@ void ScsiController::Execute()
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
SetStatus(0);
SetStatus(status::GOOD);
}
int lun = GetEffectiveLun();
@ -258,21 +256,21 @@ void ScsiController::Execute()
}
}
PrimaryDevice *device = GetDeviceForLun(lun);
auto device = GetDeviceForLun(lun);
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
if (GetOpcode() != scsi_command::eCmdRequestSense) {
device->SetStatusCode(0);
}
try {
if (!device->Dispatch(GetOpcode())) {
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetTargetId(), lun, (int)GetOpcode())
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
}
catch(const scsi_error_exception& e) { //NOSONAR This exception is handled properly
catch(const scsi_exception& e) { //NOSONAR This exception is handled properly
Error(e.get_sense_key(), e.get_asc(), e.get_status());
// Fall through
@ -298,14 +296,14 @@ void ScsiController::Status()
SysTimer::instance().SleepUsec(5);
}
LOGTRACE("%s Status Phase $%02X",__PRETTY_FUNCTION__, GetStatus())
LOGTRACE("%s Status Phase, status is $%02X",__PRETTY_FUNCTION__, (int)GetStatus())
SetPhase(BUS::phase_t::status);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(true);
bus->SetIO(true);
bus.SetMSG(false);
bus.SetCD(true);
bus.SetIO(true);
// Data transfer is 1 byte x 1 block
ResetOffset();
@ -326,9 +324,9 @@ void ScsiController::MsgIn()
SetPhase(BUS::phase_t::msgin);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(true);
bus.SetMSG(true);
bus.SetCD(true);
bus.SetIO(true);
// length, blocks are already set
assert(HasValidLength());
@ -357,9 +355,9 @@ void ScsiController::MsgOut()
SetPhase(BUS::phase_t::msgout);
bus->SetMSG(true);
bus->SetCD(true);
bus->SetIO(false);
bus.SetMSG(true);
bus.SetCD(true);
bus.SetIO(false);
// Data transfer is 1 byte x 1 block
ResetOffset();
@ -390,9 +388,9 @@ void ScsiController::DataIn()
SetPhase(BUS::phase_t::datain);
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(true);
bus.SetMSG(false);
bus.SetCD(false);
bus.SetIO(true);
// length, blocks are already set
assert(ctrl.blocks > 0);
@ -423,9 +421,9 @@ void ScsiController::DataOut()
SetPhase(BUS::phase_t::dataout);
// Signal line operated by the target
bus->SetMSG(false);
bus->SetCD(false);
bus->SetIO(false);
bus.SetMSG(false);
bus.SetCD(false);
bus.SetIO(false);
ResetOffset();
return;
@ -437,12 +435,12 @@ void ScsiController::DataOut()
void ScsiController::Error(sense_key sense_key, asc asc, status status)
{
// Get bus information
bus->Acquire();
bus.Acquire();
// Reset check
if (bus->GetRST()) {
if (bus.GetRST()) {
Reset();
bus->Reset();
bus.Reset();
return;
}
@ -461,11 +459,13 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
}
if (sense_key != sense_key::NO_SENSE || asc != asc::NO_ADDITIONAL_SENSE_INFORMATION) {
LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X", (int)sense_key, (int)asc)
// Set Sense Key and ASC for a subsequent REQUEST SENSE
GetDeviceForLun(lun)->SetStatusCode(((int)sense_key << 16) | ((int)asc << 8));
}
SetStatus((uint32_t)status);
SetStatus(status);
ctrl.message = 0x00;
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__)
@ -475,16 +475,16 @@ void ScsiController::Error(sense_key sense_key, asc asc, status status)
void ScsiController::Send()
{
assert(!bus->GetREQ());
assert(bus->GetIO());
assert(!bus.GetREQ());
assert(bus.GetIO());
if (HasValidLength()) {
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(GetOffset()) + ", length "
+ to_string(ctrl.length)).c_str())
// TODO The delay has to be taken from ctrl.unit[lun], but as there are currently no Daynaport drivers for
// LUNs other than 0 this work-around works.
if (int len = bus->SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length,
if (const int len = bus.SendHandShake(GetBuffer().data() + ctrl.offset, ctrl.length,
HasDeviceForLun(0) ? GetDeviceForLun(0)->GetSendDelay() : 0);
len != (int)ctrl.length) {
// If you cannot send all, move to status phase
@ -571,15 +571,15 @@ void ScsiController::Receive()
LOGTRACE("%s",__PRETTY_FUNCTION__)
// REQ is low
assert(!bus->GetREQ());
assert(!bus->GetIO());
assert(!bus.GetREQ());
assert(!bus.GetIO());
// Length != 0 if received
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (int len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length);
if (int len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != (int)ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",__PRETTY_FUNCTION__, ctrl.length, len)
Error(sense_key::ABORTED_COMMAND);
@ -625,6 +625,7 @@ void ScsiController::Receive()
}
// If result FALSE, move to status phase
// TODO Check whether we can raise scsi_exception here in order to simplify the error handling
if (!result) {
Error(sense_key::ABORTED_COMMAND);
return;
@ -648,7 +649,8 @@ void ScsiController::Receive()
break;
case BUS::phase_t::dataout:
FlushUnit();
// Block-oriented data have been handled above
DataOutNonBlockOriented();
Status();
break;
@ -675,14 +677,14 @@ bool ScsiController::XferMsg(int msg)
void ScsiController::ReceiveBytes()
{
assert(!bus->GetREQ());
assert(!bus->GetIO());
assert(!bus.GetREQ());
assert(!bus.GetIO());
if (HasValidLength()) {
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length)
// If not able to receive all, move to status phase
if (uint32_t len = bus->ReceiveHandShake(GetBuffer().data() + ctrl.offset, ctrl.length);
if (uint32_t len = bus.ReceiveHandShake(GetBuffer().data() + GetOffset(), ctrl.length);
len != ctrl.length) {
LOGERROR("%s Not able to receive %d bytes of data, only received %d",
__PRETTY_FUNCTION__, ctrl.length, len)
@ -769,17 +771,12 @@ bool ScsiController::XferOut(bool cont)
return false;
}
void ScsiController::FlushUnit()
void ScsiController::DataOutNonBlockOriented()
{
assert(IsDataOut());
auto disk = dynamic_cast<Disk *>(GetDeviceForLun(GetEffectiveLun()));
if (disk == nullptr) {
return;
}
// WRITE system only
switch (GetOpcode()) {
// TODO Check why these cases are needed
case scsi_command::eCmdWrite6:
case scsi_command::eCmdWrite10:
case scsi_command::eCmdWrite16:
@ -790,26 +787,24 @@ void ScsiController::FlushUnit()
break;
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10:
// TODO What is this special handling of ModeSelect good for?
// Without it we would not need this method at all.
// ModeSelect is already handled in XferOutBlockOriented(). Why would it have to be handled once more?
try {
disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset);
case scsi_command::eCmdModeSelect10: {
// 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());
}
else {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
}
catch(const scsi_error_exception& e) {
LOGWARN("Error occured while processing Mode Select command %02X\n", (int)GetOpcode())
Error(e.get_sense_key(), e.get_asc(), e.get_status());
return;
}
break;
break;
case scsi_command::eCmdSetMcastAddr:
// TODO: Eventually, we should store off the multicast address configuration data here...
break;
default:
LOGWARN("Received an unexpected flush command $%02X\n", (int)GetOpcode())
LOGWARN("Unexpected Data Out phase for command $%02X", (int)GetOpcode())
break;
}
}
@ -838,9 +833,9 @@ bool ScsiController::XferIn(vector<BYTE>& buf)
case scsi_command::eCmdRead16:
// Read from disk
try {
ctrl.length = (static_cast<Disk *>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next);
ctrl.length = (dynamic_pointer_cast<Disk>(GetDeviceForLun(lun)))->Read(ctrl.cmd, buf, ctrl.next);
}
catch(const scsi_error_exception&) {
catch(const scsi_exception&) {
// If there is an error, go to the status phase
return false;
}
@ -868,7 +863,7 @@ bool ScsiController::XferIn(vector<BYTE>& buf)
//---------------------------------------------------------------------------
bool ScsiController::XferOutBlockOriented(bool cont)
{
auto disk = dynamic_cast<Disk *>(GetDeviceForLun(GetEffectiveLun()));
auto disk = dynamic_pointer_cast<Disk>(GetDeviceForLun(GetEffectiveLun()));
if (disk == nullptr) {
return false;
}
@ -878,9 +873,9 @@ bool ScsiController::XferOutBlockOriented(bool cont)
case scsi_command::eCmdModeSelect6:
case scsi_command::eCmdModeSelect10:
try {
disk->ModeSelect(ctrl.cmd, GetBuffer(), ctrl.offset);
disk->ModeSelect(ctrl.cmd, GetBuffer(), GetOffset());
}
catch(const scsi_error_exception& e) {
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc(), e.get_status());
return false;
}
@ -895,7 +890,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
{
// Special case Write function for brige
// TODO This class must not know about SCSIBR
if (auto bridge = dynamic_cast<SCSIBR *>(disk); bridge) {
if (auto bridge = dynamic_pointer_cast<SCSIBR>(disk); bridge) {
if (!bridge->WriteBytes(ctrl.cmd, GetBuffer(), ctrl.length)) {
// Write failed
return false;
@ -907,7 +902,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
// Special case Write function for DaynaPort
// TODO This class must not know about DaynaPort
if (auto daynaport = dynamic_cast<SCSIDaynaPort *>(disk); daynaport) {
if (auto daynaport = dynamic_pointer_cast<SCSIDaynaPort>(disk); daynaport) {
daynaport->WriteBytes(ctrl.cmd, GetBuffer(), 0);
ResetOffset();
@ -918,7 +913,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
try {
disk->Write(ctrl.cmd, GetBuffer(), ctrl.next - 1);
}
catch(const scsi_error_exception& e) {
catch(const scsi_exception& e) {
Error(e.get_sense_key(), e.get_asc(), e.get_status());
// Write failed
@ -935,7 +930,7 @@ bool ScsiController::XferOutBlockOriented(bool cont)
try {
ctrl.length = disk->WriteCheck(ctrl.next - 1);
}
catch(const scsi_error_exception&) {
catch(const scsi_exception&) {
// Cannot write
return false;
}
@ -961,9 +956,10 @@ void ScsiController::ProcessCommand()
uint32_t len = GPIOBUS::GetCommandByteCount(GetBuffer()[0]);
stringstream s;
s << setfill('0') << setw(2) << hex;
for (uint32_t i = 0; i < len; i++) {
ctrl.cmd[i] = GetBuffer()[i];
s << setfill('0') << setw(2) << hex << ctrl.cmd[i];
s << ctrl.cmd[i];
}
LOGTRACE("%s CDB=$%s",__PRETTY_FUNCTION__, s.str().c_str())
@ -974,7 +970,7 @@ void ScsiController::ParseMessage()
{
int i = 0;
while (i < scsi.msc) {
BYTE message_type = scsi.msb[i];
const BYTE message_type = scsi.msb[i];
if (message_type == 0x06) {
LOGTRACE("Received ABORT message")
@ -1036,7 +1032,7 @@ void ScsiController::ParseMessage()
void ScsiController::ProcessMessage()
{
// Continue message out phase as long as ATN keeps asserting
if (bus->GetATN()) {
if (bus.GetATN()) {
// Data transfer is 1 byte x 1 block
ResetOffset();
ctrl.length = 1;
@ -1062,8 +1058,8 @@ int ScsiController::GetEffectiveLun() const
void ScsiController::Sleep()
{
if (uint32_t time = SysTimer::instance().GetTimerLow() - execstart; time < MIN_EXEC_TIME) {
SysTimer::instance().SleepUsec(MIN_EXEC_TIME - time);
if (const uint32_t time = SysTimer::GetTimerLow() - execstart; time < MIN_EXEC_TIME) {
SysTimer::SleepUsec(MIN_EXEC_TIME - time);
}
execstart = 0;
}

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -54,10 +54,8 @@ public:
// Maximum number of logical units
static const int LUN_MAX = 32;
ScsiController(shared_ptr<BUS>, int);
ScsiController(BUS&, int);
~ScsiController() override = default;
ScsiController(ScsiController&) = delete;
ScsiController& operator=(const ScsiController&) = delete;
void Reset() override;
@ -105,7 +103,7 @@ private:
void ReceiveBytes();
void Execute();
void FlushUnit();
void DataOutNonBlockOriented();
void Receive();
void ProcessCommand();

View File

@ -7,12 +7,13 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
#include "cd_track.h"
#include <cassert>
void CDTrack::Init(int track, uint32_t first, uint32_t last)
{

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
//---------------------------------------------------------------------------
@ -22,10 +22,8 @@ public:
CDTrack() = default;
~CDTrack() = default;
CDTrack(CDTrack&) = delete;
CDTrack& operator=(const CDTrack&) = delete;
void Init(int track, DWORD first, DWORD last);
void Init(int track, uint32_t first, uint32_t last);
// Properties
void SetPath(bool cdda, const Filepath& path); // Set the path
@ -34,7 +32,7 @@ public:
uint32_t GetLast() const; // Get the last LBA
uint32_t GetBlocks() const; // Get the number of blocks
int GetTrackNo() const; // Get the track number
bool IsValid(DWORD lba) const; // Is this a valid LBA?
bool IsValid(uint32_t lba) const; // Is this a valid LBA?
bool IsAudio() const; // Is this an audio track?
private:

View File

@ -48,7 +48,7 @@ static void convert(char const *src, char const *dest,
return;
}
if (size_t ret = iconv(cd, &inbuf, &in, &outbuf, &out); ret == (size_t)-1) {
if (const size_t ret = iconv(cd, &inbuf, &in, &outbuf, &out); ret == (size_t)-1) {
return;
}
@ -166,7 +166,7 @@ void Human68k::namests_t::GetCopyFilename(BYTE* szFilename) const
if (i >= 8) {
// Transfer the extraneous part
for (i = 0; i < 10; i++) {
BYTE c = add[i];
const BYTE c = add[i];
if (c == '\0')
break;
*p++ = c;
@ -178,7 +178,7 @@ void Human68k::namests_t::GetCopyFilename(BYTE* szFilename) const
if (ext[0] != ' ' || ext[1] != ' ' || ext[2] != ' ') {
*p++ = '.';
for (i = 0; i < 3; i++) {
BYTE c = ext[i];
const BYTE c = ext[i];
if (c == ' ') {
// Check that the file extension continues after a space is detected
/// TODO: Should change this function to be compatible with 8+3 chars and TwentyOne
@ -226,7 +226,7 @@ CHostDrv::~CHostDrv()
// Initialization (device boot and load)
//
//---------------------------------------------------------------------------
void CHostDrv::Init(const TCHAR* szBase, DWORD nFlag)
void CHostDrv::Init(const TCHAR* szBase, uint32_t nFlag)
{
assert(szBase);
assert(strlen(szBase) < FILEPATH_MAX);
@ -253,7 +253,7 @@ void CHostDrv::Init(const TCHAR* szBase, DWORD nFlag)
TCHAR* pClear = nullptr;
TCHAR* p = m_szBase;
for (;;) {
TCHAR c = *p;
const TCHAR c = *p;
if (c == '\0')
break;
if (c == '/' || c == '\\') {
@ -301,7 +301,7 @@ BYTE CHostDrv::GetMediaByte() const
// Get drive status
//
//---------------------------------------------------------------------------
DWORD CHostDrv::GetStatus() const
uint32_t CHostDrv::GetStatus() const
{
return 0x40 | (m_bEnable ? (m_bWriteProtect ? 0x08 : 0) | 0x02 : 0);
}
@ -408,15 +408,15 @@ bool CHostDrv::GetVolumeCache(TCHAR* szLabel) const
return m_bVolumeCache;
}
DWORD CHostDrv::GetCapacity(Human68k::capacity_t* pCapacity)
uint32_t CHostDrv::GetCapacity(Human68k::capacity_t* pCapacity)
{
assert(pCapacity);
assert(m_bEnable);
DWORD nFree = 0x7FFF8000;
DWORD freearea;
DWORD clusters;
DWORD sectors;
const uint32_t nFree = 0x7FFF8000;
uint32_t freearea;
uint32_t clusters;
uint32_t sectors;
freearea = 0xFFFF;
clusters = 0xFFFF;
@ -428,9 +428,9 @@ DWORD CHostDrv::GetCapacity(Human68k::capacity_t* pCapacity)
assert(sectors <= 64);
// Update cache
m_capCache.freearea = (WORD)freearea;
m_capCache.clusters = (WORD)clusters;
m_capCache.sectors = (WORD)sectors;
m_capCache.freearea = (uint16_t)freearea;
m_capCache.clusters = (uint16_t)clusters;
m_capCache.sectors = (uint16_t)sectors;
m_capCache.bytes = 512;
// Transfer contents
@ -808,7 +808,7 @@ void CHostFilename::ConvertHuman(int nCount)
}
size_t nMax = 18; // Number of bytes for the base segment (base name and extension)
DWORD nOption = CFileSys::GetFileOption();
uint32_t nOption = CFileSys::GetFileOption();
if (nOption & WINDRV_OPT_CONVERT_LENGTH)
nMax = 8;
@ -817,7 +817,7 @@ void CHostFilename::ConvertHuman(int nCount)
BYTE* pNumber = nullptr;
if (nCount >= 0) {
pNumber = &szNumber[8];
for (DWORD i = 0; i < 5; i++) { // Max 5+1 digits (always leave the first 2 bytes of the base name)
for (uint32_t i = 0; i < 5; i++) { // Max 5+1 digits (always leave the first 2 bytes of the base name)
int n = nCount % 36;
nMax--;
pNumber--;
@ -1082,7 +1082,7 @@ bool CHostFilename::isReduce() const
/// Evaluate Human68k directory entry attribute
//
//---------------------------------------------------------------------------
int CHostFilename::CheckAttribute(DWORD nHumanAttribute) const
int CHostFilename::CheckAttribute(uint32_t nHumanAttribute) const
{
BYTE nAttribute = m_dirHuman.attr;
if ((nAttribute & (Human68k::AT_ARCHIVE | Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) == 0)
@ -1099,7 +1099,7 @@ int CHostFilename::CheckAttribute(DWORD nHumanAttribute) const
const BYTE* CHostFilename::SeparateExt(const BYTE* szHuman) // static
{
// Obtain the file name length
size_t nLength = strlen((const char*)szHuman);
const size_t nLength = strlen((const char*)szHuman);
const BYTE* pFirst = szHuman;
const BYTE* pLast = pFirst + nLength;
@ -1124,7 +1124,7 @@ const BYTE* CHostFilename::SeparateExt(const BYTE* szHuman) // static
//
//===========================================================================
DWORD CHostPath::g_nId; ///< Identifier creation counter
uint32_t CHostPath::g_nId; ///< Identifier creation counter
CHostPath::~CHostPath()
{
@ -1144,7 +1144,7 @@ CHostPath::ring_t* CHostPath::Alloc(size_t nLength) // static
{
assert(nLength < FILEPATH_MAX);
size_t n = offsetof(ring_t, f) + CHostFilename::Offset() + (nLength + 1) * sizeof(TCHAR);
const size_t n = offsetof(ring_t, f) + CHostFilename::Offset() + (nLength + 1) * sizeof(TCHAR);
auto p = (ring_t*)malloc(n);
assert(p);
@ -1295,8 +1295,8 @@ bool CHostPath::isSameHuman(const BYTE* szHuman) const
assert(szHuman);
// Calulate number of chars
size_t nLength = strlen((const char*)m_szHuman);
size_t n = strlen((const char*)szHuman);
const size_t nLength = strlen((const char*)m_szHuman);
const size_t n = strlen((const char*)szHuman);
// Check number of chars
if (nLength != n)
@ -1331,7 +1331,7 @@ bool CHostPath::isSameChild(const BYTE* szHuman) const
/// Make sure to lock from the top.
//
//---------------------------------------------------------------------------
const CHostFilename* CHostPath::FindFilename(const BYTE* szHuman, DWORD nHumanAttribute) const
const CHostFilename* CHostPath::FindFilename(const BYTE* szHuman, uint32_t nHumanAttribute) const
{
assert(szHuman);
@ -1368,7 +1368,7 @@ const CHostFilename* CHostPath::FindFilename(const BYTE* szHuman, DWORD nHumanAt
/// Make sure to lock from the top.
//
//---------------------------------------------------------------------------
const CHostFilename* CHostPath::FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const
const CHostFilename* CHostPath::FindFilenameWildcard(const BYTE* szHuman, uint32_t nHumanAttribute, find_t* pFind) const
{
assert(szHuman);
assert(pFind);
@ -1386,7 +1386,7 @@ const CHostFilename* CHostPath::FindFilenameWildcard(const BYTE* szHuman, DWORD
p = pFind->pos;
} else {
// Find the start position in the directory entry contents
DWORD n = 0;
uint32_t n = 0;
for (;; p = (const ring_t*)p->r.Next()) {
if (p == (const ring_t*)&m_cRing) {
// Extrapolate from the count when the same entry isn't found (just in case)
@ -1547,7 +1547,7 @@ void CHostPath::Refresh()
// - No duplicated names in previous entries
// - No entity with the same name exists
if (pFilename->isReduce() || !pFilename->isCorrect()) { // Confirm that file name update is required
for (DWORD n = 0; n < XM6_HOST_FILENAME_PATTERN_MAX; n++) {
for (uint32_t n = 0; n < XM6_HOST_FILENAME_PATTERN_MAX; n++) {
// Confirm file name validity
if (pFilename->isCorrect()) {
// Confirm match with previous entry
@ -1583,14 +1583,14 @@ void CHostPath::Refresh()
nHumanAttribute |= Human68k::AT_READONLY;
pFilename->SetEntryAttribute(nHumanAttribute);
auto nHumanSize = (DWORD)sb.st_size;
auto nHumanSize = (uint32_t)sb.st_size;
pFilename->SetEntrySize(nHumanSize);
WORD nHumanDate = 0;
WORD nHumanTime = 0;
uint16_t nHumanDate = 0;
uint16_t nHumanTime = 0;
if (tm pt = {}; localtime_r(&sb.st_mtime, &pt) != nullptr) {
nHumanDate = (WORD)(((pt.tm_year - 80) << 9) | ((pt.tm_mon + 1) << 5) | pt.tm_mday);
nHumanTime = (WORD)((pt.tm_hour << 11) | (pt.tm_min << 5) | (pt.tm_sec >> 1));
nHumanDate = (uint16_t)(((pt.tm_year - 80) << 9) | ((pt.tm_mon + 1) << 5) | pt.tm_mday);
nHumanTime = (uint16_t)((pt.tm_hour << 11) | (pt.tm_min << 5) | (pt.tm_sec >> 1));
}
pFilename->SetEntryDate(nHumanDate);
pFilename->SetEntryTime(nHumanTime);
@ -1760,7 +1760,7 @@ void CHostEntry::CleanCache() const
/// Update the cache for the specified unit
//
//---------------------------------------------------------------------------
void CHostEntry::CleanCache(DWORD nUnit) const
void CHostEntry::CleanCache(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1773,7 +1773,7 @@ void CHostEntry::CleanCache(DWORD nUnit) const
/// Update the cache for the specified path
//
//---------------------------------------------------------------------------
void CHostEntry::CleanCache(DWORD nUnit, const BYTE* szHumanPath) const
void CHostEntry::CleanCache(uint32_t nUnit, const BYTE* szHumanPath) const
{
assert(szHumanPath);
assert(nUnit < DRIVE_MAX);
@ -1787,7 +1787,7 @@ void CHostEntry::CleanCache(DWORD nUnit, const BYTE* szHumanPath) const
/// Update all cache for the specified path and below
//
//---------------------------------------------------------------------------
void CHostEntry::CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath) const
void CHostEntry::CleanCacheChild(uint32_t nUnit, const BYTE* szHumanPath) const
{
assert(szHumanPath);
assert(nUnit < DRIVE_MAX);
@ -1801,7 +1801,7 @@ void CHostEntry::CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath) const
/// Delete cache for the specified path
//
//---------------------------------------------------------------------------
void CHostEntry::DeleteCache(DWORD nUnit, const BYTE* szHumanPath) const
void CHostEntry::DeleteCache(uint32_t nUnit, const BYTE* szHumanPath) const
{
assert(szHumanPath);
assert(nUnit < DRIVE_MAX);
@ -1815,7 +1815,7 @@ void CHostEntry::DeleteCache(DWORD nUnit, const BYTE* szHumanPath) const
/// Find host side names (path name + file name (can be abbreviated) + attribute)
//
//---------------------------------------------------------------------------
bool CHostEntry::Find(DWORD nUnit, CHostFiles* pFiles) const
bool CHostEntry::Find(uint32_t nUnit, CHostFiles* pFiles) const
{
assert(pFiles);
assert(nUnit < DRIVE_MAX);
@ -1829,7 +1829,7 @@ bool CHostEntry::Find(DWORD nUnit, CHostFiles* pFiles) const
/// Drive settings
//
//---------------------------------------------------------------------------
void CHostEntry::SetDrv(DWORD nUnit, CHostDrv* pDrv)
void CHostEntry::SetDrv(uint32_t nUnit, CHostDrv* pDrv)
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit] == nullptr);
@ -1842,7 +1842,7 @@ void CHostEntry::SetDrv(DWORD nUnit, CHostDrv* pDrv)
/// Is it write-protected?
//
//---------------------------------------------------------------------------
bool CHostEntry::isWriteProtect(DWORD nUnit) const
bool CHostEntry::isWriteProtect(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1855,7 +1855,7 @@ bool CHostEntry::isWriteProtect(DWORD nUnit) const
/// Is it accessible?
//
//---------------------------------------------------------------------------
bool CHostEntry::isEnable(DWORD nUnit) const
bool CHostEntry::isEnable(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1868,7 +1868,7 @@ bool CHostEntry::isEnable(DWORD nUnit) const
/// Media check
//
//---------------------------------------------------------------------------
bool CHostEntry::isMediaOffline(DWORD nUnit) const
bool CHostEntry::isMediaOffline(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1881,7 +1881,7 @@ bool CHostEntry::isMediaOffline(DWORD nUnit) const
/// Get media byte
//
//---------------------------------------------------------------------------
BYTE CHostEntry::GetMediaByte(DWORD nUnit) const
BYTE CHostEntry::GetMediaByte(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1894,7 +1894,7 @@ BYTE CHostEntry::GetMediaByte(DWORD nUnit) const
/// Get drive status
//
//---------------------------------------------------------------------------
DWORD CHostEntry::GetStatus(DWORD nUnit) const
uint32_t CHostEntry::GetStatus(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1907,7 +1907,7 @@ DWORD CHostEntry::GetStatus(DWORD nUnit) const
/// Media change check
//
//---------------------------------------------------------------------------
bool CHostEntry::CheckMedia(DWORD nUnit) const
bool CHostEntry::CheckMedia(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1920,7 +1920,7 @@ bool CHostEntry::CheckMedia(DWORD nUnit) const
/// Eject
//
//---------------------------------------------------------------------------
void CHostEntry::Eject(DWORD nUnit) const
void CHostEntry::Eject(uint32_t nUnit) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1933,7 +1933,7 @@ void CHostEntry::Eject(DWORD nUnit) const
/// Get volume label
//
//---------------------------------------------------------------------------
void CHostEntry::GetVolume(DWORD nUnit, TCHAR* szLabel) const
void CHostEntry::GetVolume(uint32_t nUnit, TCHAR* szLabel) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1946,7 +1946,7 @@ void CHostEntry::GetVolume(DWORD nUnit, TCHAR* szLabel) const
/// Get volume label from cache
//
//---------------------------------------------------------------------------
bool CHostEntry::GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const
bool CHostEntry::GetVolumeCache(uint32_t nUnit, TCHAR* szLabel) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1959,7 +1959,7 @@ bool CHostEntry::GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const
/// Get capacity
//
//---------------------------------------------------------------------------
DWORD CHostEntry::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) const
uint32_t CHostEntry::GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -1972,7 +1972,7 @@ DWORD CHostEntry::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) cons
/// Get cluster size from cache
//
//---------------------------------------------------------------------------
bool CHostEntry::GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const
bool CHostEntry::GetCapacityCache(uint32_t nUnit, Human68k::capacity_t* pCapacity) const
{
assert(nUnit < DRIVE_MAX);
assert(m_pDrv[nUnit]);
@ -2056,7 +2056,7 @@ void CHostFiles::SetPath(const Human68k::namests_t* pNamests)
/// Find file on the Human68k side and create data on the host side
//
//---------------------------------------------------------------------------
bool CHostFiles::Find(DWORD nUnit, const CHostEntry* pEntry)
bool CHostFiles::Find(uint32_t nUnit, const CHostEntry* pEntry)
{
assert(pEntry);
@ -2159,7 +2159,7 @@ void CHostFilesManager::Init()
assert(m_cRing.Prev() == &m_cRing);
// Allocate memory
for (DWORD i = 0; i < XM6_HOST_FILES_MAX; i++) {
for (uint32_t i = 0; i < XM6_HOST_FILES_MAX; i++) {
auto p = new ring_t();
p->r.Insert(&m_cRing);
}
@ -2180,7 +2180,7 @@ void CHostFilesManager::Clean()
}
}
CHostFiles* CHostFilesManager::Alloc(DWORD nKey)
CHostFiles* CHostFilesManager::Alloc(uint32_t nKey)
{
assert(nKey);
@ -2195,7 +2195,7 @@ CHostFiles* CHostFilesManager::Alloc(DWORD nKey)
return &p->f;
}
CHostFiles* CHostFilesManager::Search(DWORD nKey)
CHostFiles* CHostFilesManager::Search(uint32_t nKey)
{
// assert(nKey); // The search key may become 0 due to DPB damage
@ -2235,7 +2235,7 @@ void CHostFilesManager::Free(CHostFiles* pFiles)
/// Set file open mode
//
//---------------------------------------------------------------------------
bool CHostFcb::SetMode(DWORD nHumanMode)
bool CHostFcb::SetMode(uint32_t nHumanMode)
{
switch (nHumanMode & Human68k::OP_MASK) {
case Human68k::OP_READ:
@ -2279,7 +2279,7 @@ void CHostFcb::SetHumanPath(const BYTE* szHumanPath)
/// Return false if error is thrown.
//
//---------------------------------------------------------------------------
bool CHostFcb::Create(DWORD, bool bForce)
bool CHostFcb::Create(uint32_t, bool bForce)
{
assert((Human68k::AT_DIRECTORY | Human68k::AT_VOLUME) == 0);
assert(strlen(m_szFilename) > 0);
@ -2328,7 +2328,7 @@ bool CHostFcb::Open()
/// Return -1 if error is thrown.
//
//---------------------------------------------------------------------------
DWORD CHostFcb::Read(BYTE* pBuffer, DWORD nSize)
uint32_t CHostFcb::Read(BYTE* pBuffer, uint32_t nSize)
{
assert(pBuffer);
assert(m_pFile);
@ -2337,7 +2337,7 @@ DWORD CHostFcb::Read(BYTE* pBuffer, DWORD nSize)
if (ferror(m_pFile))
nResult = (size_t)-1;
return (DWORD)nResult;
return (uint32_t)nResult;
}
//---------------------------------------------------------------------------
@ -2348,7 +2348,7 @@ DWORD CHostFcb::Read(BYTE* pBuffer, DWORD nSize)
/// Return -1 if error is thrown.
//
//---------------------------------------------------------------------------
DWORD CHostFcb::Write(const BYTE* pBuffer, DWORD nSize)
uint32_t CHostFcb::Write(const BYTE* pBuffer, uint32_t nSize)
{
assert(pBuffer);
assert(m_pFile);
@ -2357,7 +2357,7 @@ DWORD CHostFcb::Write(const BYTE* pBuffer, DWORD nSize)
if (ferror(m_pFile))
nResult = (size_t)-1;
return (DWORD)nResult;
return (uint32_t)nResult;
}
//---------------------------------------------------------------------------
@ -2381,18 +2381,16 @@ bool CHostFcb::Truncate() const
/// Return -1 if error is thrown.
//
//---------------------------------------------------------------------------
DWORD CHostFcb::Seek(DWORD nOffset, DWORD nHumanSeek)
uint32_t CHostFcb::Seek(uint32_t nOffset, Human68k::seek_t nHumanSeek)
{
assert(nHumanSeek == Human68k::SK_BEGIN ||
nHumanSeek == Human68k::SK_CURRENT || nHumanSeek == Human68k::SK_END);
assert(m_pFile);
int nSeek;
switch (nHumanSeek) {
case Human68k::SK_BEGIN:
case Human68k::seek_t::SK_BEGIN:
nSeek = SEEK_SET;
break;
case Human68k::SK_CURRENT:
case Human68k::seek_t::SK_CURRENT:
nSeek = SEEK_CUR;
break;
// case SK_END:
@ -2401,9 +2399,9 @@ DWORD CHostFcb::Seek(DWORD nOffset, DWORD nHumanSeek)
break;
}
if (fseek(m_pFile, nOffset, nSeek))
return (DWORD)-1;
return (uint32_t)-1;
return (DWORD)ftell(m_pFile);
return (uint32_t)ftell(m_pFile);
}
//---------------------------------------------------------------------------
@ -2413,7 +2411,7 @@ DWORD CHostFcb::Seek(DWORD nOffset, DWORD nHumanSeek)
/// Return false if error is thrown.
//
//---------------------------------------------------------------------------
bool CHostFcb::TimeStamp(DWORD nHumanTime) const
bool CHostFcb::TimeStamp(uint32_t nHumanTime) const
{
assert(m_pFile || m_bFlag);
@ -2482,7 +2480,7 @@ void CHostFcbManager::Init()
assert(m_cRing.Prev() == &m_cRing);
// Memory allocation
for (DWORD i = 0; i < XM6_HOST_FCB_MAX; i++) {
for (uint32_t i = 0; i < XM6_HOST_FCB_MAX; i++) {
auto p = new ring_t;
assert(p);
p->r.Insert(&m_cRing);
@ -2503,7 +2501,7 @@ void CHostFcbManager::Clean() const
}
}
CHostFcb* CHostFcbManager::Alloc(DWORD nKey)
CHostFcb* CHostFcbManager::Alloc(uint32_t nKey)
{
assert(nKey);
@ -2525,7 +2523,7 @@ CHostFcb* CHostFcbManager::Alloc(DWORD nKey)
return &p->f;
}
CHostFcb* CHostFcbManager::Search(DWORD nKey)
CHostFcb* CHostFcbManager::Search(uint32_t nKey)
{
assert(nKey);
@ -2562,7 +2560,7 @@ void CHostFcbManager::Free(CHostFcb* pFcb)
//
//===========================================================================
DWORD CFileSys::g_nOption; // File name conversion flag
uint32_t CFileSys::g_nOption; // File name conversion flag
//---------------------------------------------------------------------------
//
@ -2609,7 +2607,7 @@ void CFileSys::Init()
m_cEntry.Init();
// Evaluate per-path setting validity
DWORD nDrives = m_nDrives;
uint32_t nDrives = m_nDrives;
if (nDrives == 0) {
// Use root directory instead of per-path settings
strcpy(m_szBase[0], "/");
@ -2618,8 +2616,8 @@ void CFileSys::Init()
}
// Register file system
DWORD nUnit = 0;
for (DWORD n = 0; n < nDrives; n++) {
uint32_t nUnit = 0;
for (uint32_t n = 0; n < nDrives; n++) {
// Don't register is base path do not exist
if (m_szBase[n][0] == '\0')
continue;
@ -2644,7 +2642,7 @@ void CFileSys::Init()
/// $40 - Device startup
//
//---------------------------------------------------------------------------
DWORD CFileSys::InitDevice(const Human68k::argument_t* pArgument)
uint32_t CFileSys::InitDevice(const Human68k::argument_t* pArgument)
{
InitOption(pArgument);
@ -2659,7 +2657,7 @@ DWORD CFileSys::InitDevice(const Human68k::argument_t* pArgument)
/// $41 - Directory check
//
//---------------------------------------------------------------------------
int CFileSys::CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
int CFileSys::CheckDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const
{
assert(pNamests);
@ -2686,7 +2684,7 @@ int CFileSys::CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
/// $42 - Create directory
//
//---------------------------------------------------------------------------
int CFileSys::MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
int CFileSys::MakeDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const
{
assert(pNamests);
@ -2728,7 +2726,7 @@ int CFileSys::MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
/// $43 - Delete directory
//
//---------------------------------------------------------------------------
int CFileSys::RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
int CFileSys::RemoveDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const
{
assert(pNamests);
@ -2778,7 +2776,7 @@ int CFileSys::RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests) const
/// $44 - Change file name
//
//---------------------------------------------------------------------------
int CFileSys::Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) const
int CFileSys::Rename(uint32_t nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) const
{
assert(pNamests);
@ -2836,7 +2834,7 @@ int CFileSys::Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Hum
/// $45 - Delete file
//
//---------------------------------------------------------------------------
int CFileSys::Delete(DWORD nUnit, const Human68k::namests_t* pNamests) const
int CFileSys::Delete(uint32_t nUnit, const Human68k::namests_t* pNamests) const
{
assert(pNamests);
@ -2876,7 +2874,7 @@ int CFileSys::Delete(DWORD nUnit, const Human68k::namests_t* pNamests) const
/// $46 - Get/set file attribute
//
//---------------------------------------------------------------------------
int CFileSys::Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute) const
int CFileSys::Attribute(uint32_t nUnit, const Human68k::namests_t* pNamests, uint32_t nHumanAttribute) const
{
assert(pNamests);
@ -2910,7 +2908,7 @@ int CFileSys::Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD
return FS_FATAL_WRITEPROTECT;
// Generate attribute
if (DWORD nAttribute = (nHumanAttribute & Human68k::AT_READONLY) | (f.GetAttribute() & ~Human68k::AT_READONLY);
if (uint32_t nAttribute = (nHumanAttribute & Human68k::AT_READONLY) | (f.GetAttribute() & ~Human68k::AT_READONLY);
f.GetAttribute() != nAttribute) {
struct stat sb;
if (stat(S2U(f.GetPath()), &sb))
@ -2941,7 +2939,7 @@ int CFileSys::Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD
/// $47 - File search
//
//---------------------------------------------------------------------------
int CFileSys::Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles)
int CFileSys::Files(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles)
{
assert(pNamests);
assert(nKey);
@ -3027,7 +3025,7 @@ int CFileSys::Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests
/// $48 - Search next file
//
//---------------------------------------------------------------------------
int CFileSys::NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles)
int CFileSys::NFiles(uint32_t nUnit, uint32_t nKey, Human68k::files_t* pFiles)
{
assert(nKey);
assert(pFiles);
@ -3071,7 +3069,7 @@ int CFileSys::NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles)
/// $49 - Create new file
//
//---------------------------------------------------------------------------
int CFileSys::Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, bool bForce)
int CFileSys::Create(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, uint32_t nHumanAttribute, bool bForce)
{
assert(pNamests);
assert(nKey);
@ -3116,7 +3114,7 @@ int CFileSys::Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamest
pHostFcb->SetHumanPath(f.GetHumanPath());
// Set open mode
pFcb->mode = (WORD)((pFcb->mode & ~Human68k::OP_MASK) | Human68k::OP_FULL);
pFcb->mode = (uint16_t)((pFcb->mode & ~Human68k::OP_MASK) | Human68k::OP_FULL);
if (!pHostFcb->SetMode(pFcb->mode)) {
m_cFcb.Free(pHostFcb);
return FS_ILLEGALMOD;
@ -3139,7 +3137,7 @@ int CFileSys::Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamest
/// $4A - File open
//
//---------------------------------------------------------------------------
int CFileSys::Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb)
int CFileSys::Open(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb)
{
assert(pNamests);
assert(nKey);
@ -3210,7 +3208,7 @@ int CFileSys::Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests,
/// $4B - File close
//
//---------------------------------------------------------------------------
int CFileSys::Close(DWORD nUnit, DWORD nKey, const Human68k::fcb_t* /* pFcb */)
int CFileSys::Close(uint32_t nUnit, uint32_t nKey, const Human68k::fcb_t* /* pFcb */)
{
assert(nKey);
@ -3248,7 +3246,7 @@ int CFileSys::Close(DWORD nUnit, DWORD nKey, const Human68k::fcb_t* /* pFcb */)
/// Clean exit when 0 bytes are read.
//
//---------------------------------------------------------------------------
int CFileSys::Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pBuffer, DWORD nSize)
int CFileSys::Read(uint32_t nKey, Human68k::fcb_t* pFcb, BYTE* pBuffer, uint32_t nSize)
{
assert(nKey);
assert(pFcb);
@ -3267,9 +3265,8 @@ int CFileSys::Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pBuffer, DWORD nSize
}
// Read
DWORD nResult;
nResult = pHostFcb->Read(pBuffer, nSize);
if (nResult == (DWORD)-1) {
const uint32_t nResult = pHostFcb->Read(pBuffer, nSize);
if (nResult == (uint32_t)-1) {
m_cFcb.Free(pHostFcb);
return FS_INVALIDFUNC; // TODO: Should return error code 10 (read error) as well here
}
@ -3288,7 +3285,7 @@ int CFileSys::Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pBuffer, DWORD nSize
/// Truncate file if 0 bytes are written.
//
//---------------------------------------------------------------------------
int CFileSys::Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, DWORD nSize)
int CFileSys::Write(uint32_t nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, uint32_t nSize)
{
assert(nKey);
assert(pFcb);
@ -3300,7 +3297,7 @@ int CFileSys::Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, DWOR
if (pHostFcb == nullptr)
return FS_NOTOPENED;
DWORD nResult;
uint32_t nResult;
if (nSize == 0) {
// Truncate
if (!pHostFcb->Truncate()) {
@ -3321,7 +3318,7 @@ int CFileSys::Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, DWOR
// Write
nResult = pHostFcb->Write(pBuffer, nSize);
if (nResult == (DWORD)-1) {
if (nResult == (uint32_t)-1) {
m_cFcb.Free(pHostFcb);
return FS_CANTWRITE; /// TODO: Should return error code 11 (write error) as well here
}
@ -3346,7 +3343,7 @@ int CFileSys::Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pBuffer, DWOR
/// $4E - File seek
//
//---------------------------------------------------------------------------
int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset)
int CFileSys::Seek(uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nSeek, int nOffset)
{
assert(pFcb);
@ -3356,14 +3353,14 @@ int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset)
return FS_NOTOPENED;
// Parameter check
if (nSeek > Human68k::SK_END) {
if (nSeek > (uint32_t)Human68k::seek_t::SK_END) {
m_cFcb.Free(pHostFcb);
return FS_INVALIDPRM;
}
// File seek
DWORD nResult = pHostFcb->Seek(nOffset, nSeek);
if (nResult == (DWORD)-1) {
uint32_t nResult = pHostFcb->Seek(nOffset, (Human68k::seek_t)nSeek);
if (nResult == (uint32_t)-1) {
m_cFcb.Free(pHostFcb);
return FS_CANTSEEK;
}
@ -3381,14 +3378,14 @@ int CFileSys::Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset)
/// Throw error when the top 16 bits are $FFFF.
//
//---------------------------------------------------------------------------
DWORD CFileSys::TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime)
uint32_t CFileSys::TimeStamp(uint32_t nUnit, uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nHumanTime)
{
assert(nKey);
assert(pFcb);
// Get only
if (nHumanTime == 0)
return ((DWORD)pFcb->date << 16) | pFcb->time;
return ((uint32_t)pFcb->date << 16) | pFcb->time;
// Unit check
if (nUnit >= CHostEntry::DRIVE_MAX)
@ -3415,8 +3412,8 @@ DWORD CFileSys::TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD
m_cFcb.Free(pHostFcb);
return FS_INVALIDPRM;
}
pFcb->date = (WORD)(nHumanTime >> 16);
pFcb->time = (WORD)nHumanTime;
pFcb->date = (uint16_t)(nHumanTime >> 16);
pFcb->time = (uint16_t)nHumanTime;
// Update cache
m_cEntry.CleanCache(nUnit, pHostFcb->GetHumanPath());
@ -3429,7 +3426,7 @@ DWORD CFileSys::TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD
/// $50 - Get capacity
//
//---------------------------------------------------------------------------
int CFileSys::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) const
int CFileSys::GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const
{
assert(pCapacity);
@ -3453,7 +3450,7 @@ int CFileSys::GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) const
/// $51 - Inspect/control drive status
//
//---------------------------------------------------------------------------
int CFileSys::CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive) const
int CFileSys::CtrlDrive(uint32_t nUnit, Human68k::ctrldrive_t* pCtrlDrive) const
{
assert(pCtrlDrive);
@ -3496,7 +3493,7 @@ int CFileSys::CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive) const
/// Therefore, treat even a unit out of bounds as normal operation.
//
//---------------------------------------------------------------------------
int CFileSys::GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) const
int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const
{
assert(pDpb);
@ -3530,8 +3527,8 @@ int CFileSys::GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) const
}
// Calculate number of shifts
DWORD nSize = 1;
DWORD nShift = 0;
uint32_t nSize = 1;
uint32_t nShift = 0;
for (;;) {
if (nSize >= cap.sectors)
break;
@ -3546,23 +3543,23 @@ int CFileSys::GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) const
// Cluster 1: FAT
// Cluster 2: Root directory
// Cluster 3: Data memory (pseudo-sector)
DWORD nFat = cap.sectors;
DWORD nRoot = cap.sectors * 2;
DWORD nData = cap.sectors * 3;
const uint32_t nFat = cap.sectors;
const uint32_t nRoot = cap.sectors * 2;
const uint32_t nData = cap.sectors * 3;
// Set DPB
pDpb->sector_size = cap.bytes; // Bytes per sector
pDpb->cluster_size =
(BYTE)(cap.sectors - 1); // Sectors per cluster - 1
pDpb->shift = (BYTE)nShift; // Number of cluster → sector shifts
pDpb->fat_sector = (WORD)nFat; // First FAT sector number
pDpb->fat_sector = (uint16_t)nFat; // First FAT sector number
pDpb->fat_max = 1; // Number of FAT memory spaces
pDpb->fat_size = (BYTE)cap.sectors; // Number of sectors controlled by FAT (excluding copies)
pDpb->file_max =
(WORD)(cap.sectors * cap.bytes / 0x20); // Number of files in the root directory
pDpb->data_sector = (WORD)nData; // First sector number of data memory
(uint16_t)(cap.sectors * cap.bytes / 0x20); // Number of files in the root directory
pDpb->data_sector = (uint16_t)nData; // First sector number of data memory
pDpb->cluster_max = cap.clusters; // Total number of clusters + 1
pDpb->root_sector = (WORD)nRoot; // First sector number of the root directory
pDpb->root_sector = (uint16_t)nRoot; // First sector number of the root directory
pDpb->media = media; // Media byte
return 0;
@ -3576,7 +3573,7 @@ int CFileSys::GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) const
/// Buffer size is hard coded to $200 byte.
//
//---------------------------------------------------------------------------
int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize)
int CFileSys::DiskRead(uint32_t nUnit, BYTE* pBuffer, uint32_t nSector, uint32_t nSize)
{
assert(pBuffer);
@ -3613,7 +3610,7 @@ int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize)
// Note that in lzdsys the sector number to read is calculated by the following formula:
// (dirent.cluster - 2) * (dpb.cluster_size + 1) + dpb.data_sector
/// @warning little endian only
dir->cluster = (WORD)(m_nHostSectorCount + 2); // Pseudo-sector number
dir->cluster = (uint16_t)(m_nHostSectorCount + 2); // Pseudo-sector number
m_nHostSectorBuffer[m_nHostSectorCount] = nSector; // Entity that points to the pseudo-sector
m_nHostSectorCount++;
m_nHostSectorCount %= XM6_HOST_PSEUDO_CLUSTER_MAX;
@ -3622,8 +3619,8 @@ int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize)
}
// Calculate the sector number from the cluster number
DWORD n = nSector - (3 * cap.sectors);
DWORD nMod = 1;
uint32_t n = nSector - (3 * cap.sectors);
uint32_t nMod = 1;
if (cap.sectors) {
// Beware that cap.sectors becomes 0 when media does not exist
nMod = n % cap.sectors;
@ -3641,9 +3638,9 @@ int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize)
if (!f.Open())
return FS_INVALIDPRM;
memset(pBuffer, 0, 0x200);
DWORD nResult = f.Read(pBuffer, 0x200);
uint32_t nResult = f.Read(pBuffer, 0x200);
f.Close();
if (nResult == (DWORD)-1)
if (nResult == (uint32_t)-1)
return FS_INVALIDPRM;
return 0;
@ -3658,7 +3655,7 @@ int CFileSys::DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize)
/// $54 - Write sector
//
//---------------------------------------------------------------------------
int CFileSys::DiskWrite(DWORD nUnit) const
int CFileSys::DiskWrite(uint32_t nUnit) const
{
// Unit check
if (nUnit >= CHostEntry::DRIVE_MAX)
@ -3684,7 +3681,7 @@ int CFileSys::DiskWrite(DWORD nUnit) const
/// $55 - IOCTRL
//
//---------------------------------------------------------------------------
int CFileSys::Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl)
int CFileSys::Ioctrl(uint32_t nUnit, uint32_t nFunction, Human68k::ioctrl_t* pIoctrl)
{
assert(pIoctrl);
@ -3707,7 +3704,7 @@ int CFileSys::Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl)
case 2:
switch (pIoctrl->param) {
case (DWORD)-1:
case (uint32_t)-1:
// Re-identify media
m_cEntry.isMediaOffline(nUnit);
return 0;
@ -3722,17 +3719,17 @@ int CFileSys::Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl)
}
break;
case (DWORD)-1:
case (uint32_t)-1:
// Resident evaluation
memcpy(pIoctrl->buffer, "WindrvXM", 8);
return 0;
case (DWORD)-2:
case (uint32_t)-2:
// Set options
SetOption(pIoctrl->param);
return 0;
case (DWORD)-3:
case (uint32_t)-3:
// Get options
pIoctrl->param = GetOption();
return 0;
@ -3749,7 +3746,7 @@ int CFileSys::Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl)
/// $56 - Flush
//
//---------------------------------------------------------------------------
int CFileSys::Flush(DWORD nUnit) const
int CFileSys::Flush(uint32_t nUnit) const
{
// Unit check
if (nUnit >= CHostEntry::DRIVE_MAX)
@ -3766,7 +3763,7 @@ int CFileSys::Flush(DWORD nUnit) const
/// $57 - Media change check
//
//---------------------------------------------------------------------------
int CFileSys::CheckMedia(DWORD nUnit) const
int CFileSys::CheckMedia(uint32_t nUnit) const
{
// Unit check
if (nUnit >= CHostEntry::DRIVE_MAX)
@ -3788,7 +3785,7 @@ int CFileSys::CheckMedia(DWORD nUnit) const
/// $58 - Lock
//
//---------------------------------------------------------------------------
int CFileSys::Lock(DWORD nUnit) const
int CFileSys::Lock(uint32_t nUnit) const
{
// Unit check
if (nUnit >= CHostEntry::DRIVE_MAX)
@ -3810,7 +3807,7 @@ int CFileSys::Lock(DWORD nUnit) const
/// Set options
//
//---------------------------------------------------------------------------
void CFileSys::SetOption(DWORD nOption)
void CFileSys::SetOption(uint32_t nOption)
{
// Clear cache when option settings change
if (m_nOption ^ nOption)
@ -3835,7 +3832,7 @@ void CFileSys::InitOption(const Human68k::argument_t* pArgument)
const BYTE* pp = pArgument->buf;
pp += strlen((const char*)pp) + 1;
DWORD nOption = m_nOptionDefault;
uint32_t nOption = m_nOptionDefault;
for (;;) {
assert(pp < pArgument->buf + sizeof(*pArgument));
const BYTE* p = pp;
@ -3843,7 +3840,7 @@ void CFileSys::InitOption(const Human68k::argument_t* pArgument)
if (c == '\0')
break;
DWORD nMode;
uint32_t nMode;
if (c == '+') {
nMode = 1;
} else if (c == '-') {
@ -3868,7 +3865,7 @@ void CFileSys::InitOption(const Human68k::argument_t* pArgument)
if (c == '\0')
break;
DWORD nBit = 0;
uint32_t nBit = 0;
switch (c) {
case 'A': case 'a': nBit = WINDRV_OPT_CONVERT_LENGTH; break;
case 'T': case 't': nBit = WINDRV_OPT_COMPARE_LENGTH; nMode ^= 1; break;
@ -3911,7 +3908,7 @@ void CFileSys::InitOption(const Human68k::argument_t* pArgument)
/// Get volume label
//
//---------------------------------------------------------------------------
bool CFileSys::FilesVolume(DWORD nUnit, Human68k::files_t* pFiles) const
bool CFileSys::FilesVolume(uint32_t nUnit, Human68k::files_t* pFiles) const
{
assert(pFiles);

View File

@ -92,39 +92,14 @@ namespace Human68k {
};
/// Seek types
enum seek_t {
enum class seek_t {
SK_BEGIN = 0, ///< From the beginning of a file
SK_CURRENT = 1, ///< From the current location
SK_END = 2, ///< From the end of the file
};
/// Media byte
enum media_t {
MEDIA_2DD_10 = 0xE0, ///< 2DD/10 sector
MEDIA_1D_9 = 0xE5, ///< 1D/9 sector
MEDIA_2D_9 = 0xE6, ///< 2D/9 sector
MEDIA_1D_8 = 0xE7, ///< 1D/8 sector
MEDIA_2D_8 = 0xE8, ///< 2D/8 sector
MEDIA_2HT = 0xEA, ///< 2HT
MEDIA_2HS = 0xEB, ///< 2HS
MEDIA_2HDE = 0xEC, ///< 2DDE
MEDIA_1DD_9 = 0xEE, ///< 1DD/9 sector
MEDIA_1DD_8 = 0xEF, ///< 1DD/8 sector
MEDIA_MANUAL = 0xF1, ///< Remote drive (manual eject)
MEDIA_REMOVABLE = 0xF2, ///< Remote drive (removable)
MEDIA_REMOTE = 0xF3, ///< Remote drive
MEDIA_DAT = 0xF4, ///< SCSI-DAT
MEDIA_CDROM = 0xF5, ///< SCSI-CDROM
MEDIA_MO = 0xF6, ///< SCSI-MO
MEDIA_SCSI_HD = 0xF7, ///< SCSI-HD
MEDIA_SASI_HD = 0xF8, ///< SASI-HD
MEDIA_RAMDISK = 0xF9, ///< RAM disk
MEDIA_2HQ = 0xFA, ///< 2HQ
MEDIA_2DD_8 = 0xFB, ///< 2DD/8 sector
MEDIA_2DD_9 = 0xFC, ///< 2DD/9 sector
MEDIA_2HC = 0xFD, ///< 2HC
MEDIA_2HD = 0xFE, ///< 2HD
};
// Media byte
const static int MEDIA_REMOTE = 0xF3; ///< Remote drive
struct namests_t {
BYTE wildcard; ///< Wildcard character length
@ -141,41 +116,41 @@ namespace Human68k {
struct files_t {
BYTE fatr; ///< + 0 search attribute; read-only
// BYTE drive; ///< + 1 drive number; read-only
DWORD sector; ///< + 2 directory sector; DOS _FILES first address substitute
// WORD cluster; ///< + 6 directory cluster; details unknown (unused)
WORD offset; ///< + 8 directory entry; write-only
uint32_t sector; ///< + 2 directory sector; DOS _FILES first address substitute
// uint16_t cluster; ///< + 6 directory cluster; details unknown (unused)
uint16_t offset; ///< + 8 directory entry; write-only
// BYTE name[8]; ///< +10 working file name; write-only (unused)
// BYTE ext[3]; ///< +18 working extension; write-only (unused)
BYTE attr; ///< +21 file attribute; write-only
WORD time; ///< +22 last change time of day; write-only
WORD date; ///< +24 last change date; write-only
DWORD size; ///< +26 file size; write-only
uint16_t time; ///< +22 last change time of day; write-only
uint16_t date; ///< +24 last change date; write-only
uint32_t size; ///< +26 file size; write-only
BYTE full[23]; ///< +30 full name; write-only
};
struct fcb_t {
// BYTE pad00[6]; ///< + 0~+ 5 (unused)
DWORD fileptr; ///< + 6~+ 9 file pointer
uint32_t fileptr; ///< + 6~+ 9 file pointer
// BYTE pad01[4]; ///< +10~+13 (unused)
WORD mode; ///< +14~+15 open mode
uint16_t mode; ///< +14~+15 open mode
// BYTE pad02[16]; ///< +16~+31 (unused)
// DWORD zero; ///< +32~+35 zeros are written when opened (unused)
// uint32_t zero; ///< +32~+35 zeros are written when opened (unused)
// BYTE name[8]; ///< +36~+43 file name (PADDING 0x20) (unused)
// BYTE ext[3]; ///< +44~+46 extension (PADDING 0x20) (unused)
BYTE attr; ///< +47 file attribute
// BYTE add[10]; ///< +48~+57 file name addition (PADDING 0x00) (unused)
WORD time; ///< +58~+59 last change time of day
WORD date; ///< +60~+61 last change date
// WORD cluster; ///< +62~+63 cluster number (unused)
DWORD size; ///< +64~+67 file size
uint16_t time; ///< +58~+59 last change time of day
uint16_t date; ///< +60~+61 last change date
// uint16_t cluster; ///< +62~+63 cluster number (unused)
uint32_t size; ///< +64~+67 file size
// BYTE pad03[28]; ///< +68~+95 FAT cache (unused)
};
struct capacity_t {
WORD freearea; ///< + 0 Number of available clusters
WORD clusters; ///< + 2 Total number of clusters
WORD sectors; ///< + 4 Number of sectors per cluster
WORD bytes; ///< + 6 Number of bytes per sector
uint16_t freearea; ///< + 0 Number of available clusters
uint16_t clusters; ///< + 2 Total number of clusters
uint16_t sectors; ///< + 4 Number of sectors per cluster
uint16_t bytes; ///< + 6 Number of bytes per sector
};
struct ctrldrive_t {
@ -184,17 +159,17 @@ namespace Human68k {
};
struct dpb_t {
WORD sector_size; ///< + 0 Number of bytes in one sector
uint16_t sector_size; ///< + 0 Number of bytes in one sector
BYTE cluster_size; ///< + 2 Number sectors in one cluster -1
BYTE shift; ///< + 3 Number of cluster→sector shifts
WORD fat_sector; ///< + 4 FAT first sector number
uint16_t fat_sector; ///< + 4 FAT first sector number
BYTE fat_max; ///< + 6 FAT storage quantity
BYTE fat_size; ///< + 7 FAT controlled sector number (excluding duplicates)
WORD file_max; ///< + 8 Number of files in the root directory
WORD data_sector; ///< +10 First sector number of data storage
WORD cluster_max; ///< +12 Total number of clusters +1
WORD root_sector; ///< +14 First sector number of root directory
// DWORD driverentry; ///< +16 Device driver pointer (unused)
uint16_t file_max; ///< + 8 Number of files in the root directory
uint16_t data_sector; ///< +10 First sector number of data storage
uint16_t cluster_max; ///< +12 Total number of clusters +1
uint16_t root_sector; ///< +14 First sector number of root directory
// uint32_t driverentry; ///< +16 Device driver pointer (unused)
BYTE media; ///< +20 Media identifier
// BYTE flag; ///< +21 Flag used by DPB (unused)
};
@ -205,22 +180,22 @@ namespace Human68k {
BYTE ext[3]; ///< + 8 Extension (PADDING 0x20)
BYTE attr; ///< +11 File attribute
BYTE add[10]; ///< +12 File name addition (PADDING 0x00)
WORD time; ///< +22 Last change time of day
WORD date; ///< +24 Last change date
WORD cluster; ///< +26 Cluster number
DWORD size; ///< +28 File size
uint16_t time; ///< +22 Last change time of day
uint16_t date; ///< +24 Last change date
uint16_t cluster; ///< +26 Cluster number
uint32_t size; ///< +28 File size
};
/// IOCTRL parameter union
union ioctrl_t {
BYTE buffer[8]; ///< Access in byte units
DWORD param; ///< Parameter (First 4 bytes)
WORD media; ///< Media byte (First 2 bytes)
uint32_t param; ///< Parameter (First 4 bytes)
uint16_t media; ///< Media byte (First 2 bytes)
};
/// Command line parameter struct
/**
The driver itself is included in the beginning of the argument,
The driver itself is included in the beginning of the argument,
so setting to a length longer than HUMAN68K_PATH_MAX
*/
struct argument_t {
@ -257,9 +232,9 @@ static const int XM6_HOST_PSEUDO_CLUSTER_MAX = 10;
/// Number of caches for directory entries
/**
Human68k carries out a large number of checks of directory entries when doing an operation
inside a subdirectory. This specifies the number of caches used to speed up this operation.
Cache is allocated per drive. The more you add the faster it gets, but use too many
Human68k carries out a large number of checks of directory entries when doing an operation
inside a subdirectory. This specifies the number of caches used to speed up this operation.
Cache is allocated per drive. The more you add the faster it gets, but use too many
and the host OS gets under a heavy load, so be careful.
Default is 16.
@ -268,10 +243,10 @@ static const int XM6_HOST_DIRENTRY_CACHE_MAX = 16;
/// Max number of entries that can be stored per directory
/**
When a large number of files are stored in a directory, a larger amount of data than
contemporanous applications can handle will be returned. This may lead to errors such as
When a large number of files are stored in a directory, a larger amount of data than
contemporanous applications can handle will be returned. This may lead to errors such as
partial data being recognized, performance dropping significantly, or OOM crashes.
To guard against this, an upper limit is defined here. In the case of a particular
To guard against this, an upper limit is defined here. In the case of a particular
file manager, the upper limit is 2560 files. This is one good example to use as reference.
Default is around 60000 entries. (Upper limit of the FAT root directory)
@ -280,17 +255,17 @@ static const int XM6_HOST_DIRENTRY_FILE_MAX = 65535;
/// Max number of patterns for file name deduplication
/**
The file names on the Human68k side are automatically created based on the file system on
the host side. However, Human68k have stricter file name length restrictions than the host has.
Because of this, there is a risk that file name duplication will occur. When this happens,
WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve
the duplication. Theoretically, there are over 60 million (36^5) unique file names that
can be generated by this method. However, in reality any more than a few hundred
deduplications will take excessive processing time. So here an upper limit to deduplication
is set in order to maintain system performance. If a system is operated with common sense,
you should only need a few dozen deduplication patterns, so this value can be kept low
to further improve performance. In the case deduplication is not carried out, multiple files
with the same name will be created. When trying to access such files,
The file names on the Human68k side are automatically created based on the file system on
the host side. However, Human68k have stricter file name length restrictions than the host has.
Because of this, there is a risk that file name duplication will occur. When this happens,
WindrvXM will use a certain renaming heuristic to generate alternate file names to resolve
the duplication. Theoretically, there are over 60 million (36^5) unique file names that
can be generated by this method. However, in reality any more than a few hundred
deduplications will take excessive processing time. So here an upper limit to deduplication
is set in order to maintain system performance. If a system is operated with common sense,
you should only need a few dozen deduplication patterns, so this value can be kept low
to further improve performance. In the case deduplication is not carried out, multiple files
with the same name will be created. When trying to access such files,
only the first entry will ever be accessed.
Default is 36 patterns.
@ -338,11 +313,7 @@ Normal is 0. Becomes 1 if attempting to mount in read-only mode.
Reserving the other values for future use.
Insurance against hard-to-detect devices such as homemade USB storage.
*/
enum {
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
};
static const uint32_t FSFLAG_WRITE_PROTECT = 0x00000001; ///< Bit0: Force write protect
//===========================================================================
//
@ -357,8 +328,8 @@ class CRing {
public:
CRing() { Init(); }
~CRing() { Remove(); }
CRing(CRing&) = delete;
CRing& operator=(const CRing&) = delete;
CRing(CRing&) = default;
CRing& operator=(const CRing&) = default;
void Init() { next = prev = this; }
@ -442,8 +413,6 @@ class CHostFilename {
public:
CHostFilename() = default;
~CHostFilename() = default;
CHostFilename(CHostFilename&) = delete;
CHostFilename& operator=(const CHostFilename&) = delete;
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
@ -460,17 +429,17 @@ public:
void SetEntryName(); ///< Set Human68k directory entry
void SetEntryAttribute(BYTE nHumanAttribute)
{ m_dirHuman.attr = nHumanAttribute; } ///< Set Human68k directory entry
void SetEntrySize(DWORD nHumanSize)
void SetEntrySize(uint32_t nHumanSize)
{ m_dirHuman.size = nHumanSize; } ///< Set Human68k directory entry
void SetEntryDate(WORD nHumanDate)
void SetEntryDate(uint16_t nHumanDate)
{ m_dirHuman.date = nHumanDate; } ///< Set Human68k directory entry
void SetEntryTime(WORD nHumanTime)
void SetEntryTime(uint16_t nHumanTime)
{ m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry
void SetEntryCluster(WORD nHumanCluster)
void SetEntryCluster(uint16_t nHumanCluster)
{ m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry
const Human68k::dirent_t* GetEntry() const
const Human68k::dirent_t* GetEntry() const
{ return &m_dirHuman; } ///< Get Human68k directory entry
int CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes
int CheckAttribute(uint32_t nHumanAttribute) const; ///< Determine Human68k directory entry attributes
bool isSameEntry(const Human68k::dirent_t* pdirHuman) const
{ assert(pdirHuman); return memcmp(&m_dirHuman, pdirHuman, sizeof(m_dirHuman)) == 0; }
///< Determine Human68k directory entry match
@ -505,7 +474,7 @@ get updated for new directories created as a result of file operations, which
triggers updates to directory entires.
However, on the host file system, new directories do typically get an updated time stamp.
The unfortunate outcome is that when copying a directory for instance, the time stamp
The unfortunate outcome is that when copying a directory for instance, the time stamp
will get overwritten even if the application did not intend for the time stamp to get updated.
Here follows an implementation of a directory cache FAT time stamp emulation feature.
@ -522,8 +491,8 @@ class CHostPath: public CRing {
public:
/// Search buffer
struct find_t {
DWORD count; ///< Search execution count + 1 (When 0 the below value is invalid)
DWORD id; ///< Entry unique ID for the path of the next search
uint32_t count; ///< Search execution count + 1 (When 0 the below value is invalid)
uint32_t id; ///< Entry unique ID for the path of the next search
const ring_t* pos; ///< Position of the next search (When identical to unique ID)
Human68k::dirent_t entry; ///< Contents of the next seach entry
@ -532,8 +501,8 @@ public:
CHostPath() = default;
~CHostPath();
CHostPath(CHostPath&) = delete;
CHostPath& operator=(const CHostPath&) = delete;
CHostPath(CHostPath&) = default;
CHostPath& operator=(const CHostPath&) = default;
void Clean(); ///< Initialialize for reuse
@ -542,9 +511,9 @@ public:
bool isSameHuman(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
bool isSameChild(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
const TCHAR* GetHost() const { return m_szHost; } ///< Obtain the name on the host side
const CHostFilename* FindFilename(const BYTE* szHuman, DWORD nHumanAttribute = Human68k::AT_ALL) const;
const CHostFilename* FindFilename(const BYTE* szHuman, uint32_t nHumanAttribute = Human68k::AT_ALL) const;
///< Find file name
const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const;
const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, uint32_t nHumanAttribute, find_t* pFind) const;
///< Find file name (with support for wildcards)
bool isRefresh() const; ///< Check that the file change has been done
void Refresh(); ///< Refresh file
@ -564,20 +533,20 @@ private:
CRing m_cRing; ///< For CHostFilename linking
time_t m_tBackup = 0; ///< For time stamp restoration
bool m_bRefresh = true; ///< Refresh flag
DWORD m_nId = 0; ///< Unique ID (When the value has changed, it means an update has been made)
uint32_t m_nId = 0; ///< Unique ID (When the value has changed, it means an update has been made)
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< The internal Human68k name for the relevant entry
TCHAR m_szHost[FILEPATH_MAX]; ///< The host side name for the relevant entry
static DWORD g_nId; ///< Counter for the unique ID generation
static uint32_t g_nId; ///< Counter for the unique ID generation
};
//===========================================================================
//
/// File search processing
///
/// It's pretty much impossible to process Human68k file names as Unicode internally.
/// It's pretty much impossible to process Human68k file names as Unicode internally.
/// So, we carry out binary conversion for processing. We leave it up to the
/// directory entry cache to handle the conversion, which allows WINDRV to read
/// directory entry cache to handle the conversion, which allows WINDRV to read
/// everything as Shift-JIS. Additionally, it allows Human68k names to be
/// fully independent of base path assignments.
///
@ -596,20 +565,18 @@ class CHostFiles {
public:
CHostFiles() = default;
~CHostFiles() = default;
CHostFiles(CHostFiles&) = delete;
CHostFiles& operator=(const CHostFiles&) = delete;
void Init();
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
bool isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetKey(uint32_t nKey) { m_nKey = nKey; } ///< Set search key
bool isSameKey(uint32_t nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetPath(const Human68k::namests_t* pNamests); ///< Create path and file name internally
bool isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< Check if root directory
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< Enable file search using wildcards
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< Enable only path names
bool isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< Check if set to only path names
void SetAttribute(DWORD nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; } ///< Set search attribute
bool Find(DWORD nUnit, const class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side
void SetAttribute(uint32_t nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; } ///< Set search attribute
bool Find(uint32_t nUnit, const class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side
const CHostFilename* Find(const CHostPath* pPath); ///< Find file name
void SetEntry(const CHostFilename* pFilename); ///< Store search results on the Human68k side
void SetResult(const TCHAR* szPath); ///< Set names on the host side
@ -620,18 +587,18 @@ public:
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Get Human68k directory entry
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute
WORD GetDate() const { return m_dirHuman.date; } ///< Get Human68k date
WORD GetTime() const { return m_dirHuman.time; } ///< Get Human68k time
DWORD GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size
uint32_t GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute
uint16_t GetDate() const { return m_dirHuman.date; } ///< Get Human68k date
uint16_t GetTime() const { return m_dirHuman.time; } ///< Get Human68k time
uint32_t GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Get Human68k file name
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Get Human68k file name search results
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
private:
DWORD m_nKey = 0; ///< FILES buffer address for Human68k; 0 is unused
DWORD m_nHumanWildcard = 0; ///< Human68k wildcard data
DWORD m_nHumanAttribute = 0; ///< Human68k search attribute
uint32_t m_nKey = 0; ///< FILES buffer address for Human68k; 0 is unused
uint32_t m_nHumanWildcard = 0; ///< Human68k wildcard data
uint32_t m_nHumanAttribute = 0; ///< Human68k search attribute
CHostPath::find_t m_findNext = {}; ///< Next search location data
Human68k::dirent_t m_dirHuman = {}; ///< Search results: Human68k file data
BYTE m_szHumanFilename[24] = {}; ///< Human68k file name
@ -653,8 +620,8 @@ public:
void Init(); ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
CHostFiles* Alloc(DWORD nKey);
CHostFiles* Search(DWORD nKey);
CHostFiles* Alloc(uint32_t nKey);
CHostFiles* Search(uint32_t nKey);
void Free(CHostFiles* pFiles);
private:
/// For memory management
@ -675,31 +642,31 @@ class CHostFcb {
public:
CHostFcb() = default;
~CHostFcb() { Close(); }
CHostFcb(CHostFcb&) = delete;
CHostFcb& operator=(const CHostFcb&) = delete;
CHostFcb(CHostFcb&) = default;
CHostFcb& operator=(const CHostFcb&) = default;
void Init();
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
bool isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetKey(uint32_t nKey) { m_nKey = nKey; } ///< Set search key
bool isSameKey(uint32_t nKey) const { return m_nKey == nKey; } ///< Compare search key
void SetUpdate() { m_bUpdate = true; } ///< Update
bool isUpdate() const { return m_bUpdate; } ///< Get update state
bool SetMode(DWORD nHumanMode); ///< Set file open mode
bool SetMode(uint32_t nHumanMode); ///< Set file open mode
void SetFilename(const TCHAR* szFilename); ///< Set file name
void SetHumanPath(const BYTE* szHumanPath); ///< Set Human68k path name
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
bool Create(DWORD nHumanAttribute, bool bForce); ///< Create file
bool Create(uint32_t nHumanAttribute, bool bForce); ///< Create file
bool Open(); ///< Open file
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
uint32_t Read(BYTE* pBuffer, uint32_t nSize); ///< Read file
uint32_t Write(const BYTE* pBuffer, uint32_t nSize); ///< Write file
bool Truncate() const; ///< Truncate file
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
bool TimeStamp(DWORD nHumanTime) const; ///< Set file time stamp
uint32_t Seek(uint32_t nOffset, Human68k::seek_t nHumanSeek); ///< Seek file
bool TimeStamp(uint32_t nHumanTime) const; ///< Set file time stamp
void Close(); ///< Close file
private:
DWORD m_nKey = 0; ///< Human68k FCB buffer address (0 if unused)
uint32_t m_nKey = 0; ///< Human68k FCB buffer address (0 if unused)
bool m_bUpdate = false; ///< Update flag
FILE* m_pFile = nullptr; ///< Host side file object
const char* m_pszMode = nullptr; ///< Host side file open mode
@ -721,8 +688,8 @@ public:
void Init(); ///< Initialization (when the driver is installed)
void Clean() const; ///< Release (when starting up or resetting)
CHostFcb* Alloc(DWORD nKey);
CHostFcb* Search(DWORD nKey);
CHostFcb* Alloc(uint32_t nKey);
CHostFcb* Search(uint32_t nKey);
void Free(CHostFcb* p);
private:
@ -747,23 +714,23 @@ class CHostDrv
public:
CHostDrv() = default;
~CHostDrv();
CHostDrv(CHostDrv&) = delete;
CHostDrv& operator=(const CHostDrv&) = delete;
CHostDrv(CHostDrv&) = default;
CHostDrv& operator=(const CHostDrv&) = default;
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
void Init(const TCHAR* szBase, uint32_t nFlag); ///< Initialization (device startup and load)
bool isWriteProtect() const { return m_bWriteProtect; }
bool isEnable() const { return m_bEnable; } ///< Is it accessible?
bool isMediaOffline() const;
BYTE GetMediaByte() const;
DWORD GetStatus() const;
uint32_t GetStatus() const;
void SetEnable(bool); ///< Set media status
bool CheckMedia(); ///< Check if media was changed
void Update(); ///< Update media status
void Eject();
void GetVolume(TCHAR* szLabel); ///< Get volume label
bool GetVolumeCache(TCHAR* szLabel) const; ///< Get volume label from cache
DWORD GetCapacity(Human68k::capacity_t* pCapacity);
uint32_t GetCapacity(Human68k::capacity_t* pCapacity);
bool GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< Get capacity from cache
// Cache operations
@ -789,7 +756,7 @@ private:
bool m_bWriteProtect = false; ///< TRUE if write-protected
bool m_bEnable = false; ///< TRUE if media is usable
DWORD m_nRing = 0; ///< Number of stored path names
uint32_t m_nRing = 0; ///< Number of stored path names
CRing m_cRing; ///< For attaching to CHostPath
Human68k::capacity_t m_capCache; ///< Sector data cache: if "sectors == 0" then not cached
bool m_bVolumeCache = false; ///< TRUE if the volume label has been read
@ -811,39 +778,39 @@ public:
CHostEntry() = default;
~CHostEntry();
CHostEntry(CHostEntry&) = delete;
CHostEntry& operator=(const CHostEntry&) = delete;
CHostEntry(CHostEntry&) = default;
CHostEntry& operator=(const CHostEntry&) = default;
void Init() const; ///< Initialization (when the driver is installed)
void Clean(); ///< Release (when starting up or resetting)
// Cache operations
void CleanCache() const; ///< Update all cache
void CleanCache(DWORD nUnit) const; ///< Update cache for the specified unit
void CleanCache(DWORD nUnit, const BYTE* szHumanPath) const; ///< Update cache for the specified path
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath) const; ///< Update cache below the specified path
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath) const; ///< Delete cache for the specified path
bool Find(DWORD nUnit, CHostFiles* pFiles) const; ///< Find host side name (path + file name (can be abbreviated) + attribute)
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system
void CleanCache(uint32_t nUnit) const; ///< Update cache for the specified unit
void CleanCache(uint32_t nUnit, const BYTE* szHumanPath) const; ///< Update cache for the specified path
void CleanCacheChild(uint32_t nUnit, const BYTE* szHumanPath) const; ///< Update cache below the specified path
void DeleteCache(uint32_t nUnit, const BYTE* szHumanPath) const; ///< Delete cache for the specified path
bool Find(uint32_t nUnit, CHostFiles* pFiles) const; ///< Find host side name (path + file name (can be abbreviated) + attribute)
void ShellNotify(uint32_t nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system
// Drive object operations
void SetDrv(DWORD nUnit, CHostDrv* pDrv);
bool isWriteProtect(DWORD nUnit) const;
bool isEnable(DWORD nUnit) const; ///< Is it accessible?
bool isMediaOffline(DWORD nUnit) const;
BYTE GetMediaByte(DWORD nUnit) const;
DWORD GetStatus(DWORD nUnit) const; ///< Get drive status
bool CheckMedia(DWORD nUnit) const; ///< Media change check
void Eject(DWORD nUnit) const;
void GetVolume(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label
bool GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label from cache
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) const;
bool GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const; ///< Get cluster size from cache
void SetDrv(uint32_t nUnit, CHostDrv* pDrv);
bool isWriteProtect(uint32_t nUnit) const;
bool isEnable(uint32_t nUnit) const; ///< Is it accessible?
bool isMediaOffline(uint32_t nUnit) const;
BYTE GetMediaByte(uint32_t nUnit) const;
uint32_t GetStatus(uint32_t nUnit) const; ///< Get drive status
bool CheckMedia(uint32_t nUnit) const; ///< Media change check
void Eject(uint32_t nUnit) const;
void GetVolume(uint32_t nUnit, TCHAR* szLabel) const; ///< Get volume label
bool GetVolumeCache(uint32_t nUnit, TCHAR* szLabel) const; ///< Get volume label from cache
uint32_t GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const;
bool GetCapacityCache(uint32_t nUnit, Human68k::capacity_t* pCapacity) const; ///< Get cluster size from cache
private:
CHostDrv* m_pDrv[DRIVE_MAX] = {}; ///< Host side drive object
DWORD m_nTimeout = 0; ///< Last time a timeout check was carried out
uint32_t m_nTimeout = 0; ///< Last time a timeout check was carried out
};
//===========================================================================
@ -854,13 +821,13 @@ private:
/** @note
Current state of affairs:
While it violates the design philosophy of XM6, we should find a way for
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
While it violates the design philosophy of XM6, we should find a way for
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
This way, we get the following benefits.
Benefit no. 1
Makes it possible to manage a large number of command handler methods in one place.
There is a high chance the command handlers will change drastically because of
Makes it possible to manage a large number of command handler methods in one place.
There is a high chance the command handlers will change drastically because of
host system architectural changes, so we will save a huge amount of maintenance work
in the long run.
@ -870,7 +837,7 @@ It is not feasible to implement code in XM6 for simultaneous use of file system
Therefore file system object polymorphism is a waste of CPU cycles.
I made the change as an experiment. Performance did improve.
The improvement was obvious from looking at the source the compiler spit out
The improvement was obvious from looking at the source the compiler spit out
after changing the FILESYS_FAST_STRUCTURE value in windrv.h.
You may understand now why I decided to rant here.
@ -887,71 +854,71 @@ public:
void Init(); ///< Initialization (device startup and load)
// Command handlers
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests) const; ///< $41 - Directory check
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests) const; ///< $42 - Create directory
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests) const; ///< $43 - Delete directory
int Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) const;
uint32_t InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup
int CheckDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const; ///< $41 - Directory check
int MakeDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const; ///< $42 - Create directory
int RemoveDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const; ///< $43 - Delete directory
int Rename(uint32_t nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) const;
///< $44 - Change file name
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests) const; ///< $45 - Delete file
int Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute) const;
int Delete(uint32_t nUnit, const Human68k::namests_t* pNamests) const; ///< $45 - Delete file
int Attribute(uint32_t nUnit, const Human68k::namests_t* pNamests, uint32_t nHumanAttribute) const;
///< $46 - Get / set file attribute
int Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles);
int Files(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles);
///< $47 - Find file
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - Find next file
int Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, bool bForce);
int NFiles(uint32_t nUnit, uint32_t nKey, Human68k::files_t* pFiles); ///< $48 - Find next file
int Create(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, uint32_t nHumanAttribute, bool bForce);
///< $49 - Create file
int Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb);
int Open(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb);
///< $4A - Open file
int Close(DWORD nUnit, DWORD nKey, const Human68k::fcb_t* pFcb); ///< $4B - Close file
int Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, DWORD nSize);
int Close(uint32_t nUnit, uint32_t nKey, const Human68k::fcb_t* pFcb); ///< $4B - Close file
int Read(uint32_t nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, uint32_t nSize);
///< $4C - Read file
int Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, DWORD nSize);
int Write(uint32_t nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, uint32_t nSize);
///< $4D - Write file
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - Seek file
DWORD TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime);
int Seek(uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nSeek, int nOffset); ///< $4E - Seek file
uint32_t TimeStamp(uint32_t nUnit, uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nHumanTime);
///< $4F - Get / set file timestamp
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity) const; ///< $50 - Get capacity
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive) const; ///< $51 - Inspect / control drive status
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb) const; ///< $52 - Get DPB
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - Read sectors
int DiskWrite(DWORD nUnit) const; ///< $54 - Write sectors
int Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL
int Flush(DWORD nUnit) const; ///< $56 - Flush
int CheckMedia(DWORD nUnit) const; ///< $57 - Media change check
int Lock(DWORD nUnit) const; ///< $58 - Lock
int GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const; ///< $50 - Get capacity
int CtrlDrive(uint32_t nUnit, Human68k::ctrldrive_t* pCtrlDrive) const; ///< $51 - Inspect / control drive status
int GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const; ///< $52 - Get DPB
int DiskRead(uint32_t nUnit, BYTE* pBuffer, uint32_t nSector, uint32_t nSize); ///< $53 - Read sectors
int DiskWrite(uint32_t nUnit) const; ///< $54 - Write sectors
int Ioctrl(uint32_t nUnit, uint32_t nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL
int Flush(uint32_t nUnit) const; ///< $56 - Flush
int CheckMedia(uint32_t nUnit) const; ///< $57 - Media change check
int Lock(uint32_t nUnit) const; ///< $58 - Lock
void SetOption(DWORD nOption); ///< Set option
DWORD GetOption() const { return m_nOption; } ///< Get option
DWORD GetDefault() const { return m_nOptionDefault; } ///< Get default options
static DWORD GetFileOption() { return g_nOption; } ///< Get file name change option
void SetOption(uint32_t nOption); ///< Set option
uint32_t GetOption() const { return m_nOption; } ///< Get option
uint32_t GetDefault() const { return m_nOptionDefault; } ///< Get default options
static uint32_t GetFileOption() { return g_nOption; } ///< Get file name change option
static const int DriveMax = CHostEntry::DRIVE_MAX; ///< Max number of drive candidates
private:
void InitOption(const Human68k::argument_t* pArgument);
bool FilesVolume(DWORD nUnit, Human68k::files_t* pFiles) const; ///< Get volume label
bool FilesVolume(uint32_t nUnit, Human68k::files_t* pFiles) const; ///< Get volume label
DWORD m_nUnits = 0; ///< Number of current drive objects (Changes for every resume)
uint32_t m_nUnits = 0; ///< Number of current drive objects (Changes for every resume)
DWORD m_nOption = 0; ///< Current runtime flag
DWORD m_nOptionDefault = 0; ///< Runtime flag at reset
uint32_t m_nOption = 0; ///< Current runtime flag
uint32_t m_nOptionDefault = 0; ///< Runtime flag at reset
DWORD m_nDrives = 0; ///< Number of candidates for base path status restoration (scan every time if 0)
uint32_t m_nDrives = 0; ///< Number of candidates for base path status restoration (scan every time if 0)
DWORD m_nKernel = 0; ///< Counter for kernel check
DWORD m_nKernelSearch = 0; ///< Initial address for NUL device
uint32_t m_nKernel = 0; ///< Counter for kernel check
uint32_t m_nKernelSearch = 0; ///< Initial address for NUL device
DWORD m_nHostSectorCount = 0; ///< Virtual sector identifier
uint32_t m_nHostSectorCount = 0; ///< Virtual sector identifier
CHostFilesManager m_cFiles; ///< File search memory
CHostFcbManager m_cFcb; ///< FCB operation memory
CHostEntry m_cEntry; ///< Drive object and directory entry
DWORD m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX];
uint32_t m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX];
///< Entity that the virtual sector points to
DWORD m_nFlag[DriveMax] = {}; ///< Candidate runtime flag for base path restoration
uint32_t m_nFlag[DriveMax] = {}; ///< Candidate runtime flag for base path restoration
TCHAR m_szBase[DriveMax][FILEPATH_MAX] = {}; ///< Candidate for base path restoration
static DWORD g_nOption; ///< File name change flag
static uint32_t g_nOption; ///< File name change flag
};

View File

@ -37,7 +37,7 @@ using namespace ras_util;
//
//---------------------------------------------------------------------------
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
#ifndef __linux
#ifndef __linux__
return false;
#else
ifreq ifr;
@ -83,7 +83,7 @@ CTapDriver::~CTapDriver()
}
static bool ip_link(int fd, const char* ifname, bool up) {
#ifndef __linux
#ifndef __linux__
return false;
#else
ifreq ifr;
@ -126,7 +126,7 @@ static bool is_interface_up(string_view interface) {
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
{
#ifndef __linux
#ifndef __linux__
return false;
#else
unordered_map<string, string> params = const_params;
@ -158,7 +158,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
LOGTRACE("Opened tap device %d", m_hTAP)
// IFF_NO_PI for no extra packet information
ifreq ifr = {};
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
@ -177,7 +177,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
LOGTRACE("Return code from ioctl was %d", ret)
int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
const int ip_fd = socket(PF_INET, SOCK_DGRAM, 0);
if (ip_fd < 0) {
LOGERROR("Can't open ip socket: %s", strerror(errno))
@ -185,7 +185,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
return false;
}
int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
const int br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (br_socket_fd < 0) {
LOGERROR("Can't open bridge socket: %s", strerror(errno))
@ -245,7 +245,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
else {
string address = inet;
string netmask = "255.255.255.0";
string netmask = "255.255.255.0"; //NOSONAR This hardcoded IP address is safe
if (size_t separatorPos = inet.find('/'); separatorPos != string::npos) {
address = inet.substr(0, separatorPos);
@ -260,7 +260,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
}
// long long is required for compatibility with 32 bit platforms
auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
const auto mask = (long long)(pow(2, 32) - (1 << (32 - m)));
netmask = to_string((mask >> 24) & 0xff) + '.' + to_string((mask >> 16) & 0xff) + '.' +
to_string((mask >> 8) & 0xff) + '.' + to_string(mask & 0xff);
@ -391,18 +391,18 @@ void CTapDriver::OpenDump(const Filepath& path) {
bool CTapDriver::Enable() const
{
int fd = socket(PF_INET, SOCK_DGRAM, 0);
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set ras0 up", __PRETTY_FUNCTION__)
bool result = ip_link(fd, "ras0", true);
const bool result = ip_link(fd, "ras0", true);
close(fd);
return result;
}
bool CTapDriver::Disable() const
{
int fd = socket(PF_INET, SOCK_DGRAM, 0);
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
LOGDEBUG("%s: ip link set ras0 down", __PRETTY_FUNCTION__)
bool result = ip_link(fd, "ras0", false);
const bool result = ip_link(fd, "ras0", false);
close(fd);
return result;
}
@ -447,12 +447,12 @@ bool CTapDriver::PendingPackets() const
}
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
uint32_t crc32(const BYTE *buf, int length) {
uint32_t CTapDriver::Crc32(const BYTE *buf, int length) {
uint32_t crc = 0xffffffff;
for (int i = 0; i < length; i++) {
crc ^= buf[i];
for (int j = 0; j < 8; j++) {
uint32_t mask = -(crc & 1);
const uint32_t mask = -((int)crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
@ -469,8 +469,8 @@ int CTapDriver::Receive(BYTE *buf)
}
// Receive
auto dwReceived = (DWORD)read(m_hTAP, buf, ETH_FRAME_LEN);
if (dwReceived == (DWORD)-1) {
auto dwReceived = (uint32_t)read(m_hTAP, buf, ETH_FRAME_LEN);
if (dwReceived == (uint32_t)-1) {
LOGWARN("%s Error occured while receiving a packet", __PRETTY_FUNCTION__)
return 0;
}
@ -480,7 +480,7 @@ int CTapDriver::Receive(BYTE *buf)
// We need to add the Frame Check Status (FCS) CRC back onto the end of the packet.
// The Linux network subsystem removes it, since most software apps shouldn't ever
// need it.
int crc = crc32(buf, dwReceived);
const int crc = Crc32(buf, dwReceived);
buf[dwReceived + 0] = (BYTE)((crc >> 0) & 0xFF);
buf[dwReceived + 1] = (BYTE)((crc >> 8) & 0xFF);

View File

@ -19,24 +19,20 @@
#include <string>
#include <array>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class CTapDriver
{
friend class SCSIDaynaPort;
friend class SCSIBR;
static constexpr const char *BRIDGE_NAME = "rascsi_bridge";
CTapDriver() = default;
~CTapDriver();
CTapDriver(CTapDriver&) = delete;
CTapDriver& operator=(const CTapDriver&) = delete;
bool Init(const unordered_map<string, string>&);
public:
CTapDriver() = default;
~CTapDriver();
CTapDriver(CTapDriver&) = default;
CTapDriver& operator=(const CTapDriver&) = default;
bool Init(const unordered_map<string, string>&);
void OpenDump(const Filepath& path); // Capture packets
void GetMacAddr(BYTE *mac) const;
int Receive(BYTE *buf);
@ -46,6 +42,8 @@ public:
bool Disable() const; // Disable the ras0 interface
void Flush(); // Purge all of the packets that are waiting to be processed
static uint32_t Crc32(const BYTE *, int);
private:
array<byte, 6> m_MacAddr; // MAC Address

View File

@ -7,17 +7,16 @@
//
//---------------------------------------------------------------------------
#include <cassert>
#include "rascsi_version.h"
#include "log.h"
#include "rascsi_exceptions.h"
#include "device.h"
#include <cassert>
#include <sstream>
#include <iomanip>
using namespace std;
Device::Device(const string& t) : type(t)
Device::Device(const string& type, int lun) : type(type), lun(lun)
{
assert(type.length() == 4);
@ -43,7 +42,7 @@ void Device::SetProtected(bool b)
void Device::SetVendor(const string& v)
{
if (v.empty() || v.length() > 8) {
throw illegal_argument_exception("Vendor '" + v + "' must be between 1 and 8 characters");
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
}
vendor = v;
@ -52,10 +51,10 @@ void Device::SetVendor(const string& v)
void Device::SetProduct(const string& p, bool force)
{
if (p.empty() || p.length() > 16) {
throw illegal_argument_exception("Product '" + p + "' must be between 1 and 16 characters");
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
}
// Changing the device name is not SCSI compliant
// Changing vital product data is not SCSI compliant
if (!product.empty() && !force) {
return;
}
@ -66,7 +65,7 @@ void Device::SetProduct(const string& p, bool force)
void Device::SetRevision(const string& r)
{
if (r.empty() || r.length() > 4) {
throw illegal_argument_exception("Revision '" + r + "' must be between 1 and 4 characters");
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
}
revision = r;
@ -74,13 +73,10 @@ void Device::SetRevision(const string& r)
string Device::GetPaddedName() const
{
string name = vendor;
name.append(8 - vendor.length(), ' ');
name += product;
name.append(16 - product.length(), ' ');
name += revision;
name.append(4 - revision.length(), ' ');
ostringstream os;
os << left << setfill(' ') << setw(8) << vendor << setw(16) << product << setw(4) << revision;
const string name = os.str();
assert(name.length() == 28);
return name;
@ -107,15 +103,6 @@ void Device::SetParams(const unordered_map<string, string>& set_params)
}
}
void Device::SetStatusCode(int s)
{
if (s) {
LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X", s >> 16, (s >> 8 &0xff), s & 0xff)
}
status_code = s;
}
bool Device::Start()
{
if (!ready) {

View File

@ -9,16 +9,13 @@
#pragma once
#include "scsi.h"
#include <unordered_map>
#include <string>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class Device
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
{
friend class DeviceFactory;
const string DEFAULT_VENDOR = "RaSCSI";
string type;
@ -45,15 +42,11 @@ class Device
bool lockable = false;
bool locked = false;
// The block size is configurable
bool block_size_configurable = false;
// Device can be created with parameters
bool supports_params = false;
// Device ID and LUN
int32_t id = 0;
int32_t lun = 0;
// Immutable LUN
int lun;
// Device identifier (for INQUIRY)
string vendor = DEFAULT_VENDOR;
@ -86,21 +79,16 @@ protected:
string GetParam(const string&) const;
void SetParams(const unordered_map<string, string>&);
explicit Device(const string&);
Device(const string&, int);
public:
virtual ~Device() = default;
Device(Device&) = delete;
Device& operator=(const Device&) = delete;
// 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; };
const string& GetType() const { return type; }
bool IsReady() const { return ready; }
void Reset();
virtual void Reset();
bool IsProtectable() const { return protectable; }
void SetProtectable(bool b) { protectable = b; }
@ -123,10 +111,8 @@ public:
bool IsLocked() const { return locked; }
void SetLocked(bool b) { locked = b; }
int32_t GetId() const { return id; }
void SetId(int32_t i) { id = i; }
int32_t GetLun() const { return lun; }
void SetLun(int32_t l) { lun = l; }
virtual int GetId() const = 0;
int GetLun() const { return lun; }
string GetVendor() const { return vendor; }
void SetVendor(const string&);
@ -142,12 +128,9 @@ public:
unordered_map<string, string> GetParams() const { return params; }
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
void SetStatusCode(int);
void SetStatusCode(int s) { status_code = s; }
bool Start();
void Stop();
virtual bool Eject(bool);
virtual void FlushCache() {
// Devices with a cache have to implement this method
}
};

View File

@ -25,8 +25,6 @@
using namespace std;
using namespace rascsi_interface;
multimap<int, unique_ptr<PrimaryDevice>> DeviceFactory::devices;
DeviceFactory::DeviceFactory()
{
sector_sizes[SCHD] = { 512, 1024, 2048, 4096 };
@ -43,9 +41,9 @@ DeviceFactory::DeviceFactory()
}
default_params[SCBR]["interface"] = network_interfaces;
default_params[SCBR]["inet"] = "10.10.20.1/24";
default_params[SCBR]["inet"] = DEFAULT_IP;
default_params[SCDP]["interface"] = network_interfaces;
default_params[SCDP]["inet"] = "10.10.20.1/24";
default_params[SCDP]["inet"] = DEFAULT_IP;
default_params[SCLP]["cmd"] = "lp -oraw %f";
default_params[SCLP]["timeout"] = "30";
@ -58,51 +56,17 @@ DeviceFactory::DeviceFactory()
extension_mapping["hdr"] = SCRM;
extension_mapping["mos"] = SCMO;
extension_mapping["iso"] = SCCD;
}
void DeviceFactory::DeleteDevice(const PrimaryDevice& device) const
{
auto [begin, end] = devices.equal_range(device.GetId());
for (auto& it = begin; it != end; ++it) {
if (it->second->GetLun() == device.GetLun()) {
devices.erase(it);
break;
}
}
}
void DeviceFactory::DeleteAllDevices() const
{
devices.clear();
}
const PrimaryDevice *DeviceFactory::GetDeviceByIdAndLun(int i, int lun) const
{
for (const auto& [id, device] : devices) {
if (device->GetId() == i && device->GetLun() == lun) {
return device.get();
}
}
return nullptr;
}
list<PrimaryDevice *> DeviceFactory::GetAllDevices() const
{
list<PrimaryDevice *> result;
for (const auto& [id, device] : devices) {
result.push_back(device.get());
}
return result;
device_mapping["bridge"] = SCBR;
device_mapping["daynaport"] = SCDP;
device_mapping["printer"] = SCLP;
device_mapping["services"] = SCHS;
}
string DeviceFactory::GetExtension(const string& filename) const
{
string ext;
if (size_t separator = filename.rfind('.'); separator != string::npos) {
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); });
@ -115,24 +79,17 @@ PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
if (const auto& it = extension_mapping.find(GetExtension(filename)); it != extension_mapping.end()) {
return it->second;
}
else if (filename == "bridge") {
return SCBR;
}
else if (filename == "daynaport") {
return SCDP;
}
else if (filename == "printer") {
return SCLP;
}
else if (filename == "services") {
return SCHS;
if (const auto& it = device_mapping.find(filename); it != device_mapping.end()) {
return it->second;
}
return UNDEFINED;
}
// ID -1 is used by rascsi to create a temporary device
PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename, int id)
shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type,
int lun, const string& filename)
{
// If no type was specified try to derive the device type from the filename
if (type == UNDEFINED) {
@ -142,13 +99,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
}
}
unique_ptr<PrimaryDevice> device;
shared_ptr<PrimaryDevice> device;
switch (type) {
case SCHD: {
if (string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_unique<SCSIHD_NEC>();
if (const string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
device = make_shared<SCSIHD_NEC>(lun);
} else {
device = make_unique<SCSIHD>(sector_sizes[SCHD], false, ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
device = make_shared<SCSIHD>(lun, sector_sizes[SCHD], false,
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
// Some Apple tools require a particular drive identification
if (ext == "hda") {
@ -162,7 +120,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
}
case SCRM:
device = make_unique<SCSIHD>(sector_sizes[SCRM], true);
device = make_shared<SCSIHD>(lun, sector_sizes[SCRM], true);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -171,7 +129,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCMO:
device = make_unique<SCSIMO>(sector_sizes[SCMO]);
device = make_shared<SCSIMO>(lun, sector_sizes[SCMO]);
device->SetProtectable(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -180,7 +138,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCCD:
device = make_unique<SCSICD>(sector_sizes[SCCD]);
device = make_shared<SCSICD>(lun, sector_sizes[SCCD]);
device->SetReadOnly(true);
device->SetStoppable(true);
device->SetRemovable(true);
@ -189,14 +147,15 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCBR:
device = make_unique<SCSIBR>();
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;
case SCDP:
device = make_unique<SCSIDaynaPort>();
device = make_shared<SCSIDaynaPort>(lun);
// Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly
device->SetVendor("Dayna");
device->SetProduct("SCSI/Link");
@ -206,14 +165,14 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
case SCHS:
device = make_unique<HostServices>(*this);
device = make_shared<HostServices>(lun, controller_manager);
// Since this is an emulation for a specific device the full INQUIRY data have to be set accordingly
device->SetVendor("RaSCSI");
device->SetProduct("Host Services");
break;
case SCLP:
device = make_unique<SCSIPrinter>();
device = make_shared<SCSIPrinter>(lun);
device->SetProduct("SCSI PRINTER");
device->SupportsParams(true);
device->SetDefaultParams(default_params[SCLP]);
@ -223,17 +182,7 @@ PrimaryDevice *DeviceFactory::CreateDevice(PbDeviceType type, const string& file
break;
}
if (device != nullptr) {
PrimaryDevice *d = device.release();
d->SetId(id);
devices.emplace(id, d);
return d;
}
return nullptr;
return device;
}
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
@ -260,7 +209,7 @@ list<string> DeviceFactory::GetNetworkInterfaces() const
{
list<string> network_interfaces;
#ifdef __linux
#ifdef __linux__
ifaddrs *addrs;
getifaddrs(&addrs);
ifaddrs *tmp = addrs;
@ -268,10 +217,10 @@ list<string> DeviceFactory::GetNetworkInterfaces() const
while (tmp) {
if (tmp->ifa_addr && tmp->ifa_addr->sa_family == AF_PACKET &&
strcmp(tmp->ifa_name, "lo") && strcmp(tmp->ifa_name, "rascsi_bridge")) {
int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
const int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
ifreq ifr = {};
strcpy(ifr.ifr_name, tmp->ifa_name);
strcpy(ifr.ifr_name, tmp->ifa_name); //NOSONAR Using strcpy is safe here
// Only list interfaces that are up
if (!ioctl(fd, SIOCGIFFLAGS, &ifr) && (ifr.ifr_flags & IFF_UP)) {
network_interfaces.emplace_back(tmp->ifa_name);

View File

@ -11,50 +11,46 @@
#pragma once
#include <string>
#include <list>
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <string>
#include "rascsi_interface.pb.h"
using namespace std; //NOSONAR Not relevant for rascsi
using namespace rascsi_interface; //NOSONAR Not relevant for rascsi
using namespace std;
using namespace rascsi_interface;
class ControllerManager;
class PrimaryDevice;
class DeviceFactory
{
const string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe
public:
DeviceFactory();
~DeviceFactory() = default;
DeviceFactory(DeviceFactory&) = delete;
DeviceFactory& operator=(const DeviceFactory&) = delete;
PrimaryDevice *CreateDevice(PbDeviceType, const string&, int);
void DeleteDevice(const PrimaryDevice&) const;
void DeleteAllDevices() const;
const PrimaryDevice *GetDeviceByIdAndLun(int, int) const;
list<PrimaryDevice *> GetAllDevices() const;
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;
unordered_map<string, PbDeviceType> GetExtensionMapping() const { return extension_mapping; }
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;
unordered_map<string, PbDeviceType> extension_mapping;
string GetExtension(const string&) const;
static std::multimap<int, unique_ptr<PrimaryDevice>> devices;
unordered_map<string, PbDeviceType> device_mapping;
unordered_set<uint32_t> empty_set;
unordered_map<string, string> empty_map;

View File

@ -14,9 +14,7 @@
//
//---------------------------------------------------------------------------
#include "os.h"
#include "fileio.h"
#include "file_support.h"
#include "rascsi_exceptions.h"
#include "dispatcher.h"
#include "scsi_command_util.h"
@ -25,7 +23,9 @@
using namespace scsi_defs;
using namespace scsi_command_util;
Disk::Disk(const string& id) : ModePageDevice(id)
unordered_map<string, id_set> Disk::reserved_files;
Disk::Disk(const string& type, int lun) : ModePageDevice(type, lun)
{
dispatcher.Add(scsi_command::eCmdRezero, "Rezero", &Disk::Rezero);
dispatcher.Add(scsi_command::eCmdFormat, "FormatUnit", &Disk::FormatUnit);
@ -73,7 +73,7 @@ bool Disk::Dispatch(scsi_command cmd)
is_medium_changed = false;
throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
}
// The superclass handles the less specific commands
@ -88,7 +88,9 @@ bool Disk::Dispatch(scsi_command cmd)
//---------------------------------------------------------------------------
void Disk::Open(const Filepath& path)
{
assert(blocks > 0);
if (blocks == 0) {
throw io_exception("Disk has 0 blocks");
}
SetReady(true);
@ -108,10 +110,17 @@ void Disk::Open(const Filepath& path)
SetLocked(false);
}
void Disk::SetUpCache(const Filepath& path, off_t image_offset)
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);
cache->SetRawMode(raw);
}
void Disk::ResizeCache(const Filepath& path, bool raw)
{
cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)blocks));
cache->SetRawMode(raw);
}
void Disk::FlushCache()
@ -132,7 +141,7 @@ void Disk::FormatUnit()
// FMTDATA=1 is not supported (but OK if there is no DEFECT LIST)
if ((ctrl->cmd[1] & 0x10) != 0 && ctrl->cmd[4] != 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
EnterStatusPhase();
@ -178,7 +187,7 @@ void Disk::ReadWriteLong10()
{
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(ctrl->cmd, 7) != 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
ValidateBlockAddress(RW10);
@ -190,7 +199,7 @@ void Disk::ReadWriteLong16()
{
// Transfer lengths other than 0 are not supported, which is compliant with the SCSI standard
if (GetInt16(ctrl->cmd, 12) != 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
ValidateBlockAddress(RW16);
@ -262,14 +271,14 @@ void Disk::Verify16()
void Disk::StartStopUnit()
{
bool start = ctrl->cmd[4] & 0x01;
bool load = ctrl->cmd[4] & 0x02;
const bool start = ctrl->cmd[4] & 0x01;
const bool load = ctrl->cmd[4] & 0x02;
if (load) {
LOGTRACE("%s", start ? "Loading medium" : "Ejecting medium")
LOGTRACE(start ? "Loading medium" : "Ejecting medium")
}
else {
LOGTRACE("%s", start ? "Starting unit" : "Stopping unit")
LOGTRACE(start ? "Starting unit" : "Stopping unit")
SetStopped(!start);
}
@ -281,12 +290,12 @@ void Disk::StartStopUnit()
if (load) {
if (IsLocked()) {
// Cannot be ejected because it is locked
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LOAD_OR_EJECT_FAILED);
}
// Eject
if (!Eject(false)) {
throw scsi_error_exception();
throw scsi_exception();
}
}
}
@ -298,12 +307,12 @@ void Disk::SendDiagnostic()
{
// Do not support PF bit
if (ctrl->cmd[1] & 0x10) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
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_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
EnterStatusPhase();
@ -313,9 +322,9 @@ void Disk::PreventAllowMediumRemoval()
{
CheckReady();
bool lock = ctrl->cmd[4] & 0x01;
const bool lock = ctrl->cmd[4] & 0x01;
LOGTRACE("%s", lock ? "Locking medium" : "Unlocking medium")
LOGTRACE(lock ? "Locking medium" : "Unlocking medium")
SetLocked(lock);
@ -331,7 +340,7 @@ void Disk::SynchronizeCache()
void Disk::ReadDefectData10()
{
size_t allocation_length = min((size_t)GetInt16(ctrl->cmd, 7), (size_t)4);
const size_t allocation_length = min((size_t)GetInt16(ctrl->cmd, 7), (size_t)4);
// The defect list is empty
fill_n(controller->GetBuffer().begin(), allocation_length, 0);
@ -346,30 +355,28 @@ void Disk::MediumChanged()
is_medium_changed = true;
}
else {
LOGWARN("%s Medium change requested for non-reomvable medium", __PRETTY_FUNCTION__)
LOGERROR("Medium change requested for non-removable medium")
}
}
bool Disk::Eject(bool force)
{
bool status = super::Eject(force);
const bool status = super::Eject(force);
if (status) {
FlushCache();
cache.reset();
// The image file for this drive is not in use anymore
if (auto file_support = dynamic_cast<FileSupport *>(this); file_support) {
file_support->UnreserveFile();
}
UnreserveFile();
}
return status;
}
int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Get length, clear buffer
auto length = (int)min((size_t)max_length, (size_t)cdb[4]);
const auto length = (int)min(buf.size(), (size_t)cdb[4]);
fill_n(buf.begin(), length, 0);
// DEVICE SPECIFIC PARAMETER
@ -378,7 +385,7 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
}
// Basic information
int info_size = 4;
int size = 4;
// Add block descriptor if DBD is 0
if ((cdb[1] & 0x08) == 0) {
@ -388,33 +395,33 @@ int Disk::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
// Only if ready
if (IsReady()) {
// Short LBA mode parameter block descriptor (number of blocks and block length)
SetInt32(buf, 4, (uint32_t)GetBlockCount());
SetInt32(buf, 4, (uint32_t)blocks);
SetInt32(buf, 8, GetSectorSizeInBytes());
}
info_size = 12;
size = 12;
}
info_size += super::AddModePages(cdb, buf, info_size, length - info_size);
if (info_size > 255) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
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 (info_size > length) {
info_size = length;
if (size > length) {
size = length;
}
// Final setting of mode data length
buf[0] = (BYTE)info_size;
buf[0] = (BYTE)size;
return info_size;
return size;
}
int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Get length, clear buffer
auto length = (int)min((size_t)max_length, (size_t)GetInt16(cdb, 7));
const auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7));
fill_n(buf.begin(), length, 0);
// DEVICE SPECIFIC PARAMETER
@ -423,11 +430,11 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
}
// Basic Information
int info_size = 8;
int size = 8;
// Add block descriptor if DBD is 0, only if ready
if ((cdb[1] & 0x08) == 0 && IsReady()) {
uint64_t disk_blocks = GetBlockCount();
uint64_t disk_blocks = blocks;
uint32_t disk_size = GetSectorSizeInBytes();
// Check LLBAA for short or long block descriptor
@ -439,7 +446,7 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
SetInt32(buf, 8, (uint32_t)disk_blocks);
SetInt32(buf, 12, disk_size);
info_size = 16;
size = 16;
}
else {
// Mode parameter header, LONGLBA
@ -452,24 +459,24 @@ int Disk::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length)
SetInt64(buf, 8, disk_blocks);
SetInt32(buf, 20, disk_size);
info_size = 24;
size = 24;
}
}
info_size += super::AddModePages(cdb, buf, info_size, length - info_size);
if (info_size > 65535) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
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 (info_size > length) {
info_size = length;
if (size > length) {
size = length;
}
// Final setting of mode data length
SetInt16(buf, 0, info_size);
SetInt16(buf, 0, size);
return info_size;
return size;
}
void Disk::SetUpModePages(map<int, vector<byte>>& pages, int page, bool changeable) const
@ -614,12 +621,12 @@ int Disk::Read(const vector<int>&, vector<BYTE>& buf, uint64_t block)
// Error if the total number of blocks is exceeded
if (block >= blocks) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// leave it to the cache
if (!cache->ReadSector(buf, (uint32_t)block)) {
throw scsi_error_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::READ_FAULT);
}
// Success
@ -632,12 +639,12 @@ int Disk::WriteCheck(uint64_t block)
// Error if the total number of blocks is exceeded
if (block >= blocks) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Error if write protected
if (IsProtected()) {
throw scsi_error_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
}
// Success
@ -651,22 +658,22 @@ void Disk::Write(const vector<int>&, const vector<BYTE>& buf, uint64_t block)
// Error if not ready
if (!IsReady()) {
throw scsi_error_exception(sense_key::NOT_READY);
throw scsi_exception(sense_key::NOT_READY);
}
// Error if the total number of blocks is exceeded
if (block >= blocks) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
}
// Error if write protected
if (IsProtected()) {
throw scsi_error_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
throw scsi_exception(sense_key::DATA_PROTECT, asc::WRITE_PROTECTED);
}
// Leave it to the cache
if (!cache->WriteSector(buf, (uint32_t)block)) {
throw scsi_error_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT);
throw scsi_exception(sense_key::MEDIUM_ERROR, asc::WRITE_FAULT);
}
}
@ -700,7 +707,7 @@ void Disk::ReadCapacity10()
CheckReady();
if (blocks == 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
}
vector<BYTE>& buf = controller->GetBuffer();
@ -728,7 +735,7 @@ void Disk::ReadCapacity16()
CheckReady();
if (blocks == 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::MEDIUM_NOT_PRESENT);
}
vector<BYTE>& buf = controller->GetBuffer();
@ -763,7 +770,7 @@ void Disk::ReadCapacity16_ReadLong16()
break;
default:
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
break;
}
}
@ -796,14 +803,12 @@ void Disk::Release()
void Disk::ValidateBlockAddress(access_mode mode) const
{
uint64_t block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2);
const uint64_t block = mode == RW16 ? GetInt64(ctrl->cmd, 2) : GetInt32(ctrl->cmd, 2);
uint64_t capacity = GetBlockCount();
if (block > capacity) {
LOGTRACE("%s", ("Capacity of " + to_string(capacity) + " block(s) exceeded: Trying to access block "
if (block > blocks) {
LOGTRACE("%s", ("Capacity of " + to_string(blocks) + " block(s) exceeded: Trying to access block "
+ to_string(block)).c_str())
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
}
}
@ -834,10 +839,10 @@ 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 = GetBlockCount(); start > capacity || start + count > capacity) {
if (uint64_t capacity = blocks; 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_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
}
// Do not process 0 blocks
@ -879,7 +884,7 @@ void Disk::SetSectorSizeInBytes(uint32_t size_in_bytes)
break;
default:
assert(false);
throw io_exception("Invalid block size of " + to_string(size_in_bytes) + " bytes");
break;
}
}
@ -900,3 +905,43 @@ 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

@ -25,12 +25,18 @@
#include <string>
#include <unordered_set>
class Disk : public ModePageDevice, public ScsiBlockCommands
using namespace std;
using id_set = pair<int, int>;
class Disk : public ModePageDevice, private ScsiBlockCommands
{
enum access_mode { RW6, RW10, RW16, SEEK6, SEEK10 };
Dispatcher<Disk> dispatcher;
unique_ptr<DiskCache> cache;
// The supported configurable sector sizes, empty if not configurable
unordered_set<uint32_t> sector_sizes;
uint32_t configured_sector_size = 0;
@ -43,12 +49,15 @@ class Disk : public ModePageDevice, public ScsiBlockCommands
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:
explicit Disk(const string&);
Disk(const string&, int);
~Disk() override;
Disk(Disk&) = delete;
Disk& operator=(const Disk&) = delete;
bool Dispatch(scsi_command) override;
@ -67,6 +76,18 @@ public:
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;
@ -105,13 +126,13 @@ private:
void ValidateBlockAddress(access_mode) const;
bool CheckAndGetStartAndCount(uint64_t&, uint32_t&, access_mode) const;
int ModeSense6(const vector<int>&, vector<BYTE>&, int) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&, int) const override;
int ModeSense6(const vector<int>&, vector<BYTE>&) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
protected:
virtual void Open(const Filepath&);
void SetUpCache(const Filepath&, off_t = 0);
void SetUpCache(const Filepath&, off_t, bool = false);
void ResizeCache(const Filepath&, bool);
void SetUpModePages(map<int, vector<byte>>&, int, bool) const override;
virtual void AddErrorPage(map<int, vector<byte>>&, bool) const;
@ -126,6 +147,5 @@ protected:
void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; }
uint32_t GetConfiguredSectorSize() const;
void SetBlockCount(uint64_t b) { blocks = b; }
unique_ptr<DiskCache> cache;
void SetPath(const Filepath& path) { diskpath = path; }
};

View File

@ -16,6 +16,8 @@
#include "disk_track.h"
#include "disk_cache.h"
#include <cstdlib>
#include <cassert>
DiskCache::DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff)
: sec_size(size), sec_blocks(blocks), imgoffset(imgoff)

View File

@ -19,7 +19,7 @@
#include <array>
#include <memory>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class DiskCache
{
@ -36,8 +36,6 @@ public:
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
~DiskCache() = default;
DiskCache(DiskCache&) = delete;
DiskCache& operator=(const DiskCache&) = delete;
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting

View File

@ -21,9 +21,7 @@
DiskTrack::~DiskTrack()
{
// Release memory, but do not save automatically
if (dt.buffer) {
free(dt.buffer);
}
free(dt.buffer);
}
void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
@ -70,7 +68,7 @@ bool DiskTrack::Load(const Filepath& path)
offset += dt.imgoffset;
// Calculate length (data size of this track)
int length = dt.sectors << dt.size;
const int length = dt.sectors << dt.size;
// Allocate buffer memory
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
@ -87,7 +85,7 @@ bool DiskTrack::Load(const Filepath& path)
}
// Reallocate if the buffer length is different
if (dt.length != (DWORD)length) {
if (dt.length != (uint32_t)length) {
free(dt.buffer);
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__)
@ -169,7 +167,7 @@ bool DiskTrack::Save(const Filepath& path)
offset += dt.imgoffset;
// Calculate length per sector
int length = 1 << dt.size;
const int length = 1 << dt.size;
// Open file
Fileio fio;
@ -267,8 +265,8 @@ bool DiskTrack::WriteSector(const vector<BYTE>& buf, int sec)
}
// Calculate offset and length
int offset = sec << dt.size;
int length = 1 << dt.size;
const int offset = sec << dt.size;
const int length = 1 << dt.size;
// Compare
assert(dt.buffer);

View File

@ -16,9 +16,10 @@
#pragma once
#include "filepath.h"
#include <cstdlib>
#include <vector>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class DiskTrack
{
@ -26,7 +27,7 @@ class DiskTrack
int track; // Track Number
int size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
int sectors; // Number of sectors(<0x100)
DWORD length; // Data buffer length
uint32_t length; // Data buffer length
BYTE *buffer; // Data buffer
bool init; // Is it initilized?
bool changed; // Changed flag

View File

@ -14,8 +14,8 @@
#include "log.h"
#include <unordered_map>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace scsi_defs; //NOSONAR Not relevant for rascsi
using namespace std;
using namespace scsi_defs;
template<class T>
class Dispatcher
@ -24,8 +24,6 @@ public:
Dispatcher() = default;
~Dispatcher() = default;
Dispatcher(Dispatcher&) = delete;
Dispatcher& operator=(const Dispatcher&) = delete;
using operation = void (T::*)();
using command_t = struct _command_t {

View File

@ -1,41 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
//---------------------------------------------------------------------------
#include "file_support.h"
using namespace std;
unordered_map<string, id_set> FileSupport::reserved_files;
void FileSupport::ReserveFile(const Filepath& path, int id, int lun) const
{
reserved_files[path.GetPath()] = make_pair(id, lun);
}
void FileSupport::UnreserveFile() const
{
reserved_files.erase(diskpath.GetPath());
}
bool FileSupport::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 FileSupport::UnreserveAll()
{
reserved_files.clear();
}

View File

@ -1,49 +0,0 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021-2022 Uwe Seimet
//
// Devices inheriting from FileSupport support image files
//
//---------------------------------------------------------------------------
#pragma once
#include <unordered_map>
#include <string>
#include "filepath.h"
using namespace std; //NOSONAR Not relevant for rascsi
using id_set = pair<int, int>;
class FileSupport
{
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:
FileSupport() = default;
virtual ~FileSupport() = default;
FileSupport(FileSupport&) = delete;
FileSupport& operator=(const FileSupport&) = delete;
void GetPath(Filepath& path) const { path = diskpath; }
void SetPath(const Filepath& path) { diskpath = path; }
void ReserveFile(const Filepath&, int, int) const;
void UnreserveFile() const;
static void UnreserveAll();
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
static void SetReservedFiles(const unordered_map<string, id_set>& files_in_use)
{ FileSupport::reserved_files = files_in_use; }
static bool GetIdsForReservedFile(const Filepath&, int&, int&);
virtual void Open(const Filepath&) = 0;
};

View File

@ -20,8 +20,9 @@
// c) start && load (LOAD): Reboot the Raspberry Pi
//
#include "controllers/controller_manager.h"
#include "controllers/scsi_controller.h"
#include "rascsi_exceptions.h"
#include "device_factory.h"
#include "scsi_command_util.h"
#include "dispatcher.h"
#include "host_services.h"
@ -30,10 +31,14 @@
using namespace scsi_defs;
using namespace scsi_command_util;
HostServices::HostServices(const DeviceFactory& factory) : ModePageDevice("SCHS"), device_factory(factory)
HostServices::HostServices(int lun, const ControllerManager& 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)
@ -55,45 +60,40 @@ vector<byte> HostServices::InquiryInternal() const
void HostServices::StartStopUnit()
{
bool start = ctrl->cmd[4] & 0x01;
bool load = ctrl->cmd[4] & 0x02;
const bool start = ctrl->cmd[4] & 0x01;
const bool load = ctrl->cmd[4] & 0x02;
if (!start) {
// Flush any caches
for (PrimaryDevice *device : device_factory.GetAllDevices()) {
for (const auto& device : controller_manager.GetAllDevices()) {
device->FlushCache();
}
if (load) {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_PI);
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_PI);
}
else {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::STOP_RASCSI);
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::STOP_RASCSI);
}
EnterStatusPhase();
return;
}
else if (load) {
controller->ScheduleShutdown(AbstractController::rascsi_shutdown_mode::RESTART_PI);
}
else {
if (load) {
controller->ScheduleShutdown(ScsiController::rascsi_shutdown_mode::RESTART_PI);
EnterStatusPhase();
return;
}
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
EnterStatusPhase();
}
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
auto length = (int)min((size_t)max_length, (size_t)cdb[4]);
const auto length = (int)min(buf.size(), (size_t)cdb[4]);
fill_n(buf.begin(), length, 0);
// Basic Information
@ -101,7 +101,7 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_
size += super::AddModePages(cdb, buf, size, length - size);
if (size > 255) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Do not return more than ALLOCATION LENGTH bytes
@ -114,14 +114,14 @@ int HostServices::ModeSense6(const vector<int>& cdb, vector<BYTE>& buf, int max_
return size;
}
int HostServices::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max_length) const
int HostServices::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf) const
{
// Block descriptors cannot be returned
if (!(cdb[1] & 0x08)) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
auto length = (int)min((size_t)max_length, (size_t)GetInt16(cdb, 7));
const auto length = (int)min(buf.size(), (size_t)GetInt16(cdb, 7));
fill_n(buf.begin(), length, 0);
// Basic Information
@ -129,7 +129,7 @@ int HostServices::ModeSense10(const vector<int>& cdb, vector<BYTE>& buf, int max
size += super::AddModePages(cdb, buf, size, length - size);
if (size > 65535) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Do not return more than ALLOCATION LENGTH bytes

View File

@ -15,23 +15,20 @@
#include <vector>
#include <map>
class DeviceFactory;
class ControllerManager;
class HostServices: public ModePageDevice
{
public:
explicit HostServices(const DeviceFactory&);
HostServices(int, const ControllerManager&);
~HostServices() override = default;
HostServices(HostServices&) = delete;
HostServices& operator=(const HostServices&) = delete;
bool Dispatch(scsi_command) override;
vector<byte> InquiryInternal() const override;
void TestUnitReady() override;
void StartStopUnit();
bool SupportsFile() const override { return false; }
@ -58,10 +55,11 @@ private:
Dispatcher<HostServices> dispatcher;
int ModeSense6(const vector<int>&, vector<BYTE>&, int) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&, int) const override;
const ControllerManager& controller_manager;
void StartStopUnit();
int ModeSense6(const vector<int>&, vector<BYTE>&) const override;
int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
void AddRealtimeClockPage(map<int, vector<byte>>&, bool) const;
const DeviceFactory& device_factory;
};

View File

@ -20,7 +20,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
ModePageDevice::ModePageDevice(const string& id) : PrimaryDevice(id)
ModePageDevice::ModePageDevice(const string& type, int lun) : PrimaryDevice(type, lun)
{
dispatcher.Add(scsi_command::eCmdModeSense6, "ModeSense6", &ModePageDevice::ModeSense6);
dispatcher.Add(scsi_command::eCmdModeSense10, "ModeSense10", &ModePageDevice::ModeSense10);
@ -40,10 +40,10 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
return 0;
}
bool changeable = (cdb[2] & 0xc0) == 0x40;
const bool changeable = (cdb[2] & 0xc0) == 0x40;
// Get page code (0x3f means all pages)
int page = cdb[2] & 0x3f;
const int page = cdb[2] & 0x3f;
LOGTRACE("%s Requesting mode page $%02X", __PRETTY_FUNCTION__, page)
@ -53,7 +53,7 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
if (pages.empty()) {
LOGTRACE("%s Unsupported mode page $%02X", __PRETTY_FUNCTION__, page)
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Holds all mode page data
@ -63,14 +63,14 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
for (auto const& [index, data] : pages) {
// The specification mandates that page 0 must be returned after all others
if (index) {
size_t offset = result.size();
const size_t off = result.size();
// Page data
result.insert(result.end(), data.begin(), data.end());
// Page code, PS bit may already have been set
result[offset] |= (byte)index;
result[off] |= (byte)index;
// Page payload size
result[offset + 1] = (byte)(data.size() - 2);
result[off + 1] = (byte)(data.size() - 2);
}
else {
page0 = data;
@ -79,10 +79,12 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
// Page 0 must be last
if (!page0.empty()) {
size_t off = result.size();
// Page data
result.insert(result.end(), page0.begin(), page0.end());
// Page payload size
result[result.size() + 1] = (byte)(page0.size() - 2);
result[off + 1] = (byte)(page0.size() - 2);
}
// Do not return more than the requested number of bytes
@ -94,21 +96,21 @@ int ModePageDevice::AddModePages(const vector<int>& cdb, vector<BYTE>& buf, int
void ModePageDevice::ModeSense6()
{
ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize());
ctrl->length = ModeSense6(ctrl->cmd, controller->GetBuffer());
EnterDataInPhase();
}
void ModePageDevice::ModeSense10()
{
ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer(), (int)controller->GetBufferSize());
ctrl->length = ModeSense10(ctrl->cmd, controller->GetBuffer());
EnterDataInPhase();
}
void ModePageDevice::ModeSelect(const vector<int>&, const vector<BYTE>&, int) const
{
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
}
void ModePageDevice::ModeSelect6()
@ -130,7 +132,7 @@ int ModePageDevice::ModeSelectCheck(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)) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
return length;
@ -145,7 +147,7 @@ int ModePageDevice::ModeSelectCheck6() const
int ModePageDevice::ModeSelectCheck10() const
{
// Receive the data specified by the parameter length
size_t length = min(controller->GetBufferSize(), (size_t)GetInt16(ctrl->cmd, 7));
size_t length = min(controller->GetBuffer().size(), (size_t)GetInt16(ctrl->cmd, 7));
return ModeSelectCheck((int)length);
}

View File

@ -18,10 +18,8 @@ class ModePageDevice: public PrimaryDevice
{
public:
explicit ModePageDevice(const string&);
ModePageDevice(const string&, int);
~ModePageDevice()override = default;
ModePageDevice(ModePageDevice&) = delete;
ModePageDevice& operator=(const ModePageDevice&) = delete;
bool Dispatch(scsi_command) override;
@ -38,8 +36,8 @@ private:
Dispatcher<ModePageDevice> dispatcher;
virtual int ModeSense6(const vector<int>&, vector<BYTE>&, int) const = 0;
virtual int ModeSense10(const vector<int>&, vector<BYTE>&, int) const = 0;
virtual int ModeSense6(const vector<int>&, vector<BYTE>&) const = 0;
virtual int ModeSense10(const vector<int>&, vector<BYTE>&) const = 0;
void ModeSense6();
void ModeSense10();

View File

@ -17,7 +17,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
PrimaryDevice::PrimaryDevice(const string& id) : Device(id)
PrimaryDevice::PrimaryDevice(const string& type, int lun) : Device(type, lun)
{
// Mandatory SCSI primary commands
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &PrimaryDevice::TestUnitReady);
@ -33,6 +33,15 @@ bool PrimaryDevice::Dispatch(scsi_command cmd)
return dispatcher.Dispatch(this, cmd);
}
int PrimaryDevice::GetId() const
{
if (controller == nullptr) {
LOGERROR("Device is missing its controller")
}
return controller != nullptr ? controller->GetTargetId() : -1;
}
void PrimaryDevice::SetController(AbstractController *c)
{
controller = c;
@ -50,12 +59,12 @@ void PrimaryDevice::Inquiry()
{
// EVPD and page code check
if ((ctrl->cmd[1] & 0x01) || ctrl->cmd[2]) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
vector<byte> buf = InquiryInternal();
size_t allocation_length = min(buf.size(), (size_t)GetInt16(ctrl->cmd, 3));
const size_t allocation_length = min(buf.size(), (size_t)GetInt16(ctrl->cmd, 3));
memcpy(controller->GetBuffer().data(), buf.data(), allocation_length);
ctrl->length = (uint32_t)allocation_length;
@ -65,7 +74,7 @@ void PrimaryDevice::Inquiry()
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, GetId())
// Signal that the requested LUN does not exist
controller->GetBuffer()[0] |= 0x7f;
controller->GetBuffer().data()[0] = 0x7f;
}
EnterDataInPhase();
@ -75,13 +84,13 @@ void PrimaryDevice::ReportLuns()
{
// Only SELECT REPORT mode 0 is supported
if (ctrl->cmd[2]) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
uint32_t allocation_length = GetInt32(ctrl->cmd, 6);
const uint32_t allocation_length = GetInt32(ctrl->cmd, 6);
vector<BYTE>& buf = controller->GetBuffer();
fill_n(buf.begin(), min(controller->GetBufferSize(), (size_t)allocation_length), 0);
fill_n(buf.begin(), min(buf.size(), (size_t)allocation_length), 0);
uint32_t size = 0;
for (int lun = 0; lun < controller->GetMaxLuns(); lun++) {
@ -115,12 +124,12 @@ void PrimaryDevice::RequestSense()
// Do not raise an exception here because the rest of the code must be executed
controller->Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
controller->SetStatus(0);
controller->SetStatus(status::GOOD);
}
vector<byte> buf = controller->GetDeviceForLun(lun)->HandleRequestSense();
size_t allocation_length = min(buf.size(), (size_t)ctrl->cmd[4]);
const size_t allocation_length = min(buf.size(), (size_t)ctrl->cmd[4]);
memcpy(controller->GetBuffer().data(), buf.data(), allocation_length);
ctrl->length = (uint32_t)allocation_length;
@ -134,20 +143,20 @@ void PrimaryDevice::CheckReady()
if (IsReset()) {
SetReset(false);
LOGTRACE("%s Device in reset", __PRETTY_FUNCTION__)
throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET);
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::POWER_ON_OR_RESET);
}
// Not ready if it needs attention
if (IsAttn()) {
SetAttn(false);
LOGTRACE("%s Device in needs attention", __PRETTY_FUNCTION__)
throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::NOT_READY_TO_READY_CHANGE);
}
// Return status if not ready
if (!IsReady()) {
LOGTRACE("%s Device not ready", __PRETTY_FUNCTION__)
throw scsi_error_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
}
// Initialization with no error
@ -171,7 +180,7 @@ vector<byte> PrimaryDevice::HandleInquiry(device_type type, scsi_level level, bo
buf[4] = (byte)0x1F;
// Padded vendor, product, revision
memcpy(&buf[8], GetPaddedName().c_str(), 28);
memcpy(&buf.data()[8], GetPaddedName().c_str(), 28);
return buf;
}
@ -180,7 +189,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
{
// Return not ready only if there are no errors
if (!GetStatusCode() && !IsReady()) {
throw scsi_error_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
throw scsi_exception(sense_key::NOT_READY, asc::MEDIUM_NOT_PRESENT);
}
// Set 18 bytes including extended sense data
@ -195,7 +204,7 @@ vector<byte> PrimaryDevice::HandleRequestSense() const
buf[12] = (byte)(GetStatusCode() >> 8);
buf[13] = (byte)GetStatusCode();
LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, controller->GetStatus(),
LOGTRACE("%s Status $%02X, Sense Key $%02X, ASC $%02X",__PRETTY_FUNCTION__, (int)controller->GetStatus(),
(int)buf[2], (int)buf[12])
return buf;

View File

@ -12,27 +12,33 @@
#pragma once
#include "interfaces/scsi_primary_commands.h"
#include "controllers/scsi_controller.h"
#include "controllers/abstract_controller.h"
#include "device.h"
#include "dispatcher.h"
#include <string>
class PrimaryDevice: public ScsiPrimaryCommands, public Device
class PrimaryDevice: private ScsiPrimaryCommands, public Device
{
public:
explicit PrimaryDevice(const string&);
PrimaryDevice(const string&, int);
~PrimaryDevice() override = default;
PrimaryDevice(PrimaryDevice&) = delete;
PrimaryDevice& operator=(const PrimaryDevice&) = delete;
virtual bool Dispatch(scsi_command);
int GetId() const override;
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; };
virtual void FlushCache() {
// Devices with a cache have to implement this method
}
protected:
vector<byte> HandleInquiry(scsi_defs::device_type, scsi_level, bool) const;

View File

@ -20,39 +20,37 @@ void scsi_command_util::ModeSelect(const vector<int>& cdb, const vector<BYTE>& b
// PF
if (!(cdb[1] & 0x10)) {
// Vendor-specific parameters (SCSI-1) are not supported
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
}
// Skip block descriptors
int offset;
if ((scsi_command)cdb[0] == scsi_command::eCmdModeSelect10) {
offset = 8 + GetInt16(buf, 6);
}
else {
offset = 4 + buf[3];
}
length -= offset;
bool has_valid_page_code = false;
// Mode Parameter header
int offset = 0;
if (length >= 12) {
// Check the block length
if (buf[9] != (BYTE)(sector_size >> 16) || buf[10] != (BYTE)(sector_size >> 8) ||
buf[11] != (BYTE)sector_size) {
// See below for details
LOGWARN("In order to change the sector size use the -b option when launching rascsi")
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
}
offset += 12;
length -= 12;
}
// Parsing the page
// TODO The length handling is wrong in case of length < size
// Parse the pages
while (length > 0) {
// Format device page
if (int page = buf[offset]; page == 0x03) {
if (length < 14) {
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
}
// With this page the sector size for a subsequent FORMAT can be selected, but only very few
// drives support this, e.g FUJITSU M2624S
// We are fine as long as the current sector size remains unchanged
if (buf[offset + 0xc] != (BYTE)(sector_size >> 8) || buf[offset + 0xd] != (BYTE)sector_size) {
if (GetInt16(buf, offset + 12) != sector_size) {
// With rascsi it is not possible to permanently (by formatting) change the sector size,
// because the size is an externally configurable setting only
LOGWARN("In order to change the sector size use the -b option when launching rascsi")
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
}
has_valid_page_code = true;
@ -63,12 +61,13 @@ void scsi_command_util::ModeSelect(const vector<int>& cdb, const vector<BYTE>& b
// Advance to the next page
int size = buf[offset + 1] + 2;
length -= size;
offset += size;
}
if (!has_valid_page_code) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_PARAMETER_LIST);
}
}
@ -92,30 +91,45 @@ void scsi_command_util::AddAppleVendorModePage(map<int, vector<byte>>& pages, bo
// No changeable area
if (!changeable) {
const char APPLE_DATA[] = "APPLE COMPUTER, INC ";
memcpy(&buf[2], APPLE_DATA, sizeof(APPLE_DATA));
memcpy(&buf.data()[2], APPLE_DATA, sizeof(APPLE_DATA));
}
pages[48] = buf;
}
int scsi_command_util::GetInt16(const vector<BYTE>& buf, int offset)
{
assert(buf.size() > (size_t)offset + 1);
return ((int)buf[offset] << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt16(const vector<int>& buf, int offset)
{
assert(buf.size() > (size_t)offset + 1);
return (buf[offset] << 8) | buf[offset + 1];
}
int scsi_command_util::GetInt24(const vector<int>& buf, int offset)
{
assert(buf.size() > (size_t)offset + 2);
return (buf[offset] << 16) | (buf[offset + 1] << 8) | buf[offset + 2];
}
uint32_t scsi_command_util::GetInt32(const vector<int>& buf, int offset)
{
assert(buf.size() > (size_t)offset + 3);
return ((uint32_t)buf[offset] << 24) | ((uint32_t)buf[offset + 1] << 16) |
((uint32_t)buf[offset + 2] << 8) | (uint32_t)buf[offset + 3];
}
uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
{
assert(buf.size() > (size_t)offset + 7);
return ((uint64_t)buf[offset] << 56) | ((uint64_t)buf[offset + 1] << 48) |
((uint64_t)buf[offset + 2] << 40) | ((uint64_t)buf[offset + 3] << 32) |
((uint64_t)buf[offset + 4] << 24) | ((uint64_t)buf[offset + 5] << 16) |
@ -124,12 +138,16 @@ uint64_t scsi_command_util::GetInt64(const vector<int>& buf, int offset)
void scsi_command_util::SetInt16(vector<byte>& buf, int offset, int value)
{
assert(buf.size() > (size_t)offset + 1);
buf[offset] = (byte)(value >> 8);
buf[offset + 1] = (byte)value;
}
void scsi_command_util::SetInt32(vector<byte>& buf, int offset, uint32_t value)
{
assert(buf.size() > (size_t)offset + 3);
buf[offset] = (byte)(value >> 24);
buf[offset + 1] = (byte)(value >> 16);
buf[offset + 2] = (byte)(value >> 8);
@ -138,12 +156,16 @@ void scsi_command_util::SetInt32(vector<byte>& buf, int offset, uint32_t value)
void scsi_command_util::SetInt16(vector<BYTE>& buf, int offset, int value)
{
assert(buf.size() > (size_t)offset + 1);
buf[offset] = (BYTE)(value >> 8);
buf[offset + 1] = (BYTE)value;
}
void scsi_command_util::SetInt32(vector<BYTE>& buf, int offset, uint32_t value)
{
assert(buf.size() > (size_t)offset + 3);
buf[offset] = (BYTE)(value >> 24);
buf[offset + 1] = (BYTE)(value >> 16);
buf[offset + 2] = (BYTE)(value >> 8);
@ -152,6 +174,8 @@ void scsi_command_util::SetInt32(vector<BYTE>& buf, int offset, uint32_t value)
void scsi_command_util::SetInt64(vector<BYTE>& buf, int offset, uint64_t value)
{
assert(buf.size() > (size_t)offset + 7);
buf[offset] = (BYTE)(value >> 56);
buf[offset + 1] = (BYTE)(value >> 48);
buf[offset + 2] = (BYTE)(value >> 40);

View File

@ -14,7 +14,7 @@
#include <vector>
#include <map>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
namespace scsi_command_util
{
@ -22,6 +22,7 @@ namespace scsi_command_util
void EnrichFormatPage(map<int, vector<byte>>&, bool, int);
void AddAppleVendorModePage(map<int, vector<byte>>&, bool);
int GetInt16(const vector<BYTE>&, int);
int GetInt16(const vector<int>&, int);
int GetInt24(const vector<int>&, int);
uint32_t GetInt32(const vector<int>&, int);

View File

@ -7,23 +7,20 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
//
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// Tiny SCSI Emulator
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
//
// Additional documentation and clarification is available at the
// Additional documentation and clarification is available at the
// following link:
// - https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link
//
// This does NOT include the file system functionality that is present
// in the Sharp X68000 host bridge.
//
// Note: This requires a DaynaPort SCSI Link driver.
//---------------------------------------------------------------------------
@ -36,7 +33,7 @@ using namespace scsi_defs;
using namespace scsi_command_util;
// TODO Disk must not be the superclass
SCSIDaynaPort::SCSIDaynaPort() : Disk("SCDP")
SCSIDaynaPort::SCSIDaynaPort(int lun) : Disk("SCDP", lun)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIDaynaPort::TestUnitReady);
dispatcher.Add(scsi_command::eCmdRead6, "Read6", &SCSIDaynaPort::Read6);
@ -133,13 +130,12 @@ vector<byte> SCSIDaynaPort::InquiryInternal() const
int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
{
int rx_packet_size = 0;
auto response = (scsi_resp_read_t*)buf.data();
const auto response = (scsi_resp_read_t*)buf.data();
int requested_length = cdb[4];
const int requested_length = cdb[4];
LOGTRACE("%s Read maximum length %d, (%04X)", __PRETTY_FUNCTION__, requested_length, requested_length)
// At host startup, it will send a READ(6) command with a length of 1. We should
// At host startup, it will send a READ(6) command with a length of 1. We should
// respond by going into the status mode with a code of 0x02
if (requested_length == 1) {
return 0;
@ -169,7 +165,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
LOGTRACE("%s Packet Sz %d (%08X) read: %d", __PRETTY_FUNCTION__, (unsigned int) rx_packet_size, (unsigned int) rx_packet_size, read_count)
// This is a very basic filter to prevent unnecessary packets from
// being sent to the SCSI initiator.
// being sent to the SCSI initiator.
send_message_to_host = false;
// The following doesn't seem to work with unicast messages. Temporarily removing the filtering
@ -192,7 +188,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
/////// }
send_message_to_host = true;
// TODO: We should check to see if this message is in the multicast
// TODO: We should check to see if this message is in the multicast
// configuration from SCSI command 0x0D
if (!send_message_to_host) {
@ -207,8 +203,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
// If there are pending packets to be processed, we'll tell the host that the read
// length was 0.
if (!m_tap.PendingPackets())
{
if (!m_tap.PendingPackets()) {
response->length = 0;
response->flags = read_data_flags_t::e_no_more_data;
return DAYNAPORT_READ_HEADER_SZ;
@ -239,7 +234,7 @@ int SCSIDaynaPort::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
// The CRC was already appended by the ctapdriver
return size + DAYNAPORT_READ_HEADER_SZ;
}
// If we got to this point, there are still messages in the queue, so
// If we got to this point, there are still messages in the queue, so
// we should loop back and get the next one.
} // end while
@ -252,8 +247,8 @@ int SCSIDaynaPort::WriteCheck(uint64_t)
{
CheckReady();
if (!m_bTapEnable){
throw scsi_error_exception(sense_key::UNIT_ATTENTION, asc::MEDIUM_NOT_PRESENT);
if (!m_bTapEnable) {
throw scsi_exception(sense_key::UNIT_ATTENTION, asc::MEDIUM_NOT_PRESENT);
}
return 1;
@ -279,28 +274,26 @@ int SCSIDaynaPort::WriteCheck(uint64_t)
//---------------------------------------------------------------------------
bool SCSIDaynaPort::WriteBytes(const vector<int>& cdb, const vector<BYTE>& buf, uint64_t)
{
int data_format = cdb[5];
const int data_format = cdb[5];
int data_length = GetInt16(cdb, 3);
if (data_format == 0x00){
if (data_format == 0x00) {
m_tap.Send(buf.data(), data_length);
LOGTRACE("%s Transmitted %u bytes (00 format)", __PRETTY_FUNCTION__, data_length)
}
else if (data_format == 0x80){
else if (data_format == 0x80) {
// The data length is specified in the first 2 bytes of the payload
data_length=buf[1] + (buf[0] << 8);
data_length = buf[1] + (((int)buf[0] & 0xff) << 8);
m_tap.Send(&(buf.data()[4]), data_length);
LOGTRACE("%s Transmitted %u bytes (80 format)", __PRETTY_FUNCTION__, data_length)
}
else
{
// LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, (unsigned int)command->format)
else {
LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, data_format)
}
return true;
}
//---------------------------------------------------------------------------
//
// RetrieveStats
@ -321,45 +314,7 @@ int SCSIDaynaPort::RetrieveStats(const vector<int>& cdb, vector<BYTE>& buf) cons
{
memcpy(buf.data(), &m_scsi_link_stats, sizeof(m_scsi_link_stats));
return (int)min(sizeof(m_scsi_link_stats), (size_t)GetInt16(cdb, 4));
}
//---------------------------------------------------------------------------
//
// Enable or Disable the interface
//
// Command: 0e 00 00 00 00 XX (XX = 80 or 00)
// Function: Enable (80) / disable (00) Ethernet interface
// Type: No data transferred
// Notes: After issuing an Enable, the initiator should avoid sending
// any subsequent commands to the device for approximately 0.5
// seconds
//
//---------------------------------------------------------------------------
bool SCSIDaynaPort::EnableInterface(const vector<int>& cdb)
{
bool result;
if (cdb[5] & 0x80) {
result = m_tap.Enable();
if (result) {
LOGINFO("The DaynaPort interface has been ENABLED.")
}
else{
LOGWARN("Unable to enable the DaynaPort Interface")
}
m_tap.Flush();
}
else {
result = m_tap.Disable();
if (result) {
LOGINFO("The DaynaPort interface has been DISABLED.")
}
else{
LOGWARN("Unable to disable the DaynaPort Interface")
}
}
return result;
return (int)min(sizeof(m_scsi_link_stats), (size_t)GetInt16(cdb, 3));
}
void SCSIDaynaPort::TestUnitReady()
@ -371,14 +326,14 @@ void SCSIDaynaPort::TestUnitReady()
void SCSIDaynaPort::Read6()
{
// Get record number and block number
uint32_t record = GetInt24(ctrl->cmd, 1) & 0x1fffff;
const uint32_t record = GetInt24(ctrl->cmd, 1) & 0x1fffff;
ctrl->blocks=1;
// If any commands have a bogus control value, they were probably not
// generated by the DaynaPort driver so ignore them
if (ctrl->cmd[5] != 0xc0 && ctrl->cmd[5] != 0x80) {
LOGTRACE("%s Control value %d, (%04X), returning invalid CDB", __PRETTY_FUNCTION__, ctrl->cmd[5], ctrl->cmd[5])
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
LOGTRACE("%s READ(6) command record=%d blocks=%d", __PRETTY_FUNCTION__, record, ctrl->blocks)
@ -397,21 +352,21 @@ void SCSIDaynaPort::Write6()
// Ensure a sufficient buffer size (because it is not transfer for each block)
controller->AllocateBuffer(DAYNAPORT_BUFFER_SIZE);
int data_format = ctrl->cmd[5];
const int data_format = ctrl->cmd[5];
if (data_format == 0x00) {
ctrl->length = GetInt16(ctrl->cmd, 3);
}
else if (data_format == 0x80) {
ctrl->length = GetInt16(ctrl->cmd, 3 + 8);
ctrl->length = GetInt16(ctrl->cmd, 3) + 8;
}
else {
LOGWARN("%s Unknown data format %02X", __PRETTY_FUNCTION__, data_format)
LOGWARN("%s Unknown data format $%02X", __PRETTY_FUNCTION__, data_format)
}
LOGTRACE("%s length: %04X (%d) format: %02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
LOGTRACE("%s length: $%04X (%d) format: $%02X", __PRETTY_FUNCTION__, ctrl->length, ctrl->length, data_format)
if (ctrl->length <= 0) {
throw scsi_error_exception();
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Set next block
@ -478,10 +433,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();
break;
default:
LOGWARN("%s Unknown SetInterface command received: %02X", __PRETTY_FUNCTION__, ctrl->cmd[5])
throw scsi_exception();
break;
}
}
@ -492,16 +449,45 @@ void SCSIDaynaPort::SetMcastAddr()
if (ctrl->length == 0) {
LOGWARN("%s Not supported SetMcastAddr Command %02X", __PRETTY_FUNCTION__, ctrl->cmd[2])
throw scsi_error_exception();
throw scsi_exception();
}
EnterDataOutPhase();
}
//---------------------------------------------------------------------------
//
// Enable or Disable the interface
//
// Command: 0e 00 00 00 00 XX (XX = 80 or 00)
// Function: Enable (80) / disable (00) Ethernet interface
// Type: No data transferred
// Notes: After issuing an Enable, the initiator should avoid sending
// any subsequent commands to the device for approximately 0.5
// seconds
//
//---------------------------------------------------------------------------
void SCSIDaynaPort::EnableInterface()
{
if (!EnableInterface(ctrl->cmd)) {
throw scsi_error_exception();
if (ctrl->cmd[5] & 0x80) {
if (!m_tap.Enable()) {
LOGWARN("Unable to enable the DaynaPort Interface")
throw scsi_exception();
}
m_tap.Flush();
LOGINFO("The DaynaPort interface has been ENABLED")
}
else {
if (!m_tap.Disable()) {
LOGWARN("Unable to disable the DaynaPort Interface")
throw scsi_exception();
}
LOGINFO("The DaynaPort interface has been DISABLED")
}
EnterStatusPhase();

View File

@ -7,12 +7,12 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ Emulation of the DaynaPort SCSI Link Ethernet interface ]
//
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// This design is derived from the SLINKCMD.TXT file, as well as David Kuder's
// Tiny SCSI Emulator
// - SLINKCMD: http://www.bitsavers.org/pdf/apple/scsi/dayna/daynaPORT/SLINKCMD.TXT
// - Tiny SCSI : https://hackaday.io/project/18974-tiny-scsi-emulator
@ -41,14 +41,12 @@
// DaynaPort SCSI Link
//
//===========================================================================
class SCSIDaynaPort final : public Disk
class SCSIDaynaPort : public Disk
{
public:
SCSIDaynaPort();
explicit SCSIDaynaPort(int);
~SCSIDaynaPort() override = default;
SCSIDaynaPort(SCSIDaynaPort&) = delete;
SCSIDaynaPort& operator=(const SCSIDaynaPort&) = delete;
bool Init(const unordered_map<string, string>&) override;
void Open(const Filepath& path) override;
@ -60,7 +58,6 @@ public:
int WriteCheck(uint64_t block) override;
int RetrieveStats(const vector<int>&, vector<BYTE>&) const;
bool EnableInterface(const vector<int>&);
void TestUnitReady() override;
void Read6() override;
@ -90,9 +87,10 @@ public:
// The READ response has a header which consists of:
// 2 bytes - payload size
// 4 bytes - status flags
static const DWORD DAYNAPORT_READ_HEADER_SZ = 2 + 4;
static const uint32_t DAYNAPORT_READ_HEADER_SZ = 2 + 4;
private:
using super = Disk;
Dispatcher<SCSIDaynaPort> dispatcher;

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI Host Bridge for the Sharp X68000 ]
@ -27,7 +27,7 @@ using namespace std;
using namespace scsi_defs;
using namespace scsi_command_util;
SCSIBR::SCSIBR() : Disk("SCBR")
SCSIBR::SCSIBR(int lun) : Disk("SCBR", lun)
{
// Create host file system
fs.Reset();
@ -41,7 +41,7 @@ bool SCSIBR::Init(const unordered_map<string, string>& params)
{
SetParams(params);
#ifdef __linux
#ifdef __linux__
// TAP Driver Generation
m_bTapEnable = tap.Init(GetParams());
if (!m_bTapEnable){
@ -107,13 +107,13 @@ void SCSIBR::TestUnitReady()
int SCSIBR::GetMessage10(const vector<int>& cdb, vector<BYTE>& buf)
{
// Type
int type = cdb[2];
const int type = cdb[2];
// Function number
int func = cdb[3];
const int func = cdb[3];
// Phase
int phase = cdb[9];
const int phase = cdb[9];
switch (type) {
case 1: // Ethernet
@ -195,16 +195,16 @@ int SCSIBR::GetMessage10(const vector<int>& cdb, vector<BYTE>& buf)
bool SCSIBR::WriteBytes(const vector<int>& cdb, vector<BYTE>& buf, uint64_t)
{
// Type
int type = cdb[2];
const int type = cdb[2];
// Function number
int func = cdb[3];
const int func = cdb[3];
// Phase
int phase = cdb[9];
const int phase = cdb[9];
// Get the number of lights
int len = GetInt24(cdb, 6);
const int len = GetInt24(cdb, 6);
switch (type) {
case 1: // Ethernet
@ -257,7 +257,7 @@ void SCSIBR::GetMessage10()
ctrl->length = GetMessage10(ctrl->cmd, controller->GetBuffer());
if (ctrl->length <= 0) {
throw scsi_error_exception();
throw scsi_exception();
}
// Set next block
@ -278,7 +278,7 @@ void SCSIBR::SendMessage10()
{
ctrl->length = GetInt24(ctrl->cmd, 6);
if (ctrl->length <= 0) {
throw scsi_error_exception();
throw scsi_exception();
}
// Ensure a sufficient buffer size (because it is not a transfer for each block)
@ -368,8 +368,8 @@ void SCSIBR::FS_InitDevice(vector<BYTE>& buf)
void SCSIBR::FS_CheckDir(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const uint32_t nUnit = ntohl(*dp);
const int i = sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -384,8 +384,8 @@ void SCSIBR::FS_CheckDir(vector<BYTE>& buf)
void SCSIBR::FS_MakeDir(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const uint32_t nUnit = ntohl(*dp);
const int i = sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -400,8 +400,8 @@ void SCSIBR::FS_MakeDir(vector<BYTE>& buf)
void SCSIBR::FS_RemoveDir(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const uint32_t nUnit = ntohl(*dp);
const int i = sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -416,7 +416,7 @@ void SCSIBR::FS_RemoveDir(vector<BYTE>& buf)
void SCSIBR::FS_Rename(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -435,8 +435,8 @@ void SCSIBR::FS_Rename(vector<BYTE>& buf)
void SCSIBR::FS_Delete(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const uint32_t nUnit = ntohl(*dp);
const int i = sizeof(uint32_t);
const auto *pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -451,7 +451,7 @@ void SCSIBR::FS_Delete(vector<BYTE>& buf)
void SCSIBR::FS_Attribute(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -471,11 +471,11 @@ void SCSIBR::FS_Attribute(vector<BYTE>& buf)
void SCSIBR::FS_Files(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -512,11 +512,11 @@ void SCSIBR::FS_Files(vector<BYTE>& buf)
void SCSIBR::FS_NFiles(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
auto files = (Human68k::files_t*)&(buf.data()[i]);
@ -550,11 +550,11 @@ void SCSIBR::FS_NFiles(vector<BYTE>& buf)
void SCSIBR::FS_Create(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -564,11 +564,11 @@ void SCSIBR::FS_Create(vector<BYTE>& buf)
i += sizeof(Human68k::fcb_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nAttribute = ntohl(*dp);
const uint32_t nAttribute = ntohl(*dp);
i += sizeof(uint32_t);
auto bp = (int*)&(buf.data()[i]);
uint32_t bForce = ntohl(*bp);
const uint32_t bForce = ntohl(*bp);
pFcb->fileptr = ntohl(pFcb->fileptr);
pFcb->mode = ntohs(pFcb->mode);
@ -599,11 +599,11 @@ void SCSIBR::FS_Create(vector<BYTE>& buf)
void SCSIBR::FS_Open(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
const auto pNamests = (Human68k::namests_t*)&(buf.data()[i]);
@ -640,11 +640,11 @@ void SCSIBR::FS_Open(vector<BYTE>& buf)
void SCSIBR::FS_Close(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
auto pFcb = (Human68k::fcb_t*)&(buf.data()[i]);
@ -678,14 +678,14 @@ void SCSIBR::FS_Close(vector<BYTE>& buf)
void SCSIBR::FS_Read(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
int i = sizeof(uint32_t);
auto pFcb = (Human68k::fcb_t*)&(buf.data()[i]);
i += sizeof(Human68k::fcb_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nSize = ntohl(*dp);
const uint32_t nSize = ntohl(*dp);
pFcb->fileptr = ntohl(pFcb->fileptr);
pFcb->mode = ntohs(pFcb->mode);
@ -718,14 +718,14 @@ void SCSIBR::FS_Read(vector<BYTE>& buf)
void SCSIBR::FS_Write(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
int i = sizeof(uint32_t);
auto pFcb = (Human68k::fcb_t*)&(buf.data()[i]);
i += sizeof(Human68k::fcb_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nSize = ntohl(*dp);
const uint32_t nSize = ntohl(*dp);
pFcb->fileptr = ntohl(pFcb->fileptr);
pFcb->mode = ntohs(pFcb->mode);
@ -756,14 +756,14 @@ void SCSIBR::FS_Write(vector<BYTE>& buf)
void SCSIBR::FS_Seek(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
int i = sizeof(uint32_t);
auto pFcb = (Human68k::fcb_t*)&(buf.data()[i]);
i += sizeof(Human68k::fcb_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nMode = ntohl(*dp);
const uint32_t nMode = ntohl(*dp);
i += sizeof(uint32_t);
auto ip = (const int*)&(buf.data()[i]);
@ -798,11 +798,11 @@ void SCSIBR::FS_Seek(vector<BYTE>& buf)
void SCSIBR::FS_TimeStamp(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nKey = ntohl(*dp);
const uint32_t nKey = ntohl(*dp);
i += sizeof(uint32_t);
auto pFcb = (Human68k::fcb_t*)&(buf.data()[i]);
@ -840,7 +840,7 @@ void SCSIBR::FS_TimeStamp(vector<BYTE>& buf)
void SCSIBR::FS_GetCapacity(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
Human68k::capacity_t cap;
fsresult = fs.GetCapacity(nUnit, &cap);
@ -862,8 +862,8 @@ void SCSIBR::FS_GetCapacity(vector<BYTE>& buf)
void SCSIBR::FS_CtrlDrive(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
const uint32_t nUnit = ntohl(*dp);
const int i = sizeof(uint32_t);
auto pCtrlDrive = (Human68k::ctrldrive_t*)&(buf.data()[i]);
@ -881,7 +881,7 @@ void SCSIBR::FS_CtrlDrive(vector<BYTE>& buf)
void SCSIBR::FS_GetDPB(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
Human68k::dpb_t dpb;
fsresult = fs.GetDPB(nUnit, &dpb);
@ -905,11 +905,11 @@ void SCSIBR::FS_GetDPB(vector<BYTE>& buf)
void SCSIBR::FS_DiskRead(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nSector = ntohl(*dp);
const uint32_t nSector = ntohl(*dp);
i += sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
@ -927,7 +927,7 @@ void SCSIBR::FS_DiskRead(vector<BYTE>& buf)
void SCSIBR::FS_DiskWrite(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
fsresult = fs.DiskWrite(nUnit);
}
@ -940,18 +940,18 @@ void SCSIBR::FS_DiskWrite(vector<BYTE>& buf)
void SCSIBR::FS_Ioctrl(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
int i = sizeof(uint32_t);
dp = (uint32_t*)&(buf.data()[i]);
uint32_t nFunction = ntohl(*dp);
const uint32_t nFunction = ntohl(*dp);
i += sizeof(uint32_t);
auto pIoctrl = (Human68k::ioctrl_t*)&(buf.data()[i]);
switch (nFunction) {
case 2:
case (DWORD)-2:
case (uint32_t)-2:
pIoctrl->param = htonl(pIoctrl->param);
break;
default:
@ -965,7 +965,7 @@ void SCSIBR::FS_Ioctrl(vector<BYTE>& buf)
pIoctrl->media = htons(pIoctrl->media);
break;
case 1:
case (DWORD)-3:
case (uint32_t)-3:
pIoctrl->param = htonl(pIoctrl->param);
break;
default:
@ -986,7 +986,7 @@ void SCSIBR::FS_Ioctrl(vector<BYTE>& buf)
void SCSIBR::FS_Flush(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
fsresult = fs.Flush(nUnit);
}
@ -999,7 +999,7 @@ void SCSIBR::FS_Flush(vector<BYTE>& buf)
void SCSIBR::FS_CheckMedia(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
fsresult = fs.CheckMedia(nUnit);
}
@ -1012,7 +1012,7 @@ void SCSIBR::FS_CheckMedia(vector<BYTE>& buf)
void SCSIBR::FS_Lock(vector<BYTE>& buf)
{
auto dp = (uint32_t*)buf.data();
uint32_t nUnit = ntohl(*dp);
const uint32_t nUnit = ntohl(*dp);
fsresult = fs.Lock(nUnit);
}

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI Host Bridge for the Sharp X68000 ]
@ -24,18 +24,16 @@
#include <string>
#include <array>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
class SCSIBR final : public Disk
class SCSIBR : public Disk
{
static constexpr const array<BYTE, 6> bcast_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
public:
SCSIBR();
explicit SCSIBR(int);
~SCSIBR() override = default;
SCSIBR(SCSIBR&) = delete;
SCSIBR& operator=(const SCSIBR&) = delete;
bool Init(const unordered_map<string, string>&) override;
bool Dispatch(scsi_command) override;
@ -101,9 +99,9 @@ private:
void FS_Lock(vector<BYTE>&); // $58 - get exclusive control
CFileSys fs; // File system accessor
DWORD fsresult = 0; // File system access result code
uint32_t fsresult = 0; // File system access result code
array<BYTE, 0x800> fsout; // File system access result buffer
DWORD fsoutlen = 0; // File system access result buffer size
uint32_t fsoutlen = 0; // File system access result buffer size
array<BYTE, 0x1000000> fsopt; // File system access buffer
DWORD fsoptlen = 0; // File system access buffer size
uint32_t fsoptlen = 0; // File system access buffer size
};

View File

@ -51,7 +51,7 @@ using namespace scsi_defs;
using namespace ras_util;
using namespace scsi_command_util;
SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP")
SCSIPrinter::SCSIPrinter(int lun) : PrimaryDevice("SCLP", lun)
{
dispatcher.Add(scsi_command::eCmdTestUnitReady, "TestUnitReady", &SCSIPrinter::TestUnitReady);
dispatcher.Add(scsi_command::eCmdReserve6, "ReserveUnit", &SCSIPrinter::ReserveUnit);
@ -60,6 +60,9 @@ SCSIPrinter::SCSIPrinter() : PrimaryDevice("SCLP")
dispatcher.Add(scsi_command::eCmdSynchronizeBuffer, "SynchronizeBuffer", &SCSIPrinter::SynchronizeBuffer);
dispatcher.Add(scsi_command::eCmdSendDiag, "SendDiagnostic", &SCSIPrinter::SendDiagnostic);
dispatcher.Add(scsi_command::eCmdStartStop, "StopPrint", &SCSIPrinter::StopPrint);
SetReady(true);
SetReset(false);
}
SCSIPrinter::~SCSIPrinter()
@ -145,15 +148,15 @@ void SCSIPrinter::Print()
{
CheckReservation();
uint32_t length = GetInt24(ctrl->cmd, 2);
const uint32_t length = GetInt24(ctrl->cmd, 2);
LOGTRACE("Receiving %d byte(s) to be printed", length)
if (length > controller->GetBufferSize()) {
LOGERROR("%s", string("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBufferSize()) +
if (length > controller->GetBuffer().size()) {
LOGERROR("%s", ("Transfer buffer overflow: Buffer size is " + to_string(controller->GetBuffer().size()) +
" bytes, " + to_string(length) + " bytes expected").c_str())
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
ctrl->length = length;
@ -167,11 +170,11 @@ void SCSIPrinter::SynchronizeBuffer()
CheckReservation();
if (fd == -1) {
throw scsi_error_exception();
throw scsi_exception();
}
// Make the file readable for the lp user
fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); //NOSONAR Granting permissions to "others" is required here
struct stat st;
fstat(fd, &st);
@ -180,7 +183,7 @@ void SCSIPrinter::SynchronizeBuffer()
fd = -1;
string cmd = GetParam("cmd");
size_t file_position = cmd.find("%f");
const size_t file_position = cmd.find("%f");
assert(file_position != string::npos);
cmd.replace(file_position, 2, filename);
cmd = "sudo -u lp " + cmd;
@ -194,7 +197,7 @@ void SCSIPrinter::SynchronizeBuffer()
unlink(filename);
throw scsi_error_exception();
throw scsi_exception();
}
unlink(filename);
@ -217,7 +220,7 @@ void SCSIPrinter::StopPrint()
bool SCSIPrinter::WriteByteSequence(vector<BYTE>& buf, uint32_t length)
{
if (fd == -1) {
strcpy(filename, TMP_FILE_PATTERN);
strcpy(filename, TMP_FILE_PATTERN); //NOSONAR Using strcpy is safe here
fd = mkstemp(filename);
if (fd == -1) {
LOGERROR("Can't create printer output file '%s': %s", filename, strerror(errno))
@ -229,7 +232,7 @@ bool SCSIPrinter::WriteByteSequence(vector<BYTE>& buf, uint32_t length)
LOGTRACE("Appending %d byte(s) to printer output file '%s'", length, filename)
auto num_written = (uint32_t)write(fd, buf.data(), length);
const auto num_written = (uint32_t)write(fd, buf.data(), length);
return num_written == length;
}
@ -248,7 +251,7 @@ void SCSIPrinter::CheckReservation()
LOGTRACE("Unknown initiator tries to access reserved device ID %d, LUN %d", GetId(), GetLun())
}
throw scsi_error_exception(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION,
throw scsi_exception(sense_key::ABORTED_COMMAND, asc::NO_ADDITIONAL_SENSE_INFORMATION,
status::RESERVATION_CONFLICT);
}

View File

@ -15,19 +15,17 @@
#include <string>
#include <unordered_map>
class SCSIPrinter final : public PrimaryDevice, public ScsiPrinterCommands //NOSONAR Custom destructor cannot be removed
class SCSIPrinter : public PrimaryDevice, ScsiPrinterCommands //NOSONAR Custom destructor cannot be removed
{
static constexpr const char *TMP_FILE_PATTERN = "/tmp/rascsi_sclp-XXXXXX";
static constexpr const char *TMP_FILE_PATTERN = "/tmp/rascsi_sclp-XXXXXX"; //NOSONAR Using /tmp is safe
static const int TMP_FILENAME_LENGTH = string_view(TMP_FILE_PATTERN).size();
static const int NOT_RESERVED = -2;
public:
SCSIPrinter();
explicit SCSIPrinter(int);
~SCSIPrinter() override;
SCSIPrinter(SCSIPrinter&) = delete;
SCSIPrinter& operator=(const SCSIPrinter&) = delete;
bool Dispatch(scsi_command) override;

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
@ -24,7 +24,7 @@
using namespace scsi_defs;
using namespace scsi_command_util;
SCSICD::SCSICD(const unordered_set<uint32_t>& sector_sizes) : Disk("SCCD")
SCSICD::SCSICD(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCCD", lun)
{
SetSectorSizes(sector_sizes);
@ -90,12 +90,9 @@ void SCSICD::Open(const Filepath& path)
assert(GetBlockCount() > 0);
super::Open(path);
FileSupport::SetPath(path);
SetPath(path);
SetUpCache(path);
// Set RAW flag
cache->SetRawMode(rawfile);
SetUpCache(path, 0, rawfile);
// Attention if ready
if (IsReady()) {
@ -103,7 +100,7 @@ void SCSICD::Open(const Filepath& path)
}
}
void SCSICD::OpenCue(const Filepath& /*path*/) const
void SCSICD::OpenCue(const Filepath&) const
{
throw io_exception("Opening CUE CD-ROM files is not supported");
}
@ -117,8 +114,8 @@ void SCSICD::OpenIso(const Filepath& path)
}
// Get file size
off_t file_size = fio.GetFileSize();
if (file_size < 0x800) {
const off_t size = fio.GetFileSize();
if (size < 0x800) {
fio.Close();
throw io_exception("ISO CD-ROM file size must be at least 2048 bytes");
}
@ -157,16 +154,16 @@ void SCSICD::OpenIso(const Filepath& path)
if (rawfile) {
// Size must be a multiple of 2536
if (file_size % 2536) {
if (size % 2536) {
throw io_exception("Raw ISO CD-ROM file size must be a multiple of 2536 bytes but is "
+ to_string(file_size) + " bytes");
+ to_string(size) + " bytes");
}
// Set the number of blocks
SetBlockCount((DWORD)(file_size / 0x930));
SetBlockCount((uint32_t)(size / 0x930));
} else {
// Set the number of blocks
SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount()));
SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount()));
}
// Create only one data track
@ -200,7 +197,7 @@ void SCSICD::OpenPhysical(const Filepath& path)
size = (size / 512) * 512;
// Set the number of blocks
SetBlockCount((DWORD)(size >> GetSectorSizeShiftCount()));
SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount()));
// Create only one data track
assert(!tracks.size());
@ -278,11 +275,11 @@ int SCSICD::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t block)
CheckReady();
// Search for the track
int index = SearchTrack((int)block);
const int index = SearchTrack((int)block);
// If invalid, out of range
if (index < 0) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::LBA_OUT_OF_RANGE);
}
assert(tracks[index]);
@ -296,9 +293,9 @@ int SCSICD::Read(const vector<int>& cdb, vector<BYTE>& buf, uint64_t block)
// Recreate the disk cache
Filepath path;
tracks[index]->GetPath(path);
// Re-assign disk cache (no need to save)
cache.reset(new DiskCache(path, GetSectorSizeShiftCount(), (uint32_t)GetBlockCount()));
cache->SetRawMode(rawfile);
ResizeCache(path, rawfile);
// Reset data index
dataindex = index;
@ -318,17 +315,17 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<BYTE>& buf)
assert(tracks[0]);
// Get allocation length, clear buffer
int length = GetInt16(cdb, 7);
const int length = GetInt16(cdb, 7);
fill_n(buf.data(), length, 0);
// Get MSF Flag
bool msf = cdb[1] & 0x02;
const bool msf = cdb[1] & 0x02;
// Get and check the last track number
int last = tracks[tracks.size() - 1]->GetTrackNo();
const int last = tracks[tracks.size() - 1]->GetTrackNo();
// Except for AA
if (cdb[6] > last && cdb[6] != 0xaa) {
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
// Check start index
@ -361,12 +358,12 @@ int SCSICD::ReadTocInternal(const vector<int>& cdb, vector<BYTE>& buf)
}
// Otherwise, error
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
}
// Number of track descriptors returned this time (number of loops)
int loop = last - tracks[index]->GetTrackNo() + 1;
const int loop = last - tracks[index]->GetTrackNo() + 1;
assert(loop >= 1);
// Create header
@ -410,11 +407,11 @@ void SCSICD::GetEventStatusNotification()
{
if (!(ctrl->cmd[1] & 0x01)) {
// Asynchronous notification is optional and not supported by rascsi
throw scsi_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
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_error_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
throw scsi_exception(sense_key::ILLEGAL_REQUEST, asc::INVALID_FIELD_IN_CDB);
}
//---------------------------------------------------------------------------
@ -427,7 +424,7 @@ void SCSICD::LBAtoMSF(uint32_t lba, BYTE *msf) const
// 75 and 75*60 get the remainder
uint32_t m = lba / (75 * 60);
uint32_t s = lba % (75 * 60);
uint32_t f = s % 75;
const uint32_t f = s % 75;
s /= 75;
// The base point is M=0, S=2, F=0
@ -462,7 +459,7 @@ void SCSICD::ClearTrack()
// * Returns -1 if not found
//
//---------------------------------------------------------------------------
int SCSICD::SearchTrack(DWORD lba) const
int SCSICD::SearchTrack(uint32_t lba) const
{
// Track loop
for (size_t i = 0; i < tracks.size(); i++) {

View File

@ -7,30 +7,24 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI CD-ROM ]
//
//---------------------------------------------------------------------------
#pragma once
#include "disk.h"
#include "filepath.h"
#include "cd_track.h"
#include "file_support.h"
#include "disk.h"
#include "interfaces/scsi_mmc_commands.h"
#include "interfaces/scsi_primary_commands.h"
class SCSICD : public Disk, public ScsiMmcCommands, public FileSupport
class SCSICD : public Disk, private ScsiMmcCommands
{
public:
explicit SCSICD(const unordered_set<uint32_t>&);
SCSICD(int, const unordered_set<uint32_t>&);
~SCSICD() override = default;
SCSICD(SCSICD&) = delete;
SCSICD& operator=(const SCSICD&) = delete;
bool Dispatch(scsi_command) override;
@ -70,7 +64,7 @@ private:
// Track management
void ClearTrack(); // Clear the track
int SearchTrack(DWORD lba) const; // Track search
int SearchTrack(uint32_t lba) const; // Track search
vector<unique_ptr<CDTrack>> tracks; // Track opbject references
int dataindex = -1; // Current data track
int audioindex = -1; // Current audio track

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
@ -22,11 +22,9 @@
using namespace scsi_command_util;
SCSIHD::SCSIHD(const unordered_set<uint32_t>& sector_sizes, bool removable, scsi_defs::scsi_level level)
: Disk(removable ? "SCRM" : "SCHD")
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)
{
scsi_level = level;
SetSectorSizes(sector_sizes);
}
@ -41,8 +39,14 @@ void SCSIHD::FinalizeSetup(const Filepath &path, off_t size, off_t image_offset)
if (!IsRemovable()) {
uint64_t capacity = GetBlockCount() * GetSectorSizeInBytes();
string unit;
if (capacity >= 1048576) {
capacity /= 1048576;
// 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 {
@ -58,8 +62,8 @@ void SCSIHD::FinalizeSetup(const Filepath &path, off_t size, off_t image_offset)
SetProtectable(true);
SetProtected(false);
Disk::Open(path);
FileSupport::SetPath(path);
super::Open(path);
SetPath(path);
SetUpCache(path, image_offset);
}
@ -75,17 +79,17 @@ void SCSIHD::Open(const Filepath& path)
}
// Get file size
off_t file_size = fio.GetFileSize();
off_t size = fio.GetFileSize();
fio.Close();
// Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount((DWORD)(file_size >> GetSectorSizeShiftCount()));
SetBlockCount((uint32_t)(size >> GetSectorSizeShiftCount()));
// Effective size must be a multiple of the sector size
file_size = (file_size / GetSectorSizeInBytes()) * GetSectorSizeInBytes();
size = (size / GetSectorSizeInBytes()) * GetSectorSizeInBytes();
FinalizeSetup(path, file_size);
FinalizeSetup(path, size);
}
vector<byte> SCSIHD::InquiryInternal() const

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI hard disk ]
@ -17,19 +17,16 @@
#pragma once
#include "disk.h"
#include "file_support.h"
#include "filepath.h"
class SCSIHD : public Disk, public FileSupport
class SCSIHD : public Disk
{
static constexpr const char *DEFAULT_PRODUCT = "SCSI HD";
public:
SCSIHD(const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
SCSIHD(int, const unordered_set<uint32_t>&, bool, scsi_defs::scsi_level = scsi_level::SCSI_2);
~SCSIHD() override = default;
SCSIHD(SCSIHD&) = delete;
SCSIHD& operator=(const SCSIHD&) = delete;
void FinalizeSetup(const Filepath&, off_t, off_t = 0);
@ -44,5 +41,7 @@ public:
private:
using super = Disk;
scsi_defs::scsi_level scsi_level;
};

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI NEC "Genuine" Hard Disk]
@ -54,18 +54,18 @@ void SCSIHD_NEC::Open(const Filepath& path)
}
// Get file size
off_t file_size = fio.GetFileSize();
off_t size = fio.GetFileSize();
// NEC root sector
array<BYTE, 512> root_sector;
if (file_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");
}
fio.Close();
// Effective size must be a multiple of 512
file_size = (file_size / 512) * 512;
size = (size / 512) * 512;
int image_size = 0;
int sector_size = 0;
@ -76,11 +76,11 @@ void SCSIHD_NEC::Open(const Filepath& path)
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)file_size;
image_size = (int)size;
sector_size = 512;
sectors = 25;
heads = 8;
cylinders = (int)(file_size >> 9);
cylinders = (int)(size >> 9);
cylinders >>= 3;
cylinders /= 25;
}
@ -113,23 +113,23 @@ void SCSIHD_NEC::Open(const Filepath& path)
}
// Image size consistency check
if (image_offset + image_size > file_size || image_size % sector_size != 0) {
if (image_offset + image_size > size || image_size % sector_size != 0) {
throw io_exception("Image size consistency check failed");
}
// Calculate sector size
for (file_size = 16; file_size > 0; --file_size) {
if ((1 << file_size) == sector_size)
for (size = 16; size > 0; --size) {
if ((1 << size) == sector_size)
break;
}
if (file_size <= 0 || file_size > 16) {
if (size <= 0 || size > 16) {
throw io_exception("Invalid NEC disk size");
}
SetSectorSizeShiftCount((uint32_t)file_size);
SetSectorSizeShiftCount((uint32_t)size);
SetBlockCount(image_size >> GetSectorSizeShiftCount());
FinalizeSetup(path, file_size, image_offset);
FinalizeSetup(path, size, image_offset);
}
vector<byte> SCSIHD_NEC::InquiryInternal() const

View File

@ -7,7 +7,7 @@
// Copyright (C) 2014-2020 GIMONS
// Copyright (C) akuker
//
// Licensed under the BSD 3-Clause License.
// Licensed under the BSD 3-Clause License.
// See LICENSE file in the project root folder.
//
// [ SCSI NEC "Genuine" Hard Disk]
@ -20,7 +20,7 @@
#include <unordered_set>
#include <map>
using namespace std; //NOSONAR Not relevant for rascsi
using namespace std;
//===========================================================================
//
@ -31,10 +31,8 @@ class SCSIHD_NEC : public SCSIHD
{
public:
SCSIHD_NEC() : SCSIHD(sector_sizes, false) {}
explicit SCSIHD_NEC(int lun) : SCSIHD(lun, sector_sizes, false) {}
~SCSIHD_NEC() override = default;
SCSIHD_NEC(SCSIHD_NEC&) = delete;
SCSIHD_NEC& operator=(const SCSIHD_NEC&) = delete;
void Open(const Filepath&) override;

View File

@ -19,7 +19,7 @@
using namespace scsi_command_util;
SCSIMO::SCSIMO(const unordered_set<uint32_t>& sector_sizes) : Disk("SCMO")
SCSIMO::SCSIMO(int lun, const unordered_set<uint32_t>& sector_sizes) : Disk("SCMO", lun)
{
SetSectorSizes(sector_sizes);
@ -45,24 +45,24 @@ void SCSIMO::Open(const Filepath& path)
}
// Get file size
off_t file_size = fio.GetFileSize();
off_t size = fio.GetFileSize();
fio.Close();
// For some capacities there are hard-coded, well-defined sector sizes and block counts
if (!SetGeometryForCapacity(file_size)) {
if (!SetGeometryForCapacity(size)) {
// Sector size (default 512 bytes) and number of blocks
SetSectorSizeInBytes(GetConfiguredSectorSize() ? GetConfiguredSectorSize() : 512);
SetBlockCount(file_size >> GetSectorSizeShiftCount());
SetBlockCount(size >> GetSectorSizeShiftCount());
}
SetReadOnly(false);
SetProtectable(true);
SetProtected(false);
Disk::Open(path);
FileSupport::SetPath(path);
super::Open(path);
SetPath(path);
SetUpCache(path);
SetUpCache(path, 0);
// Attention if ready
if (IsReady()) {

View File

@ -15,19 +15,16 @@
#pragma once
#include "disk.h"
#include "file_support.h"
#include "filepath.h"
using Geometry = pair<uint32_t, uint32_t>;
class SCSIMO : public Disk, public FileSupport
class SCSIMO : public Disk
{
public:
explicit SCSIMO(const unordered_set<uint32_t>&);
SCSIMO(int, const unordered_set<uint32_t>&);
~SCSIMO() override = default;
SCSIMO(SCSIMO&) = delete;
SCSIMO& operator=(const SCSIMO&) = delete;
void Open(const Filepath&) override;
@ -42,6 +39,8 @@ protected:
private:
using super = Disk;
void AddOptionPage(map<int, vector<byte>>&, bool) const;
bool SetGeometryForCapacity(uint64_t);

View File

@ -11,6 +11,7 @@
#include "fileio.h"
#include <fcntl.h>
#include <unistd.h>
#include <cassert>
Fileio::~Fileio()
{
@ -30,7 +31,7 @@ bool Fileio::Open(const char *fname, OpenMode mode, bool directIO)
}
// Default mode
mode_t omode = directIO ? O_DIRECT : 0;
const mode_t omode = directIO ? O_DIRECT : 0;
switch (mode) {
case OpenMode::ReadOnly:
@ -111,10 +112,10 @@ off_t Fileio::GetFileSize() const
assert(handle >= 0);
// Get file position in 64bit
off_t cur = lseek(handle, 0, SEEK_CUR);
const off_t cur = lseek(handle, 0, SEEK_CUR);
// Get file size in64bitで
off_t end = lseek(handle, 0, SEEK_END);
const off_t end = lseek(handle, 0, SEEK_END);
// Return to start position
Seek(cur);

View File

@ -11,7 +11,7 @@
#pragma once
#include "filepath.h"
#include <sys/types.h>
#include <cstdlib>
class Fileio
{
@ -25,8 +25,8 @@ public:
Fileio() = default;
virtual ~Fileio();
Fileio(Fileio&) = delete;
Fileio& operator=(const Fileio&) = delete;
Fileio(Fileio&) = default;
Fileio& operator=(const Fileio&) = default;
bool Open(const char *fname, OpenMode mode);
bool Open(const Filepath& path, OpenMode mode);

View File

@ -8,16 +8,11 @@
//
//---------------------------------------------------------------------------
#include "os.h"
#include "filepath.h"
#include "config.h"
#include "fileio.h"
#include <libgen.h>
Filepath::Filepath()
{
Clear();
}
#include <cstdlib>
#include <cstring>
#include <cassert>
Filepath& Filepath::operator=(const Filepath& path)
{
@ -27,15 +22,6 @@ Filepath& Filepath::operator=(const Filepath& path)
return *this;
}
void Filepath::Clear()
{
// Clear the path and each part
m_szPath[0] = '\0';
m_szDir[0] = '\0';
m_szFile[0] = '\0';
m_szExt[0] = '\0';
}
//---------------------------------------------------------------------------
//
// File settings (user) for MBCS
@ -69,7 +55,7 @@ void Filepath::Split()
char *pDir = strdup(m_szPath);
const char *pDirName = dirname(pDir);
char *pBase = strdup(m_szPath);
char *pBaseName = basename(pBase);
const char *pBaseName = basename(pBase);
const char *pExtName = strrchr(pBaseName, '.');
// Transmit

View File

@ -14,8 +14,6 @@
using TCHAR = char;
class Fileio;
static const int _MAX_EXT = 256;
static const int _MAX_DIR = 256;
static const int _MAX_PATH = 260;
@ -30,15 +28,13 @@ static const int FILEPATH_MAX = _MAX_PATH;
//===========================================================================
class Filepath
{
public:
Filepath();
virtual ~Filepath() = default;
Filepath(Filepath&) = delete;
Filepath() = default;
~Filepath() = default;
Filepath(Filepath&) = default;
Filepath& operator=(const Filepath&);
void Clear();
void SetPath(const char *); // File settings (user) for MBCS
const char *GetPath() const { return m_szPath; } // Get path name
const char *GetFileExt() const; // Get short name (LPCTSTR)
@ -46,10 +42,10 @@ public:
private:
void Split(); // Split the path
TCHAR m_szPath[_MAX_PATH]; // File path
TCHAR m_szDir[_MAX_DIR]; // Directory
TCHAR m_szFile[_MAX_FNAME]; // File
TCHAR m_szExt[_MAX_EXT]; // Extension
TCHAR m_szPath[_MAX_PATH] = {}; // File path
TCHAR m_szDir[_MAX_DIR] = {}; // Directory
TCHAR m_szFile[_MAX_FNAME] = {}; // File
TCHAR m_szExt[_MAX_EXT] = {}; // Extension
static TCHAR FileExt[_MAX_FNAME + _MAX_DIR]; // Short name (TCHAR)
};

View File

@ -20,7 +20,7 @@
#include "config.h"
#include "log.h"
#include <array>
#ifdef __linux
#ifdef __linux__
#include <sys/epoll.h>
#endif

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