Merge tag 'v22.08.01'

RaSCSI version 22.08.01
This commit is contained in:
Tony Kuker 2022-08-28 22:23:55 +01:00
commit 9d10347b21
100 changed files with 1452 additions and 491 deletions

2
.github/CODEOWNERS vendored
View File

@ -1 +1 @@
* @akuker @erichelgeson * @akuker @erichelgeson @rdmark

View File

@ -9,14 +9,18 @@ RaSCSI Reloaded is using the <a href="https://datasift.github.io/gitflow/Introdu
- The *master* branch should always reflect the contents of the last stable release - The *master* branch should always reflect the contents of the last stable release
- The *develop* branch should contain the latest tested & approved updates. Pull requests should be used to merge changes into develop. - The *develop* branch should contain the latest tested & approved updates. Pull requests should be used to merge changes into develop.
- The rest of the feature branches are for developing new features - The rest of the feature branches are for developing new features
- A tag will be created for each "release". The releases will be named <year>.<month> (for the first release of the month). Hot fixes, if necessary, will be released as <year>.<month>.<release number>. For example, the first release in January 2021 will be release "21.01". If a hot-fix is needed for this release, the first hotfix will be "21.01.1". - A tag will be created for each "release". The releases will be named <year>.<month>.<release number> where the release number is incremented for each subsequent release tagged in the same calendar month. The first release of the month of January 2021 is called "21.01.01", the second one in the same month "21.01.02" and so on.
Typically, releases will only be planned every few months. Typically, releases will only be planned every few months.
When you are ready to contribute code to RaSCSI Reloaded, follow the <a href="https://docs.github.com/en/get-started/quickstart/contributing-to-projects">GitHub Forking and Pull Request workflow</a> to create your own fork where you can make changes, and then contribute it back to the project. Please remember to always make Pull Requests against the *develop* branch.
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>
# Github Sponsors # GitHub Sponsors
Thank you to all of the Github sponsors who support the development community! Thank you to all of the GitHub sponsors who support the development community!
Special thank you to the Gold level sponsors! Special thank you to the Gold level sponsors!
- <a href="https://github.com/mikelord68">@mikelord68</a> - <a href="https://github.com/mikelord68">@mikelord68</a>

View File

