mirror of
https://github.com/akuker/RASCSI.git
synced 2025-02-20 16:29:21 +00:00
Merge tag 'v22.10.01'
RaSCSI version 22.10.01
This commit is contained in:
commit
39d36cf78a
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@ -0,0 +1,32 @@
|
||||
# Exclude all by default
|
||||
/*
|
||||
|
||||
# Paths to include
|
||||
!/docker/rascsi/rascsi_wrapper.sh
|
||||
!/docker/rascsi/cfilesystem.patch
|
||||
!/docker/rascsi-web/start.sh
|
||||
!/doc
|
||||
!/python
|
||||
!/src
|
||||
!/test
|
||||
!/easyinstall.sh
|
||||
!/LICENCE
|
||||
!/lido-driver.img
|
||||
!/README.md
|
||||
|
||||
# From .gitignore
|
||||
venv
|
||||
*.pyc
|
||||
core
|
||||
**/.idea
|
||||
.DS_Store
|
||||
*.swp
|
||||
__pycache__
|
||||
current
|
||||
rascsi_interface_pb2.py
|
||||
src/raspberrypi/hfdisk/
|
||||
*~
|
||||
messages.pot
|
||||
messages.mo
|
||||
s.sh
|
||||
*-backups
|
54
.github/workflows/build_code.yml
vendored
Normal file
54
.github/workflows/build_code.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: Build C++ Packages
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Add armhf as architecture
|
||||
run: sudo dpkg --add-architecture armhf
|
||||
|
||||
- name: Reconfigure apt for arch amd64
|
||||
run: sudo sed -i "s/deb /deb [arch=amd64] /g" /etc/apt/sources.list
|
||||
|
||||
- name: Add armhf repos (jammy)
|
||||
run: sudo bash -c "echo \"deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ jammy main multiverse restricted universe\" >> /etc/apt/sources.list"
|
||||
|
||||
- name: Add armhf repos (jammy-updates)
|
||||
run: sudo bash -c "echo \"deb [arch=armhf] http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main multiverse restricted universe\" >> /etc/apt/sources.list"
|
||||
|
||||
- name: Update apt
|
||||
run: sudo apt update
|
||||
|
||||
- name: Install cross compile toolchain
|
||||
run: sudo apt-get --yes install gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf binutils-arm-linux-gnueabihf libspdlog-dev
|
||||
|
||||
- name: Install libraries
|
||||
run: sudo apt-get --yes install libspdlog-dev:armhf libpcap-dev:armhf libevdev2:armhf libev-dev:armhf protobuf-compiler libprotobuf-dev:armhf
|
||||
|
||||
- name: make standard
|
||||
run: make all -j6 CONNECT_TYPE=STANDARD CROSS_COMPILE=arm-linux-gnueabihf-
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
- name: make fullspec
|
||||
run: make all -j6 CONNECT_TYPE=FULLSPEC CROSS_COMPILE=arm-linux-gnueabihf-
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
# We need to tar the binary outputs to retain the executable
|
||||
# file permission. Currently, actions/upload-artifact only
|
||||
# supports .ZIP files.
|
||||
# This is workaround for https://github.com/actions/upload-artifact/issues/38
|
||||
- name: tar binary outputs
|
||||
run: tar -czvf rascsi.tar.gz ./bin
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: arm-binaries
|
||||
path: ./src/raspberrypi/rascsi.tar.gz
|
||||
|
56
.github/workflows/c-cpp.yml
vendored
56
.github/workflows/c-cpp.yml
vendored
@ -1,56 +0,0 @@
|
||||
name: C/C++ CI
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Install cross compile toolchain
|
||||
run: sudo apt-get install gcc-8-arm-linux-gnueabihf g++-8-arm-linux-gnueabihf binutils-arm-linux-gnueabihf libspdlog-dev
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: dump arm gcc version
|
||||
run: arm-linux-gnueabihf-gcc -v
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
- name: dump native gcc version
|
||||
run: gcc -v
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
|
||||
- name: make standard
|
||||
run: make all DEBUG=1 CONNECT_TYPE=STANDARD
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
- name: make fullspec
|
||||
run: make all DEBUG=1 CONNECT_TYPE=FULLSPEC
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
# We need to tar the binary outputs to retain the executable
|
||||
# file permission. Currently, actions/upload-artifact only
|
||||
# supports .ZIP files.
|
||||
# This is workaround for https://github.com/actions/upload-artifact/issues/38
|
||||
- name: tar binary outputs
|
||||
run: tar -czvf rascsi.tar.gz ./bin
|
||||
working-directory: ./src/raspberrypi
|
||||
|
||||
- name: upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: arm-binaries
|
||||
path: ./src/raspberrypi/rascsi.tar.gz
|
||||
|
||||
# buildroot-image:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: git-fetch buildroot
|
||||
# run: git clone git://git.busybox.net/buildroot
|
||||
# - name: make defconfig
|
||||
# run: make raspberrypi4_defconfig
|
||||
# working-directory: ./buildroot
|
||||
# - name: make
|
||||
# run: make all
|
||||
# working-directory: ./buildroot
|
60
.github/workflows/run_tests.yml
vendored
Normal file
60
.github/workflows/run_tests.yml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: Run automated unit tests
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
Tests_and_SonarCloud_Analysis:
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
MAKEFLAGS: -j2 # Number of available processors
|
||||
SOURCES: src/raspberrypi
|
||||
SONAR_SCANNER_VERSION: 4.7.0.2747
|
||||
SONAR_SERVER_URL: "https://sonarcloud.io"
|
||||
BUILD_WRAPPER_OUT_DIR: build_wrapper_output_directory # Directory where build-wrapper output will be placed
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get install libspdlog-dev libpcap-dev libevdev2 libev-dev protobuf-compiler libgtest-dev libgmock-dev
|
||||
|
||||
- name: Run unit tests and save log
|
||||
run: $SOURCES/bin/fullspec/rascsi_test | tee test_log.txt
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: test log
|
||||
path: test_log.txt
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
- name: Download and set up sonar-scanner
|
||||
env:
|
||||
SONAR_SCANNER_DOWNLOAD_URL: https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${{ env.SONAR_SCANNER_VERSION }}-linux.zip
|
||||
run: |
|
||||
mkdir -p $HOME/.sonar
|
||||
curl -sSLo $HOME/.sonar/sonar-scanner.zip ${{ env.SONAR_SCANNER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/sonar-scanner.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/sonar-scanner-${{ env.SONAR_SCANNER_VERSION }}-linux/bin" >> $GITHUB_PATH
|
||||
- name: Download and set up build-wrapper
|
||||
env:
|
||||
BUILD_WRAPPER_DOWNLOAD_URL: ${{ env.SONAR_SERVER_URL }}/static/cpp/build-wrapper-linux-x86.zip
|
||||
run: |
|
||||
curl -sSLo $HOME/.sonar/build-wrapper-linux-x86.zip ${{ env.BUILD_WRAPPER_DOWNLOAD_URL }}
|
||||
unzip -o $HOME/.sonar/build-wrapper-linux-x86.zip -d $HOME/.sonar/
|
||||
echo "$HOME/.sonar/build-wrapper-linux-x86" >> $GITHUB_PATH
|
||||
- name: Run build-wrapper
|
||||
run: |
|
||||
build-wrapper-linux-x86-64 --out-dir ${{ env.BUILD_WRAPPER_OUT_DIR }} make -C $SOURCES coverage
|
||||
|
||||
- name: Run gcov
|
||||
run: (cd $SOURCES ; gcov --preserve-paths $(find -name '*.gcno'))
|
||||
- name: Run sonar-scanner
|
||||
env:
|
||||
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="**/test/**" --define sonar.cpd.exclusions="**test/**" --define sonar.python.version=3
|
||||
|
8
.gitignore
vendored
8
.gitignore
vendored
@ -11,10 +11,16 @@ src/raspberrypi/hfdisk/
|
||||
*~
|
||||
messages.pot
|
||||
messages.mo
|
||||
report.xml
|
||||
|
||||
docker/docker-compose.override.yml
|
||||
/docker/volumes/images/*
|
||||
!/docker/volumes/images/.gitkeep
|
||||
/docker/volumes/config/*
|
||||
!/docker/volumes/config/.gitkeep
|
||||
|
||||
# temporary user files
|
||||
s.sh
|
||||
|
||||
# temporary kicad files
|
||||
*-backups
|
||||
|
||||
|
@ -17,15 +17,18 @@ When you are ready to contribute code to RaSCSI Reloaded, follow the <a href="ht
|
||||
|
||||
If you want to add a new translation, or improve upon an existing one, please follow the <a href="https://github.com/akuker/RASCSI/blob/develop/python/web/README.md#localizing-the-web-interface">instructions in the Web Interface README</a>. Once the translation is complete, please use the same workflow as above to contribute it to the project.
|
||||
|
||||
<a href="https://www.tindie.com/stores/landogriffin/?ref=offsite_badges&utm_source=sellers_akuker&utm_medium=badges&utm_campaign=badge_large"><img src="https://d2ss6ovg47m0r5.cloudfront.net/badges/tindie-larges.png" alt="I sell on Tindie" width="200" height="104"></a>
|
||||
<a href="https://www.tindie.com/stores/landogriffin/?ref=offsite_badges&utm_source=sellers_akuker&utm_medium=badges&utm_campaign=badge_large"><img src="https://d2ss6ovg47m0r5.cloudfront.net/badges/tindie-larges.png" alt="I sell on Tindie" width="200" height="104"></a>[](https://sonarcloud.io/summary/new_code?id=akuker_RASCSI)
|
||||
|
||||
# GitHub Sponsors
|
||||
Thank you to all of the GitHub sponsors who support the development community!
|
||||
|
||||
Special thank you to the Gold level sponsors!
|
||||
- <a href="https://github.com/mikelord68">@mikelord68</a>
|
||||
- <a href="https://github.com/SamplerSpa-de">@samplerspa-de</a>
|
||||
|
||||
Special thank you to the Silver level sponsors!
|
||||
- <a href="https://github.com/stinkerton18">@stinkerton18</a>
|
||||
- <a href="https://github.com/hsiboy">@hsiboy</a>
|
||||
- <a href="https://github.com/pendleton115">@pendleton115</a>
|
||||
- <a href="https://github.com/Teufelhunden-0311">@Teufelhunden-0311</a>
|
||||
- Private sponsor ;]
|
||||
|
40
doc/rascsi.1
40
doc/rascsi.1
@ -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,22 +18,22 @@ 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]) or SASI (-HDn[:u]) devices can be specified.
|
||||
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. Note that SASI is considered rare and only used on very early Sharp X68000 computers.
|
||||
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.
|
||||
.PP
|
||||
RaSCSI will determine the type of device based upon the file extension of the FILE argument.
|
||||
hdf: SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000)
|
||||
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
|
||||
@ -47,7 +47,7 @@ To quit RaSCSI, press Control + C. If it is running in the background, you can k
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-b\fI " " \fIBLOCK_SIZE
|
||||
The optional block size. For SCSI drives 512, 1024, 2048 or 4096 bytes, default size is 512 bytes. For SASI drives 256 or 1024 bytes, default is 256 bytes.
|
||||
The optional block size, either 512, 1024, 2048 or 4096 bytes. Default size is 512 bytes.
|
||||
.TP
|
||||
.BR \-F\fI " " \fIFOLDER
|
||||
The default folder for image files. For files in this folder no absolute path needs to be specified. The initial default folder is '~/images'.
|
||||
@ -85,13 +85,9 @@ Overrides the default locale for client-faces error messages. The client can ove
|
||||
n is the SCSI ID number (0-7). u (0-31) is the optional LUN (logical unit). The default LUN is 0.
|
||||
.IP
|
||||
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. "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".
|
||||
.TP
|
||||
.BR \-HD\fIn[:u] " " \fIFILE
|
||||
n is the SASI ID number (0-15). The effective SASI ID is calculated as n/2, the effective SASI LUN is calculated is the remainder of n/2. Alternatively the n:u syntax can be used, where ns is the SASI ID (0-7) and u the LUN (0-1).
|
||||
.IP
|
||||
FILE is the name of the image file to use for the SASI device.
|
||||
FILE is the name of the image file to use for the SCSI device.
|
||||
.IP
|
||||
Note: SASI usage is rare, and is typically limited to early Unix workstations and Sharp X68000 systems.
|
||||
|
||||
.SH EXAMPLES
|
||||
Launch RaSCSI with no emulated drives attached:
|
||||
@ -103,13 +99,13 @@ Launch RaSCSI with an Apple hard drive image as ID 0 and a CD-ROM as ID 2
|
||||
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, 100MB HD image, use the following command:
|
||||
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:
|
||||
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/>
|
||||
|
@ -1,157 +1,115 @@
|
||||
!! ------ 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]) or SASI
|
||||
(-HDn[: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 lim‐
|
||||
ited from 0-31. Note that SASI is considered rare and only used on very
|
||||
early Sharp X68000 computers.
|
||||
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.
|
||||
hdf: SASI Hard Disk image (XM6 SASI HD image - typically only used
|
||||
with X68000)
|
||||
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. For SCSI drives 512, 1024, 2048 or 4096
|
||||
bytes, default size is 512 bytes. For SASI drives 256 or 1024
|
||||
bytes, default is 256 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".
|
||||
|
||||
-HDn[:u] FILE
|
||||
n is the SASI ID number (0-15). The effective SASI ID is calcu‐
|
||||
lated as n/2, the effective SASI LUN is calculated is the re‐
|
||||
mainder of n/2. Alternatively the n:u syntax can be used, where
|
||||
ns is the SASI ID (0-7) and u the LUN (0-1).
|
||||
|
||||
FILE is the name of the image file to use for the SASI device.
|
||||
|
||||
Note: SASI usage is rare, and is typically limited to early Unix
|
||||
workstations and Sharp X68000 systems.
|
||||
FILE is the name of the image file to use for the SCSI device.
|
||||
|
||||
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, 100MB HD image, use the following command:
|
||||
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)
|
||||
|
@ -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.
|
||||
|
||||
@ -136,7 +136,7 @@ Command is the operation being requested. Options are:
|
||||
eject, protect and unprotect are idempotent.
|
||||
.TP
|
||||
.BR \-b\fI " " \fIBLOCK_SIZE
|
||||
The optional block size. For SCSI drives 512, 1024, 2048 or 4096 bytes, default size is 512 bytes. For SASI drives 256 or 1024 bytes, default is 256 bytes.
|
||||
The optional block size, either 512, 1024, 2048 or 4096 bytes. The default size is 512 bytes.
|
||||
.TP
|
||||
.BR \-f\fI " " \fIFILE|PARAM
|
||||
Device-specific: Either a path to a disk image file, or a parameter for a non-disk device. See the rascsi(1) man page for permitted file types.
|
||||
@ -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/>
|
||||
|
@ -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,29 +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. For SCSI drives 512, 1024, 2048 or 4096
|
||||
bytes, default size is 512 bytes. For SASI drives 256 or 1024
|
||||
bytes, default is 256 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
|
||||
@ -134,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.
|
||||
@ -157,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)
|
||||
|
@ -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.
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
.TH sasidump 1
|
||||
.SH NAME
|
||||
sasidump \- SASI disk dumping tool for RaSCSI
|
||||
.SH SYNOPSIS
|
||||
.B sasidump
|
||||
\fB\-i\fR \fIID\fR
|
||||
[\fB\-u\fR \fIUT\fR]
|
||||
[\fB\-b\fR \fIBSIZE\fR]
|
||||
\fB\-c\fR \fICOUNT\fR
|
||||
\fB\-f\fR \fIFILE\fR
|
||||
[\fB\-r\fR]
|
||||
.SH DESCRIPTION
|
||||
.B sasidump
|
||||
Samples the data on physical SASI storage media, and stores it to an image file. It can also restore from a dumped file onto physical SASI storage media.
|
||||
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
.BR \-i\fI " "\fIID
|
||||
SASI ID of the target device
|
||||
.TP
|
||||
.BR \-u\fI " "\fIUD
|
||||
Unit ID of the target device
|
||||
.TP
|
||||
.BR \-b\fI " "\fIBSIZE
|
||||
Block size (default is 512)
|
||||
.TP
|
||||
.BR \-c\fI " "\fICOUNT
|
||||
Block count
|
||||
.TP
|
||||
.BR \-f\fI " "\fIFILE
|
||||
Path to the dump file
|
||||
.TP
|
||||
.BR \-r\fI
|
||||
Restoration mode
|
||||
|
||||
.SH EXAMPLES
|
||||
|
||||
.SH SEE ALSO
|
||||
rasctl(1), rascsi(1), scsimon(1), rasdump(1)
|
||||
|
||||
Full documentation is available at: <https://www.github.com/akuker/RASCSI/wiki/>
|
@ -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/>
|
||||
|
@ -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)
|
||||
|
115
docker/README.md
Normal file
115
docker/README.md
Normal file
@ -0,0 +1,115 @@
|
||||
# Docker Environment for Development and Testing
|
||||
|
||||
⚠️ **Important:** The Docker environment is unable to connect to the RaSCSI board and is
|
||||
intended for development and testing purposes only. To setup RaSCSI on a Raspberry Pi
|
||||
refer to the [setup instructions](https://github.com/akuker/RASCSI/wiki/Setup-Instructions)
|
||||
on the wiki instead.
|
||||
|
||||
## Introduction
|
||||
|
||||
This documentation currently focuses on using Docker for developing and testing the web UI.
|
||||
|
||||
Additions, amendments and contributions for additional workflows are most welcome.
|
||||
|
||||
## Getting Started
|
||||
|
||||
The easiest way to launch a new environment is to use Docker Compose.
|
||||
|
||||
```
|
||||
cd docker
|
||||
docker compose up
|
||||
```
|
||||
|
||||
Containers will be built and started for the RaSCSI server and the web UI.
|
||||
|
||||
The web UI can be accessed at:
|
||||
|
||||
* http://localhost:8080
|
||||
* https://localhost:8443
|
||||
|
||||
To stop the containers, press *Ctrl + C*, or run `docker compose stop`
|
||||
from another terminal.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
The following environment variables are available when using Docker Compose:
|
||||
|
||||
| Environment Variable | Default |
|
||||
| -------------------- | -------- |
|
||||
| `OS_DISTRO` | debian |
|
||||
| `OS_VERSION` | buster |
|
||||
| `OS_ARCH` | amd64 |
|
||||
| `WEB_HTTP_PORT` | 8080 |
|
||||
| `WEB_HTTPS_PORT` | 8443 |
|
||||
| `WEB_LOG_LEVEL` | info |
|
||||
| `RASCSI_HOST` | rascsi |
|
||||
| `RASCSI_PORT` | 6868 |
|
||||
| `RASCSI_PASSWORD` | *[None]* |
|
||||
| `RASCSI_LOG_LEVEL` | debug |
|
||||
|
||||
**Examples:**
|
||||
|
||||
Run Debian "bullseye":
|
||||
```
|
||||
OS_VERSION=bullseye docker compose up
|
||||
```
|
||||
|
||||
Start the web UI with the log level set to debug:
|
||||
```
|
||||
WEB_LOG_LEVEL=debug docker compose up
|
||||
```
|
||||
|
||||
## Volumes
|
||||
|
||||
When using Docker Compose the following volumes will be mounted automatically:
|
||||
|
||||
| Local Path | Container Path |
|
||||
| ----------------------- | ------------------------ |
|
||||
| docker/volumes/images/ | /home/pi/images/ |
|
||||
| docker/volumes/config/ | /home/pi/.config/rascsi/ |
|
||||
|
||||
|
||||
## How To
|
||||
|
||||
### Rebuild Containers
|
||||
|
||||
You should rebuild the container images after checking out a different version of
|
||||
RaSCSI or making changes which affect the environment at build time, e.g.
|
||||
`easyinstall.sh`.
|
||||
|
||||
```
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
### Open a Shell on a Running Container
|
||||
|
||||
Run the following command, replacing `[CONTAINER]` with `rascsi` or `rascsi_web`.
|
||||
|
||||
```
|
||||
docker compose exec [CONTAINER] bash
|
||||
```
|
||||
|
||||
### Setup Live Editing for the Web UI
|
||||
|
||||
Use a `docker-compose.override.yml` to mount the local `python` directory to
|
||||
`/home/pi/RASCSI/python/` in the `rascsi_web` container.
|
||||
|
||||
Any changes to *.py files on the host computer (i.e. in your IDE) will trigger
|
||||
the web UI process to be restarted in the container.
|
||||
|
||||
**Example:**
|
||||
```
|
||||
services:
|
||||
rascsi_web:
|
||||
volumes:
|
||||
- ../python:/home/pi/RASCSI/python:delegated
|
||||
```
|
||||
|
||||
### Connect the Web UI to a Real RaSCSI
|
||||
|
||||
This can be useful for testing, but there are some caveats, e.g. the RaSCSI and the
|
||||
web UI will be accessing separate `images` directories.
|
||||
|
||||
```
|
||||
RASCSI_HOST=foo RASCSI_PASSWORD=bar docker compose up
|
||||
```
|
4
docker/docker-compose.override.yml.example
Normal file
4
docker/docker-compose.override.yml.example
Normal file
@ -0,0 +1,4 @@
|
||||
services:
|
||||
rascsi_web:
|
||||
volumes:
|
||||
- ../python:/home/pi/RASCSI/python:delegated
|
71
docker/docker-compose.yml
Normal file
71
docker/docker-compose.yml
Normal file
@ -0,0 +1,71 @@
|
||||
services:
|
||||
rascsi:
|
||||
container_name: rascsi
|
||||
image: rascsi:develop-${OS_DISTRO:-debian}-${OS_VERSION:-buster}-${OS_ARCH:-amd64}-standalone
|
||||
pull_policy: never
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/rascsi/Dockerfile
|
||||
args:
|
||||
- OS_DISTRO=${OS_DISTRO:-debian}
|
||||
- OS_VERSION=${OS_VERSION:-buster}
|
||||
- OS_ARCH=${OS_ARCH:-amd64}
|
||||
volumes:
|
||||
- ./volumes/images:/home/pi/images:delegated
|
||||
- ./volumes/config:/home/pi/.config/rascsi:delegated
|
||||
ports:
|
||||
- "127.0.0.1:${RASCSI_PORT:-6868}:6868"
|
||||
environment:
|
||||
- RASCSI_PASSWORD=${RASCSI_PASSWORD:-}
|
||||
init: true
|
||||
command: [
|
||||
"/usr/local/bin/rascsi_wrapper.sh",
|
||||
"-L",
|
||||
"${RASCSI_LOG_LEVEL:-trace}",
|
||||
"-r",
|
||||
"7",
|
||||
"-F",
|
||||
"/home/pi/images"
|
||||
]
|
||||
|
||||
rascsi_web:
|
||||
container_name: rascsi_web
|
||||
image: rascsi:develop-${OS_DISTRO:-debian}-${OS_VERSION:-buster}-${OS_ARCH:-amd64}-web
|
||||
pull_policy: never
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/rascsi-web/Dockerfile
|
||||
args:
|
||||
- OS_DISTRO=${OS_DISTRO:-debian}
|
||||
- OS_VERSION=${OS_VERSION:-buster}
|
||||
- OS_ARCH=${OS_ARCH:-amd64}
|
||||
volumes:
|
||||
- ./volumes/images:/home/pi/images:delegated
|
||||
- ./volumes/config:/home/pi/.config/rascsi:delegated
|
||||
ports:
|
||||
- "127.0.0.1:${WEB_HTTP_PORT:-8080}:80"
|
||||
- "127.0.0.1:${WEB_HTTPS_PORT:-8443}:443"
|
||||
environment:
|
||||
- RASCSI_PASSWORD=${RASCSI_PASSWORD:-}
|
||||
init: true
|
||||
command: [
|
||||
"start.sh",
|
||||
"--rascsi-host=${RASCSI_HOST:-rascsi}",
|
||||
"--rascsi-port=${RASCSI_PORT:-6868}",
|
||||
"--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
5
docker/pytest/Dockerfile
Normal 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
|
35
docker/rascsi-web/Dockerfile
Normal file
35
docker/rascsi-web/Dockerfile
Normal file
@ -0,0 +1,35 @@
|
||||
ARG OS_DISTRO=debian
|
||||
ARG OS_VERSION=buster
|
||||
ARG OS_ARCH=amd64
|
||||
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 man-db man2html
|
||||
|
||||
RUN groupadd pi
|
||||
RUN useradd --create-home --shell /bin/bash -g pi pi
|
||||
RUN echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
RUN echo "pi:rascsi" | chpasswd
|
||||
|
||||
RUN mkdir /home/pi/afpshare
|
||||
RUN touch /etc/dhcpcd.conf
|
||||
RUN mkdir -p /etc/network/interfaces.d/
|
||||
|
||||
WORKDIR /home/pi/RASCSI
|
||||
USER pi
|
||||
COPY --chown=pi:pi . .
|
||||
|
||||
# Standalone RaSCSI web UI
|
||||
RUN ./easyinstall.sh --run_choice=11 --skip-token
|
||||
|
||||
# Wired network bridge
|
||||
RUN ./easyinstall.sh --run_choice=6 --headless
|
||||
|
||||
USER root
|
||||
WORKDIR /home/pi
|
||||
RUN pip3 install watchdog
|
||||
COPY docker/rascsi-web/start.sh /usr/local/bin/start.sh
|
||||
RUN chmod +x /usr/local/bin/start.sh
|
||||
CMD ["/usr/local/bin/start.sh"]
|
19
docker/rascsi-web/start.sh
Normal file
19
docker/rascsi-web/start.sh
Normal file
@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if ! [[ -f "/home/pi/RASCSI/python/common/src/rascsi_interface_pb2.py" ]]; then
|
||||
# Build rascsi_interface_pb2.py with the protobuf compiler
|
||||
protoc \
|
||||
-I=/home/pi/RASCSI/src/raspberrypi \
|
||||
--python_out=/home/pi/RASCSI/python/common/src \
|
||||
rascsi_interface.proto
|
||||
fi
|
||||
|
||||
# Start Nginx service
|
||||
nginx
|
||||
|
||||
# Pass args to web UI start script
|
||||
if [[ $RASCSI_PASSWORD ]]; then
|
||||
/home/pi/RASCSI/python/web/start.sh "$@" --password=$RASCSI_PASSWORD
|
||||
else
|
||||
/home/pi/RASCSI/python/web/start.sh "$@"
|
||||
fi
|
30
docker/rascsi/Dockerfile
Normal file
30
docker/rascsi/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
||||
ARG OS_DISTRO=debian
|
||||
ARG OS_VERSION=buster
|
||||
ARG OS_ARCH=amd64
|
||||
FROM "${OS_ARCH}/${OS_DISTRO}:${OS_VERSION}"
|
||||
|
||||
EXPOSE 6868
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends sudo systemd rsyslog patch
|
||||
|
||||
RUN groupadd pi
|
||||
RUN useradd --create-home --shell /bin/bash -g pi pi
|
||||
RUN echo "pi ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
|
||||
|
||||
WORKDIR /home/pi/RASCSI
|
||||
USER pi
|
||||
COPY --chown=pi:pi . .
|
||||
|
||||
# Workaround for Bullseye amd64 compilation error
|
||||
# https://github.com/akuker/RASCSI/issues/821
|
||||
RUN patch -p0 < docker/rascsi/cfilesystem.patch
|
||||
|
||||
# Install RaSCSI standalone
|
||||
RUN ./easyinstall.sh --run_choice=10 --cores=`nproc` --skip-token
|
||||
|
||||
USER root
|
||||
WORKDIR /home/pi
|
||||
COPY docker/rascsi/rascsi_wrapper.sh /usr/local/bin/rascsi_wrapper.sh
|
||||
RUN chmod +x /usr/local/bin/rascsi_wrapper.sh
|
||||
CMD ["/usr/local/bin/rascsi_wrapper.sh", "-L", "trace", "-r", "7", "-F", "/home/pi/images"]
|
24
docker/rascsi/cfilesystem.patch
Normal file
24
docker/rascsi/cfilesystem.patch
Normal file
@ -0,0 +1,24 @@
|
||||
--- src/raspberrypi/devices/cfilesystem.cpp 2022-09-08 12:07:14.000000000 +0100
|
||||
+++ src/raspberrypi/devices/cfilesystem.cpp.patched 2022-09-08 12:12:55.000000000 +0100
|
||||
@@ -1075,12 +1075,15 @@
|
||||
m_dirHuman.name[i] = ' ';
|
||||
}
|
||||
|
||||
- for (i = 0; i < 10; i++) {
|
||||
- if (p < m_pszHumanExt)
|
||||
- m_dirHuman.add[i] = *p++;
|
||||
- else
|
||||
- m_dirHuman.add[i] = '\0';
|
||||
- }
|
||||
+ // This code causes a compilation error on Debian "bullseye" (amd64)
|
||||
+ // https://github.com/akuker/RASCSI/issues/821
|
||||
+ //
|
||||
+ // for (i = 0; i < 10; i++) {
|
||||
+ // if (p < m_pszHumanExt)
|
||||
+ // m_dirHuman.add[i] = *p++;
|
||||
+ // else
|
||||
+ // m_dirHuman.add[i] = '\0';
|
||||
+ // }
|
||||
|
||||
if (*p == '.')
|
||||
p++;
|
11
docker/rascsi/rascsi_wrapper.sh
Normal file
11
docker/rascsi/rascsi_wrapper.sh
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if [[ $RASCSI_PASSWORD ]]; then
|
||||
TOKEN_FILE="/home/pi/.config/rascsi/rascsi_secret"
|
||||
mkdir -p /home/pi/.config/rascsi || true
|
||||
echo $RASCSI_PASSWORD > $TOKEN_FILE
|
||||
chmod 700 $TOKEN_FILE
|
||||
/usr/local/bin/rascsi "$@" -P $TOKEN_FILE
|
||||
else
|
||||
/usr/local/bin/rascsi "$@"
|
||||
fi
|
0
docker/volumes/images/.gitkeep
Normal file
0
docker/volumes/images/.gitkeep
Normal file
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-dinky
|
@ -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 
|
||||
```
|
||||
|
||||
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 we’ll help you sort it out.
|
232
easyinstall.sh
232
easyinstall.sh
@ -63,6 +63,7 @@ LIDO_DRIVER=$BASE/lido-driver.img
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
GIT_REMOTE=${GIT_REMOTE:-origin}
|
||||
TOKEN=""
|
||||
SECRET_FILE="$HOME/.config/rascsi/rascsi_secret"
|
||||
|
||||
set -e
|
||||
|
||||
@ -82,7 +83,42 @@ function sudoCheck() {
|
||||
|
||||
# install all dependency packages for RaSCSI Service
|
||||
function installPackages() {
|
||||
sudo apt-get update && sudo apt-get install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv python3-dev python3-pip nginx libpcap-dev protobuf-compiler bridge-utils libev-dev libevdev2 unar -y </dev/null
|
||||
sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y -qq \
|
||||
build-essential \
|
||||
git \
|
||||
libspdlog-dev \
|
||||
libpcap-dev \
|
||||
libprotobuf-dev \
|
||||
genisoimage \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
python3-setuptools \
|
||||
python3-wheel \
|
||||
nginx-light \
|
||||
protobuf-compiler \
|
||||
bridge-utils \
|
||||
libev-dev \
|
||||
libevdev2 \
|
||||
unzip \
|
||||
unar \
|
||||
disktype \
|
||||
libgmock-dev \
|
||||
man2html
|
||||
}
|
||||
|
||||
# install Debian packges for RaSCSI standalone
|
||||
function installPackagesStandalone() {
|
||||
sudo apt-get update && sudo DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y -qq \
|
||||
build-essential \
|
||||
libspdlog-dev \
|
||||
libpcap-dev \
|
||||
libprotobuf-dev \
|
||||
protobuf-compiler \
|
||||
disktype \
|
||||
libgmock-dev \
|
||||
man2html
|
||||
}
|
||||
|
||||
# cache the pip packages
|
||||
@ -246,32 +282,42 @@ function backupRaScsiService() {
|
||||
|
||||
# Offers the choice of enabling token-based authentication for RaSCSI
|
||||
function configureTokenAuth() {
|
||||
echo ""
|
||||
echo "Do you want to protect your RaSCSI installation with a password? [y/N]"
|
||||
read REPLY
|
||||
if [[ -f "$HOME/.rascsi_secret" ]]; then
|
||||
sudo rm "$HOME/.rascsi_secret"
|
||||
echo "Removed (legacy) RaSCSI token file"
|
||||
fi
|
||||
|
||||
SECRET_FILE="$HOME/.config/rascsi/rascsi_secret"
|
||||
if [[ -f $SECRET_FILE ]]; then
|
||||
sudo rm "$SECRET_FILE"
|
||||
echo "Removed RaSCSI token file"
|
||||
fi
|
||||
|
||||
if [[ $SKIP_TOKEN ]]; then
|
||||
echo "Skipping RaSCSI token setup"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -z $TOKEN ]]; then
|
||||
echo ""
|
||||
echo "Do you want to protect your RaSCSI installation with a password? [y/N]"
|
||||
read REPLY
|
||||
|
||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ "$REPLY" == "y" ] || [ "$REPLY" == "Y" ]; then
|
||||
echo -n "Enter the password that you want to use: "
|
||||
read -r TOKEN
|
||||
if [ -f "$HOME/.rascsi_secret" ]; then
|
||||
sudo rm "$HOME/.rascsi_secret"
|
||||
echo "Removed old RaSCSI token file"
|
||||
fi
|
||||
if [ -f "$SECRET_FILE" ]; then
|
||||
sudo rm "$SECRET_FILE"
|
||||
echo "Removed old RaSCSI token file"
|
||||
fi
|
||||
echo "$TOKEN" > "$SECRET_FILE"
|
||||
fi
|
||||
|
||||
echo "$TOKEN" > "$SECRET_FILE"
|
||||
|
||||
# Make the secret file owned and only readable by root
|
||||
sudo chown root:root "$SECRET_FILE"
|
||||
sudo chmod 600 "$SECRET_FILE"
|
||||
echo ""
|
||||
echo "Configured RaSCSI to use $SECRET_FILE for authentication. This file is readable by root only."
|
||||
echo "Make note of your password: you will need it to use rasctl and other RaSCSI clients."
|
||||
fi
|
||||
sudo chown root:root "$SECRET_FILE"
|
||||
sudo chmod 600 "$SECRET_FILE"
|
||||
echo ""
|
||||
echo "Configured RaSCSI to use $SECRET_FILE for authentication. This file is readable by root only."
|
||||
echo "Make note of your password: you will need it to use rasctl and other RaSCSI clients."
|
||||
}
|
||||
|
||||
# Modifies and installs the rascsi service
|
||||
@ -523,7 +569,7 @@ function showMacproxyStatus() {
|
||||
}
|
||||
|
||||
# Creates a drive image file with specific parameters
|
||||
function createDrive600MB() {
|
||||
function createDrive600M() {
|
||||
createDrive 600 "HD600"
|
||||
}
|
||||
|
||||
@ -531,7 +577,7 @@ function createDrive600MB() {
|
||||
function createDriveCustom() {
|
||||
driveSize=-1
|
||||
until [ $driveSize -ge "10" ] && [ $driveSize -le "4000" ]; do
|
||||
echo "What drive size would you like (in MB) (10-4000)"
|
||||
echo "What drive size would you like (in MiB) (10-4000)"
|
||||
read driveSize
|
||||
|
||||
echo "How would you like to name that drive?"
|
||||
@ -622,10 +668,10 @@ function createDrive() {
|
||||
driveSize=$1
|
||||
driveName=$2
|
||||
mkdir -p "$VIRTUAL_DRIVER_PATH"
|
||||
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}MB.hda"
|
||||
drivePath="${VIRTUAL_DRIVER_PATH}/${driveSize}M.hda"
|
||||
|
||||
if [ ! -f "$drivePath" ]; then
|
||||
echo "Creating a ${driveSize}MB Drive"
|
||||
echo "Creating a ${driveSize}MiB Drive"
|
||||
truncate --size "${driveSize}m" "$drivePath"
|
||||
|
||||
echo "Formatting drive with HFS"
|
||||
@ -647,23 +693,31 @@ function setupWiredNetworking() {
|
||||
echo "WARNING: If you continue, the IP address of your Pi may change upon reboot."
|
||||
echo "Please make sure you will not lose access to the Pi system."
|
||||
echo ""
|
||||
echo "Do you want to proceed with network configuration using the default settings? [Y/n]"
|
||||
read REPLY
|
||||
|
||||
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
|
||||
echo "Available wired interfaces on this system:"
|
||||
echo `ip -o addr show scope link | awk '{split($0, a); print $2}' | grep eth`
|
||||
echo "Please type the wired interface you want to use and press Enter:"
|
||||
read SELECTED
|
||||
LAN_INTERFACE=$SELECTED
|
||||
if [[ -z $HEADLESS ]]; then
|
||||
echo "Do you want to proceed with network configuration using the default settings? [Y/n]"
|
||||
read REPLY
|
||||
|
||||
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
|
||||
echo "Available wired interfaces on this system:"
|
||||
echo `ip -o addr show scope link | awk '{split($0, a); print $2}' | grep eth`
|
||||
echo "Please type the wired interface you want to use and press Enter:"
|
||||
read SELECTED
|
||||
LAN_INTERFACE=$SELECTED
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$(grep -c "^denyinterfaces" /etc/dhcpcd.conf)" -ge 1 ]; then
|
||||
echo "WARNING: Network forwarding may already have been configured. Proceeding will overwrite the configuration."
|
||||
echo "Press enter to continue or CTRL-C to exit"
|
||||
read REPLY
|
||||
|
||||
if [[ -z $HEADLESS ]]; then
|
||||
echo "Press enter to continue or CTRL-C to exit"
|
||||
read REPLY
|
||||
fi
|
||||
|
||||
sudo sed -i /^denyinterfaces/d /etc/dhcpcd.conf
|
||||
fi
|
||||
|
||||
sudo bash -c 'echo "denyinterfaces '$LAN_INTERFACE'" >> /etc/dhcpcd.conf'
|
||||
echo "Modified /etc/dhcpcd.conf"
|
||||
|
||||
@ -676,6 +730,12 @@ function setupWiredNetworking() {
|
||||
echo "Either use the Web UI, or do this on the command line (assuming SCSI ID 6):"
|
||||
echo "rasctl -i 6 -c attach -t scdp -f $LAN_INTERFACE"
|
||||
echo ""
|
||||
|
||||
if [[ $HEADLESS ]]; then
|
||||
echo "Skipping reboot in headless mode"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "We need to reboot your Pi"
|
||||
echo "Press Enter to reboot or CTRL-C to exit"
|
||||
read
|
||||
@ -773,6 +833,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
|
||||
@ -783,6 +857,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
|
||||
@ -1146,9 +1253,9 @@ function runChoice() {
|
||||
echo "Installing / Updating RaSCSI OLED Screen - Complete!"
|
||||
;;
|
||||
4)
|
||||
echo "Creating an HFS formatted 600MB drive image with LIDO driver"
|
||||
createDrive600MB
|
||||
echo "Creating an HFS formatted 600MB drive image with LIDO driver - Complete!"
|
||||
echo "Creating an HFS formatted 600 MiB drive image with LIDO driver"
|
||||
createDrive600M
|
||||
echo "Creating an HFS formatted 600 MiB drive image with LIDO driver - Complete!"
|
||||
;;
|
||||
5)
|
||||
echo "Creating an HFS formatted drive image with LIDO driver"
|
||||
@ -1199,8 +1306,9 @@ function runChoice() {
|
||||
echo "- Install manpages to /usr/local/man"
|
||||
sudoCheck
|
||||
createImagesDir
|
||||
configureTokenAuth
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
installPackagesStandalone
|
||||
stopRaScsi
|
||||
compileRaScsi
|
||||
installRaScsi
|
||||
@ -1218,11 +1326,13 @@ function runChoice() {
|
||||
echo "- Create a self-signed certificate in /etc/ssl"
|
||||
sudoCheck
|
||||
createCfgDir
|
||||
configureTokenAuth
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
preparePythonCommon
|
||||
cachePipPackages
|
||||
installRaScsiWebInterface
|
||||
enableWebInterfaceAuth
|
||||
echo "Configuring RaSCSI Web Interface stand-alone - Complete!"
|
||||
echo "Launch the Web Interface with the 'start.sh' script. To use a custom port for the web server: 'start.sh --web-port=8081"
|
||||
;;
|
||||
@ -1239,6 +1349,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
|
||||
;;
|
||||
@ -1252,8 +1366,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
|
||||
|
||||
@ -1270,8 +1384,8 @@ function showMenu() {
|
||||
echo " 3) install or update RaSCSI OLED Screen (requires hardware)"
|
||||
echo "CREATE HFS FORMATTED (MAC) IMAGE WITH LIDO DRIVERS"
|
||||
echo "** For the Mac Plus, it's better to create an image through the Web Interface **"
|
||||
echo " 4) 600MB drive (suggested size)"
|
||||
echo " 5) custom drive size (up to 4000MB)"
|
||||
echo " 4) 600 MiB drive (suggested size)"
|
||||
echo " 5) custom drive size (up to 4000 MiB)"
|
||||
echo "NETWORK BRIDGE ASSISTANT"
|
||||
echo " 6) configure network bridge for Ethernet (DHCP)"
|
||||
echo " 7) configure network bridge for WiFi (static IP + NAT)"
|
||||
@ -1283,6 +1397,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
|
||||
@ -1291,24 +1406,41 @@ while [ "$1" != "" ]; do
|
||||
VALUE=$(echo "$1" | awk -F= '{print $2}')
|
||||
case $PARAM in
|
||||
-c | --connect_type)
|
||||
if ! [[ $VALUE =~ ^(FULLSPEC|STANDARD|AIBOM|GAMERNIUM)$ ]]; then
|
||||
echo "ERROR: The connect type parameter must have a value of: FULLSPEC, STANDARD, AIBOM or GAMERNIUM"
|
||||
exit 1
|
||||
fi
|
||||
CONNECT_TYPE=$VALUE
|
||||
;;
|
||||
-r | --run_choice)
|
||||
if ! [[ $VALUE =~ ^[1-9][0-9]?$ && $VALUE -ge 1 && $VALUE -le 12 ]]; then
|
||||
echo "ERROR: The run choice parameter must have a numeric value between 1 and 12"
|
||||
exit 1
|
||||
fi
|
||||
RUN_CHOICE=$VALUE
|
||||
;;
|
||||
-j | --cores)
|
||||
if ! [[ $VALUE =~ ^[1-9][0-9]?$ ]]; then
|
||||
echo "ERROR: The cores parameter must have a numeric value of at least 1"
|
||||
exit 1
|
||||
fi
|
||||
CORES=$VALUE
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown parameter \"$PARAM\""
|
||||
exit 1
|
||||
-t | --token)
|
||||
if [[ -z $VALUE ]]; then
|
||||
echo "ERROR: The token parameter cannot be empty"
|
||||
exit 1
|
||||
fi
|
||||
TOKEN=$VALUE
|
||||
;;
|
||||
esac
|
||||
case $VALUE in
|
||||
FULLSPEC | STANDARD | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12)
|
||||
-s | --skip-token)
|
||||
SKIP_TOKEN=1
|
||||
;;
|
||||
-h | --headless)
|
||||
HEADLESS=1
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown option \"$VALUE\""
|
||||
echo "ERROR: Unknown parameter \"$PARAM\""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
@ -1,2 +1,2 @@
|
||||
protobuf==3.19.3
|
||||
protobuf==3.19.5
|
||||
requests==2.26.0
|
||||
|
@ -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"
|
||||
|
@ -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,33 +516,28 @@ 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.
|
||||
if isinstance(config, dict):
|
||||
self.ractl.detach_all()
|
||||
for scsi_id in range(0, 8):
|
||||
RESERVATIONS[scsi_id] = ""
|
||||
ids_to_reserve = []
|
||||
for item in config["reserved_ids"]:
|
||||
ids_to_reserve.append(item["id"])
|
||||
@ -575,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):
|
||||
"""
|
||||
@ -591,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,
|
||||
@ -608,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,
|
||||
@ -642,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):
|
||||
@ -685,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
|
||||
|
@ -40,7 +40,7 @@ class RaCtlCmds:
|
||||
version = (str(result.server_info.version_info.major_version) + "." +
|
||||
str(result.server_info.version_info.minor_version) + "." +
|
||||
str(result.server_info.version_info.patch_version))
|
||||
log_levels = result.server_info.log_level_info.log_levels
|
||||
log_levels = list(result.server_info.log_level_info.log_levels)
|
||||
current_log_level = result.server_info.log_level_info.current_log_level
|
||||
reserved_ids = list(result.server_info.reserved_ids_info.ids)
|
||||
image_dir = result.server_info.image_files_info.default_image_folder
|
||||
@ -48,15 +48,12 @@ class RaCtlCmds:
|
||||
|
||||
# Creates lists of file endings recognized by RaSCSI
|
||||
mappings = result.server_info.mapping_info.mapping
|
||||
sahd = []
|
||||
schd = []
|
||||
scrm = []
|
||||
scmo = []
|
||||
sccd = []
|
||||
for dtype in mappings:
|
||||
if mappings[dtype] == proto.PbDeviceType.SAHD:
|
||||
sahd.append(dtype)
|
||||
elif mappings[dtype] == proto.PbDeviceType.SCHD:
|
||||
if mappings[dtype] == proto.PbDeviceType.SCHD:
|
||||
schd.append(dtype)
|
||||
elif mappings[dtype] == proto.PbDeviceType.SCRM:
|
||||
scrm.append(dtype)
|
||||
@ -73,7 +70,6 @@ class RaCtlCmds:
|
||||
"reserved_ids": reserved_ids,
|
||||
"image_dir": image_dir,
|
||||
"scan_depth": scan_depth,
|
||||
"sahd": sahd,
|
||||
"schd": schd,
|
||||
"scrm": scrm,
|
||||
"scmo": scmo,
|
||||
@ -117,7 +113,7 @@ class RaCtlCmds:
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
ifs = result.network_interfaces_info.name
|
||||
return {"status": result.status, "ifs": ifs}
|
||||
return {"status": result.status, "ifs": list(ifs)}
|
||||
|
||||
def get_device_types(self):
|
||||
"""
|
||||
@ -144,7 +140,7 @@ class RaCtlCmds:
|
||||
"removable": device.properties.removable,
|
||||
"supports_file": device.properties.supports_file,
|
||||
"params": params,
|
||||
"block_sizes": device.properties.block_sizes,
|
||||
"block_sizes": list(device.properties.block_sizes),
|
||||
}
|
||||
return {"status": result.status, "device_types": device_types}
|
||||
|
||||
@ -228,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)
|
||||
@ -264,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)
|
||||
|
||||
@ -398,7 +387,7 @@ class RaCtlCmds:
|
||||
|
||||
dpath = result.devices_info.devices[i].file.name
|
||||
dfile = dpath.replace(image_files_info["images_dir"] + "/", "")
|
||||
dparam = result.devices_info.devices[i].params
|
||||
dparam = dict(result.devices_info.devices[i].params)
|
||||
dven = result.devices_info.devices[i].vendor
|
||||
dprod = result.devices_info.devices[i].product
|
||||
drev = result.devices_info.devices[i].revision
|
||||
|
@ -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:
|
||||
@ -161,7 +158,36 @@ class SysCmds:
|
||||
process = run(
|
||||
["journalctl"] + line_param + scope_param,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
if process.returncode == 0:
|
||||
return process.returncode, process.stdout.decode("utf-8")
|
||||
|
||||
return process.returncode, process.stderr.decode("utf-8")
|
||||
|
||||
@staticmethod
|
||||
def get_diskinfo(file_path):
|
||||
"""
|
||||
Takes (str) file_path path to image file to inspect.
|
||||
Returns either the disktype output, or the stderr output.
|
||||
"""
|
||||
process = run(
|
||||
["disktype", file_path],
|
||||
capture_output=True,
|
||||
)
|
||||
if process.returncode == 0:
|
||||
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")
|
||||
|
@ -10,6 +10,7 @@ import pathlib
|
||||
from tempfile import TemporaryDirectory
|
||||
from re import escape, match
|
||||
from json import loads, JSONDecodeError
|
||||
from shutil import move
|
||||
from util.run import run
|
||||
|
||||
FORK_OUTPUT_TYPE_VISIBLE = "visible"
|
||||
@ -68,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")
|
||||
|
||||
@ -140,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)
|
||||
source_path.rename(target_path)
|
||||
move(str(source_path), str(target_path))
|
||||
moved.append(member)
|
||||
|
||||
return {
|
||||
@ -151,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
|
||||
|
@ -4,7 +4,7 @@
|
||||
luma-oled==3.8.1
|
||||
Pillow==9.0.1
|
||||
RPi.GPIO==0.7.0
|
||||
protobuf==3.19.3
|
||||
protobuf==3.19.5
|
||||
unidecode==1.3.2
|
||||
smbus==1.1.post2
|
||||
|
||||
|
@ -247,7 +247,7 @@ class CtrlBoardMenuUpdateEventHandler(Observer):
|
||||
|
||||
device_type = device_info["device_list"][0]["device_type"]
|
||||
image = device_info["device_list"][0]["image"]
|
||||
if device_type in ("SAHD", "SCHD", "SCBR", "SCDP", "SCLP", "SCHS"):
|
||||
if device_type in ("SCHD", "SCBR", "SCDP", "SCLP", "SCHS"):
|
||||
result = self.ractl_cmd.detach_by_id(scsi_id)
|
||||
if result["status"] is True:
|
||||
self.show_id_action_message(scsi_id, "detached")
|
||||
|
@ -12,5 +12,5 @@ pyusb==1.2.1
|
||||
rpi-ws281x==4.3.0
|
||||
RPi.GPIO==0.7.0
|
||||
sysv-ipc==1.1.0
|
||||
protobuf==3.19.1
|
||||
protobuf==3.19.5
|
||||
unidecode==1.3.2
|
||||
|
2
python/web/.flake8
Normal file
2
python/web/.flake8
Normal file
@ -0,0 +1,2 @@
|
||||
[flake8]
|
||||
max-line-length = 100
|
8
python/web/pyproject.toml
Normal file
8
python/web/pyproject.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--junitxml=report.xml"
|
||||
log_cli = true
|
||||
log_cli_level = "warn"
|
||||
|
||||
[tool.black]
|
||||
line-length = 100
|
||||
target-version = ['py37', 'py38', 'py39']
|
6
python/web/requirements-dev.txt
Normal file
6
python/web/requirements-dev.txt
Normal file
@ -0,0 +1,6 @@
|
||||
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
|
@ -4,7 +4,7 @@ Flask==2.0.1
|
||||
itsdangerous==2.0.1
|
||||
Jinja2==3.0.1
|
||||
MarkupSafe==2.0.1
|
||||
protobuf==3.17.3
|
||||
protobuf==3.20.1
|
||||
requests==2.26.0
|
||||
simplepam==0.1.5
|
||||
flask_babel==2.0.0
|
||||
|
@ -1,4 +1,5 @@
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>RaSCSI-Web is Starting</title>
|
||||
<meta http-equiv="refresh" content="2">
|
||||
|
@ -7,8 +7,8 @@
|
||||
"block_size": 512,
|
||||
"size": 52445184,
|
||||
"name": "DEC RZ22",
|
||||
"file_type": "hds",
|
||||
"description": "Page/Swap drive for satellite workstations",
|
||||
"file_type": "hd1",
|
||||
"description": "Page/Swap drive for satellite workstations (SCSI-1)",
|
||||
"url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf"
|
||||
},
|
||||
{
|
||||
@ -19,8 +19,8 @@
|
||||
"block_size": 512,
|
||||
"size": 104890368,
|
||||
"name": "DEC RZ23",
|
||||
"file_type": "hds",
|
||||
"description": "Smallest usable drive for OpenVMS/VAX",
|
||||
"file_type": "hd1",
|
||||
"description": "Smallest usable drive for OpenVMS/VAX (SCSI-1)",
|
||||
"url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf"
|
||||
},
|
||||
{
|
||||
@ -31,8 +31,8 @@
|
||||
"block_size": 512,
|
||||
"size": 209813504,
|
||||
"name": "DEC RZ24",
|
||||
"file_type": "hds",
|
||||
"description": "Smallest usable drive for OpenVMS/VAX + Motif",
|
||||
"file_type": "hd1",
|
||||
"description": "Smallest usable drive for OpenVMS/VAX + Motif (SCSI-1)",
|
||||
"url": "http://lastin.dti.supsi.ch/VET/disks/EK-RZXXD-PS.pdf"
|
||||
},
|
||||
{
|
||||
@ -426,19 +426,19 @@
|
||||
"revision": "1.0k",
|
||||
"block_size": 2048,
|
||||
"size": null,
|
||||
"name": "Apple CD 600e",
|
||||
"name": "AppleCD 600e (Matsushita CR-8005)",
|
||||
"file_type": null,
|
||||
"description": "Emulates Apple CD ROM drive for use with Macintosh computers.",
|
||||
"description": "Emulates an Apple CD-ROM drive for use with Macintosh computers.",
|
||||
"url": ""
|
||||
},
|
||||
{
|
||||
"device_type": "SCCD",
|
||||
"vendor": null,
|
||||
"product": null,
|
||||
"product": "SCSI CD-ROM 512",
|
||||
"revision": null,
|
||||
"block_size": 512,
|
||||
"size": null,
|
||||
"name": "Generic CD-ROM 512 block size",
|
||||
"name": "Generic CD-ROM block size 512",
|
||||
"file_type": null,
|
||||
"description": "For use with host systems that expect the non-standard 512 byte block size for CD-ROM drives, such as Akai samplers.",
|
||||
"url": ""
|
||||
|
@ -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,90 +15,150 @@ form {
|
||||
|
||||
table, tr, td {
|
||||
border: 1px solid black;
|
||||
border-collapse:collapse;
|
||||
border-collapse: collapse;
|
||||
margin: none;
|
||||
}
|
||||
|
||||
.error {
|
||||
h1 {
|
||||
color: white;
|
||||
font-size:20px;
|
||||
background-color:red;
|
||||
white-space: pre-line;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: white;
|
||||
font-size:20px;
|
||||
background-color:green;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
td.inactive {
|
||||
text-align:center;
|
||||
background-color:tan;
|
||||
h2 {
|
||||
color: black;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
margin: 3px;
|
||||
}
|
||||
|
||||
summary.heading {
|
||||
color: black;
|
||||
font-size: large;
|
||||
font-weight: bold;
|
||||
margin: 0px;
|
||||
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;
|
||||
}
|
||||
|
||||
div.flash div {
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
white-space: pre-line;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
div.flash div.success {
|
||||
background-color: green;
|
||||
}
|
||||
|
||||
div.flash div.warning {
|
||||
background-color: orange;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div.flash div.error {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
div.flash div.info {
|
||||
background-color: #0d6efd;
|
||||
}
|
||||
|
||||
td.inactive {
|
||||
text-align: center;
|
||||
background-color: tan;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -1,80 +1,76 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<html lang="{{ env["locale"] }}">
|
||||
<head>
|
||||
<title>{{ _("RaSCSI Reloaded Control Page") }} [{{ host }}]</title>
|
||||
<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='message'>" + 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='message'>" + 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 auth_active %}
|
||||
{% if 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=username) }} – <a href="/logout">{{ _("Log Out") }}</a></span>
|
||||
{% if env["auth_active"] %}
|
||||
{% if env["username"] %}
|
||||
<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") }} – {{ _("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="http://github.com/akuker/RASCSI" target="_blank">
|
||||
<h1>{{ _("RaSCSI Reloaded Control Page") }}</h1>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="color: white;">
|
||||
hostname: {{ host }} ip: {{ 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/{{ running_env['git'] }}" target="_blank">{{ running_env["git"][:7] }}</a></strong></tt></center>
|
||||
<center><tt>{{ _("Pi environment: ") }}{{ 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>
|
||||
|
57
python/web/src/templates/deviceinfo.html
Normal file
57
python/web/src/templates/deviceinfo.html
Normal file
@ -0,0 +1,57 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ _("Detailed Info for Attached Devices") }}</h2>
|
||||
{% for device in devices %}
|
||||
<p>
|
||||
<table border="black" cellpadding="3" summary="Detailed information for attached devices">
|
||||
<tr>
|
||||
<th scope="row">{{ _("SCSI ID") }}</th>
|
||||
<td>{{ device["id"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("LUN") }}</th>
|
||||
<td>{{ device["unit"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Type") }}</th>
|
||||
<td>{{ device["device_type"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Status") }}</th>
|
||||
<td>{{ device["status"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("File") }}</th>
|
||||
<td>{{ device["image"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Parameters") }}</th>
|
||||
<td>{{ device["params"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Vendor") }}</th>
|
||||
<td>{{ device["vendor"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Product") }}</th>
|
||||
<td>{{ device["product"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Revision") }}</th>
|
||||
<td>{{ device["revision"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Block Size") }}</th>
|
||||
<td>{{ device["block_size"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{{ _("Image Size") }}</th>
|
||||
<td>{{ device["size"] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</p>
|
||||
{% endfor %}
|
||||
<p><a href="/">{{ _("Go to Home") }}</a></p>
|
||||
|
||||
{% endblock content %}
|
8
python/web/src/templates/diskinfo.html
Normal file
8
python/web/src/templates/diskinfo.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<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>
|
||||
|
||||
{% endblock content %}
|
@ -1,43 +1,34 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<p><a href="/">{{ _("Cancel") }}</a></p>
|
||||
<h2>{{ _("Disclaimer") }}</h2>
|
||||
<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 Drives") }}</h2>
|
||||
<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 (MB)") }}</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 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>
|
||||
@ -48,43 +39,36 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>{{ _("CD-ROM Drives") }}</h2>
|
||||
<p><em>{{ _("This will create a properties file for the given CD-ROM image. No new image file will be created.") }}</em></p>
|
||||
<table cellpadding="3" border="black">
|
||||
<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" summary="List of CD-ROM or DVD drives">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>{{ _("Name") }}</b></td>
|
||||
<td><b>{{ _("Size (MB)") }}</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 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,39 +79,31 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>{{ _("Removable Drives") }}</h2>
|
||||
<table cellpadding="3" border="black">
|
||||
<h2>{{ _("Removable Disk Drives") }}</h2>
|
||||
<table cellpadding="3" border="black" summary="List of removable disk drives">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>{{ _("Name") }}</b></td>
|
||||
<td><b>{{ _("Size (MB)") }}</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 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>
|
||||
@ -135,7 +111,7 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><small>{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}</small></p>
|
||||
<p><a href="/">{{ _("Cancel") }}</a></p>
|
||||
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}</small></p>
|
||||
<p><a href="/">{{ _("Go to Home") }}</a></p>
|
||||
|
||||
{% endblock content %}
|
||||
|
@ -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,48 +89,57 @@
|
||||
<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 }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="{{ _("Eject") }}">
|
||||
</form>
|
||||
{% else %}
|
||||
{% endif %}
|
||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('{{ _("Detach Device?") }}')">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="{{ _("Detach") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
<form action="/scsi/info" method="post">
|
||||
<input name="scsi_id" type="hidden" value="{{ device.id }}">
|
||||
<input name="unit" type="hidden" value="{{ device.unit }}">
|
||||
<input type="submit" value="{{ _("Info") }}">
|
||||
</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>
|
||||
@ -141,9 +155,14 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p><form action="/scsi/detach_all" method="post" onsubmit="return confirm('{{ _("Detach all SCSI Devices?") }}')">
|
||||
<input type="submit" value="{{ _("Detach All Devices") }}">
|
||||
</form></p>
|
||||
<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/>
|
||||
|
||||
@ -152,19 +171,28 @@
|
||||
{{ _("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>
|
||||
{{ _("Recognized image file types:") }}
|
||||
{% set comma = joiner(", ") %}
|
||||
{% for extension in valid_image_suffixes %}{{ comma() }}.{{ extension}}{% endfor %}
|
||||
</li>
|
||||
<li>
|
||||
{{ _("Recognized archive file types:") }}
|
||||
{% set comma = joiner(", ") %}
|
||||
{% for extension in ARCHIVE_FILE_SUFFIXES %}{{ comma() }}.{{ extension}}{% endfor %}
|
||||
</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>
|
||||
</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>
|
||||
@ -174,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") }} ↓">
|
||||
</form>
|
||||
</ul>
|
||||
@ -191,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>
|
||||
@ -205,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>
|
||||
@ -226,17 +254,15 @@
|
||||
{% 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 type="submit" value="{{ file['size_mb'] }} {{ _("MB") }} ↓">
|
||||
<input name="file" type="hidden" value="{{ file['name'] }}">
|
||||
<input type="submit" value="{{ file['size_mb'] }} {{ _("MiB") }} ↓">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
{% if file["name"] in attached_images %}
|
||||
<center>
|
||||
{{ _("Attached!") }}
|
||||
</center>
|
||||
{{ _("In use") }}
|
||||
{% else %}
|
||||
{% if file["archive_contents"] %}
|
||||
<form action="/files/extract_image" method="post">
|
||||
@ -249,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 %}
|
||||
@ -294,12 +321,18 @@
|
||||
<input type="submit" value="{{ _("Delete") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not file["archive_contents"] %}
|
||||
<form action="/files/diskinfo" method="post">
|
||||
<input name="file_name" type="hidden" value="{{ file['name'] }}">
|
||||
<input type="submit" value="{{ _("?") }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><small>{{ _("%(disk_space)s MB disk space remaining on the Pi", disk_space=free_disk) }}</small></p>
|
||||
<p><small>{{ _("%(disk_space)s MiB disk space remaining on the Pi", disk_space=env["free_disk_space"]) }}</small></p>
|
||||
|
||||
<hr/>
|
||||
<details>
|
||||
@ -307,39 +340,41 @@
|
||||
{{ _("Attach Peripheral Device") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>{{ _("<a href=\"%(url1)s\">DaynaPORT SCSI/Link</a> and <a href=\"%(url2)s\">X68000 Host Bridge</a> are network devices.", url1="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link", url2="https://github.com/akuker/RASCSI/wiki/X68000#Host_File_System_driver") }}
|
||||
</li>
|
||||
<ul>
|
||||
<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>{{ _("Configure the network bridge by running easyinstall.sh, or follow the <a href=\"%(url)s\">manual steps in the wiki</a>.", url="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link#manual-setup") }}
|
||||
{% 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 %}
|
||||
<li>{{ _("Please configure the <tt>rascsi_bridge</tt> network bridge before attaching an emulated network adapter!") }}</li>
|
||||
{% endif %}
|
||||
<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>{{ _("The Printer and Host Services device are currently supported on compatible Atari systems, and require <a href=\"%(url)s\">driver software</a> to be installed on the host system.", url="https://www.hddriver.net/en/rascsi_tools.html") }}
|
||||
<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>{{ _("Peripheral") }}</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 PERIPHERAL_DEVICE_TYPES %}
|
||||
{% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %}
|
||||
<tr>
|
||||
<td>
|
||||
<div>{{ device_types[type]["name"] }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ type }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/scsi/attach_device" method="post">
|
||||
<input name="type" type="hidden" value="{{ type }}">
|
||||
{% for key, value in device_types[type]["params"].items() %}
|
||||
<label for="{{ key }}">{{ key }}:</label>
|
||||
{% for key, value in device_types[type]["params"] | dictsort %}
|
||||
<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 }}
|
||||
@ -347,63 +382,89 @@
|
||||
{% 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 %}
|
||||
<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 %}>
|
||||
{% if type in REMOVABLE_DEVICE_TYPES %}
|
||||
<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 env["drive_properties"]["cd_conf"] | sort(attribute='name') %}
|
||||
<option value="{{ drive.name }}">
|
||||
{{ drive.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if type == "SCRM" %}
|
||||
{% 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 env["drive_properties"]["mo_conf"] | sort(attribute='name') %}
|
||||
<option value="{{ drive.name }}">
|
||||
{{ drive.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
{% 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>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if macproxy_configured %}
|
||||
<p><small>{{ _("Macproxy is running at %(ip_addr)s (default port 5000)", ip_addr=ip_addr) }}</small></p>
|
||||
{% else %}
|
||||
<p><small>{{ _("Install <a href=\"%(url)s\">Macproxy</a> to browse the Web with any vintage browser. It's not just for Macs!", url="https://github.com/akuker/RASCSI/wiki/Vintage-Web-Proxy#macproxy") }}</small></p>
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
<details>
|
||||
<summary class="heading">
|
||||
{{ _("Upload File") }}
|
||||
{{ _("Upload File from Local Computer") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>{{ _("Uploads file to <tt>%(directory)s</tt>. The largest file size accepted is %(max_file_size)s MB.", directory=base_dir, max_file_size=max_file_size) }}</li>
|
||||
<li>{{ _("For unrecognized file types, try renaming hard drive images to '.hds', CD-ROM images to '.iso', and removable drive images to '.hdr' before uploading.") }}</li>
|
||||
<li>{{ _("Recognized file types: %(valid_file_suffix)s", valid_file_suffix=valid_file_suffix) }}</li>
|
||||
<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\" 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"></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',
|
||||
acceptedFiles: '{{ valid_file_suffix }}',
|
||||
chunking: true,
|
||||
forceChunking: true,
|
||||
url: '/files/upload',
|
||||
maxFilesize: {{ max_file_size }}, // MB
|
||||
maxFilesize: {{ max_file_size }}, // MiB
|
||||
chunkSize: 1000000, // bytes
|
||||
dictDefaultMessage: "{{ _("Drop files here to upload") }}",
|
||||
dictFallbackMessage: "{{ _("Your browser does not support drag'n'drop file uploads.") }}",
|
||||
dictFallbackText: "{{ _("Please use the fallback form below to upload your files like in the olden days.") }}",
|
||||
dictFileTooBig: "{{ _("File is too big: {{filesize}}MB. Max filesize: {{maxFilesize}}MB.") }}",
|
||||
dictFileTooBig: "{{ _("File is too big: {{filesize}}MiB. Max filesize: {{maxFilesize}}MiB.") }}",
|
||||
dictInvalidFileType: "{{ _("You can't upload files of this type.") }}",
|
||||
dictResponseError: "{{ _("Server responded with code: {{statusCode}}") }}",
|
||||
dictCancelUpload:" {{ _("Cancel upload") }}",
|
||||
@ -412,10 +473,10 @@
|
||||
dictRemoveFile: "{{ _("Remove file") }}",
|
||||
dictMaxFilesExceeded: "{{ _("You can not upload any more files.") }}",
|
||||
dictFileSizeUnits: {
|
||||
tb: "{{ _("TB") }}",
|
||||
gb: "{{ _("GB") }}",
|
||||
mb: "{{ _("MB") }}",
|
||||
kb: "{{ _("KB") }}",
|
||||
tb: "{{ _("TiB") }}",
|
||||
gb: "{{ _("GiB") }}",
|
||||
mb: "{{ _("MiB") }}",
|
||||
kb: "{{ _("KiB") }}",
|
||||
b: "{{ _("B") }}"
|
||||
}
|
||||
}
|
||||
@ -425,61 +486,23 @@
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
{{ _("Download File to Images") }}
|
||||
{{ _("Download File from the Web") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>{{ _("Given a URL, download that file to the <tt>%(directory)s</tt> directory.", directory=base_dir) }}</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_to_images" method="post">
|
||||
<label for="url">{{ _("URL:") }}</label>
|
||||
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
|
||||
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File to Images...") }}')">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<details>
|
||||
<summary class="heading">
|
||||
{{ _("Download File to AppleShare") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>{{ _("Given a URL, download that file to the <tt>%(directory)s</tt> directory and share it over AFP.", directory=AFP_DIR) }}</li>
|
||||
<li>{{ _("Manage the files you download here through AppleShare on your vintage Mac.") }}</li>
|
||||
<li>{{ _("Requires <a href=\"%(url)s\">Netatalk</a> to be installed and configured correctly for your network.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
{% if netatalk_configured %}
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/files/download_to_afp" method="post">
|
||||
<label for="url">{{ _("URL:") }}</label>
|
||||
<input name="url" placeholder="{{ _("URL") }}" required="" type="url">
|
||||
<input type="submit" value="{{ _("Download") }}" onclick="processNotify('{{ _("Downloading File to AppleShare...") }}')">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% if netatalk_configured == 1 %}
|
||||
<p><small>{{ _("The AppleShare server is running. No active connections.") }}</small></p>
|
||||
{% elif netatalk_configured == 2 %}
|
||||
<p><small>{{ _("%(value)d active AFP connection", value=(netatalk_configured - 1)) }}</small></p>
|
||||
{% elif netatalk_configured > 2 %}
|
||||
<p><small>{{ _("%(value)d active AFP connections", value=(netatalk_configured - 1)) }}</small></p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>{{ _("Install <a href=\"%(url)s\">Netatalk</a> to use the AppleShare File Server.", url="https://github.com/akuker/RASCSI/wiki/AFP-File-Sharing") }}</p>
|
||||
{% endif %}
|
||||
<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/>
|
||||
|
||||
@ -490,50 +513,44 @@
|
||||
<ul>
|
||||
<li>{{ _("Create an ISO file system CD-ROM image with the downloaded file, and mount it on the given SCSI ID.") }}</li>
|
||||
<li>{{ _("HFS is for Mac OS, Joliet for Windows, and Rock Ridge for POSIX.") }}</li>
|
||||
<li>{{ _("On Mac OS, a <a href=\"%(url)s\">compatible CD-ROM driver</a> is required.", url="https://github.com/akuker/RASCSI/wiki/Drive-Setup#Mounting_CD_ISO_or_MO_images") }}</li>
|
||||
<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/>
|
||||
|
||||
@ -542,54 +559,37 @@
|
||||
{{ _("Create Empty Disk Image File") }}
|
||||
</summary>
|
||||
<ul>
|
||||
<li>{{ _("The Generic image type is recommended for most computer platforms.") }}</li>
|
||||
<li>{{ _("APPLE GENUINE (.hda) and NEC GENUINE (.hdn) image types will make RaSCSI behave as a particular drive type that are recognized by Mac and PC98 systems, respectively.") }}</li>
|
||||
<li>{{ _("SASI images should only be used on the original Sharp X68000, or other legacy systems that utilize this pre-SCSI standard.") }}</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">
|
||||
<option value="hds">
|
||||
{{ _("SCSI Hard Disk image (Generic) [.hds]") }}
|
||||
</option>
|
||||
<option value="hda">
|
||||
{{ _("SCSI Hard Disk image (APPLE GENUINE) [.hda]") }}
|
||||
</option>
|
||||
<option value="hdn">
|
||||
{{ _("SCSI Hard Disk image (NEC GENUINE) [.hdn]") }}
|
||||
</option>
|
||||
<option value="hdr">
|
||||
{{ _("SCSI Removable Media Disk image (Generic) [.hdr]") }}
|
||||
</option>
|
||||
<option value="hdf">
|
||||
{{ _("SASI Hard Disk image (Legacy) [.hdf]") }}
|
||||
</option>
|
||||
</select>
|
||||
<label for="size">{{ _("Size:") }}</label>
|
||||
<input name="size" type="number" placeholder="{{ _("MB") }}" 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/>
|
||||
@ -599,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/>
|
||||
|
||||
@ -671,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/>
|
||||
|
||||
@ -696,23 +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>
|
||||
|
||||
<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 %}
|
||||
|
8
python/web/src/templates/logs.html
Normal file
8
python/web/src/templates/logs.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<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>
|
||||
|
||||
{% endblock content %}
|
8
python/web/src/templates/manpage.html
Normal file
8
python/web/src/templates/manpage.html
Normal 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
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -80,20 +92,18 @@ def get_device_name(device_type):
|
||||
Takes a four letter device acronym (str) device_type.
|
||||
Returns the human-readable name for the device type.
|
||||
"""
|
||||
if device_type == "SAHD":
|
||||
return _("SASI Hard Disk")
|
||||
if device_type == "SCHD":
|
||||
return _("SCSI Hard Disk")
|
||||
return _("Hard Disk Drive")
|
||||
if device_type == "SCRM":
|
||||
return _("Removable Disk")
|
||||
return _("Removable Disk Drive")
|
||||
if device_type == "SCMO":
|
||||
return _("Magneto-Optical")
|
||||
return _("Magneto-Optical Drive")
|
||||
if device_type == "SCCD":
|
||||
return _("CD / DVD")
|
||||
return _("CD/DVD Drive")
|
||||
if device_type == "SCBR":
|
||||
return _("X68000 Host Bridge")
|
||||
return _("Host Bridge")
|
||||
if device_type == "SCDP":
|
||||
return _("DaynaPORT SCSI/Link")
|
||||
return _("Ethernet Adapter")
|
||||
if device_type == "SCLP":
|
||||
return _("Printer")
|
||||
if device_type == "SCHS":
|
||||
@ -101,6 +111,115 @@ def get_device_name(device_type):
|
||||
return device_type
|
||||
|
||||
|
||||
def map_image_file_descriptions(file_suffixes):
|
||||
"""
|
||||
Takes a (list) of (str) file suffixes for images file types.
|
||||
Returns a (dict) with file suffix and description pairs, both (str)
|
||||
"""
|
||||
supported_image_types = {}
|
||||
for suffix in file_suffixes:
|
||||
supported_image_types[suffix] = get_image_description(suffix)
|
||||
|
||||
return supported_image_types
|
||||
|
||||
|
||||
# pylint: disable=too-many-return-statements
|
||||
def get_image_description(file_suffix):
|
||||
"""
|
||||
Takes a three char file suffix (str) file_suffix.
|
||||
Returns the help text description for said file suffix.
|
||||
"""
|
||||
if file_suffix == "hds":
|
||||
return _("Hard Disk Image (Generic)")
|
||||
if file_suffix == "hda":
|
||||
return _("Hard Disk Image (Apple)")
|
||||
if file_suffix == "hdn":
|
||||
return _("Hard Disk Image (NEC)")
|
||||
if file_suffix == "hd1":
|
||||
return _("Hard Disk Image (SCSI-1)")
|
||||
if file_suffix == "hdr":
|
||||
return _("Removable Disk Image")
|
||||
if file_suffix == "mos":
|
||||
return _("Magneto-Optical Disk Image")
|
||||
return file_suffix
|
||||
|
||||
|
||||
def format_drive_properties(drive_properties):
|
||||
"""
|
||||
Takes a (dict) with structured drive properties data
|
||||
Returns a (dict) with the formatted properties, one (list) per device type
|
||||
"""
|
||||
hd_conf = []
|
||||
cd_conf = []
|
||||
rm_conf = []
|
||||
mo_conf = []
|
||||
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)
|
||||
hd_conf.append(device)
|
||||
elif device["device_type"] == "SCCD":
|
||||
device["size_mb"] = _("N/A")
|
||||
cd_conf.append(device)
|
||||
elif device["device_type"] == "SCRM":
|
||||
device["secure_name"] = secure_filename(device["name"])
|
||||
device["size_mb"] = FORMAT_FILTER.format(device["size"] / 1024 / 1024)
|
||||
rm_conf.append(device)
|
||||
elif device["device_type"] == "SCMO":
|
||||
device["secure_name"] = secure_filename(device["name"])
|
||||
device["size_mb"] = FORMAT_FILTER.format(device["size"] / 1024 / 1024)
|
||||
mo_conf.append(device)
|
||||
|
||||
return {
|
||||
"hd_conf": hd_conf,
|
||||
"cd_conf": cd_conf,
|
||||
"rm_conf": rm_conf,
|
||||
"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.
|
||||
@ -119,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):
|
||||
@ -169,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)
|
||||
|
@ -62,8 +62,7 @@ if ! test -e venv; then
|
||||
pip3 install wheel
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
git rev-parse --is-inside-work-tree &> /dev/null
|
||||
if [[ $? -eq 0 ]]; then
|
||||
if git rev-parse --is-inside-work-tree &> /dev/null; then
|
||||
git rev-parse HEAD > current
|
||||
fi
|
||||
fi
|
||||
@ -110,6 +109,9 @@ while [ "$1" != "" ]; do
|
||||
-l | --log-level)
|
||||
ARG_LOG_LEVEL="--log-level $VALUE"
|
||||
;;
|
||||
-d | --dev-mode)
|
||||
ARG_DEV_MODE="--dev-mode"
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown parameter \"$PARAM\""
|
||||
exit 1
|
||||
@ -122,4 +124,10 @@ PYTHON_COMMON_PATH=$(dirname $PWD)/common/src
|
||||
echo "Starting web server for RaSCSI Web Interface..."
|
||||
export PYTHONPATH=$PWD/src:${PYTHON_COMMON_PATH}
|
||||
cd src
|
||||
python3 web.py ${ARG_PORT} ${ARG_PASSWORD} ${ARG_RASCSI_HOST} ${ARG_RASCSI_PORT} ${ARG_LOG_LEVEL}
|
||||
|
||||
if [[ $ARG_DEV_MODE ]]; then
|
||||
watchmedo auto-restart --directory=../../ --pattern=*.py --recursive -- \
|
||||
python3 web.py ${ARG_PORT} ${ARG_PASSWORD} ${ARG_RASCSI_HOST} ${ARG_RASCSI_PORT} ${ARG_LOG_LEVEL} ${ARG_DEV_MODE}
|
||||
else
|
||||
python3 web.py ${ARG_PORT} ${ARG_PASSWORD} ${ARG_RASCSI_HOST} ${ARG_RASCSI_PORT} ${ARG_LOG_LEVEL} ${ARG_DEV_MODE}
|
||||
fi
|
82
python/web/tests/api/conftest.py
Normal file
82
python/web/tests/api/conftest.py
Normal file
@ -0,0 +1,82 @@
|
||||
import pytest
|
||||
import uuid
|
||||
import warnings
|
||||
|
||||
SCSI_ID = 6
|
||||
FILE_SIZE_1_MIB = 1048576
|
||||
STATUS_SUCCESS = "success"
|
||||
STATUS_ERROR = "error"
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def create_test_image(request, http_client):
|
||||
images = []
|
||||
|
||||
def create(image_type="hds", size=1, auto_delete=True):
|
||||
file_prefix = str(uuid.uuid4())
|
||||
file_name = f"{file_prefix}.{image_type}"
|
||||
|
||||
response = http_client.post(
|
||||
"/files/create",
|
||||
data={
|
||||
"file_name": file_prefix,
|
||||
"type": image_type,
|
||||
"size": size,
|
||||
},
|
||||
)
|
||||
|
||||
if response.json()["status"] != STATUS_SUCCESS:
|
||||
raise Exception("Failed to create temporary image")
|
||||
|
||||
if auto_delete:
|
||||
images.append(file_name)
|
||||
|
||||
return file_name
|
||||
|
||||
def delete():
|
||||
for image in images:
|
||||
response = http_client.post("/files/delete", data={"file_name": image})
|
||||
if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
|
||||
warnings.warn(
|
||||
f"Failed to auto-delete file created with create_test_image fixture: {image}"
|
||||
)
|
||||
|
||||
request.addfinalizer(delete)
|
||||
return create
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def list_files(http_client):
|
||||
def files():
|
||||
return [f["name"] for f in http_client.get("/").json()["data"]["files"]]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def list_attached_images(http_client):
|
||||
def files():
|
||||
return http_client.get("/").json()["data"]["attached_images"]
|
||||
|
||||
return files
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def delete_file(http_client):
|
||||
def delete(file_name):
|
||||
response = http_client.post("/files/delete", data={"file_name": file_name})
|
||||
if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS:
|
||||
warnings.warn(f"Failed to delete file via delete_file fixture: {file_name}")
|
||||
|
||||
return delete
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def detach_devices(http_client):
|
||||
def detach():
|
||||
response = http_client.post("/scsi/detach_all")
|
||||
if response.json()["status"] == STATUS_SUCCESS:
|
||||
return True
|
||||
raise Exception("Failed to detach SCSI devices")
|
||||
|
||||
return detach
|
44
python/web/tests/api/test_auth.py
Normal file
44
python/web/tests/api/test_auth.py
Normal file
@ -0,0 +1,44 @@
|
||||
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={
|
||||
"username": pytestconfig.getoption("rascsi_username"),
|
||||
"password": pytestconfig.getoption("rascsi_password"),
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert "env" in response_data["data"]
|
||||
|
||||
|
||||
# route("/login", methods=["POST"])
|
||||
def test_login_with_invalid_credentials(http_client_unauthenticated):
|
||||
response = http_client_unauthenticated.post(
|
||||
"/login",
|
||||
data={
|
||||
"username": "__INVALID_USER__",
|
||||
"password": "__INVALID_PASS__",
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 401
|
||||
assert response_data["status"] == STATUS_ERROR
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
"You must log in with valid credentials for a user in the 'rascsi' group"
|
||||
)
|
||||
|
||||
|
||||
# route("/logout")
|
||||
def test_logout(http_client):
|
||||
response = http_client.get("/logout")
|
||||
assert response.status_code == 200
|
263
python/web/tests/api/test_devices.py
Normal file
263
python/web/tests/api/test_devices.py
Normal file
@ -0,0 +1,263 @@
|
||||
import pytest
|
||||
|
||||
from conftest import (
|
||||
SCSI_ID,
|
||||
FILE_SIZE_1_MIB,
|
||||
STATUS_SUCCESS,
|
||||
)
|
||||
|
||||
|
||||
# route("/scsi/attach", methods=["POST"])
|
||||
def test_attach_image(http_client, create_test_image, detach_devices):
|
||||
test_image = create_test_image()
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/attach",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
"file_size": FILE_SIZE_1_MIB,
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
"type": "SCHD",
|
||||
},
|
||||
)
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"Attached {test_image} as Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0"
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
|
||||
|
||||
# route("/scsi/attach_device", methods=["POST"])
|
||||
@pytest.mark.parametrize(
|
||||
"device_name,device_config",
|
||||
[
|
||||
(
|
||||
"Removable Disk Drive",
|
||||
{
|
||||
"type": "SCRM",
|
||||
"drive_props": {
|
||||
"vendor": "HD VENDOR",
|
||||
"product": "HD PRODUCT",
|
||||
"revision": "0123",
|
||||
"block_size": "512",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
"Magneto-Optical Drive",
|
||||
{
|
||||
"type": "SCMO",
|
||||
"drive_props": {
|
||||
"vendor": "MO VENDOR",
|
||||
"product": "MO PRODUCT",
|
||||
"revision": "0123",
|
||||
"block_size": "512",
|
||||
},
|
||||
},
|
||||
),
|
||||
(
|
||||
"CD/DVD Drive",
|
||||
{
|
||||
"type": "SCCD",
|
||||
"drive_props": {
|
||||
"vendor": "CD VENDOR",
|
||||
"product": "CD PRODUCT",
|
||||
"revision": "0123",
|
||||
"block_size": "512",
|
||||
},
|
||||
},
|
||||
),
|
||||
# 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", "param_timeout": 60, "param_cmd": "lp -fart %f"}),
|
||||
],
|
||||
)
|
||||
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
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/attach_device",
|
||||
data=device_config,
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"Attached {device_name} to SCSI ID {SCSI_ID} LUN 0"
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
|
||||
|
||||
# route("/scsi/detach", methods=["POST"])
|
||||
def test_detach_device(http_client, create_test_image):
|
||||
test_image = create_test_image()
|
||||
|
||||
http_client.post(
|
||||
"/scsi/attach",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
"file_size": FILE_SIZE_1_MIB,
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
"type": "SCHD",
|
||||
},
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/detach",
|
||||
data={
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Detached SCSI ID {SCSI_ID} LUN 0"
|
||||
|
||||
|
||||
# route("/scsi/detach_all", methods=["POST"])
|
||||
def test_detach_all_devices(http_client, create_test_image, list_attached_images):
|
||||
test_images = []
|
||||
scsi_ids = [4, 5, 6]
|
||||
|
||||
for scsi_id in scsi_ids:
|
||||
test_image = create_test_image()
|
||||
test_images.append(test_image)
|
||||
|
||||
http_client.post(
|
||||
"/scsi/attach",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
"file_size": FILE_SIZE_1_MIB,
|
||||
"scsi_id": scsi_id,
|
||||
"unit": 0,
|
||||
"type": "SCHD",
|
||||
},
|
||||
)
|
||||
|
||||
assert list_attached_images() == test_images
|
||||
|
||||
response = http_client.post("/scsi/detach_all")
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert list_attached_images() == []
|
||||
|
||||
|
||||
# route("/scsi/eject", methods=["POST"])
|
||||
def test_eject_device(http_client, create_test_image, detach_devices):
|
||||
test_image = create_test_image()
|
||||
|
||||
http_client.post(
|
||||
"/scsi/attach",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
"file_size": FILE_SIZE_1_MIB,
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
"type": "SCCD", # CD-ROM
|
||||
},
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/eject",
|
||||
data={
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Ejected SCSI ID {SCSI_ID} LUN 0"
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
|
||||
|
||||
# route("/scsi/info", methods=["POST"])
|
||||
def test_show_device_info(http_client, create_test_image, detach_devices):
|
||||
test_image = create_test_image()
|
||||
|
||||
http_client.post(
|
||||
"/scsi/attach",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
"file_size": FILE_SIZE_1_MIB,
|
||||
"scsi_id": SCSI_ID,
|
||||
"unit": 0,
|
||||
"type": "SCHD",
|
||||
},
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/info",
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert "devices" in response_data["data"]
|
||||
assert response_data["data"]["devices"][0]["file"] == test_image
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
|
||||
|
||||
# route("/scsi/reserve", methods=["POST"])
|
||||
# route("/scsi/release", methods=["POST"])
|
||||
def test_reserve_and_release_device(http_client):
|
||||
scsi_id = 0
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/reserve",
|
||||
data={
|
||||
"scsi_id": scsi_id,
|
||||
"memo": "TEST",
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Reserved SCSI ID {scsi_id}"
|
||||
|
||||
response = http_client.post(
|
||||
"/scsi/release",
|
||||
data={
|
||||
"scsi_id": scsi_id,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"Released the reservation for SCSI ID {scsi_id}"
|
||||
)
|
310
python/web/tests/api/test_files.py
Normal file
310
python/web/tests/api/test_files.py
Normal file
@ -0,0 +1,310 @@
|
||||
import pytest
|
||||
import uuid
|
||||
import os
|
||||
|
||||
from conftest import (
|
||||
SCSI_ID,
|
||||
FILE_SIZE_1_MIB,
|
||||
STATUS_SUCCESS,
|
||||
)
|
||||
|
||||
|
||||
# route("/files/create", methods=["POST"])
|
||||
def test_create_file(http_client, list_files, delete_file):
|
||||
file_prefix = str(uuid.uuid4())
|
||||
file_name = f"{file_prefix}.hds"
|
||||
|
||||
response = http_client.post(
|
||||
"/files/create",
|
||||
data={
|
||||
"file_name": file_prefix,
|
||||
"type": "hds",
|
||||
"size": 1,
|
||||
"drive_name": "DEC RZ22",
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 201
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["data"]["image"] == file_name
|
||||
assert response_data["messages"][0]["message"] == f"Image file created: {file_name}"
|
||||
assert file_name in list_files()
|
||||
|
||||
# Cleanup
|
||||
delete_file(file_name)
|
||||
|
||||
|
||||
# route("/files/rename", methods=["POST"])
|
||||
def test_rename_file(http_client, create_test_image, list_files, delete_file):
|
||||
original_file = create_test_image(auto_delete=False)
|
||||
renamed_file = f"{uuid.uuid4()}.rename"
|
||||
|
||||
response = http_client.post(
|
||||
"/files/rename",
|
||||
data={"file_name": original_file, "new_file_name": renamed_file},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Image file renamed to: {renamed_file}"
|
||||
assert renamed_file in list_files()
|
||||
|
||||
# Cleanup
|
||||
delete_file(renamed_file)
|
||||
|
||||
|
||||
# route("/files/copy", methods=["POST"])
|
||||
def test_copy_file(http_client, create_test_image, list_files, delete_file):
|
||||
original_file = create_test_image()
|
||||
copy_file = f"{uuid.uuid4()}.copy"
|
||||
|
||||
response = http_client.post(
|
||||
"/files/copy",
|
||||
data={
|
||||
"file_name": original_file,
|
||||
"copy_file_name": copy_file,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
files = list_files()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Copy of image file saved as: {copy_file}"
|
||||
assert original_file in files
|
||||
assert copy_file in files
|
||||
|
||||
# Cleanup
|
||||
delete_file(copy_file)
|
||||
|
||||
|
||||
# route("/files/delete", methods=["POST"])
|
||||
def test_delete_file(http_client, create_test_image, list_files):
|
||||
file_name = create_test_image(auto_delete=False)
|
||||
|
||||
response = http_client.post("/files/delete", data={"file_name": file_name})
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Image file deleted: {file_name}"
|
||||
assert file_name not in list_files()
|
||||
|
||||
|
||||
# route("/files/extract_image", methods=["POST"])
|
||||
@pytest.mark.parametrize(
|
||||
"archive_file_name,image_file_name",
|
||||
[
|
||||
("test_image.zip", "test_image_from_zip.hds"),
|
||||
("test_image.sit", "test_image_from_sit.hds"),
|
||||
("test_image.7z", "test_image_from_7z.hds"),
|
||||
],
|
||||
)
|
||||
def test_extract_file(
|
||||
httpserver, http_client, list_files, delete_file, archive_file_name, image_file_name
|
||||
):
|
||||
http_path = f"/images/{archive_file_name}"
|
||||
url = httpserver.url_for(http_path)
|
||||
|
||||
with open(f"tests/assets/{archive_file_name}", mode="rb") as file:
|
||||
zip_file_data = file.read()
|
||||
|
||||
httpserver.expect_request(http_path).respond_with_data(
|
||||
zip_file_data,
|
||||
mimetype="application/octet-stream",
|
||||
)
|
||||
|
||||
http_client.post(
|
||||
"/files/download_url",
|
||||
data={
|
||||
"destination": "images",
|
||||
"url": url,
|
||||
},
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/files/extract_image",
|
||||
data={
|
||||
"archive_file": archive_file_name,
|
||||
"archive_members": image_file_name,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == "Extracted 1 file(s)"
|
||||
assert image_file_name in list_files()
|
||||
|
||||
# Cleanup
|
||||
delete_file(archive_file_name)
|
||||
delete_file(image_file_name)
|
||||
|
||||
|
||||
# route("/files/upload", methods=["POST"])
|
||||
def test_upload_file(http_client, delete_file):
|
||||
file_name = f"{uuid.uuid4()}.test"
|
||||
|
||||
with open("tests/assets/test_image.hds", mode="rb") as file:
|
||||
file.seek(0, os.SEEK_END)
|
||||
file_size = file.tell()
|
||||
file.seek(0, 0)
|
||||
|
||||
number_of_chunks = 4
|
||||
|
||||
# Note: The test file needs to be cleanly divisible by the chunk size
|
||||
chunk_size = int(file_size / number_of_chunks)
|
||||
|
||||
for chunk_number in range(0, 4):
|
||||
if chunk_number == 0:
|
||||
chunk_byte_offset = 0
|
||||
else:
|
||||
chunk_byte_offset = chunk_number * chunk_size
|
||||
|
||||
form_data = {
|
||||
"dzuuid": str(uuid.uuid4()),
|
||||
"dzchunkindex": chunk_number,
|
||||
"dzchunksize": chunk_size,
|
||||
"dzchunkbyteoffset": chunk_byte_offset,
|
||||
"dztotalfilesize": file_size,
|
||||
"dztotalchunkcount": number_of_chunks,
|
||||
}
|
||||
|
||||
file_data = {"file": (file_name, file.read(chunk_size))}
|
||||
|
||||
response = http_client.post(
|
||||
"/files/upload",
|
||||
data=form_data,
|
||||
files=file_data,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.text == "File upload successful!"
|
||||
|
||||
file = [f for f in http_client.get("/").json()["data"]["files"] if f["name"] == file_name][0]
|
||||
|
||||
assert file["size"] == file_size
|
||||
|
||||
# Cleanup
|
||||
delete_file(file_name)
|
||||
|
||||
|
||||
# route("/files/download", methods=["POST"])
|
||||
def test_download_file(http_client, create_test_image):
|
||||
file_name = create_test_image()
|
||||
|
||||
response = http_client.post("/files/download", data={"file": file_name})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-type"] == "application/octet-stream"
|
||||
assert response.headers["content-disposition"] == f"attachment; filename={file_name}"
|
||||
assert response.headers["content-length"] == str(FILE_SIZE_1_MIB)
|
||||
|
||||
|
||||
# route("/files/download_url", methods=["POST"])
|
||||
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)
|
||||
|
||||
with open("tests/assets/test_image.hds", mode="rb") as file:
|
||||
test_file_data = file.read()
|
||||
|
||||
httpserver.expect_request(http_path).respond_with_data(
|
||||
test_file_data,
|
||||
mimetype="application/octet-stream",
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/files/download_url",
|
||||
data={
|
||||
"destination": "images",
|
||||
"url": url,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
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 {env['images_dir']}"
|
||||
)
|
||||
|
||||
# Cleanup
|
||||
delete_file(file_name)
|
||||
|
||||
|
||||
# route("/files/download_to_iso", methods=["POST"])
|
||||
def test_download_url_to_iso(
|
||||
env,
|
||||
httpserver,
|
||||
http_client,
|
||||
list_files,
|
||||
list_attached_images,
|
||||
detach_devices,
|
||||
delete_file,
|
||||
):
|
||||
test_file_name = str(uuid.uuid4())
|
||||
iso_file_name = f"{test_file_name}.iso"
|
||||
|
||||
http_path = f"/images/{test_file_name}"
|
||||
url = httpserver.url_for(http_path)
|
||||
|
||||
with open("tests/assets/test_image.hds", mode="rb") as file:
|
||||
test_file_data = file.read()
|
||||
|
||||
httpserver.expect_request(http_path).respond_with_data(
|
||||
test_file_data,
|
||||
mimetype="application/octet-stream",
|
||||
)
|
||||
|
||||
response = http_client.post(
|
||||
"/files/download_to_iso",
|
||||
data={
|
||||
"scsi_id": SCSI_ID,
|
||||
"type": "-hfs",
|
||||
"url": url,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert iso_file_name in list_files()
|
||||
assert iso_file_name in list_attached_images()
|
||||
|
||||
m = response_data["messages"]
|
||||
assert m[0]["message"] == 'Created CD-ROM ISO image with arguments "-hfs"'
|
||||
assert m[1]["message"] == f"Saved image as: {env['images_dir']}/{iso_file_name}"
|
||||
assert m[2]["message"] == f"Attached to SCSI ID {SCSI_ID}"
|
||||
|
||||
# Cleanup
|
||||
detach_devices()
|
||||
delete_file(iso_file_name)
|
||||
|
||||
|
||||
# route("/files/diskinfo", methods=["POST"])
|
||||
def test_show_diskinfo(http_client, create_test_image):
|
||||
test_image = create_test_image()
|
||||
|
||||
response = http_client.post(
|
||||
"/files/diskinfo",
|
||||
data={
|
||||
"file_name": test_image,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert "Regular file" in response_data["data"]["diskinfo"]
|
98
python/web/tests/api/test_misc.py
Normal file
98
python/web/tests/api/test_misc.py
Normal file
@ -0,0 +1,98 @@
|
||||
import uuid
|
||||
|
||||
from conftest import (
|
||||
FILE_SIZE_1_MIB,
|
||||
STATUS_SUCCESS,
|
||||
)
|
||||
|
||||
|
||||
# route("/")
|
||||
def test_index(http_client):
|
||||
response = http_client.get("/")
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert "devices" in response_data["data"]
|
||||
|
||||
|
||||
# route("/env")
|
||||
def test_get_env_info(http_client):
|
||||
response = http_client.get("/env")
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert "running_env" in response_data["data"]
|
||||
|
||||
|
||||
# route("/pwa/<path:pwa_path>")
|
||||
def test_pwa_route(http_client):
|
||||
response = http_client.get("/pwa/favicon.ico")
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response.headers["content-disposition"] == "inline; filename=favicon.ico"
|
||||
|
||||
|
||||
# route("/drive/list", methods=["GET"])
|
||||
def test_show_named_drive_presets(http_client):
|
||||
response = http_client.get("/drive/list")
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert "files" in response_data["data"]
|
||||
|
||||
|
||||
# route("/drive/cdrom", methods=["POST"])
|
||||
def test_create_cdrom_properties_file(env, http_client):
|
||||
file_name = f"{uuid.uuid4()}.iso"
|
||||
|
||||
response = http_client.post(
|
||||
"/drive/cdrom",
|
||||
data={
|
||||
"drive_name": "Sony CDU-8012",
|
||||
"file_name": file_name,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"File created: {env['cfg_dir']}/{file_name}.properties"
|
||||
)
|
||||
|
||||
|
||||
# route("/drive/create", methods=["POST"])
|
||||
def test_create_image_with_properties_file(http_client, delete_file):
|
||||
file_prefix = str(uuid.uuid4())
|
||||
file_name = f"{file_prefix}.hds"
|
||||
|
||||
response = http_client.post(
|
||||
"/drive/create",
|
||||
data={
|
||||
"drive_name": "Miniscribe M8425",
|
||||
"size": FILE_SIZE_1_MIB,
|
||||
"file_name": file_prefix,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Image file created: {file_name}"
|
||||
|
||||
# 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"]
|
152
python/web/tests/api/test_settings.py
Normal file
152
python/web/tests/api/test_settings.py
Normal file
@ -0,0 +1,152 @@
|
||||
import pytest
|
||||
import uuid
|
||||
|
||||
from conftest import STATUS_SUCCESS
|
||||
|
||||
|
||||
# route("/language", methods=["POST"])
|
||||
@pytest.mark.parametrize(
|
||||
"locale,confirm_message",
|
||||
[
|
||||
("de", "Webinterface-Sprache auf Deutsch geändert"),
|
||||
("es", "Se ha cambiado el lenguaje de la Interfaz Web a español"),
|
||||
("fr", "Langue de l’interface web changée pour français"),
|
||||
("sv", "Bytte webbgränssnittets språk till svenska"),
|
||||
("en", "Changed Web Interface language to English"),
|
||||
],
|
||||
)
|
||||
def test_set_language(http_client, locale, confirm_message):
|
||||
response = http_client.post(
|
||||
"/language",
|
||||
data={
|
||||
"locale": locale,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == confirm_message
|
||||
|
||||
|
||||
# route("/logs/level", methods=["POST"])
|
||||
@pytest.mark.parametrize("level", ["trace", "debug", "info", "warn", "err", "critical", "off"])
|
||||
def test_set_log_level(http_client, level):
|
||||
response = http_client.post(
|
||||
"/logs/level",
|
||||
data={
|
||||
"level": level,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == f"Log level set to {level}"
|
||||
|
||||
# Cleanup
|
||||
http_client.post(
|
||||
"/logs/level",
|
||||
data={
|
||||
"level": "debug",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# route("/logs/show", methods=["POST"])
|
||||
def test_show_logs(http_client):
|
||||
response = http_client.post(
|
||||
"/logs/show",
|
||||
data={
|
||||
"lines": 100,
|
||||
"scope": "rascsi",
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["data"]["lines"] == "100"
|
||||
assert response_data["data"]["scope"] == "rascsi"
|
||||
|
||||
|
||||
# route("/config/save", methods=["POST"])
|
||||
# route("/config/load", methods=["POST"])
|
||||
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
|
||||
reservation_memo = str(uuid.uuid4())
|
||||
|
||||
# Confirm the initial state
|
||||
assert http_client.get("/").json()["data"]["RESERVATIONS"][0] == ""
|
||||
|
||||
# Save the initial state to a config
|
||||
response = http_client.post(
|
||||
"/config/save",
|
||||
data={
|
||||
"name": config_name,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"File created: {env['cfg_dir']}/{config_json_file}"
|
||||
)
|
||||
|
||||
assert config_json_file in http_client.get("/").json()["data"]["config_files"]
|
||||
|
||||
# Modify the state
|
||||
http_client.post(
|
||||
"/scsi/reserve",
|
||||
data={
|
||||
"scsi_id": reserved_scsi_id,
|
||||
"memo": reservation_memo,
|
||||
},
|
||||
)
|
||||
|
||||
assert http_client.get("/").json()["data"]["RESERVATIONS"][0] == reservation_memo
|
||||
|
||||
# Load the saved config
|
||||
response = http_client.post(
|
||||
"/config/load",
|
||||
data={
|
||||
"name": config_json_file,
|
||||
"load": True,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"Loaded configurations from: {config_json_file}"
|
||||
)
|
||||
|
||||
# Confirm the application has returned to its initial state
|
||||
assert http_client.get("/").json()["data"]["RESERVATIONS"][0] == ""
|
||||
|
||||
# Delete the saved config
|
||||
response = http_client.post(
|
||||
"/config/load",
|
||||
data={
|
||||
"name": config_json_file,
|
||||
"delete": True,
|
||||
},
|
||||
)
|
||||
|
||||
response_data = response.json()
|
||||
|
||||
assert response.status_code == 200
|
||||
assert response_data["status"] == STATUS_SUCCESS
|
||||
assert response_data["messages"][0]["message"] == (
|
||||
f"File deleted: {env['cfg_dir']}/{config_json_file}"
|
||||
)
|
||||
|
||||
assert config_json_file not in http_client.get("/").json()["data"]["config_files"]
|
BIN
python/web/tests/assets/test_image.7z
Normal file
BIN
python/web/tests/assets/test_image.7z
Normal file
Binary file not shown.
BIN
python/web/tests/assets/test_image.hds
Normal file
BIN
python/web/tests/assets/test_image.hds
Normal file
Binary file not shown.
BIN
python/web/tests/assets/test_image.sit
Normal file
BIN
python/web/tests/assets/test_image.sit
Normal file
Binary file not shown.
BIN
python/web/tests/assets/test_image.zip
Normal file
BIN
python/web/tests/assets/test_image.zip
Normal file
Binary file not shown.
82
python/web/tests/conftest.py
Normal file
82
python/web/tests/conftest.py
Normal file
@ -0,0 +1,82 @@
|
||||
import pytest
|
||||
import requests
|
||||
import socket
|
||||
import os
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
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)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function", autouse=True)
|
||||
def set_httpserver_hostname(pytestconfig, httpserver):
|
||||
# We need httpserver.url_for() to generate URLs pointing to the correct host
|
||||
httpserver.host = pytestconfig.getoption("httpserver_host")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def ensure_all_devices_detached(create_http_client):
|
||||
http_client = create_http_client()
|
||||
http_client.post("/scsi/detach_all")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def create_http_client(pytestconfig):
|
||||
def create(authenticate=True):
|
||||
session = requests.Session()
|
||||
session.headers.update({"Accept": "application/json"})
|
||||
session.original_request = session.request
|
||||
|
||||
def relative_request(method, url, *args, **kwargs):
|
||||
if url[:4] != "http":
|
||||
url = pytestconfig.getoption("base_url") + url
|
||||
|
||||
return session.original_request(method, url, *args, **kwargs)
|
||||
|
||||
session.request = relative_request
|
||||
|
||||
if authenticate:
|
||||
session.post(
|
||||
"/login",
|
||||
data={
|
||||
"username": pytestconfig.getoption("rascsi_username"),
|
||||
"password": pytestconfig.getoption("rascsi_password"),
|
||||
},
|
||||
)
|
||||
return session
|
||||
|
||||
return create
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def http_client(create_http_client):
|
||||
return create_http_client(authenticate=True)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def http_client_unauthenticated(create_http_client):
|
||||
return create_http_client(authenticate=False)
|
8
src/raspberrypi/.gitignore
vendored
8
src/raspberrypi/.gitignore
vendored
@ -7,14 +7,10 @@
|
||||
*.vcd
|
||||
*.json
|
||||
*.html
|
||||
rascsi
|
||||
scsimon
|
||||
rasctl
|
||||
sasidump
|
||||
rasdump
|
||||
scisparse
|
||||
rascsi.dat
|
||||
obj
|
||||
bin
|
||||
coverage
|
||||
/rascsi_interface.pb.cpp
|
||||
/rascsi_interface.pb.h
|
||||
.project
|
||||
|
@ -1,5 +1,8 @@
|
||||
.DEFAULT_GOAL: all
|
||||
|
||||
# Depending on the GCC version the compilation flags differ
|
||||
GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10)
|
||||
|
||||
## Optional build flags:
|
||||
## CROSS_COMPILE : Specify which compiler toolchain to use.
|
||||
## To cross compile set this accordingly, e.g. to:
|
||||
@ -14,14 +17,12 @@ CXX = $(CROSS_COMPILE)g++
|
||||
## this is only used by developers.
|
||||
DEBUG ?= 0
|
||||
ifeq ($(DEBUG), 1)
|
||||
# Debug CFLAGS
|
||||
CFLAGS += -O0 -g -Wall -DDEBUG
|
||||
CXXFLAGS += -O0 -g -Wall -DDEBUG
|
||||
# Debug compiler flags
|
||||
CXXFLAGS += -O0 -g -Wall -Wextra -DDEBUG
|
||||
BUILD_TYPE = Debug
|
||||
else
|
||||
# Release CFLAGS
|
||||
CFLAGS += -O3 -Wall -Werror -DNDEBUG
|
||||
CXXFLAGS += -O3 -Wall -Werror -DNDEBUG
|
||||
# Release compiler flags
|
||||
CXXFLAGS += -O3 -Wall -Werror -Wextra -DNDEBUG
|
||||
BUILD_TYPE = Release
|
||||
endif
|
||||
ifeq ("$(shell uname -s)","Linux")
|
||||
@ -29,23 +30,17 @@ ifeq ("$(shell uname -s)","Linux")
|
||||
CXXFLAGS += -Wno-psabi
|
||||
endif
|
||||
|
||||
CFLAGS += -iquote . -D_FILE_OFFSET_BITS=64 -MD -MP
|
||||
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -MD -MP
|
||||
|
||||
CXXFLAGS += -std=c++17 -iquote . -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE -MD -MP
|
||||
|
||||
## EXTRA_FLAGS : Can be used to pass special purpose flags
|
||||
CFLAGS += $(EXTRA_FLAGS)
|
||||
CXXFLAGS += $(EXTRA_FLAGS)
|
||||
|
||||
# If we're using GCC version 10 or later, we need to add the FMT_HEADER_ONLY definition
|
||||
GCCVERSION10 := $(shell expr `gcc -dumpversion` \>= 10)
|
||||
|
||||
ifeq "$(GCCVERSION10)" "1"
|
||||
CFLAGS += -DFMT_HEADER_ONLY
|
||||
CXXFLAGS += -DFMT_HEADER_ONLY
|
||||
endif
|
||||
|
||||
|
||||
|
||||
## CONNECT_TYPE=FULLSPEC : Specify the type of RaSCSI board type
|
||||
## that you are using. The typical options are
|
||||
## STANDARD or FULLSPEC. The default is FULLSPEC
|
||||
@ -55,14 +50,12 @@ endif
|
||||
CONNECT_TYPE ?= FULLSPEC
|
||||
|
||||
ifdef CONNECT_TYPE
|
||||
CFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE)
|
||||
CXXFLAGS += -DCONNECT_TYPE_$(CONNECT_TYPE)
|
||||
endif
|
||||
|
||||
RASCSI = rascsi
|
||||
RASCTL = rasctl
|
||||
RASDUMP = rasdump
|
||||
SASIDUMP = sasidump
|
||||
SCSIMON = scsimon
|
||||
RASCSI_TEST = rascsi_test
|
||||
|
||||
@ -73,6 +66,8 @@ RSYSLOG_LOG = /var/log/rascsi.log
|
||||
USR_LOCAL_BIN = /usr/local/bin
|
||||
MAN_PAGE_DIR = /usr/local/man/man1
|
||||
DOC_DIR = ../../doc
|
||||
COVERAGE_DIR = ./coverage
|
||||
COVERAGE_FILE = rascsi.dat
|
||||
OS_FILES = ./os_integration
|
||||
|
||||
OBJDIR := ./obj/$(shell echo $(CONNECT_TYPE) | tr '[:upper:]' '[:lower:]')
|
||||
@ -82,8 +77,7 @@ BIN_ALL = \
|
||||
$(BINDIR)/$(RASCSI) \
|
||||
$(BINDIR)/$(RASCTL) \
|
||||
$(BINDIR)/$(SCSIMON) \
|
||||
$(BINDIR)/$(RASDUMP) \
|
||||
$(BINDIR)/$(SASIDUMP)
|
||||
$(BINDIR)/$(RASDUMP)
|
||||
|
||||
SRC_PROTOC = \
|
||||
rascsi_interface.proto
|
||||
@ -91,80 +85,67 @@ SRC_PROTOC = \
|
||||
SRC_PROTOBUF = \
|
||||
rascsi_interface.pb.cpp
|
||||
|
||||
SRC_RASCSI_CORE = scsi.cpp \
|
||||
gpiobus.cpp \
|
||||
filepath.cpp \
|
||||
fileio.cpp \
|
||||
SRC_SHARED = \
|
||||
rascsi_version.cpp \
|
||||
rascsi_image.cpp \
|
||||
rascsi_response.cpp \
|
||||
rasutil.cpp \
|
||||
protobuf_util.cpp \
|
||||
localizer.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 += $(SRC_PROTOBUF)
|
||||
SRC_RASCSI_CORE += $(shell find ./hal -name '*.cpp')
|
||||
|
||||
SRC_RASCSI = rascsi.cpp
|
||||
SRC_RASCSI += $(SRC_RASCSI_CORE)
|
||||
|
||||
SRC_SCSIMON = \
|
||||
scsimon.cpp \
|
||||
scsi.cpp \
|
||||
gpiobus.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 \
|
||||
protobuf_util.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 \
|
||||
gpiobus.cpp \
|
||||
bus.cpp \
|
||||
filepath.cpp \
|
||||
fileio.cpp \
|
||||
rascsi_version.cpp
|
||||
|
||||
SRC_SASIDUMP = \
|
||||
sasidump.cpp \
|
||||
scsi.cpp \
|
||||
gpiobus.cpp \
|
||||
filepath.cpp \
|
||||
fileio.cpp \
|
||||
rascsi_version.cpp
|
||||
|
||||
SRC_RASDUMP += $(shell find ./hal -name '*.cpp')
|
||||
|
||||
SRC_RASCSI_TEST = $(shell find ./test -name '*.cpp')
|
||||
SRC_RASCSI_TEST += $(SRC_RASCSI_CORE)
|
||||
|
||||
|
||||
vpath %.h ./ ./controllers ./devices ./monitor
|
||||
vpath %.cpp ./ ./controllers ./devices ./monitor ./test
|
||||
vpath %.h ./ ./controllers ./devices ./monitor ./hal ./rascsi ./rasctl
|
||||
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_SASIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SASIDUMP:%.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)))
|
||||
|
||||
GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h
|
||||
|
||||
|
||||
# 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) $(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):
|
||||
@ -183,41 +164,51 @@ $(SRC_PROTOBUF): $(SRC_PROTOC)
|
||||
## all : Rebuild all of the executable files and re-generate
|
||||
## the text versions of the manpages
|
||||
## docs : Re-generate the text versions of the man pages
|
||||
## test : Build and run unit tests
|
||||
## coverage : Build and run unit tests and create coverage SonarQube files.
|
||||
## lcov : Build and run unit tests and create coverage HTML files.
|
||||
## Note that you have to run 'make clean' before switching
|
||||
## between coverage and non-coverage builds.
|
||||
.DEFAULT_GOAL := all
|
||||
.PHONY: all ALL docs
|
||||
.PHONY: all ALL docs test coverage lcov
|
||||
all: $(BIN_ALL) docs
|
||||
ALL: all
|
||||
|
||||
test: $(BINDIR)/$(RASCSI_TEST)
|
||||
$(BINDIR)/$(RASCSI_TEST)
|
||||
|
||||
coverage: CXXFLAGS += --coverage
|
||||
coverage: test
|
||||
|
||||
lcov: CXXFLAGS += --coverage
|
||||
lcov: test
|
||||
lcov -q -c -d . --include '*/raspberrypi/*' -o $(COVERAGE_FILE) --exclude '*/test/*' --exclude '*/interfaces/*' --exclude '*/rascsi_interface.pb*'
|
||||
genhtml -q -o $(COVERAGE_DIR) --legend $(COVERAGE_FILE)
|
||||
|
||||
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) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI) -lpthread -lpcap -lprotobuf -lstdc++fs
|
||||
$(SRC_SHARED): $(SRC_PROTOBUF)
|
||||
|
||||
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL) -lpthread -lprotobuf -lstdc++fs
|
||||
$(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCSI) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs
|
||||
|
||||
$(BINDIR)/$(RASCTL): $(SRC_PROTOBUF) $(OBJ_RASCTL_CORE) $(OBJ_RASCTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCTL_CORE) $(OBJ_RASCTL) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lprotobuf
|
||||
|
||||
$(BINDIR)/$(RASDUMP): $(OBJ_RASDUMP) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASDUMP)
|
||||
|
||||
$(BINDIR)/$(SASIDUMP): $(OBJ_SASIDUMP) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_SASIDUMP)
|
||||
|
||||
$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_SCSIMON) -lpthread
|
||||
|
||||
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_TEST) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_TEST) -lpcap -lprotobuf -lgmock -lgtest -lgtest_main
|
||||
$(BINDIR)/$(RASCSI_TEST): $(SRC_PROTOBUF) $(OBJ_RASCSI_CORE) $(OBJ_RASCTL_CORE) $(OBJ_RASCSI_TEST) $(OBJ_RASCTL_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) | $(BINDIR)
|
||||
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_RASCSI_CORE) $(OBJ_RASCTL_CORE) $(OBJ_RASCSI_TEST) $(OBJ_SHARED) $(OBJ_PROTOBUF) -lpthread -lpcap -lprotobuf -lstdc++fs -lgmock -lgtest
|
||||
|
||||
|
||||
# Phony rules for building individual utilities
|
||||
.PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SASIDUMP) $(SCSIMON)
|
||||
.PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SCSIMON)
|
||||
$(RASCSI) : $(BINDIR)/$(RASCSI)
|
||||
$(RASCTL) : $(BINDIR)/$(RASCTL)
|
||||
$(RASDUMP) : $(BINDIR)/$(RASDUMP)
|
||||
$(SASIDUMP): $(BINDIR)/$(SASIDUMP)
|
||||
$(SCSIMON) : $(BINDIR)/$(SCSIMON)
|
||||
|
||||
|
||||
@ -225,7 +216,7 @@ $(SCSIMON) : $(BINDIR)/$(SCSIMON)
|
||||
## compiler files and executable files
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(BINDIR) $(GEN_PROTOBUF)
|
||||
rm -rf $(OBJDIR) $(BINDIR) $(GEN_PROTOBUF) $(COVERAGE_DIR) $(COVERAGE_FILE)
|
||||
|
||||
## install : Copies all of the man pages to the correct location
|
||||
## Copies the binaries to a global install location
|
||||
@ -245,12 +236,10 @@ install: \
|
||||
$(MAN_PAGE_DIR)/rasctl.1 \
|
||||
$(MAN_PAGE_DIR)/scsimon.1 \
|
||||
$(MAN_PAGE_DIR)/rasdump.1 \
|
||||
$(MAN_PAGE_DIR)/sasidump.1 \
|
||||
$(USR_LOCAL_BIN)/$(RASCTL) \
|
||||
$(USR_LOCAL_BIN)/$(RASCSI) \
|
||||
$(USR_LOCAL_BIN)/$(SCSIMON) \
|
||||
$(USR_LOCAL_BIN)/$(RASDUMP) \
|
||||
$(USR_LOCAL_BIN)/$(SASIDUMP) \
|
||||
$(SYSTEMD_CONF) \
|
||||
$(RSYSLOG_CONF) \
|
||||
$(RSYSLOG_LOG)
|
||||
|
85
src/raspberrypi/bus.cpp
Normal file
85
src/raspberrypi/bus.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "bus.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Phase Acquisition
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
BUS::phase_t BUS::GetPhase()
|
||||
{
|
||||
// Selection Phase
|
||||
if (GetSEL()) {
|
||||
return phase_t::selection;
|
||||
}
|
||||
|
||||
// Bus busy phase
|
||||
if (!GetBSY()) {
|
||||
return phase_t::busfree;
|
||||
}
|
||||
|
||||
// Get target phase from bus signal line
|
||||
int mci = GetMSG() ? 0b100 : 0b000;
|
||||
mci |= GetCD() ? 0b010 : 0b000;
|
||||
mci |= GetIO() ? 0b001 : 0b000;
|
||||
return GetPhase(mci);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Determine Phase String phase enum
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
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";
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Phase Table
|
||||
// Reference Table 8: https://www.staff.uni-mainz.de/tacke/scsi/SCSI2-06.html
|
||||
// This determines the phase based upon the Msg, C/D and I/O signals.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
const array<BUS::phase_t, 8> BUS::phase_table = {
|
||||
// | MSG|C/D|I/O |
|
||||
phase_t::dataout, // | 0 | 0 | 0 |
|
||||
phase_t::datain, // | 0 | 0 | 1 |
|
||||
phase_t::command, // | 0 | 1 | 0 |
|
||||
phase_t::status, // | 0 | 1 | 1 |
|
||||
phase_t::reserved, // | 1 | 0 | 0 |
|
||||
phase_t::reserved, // | 1 | 0 | 1 |
|
||||
phase_t::msgout, // | 1 | 1 | 0 |
|
||||
phase_t::msgin // | 1 | 1 | 1 |
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Phase string to phase mapping
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
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
113
src/raspberrypi/bus.h
Normal file
@ -0,0 +1,113 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(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;
|
||||
};
|
87
src/raspberrypi/command_util.cpp
Normal file
87
src/raspberrypi/command_util.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "log.h"
|
||||
#include "rascsi_interface.pb.h"
|
||||
#include "protobuf_serializer.h"
|
||||
#include "command_util.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace rascsi_interface;
|
||||
|
||||
#define FPRT(fp, ...) fprintf(fp, __VA_ARGS__ )
|
||||
|
||||
static const char COMPONENT_SEPARATOR = ':';
|
||||
static const char KEY_VALUE_SEPARATOR = '=';
|
||||
|
||||
void command_util::ParseParameters(PbDeviceDefinition& device, const string& params)
|
||||
{
|
||||
if (params.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Old style parameters, for backwards compatibility only.
|
||||
// Only one of these parameters will be used by rascsi, depending on the device type.
|
||||
if (params.find(KEY_VALUE_SEPARATOR) == string::npos) {
|
||||
AddParam(device, "file", params);
|
||||
if (params != "bridge" && params != "daynaport" && params != "printer" && params != "services") {
|
||||
AddParam(device, "interfaces", params);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
stringstream ss(params);
|
||||
string p;
|
||||
while (getline(ss, p, COMPONENT_SEPARATOR)) {
|
||||
if (!p.empty()) {
|
||||
size_t separator_pos = p.find(KEY_VALUE_SEPARATOR);
|
||||
if (separator_pos != string::npos) {
|
||||
AddParam(device, p.substr(0, separator_pos), string_view(p).substr(separator_pos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string command_util::GetParam(const PbCommand& command, const string& key)
|
||||
{
|
||||
const auto& it = command.params().find(key);
|
||||
return it != command.params().end() ? it->second : "";
|
||||
}
|
||||
|
||||
string command_util::GetParam(const PbDeviceDefinition& device, const string& key)
|
||||
{
|
||||
const auto& it = device.params().find(key);
|
||||
return it != device.params().end() ? it->second : "";
|
||||
}
|
||||
|
||||
void command_util::AddParam(PbCommand& command, const string& key, string_view value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *command.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void command_util::AddParam(PbDevice& device, const string& key, string_view value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *device.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
void command_util::AddParam(PbDeviceDefinition& device, const string& key, string_view value)
|
||||
{
|
||||
if (!key.empty() && !value.empty()) {
|
||||
auto& map = *device.mutable_params();
|
||||
map[key] = value;
|
||||
}
|
||||
}
|
@ -18,9 +18,8 @@
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#define USE_SEL_EVENT_ENABLE // Check SEL signal by event
|
||||
#define REMOVE_FIXED_SASIHD_SIZE // remove the size limitation of SASIHD
|
||||
// This avoids an indefinite loop with warnings if there is no RaSCSI hardware
|
||||
// and thus helps with running certain tests on X86 hardware.
|
||||
#if defined(__x86_64__) || defined(__X86__)
|
||||
// and thus helps with running rasctl and unit test on x86 hardware.
|
||||
#if defined(__x86_64__) || defined(__X86__) || !defined(__linux__)
|
||||
#undef USE_SEL_EVENT_ENABLE
|
||||
#endif
|
||||
|
136
src/raspberrypi/controllers/abstract_controller.cpp
Normal file
136
src/raspberrypi/controllers/abstract_controller.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "abstract_controller.h"
|
||||
|
||||
void AbstractController::AllocateBuffer(size_t size)
|
||||
{
|
||||
if (size > ctrl.buffer.size()) {
|
||||
ctrl.buffer.resize(size);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void AbstractController::Reset()
|
||||
{
|
||||
SetPhase(BUS::phase_t::busfree);
|
||||
|
||||
ctrl.status = status::GOOD;
|
||||
ctrl.message = 0x00;
|
||||
ctrl.blocks = 0;
|
||||
ctrl.next = 0;
|
||||
ctrl.offset = 0;
|
||||
ctrl.length = 0;
|
||||
|
||||
// Reset all LUNs
|
||||
for (const auto& [lun, device] : luns) {
|
||||
device->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractController::ProcessPhase()
|
||||
{
|
||||
switch (GetPhase()) {
|
||||
case BUS::phase_t::busfree:
|
||||
BusFree();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::selection:
|
||||
Selection();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::dataout:
|
||||
DataOut();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::datain:
|
||||
DataIn();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::command:
|
||||
Command();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::status:
|
||||
Status();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::msgout:
|
||||
MsgOut();
|
||||
break;
|
||||
|
||||
case BUS::phase_t::msgin:
|
||||
MsgIn();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOGERROR("Cannot process phase %s", BUS::GetPhaseStrRaw(GetPhase()))
|
||||
throw scsi_exception();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractController::AddDevice(shared_ptr<PrimaryDevice> device)
|
||||
{
|
||||
if (device->GetLun() < 0 || device->GetLun() >= GetMaxLuns() || HasDeviceForLun(device->GetLun())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
luns[device->GetLun()] = device;
|
||||
device->SetController(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AbstractController::DeleteDevice(const shared_ptr<PrimaryDevice> device)
|
||||
{
|
||||
return luns.erase(device->GetLun()) == 1;
|
||||
}
|
||||
|
||||
bool AbstractController::HasDeviceForLun(int lun) const
|
||||
{
|
||||
return luns.find(lun) != luns.end();
|
||||
}
|
||||
|
||||
int AbstractController::ExtractInitiatorId(int id_data) const
|
||||
{
|
||||
int initiator_id = -1;
|
||||
|
||||
if (int tmp = id_data - (1 << target_id); tmp) {
|
||||
initiator_id = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
tmp >>= 1;
|
||||
if (tmp) {
|
||||
initiator_id++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initiator_id;
|
||||
}
|
112
src/raspberrypi/controllers/abstract_controller.h
Normal file
112
src/raspberrypi/controllers/abstract_controller.h
Normal file
@ -0,0 +1,112 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
// Base class for device controllers
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#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;
|
||||
|
||||
class PrimaryDevice;
|
||||
|
||||
class AbstractController : public PhaseHandler
|
||||
{
|
||||
friend class PrimaryDevice;
|
||||
friend class ScsiController;
|
||||
|
||||
// Logical units of this controller mapped to their LUN numbers
|
||||
unordered_map<int, shared_ptr<PrimaryDevice>> luns;
|
||||
|
||||
public:
|
||||
|
||||
enum class rascsi_shutdown_mode {
|
||||
NONE,
|
||||
STOP_RASCSI,
|
||||
STOP_PI,
|
||||
RESTART_PI
|
||||
};
|
||||
|
||||
using ctrl_t = struct _ctrl_t {
|
||||
vector<int> cmd; // Command data, dynamically allocated per received command
|
||||
scsi_defs::status status; // Status data
|
||||
int message; // Message data
|
||||
|
||||
// Transfer
|
||||
vector<BYTE> buffer; // Transfer data buffer
|
||||
uint32_t blocks; // Number of transfer blocks
|
||||
uint64_t next; // Next record
|
||||
uint32_t offset; // Transfer offset
|
||||
uint32_t length; // Transfer remaining length
|
||||
};
|
||||
|
||||
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;
|
||||
virtual void Reset();
|
||||
virtual int GetInitiatorId() const = 0;
|
||||
virtual void SetByteTransfer(bool) = 0;
|
||||
|
||||
// Get requested LUN based on IDENTIFY message, with LUN from the CDB as fallback
|
||||
virtual int GetEffectiveLun() const = 0;
|
||||
|
||||
virtual void ScheduleShutdown(rascsi_shutdown_mode) = 0;
|
||||
|
||||
int GetTargetId() const { return target_id; }
|
||||
int GetMaxLuns() const { return max_luns; }
|
||||
int GetLunCount() const { return (int)luns.size(); }
|
||||
|
||||
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) const;
|
||||
|
||||
void AllocateBuffer(size_t);
|
||||
vector<BYTE>& GetBuffer() { return ctrl.buffer; }
|
||||
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:
|
||||
|
||||
scsi_defs::scsi_command GetOpcode() const { return (scsi_defs::scsi_command)ctrl.cmd[0]; }
|
||||
int GetLun() const { return (ctrl.cmd[1] >> 5) & 0x07; }
|
||||
|
||||
void ProcessPhase();
|
||||
|
||||
vector<int>& InitCmd(int size) { ctrl.cmd.resize(size); return ctrl.cmd; }
|
||||
|
||||
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; }
|
||||
|
||||
private:
|
||||
|
||||
int target_id;
|
||||
|
||||
BUS& bus;
|
||||
|
||||
int max_luns;
|
||||
|
||||
ctrl_t ctrl = {};
|
||||
|
||||
ctrl_t* GetCtrl() { return &ctrl; }
|
||||
};
|
87
src/raspberrypi/controllers/controller_manager.cpp
Normal file
87
src/raspberrypi/controllers/controller_manager.cpp
Normal file
@ -0,0 +1,87 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "devices/device_factory.h"
|
||||
#include "devices/primary_device.h"
|
||||
#include "scsi_controller.h"
|
||||
#include "controller_manager.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
bool ControllerManager::AttachToScsiController(int id, shared_ptr<PrimaryDevice> device)
|
||||
{
|
||||
auto controller = FindController(id);
|
||||
if (controller == nullptr) {
|
||||
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) {
|
||||
if (data & (1 << controller->GetTargetId())) {
|
||||
return controller;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
shared_ptr<AbstractController> ControllerManager::FindController(int target_id) const
|
||||
{
|
||||
const auto& it = controllers.find(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();
|
||||
}
|
||||
|
||||
void ControllerManager::ResetAllControllers() const
|
||||
{
|
||||
for (const auto& [id, controller] : controllers) {
|
||||
controller->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<PrimaryDevice> ControllerManager::GetDeviceByIdAndLun(int id, int lun) const
|
||||
{
|
||||
if (const auto controller = FindController(id); controller != nullptr) {
|
||||
return controller->GetDeviceForLun(lun);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
46
src/raspberrypi/controllers/controller_manager.h
Normal file
46
src/raspberrypi/controllers/controller_manager.h
Normal file
@ -0,0 +1,46 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2022 Uwe Seimet
|
||||
//
|
||||
// Keeps track of and manages the controllers
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class BUS;
|
||||
class AbstractController;
|
||||
class PrimaryDevice;
|
||||
|
||||
class ControllerManager
|
||||
{
|
||||
BUS& bus;
|
||||
|
||||
unordered_map<int, shared_ptr<AbstractController>> controllers;
|
||||
|
||||
public:
|
||||
|
||||
explicit ControllerManager(BUS& bus) : bus(bus) {}
|
||||
~ControllerManager() = default;
|
||||
|
||||
// Maximum number of controller devices
|
||||
static const int DEVICE_MAX = 8;
|
||||
|
||||
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;
|
||||
shared_ptr<PrimaryDevice> GetDeviceByIdAndLun(int, int) const;
|
||||
};
|
46
src/raspberrypi/controllers/phase_handler.h
Normal file
46
src/raspberrypi/controllers/phase_handler.h
Normal 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; }
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,171 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ SASI device controller ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#pragma once
|
||||
|
||||
#include "../config.h"
|
||||
#include "os.h"
|
||||
#include "scsi.h"
|
||||
#include "fileio.h"
|
||||
|
||||
class PrimaryDevice;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SASI Controller
|
||||
//
|
||||
//===========================================================================
|
||||
class SASIDEV
|
||||
{
|
||||
protected:
|
||||
|
||||
private:
|
||||
enum sasi_command : int {
|
||||
eCmdTestUnitReady = 0x00,
|
||||
eCmdRezero = 0x01,
|
||||
eCmdRequestSense = 0x03,
|
||||
eCmdFormat = 0x04,
|
||||
eCmdReadCapacity = 0x05,
|
||||
eCmdFormatLegacy = 0x06,
|
||||
eCmdReassign = 0x07,
|
||||
eCmdRead6 = 0x08,
|
||||
eCmdWrite6 = 0x0A,
|
||||
eCmdSeek6 = 0x0B,
|
||||
eCmdSetMcastAddr = 0x0D, // DaynaPort specific command
|
||||
eCmdInquiry = 0x12,
|
||||
eCmdModeSelect6 = 0x15,
|
||||
eCmdReserve6 = 0x16,
|
||||
eCmdRelease6 = 0x17,
|
||||
eCmdRead10 = 0x28,
|
||||
eCmdWrite10 = 0x2A,
|
||||
eCmdVerify10 = 0x2E,
|
||||
eCmdVerify = 0x2F,
|
||||
eCmdModeSelect10 = 0x55,
|
||||
eCmdRead16 = 0x88,
|
||||
eCmdWrite16 = 0x8A,
|
||||
eCmdVerify16 = 0x8F,
|
||||
eCmdWriteLong10 = 0x3F,
|
||||
eCmdWriteLong16 = 0x9F,
|
||||
eCmdInvalid = 0xC2,
|
||||
eCmdSasiCmdAssign = 0x0E
|
||||
};
|
||||
|
||||
public:
|
||||
enum {
|
||||
UnitMax = 32 // Maximum number of logical units
|
||||
};
|
||||
|
||||
const int UNKNOWN_SCSI_ID = -1;
|
||||
const int DEFAULT_BUFFER_SIZE = 0x1000;
|
||||
// TODO Remove this duplicate
|
||||
const int DAYNAPORT_BUFFER_SIZE = 0x1000000;
|
||||
|
||||
// For timing adjustments
|
||||
enum {
|
||||
min_exec_time_sasi = 100, // SASI BOOT/FORMAT 30:NG 35:OK
|
||||
min_exec_time_scsi = 50
|
||||
};
|
||||
|
||||
// Internal data definition
|
||||
typedef struct {
|
||||
// General
|
||||
BUS::phase_t phase; // Transition phase
|
||||
int m_scsi_id; // Controller ID (0-7)
|
||||
BUS *bus; // Bus
|
||||
|
||||
// commands
|
||||
DWORD cmd[16]; // Command data
|
||||
DWORD status; // Status data
|
||||
DWORD message; // Message data
|
||||
|
||||
// Run
|
||||
DWORD execstart; // Execution start time
|
||||
|
||||
// Transfer
|
||||
BYTE *buffer; // Transfer data buffer
|
||||
int bufsize; // Transfer data buffer size
|
||||
uint32_t blocks; // Number of transfer block
|
||||
DWORD next; // Next record
|
||||
DWORD offset; // Transfer offset
|
||||
DWORD length; // Transfer remaining length
|
||||
|
||||
// Logical units
|
||||
PrimaryDevice *unit[UnitMax];
|
||||
|
||||
// The current device
|
||||
PrimaryDevice *device;
|
||||
|
||||
// The LUN from the IDENTIFY message
|
||||
int lun;
|
||||
} ctrl_t;
|
||||
|
||||
public:
|
||||
// Basic Functions
|
||||
SASIDEV();
|
||||
virtual ~SASIDEV(); // Destructor
|
||||
virtual void Reset(); // Device Reset
|
||||
|
||||
// External API
|
||||
virtual BUS::phase_t Process(int); // Run
|
||||
|
||||
// Connect
|
||||
void Connect(int id, BUS *sbus); // Controller connection
|
||||
PrimaryDevice* GetUnit(int no); // Get logical unit
|
||||
void SetUnit(int no, PrimaryDevice *dev); // Logical unit setting
|
||||
bool HasUnit(); // Has a valid logical unit
|
||||
|
||||
// Other
|
||||
BUS::phase_t GetPhase() {return ctrl.phase;} // Get the phase
|
||||
|
||||
int GetSCSIID() {return ctrl.m_scsi_id;} // Get the ID
|
||||
ctrl_t* GetCtrl() { return &ctrl; } // Get the internal information address
|
||||
virtual bool IsSASI() const { return true; } // SASI Check
|
||||
virtual bool IsSCSI() const { return false; } // SCSI check
|
||||
|
||||
public:
|
||||
void DataIn(); // Data in phase
|
||||
void Status(); // Status phase
|
||||
void MsgIn(); // Message in phase
|
||||
void DataOut(); // Data out phase
|
||||
|
||||
virtual int GetEffectiveLun() const;
|
||||
|
||||
virtual void Error(scsi_defs::sense_key sense_key = scsi_defs::sense_key::NO_SENSE,
|
||||
scsi_defs::asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||
scsi_defs::status = scsi_defs::status::CHECK_CONDITION); // Common error handling
|
||||
|
||||
protected:
|
||||
// Phase processing
|
||||
virtual void BusFree(); // Bus free phase
|
||||
virtual void Selection(); // Selection phase
|
||||
virtual void Command(); // Command phase
|
||||
virtual void Execute(); // Execution phase
|
||||
|
||||
// Commands
|
||||
void CmdAssign(); // ASSIGN command
|
||||
void CmdSpecify(); // SPECIFY command
|
||||
|
||||
// Data transfer
|
||||
virtual void Send(); // Send data
|
||||
virtual void Receive(); // Receive data
|
||||
|
||||
bool XferIn(BYTE* buf); // Data transfer IN
|
||||
virtual bool XferOut(bool cont); // Data transfer OUT
|
||||
|
||||
// Special operations
|
||||
void FlushUnit(); // Flush the logical unit
|
||||
|
||||
ctrl_t ctrl; // Internal data
|
||||
};
|
1065
src/raspberrypi/controllers/scsi_controller.cpp
Normal file
1065
src/raspberrypi/controllers/scsi_controller.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
src/raspberrypi/controllers/scsi_controller.h
Normal file
121
src/raspberrypi/controllers/scsi_controller.h
Normal file
@ -0,0 +1,121 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "abstract_controller.h"
|
||||
#include "os.h"
|
||||
#include "scsi.h"
|
||||
#include <array>
|
||||
|
||||
class PrimaryDevice;
|
||||
|
||||
class ScsiController : public AbstractController
|
||||
{
|
||||
// For timing adjustments
|
||||
static const unsigned int MIN_EXEC_TIME = 50;
|
||||
|
||||
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||
static const int MAX_SYNC_PERIOD = 50;
|
||||
|
||||
// REQ/ACK offset(limited to 16)
|
||||
static const BYTE MAX_SYNC_OFFSET = 16;
|
||||
|
||||
static const int UNKNOWN_INITIATOR_ID = -1;
|
||||
|
||||
const int DEFAULT_BUFFER_SIZE = 0x1000;
|
||||
|
||||
using scsi_t = struct _scsi_t {
|
||||
// Synchronous transfer
|
||||
bool syncenable; // Synchronous transfer possible
|
||||
BYTE syncperiod = MAX_SYNC_PERIOD; // Synchronous transfer period
|
||||
BYTE syncoffset; // Synchronous transfer offset
|
||||
int syncack; // Number of synchronous transfer ACKs
|
||||
|
||||
// ATN message
|
||||
bool atnmsg;
|
||||
int msc;
|
||||
std::array<BYTE, 256> msb;
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
// Maximum number of logical units
|
||||
static const int LUN_MAX = 32;
|
||||
|
||||
ScsiController(BUS&, int);
|
||||
~ScsiController() override = default;
|
||||
|
||||
void Reset() override;
|
||||
|
||||
BUS::phase_t Process(int) override;
|
||||
|
||||
int GetEffectiveLun() const override;
|
||||
|
||||
void Error(scsi_defs::sense_key sense_key, scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
|
||||
|
||||
int GetInitiatorId() const override { return initiator_id; }
|
||||
void SetByteTransfer(bool b) override { is_byte_transfer = b; }
|
||||
|
||||
void Status() override;
|
||||
void DataIn() override;
|
||||
void DataOut() override;
|
||||
|
||||
private:
|
||||
|
||||
// Execution start time
|
||||
uint32_t execstart = 0;
|
||||
|
||||
// The initiator ID may be unavailable, e.g. with Atari ACSI and old host adapters
|
||||
int initiator_id = UNKNOWN_INITIATOR_ID;
|
||||
|
||||
// The LUN from the IDENTIFY message
|
||||
int identified_lun = -1;
|
||||
|
||||
bool is_byte_transfer = false;
|
||||
uint32_t bytes_to_transfer = 0;
|
||||
|
||||
// Phases
|
||||
void BusFree() override;
|
||||
void Selection() override;
|
||||
void Command() override;
|
||||
void MsgIn() override;
|
||||
void MsgOut() override;
|
||||
|
||||
// Data transfer
|
||||
void Send();
|
||||
bool XferMsg(int);
|
||||
bool XferIn(vector<BYTE>&);
|
||||
bool XferOut(bool);
|
||||
bool XferOutBlockOriented(bool);
|
||||
void ReceiveBytes();
|
||||
|
||||
void Execute();
|
||||
void DataOutNonBlockOriented();
|
||||
void Receive();
|
||||
|
||||
void ProcessCommand();
|
||||
void ParseMessage();
|
||||
void ProcessMessage();
|
||||
|
||||
void ScheduleShutdown(rascsi_shutdown_mode mode) override { shutdown_mode = mode; }
|
||||
|
||||
void Sleep();
|
||||
|
||||
scsi_t scsi = {};
|
||||
|
||||
AbstractController::rascsi_shutdown_mode shutdown_mode = AbstractController::rascsi_shutdown_mode::NONE;
|
||||
};
|
||||
|
@ -1,918 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ SCSI device controller ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#include "log.h"
|
||||
#include "controllers/scsidev_ctrl.h"
|
||||
#include "gpiobus.h"
|
||||
#include "devices/scsi_daynaport.h"
|
||||
#include "devices/scsi_printer.h"
|
||||
|
||||
using namespace scsi_defs;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SCSI Device
|
||||
//
|
||||
//===========================================================================
|
||||
|
||||
SCSIDEV::SCSIDEV() : SASIDEV()
|
||||
{
|
||||
scsi.is_byte_transfer = false;
|
||||
scsi.bytes_to_transfer = 0;
|
||||
shutdown_mode = NONE;
|
||||
|
||||
// Synchronous transfer work initialization
|
||||
scsi.syncenable = FALSE;
|
||||
scsi.syncperiod = 50;
|
||||
scsi.syncoffset = 0;
|
||||
scsi.atnmsg = false;
|
||||
scsi.msc = 0;
|
||||
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||
}
|
||||
|
||||
SCSIDEV::~SCSIDEV()
|
||||
{
|
||||
}
|
||||
|
||||
void SCSIDEV::Reset()
|
||||
{
|
||||
scsi.is_byte_transfer = false;
|
||||
scsi.bytes_to_transfer = 0;
|
||||
|
||||
// Work initialization
|
||||
scsi.atnmsg = false;
|
||||
scsi.msc = 0;
|
||||
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||
|
||||
super::Reset();
|
||||
}
|
||||
|
||||
BUS::phase_t SCSIDEV::Process(int initiator_id)
|
||||
{
|
||||
// Do nothing if not connected
|
||||
if (ctrl.m_scsi_id < 0 || ctrl.bus == NULL) {
|
||||
return ctrl.phase;
|
||||
}
|
||||
|
||||
// Get bus information
|
||||
ctrl.bus->Aquire();
|
||||
|
||||
// Check to see if the reset signal was asserted
|
||||
if (ctrl.bus->GetRST()) {
|
||||
LOGWARN("RESET signal received!");
|
||||
|
||||
// Reset the controller
|
||||
Reset();
|
||||
|
||||
// Reset the bus
|
||||
ctrl.bus->Reset();
|
||||
return ctrl.phase;
|
||||
}
|
||||
|
||||
scsi.initiator_id = initiator_id;
|
||||
|
||||
// Phase processing
|
||||
switch (ctrl.phase) {
|
||||
// Bus free phase
|
||||
case BUS::busfree:
|
||||
BusFree();
|
||||
break;
|
||||
|
||||
// Selection
|
||||
case BUS::selection:
|
||||
Selection();
|
||||
break;
|
||||
|
||||
// Data out (MCI=000)
|
||||
case BUS::dataout:
|
||||
DataOut();
|
||||
break;
|
||||
|
||||
// Data in (MCI=001)
|
||||
case BUS::datain:
|
||||
DataIn();
|
||||
break;
|
||||
|
||||
// Command (MCI=010)
|
||||
case BUS::command:
|
||||
Command();
|
||||
break;
|
||||
|
||||
// Status (MCI=011)
|
||||
case BUS::status:
|
||||
Status();
|
||||
break;
|
||||
|
||||
// Message out (MCI=110)
|
||||
case BUS::msgout:
|
||||
MsgOut();
|
||||
break;
|
||||
|
||||
// Message in (MCI=111)
|
||||
case BUS::msgin:
|
||||
MsgIn();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return ctrl.phase;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Bus free phase
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::BusFree()
|
||||
{
|
||||
// Phase change
|
||||
if (ctrl.phase != BUS::busfree) {
|
||||
LOGTRACE("%s Bus free phase", __PRETTY_FUNCTION__);
|
||||
|
||||
// Phase setting
|
||||
ctrl.phase = BUS::busfree;
|
||||
|
||||
// Set Signal lines
|
||||
ctrl.bus->SetREQ(FALSE);
|
||||
ctrl.bus->SetMSG(FALSE);
|
||||
ctrl.bus->SetCD(FALSE);
|
||||
ctrl.bus->SetIO(FALSE);
|
||||
ctrl.bus->SetBSY(false);
|
||||
|
||||
// Initialize status and message
|
||||
ctrl.status = 0x00;
|
||||
ctrl.message = 0x00;
|
||||
|
||||
// Initialize ATN message reception status
|
||||
scsi.atnmsg = false;
|
||||
|
||||
ctrl.lun = -1;
|
||||
|
||||
scsi.is_byte_transfer = false;
|
||||
scsi.bytes_to_transfer = 0;
|
||||
|
||||
// When the bus is free RaSCSI or the Pi may be shut down
|
||||
switch(shutdown_mode) {
|
||||
case STOP_RASCSI:
|
||||
LOGINFO("RaSCSI shutdown requested");
|
||||
exit(0);
|
||||
break;
|
||||
|
||||
case STOP_PI:
|
||||
LOGINFO("Raspberry Pi shutdown requested");
|
||||
if (system("init 0") == -1) {
|
||||
LOGERROR("Raspberry Pi shutdown failed: %s", strerror(errno));
|
||||
}
|
||||
break;
|
||||
|
||||
case RESTART_PI:
|
||||
LOGINFO("Raspberry Pi restart requested");
|
||||
if (system("init 6") == -1) {
|
||||
LOGERROR("Raspberry Pi restart failed: %s", strerror(errno));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to selection phase
|
||||
if (ctrl.bus->GetSEL() && !ctrl.bus->GetBSY()) {
|
||||
Selection();
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Selection Phase
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::Selection()
|
||||
{
|
||||
// Phase change
|
||||
if (ctrl.phase != BUS::selection) {
|
||||
// invalid if IDs do not match
|
||||
int id = 1 << ctrl.m_scsi_id;
|
||||
if ((ctrl.bus->GetDAT() & id) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Return if there is no valid LUN
|
||||
if (!HasUnit()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGTRACE("%s Selection Phase ID=%d (with device)", __PRETTY_FUNCTION__, (int)ctrl.m_scsi_id);
|
||||
|
||||
if (scsi.initiator_id != UNKNOWN_SCSI_ID) {
|
||||
LOGTRACE("%s Initiator ID is %d", __PRETTY_FUNCTION__, scsi.initiator_id);
|
||||
}
|
||||
else {
|
||||
LOGTRACE("%s Initiator ID is unknown", __PRETTY_FUNCTION__);
|
||||
}
|
||||
|
||||
// Phase setting
|
||||
ctrl.phase = BUS::selection;
|
||||
|
||||
// Raise BSY and respond
|
||||
ctrl.bus->SetBSY(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Selection completed
|
||||
if (!ctrl.bus->GetSEL() && ctrl.bus->GetBSY()) {
|
||||
// Message out phase if ATN=1, otherwise command phase
|
||||
if (ctrl.bus->GetATN()) {
|
||||
MsgOut();
|
||||
} else {
|
||||
Command();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Execution Phase
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::Execute()
|
||||
{
|
||||
LOGTRACE("%s Execution phase command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
||||
|
||||
// Phase Setting
|
||||
ctrl.phase = BUS::execute;
|
||||
|
||||
// Initialization for data transfer
|
||||
ctrl.offset = 0;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.execstart = SysTimer::GetTimerLow();
|
||||
|
||||
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
||||
if ((scsi_command)ctrl.cmd[0] != scsi_command::eCmdRequestSense) {
|
||||
ctrl.status = 0;
|
||||
}
|
||||
|
||||
LOGDEBUG("++++ CMD ++++ %s Executing command $%02X", __PRETTY_FUNCTION__, (unsigned int)ctrl.cmd[0]);
|
||||
|
||||
int lun = GetEffectiveLun();
|
||||
if (!ctrl.unit[lun]) {
|
||||
if ((scsi_command)ctrl.cmd[0] != scsi_command::eCmdInquiry &&
|
||||
(scsi_command)ctrl.cmd[0] != scsi_command::eCmdRequestSense) {
|
||||
LOGDEBUG("Invalid LUN %d for ID %d", lun, GetSCSIID());
|
||||
|
||||
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_LUN);
|
||||
return;
|
||||
}
|
||||
// Use LUN 0 for INQUIRY and REQUEST SENSE because LUN0 is assumed to be always available.
|
||||
// INQUIRY and REQUEST SENSE have a special LUN handling of their own, required by the SCSI standard.
|
||||
else {
|
||||
assert(ctrl.unit[0]);
|
||||
|
||||
lun = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ctrl.device = ctrl.unit[lun];
|
||||
|
||||
// Discard pending sense data from the previous command if the current command is not REQUEST SENSE
|
||||
if ((scsi_command)ctrl.cmd[0] != scsi_command::eCmdRequestSense) {
|
||||
ctrl.device->SetStatusCode(0);
|
||||
}
|
||||
|
||||
if (!ctrl.device->Dispatch(this)) {
|
||||
LOGTRACE("ID %d LUN %d received unsupported command: $%02X", GetSCSIID(), lun, (BYTE)ctrl.cmd[0]);
|
||||
|
||||
Error(sense_key::ILLEGAL_REQUEST, asc::INVALID_COMMAND_OPERATION_CODE);
|
||||
}
|
||||
|
||||
// SCSI-2 p.104 4.4.3 Incorrect logical unit handling
|
||||
if ((scsi_command)ctrl.cmd[0] == scsi_command::eCmdInquiry && !ctrl.unit[lun]) {
|
||||
lun = GetEffectiveLun();
|
||||
|
||||
LOGTRACE("Reporting LUN %d for device ID %d as not supported", lun, ctrl.device->GetId());
|
||||
|
||||
ctrl.buffer[0] = 0x7f;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Message out phase
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::MsgOut()
|
||||
{
|
||||
LOGTRACE("%s ID %d",__PRETTY_FUNCTION__, GetSCSIID());
|
||||
|
||||
// Phase change
|
||||
if (ctrl.phase != BUS::msgout) {
|
||||
LOGTRACE("Message Out Phase");
|
||||
|
||||
// process the IDENTIFY message
|
||||
if (ctrl.phase == BUS::selection) {
|
||||
scsi.atnmsg = true;
|
||||
scsi.msc = 0;
|
||||
memset(scsi.msb, 0x00, sizeof(scsi.msb));
|
||||
}
|
||||
|
||||
// Phase Setting
|
||||
ctrl.phase = BUS::msgout;
|
||||
|
||||
// Signal line operated by the target
|
||||
ctrl.bus->SetMSG(TRUE);
|
||||
ctrl.bus->SetCD(TRUE);
|
||||
ctrl.bus->SetIO(FALSE);
|
||||
|
||||
// Data transfer is 1 byte x 1 block
|
||||
ctrl.offset = 0;
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Receive();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Common Error Handling
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::Error(sense_key sense_key, asc asc, status status)
|
||||
{
|
||||
// Get bus information
|
||||
ctrl.bus->Aquire();
|
||||
|
||||
// Reset check
|
||||
if (ctrl.bus->GetRST()) {
|
||||
// Reset the controller
|
||||
Reset();
|
||||
|
||||
// Reset the bus
|
||||
ctrl.bus->Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
// Bus free for status phase and message in phase
|
||||
if (ctrl.phase == BUS::status || ctrl.phase == BUS::msgin) {
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
int lun = GetEffectiveLun();
|
||||
if (!ctrl.unit[lun] || asc == INVALID_LUN) {
|
||||
lun = 0;
|
||||
|
||||
assert(ctrl.unit[lun]);
|
||||
}
|
||||
|
||||
if (sense_key || asc) {
|
||||
// Set Sense Key and ASC for a subsequent REQUEST SENSE
|
||||
ctrl.unit[lun]->SetStatusCode((sense_key << 16) | (asc << 8));
|
||||
}
|
||||
|
||||
ctrl.status = status;
|
||||
ctrl.message = 0x00;
|
||||
|
||||
LOGTRACE("%s Error (to status phase)", __PRETTY_FUNCTION__);
|
||||
|
||||
Status();
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Send data
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::Send()
|
||||
{
|
||||
ASSERT(!ctrl.bus->GetREQ());
|
||||
ASSERT(ctrl.bus->GetIO());
|
||||
|
||||
if (ctrl.length != 0) {
|
||||
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Sending handhake with offset " + to_string(ctrl.offset) + ", length "
|
||||
+ to_string(ctrl.length)).c_str());
|
||||
|
||||
// TODO The delay has to be taken from ctrl.unit[lun], but as there are no Daynaport drivers for
|
||||
// LUNs other than 0 this work-around works.
|
||||
int len = ctrl.bus->SendHandShake(&ctrl.buffer[ctrl.offset], ctrl.length, ctrl.unit[0] ? ctrl.unit[0]->GetSendDelay() : 0);
|
||||
|
||||
// If you cannot send all, move to status phase
|
||||
if (len != (int)ctrl.length) {
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
// offset and length
|
||||
ctrl.offset += ctrl.length;
|
||||
ctrl.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Block subtraction, result initialization
|
||||
ctrl.blocks--;
|
||||
bool result = true;
|
||||
|
||||
// Processing after data collection (read/data-in only)
|
||||
if (ctrl.phase == BUS::datain) {
|
||||
if (ctrl.blocks != 0) {
|
||||
// set next buffer (set offset, length)
|
||||
result = XferIn(ctrl.buffer);
|
||||
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Processing after data collection. Blocks: " + to_string(ctrl.blocks)).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue sending if block !=0
|
||||
if (ctrl.blocks != 0){
|
||||
LOGTRACE("%s%s", __PRETTY_FUNCTION__, (" Continuing to send. Blocks: " + to_string(ctrl.blocks)).c_str());
|
||||
ASSERT(ctrl.length > 0);
|
||||
ASSERT(ctrl.offset == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next phase
|
||||
LOGTRACE("%s Move to next phase %s (%d)", __PRETTY_FUNCTION__, BUS::GetPhaseStrRaw(ctrl.phase), ctrl.phase);
|
||||
switch (ctrl.phase) {
|
||||
// Message in phase
|
||||
case BUS::msgin:
|
||||
// Completed sending response to extended message of IDENTIFY message
|
||||
if (scsi.atnmsg) {
|
||||
// flag off
|
||||
scsi.atnmsg = false;
|
||||
|
||||
// command phase
|
||||
Command();
|
||||
} else {
|
||||
// Bus free phase
|
||||
BusFree();
|
||||
}
|
||||
break;
|
||||
|
||||
// Data-in Phase
|
||||
case BUS::datain:
|
||||
// status phase
|
||||
Status();
|
||||
break;
|
||||
|
||||
// status phase
|
||||
case BUS::status:
|
||||
// Message in phase
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.buffer[0] = (BYTE)ctrl.message;
|
||||
MsgIn();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Receive Data
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
void SCSIDEV::Receive()
|
||||
{
|
||||
if (scsi.is_byte_transfer) {
|
||||
ReceiveBytes();
|
||||
return;
|
||||
}
|
||||
|
||||
int len;
|
||||
BYTE data;
|
||||
|
||||
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
||||
|
||||
// REQ is low
|
||||
ASSERT(!ctrl.bus->GetREQ());
|
||||
ASSERT(!ctrl.bus->GetIO());
|
||||
|
||||
// Length != 0 if received
|
||||
if (ctrl.length != 0) {
|
||||
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, (int)ctrl.length);
|
||||
// Receive
|
||||
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||
|
||||
// If not able to receive all, move to status phase
|
||||
if (len != (int)ctrl.length) {
|
||||
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",__PRETTY_FUNCTION__, (int)ctrl.length, len);
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
// Offset and Length
|
||||
ctrl.offset += ctrl.length;
|
||||
ctrl.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Block subtraction, result initialization
|
||||
ctrl.blocks--;
|
||||
bool result = true;
|
||||
|
||||
// Processing after receiving data (by phase)
|
||||
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
||||
switch (ctrl.phase) {
|
||||
|
||||
// Data out phase
|
||||
case BUS::dataout:
|
||||
if (ctrl.blocks == 0) {
|
||||
// End with this buffer
|
||||
result = XferOut(false);
|
||||
} else {
|
||||
// Continue to next buffer (set offset, length)
|
||||
result = XferOut(true);
|
||||
}
|
||||
break;
|
||||
|
||||
// Message out phase
|
||||
case BUS::msgout:
|
||||
ctrl.message = ctrl.buffer[0];
|
||||
if (!XferMsg(ctrl.message)) {
|
||||
// Immediately free the bus if message output fails
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear message data in preparation for message-in
|
||||
ctrl.message = 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
// Continue to receive if block !=0
|
||||
if (ctrl.blocks != 0){
|
||||
ASSERT(ctrl.length > 0);
|
||||
ASSERT(ctrl.offset == 0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next phase
|
||||
switch (ctrl.phase) {
|
||||
// Command phase
|
||||
case BUS::command:
|
||||
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
ctrl.cmd[i] = ctrl.buffer[i];
|
||||
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
||||
}
|
||||
|
||||
// Execution Phase
|
||||
Execute();
|
||||
break;
|
||||
|
||||
// Message out phase
|
||||
case BUS::msgout:
|
||||
// Continue message out phase as long as ATN keeps asserting
|
||||
if (ctrl.bus->GetATN()) {
|
||||
// Data transfer is 1 byte x 1 block
|
||||
ctrl.offset = 0;
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parsing messages sent by ATN
|
||||
if (scsi.atnmsg) {
|
||||
int i = 0;
|
||||
while (i < scsi.msc) {
|
||||
// Message type
|
||||
data = scsi.msb[i];
|
||||
|
||||
// ABORT
|
||||
if (data == 0x06) {
|
||||
LOGTRACE("Message code ABORT $%02X", data);
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// BUS DEVICE RESET
|
||||
if (data == 0x0C) {
|
||||
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
||||
scsi.syncoffset = 0;
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// IDENTIFY
|
||||
if (data >= 0x80) {
|
||||
ctrl.lun = data & 0x1F;
|
||||
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
||||
}
|
||||
|
||||
// Extended Message
|
||||
if (data == 0x01) {
|
||||
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
||||
|
||||
// Check only when synchronous transfer is possible
|
||||
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.buffer[0] = 0x07;
|
||||
MsgIn();
|
||||
return;
|
||||
}
|
||||
|
||||
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||
scsi.syncperiod = scsi.msb[i + 3];
|
||||
if (scsi.syncperiod > 50) {
|
||||
scsi.syncperiod = 50;
|
||||
}
|
||||
|
||||
// REQ/ACK offset(limited to 16)
|
||||
scsi.syncoffset = scsi.msb[i + 4];
|
||||
if (scsi.syncoffset > 16) {
|
||||
scsi.syncoffset = 16;
|
||||
}
|
||||
|
||||
// STDR response message generation
|
||||
ctrl.length = 5;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.buffer[0] = 0x01;
|
||||
ctrl.buffer[1] = 0x03;
|
||||
ctrl.buffer[2] = 0x01;
|
||||
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
||||
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
||||
MsgIn();
|
||||
return;
|
||||
}
|
||||
|
||||
// next
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize ATN message reception status
|
||||
scsi.atnmsg = false;
|
||||
|
||||
// Command phase
|
||||
Command();
|
||||
break;
|
||||
|
||||
// Data out phase
|
||||
case BUS::dataout:
|
||||
FlushUnit();
|
||||
|
||||
// status phase
|
||||
Status();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Transfer MSG
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool SCSIDEV::XferMsg(int msg)
|
||||
{
|
||||
ASSERT(ctrl.phase == BUS::msgout);
|
||||
|
||||
// Save message out data
|
||||
if (scsi.atnmsg) {
|
||||
scsi.msb[scsi.msc] = (BYTE)msg;
|
||||
scsi.msc++;
|
||||
scsi.msc %= 256;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SCSIDEV::ReceiveBytes()
|
||||
{
|
||||
uint32_t len;
|
||||
BYTE data;
|
||||
|
||||
LOGTRACE("%s",__PRETTY_FUNCTION__);
|
||||
|
||||
// REQ is low
|
||||
ASSERT(!ctrl.bus->GetREQ());
|
||||
ASSERT(!ctrl.bus->GetIO());
|
||||
|
||||
if (ctrl.length) {
|
||||
LOGTRACE("%s Length is %d bytes", __PRETTY_FUNCTION__, ctrl.length);
|
||||
|
||||
len = ctrl.bus->ReceiveHandShake(&ctrl.buffer[ctrl.offset], ctrl.length);
|
||||
|
||||
// If not able to receive all, move to status phase
|
||||
if (len != ctrl.length) {
|
||||
LOGERROR("%s Not able to receive %d bytes of data, only received %d. Going to error",
|
||||
__PRETTY_FUNCTION__, ctrl.length, len);
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
ctrl.offset += ctrl.length;
|
||||
scsi.bytes_to_transfer = ctrl.length;
|
||||
ctrl.length = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Result initialization
|
||||
bool result = true;
|
||||
|
||||
// Processing after receiving data (by phase)
|
||||
LOGTRACE("%s ctrl.phase: %d (%s)",__PRETTY_FUNCTION__, (int)ctrl.phase, BUS::GetPhaseStrRaw(ctrl.phase));
|
||||
switch (ctrl.phase) {
|
||||
|
||||
case BUS::dataout:
|
||||
result = XferOut(false);
|
||||
break;
|
||||
|
||||
case BUS::msgout:
|
||||
ctrl.message = ctrl.buffer[0];
|
||||
if (!XferMsg(ctrl.message)) {
|
||||
// Immediately free the bus if message output fails
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear message data in preparation for message-in
|
||||
ctrl.message = 0x00;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// If result FALSE, move to status phase
|
||||
if (!result) {
|
||||
Error();
|
||||
return;
|
||||
}
|
||||
|
||||
// Move to next phase
|
||||
switch (ctrl.phase) {
|
||||
case BUS::command:
|
||||
len = GPIOBUS::GetCommandByteCount(ctrl.buffer[0]);
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
ctrl.cmd[i] = ctrl.buffer[i];
|
||||
LOGTRACE("%s Command Byte %d: $%02X",__PRETTY_FUNCTION__, i, ctrl.cmd[i]);
|
||||
}
|
||||
|
||||
Execute();
|
||||
break;
|
||||
|
||||
case BUS::msgout:
|
||||
// Continue message out phase as long as ATN keeps asserting
|
||||
if (ctrl.bus->GetATN()) {
|
||||
// Data transfer is 1 byte x 1 block
|
||||
ctrl.offset = 0;
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
// Parsing messages sent by ATN
|
||||
if (scsi.atnmsg) {
|
||||
int i = 0;
|
||||
while (i < scsi.msc) {
|
||||
// Message type
|
||||
data = scsi.msb[i];
|
||||
|
||||
// ABORT
|
||||
if (data == 0x06) {
|
||||
LOGTRACE("Message code ABORT $%02X", data);
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// BUS DEVICE RESET
|
||||
if (data == 0x0C) {
|
||||
LOGTRACE("Message code BUS DEVICE RESET $%02X", data);
|
||||
scsi.syncoffset = 0;
|
||||
BusFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// IDENTIFY
|
||||
if (data >= 0x80) {
|
||||
ctrl.lun = data & 0x1F;
|
||||
LOGTRACE("Message code IDENTIFY $%02X, LUN %d selected", data, ctrl.lun);
|
||||
}
|
||||
|
||||
// Extended Message
|
||||
if (data == 0x01) {
|
||||
LOGTRACE("Message code EXTENDED MESSAGE $%02X", data);
|
||||
|
||||
// Check only when synchronous transfer is possible
|
||||
if (!scsi.syncenable || scsi.msb[i + 2] != 0x01) {
|
||||
ctrl.length = 1;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.buffer[0] = 0x07;
|
||||
MsgIn();
|
||||
return;
|
||||
}
|
||||
|
||||
// Transfer period factor (limited to 50 x 4 = 200ns)
|
||||
scsi.syncperiod = scsi.msb[i + 3];
|
||||
if (scsi.syncperiod > 50) {
|
||||
scsi.syncoffset = 50;
|
||||
}
|
||||
|
||||
// REQ/ACK offset(limited to 16)
|
||||
scsi.syncoffset = scsi.msb[i + 4];
|
||||
if (scsi.syncoffset > 16) {
|
||||
scsi.syncoffset = 16;
|
||||
}
|
||||
|
||||
// STDR response message generation
|
||||
ctrl.length = 5;
|
||||
ctrl.blocks = 1;
|
||||
ctrl.buffer[0] = 0x01;
|
||||
ctrl.buffer[1] = 0x03;
|
||||
ctrl.buffer[2] = 0x01;
|
||||
ctrl.buffer[3] = (BYTE)scsi.syncperiod;
|
||||
ctrl.buffer[4] = (BYTE)scsi.syncoffset;
|
||||
MsgIn();
|
||||
return;
|
||||
}
|
||||
|
||||
// next
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize ATN message reception status
|
||||
scsi.atnmsg = false;
|
||||
|
||||
Command();
|
||||
break;
|
||||
|
||||
case BUS::dataout:
|
||||
Status();
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool SCSIDEV::XferOut(bool cont)
|
||||
{
|
||||
if (!scsi.is_byte_transfer) {
|
||||
return super::XferOut(cont);
|
||||
}
|
||||
|
||||
ASSERT(ctrl.phase == BUS::dataout);
|
||||
|
||||
scsi.is_byte_transfer = false;
|
||||
|
||||
PrimaryDevice *device = dynamic_cast<PrimaryDevice *>(ctrl.unit[GetEffectiveLun()]);
|
||||
if (device && ctrl.cmd[0] == scsi_command::eCmdWrite6) {
|
||||
return device->WriteBytes(ctrl.buffer, scsi.bytes_to_transfer);
|
||||
}
|
||||
|
||||
LOGWARN("Received an unexpected command ($%02X) in %s", (WORD)ctrl.cmd[0] , __PRETTY_FUNCTION__)
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int SCSIDEV::GetEffectiveLun() const
|
||||
{
|
||||
return ctrl.lun != -1 ? ctrl.lun : (ctrl.cmd[1] >> 5) & 0x07;
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
// [ SCSI device controller ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#pragma once
|
||||
|
||||
#include "controllers/sasidev_ctrl.h"
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// SCSI Device (Interits SASI device)
|
||||
//
|
||||
//===========================================================================
|
||||
class SCSIDEV : public SASIDEV
|
||||
{
|
||||
public:
|
||||
|
||||
enum rascsi_shutdown_mode {
|
||||
NONE,
|
||||
STOP_RASCSI,
|
||||
STOP_PI,
|
||||
RESTART_PI
|
||||
};
|
||||
|
||||
// Internal data definition
|
||||
typedef struct {
|
||||
// Synchronous transfer
|
||||
BOOL syncenable; // Synchronous transfer possible
|
||||
int syncperiod; // Synchronous transfer period
|
||||
int syncoffset; // Synchronous transfer offset
|
||||
int syncack; // Number of synchronous transfer ACKs
|
||||
|
||||
// ATN message
|
||||
bool atnmsg;
|
||||
int msc;
|
||||
BYTE msb[256];
|
||||
|
||||
// -1 means that the initiator ID is unknown, e.g. with Atari ACSI and old host adapters
|
||||
int initiator_id;
|
||||
|
||||
bool is_byte_transfer;
|
||||
uint32_t bytes_to_transfer;
|
||||
} scsi_t;
|
||||
|
||||
SCSIDEV();
|
||||
~SCSIDEV();
|
||||
|
||||
void Reset() override;
|
||||
|
||||
BUS::phase_t Process(int) override;
|
||||
|
||||
void Receive() override;
|
||||
|
||||
// Get LUN based on IDENTIFY message, with LUN from the CDB as fallback
|
||||
int GetEffectiveLun() const;
|
||||
|
||||
bool IsSASI() const override { return false; }
|
||||
bool IsSCSI() const override { return true; }
|
||||
|
||||
// Common error handling
|
||||
void Error(scsi_defs::sense_key sense_key = scsi_defs::sense_key::NO_SENSE,
|
||||
scsi_defs::asc asc = scsi_defs::asc::NO_ADDITIONAL_SENSE_INFORMATION,
|
||||
scsi_defs::status status = scsi_defs::status::CHECK_CONDITION) override;
|
||||
|
||||
void ScheduleShutDown(rascsi_shutdown_mode shutdown_mode) { this->shutdown_mode = shutdown_mode; }
|
||||
|
||||
int GetInitiatorId() const { return scsi.initiator_id; }
|
||||
bool IsByteTransfer() const { return scsi.is_byte_transfer; }
|
||||
void SetByteTransfer(bool is_byte_transfer) { scsi.is_byte_transfer = is_byte_transfer; }
|
||||
|
||||
private:
|
||||
typedef SASIDEV super;
|
||||
|
||||
// Phases
|
||||
void BusFree() override;
|
||||
void Selection() override;
|
||||
void Execute() override;
|
||||
void MsgOut();
|
||||
|
||||
// Data transfer
|
||||
void Send() override;
|
||||
bool XferMsg(int);
|
||||
bool XferOut(bool);
|
||||
void ReceiveBytes();
|
||||
|
||||
// Internal data
|
||||
scsi_t scsi;
|
||||
|
||||
rascsi_shutdown_mode shutdown_mode;
|
||||
};
|
||||
|
126
src/raspberrypi/devices/cd_track.cpp
Normal file
126
src/raspberrypi/devices/cd_track.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// 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)
|
||||
{
|
||||
assert(!valid);
|
||||
assert(track >= 1);
|
||||
assert(first < last);
|
||||
|
||||
// Set and enable track number
|
||||
track_no = track;
|
||||
valid = true;
|
||||
|
||||
// Remember LBA
|
||||
first_lba = first;
|
||||
last_lba = last;
|
||||
}
|
||||
|
||||
void CDTrack::SetPath(bool cdda, const Filepath& path)
|
||||
{
|
||||
assert(valid);
|
||||
|
||||
// CD-DA or data
|
||||
audio = cdda;
|
||||
|
||||
// Remember the path
|
||||
imgpath = path;
|
||||
}
|
||||
|
||||
void CDTrack::GetPath(Filepath& path) const
|
||||
{
|
||||
assert(valid);
|
||||
|
||||
// Return the path (by reference)
|
||||
path = imgpath;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Gets the start of LBA
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
uint32_t CDTrack::GetFirst() const
|
||||
{
|
||||
assert(valid);
|
||||
assert(first_lba < last_lba);
|
||||
|
||||
return first_lba;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Get the end of LBA
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
uint32_t CDTrack::GetLast() const
|
||||
{
|
||||
assert(valid);
|
||||
assert(first_lba < last_lba);
|
||||
|
||||
return last_lba;
|
||||
}
|
||||
|
||||
uint32_t CDTrack::GetBlocks() const
|
||||
{
|
||||
assert(valid);
|
||||
assert(first_lba < last_lba);
|
||||
|
||||
// Calculate from start LBA and end LBA
|
||||
return last_lba - first_lba + 1;
|
||||
}
|
||||
|
||||
int CDTrack::GetTrackNo() const
|
||||
{
|
||||
assert(valid);
|
||||
assert(track_no >= 1);
|
||||
|
||||
return track_no;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Is valid block
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool CDTrack::IsValid(uint32_t lba) const
|
||||
{
|
||||
// false if the track itself is invalid
|
||||
if (!valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the block is BEFORE the first block
|
||||
if (lba < first_lba) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the block is AFTER the last block
|
||||
if (last_lba < lba) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This track is valid
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CDTrack::IsAudio() const
|
||||
{
|
||||
assert(valid);
|
||||
|
||||
return audio;
|
||||
}
|
45
src/raspberrypi/devices/cd_track.h
Normal file
45
src/raspberrypi/devices/cd_track.h
Normal file
@ -0,0 +1,45 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// Licensed under the BSD 3-Clause License.
|
||||
// See LICENSE file in the project root folder.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "filepath.h"
|
||||
|
||||
class CDTrack final
|
||||
{
|
||||
public:
|
||||
|
||||
CDTrack() = default;
|
||||
~CDTrack() = default;
|
||||
|
||||
void Init(int track, uint32_t first, uint32_t last);
|
||||
|
||||
// Properties
|
||||
void SetPath(bool cdda, const Filepath& path); // Set the path
|
||||
void GetPath(Filepath& path) const; // Get the path
|
||||
uint32_t GetFirst() const; // Get the start LBA
|
||||
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(uint32_t lba) const; // Is this a valid LBA?
|
||||
bool IsAudio() const; // Is this an audio track?
|
||||
|
||||
private:
|
||||
bool valid = false; // Valid track
|
||||
int track_no = -1; // Track number
|
||||
uint32_t first_lba = 0; // First LBA
|
||||
uint32_t last_lba = 0; // Last LBA
|
||||
bool audio = false; // Audio track flag
|
||||
Filepath imgpath; // Image file path
|
||||
};
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -7,63 +7,90 @@
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// [ TAP Driver ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <arpa/inet.h>
|
||||
#ifdef __linux__
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <linux/sockios.h>
|
||||
#endif
|
||||
#include "os.h"
|
||||
#include "ctapdriver.h"
|
||||
#include "log.h"
|
||||
#include "rasutil.h"
|
||||
#include "exceptions.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sstream>
|
||||
|
||||
#define BRIDGE_NAME "rascsi_bridge"
|
||||
#ifdef __linux__
|
||||
#include <sys/epoll.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/if_tun.h>
|
||||
#include <linux/sockios.h>
|
||||
#endif
|
||||
|
||||
using namespace std;
|
||||
using namespace ras_util;
|
||||
|
||||
CTapDriver::CTapDriver()
|
||||
{
|
||||
m_hTAP = -1;
|
||||
memset(&m_MacAddr, 0, sizeof(m_MacAddr));
|
||||
m_pcap = NULL;
|
||||
m_pcap_dumper = NULL;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Initialization
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
static bool br_setif(int br_socket_fd, const char* bridgename, const char* ifname, bool add) {
|
||||
struct ifreq ifr;
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
#else
|
||||
ifreq ifr;
|
||||
ifr.ifr_ifindex = if_nametoindex(ifname);
|
||||
if (ifr.ifr_ifindex == 0) {
|
||||
LOGERROR("Can't if_nametoindex: %s", strerror(errno));
|
||||
LOGERROR("Can't if_nametoindex %s: %s", ifname, strerror(errno))
|
||||
return false;
|
||||
}
|
||||
strncpy(ifr.ifr_name, bridgename, IFNAMSIZ);
|
||||
strncpy(ifr.ifr_name, bridgename, IFNAMSIZ - 1);
|
||||
if (ioctl(br_socket_fd, add ? SIOCBRADDIF : SIOCBRDELIF, &ifr) < 0) {
|
||||
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno));
|
||||
LOGERROR("Can't ioctl %s: %s", add ? "SIOCBRADDIF" : "SIOCBRDELIF", strerror(errno))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
CTapDriver::~CTapDriver()
|
||||
{
|
||||
if (m_hTAP != -1) {
|
||||
if (int br_socket_fd; (br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
LOGERROR("Can't open bridge socket: %s", strerror(errno))
|
||||
} else {
|
||||
LOGDEBUG("brctl delif %s ras0", BRIDGE_NAME)
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", false)) { //NOSONAR No exception is raised here
|
||||
LOGWARN("Warning: Removing ras0 from the bridge failed.")
|
||||
LOGWARN("You may need to manually remove the ras0 tap device from the bridge")
|
||||
}
|
||||
close(br_socket_fd);
|
||||
}
|
||||
|
||||
// Release TAP defice
|
||||
close(m_hTAP);
|
||||
}
|
||||
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_dump_close(m_pcap_dumper);
|
||||
}
|
||||
|
||||
if (m_pcap != nullptr) {
|
||||
pcap_close(m_pcap);
|
||||
}
|
||||
}
|
||||
|
||||
static bool ip_link(int fd, const char* ifname, bool up) {
|
||||
struct ifreq ifr;
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
#else
|
||||
ifreq ifr;
|
||||
strncpy(ifr.ifr_name, ifname, IFNAMSIZ-1); // Need to save room for null terminator
|
||||
int err = ioctl(fd, SIOCGIFFLAGS, &ifr);
|
||||
if (err) {
|
||||
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno));
|
||||
LOGERROR("Can't ioctl SIOCGIFFLAGS: %s", strerror(errno))
|
||||
return false;
|
||||
}
|
||||
ifr.ifr_flags &= ~IFF_UP;
|
||||
@ -72,13 +99,14 @@ static bool ip_link(int fd, const char* ifname, bool up) {
|
||||
}
|
||||
err = ioctl(fd, SIOCSIFFLAGS, &ifr);
|
||||
if (err) {
|
||||
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno));
|
||||
LOGERROR("Can't ioctl SIOCSIFFLAGS: %s", strerror(errno))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool is_interface_up(const string& interface) {
|
||||
static bool is_interface_up(string_view interface) {
|
||||
string file = "/sys/class/net/";
|
||||
file += interface;
|
||||
file += "/carrier";
|
||||
@ -98,66 +126,68 @@ static bool is_interface_up(const string& interface) {
|
||||
|
||||
bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
{
|
||||
#ifndef __linux__
|
||||
return false;
|
||||
#else
|
||||
unordered_map<string, string> params = const_params;
|
||||
if (params.count("interfaces")) {
|
||||
LOGWARN("You are using the deprecated 'interfaces' parameter. "
|
||||
"Provide the interface list and the IP address/netmask with the 'interface' and 'inet' parameters");
|
||||
"Provide the interface list and the IP address/netmask with the 'interface' and 'inet' parameters")
|
||||
|
||||
// TODO Remove the deprecated syntax in a future version
|
||||
const string& interfaces = params["interfaces"];
|
||||
size_t separatorPos = interfaces.find(':');
|
||||
const string& ifaces = params["interfaces"];
|
||||
size_t separatorPos = ifaces.find(':');
|
||||
if (separatorPos != string::npos) {
|
||||
params["interface"] = interfaces.substr(0, separatorPos);
|
||||
params["inet"] = interfaces.substr(separatorPos + 1);
|
||||
params["interface"] = ifaces.substr(0, separatorPos);
|
||||
params["inet"] = ifaces.substr(separatorPos + 1);
|
||||
}
|
||||
}
|
||||
|
||||
stringstream s(params["interface"]);
|
||||
string interface;
|
||||
while (getline(s, interface, ',')) {
|
||||
this->interfaces.push_back(interface);
|
||||
interfaces.push_back(interface);
|
||||
}
|
||||
this->inet = params["inet"];
|
||||
inet = params["inet"];
|
||||
|
||||
LOGTRACE("Opening Tap device");
|
||||
LOGTRACE("Opening Tap device")
|
||||
// TAP device initilization
|
||||
if ((m_hTAP = open("/dev/net/tun", O_RDWR)) < 0) {
|
||||
LOGERROR("Can't open tun: %s", strerror(errno));
|
||||
LOGERROR("Can't open tun: %s", strerror(errno))
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("Opened tap device %d",m_hTAP);
|
||||
|
||||
// IFF_NO_PI for no extra packet information
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
||||
char dev[IFNAMSIZ] = "ras0";
|
||||
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
|
||||
LOGTRACE("Opened tap device %d", m_hTAP)
|
||||
|
||||
LOGTRACE("Going to open %s", ifr.ifr_name);
|
||||
// IFF_NO_PI for no extra packet information
|
||||
ifreq ifr = {};
|
||||
ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
|
||||
string dev = "ras0";
|
||||
strncpy(ifr.ifr_name, dev.c_str(), IFNAMSIZ - 1);
|
||||
|
||||
LOGTRACE("Going to open %s", ifr.ifr_name)
|
||||
|
||||
int ret = ioctl(m_hTAP, TUNSETIFF, (void *)&ifr);
|
||||
if (ret < 0) {
|
||||
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno));
|
||||
LOGERROR("Can't ioctl TUNSETIFF: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("Return code from ioctl was %d", ret);
|
||||
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));
|
||||
LOGERROR("Can't open ip socket: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
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));
|
||||
LOGERROR("Can't open bridge socket: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -168,35 +198,35 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
string sys_file = "/sys/class/net/";
|
||||
sys_file += BRIDGE_NAME;
|
||||
if (access(sys_file.c_str(), F_OK)) {
|
||||
LOGINFO("%s is not yet available", BRIDGE_NAME);
|
||||
LOGINFO("%s is not yet available", BRIDGE_NAME)
|
||||
|
||||
LOGTRACE("Checking which interface is available for creating the bridge");
|
||||
LOGTRACE("Checking which interface is available for creating the bridge")
|
||||
|
||||
string bridge_interface;
|
||||
for (const string& interface : interfaces) {
|
||||
if (is_interface_up(interface)) {
|
||||
LOGTRACE("%s", string("Interface " + interface + " is up").c_str());
|
||||
for (const string& iface : interfaces) {
|
||||
if (is_interface_up(iface)) {
|
||||
LOGTRACE("%s", string("Interface " + iface + " is up").c_str())
|
||||
|
||||
bridge_interface = interface;
|
||||
bridge_interface = iface;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
LOGTRACE("%s", string("Interface " + interface + " is not available or is not up").c_str());
|
||||
LOGTRACE("%s", string("Interface " + iface + " is not available or is not up").c_str())
|
||||
}
|
||||
}
|
||||
|
||||
if (bridge_interface.empty()) {
|
||||
LOGERROR("No interface is up, not creating bridge");
|
||||
LOGERROR("No interface is up, not creating bridge")
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str());
|
||||
LOGINFO("Creating %s for interface %s", BRIDGE_NAME, bridge_interface.c_str())
|
||||
|
||||
if (bridge_interface == "eth0") {
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME);
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
|
||||
|
||||
if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno));
|
||||
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -204,7 +234,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str());
|
||||
LOGTRACE("brctl addif %s %s", BRIDGE_NAME, bridge_interface.c_str())
|
||||
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, bridge_interface.c_str(), true)) {
|
||||
close(m_hTAP);
|
||||
@ -215,14 +245,13 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
}
|
||||
else {
|
||||
string address = inet;
|
||||
string netmask = "255.255.255.0";
|
||||
size_t separatorPos = inet.find('/');
|
||||
if (separatorPos != string::npos) {
|
||||
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);
|
||||
|
||||
int m;
|
||||
if (!GetAsInt(inet.substr(separatorPos + 1), m) || m < 8 || m > 32) {
|
||||
LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str());
|
||||
LOGERROR("Invalid CIDR netmask notation '%s'", inet.substr(separatorPos + 1).c_str())
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -231,17 +260,16 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
}
|
||||
|
||||
// long long is required for compatibility with 32 bit platforms
|
||||
long long mask = pow(2, 32) - (1 << (32 - m));
|
||||
char buf[16];
|
||||
sprintf(buf, "%lld.%lld.%lld.%lld", (mask >> 24) & 0xff, (mask >> 16) & 0xff, (mask >> 8) & 0xff,
|
||||
mask & 0xff);
|
||||
netmask = buf;
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME);
|
||||
LOGTRACE("brctl addbr %s", BRIDGE_NAME)
|
||||
|
||||
if ((ret = ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME)) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno));
|
||||
if (ioctl(br_socket_fd, SIOCBRADDBR, BRIDGE_NAME) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCBRADDBR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -249,12 +277,12 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ifreq ifr_a;
|
||||
ifreq ifr_a;
|
||||
ifr_a.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_a.ifr_name, BRIDGE_NAME, IFNAMSIZ);
|
||||
struct sockaddr_in* addr = (struct sockaddr_in*)&ifr_a.ifr_addr;
|
||||
if (inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno));
|
||||
if (auto addr = (sockaddr_in*)&ifr_a.ifr_addr;
|
||||
inet_pton(AF_INET, address.c_str(), &addr->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a network address: %s", address.c_str(), strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -262,12 +290,12 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
return false;
|
||||
}
|
||||
|
||||
struct ifreq ifr_n;
|
||||
ifreq ifr_n;
|
||||
ifr_n.ifr_addr.sa_family = AF_INET;
|
||||
strncpy(ifr_n.ifr_name, BRIDGE_NAME, IFNAMSIZ);
|
||||
struct sockaddr_in* mask = (struct sockaddr_in*)&ifr_n.ifr_addr;
|
||||
if (inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno));
|
||||
if (auto mask = (sockaddr_in*)&ifr_n.ifr_addr;
|
||||
inet_pton(AF_INET, netmask.c_str(), &mask->sin_addr) != 1) {
|
||||
LOGERROR("Can't convert '%s' into a netmask: %s", netmask.c_str(), strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -275,10 +303,10 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME);
|
||||
LOGTRACE("ip address add %s dev %s", inet.c_str(), BRIDGE_NAME)
|
||||
|
||||
if (ioctl(ip_fd, SIOCSIFADDR, &ifr_a) < 0 || ioctl(ip_fd, SIOCSIFNETMASK, &ifr_n) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno));
|
||||
LOGERROR("Can't ioctl SIOCSIFADDR or SIOCSIFNETMASK: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
@ -287,7 +315,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
}
|
||||
}
|
||||
|
||||
LOGTRACE("ip link set dev %s up", BRIDGE_NAME);
|
||||
LOGTRACE("ip link set dev %s up", BRIDGE_NAME)
|
||||
|
||||
if (!ip_link(ip_fd, BRIDGE_NAME, true)) {
|
||||
close(m_hTAP);
|
||||
@ -298,10 +326,10 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGINFO("%s is already available", BRIDGE_NAME);
|
||||
LOGINFO("%s is already available", BRIDGE_NAME)
|
||||
}
|
||||
|
||||
LOGTRACE("ip link set ras0 up");
|
||||
LOGTRACE("ip link set ras0 up")
|
||||
|
||||
if (!ip_link(ip_fd, "ras0", true)) {
|
||||
close(m_hTAP);
|
||||
@ -310,7 +338,7 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGTRACE("brctl addif %s ras0", BRIDGE_NAME);
|
||||
LOGTRACE("brctl addif %s ras0", BRIDGE_NAME)
|
||||
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", true)) {
|
||||
close(m_hTAP);
|
||||
@ -320,105 +348,79 @@ bool CTapDriver::Init(const unordered_map<string, string>& const_params)
|
||||
}
|
||||
|
||||
// Get MAC address
|
||||
LOGTRACE("Getting the MAC address");
|
||||
LOGTRACE("Getting the MAC address")
|
||||
|
||||
ifr.ifr_addr.sa_family = AF_INET;
|
||||
if ((ret = ioctl(m_hTAP, SIOCGIFHWADDR, &ifr)) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno));
|
||||
if (ioctl(m_hTAP, SIOCGIFHWADDR, &ifr) < 0) {
|
||||
LOGERROR("Can't ioctl SIOCGIFHWADDR: %s", strerror(errno))
|
||||
|
||||
close(m_hTAP);
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
return false;
|
||||
}
|
||||
LOGTRACE("Got the MAC");
|
||||
LOGTRACE("Got the MAC")
|
||||
|
||||
// Save MAC address
|
||||
memcpy(m_MacAddr, ifr.ifr_hwaddr.sa_data, sizeof(m_MacAddr));
|
||||
memcpy(m_MacAddr.data(), ifr.ifr_hwaddr.sa_data, m_MacAddr.size());
|
||||
|
||||
close(ip_fd);
|
||||
close(br_socket_fd);
|
||||
|
||||
LOGINFO("Tap device %s created", ifr.ifr_name);
|
||||
LOGINFO("Tap device %s created", ifr.ifr_name)
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void CTapDriver::OpenDump(const Filepath& path) {
|
||||
if (m_pcap == NULL) {
|
||||
if (m_pcap == nullptr) {
|
||||
m_pcap = pcap_open_dead(DLT_EN10MB, 65535);
|
||||
}
|
||||
if (m_pcap_dumper != NULL) {
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_dump_close(m_pcap_dumper);
|
||||
}
|
||||
m_pcap_dumper = pcap_dump_open(m_pcap, path.GetPath());
|
||||
if (m_pcap_dumper == NULL) {
|
||||
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap));
|
||||
if (m_pcap_dumper == nullptr) {
|
||||
LOGERROR("Can't open pcap file: %s", pcap_geterr(m_pcap))
|
||||
throw io_exception("Can't open pcap file");
|
||||
}
|
||||
|
||||
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.GetPath());
|
||||
LOGTRACE("%s Opened %s for dumping", __PRETTY_FUNCTION__, path.GetPath())
|
||||
}
|
||||
|
||||
void CTapDriver::Cleanup()
|
||||
bool CTapDriver::Enable() const
|
||||
{
|
||||
int br_socket_fd = -1;
|
||||
if ((br_socket_fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
|
||||
LOGERROR("Can't open bridge socket: %s", strerror(errno));
|
||||
} else {
|
||||
LOGDEBUG("brctl delif %s ras0", BRIDGE_NAME);
|
||||
if (!br_setif(br_socket_fd, BRIDGE_NAME, "ras0", false)) {
|
||||
LOGWARN("Warning: Removing ras0 from the bridge failed.");
|
||||
LOGWARN("You may need to manually remove the ras0 tap device from the bridge");
|
||||
}
|
||||
close(br_socket_fd);
|
||||
}
|
||||
|
||||
// Release TAP defice
|
||||
if (m_hTAP != -1) {
|
||||
close(m_hTAP);
|
||||
m_hTAP = -1;
|
||||
}
|
||||
|
||||
if (m_pcap_dumper != NULL) {
|
||||
pcap_dump_close(m_pcap_dumper);
|
||||
m_pcap_dumper = NULL;
|
||||
}
|
||||
|
||||
if (m_pcap != NULL) {
|
||||
pcap_close(m_pcap);
|
||||
m_pcap = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool CTapDriver::Enable(){
|
||||
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 int fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
LOGDEBUG("%s: ip link set ras0 up", __PRETTY_FUNCTION__)
|
||||
const bool result = ip_link(fd, "ras0", true);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CTapDriver::Disable(){
|
||||
int fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
LOGDEBUG("%s: ip link set ras0 down", __PRETTY_FUNCTION__);
|
||||
bool result = ip_link(fd, "ras0", false);
|
||||
bool CTapDriver::Disable() const
|
||||
{
|
||||
const int fd = socket(PF_INET, SOCK_DGRAM, 0);
|
||||
LOGDEBUG("%s: ip link set ras0 down", __PRETTY_FUNCTION__)
|
||||
const bool result = ip_link(fd, "ras0", false);
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CTapDriver::Flush(){
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__);
|
||||
while(PendingPackets()){
|
||||
(void)Rx(m_garbage_buffer);
|
||||
void CTapDriver::Flush()
|
||||
{
|
||||
LOGTRACE("%s", __PRETTY_FUNCTION__)
|
||||
while (PendingPackets()) {
|
||||
array<BYTE, ETH_FRAME_LEN> m_garbage_buffer;
|
||||
(void)Receive(m_garbage_buffer.data());
|
||||
}
|
||||
}
|
||||
|
||||
void CTapDriver::GetMacAddr(BYTE *mac)
|
||||
void CTapDriver::GetMacAddr(BYTE *mac) const
|
||||
{
|
||||
ASSERT(mac);
|
||||
assert(mac);
|
||||
|
||||
memcpy(mac, m_MacAddr, sizeof(m_MacAddr));
|
||||
memcpy(mac, m_MacAddr.data(), m_MacAddr.size());
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
@ -426,46 +428,40 @@ void CTapDriver::GetMacAddr(BYTE *mac)
|
||||
// Receive
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool CTapDriver::PendingPackets()
|
||||
bool CTapDriver::PendingPackets() const
|
||||
{
|
||||
struct pollfd fds;
|
||||
|
||||
ASSERT(m_hTAP != -1);
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
// Check if there is data that can be received
|
||||
pollfd fds;
|
||||
fds.fd = m_hTAP;
|
||||
fds.events = POLLIN | POLLERR;
|
||||
fds.revents = 0;
|
||||
poll(&fds, 1, 0);
|
||||
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents);
|
||||
LOGTRACE("%s %u revents", __PRETTY_FUNCTION__, fds.revents)
|
||||
if (!(fds.revents & POLLIN)) {
|
||||
return false;
|
||||
}else {
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// See https://stackoverflow.com/questions/21001659/crc32-algorithm-implementation-in-c-without-a-look-up-table-and-with-a-public-li
|
||||
uint32_t crc32(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);
|
||||
}
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Receive
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int CTapDriver::Rx(BYTE *buf)
|
||||
int CTapDriver::Receive(BYTE *buf)
|
||||
{
|
||||
ASSERT(m_hTAP != -1);
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
// Check if there is data that can be received
|
||||
if (!PendingPackets()) {
|
||||
@ -473,9 +469,9 @@ int CTapDriver::Rx(BYTE *buf)
|
||||
}
|
||||
|
||||
// Receive
|
||||
DWORD dwReceived = read(m_hTAP, buf, ETH_FRAME_LEN);
|
||||
if (dwReceived == (DWORD)-1) {
|
||||
LOGWARN("%s Error occured while receiving an packet", __PRETTY_FUNCTION__);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -484,52 +480,49 @@ int CTapDriver::Rx(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);
|
||||
buf[dwReceived + 2] = (BYTE)((crc >> 16) & 0xFF);
|
||||
buf[dwReceived + 3] = (BYTE)((crc >> 24) & 0xFF);
|
||||
|
||||
LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3]);
|
||||
LOGDEBUG("%s CRC is %08X - %02X %02X %02X %02X\n", __PRETTY_FUNCTION__, crc, buf[dwReceived+0], buf[dwReceived+1], buf[dwReceived+2], buf[dwReceived+3])
|
||||
|
||||
// Add FCS size to the received message size
|
||||
dwReceived += 4;
|
||||
}
|
||||
|
||||
if (m_pcap_dumper != NULL) {
|
||||
struct pcap_pkthdr h = {
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_pkthdr h = {
|
||||
.ts = {},
|
||||
.caplen = dwReceived,
|
||||
.len = dwReceived,
|
||||
.len = dwReceived
|
||||
};
|
||||
gettimeofday(&h.ts, NULL);
|
||||
gettimeofday(&h.ts, nullptr);
|
||||
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1]);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)dwReceived, buf[0], buf[dwReceived-1])
|
||||
}
|
||||
|
||||
// Return the number of bytes
|
||||
return dwReceived;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Send
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
int CTapDriver::Tx(const BYTE *buf, int len)
|
||||
int CTapDriver::Send(const BYTE *buf, int len)
|
||||
{
|
||||
ASSERT(m_hTAP != -1);
|
||||
assert(m_hTAP != -1);
|
||||
|
||||
if (m_pcap_dumper != NULL) {
|
||||
struct pcap_pkthdr h = {
|
||||
if (m_pcap_dumper != nullptr) {
|
||||
pcap_pkthdr h = {
|
||||
.ts = {},
|
||||
.caplen = (bpf_u_int32)len,
|
||||
.len = (bpf_u_int32)len,
|
||||
};
|
||||
gettimeofday(&h.ts, NULL);
|
||||
gettimeofday(&h.ts, nullptr);
|
||||
pcap_dump((u_char*)m_pcap_dumper, &h, buf);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1]);
|
||||
LOGTRACE("%s Dumped %d byte packet (first byte: %02x last byte: %02x)", __PRETTY_FUNCTION__, (unsigned int)h.len, buf[0], buf[h.len-1])
|
||||
}
|
||||
|
||||
// Start sending
|
||||
return write(m_hTAP, buf, len);
|
||||
return (int)write(m_hTAP, buf, len);
|
||||
}
|
||||
|
@ -7,65 +7,53 @@
|
||||
// Copyright (C) 2016-2020 GIMONS
|
||||
// Copyright (C) akuker
|
||||
//
|
||||
// [ TAP Driver ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <pcap/pcap.h>
|
||||
#include <net/ethernet.h>
|
||||
#include "filepath.h"
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#ifndef ETH_FRAME_LEN
|
||||
#define ETH_FRAME_LEN 1514
|
||||
#endif
|
||||
#include <array>
|
||||
|
||||
using namespace std;
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
// Linux Tap Driver
|
||||
//
|
||||
//===========================================================================
|
||||
class CTapDriver
|
||||
{
|
||||
private:
|
||||
|
||||
friend class SCSIDaynaPort;
|
||||
friend class SCSIBR;
|
||||
|
||||
CTapDriver();
|
||||
~CTapDriver() {}
|
||||
|
||||
bool Init(const unordered_map<string, string>&);
|
||||
static constexpr const char *BRIDGE_NAME = "rascsi_bridge";
|
||||
|
||||
public:
|
||||
|
||||
void OpenDump(const Filepath& path);
|
||||
// Capture packets
|
||||
void Cleanup(); // Cleanup
|
||||
void GetMacAddr(BYTE *mac); // Get Mac Address
|
||||
int Rx(BYTE *buf); // Receive
|
||||
int Tx(const BYTE *buf, int len); // Send
|
||||
bool PendingPackets(); // Check if there are IP packets available
|
||||
bool Enable(); // Enable the ras0 interface
|
||||
bool Disable(); // Disable the ras0 interface
|
||||
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);
|
||||
int Send(const BYTE *buf, int len);
|
||||
bool PendingPackets() const; // Check if there are IP packets available
|
||||
bool Enable() const; // Enable the ras0 interface
|
||||
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:
|
||||
BYTE m_MacAddr[6]; // MAC Address
|
||||
int m_hTAP; // File handle
|
||||
array<byte, 6> m_MacAddr; // MAC Address
|
||||
|
||||
BYTE m_garbage_buffer[ETH_FRAME_LEN];
|
||||
int m_hTAP = -1; // File handle
|
||||
|
||||
pcap_t *m_pcap;
|
||||
pcap_dumper_t *m_pcap_dumper;
|
||||
pcap_t *m_pcap = nullptr;
|
||||
pcap_dumper_t *m_pcap_dumper = nullptr;
|
||||
|
||||
// Prioritized comma-separated list of interfaces to create the bridge for
|
||||
vector<string> interfaces;
|
||||
list<string> interfaces;
|
||||
|
||||
string inet;
|
||||
};
|
||||
|
@ -3,57 +3,26 @@
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021 Uwe Seimet
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include <cassert>
|
||||
#include "rascsi_version.h"
|
||||
#include "os.h"
|
||||
#include "log.h"
|
||||
#include "exceptions.h"
|
||||
#include "device.h"
|
||||
#include <cassert>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
|
||||
unordered_set<Device *> Device::devices;
|
||||
using namespace std;
|
||||
|
||||
Device::Device(const string& type)
|
||||
Device::Device(const string& type, int lun) : type(type), lun(lun)
|
||||
{
|
||||
assert(type.length() == 4);
|
||||
|
||||
devices.insert(this);
|
||||
|
||||
this->type = type;
|
||||
|
||||
vendor = DEFAULT_VENDOR;
|
||||
char rev[5];
|
||||
sprintf(rev, "%02d%02d", rascsi_major_version, rascsi_minor_version);
|
||||
revision = rev;
|
||||
|
||||
ready = false;
|
||||
reset = false;
|
||||
attn = false;
|
||||
supported_luns = 32;
|
||||
protectable = false;
|
||||
write_protected = false;
|
||||
read_only = false;
|
||||
stoppable = false;
|
||||
stopped = false;
|
||||
removable = false;
|
||||
removed = false;
|
||||
lockable = false;
|
||||
locked = false;
|
||||
block_size_configurable = false;
|
||||
supports_params = false;
|
||||
|
||||
id = 0;
|
||||
lun = 0;
|
||||
|
||||
status_code = STATUS_NOERROR;
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
devices.erase(this);
|
||||
ostringstream os;
|
||||
os << setw(2) << setfill('0') << rascsi_major_version << setw(2) << setfill('0') << rascsi_minor_version;
|
||||
revision = os.str();
|
||||
}
|
||||
|
||||
void Device::Reset()
|
||||
@ -63,88 +32,77 @@ void Device::Reset()
|
||||
reset = false;
|
||||
}
|
||||
|
||||
void Device::SetProtected(bool write_protected)
|
||||
void Device::SetProtected(bool b)
|
||||
{
|
||||
if (!read_only) {
|
||||
this->write_protected = write_protected;
|
||||
write_protected = b;
|
||||
}
|
||||
}
|
||||
|
||||
void Device::SetVendor(const string& vendor)
|
||||
void Device::SetVendor(const string& v)
|
||||
{
|
||||
if (vendor.empty() || vendor.length() > 8) {
|
||||
throw illegal_argument_exception("Vendor '" + vendor + "' must be between 1 and 8 characters");
|
||||
if (v.empty() || v.length() > 8) {
|
||||
throw invalid_argument("Vendor '" + v + "' must be between 1 and 8 characters");
|
||||
}
|
||||
|
||||
this->vendor = vendor;
|
||||
vendor = v;
|
||||
}
|
||||
|
||||
void Device::SetProduct(const string& product, bool force)
|
||||
void Device::SetProduct(const string& p, bool force)
|
||||
{
|
||||
// Changing the device name is not SCSI compliant
|
||||
if (!this->product.empty() && !force) {
|
||||
if (p.empty() || p.length() > 16) {
|
||||
throw invalid_argument("Product '" + p + "' must be between 1 and 16 characters");
|
||||
}
|
||||
|
||||
// Changing vital product data is not SCSI compliant
|
||||
if (!product.empty() && !force) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (product.empty() || product.length() > 16) {
|
||||
throw illegal_argument_exception("Product '" + product + "' must be between 1 and 16 characters");
|
||||
}
|
||||
|
||||
this->product = product;
|
||||
product = p;
|
||||
}
|
||||
|
||||
void Device::SetRevision(const string& revision)
|
||||
void Device::SetRevision(const string& r)
|
||||
{
|
||||
if (revision.empty() || revision.length() > 4) {
|
||||
throw illegal_argument_exception("Revision '" + revision + "' must be between 1 and 4 characters");
|
||||
if (r.empty() || r.length() > 4) {
|
||||
throw invalid_argument("Revision '" + r + "' must be between 1 and 4 characters");
|
||||
}
|
||||
|
||||
this->revision = revision;
|
||||
revision = r;
|
||||
}
|
||||
|
||||
const string Device::GetPaddedName() const
|
||||
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;
|
||||
}
|
||||
|
||||
const string Device::GetParam(const string& key)
|
||||
string Device::GetParam(const string& key) const
|
||||
{
|
||||
return params.find(key) != params.end() ? params[key] : "";
|
||||
const auto& it = params.find(key);
|
||||
return it == params.end() ? "" : it->second;
|
||||
}
|
||||
|
||||
void Device::SetParams(const unordered_map<string, string>& params)
|
||||
void Device::SetParams(const unordered_map<string, string>& set_params)
|
||||
{
|
||||
this->params = GetDefaultParams();
|
||||
params = default_params;
|
||||
|
||||
for (const auto& param : params) {
|
||||
for (const auto& [key, value] : set_params) {
|
||||
// It is assumed that there are default parameters for all supported parameters
|
||||
if (this->params.find(param.first) != this->params.end()) {
|
||||
this->params[param.first] = param.second;
|
||||
if (params.find(key) != params.end()) {
|
||||
params[key] = value;
|
||||
}
|
||||
else {
|
||||
LOGWARN("%s", string("Ignored unknown parameter '" + param.first + "'").c_str());
|
||||
LOGWARN("%s", string("Ignored unknown parameter '" + key + "'").c_str())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Device::SetStatusCode(int status_code)
|
||||
{
|
||||
if (status_code) {
|
||||
LOGDEBUG("Error status: Sense Key $%02X, ASC $%02X, ASCQ $%02X", status_code >> 16, (status_code >> 8 &0xff), status_code & 0xff);
|
||||
}
|
||||
|
||||
this->status_code = status_code;
|
||||
}
|
||||
|
||||
bool Device::Start()
|
||||
{
|
||||
if (!ready) {
|
||||
@ -161,6 +119,8 @@ void Device::Stop()
|
||||
ready = false;
|
||||
attn = false;
|
||||
stopped = true;
|
||||
|
||||
status_code = 0;
|
||||
}
|
||||
|
||||
bool Device::Eject(bool force)
|
||||
|
@ -3,96 +3,53 @@
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021 Uwe Seimet
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define DEFAULT_VENDOR "RaSCSI"
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Error definition (sense code returned by REQUEST SENSE)
|
||||
//
|
||||
// MSB Reserved (0x00)
|
||||
// Sense Key
|
||||
// Additional Sense Code (ASC)
|
||||
// LSB Additional Sense Code Qualifier(ASCQ)
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
#define STATUS_NOERROR 0x00000000 // NO ADDITIONAL SENSE INFO.
|
||||
#define STATUS_DEVRESET 0x00062900 // POWER ON OR RESET OCCURED
|
||||
#define STATUS_NOTREADY 0x00023a00 // MEDIUM NOT PRESENT
|
||||
#define STATUS_ATTENTION 0x00062800 // MEDIUM MAY HAVE CHANGED
|
||||
#define STATUS_PREVENT 0x00045302 // MEDIUM REMOVAL PREVENTED
|
||||
#define STATUS_READFAULT 0x00031100 // UNRECOVERED READ ERROR
|
||||
#define STATUS_WRITEFAULT 0x00030300 // PERIPHERAL DEVICE WRITE FAULT
|
||||
#define STATUS_WRITEPROTECT 0x00042700 // WRITE PROTECTED
|
||||
#define STATUS_MISCOMPARE 0x000e1d00 // MISCOMPARE DURING VERIFY
|
||||
#define STATUS_INVALIDCMD 0x00052000 // INVALID COMMAND OPERATION CODE
|
||||
#define STATUS_INVALIDLBA 0x00052100 // LOGICAL BLOCK ADDR. OUT OF RANGE
|
||||
#define STATUS_INVALIDCDB 0x00052400 // INVALID FIELD IN CDB
|
||||
#define STATUS_INVALIDLUN 0x00052500 // LOGICAL UNIT NOT SUPPORTED
|
||||
#define STATUS_INVALIDPRM 0x00052600 // INVALID FIELD IN PARAMETER LIST
|
||||
#define STATUS_INVALIDMSG 0x00054900 // INVALID MESSAGE ERROR
|
||||
#define STATUS_PARAMLEN 0x00051a00 // PARAMETERS LIST LENGTH ERROR
|
||||
#define STATUS_PARAMNOT 0x00052601 // PARAMETERS NOT SUPPORTED
|
||||
#define STATUS_PARAMVALUE 0x00052602 // PARAMETERS VALUE INVALID
|
||||
#define STATUS_PARAMSAVE 0x00053900 // SAVING PARAMETERS NOT SUPPORTED
|
||||
#define STATUS_NODEFECT 0x00010000 // DEFECT LIST NOT FOUND
|
||||
|
||||
class SCSIDEV;
|
||||
|
||||
class Device
|
||||
class Device //NOSONAR The number of fields and methods is justified, the complexity is low
|
||||
{
|
||||
private:
|
||||
const string DEFAULT_VENDOR = "RaSCSI";
|
||||
|
||||
string type;
|
||||
|
||||
bool ready;
|
||||
bool reset;
|
||||
bool attn;
|
||||
|
||||
// Number of supported luns
|
||||
int supported_luns;
|
||||
bool ready = false;
|
||||
bool reset = false;
|
||||
bool attn = false;
|
||||
|
||||
// Device is protectable/write-protected
|
||||
bool protectable;
|
||||
bool write_protected;
|
||||
bool protectable = false;
|
||||
bool write_protected = false;
|
||||
// Device is permanently read-only
|
||||
bool read_only;
|
||||
bool read_only = false;
|
||||
|
||||
// Device can be stopped (parked)/is stopped (parked)
|
||||
bool stoppable;
|
||||
bool stopped;
|
||||
bool stoppable = false;
|
||||
bool stopped = false;
|
||||
|
||||
// Device is removable/removed
|
||||
bool removable;
|
||||
bool removed;
|
||||
bool removable = false;
|
||||
bool removed = false;
|
||||
|
||||
// Device is lockable/locked
|
||||
bool lockable;
|
||||
bool locked;
|
||||
|
||||
// The block size is configurable
|
||||
bool block_size_configurable;
|
||||
bool lockable = false;
|
||||
bool locked = false;
|
||||
|
||||
// Device can be created with parameters
|
||||
bool supports_params;
|
||||
bool supports_params = false;
|
||||
|
||||
// Device ID and LUN
|
||||
int32_t id;
|
||||
int32_t lun;
|
||||
// Immutable LUN
|
||||
int lun;
|
||||
|
||||
// Device identifier (for INQUIRY)
|
||||
string vendor;
|
||||
string vendor = DEFAULT_VENDOR;
|
||||
string product;
|
||||
string revision;
|
||||
|
||||
@ -103,85 +60,77 @@ private:
|
||||
unordered_map<string, string> default_params;
|
||||
|
||||
// Sense Key, ASC and ASCQ
|
||||
int status_code;
|
||||
// MSB Reserved (0x00)
|
||||
// Sense Key
|
||||
// Additional Sense Code (ASC)
|
||||
// LSB Additional Sense Code Qualifier(ASCQ)
|
||||
int status_code = 0;
|
||||
|
||||
protected:
|
||||
|
||||
static unordered_set<Device *> devices;
|
||||
void SetReady(bool b) { ready = b; }
|
||||
bool IsReset() const { return reset; }
|
||||
void SetReset(bool b) { reset = b; }
|
||||
bool IsAttn() const { return attn; }
|
||||
void SetAttn(bool b) { attn = b; }
|
||||
|
||||
int GetStatusCode() const { return status_code; }
|
||||
|
||||
string GetParam(const string&) const;
|
||||
void SetParams(const unordered_map<string, string>&);
|
||||
|
||||
Device(const string&, int);
|
||||
|
||||
public:
|
||||
|
||||
Device(const string&);
|
||||
virtual ~Device();
|
||||
|
||||
// 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 bool Dispatch(SCSIDEV *) = 0;
|
||||
virtual ~Device() = default;
|
||||
|
||||
const string& GetType() const { return type; }
|
||||
|
||||
bool IsReady() const { return ready; }
|
||||
void SetReady(bool ready) { this->ready = ready; }
|
||||
bool IsReset() const { return reset; }
|
||||
void SetReset(bool reset) { this->reset = reset; }
|
||||
void Reset();
|
||||
bool IsAttn() const { return attn; }
|
||||
void SetAttn(bool attn) { this->attn = attn; }
|
||||
|
||||
int GetSupportedLuns() const { return supported_luns; }
|
||||
void SetSupportedLuns(int supported_luns) { this->supported_luns = supported_luns; }
|
||||
virtual void Reset();
|
||||
|
||||
bool IsProtectable() const { return protectable; }
|
||||
void SetProtectable(bool protectable) { this->protectable = protectable; }
|
||||
void SetProtectable(bool b) { protectable = b; }
|
||||
bool IsProtected() const { return write_protected; }
|
||||
void SetProtected(bool);
|
||||
bool IsReadOnly() const { return read_only; }
|
||||
void SetReadOnly(bool read_only) { this->read_only = read_only; }
|
||||
void SetReadOnly(bool b) { read_only = b; }
|
||||
|
||||
bool IsStoppable() const { return stoppable; }
|
||||
void SetStoppable(bool stoppable) { this->stoppable = stoppable; }
|
||||
void SetStoppable(bool b) { stoppable = b; }
|
||||
bool IsStopped() const { return stopped; }
|
||||
void SetStopped(bool stopped) { this->stopped = stopped; }
|
||||
void SetStopped(bool b) { stopped = b; }
|
||||
bool IsRemovable() const { return removable; }
|
||||
void SetRemovable(bool removable) { this->removable = removable; }
|
||||
void SetRemovable(bool b) { removable = b; }
|
||||
bool IsRemoved() const { return removed; }
|
||||
void SetRemoved(bool removed) { this->removed = removed; }
|
||||
void SetRemoved(bool b) { removed = b; }
|
||||
|
||||
bool IsLockable() const { return lockable; }
|
||||
void SetLockable(bool lockable) { this->lockable = lockable; }
|
||||
void SetLockable(bool b) { lockable = b; }
|
||||
bool IsLocked() const { return locked; }
|
||||
void SetLocked(bool locked) { this->locked = locked; }
|
||||
void SetLocked(bool b) { locked = b; }
|
||||
|
||||
int32_t GetId() const { return id; }
|
||||
void SetId(int32_t id) { this->id = id; }
|
||||
int32_t GetLun() const { return lun; }
|
||||
void SetLun(int32_t lun) { this->lun = lun; }
|
||||
virtual int GetId() const = 0;
|
||||
int GetLun() const { return lun; }
|
||||
|
||||
const string GetVendor() const { return vendor; }
|
||||
string GetVendor() const { return vendor; }
|
||||
void SetVendor(const string&);
|
||||
const string GetProduct() const { return product; }
|
||||
string GetProduct() const { return product; }
|
||||
void SetProduct(const string&, bool = true);
|
||||
const string GetRevision() const { return revision; }
|
||||
string GetRevision() const { return revision; }
|
||||
void SetRevision(const string&);
|
||||
const string GetPaddedName() const;
|
||||
string GetPaddedName() const;
|
||||
|
||||
bool SupportsParams() const { return supports_params; }
|
||||
virtual bool SupportsFile() const { return !supports_params; }
|
||||
void SupportsParams(bool supports_paams) { this->supports_params = supports_paams; }
|
||||
const unordered_map<string, string> GetParams() const { return params; }
|
||||
const string GetParam(const string&);
|
||||
void SetParams(const unordered_map<string, string>&);
|
||||
const unordered_map<string, string> GetDefaultParams() const { return default_params; }
|
||||
void SetDefaultParams(const unordered_map<string, string>& default_params) { this->default_params = default_params; }
|
||||
void SupportsParams(bool b) { supports_params = b; }
|
||||
unordered_map<string, string> GetParams() const { return params; }
|
||||
void SetDefaultParams(const unordered_map<string, string>& p) { default_params = p; }
|
||||
|
||||
int GetStatusCode() const { return status_code; }
|
||||
void SetStatusCode(int);
|
||||
void SetStatusCode(int s) { status_code = s; }
|
||||
|
||||
bool Start();
|
||||
void Stop();
|
||||
virtual bool Eject(bool);
|
||||
|
||||
bool IsSASIHD() const { return type == "SAHD"; }
|
||||
bool IsSCSIHD() const { return type == "SCHD" || type == "SCRM"; }
|
||||
};
|
||||
|
@ -3,11 +3,10 @@
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021 Uwe Seimet
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "sasihd.h"
|
||||
#include "scsihd.h"
|
||||
#include "scsihd_nec.h"
|
||||
#include "scsimo.h"
|
||||
@ -15,31 +14,24 @@
|
||||
#include "scsi_printer.h"
|
||||
#include "scsi_host_bridge.h"
|
||||
#include "scsi_daynaport.h"
|
||||
#include "exceptions.h"
|
||||
#include "rascsi_exceptions.h"
|
||||
#include "host_services.h"
|
||||
#include "device_factory.h"
|
||||
#include <ifaddrs.h>
|
||||
#include "host_services.h"
|
||||
#include <sys/ioctl.h>
|
||||
#include <netinet/in.h>
|
||||
#include <net/if.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace rascsi_interface;
|
||||
|
||||
DeviceFactory::DeviceFactory()
|
||||
{
|
||||
sector_sizes[SAHD] = { 256, 512, 1024 };
|
||||
sector_sizes[SCHD] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCRM] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCMO] = { 512, 1024, 2048, 4096 };
|
||||
sector_sizes[SCCD] = { 512, 2048};
|
||||
|
||||
// 128 MB, 512 bytes per sector, 248826 sectors
|
||||
geometries[SCMO][0x797f400] = make_pair(512, 248826);
|
||||
// 230 MB, 512 bytes per block, 446325 sectors
|
||||
geometries[SCMO][0xd9eea00] = make_pair(512, 446325);
|
||||
// 540 MB, 512 bytes per sector, 1041500 sectors
|
||||
geometries[SCMO][0x1fc8b800] = make_pair(512, 1041500);
|
||||
// 640 MB, 20248 bytes per sector, 310352 sectors
|
||||
geometries[SCMO][0x25e28000] = make_pair(2048, 310352);
|
||||
|
||||
string network_interfaces;
|
||||
for (const auto& network_interface : GetNetworkInterfaces()) {
|
||||
if (!network_interfaces.empty()) {
|
||||
@ -49,13 +41,13 @@ 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";
|
||||
|
||||
extension_mapping["hdf"] = SAHD;
|
||||
extension_mapping["hd1"] = SCHD;
|
||||
extension_mapping["hds"] = SCHD;
|
||||
extension_mapping["hda"] = SCHD;
|
||||
extension_mapping["hdn"] = SCHD;
|
||||
@ -64,19 +56,17 @@ DeviceFactory::DeviceFactory()
|
||||
extension_mapping["hdr"] = SCRM;
|
||||
extension_mapping["mos"] = SCMO;
|
||||
extension_mapping["iso"] = SCCD;
|
||||
}
|
||||
|
||||
DeviceFactory& DeviceFactory::instance()
|
||||
{
|
||||
static DeviceFactory instance;
|
||||
return instance;
|
||||
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;
|
||||
size_t separator = filename.rfind('.');
|
||||
if (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); });
|
||||
@ -86,187 +76,164 @@ string DeviceFactory::GetExtension(const string& filename) const
|
||||
|
||||
PbDeviceType DeviceFactory::GetTypeForFile(const string& filename) const
|
||||
{
|
||||
string ext = GetExtension(filename);
|
||||
|
||||
const auto& it = extension_mapping.find(ext);
|
||||
if (it != extension_mapping.end()) {
|
||||
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;
|
||||
}
|
||||
|
||||
Device *DeviceFactory::CreateDevice(PbDeviceType type, const string& filename)
|
||||
// ID -1 is used by rascsi to create a temporary device
|
||||
shared_ptr<PrimaryDevice> DeviceFactory::CreateDevice(const ControllerManager& controller_manager, PbDeviceType type,
|
||||
int lun, const string& filename)
|
||||
{
|
||||
// If no type was specified try to derive the device type from the filename
|
||||
if (type == UNDEFINED) {
|
||||
type = GetTypeForFile(filename);
|
||||
if (type == UNDEFINED) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Device *device = NULL;
|
||||
try {
|
||||
switch (type) {
|
||||
case SAHD:
|
||||
device = new SASIHD(sector_sizes[SAHD]);
|
||||
device->SetSupportedLuns(2);
|
||||
device->SetProduct("SASI HD");
|
||||
break;
|
||||
shared_ptr<PrimaryDevice> device;
|
||||
switch (type) {
|
||||
case SCHD: {
|
||||
if (const string ext = GetExtension(filename); ext == "hdn" || ext == "hdi" || ext == "nhd") {
|
||||
device = make_shared<SCSIHD_NEC>(lun);
|
||||
} else {
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes[SCHD], false,
|
||||
ext == "hd1" ? scsi_level::SCSI_1_CCS : scsi_level::SCSI_2);
|
||||
|
||||
case SCHD: {
|
||||
string ext = GetExtension(filename);
|
||||
if (ext == "hdn" || ext == "hdi" || ext == "nhd") {
|
||||
device = new SCSIHD_NEC({ 512 });
|
||||
} else {
|
||||
device = new SCSIHD(sector_sizes[SCHD], false);
|
||||
|
||||
// Some Apple tools require a particular drive identification
|
||||
if (ext == "hda") {
|
||||
device->SetVendor("QUANTUM");
|
||||
device->SetProduct("FIREBALL");
|
||||
}
|
||||
}
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
break;
|
||||
// Some Apple tools require a particular drive identification
|
||||
if (ext == "hda") {
|
||||
device->SetVendor("QUANTUM");
|
||||
device->SetProduct("FIREBALL");
|
||||
}
|
||||
|
||||
case SCRM:
|
||||
device = new SCSIHD(sector_sizes[SCRM], true);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI HD (REM.)");
|
||||
break;
|
||||
|
||||
case SCMO:
|
||||
device = new SCSIMO(sector_sizes[SCMO], geometries[SCMO]);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI MO");
|
||||
break;
|
||||
|
||||
case SCCD:
|
||||
device = new SCSICD(sector_sizes[SCCD]);
|
||||
device->SetReadOnly(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI CD-ROM");
|
||||
break;
|
||||
|
||||
case SCBR:
|
||||
device = new SCSIBR();
|
||||
device->SetProduct("SCSI HOST BRIDGE");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCBR]);
|
||||
break;
|
||||
|
||||
case SCDP:
|
||||
device = new SCSIDaynaPort();
|
||||
// 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");
|
||||
device->SetRevision("1.4a");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCDP]);
|
||||
break;
|
||||
|
||||
case SCHS:
|
||||
device = new HostServices();
|
||||
// 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 = new SCSIPrinter();
|
||||
device->SetProduct("SCSI PRINTER");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCLP]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
break;
|
||||
}
|
||||
catch(const illegal_argument_exception& e) {
|
||||
// There was an internal problem with setting up the device data for INQUIRY
|
||||
return NULL;
|
||||
|
||||
case SCRM:
|
||||
device = make_shared<SCSIHD>(lun, sector_sizes[SCRM], true);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI HD (REM.)");
|
||||
break;
|
||||
|
||||
case SCMO:
|
||||
device = make_shared<SCSIMO>(lun, sector_sizes[SCMO]);
|
||||
device->SetProtectable(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI MO");
|
||||
break;
|
||||
|
||||
case SCCD:
|
||||
device = make_shared<SCSICD>(lun, sector_sizes[SCCD]);
|
||||
device->SetReadOnly(true);
|
||||
device->SetStoppable(true);
|
||||
device->SetRemovable(true);
|
||||
device->SetLockable(true);
|
||||
device->SetProduct("SCSI CD-ROM");
|
||||
break;
|
||||
|
||||
case SCBR:
|
||||
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_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");
|
||||
device->SetRevision("1.4a");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCDP]);
|
||||
break;
|
||||
|
||||
case SCHS:
|
||||
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_shared<SCSIPrinter>(lun);
|
||||
device->SetProduct("SCSI PRINTER");
|
||||
device->SupportsParams(true);
|
||||
device->SetDefaultParams(default_params[SCLP]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(const string& type)
|
||||
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(PbDeviceType type) const
|
||||
{
|
||||
const auto& it = sector_sizes.find(type);
|
||||
return it != sector_sizes.end() ? it->second : empty_set;
|
||||
}
|
||||
|
||||
const unordered_set<uint32_t>& DeviceFactory::GetSectorSizes(const string& type) const
|
||||
{
|
||||
PbDeviceType t = UNDEFINED;
|
||||
PbDeviceType_Parse(type, &t);
|
||||
return sector_sizes[t];
|
||||
|
||||
return GetSectorSizes(t);
|
||||
}
|
||||
|
||||
const unordered_set<uint64_t> DeviceFactory::GetCapacities(PbDeviceType type) const
|
||||
const unordered_map<string, string>& DeviceFactory::GetDefaultParams(PbDeviceType type) const
|
||||
{
|
||||
unordered_set<uint64_t> keys;
|
||||
|
||||
for (auto it = geometries.begin(); it != geometries.end(); ++it) {
|
||||
keys.insert(it->first);
|
||||
}
|
||||
|
||||
return keys;
|
||||
const auto& it = default_params.find(type);
|
||||
return it != default_params.end() ? it->second : empty_map;
|
||||
}
|
||||
|
||||
const list<string> DeviceFactory::GetNetworkInterfaces() const
|
||||
list<string> DeviceFactory::GetNetworkInterfaces() const
|
||||
{
|
||||
list<string> network_interfaces;
|
||||
|
||||
struct ifaddrs *addrs;
|
||||
#ifdef __linux__
|
||||
ifaddrs *addrs;
|
||||
getifaddrs(&addrs);
|
||||
struct ifaddrs *tmp = addrs;
|
||||
ifaddrs *tmp = addrs;
|
||||
|
||||
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);
|
||||
|
||||
struct ifreq ifr;
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
|
||||
strcpy(ifr.ifr_name, tmp->ifa_name);
|
||||
if (!ioctl(fd, SIOCGIFFLAGS, &ifr)) {
|
||||
close(fd);
|
||||
|
||||
// Only list interfaces that are up
|
||||
if (ifr.ifr_flags & IFF_UP) {
|
||||
network_interfaces.push_back(tmp->ifa_name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
close(fd);
|
||||
ifreq ifr = {};
|
||||
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);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
tmp = tmp->ifa_next;
|
||||
}
|
||||
|
||||
freeifaddrs(addrs);
|
||||
#endif
|
||||
|
||||
return network_interfaces;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
// SCSI Target Emulator RaSCSI Reloaded
|
||||
// for Raspberry Pi
|
||||
//
|
||||
// Copyright (C) 2021 Uwe Seimet
|
||||
// Copyright (C) 2021-2022 Uwe Seimet
|
||||
//
|
||||
// The DeviceFactory singleton creates devices based on their type and the image file extension
|
||||
//
|
||||
@ -11,47 +11,47 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <list>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include "rascsi_interface.pb.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace rascsi_interface;
|
||||
|
||||
typedef pair<uint32_t, uint32_t> Geometry;
|
||||
|
||||
class Device;
|
||||
class ControllerManager;
|
||||
class PrimaryDevice;
|
||||
|
||||
class DeviceFactory
|
||||
{
|
||||
DeviceFactory();
|
||||
~DeviceFactory() {}
|
||||
const string DEFAULT_IP = "10.10.20.1/24"; //NOSONAR This hardcoded IP address is safe
|
||||
|
||||
public:
|
||||
|
||||
static DeviceFactory& instance();
|
||||
DeviceFactory();
|
||||
~DeviceFactory() = default;
|
||||
|
||||
Device *CreateDevice(PbDeviceType, const string&);
|
||||
shared_ptr<PrimaryDevice> CreateDevice(const ControllerManager&, PbDeviceType, int, const string&);
|
||||
PbDeviceType GetTypeForFile(const string&) const;
|
||||
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) { return sector_sizes[type]; }
|
||||
const unordered_set<uint32_t>& GetSectorSizes(const string&);
|
||||
const unordered_set<uint64_t> GetCapacities(PbDeviceType) const;
|
||||
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) { return default_params[type]; }
|
||||
const list<string> GetNetworkInterfaces() const;
|
||||
const unordered_map<string, PbDeviceType> GetExtensionMapping() const { return extension_mapping; }
|
||||
const unordered_set<uint32_t>& GetSectorSizes(PbDeviceType type) const;
|
||||
const unordered_set<uint32_t>& GetSectorSizes(const string&) const;
|
||||
const unordered_map<string, string>& GetDefaultParams(PbDeviceType type) const;
|
||||
list<string> GetNetworkInterfaces() const;
|
||||
const unordered_map<string, PbDeviceType>& GetExtensionMapping() const { return extension_mapping; }
|
||||
|
||||
private:
|
||||
|
||||
unordered_map<PbDeviceType, unordered_set<uint32_t>> sector_sizes;
|
||||
string GetExtension(const string&) const;
|
||||
|
||||
// Optional mapping of drive capacities to drive geometries
|
||||
unordered_map<PbDeviceType, unordered_map<uint64_t, Geometry>> geometries;
|
||||
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;
|
||||
unordered_map<string, PbDeviceType> device_mapping;
|
||||
|
||||
unordered_set<uint32_t> empty_set;
|
||||
unordered_map<string, string> empty_map;
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -11,152 +11,141 @@
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
// [ Disk ]
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "log.h"
|
||||
#include "scsi.h"
|
||||
#include "controllers/scsidev_ctrl.h"
|
||||
#include "device.h"
|
||||
#include "device_factory.h"
|
||||
#include "disk_track_cache.h"
|
||||
#include "file_support.h"
|
||||
#include "disk_track.h"
|
||||
#include "disk_cache.h"
|
||||
#include "filepath.h"
|
||||
#include "interfaces/scsi_block_commands.h"
|
||||
#include "mode_page_device.h"
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class Disk : public ModePageDevice, ScsiBlockCommands
|
||||
using id_set = pair<int, int>;
|
||||
|
||||
class Disk : public ModePageDevice, private ScsiBlockCommands
|
||||
{
|
||||
private:
|
||||
enum access_mode { RW6, RW10, RW16, SEEK6, SEEK10 };
|
||||
|
||||
// The supported configurable block sizes, empty if not configurable
|
||||
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;
|
||||
uint32_t configured_sector_size = 0;
|
||||
|
||||
// The mapping of supported capacities to block sizes and block counts, empty if there is no capacity restriction
|
||||
unordered_map<uint64_t, Geometry> geometries;
|
||||
// Sector size shift count (9=512, 10=1024, 11=2048, 12=4096)
|
||||
uint32_t size_shift_count = 0;
|
||||
|
||||
typedef struct {
|
||||
uint32_t size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
|
||||
// TODO blocks should be a 64 bit value in order to support higher capacities
|
||||
uint32_t blocks; // Total number of sectors
|
||||
DiskCache *dcache; // Disk cache
|
||||
off_t image_offset; // Offset to actual data
|
||||
bool is_medium_changed;
|
||||
} disk_t;
|
||||
// Total number of sectors
|
||||
uint64_t blocks = 0;
|
||||
|
||||
Dispatcher<Disk, SASIDEV> dispatcher;
|
||||
bool is_medium_changed = false;
|
||||
|
||||
Filepath diskpath;
|
||||
|
||||
// The list of image files in use and the IDs and LUNs using these files
|
||||
static unordered_map<string, id_set> reserved_files;
|
||||
|
||||
public:
|
||||
Disk(const string&);
|
||||
virtual ~Disk();
|
||||
|
||||
virtual bool Dispatch(SCSIDEV *) override;
|
||||
Disk(const string&, int);
|
||||
~Disk() override;
|
||||
|
||||
bool Dispatch(scsi_command) override;
|
||||
|
||||
void MediumChanged();
|
||||
void ReserveFile(const string&);
|
||||
|
||||
// Media Operations
|
||||
virtual void Open(const Filepath& path);
|
||||
void GetPath(Filepath& path) const;
|
||||
bool Eject(bool) override;
|
||||
|
||||
private:
|
||||
friend class SASIDEV;
|
||||
|
||||
typedef ModePageDevice super;
|
||||
|
||||
// Commands covered by the SCSI specification (see https://www.t10.org/drafts.htm)
|
||||
void StartStopUnit(SASIDEV *);
|
||||
void SendDiagnostic(SASIDEV *);
|
||||
void PreventAllowMediumRemoval(SASIDEV *);
|
||||
void SynchronizeCache10(SASIDEV *);
|
||||
void SynchronizeCache16(SASIDEV *);
|
||||
void ReadDefectData10(SASIDEV *);
|
||||
virtual void Read6(SASIDEV *);
|
||||
void Read10(SASIDEV *) override;
|
||||
void Read16(SASIDEV *) override;
|
||||
virtual void Write6(SASIDEV *);
|
||||
void Write10(SASIDEV *) override;
|
||||
void Write16(SASIDEV *) override;
|
||||
void ReadLong10(SASIDEV *);
|
||||
void ReadLong16(SASIDEV *);
|
||||
void WriteLong10(SASIDEV *);
|
||||
void WriteLong16(SASIDEV *);
|
||||
void Verify10(SASIDEV *);
|
||||
void Verify16(SASIDEV *);
|
||||
void Seek(SASIDEV *);
|
||||
void Seek10(SASIDEV *);
|
||||
virtual void ReadCapacity10(SASIDEV *) override;
|
||||
void ReadCapacity16(SASIDEV *) override;
|
||||
void Reserve(SASIDEV *);
|
||||
void Release(SASIDEV *);
|
||||
|
||||
public:
|
||||
|
||||
// Commands covered by the SCSI specification (see https://www.t10.org/drafts.htm)
|
||||
void Rezero(SASIDEV *);
|
||||
void FormatUnit(SASIDEV *) override;
|
||||
void ReassignBlocks(SASIDEV *);
|
||||
void Seek6(SASIDEV *);
|
||||
|
||||
// Command helpers
|
||||
virtual int WriteCheck(DWORD block);
|
||||
virtual bool Write(const DWORD *cdb, const BYTE *buf, DWORD block);
|
||||
bool StartStop(const DWORD *cdb);
|
||||
bool SendDiag(const DWORD *cdb) const;
|
||||
virtual int WriteCheck(uint64_t);
|
||||
virtual void Write(const vector<int>&, const vector<BYTE>&, uint64_t);
|
||||
|
||||
virtual int Read(const DWORD *cdb, BYTE *buf, uint64_t block);
|
||||
virtual int Read(const vector<int>&, vector<BYTE>& , uint64_t);
|
||||
|
||||
uint32_t GetSectorSizeInBytes() const;
|
||||
void SetSectorSizeInBytes(uint32_t, bool);
|
||||
uint32_t GetSectorSizeShiftCount() const;
|
||||
void SetSectorSizeShiftCount(uint32_t);
|
||||
bool IsSectorSizeConfigurable() const;
|
||||
unordered_set<uint32_t> GetSectorSizes() const;
|
||||
void SetSectorSizes(const unordered_set<uint32_t>&);
|
||||
uint32_t GetConfiguredSectorSize() const;
|
||||
bool SetConfiguredSectorSize(uint32_t);
|
||||
void SetGeometries(const unordered_map<uint64_t, Geometry>&);
|
||||
bool SetGeometryForCapacity(uint64_t);
|
||||
uint64_t GetBlockCount() const;
|
||||
void SetBlockCount(uint32_t);
|
||||
void FlushCache();
|
||||
bool IsSectorSizeConfigurable() const { return !sector_sizes.empty(); }
|
||||
bool SetConfiguredSectorSize(const DeviceFactory&, uint32_t);
|
||||
uint64_t GetBlockCount() const { return blocks; }
|
||||
void FlushCache() override;
|
||||
|
||||
virtual void Open(const Filepath&);
|
||||
void GetPath(Filepath& path) const { path = diskpath; }
|
||||
|
||||
void ReserveFile(const Filepath&, int, int) const;
|
||||
void UnreserveFile() const;
|
||||
static void UnreserveAll();
|
||||
bool FileExists(const Filepath&);
|
||||
|
||||
static unordered_map<string, id_set> GetReservedFiles() { return reserved_files; }
|
||||
static void SetReservedFiles(const unordered_map<string, id_set>& files_in_use) { reserved_files = files_in_use; }
|
||||
static bool GetIdsForReservedFile(const Filepath&, int&, int&);
|
||||
|
||||
private:
|
||||
|
||||
using super = ModePageDevice;
|
||||
|
||||
// Commands covered by the SCSI specifications (see https://www.t10.org/drafts.htm)
|
||||
void StartStopUnit();
|
||||
void SendDiagnostic();
|
||||
void PreventAllowMediumRemoval();
|
||||
void SynchronizeCache();
|
||||
void ReadDefectData10();
|
||||
virtual void Read6();
|
||||
void Read10() override;
|
||||
void Read16() override;
|
||||
virtual void Write6();
|
||||
void Write10() override;
|
||||
void Write16() override;
|
||||
void Verify10();
|
||||
void Verify16();
|
||||
void Seek();
|
||||
void Seek10();
|
||||
void ReadCapacity10() override;
|
||||
void ReadCapacity16() override;
|
||||
void Reserve();
|
||||
void Release();
|
||||
void Rezero();
|
||||
void FormatUnit() override;
|
||||
void ReassignBlocks();
|
||||
void Seek6();
|
||||
void Read(access_mode);
|
||||
void Write(access_mode);
|
||||
void Verify(access_mode);
|
||||
void ReadWriteLong10();
|
||||
void ReadWriteLong16();
|
||||
void ReadCapacity16_ReadLong16();
|
||||
|
||||
void ValidateBlockAddress(access_mode) const;
|
||||
bool CheckAndGetStartAndCount(uint64_t&, uint32_t&, access_mode) const;
|
||||
|
||||
int ModeSense6(const vector<int>&, vector<BYTE>&) const override;
|
||||
int ModeSense10(const vector<int>&, vector<BYTE>&) const override;
|
||||
|
||||
protected:
|
||||
|
||||
int ModeSense6(const DWORD *cdb, BYTE *buf);
|
||||
int ModeSense10(const DWORD *cdb, BYTE *buf, int);
|
||||
virtual void SetDeviceParameters(BYTE *);
|
||||
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
|
||||
virtual void AddErrorPage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddFormatPage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddDrivePage(map<int, vector<BYTE>>&, bool) const;
|
||||
void AddCachePage(map<int, vector<BYTE>>&, bool) const;
|
||||
virtual void AddVendorPage(map<int, vector<BYTE>>&, int, bool) const;
|
||||
void SetUpCache(const Filepath&, off_t, bool = false);
|
||||
void ResizeCache(const Filepath&, bool);
|
||||
|
||||
// Internal disk data
|
||||
disk_t disk;
|
||||
|
||||
private:
|
||||
|
||||
void Read(SASIDEV *, uint64_t);
|
||||
void Write(SASIDEV *, uint64_t);
|
||||
void Verify(SASIDEV *, uint64_t);
|
||||
void ReadWriteLong10(SASIDEV *);
|
||||
void ReadWriteLong16(SASIDEV *);
|
||||
void ReadCapacity16_ReadLong16(SASIDEV *);
|
||||
bool Format(const DWORD *cdb);
|
||||
|
||||
bool ValidateBlockAddress(SASIDEV *, access_mode);
|
||||
bool GetStartAndCount(SASIDEV *, uint64_t&, uint32_t&, access_mode);
|
||||
void SetUpModePages(map<int, vector<byte>>&, int, bool) const override;
|
||||
virtual void AddErrorPage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddFormatPage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddDrivePage(map<int, vector<byte>>&, bool) const;
|
||||
void AddCachePage(map<int, vector<byte>>&, bool) const;
|
||||
virtual void AddVendorPage(map<int, vector<byte>>&, int, bool) const;
|
||||
unordered_set<uint32_t> GetSectorSizes() const;
|
||||
void SetSectorSizes(const unordered_set<uint32_t>& sizes) { sector_sizes = sizes; }
|
||||
void SetSectorSizeInBytes(uint32_t);
|
||||
uint32_t GetSectorSizeShiftCount() const { return size_shift_count; }
|
||||
void SetSectorSizeShiftCount(uint32_t count) { size_shift_count = count; }
|
||||
uint32_t GetConfiguredSectorSize() const;
|
||||
void SetBlockCount(uint64_t b) { blocks = b; }
|
||||
void SetPath(const Filepath& path) { diskpath = path; }
|
||||
};
|
||||
|
199
src/raspberrypi/devices/disk_cache.cpp
Normal file
199
src/raspberrypi/devices/disk_cache.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
//
|
||||
// XM6i
|
||||
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||
// Copyright (C) 2010 Y.Sugahara
|
||||
//
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#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)
|
||||
{
|
||||
assert(blocks > 0);
|
||||
assert(imgoff >= 0);
|
||||
|
||||
sec_path = path;
|
||||
}
|
||||
|
||||
bool DiskCache::Save() const
|
||||
{
|
||||
// Save track
|
||||
for (const cache_t& c : cache) {
|
||||
// Save if this is a valid track
|
||||
if (c.disktrk && !c.disktrk->Save(sec_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
shared_ptr<DiskTrack> DiskCache::GetTrack(uint32_t block)
|
||||
{
|
||||
// Update first
|
||||
UpdateSerialNumber();
|
||||
|
||||
// Calculate track (fixed to 256 sectors/track)
|
||||
int track = block >> 8;
|
||||
|
||||
// Get track data
|
||||
return Assign(track);
|
||||
}
|
||||
|
||||
bool DiskCache::ReadSector(vector<BYTE>& buf, uint32_t block)
|
||||
{
|
||||
shared_ptr<DiskTrack> disktrk = GetTrack(block);
|
||||
if (disktrk == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the track data to the cache
|
||||
return disktrk->ReadSector(buf, block & 0xff);
|
||||
}
|
||||
|
||||
bool DiskCache::WriteSector(const vector<BYTE>& buf, uint32_t block)
|
||||
{
|
||||
shared_ptr<DiskTrack> disktrk = GetTrack(block);
|
||||
if (disktrk == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write the data to the cache
|
||||
return disktrk->WriteSector(buf, block & 0xff);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Track Assignment
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
shared_ptr<DiskTrack> DiskCache::Assign(int track)
|
||||
{
|
||||
assert(sec_size != 0);
|
||||
assert(track >= 0);
|
||||
|
||||
// First, check if it is already assigned
|
||||
for (cache_t& c : cache) {
|
||||
if (c.disktrk && c.disktrk->GetTrack() == track) {
|
||||
// Track match
|
||||
c.serial = serial;
|
||||
return c.disktrk;
|
||||
}
|
||||
}
|
||||
|
||||
// Next, check for empty
|
||||
for (size_t i = 0; i < cache.size(); i++) {
|
||||
if (cache[i].disktrk == nullptr) {
|
||||
// Try loading
|
||||
if (Load((int)i, track, nullptr)) {
|
||||
// Success loading
|
||||
cache[i].serial = serial;
|
||||
return cache[i].disktrk;
|
||||
}
|
||||
|
||||
// Load failed
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, find the youngest serial number and delete it
|
||||
|
||||
// Set index 0 as candidate c
|
||||
uint32_t s = cache[0].serial;
|
||||
size_t c = 0;
|
||||
|
||||
// Compare candidate with serial and update to smaller one
|
||||
for (size_t i = 0; i < cache.size(); i++) {
|
||||
assert(cache[i].disktrk);
|
||||
|
||||
// Compare and update the existing serial
|
||||
if (cache[i].serial < s) {
|
||||
s = cache[i].serial;
|
||||
c = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Save this track
|
||||
if (!cache[c].disktrk->Save(sec_path)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Delete this track
|
||||
shared_ptr<DiskTrack> disktrk = cache[c].disktrk;
|
||||
cache[c].disktrk.reset();
|
||||
|
||||
if (Load((int)c, track, disktrk)) {
|
||||
// Successful loading
|
||||
cache[c].serial = serial;
|
||||
return cache[c].disktrk;
|
||||
}
|
||||
|
||||
// Load failed
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Load cache
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
bool DiskCache::Load(int index, int track, shared_ptr<DiskTrack> disktrk)
|
||||
{
|
||||
assert(index >= 0 && index < (int)cache.size());
|
||||
assert(track >= 0);
|
||||
assert(cache[index].disktrk == nullptr);
|
||||
|
||||
// Get the number of sectors on this track
|
||||
int sectors = sec_blocks - (track << 8);
|
||||
assert(sectors > 0);
|
||||
if (sectors > 0x100) {
|
||||
sectors = 0x100;
|
||||
}
|
||||
|
||||
// Create a disk track
|
||||
if (disktrk == nullptr) {
|
||||
disktrk = make_shared<DiskTrack>();
|
||||
}
|
||||
|
||||
// Initialize disk track
|
||||
disktrk->Init(track, sec_size, sectors, cd_raw, imgoffset);
|
||||
|
||||
// Try loading
|
||||
if (!disktrk->Load(sec_path)) {
|
||||
// Failure
|
||||
return false;
|
||||
}
|
||||
|
||||
// Allocation successful, work set
|
||||
cache[index].disktrk = disktrk;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DiskCache::UpdateSerialNumber()
|
||||
{
|
||||
// Update and do nothing except 0
|
||||
serial++;
|
||||
if (serial != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear serial of all caches
|
||||
for (cache_t& c : cache) {
|
||||
c.serial = 0;
|
||||
}
|
||||
}
|
||||
|
64
src/raspberrypi/devices/disk_cache.h
Normal file
64
src/raspberrypi/devices/disk_cache.h
Normal file
@ -0,0 +1,64 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
//
|
||||
// XM6i
|
||||
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||
//
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "filepath.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
using namespace std;
|
||||
|
||||
class DiskCache
|
||||
{
|
||||
// Number of tracks to cache
|
||||
static const int CACHE_MAX = 16;
|
||||
|
||||
public:
|
||||
|
||||
// Internal data definition
|
||||
using cache_t = struct {
|
||||
shared_ptr<DiskTrack> disktrk; // Disk Track
|
||||
uint32_t serial; // Serial
|
||||
};
|
||||
|
||||
DiskCache(const Filepath& path, int size, uint32_t blocks, off_t imgoff = 0);
|
||||
~DiskCache() = default;
|
||||
|
||||
void SetRawMode(bool b) { cd_raw = b; } // CD-ROM raw mode setting
|
||||
|
||||
// Access
|
||||
bool Save() const; // Save and release all
|
||||
bool ReadSector(vector<BYTE>&, uint32_t); // Sector Read
|
||||
bool WriteSector(const vector<BYTE>&, uint32_t); // Sector Write
|
||||
|
||||
private:
|
||||
|
||||
// Internal Management
|
||||
shared_ptr<DiskTrack> Assign(int);
|
||||
shared_ptr<DiskTrack> GetTrack(uint32_t);
|
||||
bool Load(int index, int track, shared_ptr<DiskTrack>);
|
||||
void UpdateSerialNumber();
|
||||
|
||||
// Internal data
|
||||
array<cache_t, CACHE_MAX> cache = {}; // Cache management
|
||||
uint32_t serial = 0; // Last serial number
|
||||
Filepath sec_path; // Path
|
||||
int sec_size; // Sector Size (8=256, 9=512, 10=1024, 11=2048, 12=4096)
|
||||
int sec_blocks; // Blocks per sector
|
||||
bool cd_raw = false; // CD-ROM RAW mode
|
||||
off_t imgoffset; // Offset to actual data
|
||||
};
|
||||
|
286
src/raspberrypi/devices/disk_track.cpp
Normal file
286
src/raspberrypi/devices/disk_track.cpp
Normal file
@ -0,0 +1,286 @@
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// X68000 EMULATOR "XM6"
|
||||
//
|
||||
// Copyright (C) 2001-2006 PI.(ytanaka@ipc-tokai.or.jp)
|
||||
// Copyright (C) 2014-2020 GIMONS
|
||||
//
|
||||
// XM6i
|
||||
// Copyright (C) 2010-2015 isaki@NetBSD.org
|
||||
// Copyright (C) 2010 Y.Sugahara
|
||||
//
|
||||
// Imported sava's Anex86/T98Next image and MO format support patch.
|
||||
// Comments translated to english by akuker.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
#include "log.h"
|
||||
#include "fileio.h"
|
||||
#include "disk_track.h"
|
||||
|
||||
DiskTrack::~DiskTrack()
|
||||
{
|
||||
// Release memory, but do not save automatically
|
||||
free(dt.buffer);
|
||||
}
|
||||
|
||||
void DiskTrack::Init(int track, int size, int sectors, bool raw, off_t imgoff)
|
||||
{
|
||||
assert(track >= 0);
|
||||
assert((sectors > 0) && (sectors <= 0x100));
|
||||
assert(imgoff >= 0);
|
||||
|
||||
// Set Parameters
|
||||
dt.track = track;
|
||||
dt.size = size;
|
||||
dt.sectors = sectors;
|
||||
dt.raw = raw;
|
||||
|
||||
// Not initialized (needs to be loaded)
|
||||
dt.init = false;
|
||||
|
||||
// Not Changed
|
||||
dt.changed = false;
|
||||
|
||||
// Offset to actual data
|
||||
dt.imgoffset = imgoff;
|
||||
}
|
||||
|
||||
bool DiskTrack::Load(const Filepath& path)
|
||||
{
|
||||
// Not needed if already loaded
|
||||
if (dt.init) {
|
||||
assert(dt.buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||
off_t offset = ((off_t)dt.track << 8);
|
||||
if (dt.raw) {
|
||||
assert(dt.size == 11);
|
||||
offset *= 0x930;
|
||||
offset += 0x10;
|
||||
} else {
|
||||
offset <<= dt.size;
|
||||
}
|
||||
|
||||
// Add offset to real image
|
||||
offset += dt.imgoffset;
|
||||
|
||||
// Calculate length (data size of this track)
|
||||
const int length = dt.sectors << dt.size;
|
||||
|
||||
// Allocate buffer memory
|
||||
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||
|
||||
if (dt.buffer == nullptr) {
|
||||
if (posix_memalign((void **)&dt.buffer, 512, ((length + 511) / 512) * 512)) {
|
||||
LOGWARN("%s posix_memalign failed", __PRETTY_FUNCTION__)
|
||||
}
|
||||
dt.length = length;
|
||||
}
|
||||
|
||||
if (dt.buffer == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reallocate if the buffer length is different
|
||||
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__)
|
||||
}
|
||||
dt.length = length;
|
||||
}
|
||||
|
||||
// Resize and clear changemap
|
||||
dt.changemap.resize(dt.sectors);
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false);
|
||||
|
||||
// Read from File
|
||||
Fileio fio;
|
||||
if (!fio.OpenDIO(path, Fileio::OpenMode::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dt.raw) {
|
||||
// Split Reading
|
||||
for (int i = 0; i < dt.sectors; i++) {
|
||||
// Seek
|
||||
if (!fio.Seek(offset)) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read
|
||||
if (!fio.Read(&dt.buffer[i << dt.size], 1 << dt.size)) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Next offset
|
||||
offset += 0x930;
|
||||
}
|
||||
} else {
|
||||
// Continuous reading
|
||||
if (!fio.Seek(offset)) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
if (!fio.Read(dt.buffer, length)) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
fio.Close();
|
||||
|
||||
// Set a flag and end normally
|
||||
dt.init = true;
|
||||
dt.changed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskTrack::Save(const Filepath& path)
|
||||
{
|
||||
// Not needed if not initialized
|
||||
if (!dt.init) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Not needed unless changed
|
||||
if (!dt.changed) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Need to write
|
||||
assert(dt.buffer);
|
||||
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||
|
||||
// Writing in RAW mode is not allowed
|
||||
assert(!dt.raw);
|
||||
|
||||
// Calculate offset (previous tracks are considered to hold 256 sectors)
|
||||
off_t offset = ((off_t)dt.track << 8);
|
||||
offset <<= dt.size;
|
||||
|
||||
// Add offset to real image
|
||||
offset += dt.imgoffset;
|
||||
|
||||
// Calculate length per sector
|
||||
const int length = 1 << dt.size;
|
||||
|
||||
// Open file
|
||||
Fileio fio;
|
||||
if (!fio.Open(path, Fileio::OpenMode::ReadWrite)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Partial write loop
|
||||
int total;
|
||||
for (int i = 0; i < dt.sectors;) {
|
||||
// If changed
|
||||
if (dt.changemap[i]) {
|
||||
// Initialize write size
|
||||
total = 0;
|
||||
|
||||
// Seek
|
||||
if (!fio.Seek(offset + ((off_t)i << dt.size))) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consectutive sector length
|
||||
int j;
|
||||
for (j = i; j < dt.sectors; j++) {
|
||||
// end when interrupted
|
||||
if (!dt.changemap[j]) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Add one sector
|
||||
total += length;
|
||||
}
|
||||
|
||||
// Write
|
||||
if (!fio.Write(&dt.buffer[i << dt.size], total)) {
|
||||
fio.Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// To unmodified sector
|
||||
i = j;
|
||||
} else {
|
||||
// Next Sector
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
fio.Close();
|
||||
|
||||
// Drop the change flag and exit
|
||||
fill(dt.changemap.begin(), dt.changemap.end(), false);
|
||||
dt.changed = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskTrack::ReadSector(vector<BYTE>& buf, int sec) const
|
||||
{
|
||||
assert(sec >= 0 && sec < 0x100);
|
||||
|
||||
LOGTRACE("%s reading sector: %d", __PRETTY_FUNCTION__,sec)
|
||||
|
||||
// Error if not initialized
|
||||
if (!dt.init) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// // Error if the number of sectors exceeds the valid number
|
||||
if (sec >= dt.sectors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Copy
|
||||
assert(dt.buffer);
|
||||
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||
memcpy(buf.data(), &dt.buffer[(off_t)sec << dt.size], (off_t)1 << dt.size);
|
||||
|
||||
// Success
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DiskTrack::WriteSector(const vector<BYTE>& buf, int sec)
|
||||
{
|
||||
assert((sec >= 0) && (sec < 0x100));
|
||||
assert(!dt.raw);
|
||||
|
||||
// Error if not initialized
|
||||
if (!dt.init) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// // Error if the number of sectors exceeds the valid number
|
||||
if (sec >= dt.sectors) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate offset and length
|
||||
const int offset = sec << dt.size;
|
||||
const int length = 1 << dt.size;
|
||||
|
||||
// Compare
|
||||
assert(dt.buffer);
|
||||
assert((dt.sectors > 0) && (dt.sectors <= 0x100));
|
||||
if (memcmp(buf.data(), &dt.buffer[offset], length) == 0) {
|
||||
// Exit normally since it's attempting to write the same thing
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy, change
|
||||
memcpy(&dt.buffer[offset], buf.data(), length);
|
||||
dt.changemap[sec] = true;
|
||||
dt.changed = true;
|
||||
|
||||
// Success
|
||||
return true;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user