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 *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
- 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.
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>
# Github Sponsors
Thank you to all of the Github sponsors who support the development community!
# GitHub Sponsors
Thank you to all of the GitHub sponsors who support the development community!
Special thank you to the Gold level sponsors!
- <a href="https://github.com/mikelord68">@mikelord68</a>

View File

@ -13,7 +13,7 @@ logo="""
 ~ (|_____|) ~\n
( : ║ .  __ ║ : )\n
 ~ .╚╦═════╦╝. ~\n
  (  ¯¯¯¯¯¯¯  ) RaSCSI Assistant\n
  (  ¯¯¯¯¯¯¯  ) RaSCSI Reloaded Assistant\n
   '~ .~~~. ~'\n
       '~'\n
"""
@ -55,6 +55,8 @@ OLED_INSTALL_PATH="$BASE/python/oled"
CTRLBOARD_INSTALL_PATH="$BASE/python/ctrlboard"
PYTHON_COMMON_PATH="$BASE/python/common"
SYSTEMD_PATH="/etc/systemd/system"
SSL_CERTS_PATH="/etc/ssl/certs"
SSL_KEYS_PATH="/etc/ssl/private"
HFS_FORMAT=/usr/bin/hformat
HFDISK_BIN=/usr/bin/hfdisk
LIDO_DRIVER=$BASE/lido-driver.img
@ -80,7 +82,7 @@ function sudoCheck() {
# install all dependency packages for RaSCSI Service
function installPackages() {
sudo apt-get update && sudo apt-get install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv python3-dev python3-pip nginx libpcap-dev protobuf-compiler bridge-utils libev-dev libevdev2 -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
@ -88,7 +90,7 @@ function cachePipPackages(){
pushd $WEB_INSTALL_PATH
# Refresh the sudo authentication, which shouldn't trigger another password prompt
sudo -v
sudo pip install -r ./requirements.txt
sudo pip3 install -r ./requirements.txt
popd
}
@ -147,6 +149,21 @@ function installRaScsiWebInterface() {
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
}
@ -753,7 +770,7 @@ function setupWirelessNetworking() {
# Downloads, compiles, and installs Netatalk (AppleShare server)
function installNetatalk() {
NETATALK_VERSION="2-220702"
NETATALK_VERSION="2-220801"
AFP_SHARE_PATH="$HOME/afpshare"
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: "
read -r CHOICE
if [ $CHOICE -ge "1024" ] && [ $CHOICE -le "65535" ]; then
if [[ $CHOICE -ge "1024" ]] && [[ $CHOICE -le "65535" ]]; then
PORT=$CHOICE
else
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
MACPROXY_VER="21.12.3"
MACPROXY_VER="22.8"
MACPROXY_PATH="$HOME/macproxy-$MACPROXY_VER"
if [ -d "$MACPROXY_PATH" ]; then
echo "The $MACPROXY_PATH directory already exists. Delete it to proceed with the installation."
exit 1
echo "The $MACPROXY_PATH directory already exists. Deleting before downloading again..."
sudo rm -rf "$MACPROXY_PATH"
fi
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"
stopMacproxy
sudo cp "$MACPROXY_PATH/macproxy.service" "$SYSTEMD_PATH"
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 enable macproxy
startMacproxy
@ -1048,12 +1064,13 @@ function runChoice() {
echo "- Modify user groups and permissions"
echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man"
echo "- Create a self-signed certificate in /etc/ssl"
sudoCheck
createImagesDir
createCfgDir
configureTokenAuth
stopOldWebInterface
updateRaScsiGit
createImagesDir
createCfgDir
installPackages
stopRaScsiScreen
stopRaScsi
@ -1091,9 +1108,10 @@ function runChoice() {
echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man"
sudoCheck
createImagesDir
createCfgDir
configureTokenAuth
updateRaScsiGit
createImagesDir
installPackages
stopRaScsiScreen
stopRaScsi
@ -1168,6 +1186,7 @@ function runChoice() {
echo "- Install additional packages with apt-get"
echo "- Add and modify systemd services"
sudoCheck
stopMacproxy
installMacproxy
echo "Installing Web Proxy Server - Complete!"
;;
@ -1179,8 +1198,8 @@ function runChoice() {
echo "- Install binaries to /usr/local/bin"
echo "- Install manpages to /usr/local/man"
sudoCheck
updateRaScsiGit
createImagesDir
updateRaScsiGit
installPackages
stopRaScsi
compileRaScsi
@ -1196,9 +1215,10 @@ function runChoice() {
echo "- Modify and enable Apache2 and Nginx web service"
echo "- Create directories and change permissions"
echo "- Modify user groups and permissions"
echo "- Create a self-signed certificate in /etc/ssl"
sudoCheck
updateRaScsiGit
createCfgDir
updateRaScsiGit
installPackages
preparePythonCommon
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.
The following paragraphs in this README contain instructions that are shared
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
and keep the code readable and maintainable.
@ -29,4 +38,4 @@ pylint web/src/web.py
pylint common/src
pylint web/src
pylint oled/src
```
```

View File

@ -12,6 +12,15 @@ CONFIG_FILE_SUFFIX = "json"
# File ending used for drive properties files
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.
# Initialize with a list of 8 empty strings.
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 logging
import asyncio
from functools import lru_cache
from pathlib import PurePath
from zipfile import ZipFile, is_zipfile
from re import escape, findall
from time import time
from subprocess import run, CalledProcessError
from json import dump, load
@ -16,10 +16,11 @@ from shutil import copyfile
import requests
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.return_codes import ReturnCodes
from rascsi.socket_cmds import SocketCmds
from util import unarchiver
class FileCmds:
@ -97,19 +98,31 @@ class FileCmds:
prop = process["conf"]
else:
prop = False
if file.name.lower().endswith(".zip"):
zip_path = f"{server_info['image_dir']}/{file.name}"
if is_zipfile(zip_path):
zipfile = ZipFile(zip_path)
# Get a list of (str) containing all zipfile members
zip_members = zipfile.namelist()
# Strip out directories from the list
zip_members = [x for x in zip_members if not x.endswith("/")]
else:
logging.warning("%s is an invalid zip file", zip_path)
zip_members = False
else:
zip_members = False
archive_contents = []
if PurePath(file.name).suffix.lower()[1:] in ARCHIVE_FILE_SUFFIXES:
try:
archive_info = self._get_archive_info(
f"{server_info['image_dir']}/{file.name}",
_cache_extra_key=file.size
)
properties_files = [x["path"]
for x in archive_info["members"]
if x["path"].endswith(PROPERTIES_SUFFIX)]
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)
dtype = proto.PbDeviceType.Name(file.type)
@ -119,7 +132,7 @@ class FileCmds:
"size_mb": size_mb,
"detected_type": dtype,
"prop": prop,
"zip_members": zip_members,
"archive_contents": archive_contents,
})
return {"status": result.status, "msg": result.msg, "files": files}
@ -266,62 +279,73 @@ class FileCmds:
"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
file_name is the name of the zip file to unzip
member is the full path to the particular file in the zip file to unzip
members contains all of the full paths to each of the zip archive members
Returns (dict) with (boolean) status and (list of str) msg
Takes (str) file_path, (list) members, optional (bool) move_properties_files_to_config
file_name is the path of the archive file to extract, relative to the images directory
members is a list of file paths in the archive file to extract
move_properties_files_to_config controls if .properties files are auto-moved to CFG_DIR
Returns (dict) result
"""
server_info = self.ractl.get_server_info()
prop_flag = False
if not member:
unzip_proc = asyncio.run(self.run_async("unzip", [
"-d",
server_info['image_dir'],
"-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 not members:
return {
"status": False,
"return_code": ReturnCodes.EXTRACTIMAGE_NO_FILES_SPECIFIED,
}
if unzip_prop["returncode"] == 0:
prop_flag = True
if unzip_proc["returncode"] != 0:
logging.warning("Unzipping failed: %s", unzip_proc["stderr"])
return {"status": False, "msg": unzip_proc["stderr"]}
try:
extract_result = unarchiver.extract_archive(
f"{server_info['image_dir']}/{file_path}",
members=members,
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):
"""
@ -652,3 +676,14 @@ class FileCmds:
logging.info("stderr: %s", 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_COULD_NOT_READ = 71
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.
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
```
@ -40,7 +42,7 @@ $ git push pi master
## 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.
@ -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
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.
@ -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.
```
$ pybabel init -i messages.pot -d translations -l xx
$ pybabel init -i messages.pot -d src/translations -l xx
```
### Update an existing loclization
@ -114,6 +116,10 @@ msgstr ""
"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
Install the gettext package and use msgfmt to see the translation progress.
```

View File

@ -3,6 +3,16 @@
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 / {
proxy_pass http://127.0.0.1:8080;

View File

@ -430,5 +430,17 @@
"file_type": null,
"description": "Emulates Apple CD ROM drive for use with Macintosh computers.",
"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"""
MESSAGES = {
ReturnCodes.DELETEFILE_SUCCESS: _("File deleted: %(file_path)s"),
ReturnCodes.DELETEFILE_FILE_NOT_FOUND: _("File to delete not found: %(file_path)s"),
ReturnCodes.RENAMEFILE_SUCCESS: _("File moved to: %(target_path)s"),
ReturnCodes.RENAMEFILE_UNABLE_TO_MOVE: _("Unable to move file to: %(target_path)s"),
ReturnCodes.DOWNLOADFILETOISO_SUCCESS: _("Created CD-ROM ISO image with "
"arguments \"%(value)s\""),
ReturnCodes.DOWNLOADTODIR_SUCCESS: _("%(file_name)s downloaded to %(save_dir)s"),
ReturnCodes.WRITEFILE_SUCCESS: _("File created: %(target_path)s"),
ReturnCodes.WRITEFILE_COULD_NOT_WRITE: _("Could not create file: %(target_path)s"),
ReturnCodes.READCONFIG_SUCCESS: _("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.DELETEFILE_SUCCESS:
_("File deleted: %(file_path)s"),
ReturnCodes.DELETEFILE_FILE_NOT_FOUND:
_("File to delete not found: %(file_path)s"),
ReturnCodes.RENAMEFILE_SUCCESS:
_("File moved to: %(target_path)s"),
ReturnCodes.RENAMEFILE_UNABLE_TO_MOVE:
_("Unable to move file to: %(target_path)s"),
ReturnCodes.DOWNLOADFILETOISO_SUCCESS:
_("Created CD-ROM ISO image with arguments \"%(value)s\""),
ReturnCodes.DOWNLOADTODIR_SUCCESS:
_("%(file_name)s downloaded to %(save_dir)s"),
ReturnCodes.WRITEFILE_SUCCESS:
_("File created: %(target_path)s"),
ReturnCodes.WRITEFILE_COULD_NOT_WRITE:
_("Could not create file: %(target_path)s"),
ReturnCodes.READCONFIG_SUCCESS:
_("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
def add_msg(payload):
@ -36,10 +54,14 @@ class ReturnCodeMapper:
if "return_code" not in 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"]],
**parameters,
)
else:
payload["msg"] = lazy_gettext(ReturnCodeMapper.MESSAGES[payload["return_code"]])
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
ARCHIVE_FILE_SUFFIX = "zip"
# The file name of the default config file that loads when rascsi-web starts
DEFAULT_CONFIG = f"default.{rascsi.common_settings.CONFIG_FILE_SUFFIX}"
# File containing canonical drive properties

View File

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

View File

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

View File

@ -185,41 +185,41 @@
</ul>
</details>
</td>
{% elif file["zip_members"] %}
{% elif file["archive_contents"] %}
<td>
<details>
<summary>
{{ file["name"] }}
</summary>
<ul style="list-style: none;">
{% for member in file["zip_members"] %}
{% if not member.lower().endswith(PROPERTIES_SUFFIX) %}
<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;">
{% for member in file["archive_contents"] %}
{% if not member["is_properties_file"] %}
<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>
</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 %}
</li>
{% endif %}
{% endfor %}
{% endfor %}
</ul>
</details>
</td>
@ -238,11 +238,12 @@
{{ _("Attached!") }}
</center>
{% else %}
{% if file["name"].lower().endswith(ARCHIVE_FILE_SUFFIX) %}
<form action="/files/unzip" method="post">
<input name="zip_file" type="hidden" value="{{ file['name'] }}">
<input name="zip_members" type="hidden" value="{{ file['zip_members'] }}">
<input type="submit" value="{{ _("Unzip All") }}" onclick="processNotify('{{ _("Unzipping all files...") }}')">
{% if file["archive_contents"] %}
<form action="/files/extract_image" method="post">
<input name="archive_file" type="hidden" value="{{ file['name'] }}">
{% set pipe = joiner("|") %}
<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>
{% else %}
<form action="/scsi/attach" method="post">
@ -257,7 +258,7 @@
{% endfor %}
</select>
<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" %}
<input name="type" type="hidden" value="{{ file['detected_type'] }}">
{{ file['detected_type_name'] }}
@ -336,7 +337,7 @@
{% for key, value in device_types[type]["params"].items() %}
<label for="{{ key }}">{{ key }}:</label>
{% if value.isnumeric() %}
<input name="{{ key }}" type="number" size="{{ value|length }}" value="{{ value }}">
<input name="{{ key }}" type="number" value="{{ value }}">
{% elif key == "interface" %}
<select name="interface">
{% for if in netinfo["ifs"] %}
@ -358,7 +359,7 @@
{% endfor %}
</select>
<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") }}">
</form>
</td>
@ -415,7 +416,7 @@
gb: "{{ _("GB") }}",
mb: "{{ _("MB") }}",
kb: "{{ _("KB") }}",
b: "{{ _("b") }}"
b: "{{ _("B") }}"
}
}
</script>
@ -571,7 +572,7 @@
</option>
</select>
<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") }}">
</form>
</td>
@ -606,7 +607,7 @@
<td style="border: none; vertical-align:top;">
<form action="/logs/show" method="post">
<label for="lines">{{ _("Log Lines:") }}</label>
<input name="lines" type="number" value="200" min="1" size="4">
<input name="lines" type="number" value="200" min="0" max="99999" step="100">
<label for="scope">{{ _("Scope:") }}</label>
<select name="scope">
<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 functools import wraps
from grp import getgrall
from ast import literal_eval
import bjoern
from rascsi.return_codes import ReturnCodes
from werkzeug.utils import secure_filename
from simplepam import authenticate
from flask_babel import Babel, Locale, refresh, _
@ -37,6 +37,7 @@ from rascsi.common_settings import (
CFG_DIR,
CONFIG_FILE_SUFFIX,
PROPERTIES_SUFFIX,
ARCHIVE_FILE_SUFFIXES,
RESERVATIONS,
)
@ -55,7 +56,6 @@ from web_utils import (
from settings import (
AFP_DIR,
MAX_FILE_SIZE,
ARCHIVE_FILE_SUFFIX,
DEFAULT_CONFIG,
DRIVE_PROPERTIES_FILE,
AUTH_GROUP,
@ -133,14 +133,13 @@ def index():
scsi_ids, recommended_id = get_valid_scsi_ids(devices["device_list"], reserved_scsi_ids)
formatted_devices = sort_and_format_devices(devices["device_list"])
valid_file_suffix = "."+", .".join(
valid_file_suffix = "." + ", .".join(
server_info["sahd"] +
server_info["schd"] +
server_info["scrm"] +
server_info["scmo"] +
server_info["sccd"] +
[ARCHIVE_FILE_SUFFIX]
)
ARCHIVE_FILE_SUFFIXES)
if "username" in session:
username = session["username"]
@ -182,7 +181,6 @@ def index():
mo_file_suffix=tuple(server_info["scmo"]),
username=username,
auth_active=auth_active(AUTH_GROUP)["status"],
ARCHIVE_FILE_SUFFIX=ARCHIVE_FILE_SUFFIX,
PROPERTIES_SUFFIX=PROPERTIES_SUFFIX,
REMOVABLE_DEVICE_TYPES=ractl_cmd.get_removable_device_types(),
DISK_DEVICE_TYPES=ractl_cmd.get_disk_device_types(),
@ -945,33 +943,38 @@ def copy():
return redirect(url_for("index"))
@APP.route("/files/unzip", methods=["POST"])
@APP.route("/files/extract_image", methods=["POST"])
@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")
zip_member = request.form.get("zip_member") or False
zip_members = request.form.get("zip_members") or False
archive_file = request.form.get("archive_file")
archive_members_raw = request.form.get("archive_members") or None
archive_members = archive_members_raw.split("|") if archive_members_raw else None
if zip_members:
zip_members = literal_eval(zip_members)
extract_result = file_cmd.extract_image(
archive_file,
archive_members
)
process = file_cmd.unzip_file(zip_file, zip_member, zip_members)
if process["status"]:
if not process["msg"]:
flash(_("Aborted unzip: File(s) with the same name already exists."), "error")
return redirect(url_for("index"))
flash(_("Unzipped the following files:"))
for msg in process["msg"]:
flash(msg)
if process["prop_flag"]:
flash(_("Properties file(s) have been moved to %(directory)s", directory=CFG_DIR))
return redirect(url_for("index"))
if extract_result["return_code"] == ReturnCodes.EXTRACTIMAGE_SUCCESS:
flash(ReturnCodeMapper.add_msg(extract_result).get("msg"))
for properties_file in extract_result["properties_files_moved"]:
if properties_file["status"]:
flash(_("Properties file %(file)s moved to %(directory)s",
file=properties_file['name'],
directory=CFG_DIR
))
else:
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"))

View File

@ -25,6 +25,11 @@ if ! command -v unzip &> /dev/null ; then
echo "Run 'sudo apt install unzip' to fix."
ERROR=1
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
echo
echo "Fix errors and re-run ./start.sh"

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//
// 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
//
// 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
//
// 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
//
// Powered by XM6 TypeG Technology.

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2021 Uwe Seimet
@ -167,7 +167,7 @@ public:
const string GetPaddedName() const;
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; }
const unordered_map<string, string> GetParams() const { return params; }
const string GetParam(const string&);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//
// 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
//
// Copyright (C) 2020 akuker

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//
// 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
//
// 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);
write(fd, buf, length);
uint32_t num_written = write(fd, buf, length);
return true;
return (num_written == length);
}
bool SCSIPrinter::CheckReservation(SCSIDEV *controller)

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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
//
// 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
//
// Copyright (C) 2021 Uwe Seimet

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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
//

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// Copyright (C) 2020 akuker
@ -13,8 +13,8 @@
// The following should be updated for each release
const int rascsi_major_version = 22; // Last two digits of year
const int rascsi_minor_version = 07; // Month
const int rascsi_patch_version = 2; // Patch number - increment for each update
const int rascsi_minor_version = 8; // Month
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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
//---------------------------------------------------------------------------
//
// SCSI Target Emulator RaSCSI (*^..^*)
// SCSI Target Emulator RaSCSI Reloaded
// for Raspberry Pi
//
// 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());
}
}