@ -13,7 +13,7 @@ logo="""
 ~ (|_____|) ~\n  ~ (|_____|) ~\n
( : ║ .  __ ║ : )\n ( : ║ .  __ ║ : )\n
 ~ .╚╦═════╦╝. ~\n  ~ .╚╦═════╦╝. ~\n
  (  ¯¯¯¯¯¯¯  ) RaSCSI Assistant\n   (  ¯¯¯¯¯¯¯  ) RaSCSI Reloaded Assistant\n
   '~ .~~~. ~'\n    '~ .~~~. ~'\n
       '~'\n        '~'\n
""" """
@ -55,6 +55,8 @@ OLED_INSTALL_PATH="$BASE/python/oled"
CTRLBOARD_INSTALL_PATH="$BASE/python/ctrlboard" CTRLBOARD_INSTALL_PATH="$BASE/python/ctrlboard"
PYTHON_COMMON_PATH="$BASE/python/common" PYTHON_COMMON_PATH="$BASE/python/common"
SYSTEMD_PATH="/etc/systemd/system" SYSTEMD_PATH="/etc/systemd/system"
SSL_CERTS_PATH="/etc/ssl/certs"
SSL_KEYS_PATH="/etc/ssl/private"
HFS_FORMAT=/usr/bin/hformat HFS_FORMAT=/usr/bin/hformat
HFDISK_BIN=/usr/bin/hfdisk HFDISK_BIN=/usr/bin/hfdisk
LIDO_DRIVER=$BASE/lido-driver.img LIDO_DRIVER=$BASE/lido-driver.img
@ -80,7 +82,7 @@ function sudoCheck() {
# install all dependency packages for RaSCSI Service # install all dependency packages for RaSCSI Service
function installPackages() { 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 -y </dev/null 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
} }
# cache the pip packages # cache the pip packages
@ -88,7 +90,7 @@ function cachePipPackages(){
pushd $WEB_INSTALL_PATH pushd $WEB_INSTALL_PATH
# Refresh the sudo authentication, which shouldn't trigger another password prompt # Refresh the sudo authentication, which shouldn't trigger another password prompt
sudo -v sudo -v
sudo pip install -r ./requirements.txt sudo pip3 install -r ./requirements.txt
popd popd
} }
@ -147,6 +149,21 @@ function installRaScsiWebInterface() {
sudo usermod -a -G $USER www-data sudo usermod -a -G $USER www-data
if [ -f "$SSL_CERTS_PATH/rascsi-web.crt" ]; then
echo "SSL certificate $SSL_CERTS_PATH/rascsi-web.crt already exists."
else
echo "SSL certificate $SSL_CERTS_PATH/rascsi-web.crt does not exist; creating self-signed certificate..."
sudo mkdir -p "$SSL_CERTS_PATH" || true
sudo mkdir -p "$SSL_KEYS_PATH" || true
sudo openssl req -x509 -nodes -sha256 -days 3650 \
-newkey rsa:4096 \
-keyout "$SSL_KEYS_PATH/rascsi-web.key" \
-out "$SSL_CERTS_PATH/rascsi-web.crt" \
-subj '/CN=rascsi' \
-addext 'subjectAltName=DNS:rascsi' \
-addext 'extendedKeyUsage=serverAuth'
fi
sudo systemctl reload nginx || true sudo systemctl reload nginx || true
} }
@ -753,7 +770,7 @@ function setupWirelessNetworking() {
# Downloads, compiles, and installs Netatalk (AppleShare server) # Downloads, compiles, and installs Netatalk (AppleShare server)
function installNetatalk() { function installNetatalk() {
NETATALK_VERSION="2-220702" NETATALK_VERSION="2-220801"
AFP_SHARE_PATH="$HOME/afpshare" AFP_SHARE_PATH="$HOME/afpshare"
AFP_SHARE_NAME="Pi File Server" AFP_SHARE_NAME="Pi File Server"
@ -776,7 +793,7 @@ function installMacproxy {
echo -n "Enter a port number 1024 - 65535, or press Enter to use the default port: " echo -n "Enter a port number 1024 - 65535, or press Enter to use the default port: "
read -r CHOICE read -r CHOICE
if [ $CHOICE -ge "1024" ] && [ $CHOICE -le "65535" ]; then if [[ $CHOICE -ge "1024" ]] && [[ $CHOICE -le "65535" ]]; then
PORT=$CHOICE PORT=$CHOICE
else else
echo "Using the default port $PORT" echo "Using the default port $PORT"
@ -784,20 +801,19 @@ function installMacproxy {
( sudo apt-get update && sudo apt-get install python3 python3-venv --assume-yes ) </dev/null ( sudo apt-get update && sudo apt-get install python3 python3-venv --assume-yes ) </dev/null
MACPROXY_VER="21.12.3" MACPROXY_VER="22.8"
MACPROXY_PATH="$HOME/macproxy-$MACPROXY_VER" MACPROXY_PATH="$HOME/macproxy-$MACPROXY_VER"
if [ -d "$MACPROXY_PATH" ]; then if [ -d "$MACPROXY_PATH" ]; then
echo "The $MACPROXY_PATH directory already exists. Delete it to proceed with the installation." echo "The $MACPROXY_PATH directory already exists. Deleting before downloading again..."
exit 1 sudo rm -rf "$MACPROXY_PATH"
fi fi
cd "$HOME" || exit 1 cd "$HOME" || exit 1
wget -O "macproxy-$MACPROXY_VER.tar.gz" "https://github.com/rdmark/macproxy/archive/refs/tags/$MACPROXY_VER.tar.gz" </dev/null wget -O "macproxy-$MACPROXY_VER.tar.gz" "https://github.com/rdmark/macproxy/archive/refs/tags/v$MACPROXY_VER.tar.gz" </dev/null
tar -xzvf "macproxy-$MACPROXY_VER.tar.gz" tar -xzvf "macproxy-$MACPROXY_VER.tar.gz"
stopMacproxy
sudo cp "$MACPROXY_PATH/macproxy.service" "$SYSTEMD_PATH" sudo cp "$MACPROXY_PATH/macproxy.service" "$SYSTEMD_PATH"
sudo sed -i /^ExecStart=/d "$SYSTEMD_PATH/macproxy.service" sudo sed -i /^ExecStart=/d "$SYSTEMD_PATH/macproxy.service"
sudo sed -i "8 i ExecStart=$MACPROXY_PATH/start_macproxy.sh" "$SYSTEMD_PATH/macproxy.service" sudo sed -i "8 i ExecStart=$MACPROXY_PATH/start_macproxy.sh -p=$PORT" "$SYSTEMD_PATH/macproxy.service"
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl enable macproxy sudo systemctl enable macproxy
startMacproxy startMacproxy
@ -1048,12 +1064,13 @@ function runChoice() {
echo "- Modify user groups and permissions" echo "- Modify user groups and permissions"
echo "- Install binaries to /usr/local/bin" echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man" echo "- Install manpages to /usr/local/man"
echo "- Create a self-signed certificate in /etc/ssl"
sudoCheck sudoCheck
createImagesDir
createCfgDir
configureTokenAuth configureTokenAuth
stopOldWebInterface stopOldWebInterface
updateRaScsiGit updateRaScsiGit
createImagesDir
createCfgDir
installPackages installPackages
stopRaScsiScreen stopRaScsiScreen
stopRaScsi stopRaScsi
@ -1091,9 +1108,10 @@ function runChoice() {
echo "- Install binaries to /usr/local/bin" echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man" echo "- Install manpages to /usr/local/man"
sudoCheck sudoCheck
createImagesDir
createCfgDir
configureTokenAuth configureTokenAuth
updateRaScsiGit updateRaScsiGit
createImagesDir
installPackages installPackages
stopRaScsiScreen stopRaScsiScreen
stopRaScsi stopRaScsi
@ -1168,6 +1186,7 @@ function runChoice() {
echo "- Install additional packages with apt-get" echo "- Install additional packages with apt-get"
echo "- Add and modify systemd services" echo "- Add and modify systemd services"
sudoCheck sudoCheck
stopMacproxy
installMacproxy installMacproxy
echo "Installing Web Proxy Server - Complete!" echo "Installing Web Proxy Server - Complete!"
;; ;;
@ -1179,8 +1198,8 @@ function runChoice() {
echo "- Install binaries to /usr/local/bin" echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man" echo "- Install manpages to /usr/local/man"
sudoCheck sudoCheck
updateRaScsiGit
createImagesDir createImagesDir
updateRaScsiGit
installPackages installPackages
stopRaScsi stopRaScsi
compileRaScsi compileRaScsi
@ -1196,9 +1215,10 @@ function runChoice() {
echo "- Modify and enable Apache2 and Nginx web service" echo "- Modify and enable Apache2 and Nginx web service"
echo "- Create directories and change permissions" echo "- Create directories and change permissions"
echo "- Modify user groups and permissions" echo "- Modify user groups and permissions"
echo "- Create a self-signed certificate in /etc/ssl"
sudoCheck sudoCheck
updateRaScsiGit
createCfgDir createCfgDir
updateRaScsiGit
installPackages installPackages
preparePythonCommon preparePythonCommon
cachePipPackages cachePipPackages

View File

@ -1,12 +1,21 @@
# RaSCSI Python Apps # RaSCSI Reloaded Python Apps
This directory contains Python-based clients for RaSCSI as well as common This directory contains Python-based clients for RaSCSI Reloaded as well as common
packages that are shared among the clients. packages that are shared among the clients.
The following paragraphs in this README contain instructions that are shared The following paragraphs in this README contain instructions that are shared
among all Python apps. among all Python apps.
### Static analysis with pylint ## Supported Python interpreter
The policy in this project is to support the Python 3 interpreter that comes
standard with the current stable, as well as previous stable release of Debian.
At the time of writing they are:
- Python 3.9.2 in [Debian Bullseye](https://packages.debian.org/bullseye/python3)
- Python 3.7.3 in [Debian Buster](https://packages.debian.org/buster/python3)
## Static analysis with pylint
It is recommended to run pylint against new code to protect against bugs It is recommended to run pylint against new code to protect against bugs
and keep the code readable and maintainable. and keep the code readable and maintainable.
@ -29,4 +38,4 @@ pylint web/src/web.py
pylint common/src pylint common/src
pylint web/src pylint web/src
pylint oled/src pylint oled/src
``` ```

View File

@ -12,6 +12,15 @@ CONFIG_FILE_SUFFIX = "json"
# File ending used for drive properties files # File ending used for drive properties files
PROPERTIES_SUFFIX = "properties" PROPERTIES_SUFFIX = "properties"
# Supported archive file suffixes
ARCHIVE_FILE_SUFFIXES = [
"zip",
"sit",
"tar",
"gz",
"7z"
]
# The RESERVATIONS list is used to keep track of the reserved ID memos. # The RESERVATIONS list is used to keep track of the reserved ID memos.
# Initialize with a list of 8 empty strings. # Initialize with a list of 8 empty strings.
RESERVATIONS = ["" for x in range(0, 8)] RESERVATIONS = ["" for x in range(0, 8)]

View File

@ -5,9 +5,9 @@ Module for methods reading from and writing to the file system
import os import os
import logging import logging
import asyncio import asyncio
from functools import lru_cache
from pathlib import PurePath from pathlib import PurePath
from zipfile import ZipFile, is_zipfile from zipfile import ZipFile, is_zipfile
from re import escape, findall
from time import time from time import time
from subprocess import run, CalledProcessError from subprocess import run, CalledProcessError
from json import dump, load from json import dump, load
@ -16,10 +16,11 @@ from shutil import copyfile
import requests import requests
import rascsi_interface_pb2 as proto import rascsi_interface_pb2 as proto
from rascsi.common_settings import CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, RESERVATIONS from rascsi.common_settings import CFG_DIR, CONFIG_FILE_SUFFIX, PROPERTIES_SUFFIX, ARCHIVE_FILE_SUFFIXES, RESERVATIONS
from rascsi.ractl_cmds import RaCtlCmds from rascsi.ractl_cmds import RaCtlCmds
from rascsi.return_codes import ReturnCodes from rascsi.return_codes import ReturnCodes
from rascsi.socket_cmds import SocketCmds from rascsi.socket_cmds import SocketCmds
from util import unarchiver
class FileCmds: class FileCmds:
@ -97,19 +98,31 @@ class FileCmds:
prop = process["conf"] prop = process["conf"]
else: else:
prop = False prop = False
if file.name.lower().endswith(".zip"):
zip_path = f"{server_info['image_dir']}/{file.name}" archive_contents = []
if is_zipfile(zip_path): if PurePath(file.name).suffix.lower()[1:] in ARCHIVE_FILE_SUFFIXES:
zipfile = ZipFile(zip_path) try:
# Get a list of (str) containing all zipfile members archive_info = self._get_archive_info(
zip_members = zipfile.namelist() f"{server_info['image_dir']}/{file.name}",
# Strip out directories from the list _cache_extra_key=file.size
zip_members = [x for x in zip_members if not x.endswith("/")] )
else:
logging.warning("%s is an invalid zip file", zip_path) properties_files = [x["path"]
zip_members = False for x in archive_info["members"]
else: if x["path"].endswith(PROPERTIES_SUFFIX)]
zip_members = False
for member in archive_info["members"]:
if member["is_dir"] or member["is_resource_fork"]:
continue
if PurePath(member["path"]).suffix.lower()[1:] == PROPERTIES_SUFFIX:
member["is_properties_file"] = True
elif f"{member['path']}.{PROPERTIES_SUFFIX}" in properties_files:
member["related_properties_file"] = f"{member['path']}.{PROPERTIES_SUFFIX}"
archive_contents.append(member)
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError):
pass
size_mb = "{:,.1f}".format(file.size / 1024 / 1024) size_mb = "{:,.1f}".format(file.size / 1024 / 1024)
dtype = proto.PbDeviceType.Name(file.type) dtype = proto.PbDeviceType.Name(file.type)
@ -119,7 +132,7 @@ class FileCmds:
"size_mb": size_mb, "size_mb": size_mb,
"detected_type": dtype, "detected_type": dtype,
"prop": prop, "prop": prop,
"zip_members": zip_members, "archive_contents": archive_contents,
}) })
return {"status": result.status, "msg": result.msg, "files": files} return {"status": result.status, "msg": result.msg, "files": files}
@ -266,62 +279,73 @@ class FileCmds:
"parameters": parameters, "parameters": parameters,
} }
def unzip_file(self, file_name, member=False, members=False): def extract_image(self, file_path, members=None, move_properties_files_to_config=True):
""" """
Takes (str) file_name, optional (str) member, optional (list) of (str) members Takes (str) file_path, (list) members, optional (bool) move_properties_files_to_config
file_name is the name of the zip file to unzip file_name is the path of the archive file to extract, relative to the images directory
member is the full path to the particular file in the zip file to unzip members is a list of file paths in the archive file to extract
members contains all of the full paths to each of the zip archive members move_properties_files_to_config controls if .properties files are auto-moved to CFG_DIR
Returns (dict) with (boolean) status and (list of str) msg Returns (dict) result
""" """
server_info = self.ractl.get_server_info() server_info = self.ractl.get_server_info()
prop_flag = False
if not member: if not members:
unzip_proc = asyncio.run(self.run_async("unzip", [ return {
"-d", "status": False,
server_info['image_dir'], "return_code": ReturnCodes.EXTRACTIMAGE_NO_FILES_SPECIFIED,
"-n", }
"-j",
f"{server_info['image_dir']}/{file_name}",
]))
if members:
for path in members:
if path.endswith(PROPERTIES_SUFFIX):
name = PurePath(path).name
self.rename_file(f"{server_info['image_dir']}/{name}", f"{CFG_DIR}/{name}")
prop_flag = True
else:
member = escape(member)
unzip_proc = asyncio.run(self.run_async("unzip", [
"-d",
server_info['image_dir'],
"-n",
"-j",
f"{server_info['image_dir']}/{file_name}",
member,
]))
# Attempt to unzip a properties file in the same archive dir
unzip_prop = asyncio.run(self.run_async("unzip", [
"-d",
CFG_DIR,
"-n",
"-j",
f"{server_info['image_dir']}/{file_name}",
f"{member}.{PROPERTIES_SUFFIX}",
]))
if unzip_prop["returncode"] == 0: try:
prop_flag = True extract_result = unarchiver.extract_archive(
if unzip_proc["returncode"] != 0: f"{server_info['image_dir']}/{file_path}",
logging.warning("Unzipping failed: %s", unzip_proc["stderr"]) members=members,
return {"status": False, "msg": unzip_proc["stderr"]} output_dir=server_info["image_dir"],
)
properties_files_moved = []
if move_properties_files_to_config:
for file in extract_result["extracted"]:
if file.get("name").endswith(".properties"):
if (self.rename_file(
file["absolute_path"],
f"{CFG_DIR}/{file['name']}"
)):
properties_files_moved.append({
"status": True,
"name": file["path"],
"path": f"{CFG_DIR}/{file['name']}",
})
else:
properties_files_moved.append({
"status": False,
"name": file["path"],
"path": f"{CFG_DIR}/{file['name']}",
})
return {
"status": True,
"return_code": ReturnCodes.EXTRACTIMAGE_SUCCESS,
"parameters": {
"count": len(extract_result["extracted"]),
},
"extracted": extract_result["extracted"],
"skipped": extract_result["skipped"],
"properties_files_moved": properties_files_moved,
}
except unarchiver.UnarNoFilesExtractedError:
return {
"status": False,
"return_code": ReturnCodes.EXTRACTIMAGE_NO_FILES_EXTRACTED,
}
except (unarchiver.UnarCommandError, unarchiver.UnarUnexpectedOutputError) as error:
return {
"status": False,
"return_code": ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR,
"parameters": {
"error": error,
}
}
unzipped = findall(
"(?:inflating|extracting):(.+)\n",
unzip_proc["stdout"]
)
return {"status": True, "msg": unzipped, "prop_flag": prop_flag}
def download_file_to_iso(self, url, *iso_args): def download_file_to_iso(self, url, *iso_args):
""" """
@ -652,3 +676,14 @@ class FileCmds:
logging.info("stderr: %s", stderr) logging.info("stderr: %s", stderr)
return {"returncode": proc.returncode, "stdout": stdout, "stderr": stderr} return {"returncode": proc.returncode, "stdout": stdout, "stderr": stderr}
# noinspection PyMethodMayBeStatic
@lru_cache(maxsize=32)
def _get_archive_info(self, file_path, **kwargs):
"""
Cached wrapper method to improve performance, e.g. on index screen
"""
try:
return unarchiver.inspect_archive(file_path)
except (unarchiver.LsarCommandError, unarchiver.LsarOutputError):
raise

View File

@ -20,3 +20,7 @@ class ReturnCodes:
READDRIVEPROPS_SUCCESS = 70 READDRIVEPROPS_SUCCESS = 70
READDRIVEPROPS_COULD_NOT_READ = 71 READDRIVEPROPS_COULD_NOT_READ = 71
ATTACHIMAGE_COULD_NOT_ATTACH = 80 ATTACHIMAGE_COULD_NOT_ATTACH = 80
EXTRACTIMAGE_SUCCESS = 90
EXTRACTIMAGE_NO_FILES_SPECIFIED = 91
EXTRACTIMAGE_NO_FILES_EXTRACTED = 92
EXTRACTIMAGE_COMMAND_ERROR = 93

View File

View File

@ -0,0 +1,45 @@
"""
Utility module for running system commands with basic logging
"""
import asyncio
import logging
import os
def run(program, args=None):
""" Run a command and return its output """
return asyncio.run(run_async(program, args))
async def run_async(program, args=None):
""" Run a command in the background """
proc = await asyncio.create_subprocess_exec(
program,
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
logging.info(
"Executed command \"%s %s\" with status code %d",
program,
" ".join(args),
proc.returncode
)
if stdout:
stdout = stdout.decode()
logging.debug(stdout)
if stderr:
stderr = stderr.decode()
logging.warning(stderr)
return {
"returncode": proc.returncode,
"stdout": stdout,
"stderr": stderr,
}

View File

@ -0,0 +1,206 @@
"""
A minimal wrapper around 'The Unarchiver' command line tools (v1.10.1)
https://theunarchiver.com/command-line
Later versions (untested) available at: https://github.com/MacPaw/XADMaster
"""
import logging
import pathlib
from tempfile import TemporaryDirectory
from re import escape, match
from json import loads, JSONDecodeError
from util.run import run
FORK_OUTPUT_TYPE_VISIBLE = "visible"
FORK_OUTPUT_TYPE_HIDDEN = "hidden"
FORK_OUTPUT_TYPES = [FORK_OUTPUT_TYPE_VISIBLE, FORK_OUTPUT_TYPE_HIDDEN]
def extract_archive(file_path, **kwargs):
"""
Extracts files from an archive
Takes (str) file_path, and kwargs:
- (list) members - list of (str) files to be extracted (all files are extracted if None)
- (str) output_dir - directory to place the extracted files
- (str) fork_output_type - output type for resource forks; "visible" for *.rsrc files, "hidden" for ._* files
Returns (dict) of extracted and skipped members
"""
members = kwargs.get("members")
if kwargs.get("output_dir"):
if not pathlib.Path(kwargs["output_dir"]).is_dir():
raise ValueError("Argument output_dir must be a directory")
output_dir = str(pathlib.Path(kwargs["output_dir"]).resolve())
else:
output_dir = str(pathlib.Path(file_path).parent.resolve())
if kwargs.get("fork_output_type"):
if kwargs["fork_output_type"] not in FORK_OUTPUT_TYPES:
raise ValueError(f"Argument fork_output_type must be one of: {','.join(FORK_OUTPUT_TYPES)} ")
fork_output_type = kwargs["fork_output_type"]
fork_output_type_args = ["-forks", fork_output_type or FORK_OUTPUT_TYPE_VISIBLE]
else:
fork_output_type = None
fork_output_type_args = []
with TemporaryDirectory() as tmp_dir:
unar_args = [
"-output-directory",
tmp_dir,
"-force-skip",
"-no-directory",
*fork_output_type_args,
'--',
file_path,
]
if members:
for member in members:
unar_args.append(escape(member))
process = run("unar", unar_args)
if process["returncode"] != 0:
raise UnarCommandError(f"Non-zero return code: {process['returncode']}")
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]+)\.$"
lines = process["stdout"].rstrip("\n").split("\n")
if lines[-1] == unar_result_no_files:
raise UnarNoFilesExtractedError
if match(unar_result_success, lines[-1]):
extracted_members = []
for line in lines[1:-1]:
line_matches = match(unar_file_extracted, line)
if line_matches:
matches = line_matches.groupdict()
member = {
"name": str(pathlib.PurePath(matches["path"]).name),
"path": matches["path"],
"size": matches["size"] or 0,
"is_dir": False,
"is_resource_fork": False,
"absolute_path": str(pathlib.PurePath(tmp_dir).joinpath(matches["path"])),
}
member_types = matches.get("types", "")
if member_types.startswith(", "):
member_types = member_types[2:].split(", ")
else:
member_types = member_types.split(", ")
if "dir" in member_types:
member["is_dir"] = True
if "rsrc" in member_types:
if not fork_output_type:
continue
member["is_resource_fork"] = True
# Update names/paths to match unar resource fork naming convention
if fork_output_type == FORK_OUTPUT_TYPE_HIDDEN:
member["name"] = f"._{member['name']}"
else:
member["name"] += ".rsrc"
member["path"] = str(pathlib.PurePath(member["path"]).parent.joinpath(member["name"]))
member["absolute_path"] = str(pathlib.PurePath(tmp_dir).joinpath(member["path"]))
logging.debug("Extracted: %s -> %s", member['path'], member['absolute_path'])
extracted_members.append(member)
else:
raise UnarUnexpectedOutputError(f"Unexpected output: {line}")
moved = []
skipped = []
for member in sorted(extracted_members, key=lambda m: m["path"]):
source_path = pathlib.Path(member["absolute_path"])
target_path = pathlib.Path(output_dir).joinpath(member["path"])
member["absolute_path"] = str(target_path)
if target_path.exists():
logging.info("Skipping temp file/dir as the target already exists: %s", target_path)
skipped.append(member)
continue
if member["is_dir"]:
logging.debug("Creating empty dir: %s -> %s", source_path, target_path)
target_path.mkdir(parents=True, exist_ok=True)
moved.append(member)
continue
# 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)
moved.append(member)
return {
"extracted": moved,
"skipped": skipped,
}
raise UnarUnexpectedOutputError(lines[-1])
def inspect_archive(file_path, **kwargs):
"""
Calls `lsar` to inspect the contents of an archive
Takes (str) file_path
Returns (dict) of (str) format, (list) members
"""
if not pathlib.Path(file_path):
raise FileNotFoundError(f"File {file_path} does not exist")
process = run("lsar", ["-json", "--", file_path])
if process["returncode"] != 0:
raise LsarCommandError(f"Non-zero return code: {process['returncode']}")
try:
archive_info = loads(process["stdout"])
except JSONDecodeError as error:
raise LsarOutputError(f"Unable to read JSON output from lsar: {error.msg}") from error
members = [{
"name": pathlib.PurePath(member.get("XADFileName")).name,
"path": member.get("XADFileName"),
"size": member.get("XADFileSize"),
"is_dir": member.get("XADIsDirectory"),
"is_resource_fork": member.get("XADIsResourceFork"),
"raw": member,
} for member in archive_info.get("lsarContents", [])]
return {
"format": archive_info.get("lsarFormatName"),
"members": members,
}
class UnarCommandError(Exception):
""" Command execution was unsuccessful """
pass
class UnarNoFilesExtractedError(Exception):
""" Command completed, but no files extracted """
class UnarUnexpectedOutputError(Exception):
""" Command output not recognized """
class LsarCommandError(Exception):
""" Command execution was unsuccessful """
class LsarOutputError(Exception):
""" Command output could not be parsed"""

View File

@ -21,7 +21,9 @@ You may edit the files under `mock/bin` to simulate Linux command responses.
TODO: rascsi-web uses protobuf commands to send and receive data from rascsi. TODO: rascsi-web uses protobuf commands to send and receive data from rascsi.
A separate mocking solution will be needed for this interface. A separate mocking solution will be needed for this interface.
## Pushing to the Pi via git ## (Optional) Pushing to the Pi via git
This is a setup for pushing code changes from your local development environment to the Raspberry Pi without a roundtrip to the remote GitHub repository.
Setup a bare repo on the rascsi Setup a bare repo on the rascsi
``` ```
@ -40,7 +42,7 @@ $ git push pi master
## Localizing the Web Interface ## Localizing the Web Interface
We use the Flask-Babel library and Flask/Jinja2 extension for i18n. We use the Flask-Babel library and Flask/Jinja2 extension for internationalization (i18n).
It uses the 'pybabel' command line tool for extracting and compiling localizations. The Web Interface start script will automatically compile localizations upon launch. It uses the 'pybabel' command line tool for extracting and compiling localizations. The Web Interface start script will automatically compile localizations upon launch.
@ -55,7 +57,7 @@ To create a new localization, it needs to be added to the LANGAUGES constant in
web/settings.py. To localize messages coming from the RaSCSI backend, update also code in web/settings.py. To localize messages coming from the RaSCSI backend, update also code in
raspberrypi/localizer.cpp in the RaSCSI C++ code. raspberrypi/localizer.cpp in the RaSCSI C++ code.
Once this is done, it is time to localize the Python code. The below steps are derived from the [Flask-Babel documentation](https://flask-babel.tkte.ch/#translating-applications). Once this is done, it is time to localize the Python code. The below steps are derived from the [Flask-Babel documentation](https://python-babel.github.io/flask-babel/index.html#translating-applications).
First, generate the raw messages.pot file containing extracted strings. First, generate the raw messages.pot file containing extracted strings.
@ -68,7 +70,7 @@ $ pybabel extract -F babel.cfg -o messages.pot .
When adding a localization for a new language, initialize the directory structure. Replace 'xx' with the two character code for the language. When adding a localization for a new language, initialize the directory structure. Replace 'xx' with the two character code for the language.
``` ```
$ pybabel init -i messages.pot -d translations -l xx $ pybabel init -i messages.pot -d src/translations -l xx
``` ```
### Update an existing loclization ### Update an existing loclization
@ -114,6 +116,10 @@ msgstr ""
"drivrutiner och inställningar</a>." "drivrutiner och inställningar</a>."
``` ```
### Contributing to the project
New or updated localizations are treated just like any other code change. See the [project README](https://github.com/akuker/RASCSI/tree/rdmark-readme-contributions#how-do-i-contribute) for further information.
### (Optional) See translation stats for a localization ### (Optional) See translation stats for a localization
Install the gettext package and use msgfmt to see the translation progress. Install the gettext package and use msgfmt to see the translation progress.
``` ```

View File

@ -3,6 +3,16 @@
server { server {
listen [::]:80 default_server; listen [::]:80 default_server;
listen 80 default_server; listen 80 default_server;
listen 443 ssl http2;
listen [::]:443 ssl http2;
ssl_certificate /etc/ssl/certs/rascsi-web.crt;
ssl_certificate_key /etc/ssl/private/rascsi-web.key;
ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;
ssl_protocols TLSv1.3;
ssl_prefer_server_ciphers off;
location / { location / {
proxy_pass http://127.0.0.1:8080; proxy_pass http://127.0.0.1:8080;

View File

@ -430,5 +430,17 @@
"file_type": null, "file_type": null,
"description": "Emulates Apple CD ROM drive for use with Macintosh computers.", "description": "Emulates Apple CD ROM drive for use with Macintosh computers.",
"url": "" "url": ""
},
{
"device_type": "SCCD",
"vendor": null,
"product": null,
"revision": null,
"block_size": 512,
"size": null,
"name": "Generic CD-ROM 512 block size",
"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": ""
} }
] ]

View File

@ -9,25 +9,43 @@ class ReturnCodeMapper:
"""Class for mapping between rascsi return codes and translated strings""" """Class for mapping between rascsi return codes and translated strings"""
MESSAGES = { MESSAGES = {
ReturnCodes.DELETEFILE_SUCCESS: _("File deleted: %(file_path)s"), ReturnCodes.DELETEFILE_SUCCESS:
ReturnCodes.DELETEFILE_FILE_NOT_FOUND: _("File to delete not found: %(file_path)s"), _("File deleted: %(file_path)s"),
ReturnCodes.RENAMEFILE_SUCCESS: _("File moved to: %(target_path)s"), ReturnCodes.DELETEFILE_FILE_NOT_FOUND:
ReturnCodes.RENAMEFILE_UNABLE_TO_MOVE: _("Unable to move file to: %(target_path)s"), _("File to delete not found: %(file_path)s"),
ReturnCodes.DOWNLOADFILETOISO_SUCCESS: _("Created CD-ROM ISO image with " ReturnCodes.RENAMEFILE_SUCCESS:
"arguments \"%(value)s\""), _("File moved to: %(target_path)s"),
ReturnCodes.DOWNLOADTODIR_SUCCESS: _("%(file_name)s downloaded to %(save_dir)s"), ReturnCodes.RENAMEFILE_UNABLE_TO_MOVE:
ReturnCodes.WRITEFILE_SUCCESS: _("File created: %(target_path)s"), _("Unable to move file to: %(target_path)s"),
ReturnCodes.WRITEFILE_COULD_NOT_WRITE: _("Could not create file: %(target_path)s"), ReturnCodes.DOWNLOADFILETOISO_SUCCESS:
ReturnCodes.READCONFIG_SUCCESS: _("Loaded configurations from: %(file_name)s"), _("Created CD-ROM ISO image with arguments \"%(value)s\""),
ReturnCodes.READCONFIG_COULD_NOT_READ: _("Could not read configuration " ReturnCodes.DOWNLOADTODIR_SUCCESS:
"file: %(file_name)s"), _("%(file_name)s downloaded to %(save_dir)s"),
ReturnCodes.READCONFIG_INVALID_CONFIG_FILE_FORMAT: _("Invalid configuration file format"), ReturnCodes.WRITEFILE_SUCCESS:
ReturnCodes.READDRIVEPROPS_SUCCESS: _("Read properties from file: %(file_path)s"), _("File created: %(target_path)s"),
ReturnCodes.READDRIVEPROPS_COULD_NOT_READ: _("Could not read properties from " ReturnCodes.WRITEFILE_COULD_NOT_WRITE:
"file: %(file_path)s"), _("Could not create file: %(target_path)s"),
ReturnCodes.ATTACHIMAGE_COULD_NOT_ATTACH: _("Cannot insert an image for %(device_type)s " ReturnCodes.READCONFIG_SUCCESS:
"into a %(current_device_type)s device"), _("Loaded configurations from: %(file_name)s"),
} ReturnCodes.READCONFIG_COULD_NOT_READ:
_("Could not read configuration file: %(file_name)s"),
ReturnCodes.READCONFIG_INVALID_CONFIG_FILE_FORMAT:
_("Invalid configuration file format"),
ReturnCodes.READDRIVEPROPS_SUCCESS:
_("Read properties from file: %(file_path)s"),
ReturnCodes.READDRIVEPROPS_COULD_NOT_READ:
_("Could not read properties from file: %(file_path)s"),
ReturnCodes.ATTACHIMAGE_COULD_NOT_ATTACH:
_("Cannot insert an image for %(device_type)s into a %(current_device_type)s device"),
ReturnCodes.EXTRACTIMAGE_SUCCESS:
_("Extracted %(count)s file(s)"),
ReturnCodes.EXTRACTIMAGE_NO_FILES_SPECIFIED:
_("Unable to extract archive: No files were specified"),
ReturnCodes.EXTRACTIMAGE_NO_FILES_EXTRACTED:
_("No files were extracted (existing files are skipped)"),
ReturnCodes.EXTRACTIMAGE_COMMAND_ERROR:
_("Unable to extract archive: %(error)s"),
}
@staticmethod @staticmethod
def add_msg(payload): def add_msg(payload):
@ -36,10 +54,14 @@ class ReturnCodeMapper:
if "return_code" not in payload: if "return_code" not in payload:
return payload return payload
parameters = payload["parameters"] parameters = payload.get("parameters")
payload["msg"] = lazy_gettext( if parameters:
payload["msg"] = lazy_gettext(
ReturnCodeMapper.MESSAGES[payload["return_code"]], ReturnCodeMapper.MESSAGES[payload["return_code"]],
**parameters, **parameters,
) )
else:
payload["msg"] = lazy_gettext(ReturnCodeMapper.MESSAGES[payload["return_code"]])
return payload return payload

View File

@ -12,8 +12,6 @@ AFP_DIR = f"{HOME_DIR}/afpshare"
MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", str(1024 * 1024 * 1024 * 4)) # 4gb MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", str(1024 * 1024 * 1024 * 4)) # 4gb
ARCHIVE_FILE_SUFFIX = "zip"
# The file name of the default config file that loads when rascsi-web starts # The file name of the default config file that loads when rascsi-web starts
DEFAULT_CONFIG = f"default.{rascsi.common_settings.CONFIG_FILE_SUFFIX}" DEFAULT_CONFIG = f"default.{rascsi.common_settings.CONFIG_FILE_SUFFIX}"
# File containing canonical drive properties # File containing canonical drive properties

View File

@ -35,12 +35,14 @@ table, tr, td {
color: white; color: white;
font-size:20px; font-size:20px;
background-color:red; background-color:red;
white-space: pre-line;
} }
.message { .message {
color: white; color: white;
font-size:20px; font-size:20px;
background-color:green; background-color:green;
white-space: pre-line;
} }
td.inactive { td.inactive {

View File

@ -34,7 +34,7 @@
<input type="hidden" name="revision" value="{{ hd.revision }}"> <input type="hidden" name="revision" value="{{ hd.revision }}">
<input type="hidden" name="blocks" value="{{ hd.blocks }}"> <input type="hidden" name="blocks" value="{{ hd.blocks }}">
<input type="hidden" name="block_size" value="{{ hd.block_size }}"> <input type="hidden" name="block_size" value="{{ hd.block_size }}">
<input type="hidden" name="size" value="{{ hd.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 }}"> <input type="hidden" name="file_type" value="{{ hd.file_type }}">
<label for="file_name">{{ _("Save as:") }}</label> <label for="file_name">{{ _("Save as:") }}</label>
<input type="text" name="file_name" value="{{ hd.secure_name }}" required />.{{ hd.file_type }} <input type="text" name="file_name" value="{{ hd.secure_name }}" required />.{{ hd.file_type }}
@ -124,7 +124,7 @@
<input type="hidden" name="revision" value="{{ rm.revision }}"> <input type="hidden" name="revision" value="{{ rm.revision }}">
<input type="hidden" name="blocks" value="{{ rm.blocks }}"> <input type="hidden" name="blocks" value="{{ rm.blocks }}">
<input type="hidden" name="block_size" value="{{ rm.block_size }}"> <input type="hidden" name="block_size" value="{{ rm.block_size }}">
<input type="hidden" name="size" value="{{ rm.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 }}"> <input type="hidden" name="file_type" value="{{ rm.file_type }}">
<label for="file_name">{{ _("Save as:") }}</label> <label for="file_name">{{ _("Save as:") }}</label>
<input type="text" name="file_name" value="{{ rm.secure_name }}" required />.{{ rm.file_type }} <input type="text" name="file_name" value="{{ rm.secure_name }}" required />.{{ rm.file_type }}

View File

@ -185,41 +185,41 @@
</ul> </ul>
</details> </details>
</td> </td>
{% elif file["zip_members"] %} {% elif file["archive_contents"] %}
<td> <td>
<details> <details>
<summary> <summary>
{{ file["name"] }} {{ file["name"] }}
</summary> </summary>
<ul style="list-style: none;"> <ul style="list-style: none;">
{% for member in file["zip_members"] %} {% for member in file["archive_contents"] %}
{% if not member.lower().endswith(PROPERTIES_SUFFIX) %} {% if not member["is_properties_file"] %}
<li>
{% if member + "." + PROPERTIES_SUFFIX in file["zip_members"] %}
<details><summary>{{ member }}
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_member" type="hidden" value="{{ member }}">
<input type="submit" value="{{ _("Unzip") }}" onclick="processNotify('{{ _("Unzipping a single file...") }}')">
</form>
</summary>
<ul style="list-style: none;">
<li> <li>
{{ member + "." + PROPERTIES_SUFFIX }} {% if member["related_properties_file"] %}
<details>
<summary>
<label>{{ member["path"] }}</label>
<form action="/files/extract_image" method="post">
<input name="archive_file" type="hidden" value="{{ file['name'] }}">
<input name="archive_members" type="hidden" value="{{ member["path"] }}|{{ member["related_properties_file"] }}">
<input type="submit" value="{{ _("Extract") }}" onclick="processNotify('{{ _("Extracting a single file...") }}')">
</form>
</summary>
<ul style="list-style: none;">
<li>{{ member["related_properties_file"] }}</li>
</ul>
</details>
{% else %}
<label>{{ member["path"] }}</label>
<form action="/files/extract_image" method="post">
<input name="archive_file" type="hidden" value="{{ file["name"] }}">
<input name="archive_members" type="hidden" value="{{ member["path"] }}">
<input type="submit" value="{{ _("Extract") }}" onclick="processNotify('{{ _("Extracting a single file...") }}')">
</form>
{% endif %}
</li> </li>
</ul>
</details>
{% else %}
<label for="zip_member">{{ member }}</label>
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_member" type="hidden" value="{{ member }}">
<input type="submit" value="{{ _("Unzip") }}" onclick="processNotify('{{ _("Unzipping a single file...") }}')">
</form>
{% endif %} {% endif %}
</li> {% endfor %}
{% endif %}
{% endfor %}
</ul> </ul>
</details> </details>
</td> </td>
@ -238,11 +238,12 @@
{{ _("Attached!") }} {{ _("Attached!") }}
</center> </center>
{% else %} {% else %}
{% if file["name"].lower().endswith(ARCHIVE_FILE_SUFFIX) %} {% if file["archive_contents"] %}
<form action="/files/unzip" method="post"> <form action="/files/extract_image" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}"> <input name="archive_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_members" type="hidden" value="{{ file['zip_members'] }}"> {% set pipe = joiner("|") %}
<input type="submit" value="{{ _("Unzip All") }}" onclick="processNotify('{{ _("Unzipping all files...") }}')"> <input name="archive_members" type="hidden" value="{% for member in file["archive_contents"] %}{{ pipe() }}{{ member["path"] }}{% endfor %}">
<input type="submit" value="{{ _("Extract All") }}" onclick="processNotify('{{ _("Extracting all files...") }}')">
</form> </form>
{% else %} {% else %}
<form action="/scsi/attach" method="post"> <form action="/scsi/attach" method="post">
@ -257,7 +258,7 @@
{% endfor %} {% endfor %}
</select> </select>
<label for="unit">{{ _("LUN") }}</label> <label for="unit">{{ _("LUN") }}</label>
<input name="unit" type="number" size="2" value="0" min="0" max="31"> <input name="unit" type="number" value="0" min="0" max="31" step="1">
{% if file["detected_type"] != "UNDEFINED" %} {% if file["detected_type"] != "UNDEFINED" %}
<input name="type" type="hidden" value="{{ file['detected_type'] }}"> <input name="type" type="hidden" value="{{ file['detected_type'] }}">
{{ file['detected_type_name'] }} {{ file['detected_type_name'] }}
@ -336,7 +337,7 @@
{% for key, value in device_types[type]["params"].items() %} {% for key, value in device_types[type]["params"].items() %}
<label for="{{ key }}">{{ key }}:</label> <label for="{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %} {% if value.isnumeric() %}
<input name="{{ key }}" type="number" size="{{ value|length }}" value="{{ value }}"> <input name="{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %} {% elif key == "interface" %}
<select name="interface"> <select name="interface">
{% for if in netinfo["ifs"] %} {% for if in netinfo["ifs"] %}
@ -358,7 +359,7 @@
{% endfor %} {% endfor %}
</select> </select>
<label for="unit">{{ _("LUN") }}</label> <label for="unit">{{ _("LUN") }}</label>
<input name="unit" type="number" size="2" value="0" min="0" max="31"> <input name="unit" type="number" value="0" min="0" max="31" step="1">
<input type="submit" value="{{ _("Attach") }}"> <input type="submit" value="{{ _("Attach") }}">
</form> </form>
</td> </td>
@ -415,7 +416,7 @@
gb: "{{ _("GB") }}", gb: "{{ _("GB") }}",
mb: "{{ _("MB") }}", mb: "{{ _("MB") }}",
kb: "{{ _("KB") }}", kb: "{{ _("KB") }}",
b: "{{ _("b") }}" b: "{{ _("B") }}"
} }
} }
</script> </script>
@ -571,7 +572,7 @@
</option> </option>
</select> </select>
<label for="size">{{ _("Size:") }}</label> <label for="size">{{ _("Size:") }}</label>
<input name="size" type="number" placeholder="{{ _("MB") }}" min="1" size="6" required> <input name="size" type="number" placeholder="{{ _("MB") }}" min="1" max="262144" required>
<input type="submit" value="{{ _("Create") }}"> <input type="submit" value="{{ _("Create") }}">
</form> </form>
</td> </td>
@ -606,7 +607,7 @@
<td style="border: none; vertical-align:top;"> <td style="border: none; vertical-align:top;">
<form action="/logs/show" method="post"> <form action="/logs/show" method="post">
<label for="lines">{{ _("Log Lines:") }}</label> <label for="lines">{{ _("Log Lines:") }}</label>
<input name="lines" type="number" value="200" min="1" size="4"> <input name="lines" type="number" value="200" min="0" max="99999" step="100">
<label for="scope">{{ _("Scope:") }}</label> <label for="scope">{{ _("Scope:") }}</label>
<select name="scope"> <select name="scope">
<option value=""> <option value="">

File diff suppressed because it is too large Load Diff

View File

@ -8,9 +8,9 @@ import argparse
from pathlib import Path from pathlib import Path
from functools import wraps from functools import wraps
from grp import getgrall from grp import getgrall
from ast import literal_eval
import bjoern import bjoern
from rascsi.return_codes import ReturnCodes
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from simplepam import authenticate from simplepam import authenticate
from flask_babel import Babel, Locale, refresh, _ from flask_babel import Babel, Locale, refresh, _
@ -37,6 +37,7 @@ from rascsi.common_settings import (
CFG_DIR, CFG_DIR,
CONFIG_FILE_SUFFIX, CONFIG_FILE_SUFFIX,
PROPERTIES_SUFFIX, PROPERTIES_SUFFIX,
ARCHIVE_FILE_SUFFIXES,
RESERVATIONS, RESERVATIONS,
) )
@ -55,7 +56,6 @@ from web_utils import (
from settings import ( from settings import (
AFP_DIR, AFP_DIR,
MAX_FILE_SIZE, MAX_FILE_SIZE,
ARCHIVE_FILE_SUFFIX,
DEFAULT_CONFIG, DEFAULT_CONFIG,
DRIVE_PROPERTIES_FILE, DRIVE_PROPERTIES_FILE,
AUTH_GROUP, AUTH_GROUP,
@ -133,14 +133,13 @@ def index():
scsi_ids, recommended_id = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids) scsi_ids, recommended_id = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids)
formatted_devices = sort_and_format_devices(devices["device_list"]) formatted_devices = sort_and_format_devices(devices["device_list"])
valid_file_suffix = "."+", .".join( valid_file_suffix = "." + ", .".join(
server_info["sahd"] + server_info["sahd"] +
server_info["schd"] + server_info["schd"] +
server_info["scrm"] + server_info["scrm"] +
server_info["scmo"] + server_info["scmo"] +
server_info["sccd"] + server_info["sccd"] +
[ARCHIVE_FILE_SUFFIX] ARCHIVE_FILE_SUFFIXES)
)
if "username" in session: if "username" in session:
username = session["username"] username = session["username"]
@ -182,7 +181,6 @@ def index():
mo_file_suffix=tuple(server_info["scmo"]), mo_file_suffix=tuple(server_info["scmo"]),
username=username, username=username,
auth_active=auth_active(AUTH_GROUP)["status"], auth_active=auth_active(AUTH_GROUP)["status"],
ARCHIVE_FILE_SUFFIX=ARCHIVE_FILE_SUFFIX,
PROPERTIES_SUFFIX=PROPERTIES_SUFFIX, PROPERTIES_SUFFIX=PROPERTIES_SUFFIX,
REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(), REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(),
DISK_DEVICE_TYPES=ractl_cmd.get_disk_device_types(), DISK_DEVICE_TYPES=ractl_cmd.get_disk_device_types(),
@ -945,33 +943,38 @@ def copy():
return redirect(url_for("index")) return redirect(url_for("index"))
@APP.route("/files/unzip", methods=["POST"]) @APP.route("/files/extract_image", methods=["POST"])
@login_required @login_required
def unzip(): def extract_image():
""" """
Unzips all files in a specified zip archive, or a single file in the zip archive Extracts all or a subset of files in the specified archive
""" """
zip_file = request.form.get("zip_file") archive_file = request.form.get("archive_file")
zip_member = request.form.get("zip_member") or False archive_members_raw = request.form.get("archive_members") or None
zip_members = request.form.get("zip_members") or False archive_members = archive_members_raw.split("|") if archive_members_raw else None
if zip_members: extract_result = file_cmd.extract_image(
zip_members = literal_eval(zip_members) archive_file,
archive_members
)
process = file_cmd.unzip_file(zip_file, zip_member, zip_members) if extract_result["return_code"] == ReturnCodes.EXTRACTIMAGE_SUCCESS:
if process["status"]: flash(ReturnCodeMapper.add_msg(extract_result).get("msg"))
if not process["msg"]:
flash(_("Aborted unzip: File(s) with the same name already exists."), "error") for properties_file in extract_result["properties_files_moved"]:
return redirect(url_for("index")) if properties_file["status"]:
flash(_("Unzipped the following files:")) flash(_("Properties file %(file)s moved to %(directory)s",
for msg in process["msg"]: file=properties_file['name'],
flash(msg) directory=CFG_DIR
if process["prop_flag"]: ))
flash(_("Properties file(s) have been moved to %(directory)s", directory=CFG_DIR)) else:
return redirect(url_for("index")) flash(_("Failed to move properties file %(file)s to %(directory)s",
file=properties_file['name'],
directory=CFG_DIR
), "error")
else:
flash(ReturnCodeMapper.add_msg(extract_result).get("msg"), "error")
flash(_("Failed to unzip %(zip_file)s", zip_file=zip_file), "error")
flash(process["msg"], "error")
return redirect(url_for("index")) return redirect(url_for("index"))

View File

@ -25,6 +25,11 @@ if ! command -v unzip &> /dev/null ; then
echo "Run 'sudo apt install unzip' to fix." echo "Run 'sudo apt install unzip' to fix."
ERROR=1 ERROR=1
fi fi
if ! command -v unar &> /dev/null ; then
echo "unar could not be found"
echo "Run 'sudo apt install unar' to fix."
ERROR=1
fi
if [ $ERROR = 1 ] ; then if [ $ERROR = 1 ] ; then
echo echo
echo "Fix errors and re-run ./start.sh" echo "Fix errors and re-run ./start.sh"

View File

@ -64,6 +64,7 @@ RASCTL = rasctl
RASDUMP = rasdump RASDUMP = rasdump
SASIDUMP = sasidump SASIDUMP = sasidump
SCSIMON = scsimon SCSIMON = scsimon
RASCSI_TEST = rascsi_test
SYSTEMD_CONF = /etc/systemd/system/rascsi.service SYSTEMD_CONF = /etc/systemd/system/rascsi.service
RSYSLOG_CONF = /etc/rsyslog.d/rascsi.conf RSYSLOG_CONF = /etc/rsyslog.d/rascsi.conf
@ -90,21 +91,22 @@ SRC_PROTOC = \
SRC_PROTOBUF = \ SRC_PROTOBUF = \
rascsi_interface.pb.cpp rascsi_interface.pb.cpp
SRC_RASCSI = \ SRC_RASCSI_CORE = scsi.cpp \
rascsi.cpp \
scsi.cpp \
gpiobus.cpp \ gpiobus.cpp \
filepath.cpp \ filepath.cpp \
fileio.cpp\ fileio.cpp \
rascsi_version.cpp \ rascsi_version.cpp \
rascsi_image.cpp \ rascsi_image.cpp \
rascsi_response.cpp \ rascsi_response.cpp \
rasutil.cpp \ rasutil.cpp \
protobuf_util.cpp \ protobuf_util.cpp \
localizer.cpp localizer.cpp
SRC_RASCSI += $(shell find ./controllers -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./controllers -name '*.cpp')
SRC_RASCSI += $(shell find ./devices -name '*.cpp') SRC_RASCSI_CORE += $(shell find ./devices -name '*.cpp')
SRC_RASCSI += $(SRC_PROTOBUF) SRC_RASCSI_CORE += $(SRC_PROTOBUF)
SRC_RASCSI = rascsi.cpp
SRC_RASCSI += $(SRC_RASCSI_CORE)
SRC_SCSIMON = \ SRC_SCSIMON = \
scsimon.cpp \ scsimon.cpp \
@ -128,7 +130,7 @@ SRC_RASDUMP = \
scsi.cpp \ scsi.cpp \
gpiobus.cpp \ gpiobus.cpp \
filepath.cpp \ filepath.cpp \
fileio.cpp\ fileio.cpp \
rascsi_version.cpp rascsi_version.cpp
SRC_SASIDUMP = \ SRC_SASIDUMP = \
@ -136,11 +138,16 @@ SRC_SASIDUMP = \
scsi.cpp \ scsi.cpp \
gpiobus.cpp \ gpiobus.cpp \
filepath.cpp \ filepath.cpp \
fileio.cpp\ fileio.cpp \
rascsi_version.cpp rascsi_version.cpp
SRC_RASCSI_TEST = $(shell find ./test -name '*.cpp')
SRC_RASCSI_TEST += $(SRC_RASCSI_CORE)
vpath %.h ./ ./controllers ./devices ./monitor vpath %.h ./ ./controllers ./devices ./monitor
vpath %.cpp ./ ./controllers ./devices ./monitor vpath %.cpp ./ ./controllers ./devices ./monitor ./test
vpath %.o ./$(OBJDIR) vpath %.o ./$(OBJDIR)
vpath ./$(BINDIR) vpath ./$(BINDIR)
@ -150,13 +157,14 @@ OBJ_RASCTL := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCTL:%.cpp=%.o)))
OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o))) OBJ_RASDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASDUMP:%.cpp=%.o)))
OBJ_SASIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SASIDUMP:%.cpp=%.o))) OBJ_SASIDUMP := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SASIDUMP:%.cpp=%.o)))
OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o))) OBJ_SCSIMON := $(addprefix $(OBJDIR)/,$(notdir $(SRC_SCSIMON:%.cpp=%.o)))
OBJ_RASCSI_TEST := $(addprefix $(OBJDIR)/,$(notdir $(SRC_RASCSI_TEST:%.cpp=%.o)))
GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h GEN_PROTOBUF := $(SRC_PROTOBUF) rascsi_interface.pb.h
# The following will include all of the auto-generated dependency files (*.d) # 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 # 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)) ALL_DEPS := $(patsubst %.o,%.d,$(OBJ_RASCSI) $(OBJ_RASCTL) $(OBJ_SCSIMON) $(OBJ_RASCSI_TEST))
-include $(ALL_DEPS) -include $(ALL_DEPS)
$(OBJDIR) $(BINDIR): $(OBJDIR) $(BINDIR):
@ -180,6 +188,9 @@ $(SRC_PROTOBUF): $(SRC_PROTOC)
all: $(BIN_ALL) docs all: $(BIN_ALL) docs
ALL: all ALL: all
test: $(BINDIR)/$(RASCSI_TEST)
$(BINDIR)/$(RASCSI_TEST)
docs: $(DOC_DIR)/rascsi_man_page.txt $(DOC_DIR)/rasctl_man_page.txt $(DOC_DIR)/scsimon_man_page.txt 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) $(BINDIR)/$(RASCSI): $(SRC_PROTOBUF) $(OBJ_RASCSI) | $(BINDIR)
@ -197,6 +208,9 @@ $(BINDIR)/$(SASIDUMP): $(OBJ_SASIDUMP) | $(BINDIR)
$(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR) $(BINDIR)/$(SCSIMON): $(OBJ_SCSIMON) | $(BINDIR)
$(CXX) $(CXXFLAGS) -o $@ $(OBJ_SCSIMON) -lpthread $(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
# Phony rules for building individual utilities # Phony rules for building individual utilities
.PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SASIDUMP) $(SCSIMON) .PHONY: $(RASCSI) $(RASCTL) $(RASDUMP) $(SASIDUMP) $(SCSIMON)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet
@ -167,7 +167,7 @@ public:
const string GetPaddedName() const; const string GetPaddedName() const;
bool SupportsParams() const { return supports_params; } bool SupportsParams() const { return supports_params; }
bool SupportsFile() const { return !supports_params; } virtual bool SupportsFile() const { return !supports_params; }
void SupportsParams(bool supports_paams) { this->supports_params = supports_paams; } void SupportsParams(bool supports_paams) { this->supports_params = supports_paams; }
const unordered_map<string, string> GetParams() const { return params; } const unordered_map<string, string> GetParams() const { return params; }
const string GetParam(const string&); const string GetParam(const string&);

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet
@ -26,7 +26,6 @@ class Device;
class DeviceFactory class DeviceFactory
{ {
private:
DeviceFactory(); DeviceFactory();
~DeviceFactory() {} ~DeviceFactory() {}

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet
@ -32,12 +32,17 @@ public:
int ModeSense6(const DWORD *, BYTE *); int ModeSense6(const DWORD *, BYTE *);
int ModeSense10(const DWORD *, BYTE *, int); int ModeSense10(const DWORD *, BYTE *, int);
bool SupportsFile() const override { return false; }
protected:
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
private: private:
typedef ModePageDevice super; typedef ModePageDevice super;
Dispatcher<HostServices, SCSIDEV> dispatcher; Dispatcher<HostServices, SCSIDEV> dispatcher;
void AddModePages(map<int, vector<BYTE>>&, int, bool) const override;
void AddRealtimeClockPage(map<int, vector<BYTE>>&, bool) const; void AddRealtimeClockPage(map<int, vector<BYTE>>&, bool) const;
}; };

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet
@ -50,43 +50,41 @@ int ModePageDevice::AddModePages(const DWORD *cdb, BYTE *buf, int max_length)
return 0; return 0;
} }
int size = 0; // Holds all mode page data
vector<BYTE> result;
vector<BYTE> page0; vector<BYTE> page0;
for (auto const& page : pages) { for (auto const& page : pages) {
if (size + (int)page.second.size() > max_length) { // The specification mandates that page 0 must be returned after all others
LOGWARN("Mode page data size exceeds reserved buffer size"); if (page.first) {
size_t offset = result.size();
page0.clear(); // Page data
result.insert(result.end(), page.second.begin(), page.second.end());
break; // Page code, PS bit may already have been set
result[offset] |= page.first;
// Page payload size
result[offset + 1] = page.second.size() - 2;
} }
else { else {
// The specification mandates that page 0 must be returned after all others page0 = page.second;
if (page.first) {
// Page data
memcpy(&buf[size], page.second.data(), page.second.size());
// Page code, PS bit may already have been set
buf[size] |= page.first;
// Page payload size
buf[size + 1] = page.second.size() - 2;
size += page.second.size();
}
else {
page0 = page.second;
}
} }
} }
// Page 0 must be last // Page 0 must be last
if (!page0.empty()) { if (!page0.empty()) {
memcpy(&buf[size], page0.data(), page0.size()); size_t offset = result.size();
// Page data
result.insert(result.end(), page0.begin(), page0.end());
// Page payload size // Page payload size
buf[size + 1] = page0.size() - 2; result[offset + 1] = page0.size() - 2;
size += page0.size();
} }
// Do not return more than the requested number of bytes
size_t size = (size_t)max_length < result.size() ? max_length : result.size();
memcpy(buf, result.data(), size);
return size; return size;
} }

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet
@ -257,9 +257,9 @@ bool SCSIPrinter::WriteBytes(BYTE *buf, uint32_t length)
LOGTRACE("Appending %d byte(s) to printer output file", length); LOGTRACE("Appending %d byte(s) to printer output file", length);
write(fd, buf, length); uint32_t num_written = write(fd, buf, length);
return true; return (num_written == length);
} }
bool SCSIPrinter::CheckReservation(SCSIDEV *controller) bool SCSIPrinter::CheckReservation(SCSIDEV *controller)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2022 Uwe Seimet // Copyright (C) 2022 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp) // Copyright (C) 2001-2006 (ytanaka@ipc-tokai.or.jp)

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
// //
// Copyright (C) 2020-2021 akuker // Copyright (C) 2020-2021 akuker
// //

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2020-2021 akuker // Copyright (C) 2020-2021 akuker

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
// //
// Copyright (C) 2020-2021 akuker // Copyright (C) 2020-2021 akuker
// //

View File

@ -1,6 +1,7 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) for Raspberry Pi // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
// //
// Copyright (C) 2020-2021 akuker // Copyright (C) 2020-2021 akuker
// //

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker
@ -13,8 +13,8 @@
// The following should be updated for each release // The following should be updated for each release
const int rascsi_major_version = 22; // Last two digits of year const int rascsi_major_version = 22; // Last two digits of year
const int rascsi_minor_version = 07; // Month const int rascsi_minor_version = 8; // Month
const int rascsi_patch_version = 2; // Patch number - increment for each update const int rascsi_patch_version = 1; // Patch number - increment for each update
static char rascsi_version_string[30]; // Allow for string up to "XX.XX.XXX" + null character + "development build" static char rascsi_version_string[30]; // Allow for string up to "XX.XX.XXX" + null character + "development build"

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2020 akuker // Copyright (C) 2020 akuker

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Copyright (C) 2021 Uwe Seimet // Copyright (C) 2021 Uwe Seimet

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -1,6 +1,6 @@
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// //
// SCSI Target Emulator RaSCSI (*^..^*) // SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi // for Raspberry Pi
// //
// Powered by XM6 TypeG Technology. // Powered by XM6 TypeG Technology.

View File

@ -0,0 +1,286 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Unit tests based on GoogleTest and GoogleMock
//
//---------------------------------------------------------------------------
#include <gtest/gtest.h>
#include "rascsi_version.h"
#include "../devices/device.h"
#include "../devices/device_factory.h"
using namespace rascsi_interface;
namespace DeviceFactoryTest
{
DeviceFactory& device_factory = DeviceFactory::instance();
TEST(DeviceFactoryTest, GetTypeForFile)
{
EXPECT_EQ(device_factory.GetTypeForFile("test.hdf"), SAHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.hds"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.HDS"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.hda"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.hdn"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.hdi"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.nhd"), SCHD);
EXPECT_EQ(device_factory.GetTypeForFile("test.hdr"), SCRM);
EXPECT_EQ(device_factory.GetTypeForFile("test.mos"), SCMO);
EXPECT_EQ(device_factory.GetTypeForFile("test.iso"), SCCD);
EXPECT_EQ(device_factory.GetTypeForFile("test.suffix.iso"), SCCD);
EXPECT_EQ(device_factory.GetTypeForFile("bridge"), SCBR);
EXPECT_EQ(device_factory.GetTypeForFile("daynaport"), SCDP);
EXPECT_EQ(device_factory.GetTypeForFile("printer"), SCLP);
EXPECT_EQ(device_factory.GetTypeForFile("services"), SCHS);
EXPECT_EQ(device_factory.GetTypeForFile("unknown"), UNDEFINED);
EXPECT_EQ(device_factory.GetTypeForFile("test.iso.suffix"), UNDEFINED);
}
TEST(DeviceFactoryTest, GetSectorSizes)
{
unordered_set<uint32_t> sector_sizes;
sector_sizes = device_factory.GetSectorSizes("SCHD");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCHD);
EXPECT_EQ(4, sector_sizes.size());
EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCRM");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCRM);
EXPECT_EQ(4, sector_sizes.size());
EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCMO");
EXPECT_EQ(4, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCMO);
EXPECT_EQ(4, sector_sizes.size());
EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(1024) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(4096) != sector_sizes.end());
sector_sizes = device_factory.GetSectorSizes("SCCD");
EXPECT_EQ(2, sector_sizes.size());
sector_sizes = device_factory.GetSectorSizes(SCCD);
EXPECT_EQ(2, sector_sizes.size());
EXPECT_TRUE(sector_sizes.find(512) != sector_sizes.end());
EXPECT_TRUE(sector_sizes.find(2048) != sector_sizes.end());
}
TEST(DeviceFactoryTest, UnknownDeviceType)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "test");
EXPECT_EQ(nullptr, device);
}
TEST(DeviceFactoryTest, SCHD_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "test.hda");
EXPECT_NE(nullptr, device);
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_FALSE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_FALSE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("QUANTUM", device->GetVendor()) << "Invalid default vendor for Apple drive";
EXPECT_EQ("FIREBALL", device->GetProduct()) << "Invalid default vendor for Apple drive";
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCRM_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "test.hdr");
EXPECT_NE(nullptr, device);
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI HD (REM.)", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCMO_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "test.mos");
EXPECT_NE(nullptr, device);
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_TRUE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI MO", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCCD_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "test.iso");
EXPECT_NE(nullptr, device);
EXPECT_TRUE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_TRUE(device->IsReadOnly());
EXPECT_TRUE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_TRUE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_TRUE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI CD-ROM", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCBR_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "bridge");
EXPECT_NE(nullptr, device);
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_FALSE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_FALSE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_FALSE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI HOST BRIDGE", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCDP_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "daynaport");
EXPECT_NE(nullptr, device);
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_FALSE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_FALSE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_FALSE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("Dayna", device->GetVendor());
EXPECT_EQ("SCSI/Link", device->GetProduct());
EXPECT_EQ("1.4a", device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCHS_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "services");
EXPECT_NE(nullptr, device);
EXPECT_FALSE(device->SupportsFile());
EXPECT_FALSE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_FALSE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_FALSE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_FALSE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("Host Services", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
TEST(DeviceFactoryTest, SCLP_Device_Defaults)
{
Device *device = device_factory.CreateDevice(UNDEFINED, "printer");
EXPECT_NE(nullptr, device);
EXPECT_FALSE(device->SupportsFile());
EXPECT_TRUE(device->SupportsParams());
EXPECT_FALSE(device->IsProtectable());
EXPECT_FALSE(device->IsProtected());
EXPECT_FALSE(device->IsReadOnly());
EXPECT_FALSE(device->IsRemovable());
EXPECT_FALSE(device->IsRemoved());
EXPECT_FALSE(device->IsLockable());
EXPECT_FALSE(device->IsLocked());
EXPECT_FALSE(device->IsStoppable());
EXPECT_FALSE(device->IsStopped());
EXPECT_EQ("RaSCSI", device->GetVendor());
EXPECT_EQ("SCSI PRINTER", device->GetProduct());
EXPECT_EQ(string(rascsi_get_version_string()).substr(0, 2) + string(rascsi_get_version_string()).substr(3, 2),
device->GetRevision());
EXPECT_EQ(32, device->GetSupportedLuns());
}
}

View File

@ -0,0 +1,72 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Unit tests based on GoogleTest and GoogleMock
//
//---------------------------------------------------------------------------
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "gpiobus.h"
#include "../devices/scsihd.h"
#include "../devices/device_factory.h"
using namespace rascsi_interface;
namespace DeviceTest
{
class MockController : public SCSIDEV
{
MOCK_METHOD(BUS::phase_t, Process, (int), (override));
MOCK_METHOD(int, GetEffectiveLun, (), ());
MOCK_METHOD(void, Error, (scsi_defs::sense_key, scsi_defs::asc, scsi_defs::status), (override));
MOCK_METHOD(int, GetInitiatorId, (), ());
MOCK_METHOD(void, SetUnit, (int), ());
MOCK_METHOD(void, Connect, (int, BUS *), ());
MOCK_METHOD(void, Status, (), ());
MOCK_METHOD(void, DataIn, (), ());
MOCK_METHOD(void, DataOut, (), ());
MOCK_METHOD(void, BusFree, (), ());
MOCK_METHOD(void, Selection, (), ());
MOCK_METHOD(void, Command, (), ());
MOCK_METHOD(void, MsgIn, (), ());
MOCK_METHOD(void, MsgOut, (), ());
MOCK_METHOD(void, Send, (), (override));
MOCK_METHOD(bool, XferMsg, (int), ());
MOCK_METHOD(bool, XferIn, (BYTE *), ());
MOCK_METHOD(bool, XferOut, (bool), (override));
MOCK_METHOD(void, ReceiveBytes, (), ());
MOCK_METHOD(void, Execute, (), (override));
MOCK_METHOD(void, FlushUnit, (), ());
MOCK_METHOD(void, Receive, (), (override));
MOCK_METHOD(bool, HasUnit, (), (const override));
FRIEND_TEST(DeviceTest, TestUnitReady);
MockController() { }
~MockController() { }
};
DeviceFactory& device_factory = DeviceFactory::instance();
TEST(DeviceTest, TestUnitReady)
{
MockController controller;
Device *device = device_factory.CreateDevice(SCHD, "test");
controller.ctrl.cmd[0] = eCmdTestUnitReady;
device->SetReady(false);
EXPECT_CALL(controller, Error);
EXPECT_TRUE(device->Dispatch(&controller));
// TODO Add tests for a device that is ready after the SASI code removal
}
}

View File

@ -0,0 +1,184 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2022 Uwe Seimet
//
// Unit tests based on GoogleTest and GoogleMock
//
//---------------------------------------------------------------------------
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "../devices/scsihd.h"
#include "../devices/scsihd_nec.h"
#include "../devices/scsicd.h"
#include "../devices/scsimo.h"
#include "../devices/host_services.h"
using namespace std;
namespace ModePagesTest
{
unordered_set<uint32_t> sector_sizes;
class MockModePageDevice : public ModePageDevice
{
public:
MockModePageDevice() : ModePageDevice("test") { }
~MockModePageDevice() { }
MOCK_METHOD(vector<BYTE>, Inquiry, (), (const, override));
MOCK_METHOD(int, ModeSense6, (const DWORD *, BYTE *), (override));
MOCK_METHOD(int, ModeSense10, (const DWORD *, BYTE *, int), (override));
void AddModePages(map<int, vector<BYTE>>& pages, int page, bool) const override {
// Return dummy data for other pages than page 0
if (page) {
vector<BYTE> buf(255);
pages[page] = buf;
}
}
// Make protected methods visible for testing
// TODO Why does FRIEND_TEST not work for this method?
int AddModePages(const DWORD *cdb, BYTE *buf, int max_length) {
return ModePageDevice::AddModePages(cdb, buf, max_length);
}
};
class MockSCSIHD : public SCSIHD
{
FRIEND_TEST(ModePagesTest, SCSIHD_AddModePages);
MockSCSIHD(const unordered_set<uint32_t>& sector_sizes) : SCSIHD(sector_sizes, false) { };
~MockSCSIHD() { };
};
class MockSCSIHD_NEC : public SCSIHD_NEC
{
FRIEND_TEST(ModePagesTest, SCSIHD_NEC_AddModePages);
MockSCSIHD_NEC(const unordered_set<uint32_t>& sector_sizes) : SCSIHD_NEC(sector_sizes) { };
~MockSCSIHD_NEC() { };
};
class MockSCSICD : public SCSICD
{
FRIEND_TEST(ModePagesTest, SCSICD_AddModePages);
MockSCSICD(const unordered_set<uint32_t>& sector_sizes) : SCSICD(sector_sizes) { };
~MockSCSICD() { };
};
class MockSCSIMO : public SCSIMO
{
FRIEND_TEST(ModePagesTest, SCSIMO_AddModePages);
MockSCSIMO(const unordered_set<uint32_t>& sector_sizes, const unordered_map<uint64_t, Geometry>& geometries)
: SCSIMO(sector_sizes, geometries) { };
~MockSCSIMO() { };
};
class MockHostServices : public HostServices
{
FRIEND_TEST(ModePagesTest, HostServices_AddModePages);
MockHostServices() { };
~MockHostServices() { };
};
TEST(ModePagesTest, ModePageDevice_AddModePages)
{
DWORD cdb[6];
BYTE buf[512];
MockModePageDevice device;
cdb[2] = 0x3f;
EXPECT_EQ(1, device.AddModePages(cdb, buf, 1)) << "Allocation length was not limited";
cdb[2] = 0x00;
EXPECT_EQ(0, device.AddModePages(cdb, buf, 12)) << "Data for non-existing code page 0 were returned";
}
TEST(ModePagesTest, SCSIHD_AddModePages)
{
map<int, vector<BYTE>> mode_pages;
MockSCSIHD device(sector_sizes);
device.AddModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of code pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(30, mode_pages[48].size());
}
TEST(ModePagesTest, SCSIHD_NEC_AddModePages)
{
map<int, vector<BYTE>> mode_pages;
MockSCSIHD_NEC device(sector_sizes);
device.AddModePages(mode_pages, 0x3f, false);
EXPECT_EQ(5, mode_pages.size()) << "Unexpected number of code pages";
EXPECT_EQ(8, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(20, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(30, mode_pages[48].size());
}
TEST(ModePagesTest, SCSICD_AddModePages)
{
map<int, vector<BYTE>> mode_pages;
MockSCSICD device(sector_sizes);
device.AddModePages(mode_pages, 0x3f, false);
EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of code pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(8, mode_pages[13].size());
EXPECT_EQ(16, mode_pages[14].size());
}
TEST(ModePagesTest, SCSIMO_AddModePages)
{
map<int, vector<BYTE>> mode_pages;
unordered_map<uint64_t, Geometry> geometries;
MockSCSIMO device(sector_sizes, geometries);
device.AddModePages(mode_pages, 0x3f, false);
EXPECT_EQ(6, mode_pages.size()) << "Unexpected number of code pages";
EXPECT_EQ(12, mode_pages[1].size());
EXPECT_EQ(24, mode_pages[3].size());
EXPECT_EQ(24, mode_pages[4].size());
EXPECT_EQ(4, mode_pages[6].size());
EXPECT_EQ(12, mode_pages[8].size());
EXPECT_EQ(12, mode_pages[32].size());
}
TEST(ModePagesTest, HostServices_AddModePages)
{
map<int, vector<BYTE>> mode_pages;
MockHostServices device;
device.AddModePages(mode_pages, 0x3f, false);
EXPECT_EQ(1, mode_pages.size()) << "Unexpected number of code pages";
EXPECT_EQ(10, mode_pages[32].size());
}
}